Updated on 2026-08-14
This commit is contained in:
commit
7db00540c5
351 changed files with 6478 additions and 1691 deletions
|
|
@ -26,10 +26,10 @@ internal class DevApiConfigsManager(
|
|||
) : MutableApiConfigsManager() {
|
||||
|
||||
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
|
||||
field = MutableStateFlow(value = getInitialConfigs())
|
||||
field = MutableStateFlow(value = getInitialConfigs())
|
||||
|
||||
override val isInitialized: StateFlow<Boolean>
|
||||
field = MutableStateFlow(value = false)
|
||||
field = MutableStateFlow(value = false)
|
||||
|
||||
override fun initialize() {
|
||||
isInitialized.value = false
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ internal class MockApiConfigsManager(
|
|||
) : MutableApiConfigsManager() {
|
||||
|
||||
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
|
||||
field = MutableStateFlow(value = getInitialConfigs())
|
||||
field = MutableStateFlow(value = getInitialConfigs())
|
||||
|
||||
override val isInitialized: StateFlow<Boolean> = MutableStateFlow(value = true)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ abstract class MutableApiConfigsManager : ApiConfigsManager {
|
|||
* These listeners are notified whenever an environment change occurs.
|
||||
*/
|
||||
protected val registerListeners: Set<ApiConfigEnvChangeListener>
|
||||
field = mutableSetOf<ApiConfigEnvChangeListener>()
|
||||
field = mutableSetOf<ApiConfigEnvChangeListener>()
|
||||
|
||||
/** Change api environment [environment] by [id] */
|
||||
abstract suspend fun changeEnvironment(id: String, environment: ApiEnvironment)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import retrofit2.http.Header
|
|||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
private const val TX_HISTORY_PAGING_DEFAULT_LIMIT = 20
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface TangemPayApi {
|
||||
|
||||
|
|
@ -107,6 +109,13 @@ interface TangemPayApi {
|
|||
@Query("offset") offset: Int,
|
||||
): ApiResponse<VisaTxHistoryResponse>
|
||||
|
||||
@GET("v1/customer/transactions")
|
||||
suspend fun getTangemPayTxHistory(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("cursor") cursor: String?,
|
||||
@Query("limit") limit: Int = TX_HISTORY_PAGING_DEFAULT_LIMIT,
|
||||
): ApiResponse<TangemPayTxHistoryResponse>
|
||||
|
||||
@GET("v1/customer/kyc")
|
||||
suspend fun getKycAccess(@Header("Authorization") authHeader: String): ApiResponse<KycAccessInfoResponse>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TangemPayTxHistoryResponse(
|
||||
@Json(name = "error") val error: String?,
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "transactions") val transactions: List<Transaction>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Transaction(
|
||||
@Json(name = "id") val id: String, // UUID (used as cursor for pagination)
|
||||
@Json(name = "type") val type: String, // "SPEND", "COLLATERAL", "PAYMENT", "FEE"
|
||||
@Json(name = "spend") val spend: Spend? = null,
|
||||
@Json(name = "collateral") val collateral: Collateral? = null,
|
||||
@Json(name = "payment") val payment: Payment? = null,
|
||||
@Json(name = "fee") val fee: Fee? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Spend(
|
||||
@Json(name = "amount") val amount: BigDecimal,
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "local_amount") val localAmount: BigDecimal? = null,
|
||||
@Json(name = "local_currency") val localCurrency: String? = null,
|
||||
@Json(name = "authorized_amount") val authorizedAmount: BigDecimal? = null,
|
||||
@Json(name = "authorization_method") val authorizationMethod: String? = null,
|
||||
@Json(name = "memo") val memo: String? = null,
|
||||
@Json(name = "receipt") val receipt: Boolean? = null,
|
||||
@Json(name = "merchant_name") val merchantName: String? = null,
|
||||
@Json(name = "merchant_category") val merchantCategory: String? = null,
|
||||
@Json(name = "merchant_category_code") val merchantCategoryCode: String? = null,
|
||||
@Json(name = "merchant_id") val merchantId: String? = null,
|
||||
@Json(name = "enriched_merchant_icon") val enrichedMerchantIcon: String? = null,
|
||||
@Json(name = "enriched_merchant_name") val enrichedMerchantName: String? = null,
|
||||
@Json(name = "enriched_merchant_category") val enrichedMerchantCategory: String? = null,
|
||||
@Json(name = "card_id") val cardId: String? = null,
|
||||
@Json(name = "card_type") val cardType: String? = null,
|
||||
@Json(name = "status") val status: String? = null,
|
||||
@Json(name = "declined_reason") val declinedReason: String? = null,
|
||||
@Json(name = "authorized_at") val authorizedAt: DateTime? = null,
|
||||
@Json(name = "posted_at") val postedAt: DateTime? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Collateral(
|
||||
@Json(name = "amount") val amount: BigDecimal,
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "memo") val memo: String? = null,
|
||||
@Json(name = "chain_id") val chainId: Long? = null,
|
||||
@Json(name = "wallet_address") val walletAddress: String? = null,
|
||||
@Json(name = "transaction_hash") val transactionHash: String? = null,
|
||||
@Json(name = "posted_at") val postedAt: DateTime? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Payment(
|
||||
@Json(name = "amount") val amount: BigDecimal,
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "memo") val memo: String? = null,
|
||||
@Json(name = "chain_id") val chainId: Long? = null,
|
||||
@Json(name = "wallet_address") val walletAddress: String? = null,
|
||||
@Json(name = "transaction_hash") val transactionHash: String? = null,
|
||||
@Json(name = "status") val status: String? = null,
|
||||
@Json(name = "posted_at") val postedAt: DateTime? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Fee(
|
||||
@Json(name = "amount") val amount: BigDecimal,
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "description") val description: String? = null,
|
||||
@Json(name = "posted_at") val postedAt: DateTime? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -198,6 +198,7 @@ data class YieldDTO(
|
|||
enum class RewardTypeDTO {
|
||||
@Json(name = "apy")
|
||||
APY, // compound rate
|
||||
|
||||
@Json(name = "apr")
|
||||
APR, // simple rate,
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,5 @@ data class SeedPhraseNotificationDTO(val status: Status) {
|
|||
|
||||
@Json(name = "accepted")
|
||||
ACCEPTED,
|
||||
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ package com.tangem.datasource.di
|
|||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.txhistory.DefaultTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -20,4 +22,12 @@ internal object TxHistoryItemsStoreModule {
|
|||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayTxHistoryItemsStore(): TangemPayTxHistoryItemsStore {
|
||||
return DefaultTangemPayTxHistoryItemsStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
|
||||
internal class DefaultTangemPayTxHistoryItemsStore(
|
||||
dataStore: StringKeyDataStore<Map<String, List<TangemPayTxHistoryItem>>>,
|
||||
) : TangemPayTxHistoryItemsStore,
|
||||
StringKeyDataStoreDecorator<UserWalletId, Map<String, List<TangemPayTxHistoryItem>>>(dataStore) {
|
||||
override fun provideStringKey(key: UserWalletId): String = key.stringValue
|
||||
|
||||
override suspend fun getSyncOrNull(key: UserWalletId, cursor: String): List<TangemPayTxHistoryItem>? {
|
||||
val storedValue = getSyncOrNull(key)
|
||||
return storedValue?.get(cursor)
|
||||
}
|
||||
|
||||
override suspend fun store(key: UserWalletId, cursor: String, value: List<TangemPayTxHistoryItem>) {
|
||||
val oldValue = getSyncOrNull(key).orEmpty()
|
||||
val newValue = oldValue.toMutableMap().apply { put(cursor, value) }
|
||||
store(key, newValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
|
||||
interface TangemPayTxHistoryItemsStore {
|
||||
|
||||
suspend fun getSyncOrNull(key: UserWalletId, cursor: String): List<TangemPayTxHistoryItem>?
|
||||
|
||||
suspend fun remove(key: UserWalletId)
|
||||
|
||||
suspend fun store(key: UserWalletId, cursor: String, value: List<TangemPayTxHistoryItem>)
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.tangem.pagination.fetcher
|
||||
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.exception.EndOfPaginationException
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
/**
|
||||
* First page: cursor = null
|
||||
* Next pages: cursor = cursorFromItem(lastItemOfPreviousPage)
|
||||
*/
|
||||
class CursorBatchFetcher<TRequestParams : Any, TItem : Any>(
|
||||
private val prefetchDistance: Int,
|
||||
private val batchSize: Int,
|
||||
private val subFetcher: SubFetcher<TRequestParams, TItem>,
|
||||
private val cursorFromItem: (TItem) -> String,
|
||||
) : BatchFetcher<TRequestParams, List<TItem>> {
|
||||
|
||||
data class Request<TRequestParams>(
|
||||
val limit: Int,
|
||||
val cursor: String?,
|
||||
val params: TRequestParams,
|
||||
)
|
||||
|
||||
fun interface SubFetcher<TRequestParams : Any, TItem : Any> {
|
||||
suspend fun fetch(
|
||||
request: Request<TRequestParams>,
|
||||
lastResult: BatchFetchResult<List<TItem>>?,
|
||||
isFirstBatchFetching: Boolean,
|
||||
): BatchFetchResult<List<TItem>>
|
||||
}
|
||||
|
||||
private val lastRequest = MutableStateFlow<Request<TRequestParams>?>(null)
|
||||
|
||||
override suspend fun fetchFirst(requestParams: TRequestParams): BatchFetchResult<List<TItem>> {
|
||||
val request = Request(
|
||||
cursor = null,
|
||||
limit = prefetchDistance,
|
||||
params = requestParams,
|
||||
)
|
||||
|
||||
val result = runCatching {
|
||||
subFetcher.fetch(request = request, lastResult = null, isFirstBatchFetching = true)
|
||||
}.getOrElse {
|
||||
currentCoroutineContext().ensureActive()
|
||||
return BatchFetchResult.Error(it)
|
||||
}
|
||||
|
||||
lastRequest.value = request
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun fetchNext(
|
||||
overrideRequestParams: TRequestParams?,
|
||||
lastResult: BatchFetchResult<List<TItem>>,
|
||||
): BatchFetchResult<List<TItem>> {
|
||||
val lastRequest = requireNotNull(lastRequest.value) { "fetchFirst() must be called before fetchNext()" }
|
||||
|
||||
if (lastResult is BatchFetchResult.Success && lastResult.last && overrideRequestParams == null) {
|
||||
return BatchFetchResult.Error(EndOfPaginationException())
|
||||
}
|
||||
|
||||
val nextReq: Request<TRequestParams> =
|
||||
if (lastResult is BatchFetchResult.Success<List<TItem>>) {
|
||||
val items = lastResult.data
|
||||
if (items.isEmpty()) {
|
||||
return BatchFetchResult.Error(EndOfPaginationException())
|
||||
}
|
||||
|
||||
val nextCursor = cursorFromItem(items.last())
|
||||
|
||||
Request(
|
||||
cursor = nextCursor,
|
||||
limit = batchSize,
|
||||
params = overrideRequestParams ?: lastRequest.params,
|
||||
)
|
||||
} else {
|
||||
lastRequest.copy(limit = batchSize, params = overrideRequestParams ?: lastRequest.params)
|
||||
}
|
||||
|
||||
val result = runCatching {
|
||||
subFetcher.fetch(request = nextReq, lastResult = lastResult, isFirstBatchFetching = false)
|
||||
}.getOrElse {
|
||||
currentCoroutineContext().ensureActive()
|
||||
return BatchFetchResult.Error(it)
|
||||
}
|
||||
|
||||
this.lastRequest.value = nextReq
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
@ -520,9 +520,12 @@
|
|||
<string name="hw_backup_need_action">バックアップへ移動</string>
|
||||
<string name="hw_backup_need_description">アクセスコードを作成する前にウォレットをバックアップしてください。</string>
|
||||
<string name="hw_backup_need_title">まずバックアップを完了する</string>
|
||||
<string name="hw_backup_no_backup">バックアップなし</string>
|
||||
<string name="hw_backup_seed_description">秘密鍵をオフラインで安全に保存する物理デバイス。</string>
|
||||
<string name="hw_backup_seed_title">リカバリーフレーズ</string>
|
||||
<string name="hw_create_keys_description">受信取引の通知を受け取る</string>
|
||||
<string name="hw_create_keys_title">鍵はアプリに保存されます</string>
|
||||
<string name="hw_create_seed_description">新機能やアップデートの最新情報を入手</string>
|
||||
<string name="hw_create_seed_title">シードフレーズのバックアップ</string>
|
||||
<string name="hw_create_title">モバイルウォレットを作成する</string>
|
||||
<string name="hw_import_seed_phrase_already_imported">このリカバリーフレーズはすでにインポートされています。</string>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="access_code_alert_skip_description">Ваш кошелёк не защищён без кода доступа.</string>
|
||||
<string name="account_details_archive_action">Архив</string>
|
||||
<string name="account_details_archive_description">Вы архивируете свой аккаунт, но в любое время можете вернуть его обратно</string>
|
||||
<string name="account_form_placeholder_edit_account">Аккаунт</string>
|
||||
|
|
@ -42,14 +41,12 @@
|
|||
<string name="alert_manage_tokens_unsupported_message">Токены в сети %1$s не поддерживаются этой картой или кольцом из-за ограничений прошивки.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">У вас возникли трудности со сканированием карты или кольца?</string>
|
||||
<string name="alert_unsupported_card">Эта карта не предназначена для работы с этим приложением</string>
|
||||
<string name="app_settings_biometrics_footer">Используйте %1$s, чтобы быстро и безопасно разблокировать кошелёк и выполнять чувствительные действия, например, подписывать транзакции. Для аппаратных кошельков всё ещё требуется карта или кольцо для подписи.</string>
|
||||
<string name="app_settings_default_fee">Комиссия по-умолчанию</string>
|
||||
<string name="app_settings_default_fee_footer">Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться.</string>
|
||||
<string name="app_settings_enable_biometrics_description">Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem</string>
|
||||
<string name="app_settings_enable_biometrics_title">Включите биометрическую аутентификацию</string>
|
||||
<string name="app_settings_off_saved_access_code_alert_message">Все сохраненные коды доступа будут удалены. Вам потребуется вводить код доступа при работе с кошельком.</string>
|
||||
<string name="app_settings_off_saved_wallet_alert_message">При отключении функции сохранения кошелька все ранее сохраненные кошельки будут удалены из приложения.</string>
|
||||
<string name="app_settings_require_access_code_footer">Эта опция отключает использование биометрии для выполнения чувствительных действий. Каждый раз, например при подписании транзакции, вам потребуется вводить код доступа.</string>
|
||||
<string name="app_settings_saved_access_codes">Сохранение кода доступа</string>
|
||||
<string name="app_settings_saved_access_codes_footer">Подключите функцию хранения кодов доступа от карт на телефоне в зашифрованном виде, и при работе с картой или кольцом вместо кода доступа будет запрашиваться биометрическая аутентификация.</string>
|
||||
<string name="app_settings_saved_wallet">Cохранение кошелька</string>
|
||||
|
|
@ -59,12 +56,6 @@
|
|||
<string name="app_settings_theme_mode_system">Как в системе</string>
|
||||
<string name="app_settings_theme_selector_title">Тема</string>
|
||||
<string name="app_settings_title">Настройки приложения</string>
|
||||
<string name="backup_complete_seed_description">Эти слова невозможно восстановить, если они будут потеряны. Храните их в надёжном месте.</string>
|
||||
<string name="backup_info_description">Ваша секретная фраза восстановления — это фиксированный набор из %s случайных слов для доступа к вашему кошельку и его восстановления.</string>
|
||||
<string name="backup_info_keep_description">Эти слова невозможно восстановить, если они будут потеряны. Храните их в безопасности.</string>
|
||||
<string name="backup_info_keep_title">Храните в безопасности</string>
|
||||
<string name="backup_seed_caution">Никому не сообщайте эти слова. Tangem никогда не будет их спрашивать. Ниже приведены %s слов вашей фразы восстановления кошелька. Используйте их, чтобы восстановить кошелёк в случае потери устройства.</string>
|
||||
<string name="backup_seed_description">Запишите эти %s слов в указанном порядке и храните их в безопасности и в тайне.</string>
|
||||
<string name="balance_hidden_description">Чтобы скрыть или показать баланс, просто поверните ваше устройство вниз или отключите опцию его в разделе \"Настройки\"</string>
|
||||
<string name="balance_hidden_do_not_show_button">Больше не показывать</string>
|
||||
<string name="balance_hidden_got_it_button">Понятно</string>
|
||||
|
|
@ -446,12 +437,12 @@
|
|||
<string name="hw_backup_need_action">Перейти к бэкапу</string>
|
||||
<string name="hw_backup_need_description">Пожалуйста, создайте резервную копию вашего кошелька перед установкой кода доступа.</string>
|
||||
<string name="hw_backup_need_title">Сначала завершите создание резервной копии</string>
|
||||
<string name="hw_backup_no_backup">Не завершено</string>
|
||||
<string name="hw_backup_no_backup">Нет бэкапа</string>
|
||||
<string name="hw_backup_seed_description">Физические устройства, которые надёжно хранят ваш приватный ключ офлайн.</string>
|
||||
<string name="hw_backup_seed_title">Фраза восстановления</string>
|
||||
<string name="hw_create_keys_description">Ваши приватные ключи надёжно зашифрованы и хранятся на вашем телефоне</string>
|
||||
<string name="hw_create_keys_description">Получайте уведомления о входящих транзакциях</string>
|
||||
<string name="hw_create_keys_title">Ключи хранятся в приложении</string>
|
||||
<string name="hw_create_seed_description">Создайте или восстановите свой кошелёк с помощью фразы восстановления — вашей встроенной резервной копии</string>
|
||||
<string name="hw_create_seed_description">Будьте в курсе новых функций и новостей</string>
|
||||
<string name="hw_create_seed_title">Резервная копия сид-фразы</string>
|
||||
<string name="hw_create_title">Создать мобильный кошелек</string>
|
||||
<string name="hw_import_seed_phrase_already_imported">Эта фраза восстановления уже была импортирована</string>
|
||||
|
|
@ -705,7 +696,7 @@
|
|||
<string name="onboarding_add_tokens">Добавление токенов</string>
|
||||
<string name="onboarding_alert_message_not_max_backup_cards_added">Вы добавили одну резервную карту или кольцо. После того, как процесс будет завершен, Вы больше не сможете добавить еще. Если у Вас есть еще одна карта или кольцо, добавьте ее в резервную копию. Хотите продолжить?</string>
|
||||
<string name="onboarding_backup_exit_warning">Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас.</string>
|
||||
<string name="onboarding_bottom_sheet_passphrase_description">Парольная фраза — это дополнительная функция безопасности, которая добавляет слово или фразу к вашей фразе восстановления, создавая новый набор адресов кошелька для дополнительной защиты.</string>
|
||||
<string name="onboarding_bottom_sheet_passphrase_description">Парольная фраза — это расширенная функция безопасности, которую используют криптокошельки. Она добавляет дополнительное слово или фразу по вашему выбору к уже существующей seed - фразе, чтобы разблокировать совершенно новый набор адресов.</string>
|
||||
<string name="onboarding_button_add_backup_card">Добавить карту или кольцо</string>
|
||||
<string name="onboarding_button_backup_card">Сканировать карту</string>
|
||||
<string name="onboarding_button_backup_card_format">Сканировать карту #%d</string>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="access_code_alert_skip_description">Without an access code, your wallet is not secure.</string>
|
||||
<string name="access_code_alert_skip_description">Without an access code, your wallet isn\'t secure.</string>
|
||||
<string name="access_code_alert_skip_ok">Skip anyway</string>
|
||||
<string name="access_code_alert_skip_title">Access code not set</string>
|
||||
<string name="access_code_check_title">Enter access code</string>
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
<string name="alert_manage_tokens_unsupported_message">Tokens in %1$s network are not supported by this card or ring due to firmware limitation.</string>
|
||||
<string name="alert_troubleshooting_scan_card_title">Are you having difficulty scanning your card or ring?</string>
|
||||
<string name="alert_unsupported_card">This card is not designed to work with this app</string>
|
||||
<string name="app_settings_biometrics_footer">Use %1$s to unlock your wallet and approve sensitive actions, like signing transactions. For hardware wallets, a card or ring is still required to sign.</string>
|
||||
<string name="app_settings_biometrics_footer">Use %1$s to quickly and securely unlock your wallet and authorize all sensitive actions, such as signing transactions. For hardware wallets, you will still need a card to sign.</string>
|
||||
<string name="app_settings_default_fee">Default Fee</string>
|
||||
<string name="app_settings_default_fee_footer">Enable Default Fee to set transaction fees automatically and skip the Fee page when sending funds. You can always go back to this page if necessary.</string>
|
||||
<string name="app_settings_enable_biometrics_description">Go to settings to enable biometric authentication in the Tangem App</string>
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
<string name="app_settings_off_saved_wallet_alert_message">Removing the saved devices deletes all the saved wallets and their access codes from the app.</string>
|
||||
<string name="app_settings_on_require_access_code_alert_message">This will delete all the saved wallet access codes. Any further interaction with the wallet will require submitting the access code.</string>
|
||||
<string name="app_settings_require_access_code">Require Access Code</string>
|
||||
<string name="app_settings_require_access_code_footer">This option turns off biometrics for sensitive actions. You’ll need to enter your access code each time you sign a transaction.</string>
|
||||
<string name="app_settings_require_access_code_footer">This option disables biometric authentication for sensitive actions. You will be required to enter your access code every time, such as when signing a transaction.</string>
|
||||
<string name="app_settings_saved_access_codes">Save Access Code</string>
|
||||
<string name="app_settings_saved_access_codes_footer">Biometric authentication will be requested instead of the access code for interactions with your card or ring.</string>
|
||||
<string name="app_settings_saved_wallet">Keep the wallet in the app</string>
|
||||
|
|
@ -528,12 +528,12 @@
|
|||
<string name="hw_backup_need_action">Go to backup</string>
|
||||
<string name="hw_backup_need_description">Please back up your wallet before creating an access code.</string>
|
||||
<string name="hw_backup_need_title">Finalize backup first</string>
|
||||
<string name="hw_backup_no_backup">Incomplete</string>
|
||||
<string name="hw_backup_no_backup">No backup</string>
|
||||
<string name="hw_backup_seed_description">Physical devices that securely store your private key offline.</string>
|
||||
<string name="hw_backup_seed_title">Recovery phrase</string>
|
||||
<string name="hw_create_keys_description">Your private keys are securely encrypted and stored on your phone</string>
|
||||
<string name="hw_create_keys_description">Get notified of incoming transactions</string>
|
||||
<string name="hw_create_keys_title">Keys are stored in the app</string>
|
||||
<string name="hw_create_seed_description">Create or restore your wallet using a recovery phrase — your built-in backup.</string>
|
||||
<string name="hw_create_seed_description">Stay informed about new features and updates</string>
|
||||
<string name="hw_create_seed_title">Seed phrase backup</string>
|
||||
<string name="hw_create_title">Create Mobile Wallet</string>
|
||||
<string name="hw_import_seed_phrase_already_imported">This recovery phrase has already been imported</string>
|
||||
|
|
@ -549,7 +549,7 @@
|
|||
<string name="hw_upgrade_key_migration_title">Key Migration</string>
|
||||
<string name="hw_upgrade_scan_device">Scan device</string>
|
||||
<string name="hw_upgrade_start_action">Start upgrade</string>
|
||||
<string name="hw_upgrade_start_description">You’re about to upgrade to our hardware wallet. This will keep your assets safe in cold storage.</string>
|
||||
<string name="hw_upgrade_start_description">You\'re about to upgrade to a Tangem device, where your assets stay safe in cold storage.</string>
|
||||
<string name="hw_upgrade_start_title">Tangem Wallet</string>
|
||||
<string name="hw_upgrade_title">Upgrade to Hardware Wallet</string>
|
||||
<string name="hw_upgrade_to_cold_banner_description">Keep your crypto safe with Tangem\'s top-tier hardware wallet.</string>
|
||||
|
|
@ -778,7 +778,7 @@
|
|||
<string name="onboarding_add_tokens">Add tokens</string>
|
||||
<string name="onboarding_alert_message_not_max_backup_cards_added">You\'ve added one backup card or ring. When backup process is finished you can\'t add more backup devices. If you have one more card or ring, add it to the backup. Would you like to continue the backup process?</string>
|
||||
<string name="onboarding_backup_exit_warning">The backup process is partly complete. You can\'t exit it now.</string>
|
||||
<string name="onboarding_bottom_sheet_passphrase_description">A passphrase is an optional security feature that adds a word or phrase to your recovery phrase, creating a new set of wallet addresses for extra protection.</string>
|
||||
<string name="onboarding_bottom_sheet_passphrase_description">The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses.</string>
|
||||
<string name="onboarding_button_add_backup_card">Add a card or ring</string>
|
||||
<string name="onboarding_button_backup_card">Scan card</string>
|
||||
<string name="onboarding_button_backup_card_format">Scan card #%d</string>
|
||||
|
|
@ -1344,15 +1344,9 @@
|
|||
<string name="unlock_wallet_description_full">Use %s or scan a card/ring to have access to your wallet</string>
|
||||
<string name="unsupported_wc_version">Connection failed: This dApp uses Wallet Connect version 1.0, which is not supported. Please ensure the dApp supports Wallet Connect version 2.0 to connect successfully.</string>
|
||||
<string name="user_push_notification_agreement_argument_one">Stay up to date with the latest features and news</string>
|
||||
<string name="user_push_notification_agreement_argument_one_subtitle">Real-time alerts for transactions, exchanges, and critical updates.</string>
|
||||
<string name="user_push_notification_agreement_argument_one_title">Transaction Alerts</string>
|
||||
<string name="user_push_notification_agreement_argument_three">Get notified of incoming transactions</string>
|
||||
<string name="user_push_notification_agreement_argument_two">Be the first to know about new promotions</string>
|
||||
<string name="user_push_notification_agreement_argument_two_subtitle">Early access to fresh features and exclusive offers.</string>
|
||||
<string name="user_push_notification_agreement_argument_two_title">Feature and News Updates</string>
|
||||
<string name="user_push_notification_agreement_header">Would you like to use\nPush-notifications?</string>
|
||||
<string name="user_push_notification_banner_subtitle">Enable push notifications and we’ll notify you instantly when funds arrive\n</string>
|
||||
<string name="user_push_notification_banner_title">Don’t Miss a Transaction</string>
|
||||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to forget this wallet?</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card or ring to log in</string>
|
||||
|
|
@ -1450,8 +1444,6 @@
|
|||
<string name="wallet_connect_error_wrong_card_selected">Wrong card or ring selected in Tangem App</string>
|
||||
<string name="wallet_connect_failed_to_build_tx">Failed to create transaction from Dapp data. Code: %s</string>
|
||||
<string name="wallet_connect_generic_error_with_code">We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support</string>
|
||||
<string name="wallet_connect_multiple_transactions">Multiple transactions</string>
|
||||
<string name="wallet_connect_multiple_transactions_description">You’ll need to tap your Tangem device a few times to complete this process.</string>
|
||||
<string name="wallet_connect_no_sessions_message">No opened WalletConnect sessions</string>
|
||||
<string name="wallet_connect_no_sessions_title">Ooops. No Sessions.</string>
|
||||
<string name="wallet_connect_pairing_error">Failed to pairing WalletConnect session: %1$s</string>
|
||||
|
|
@ -1463,8 +1455,6 @@
|
|||
<string name="wallet_connect_scanner_error_not_valid_card">This card can\'t be used to establish WalletConnect session</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
|
||||
<string name="wallet_connect_select_network">Select network</string>
|
||||
<string name="wallet_connect_sending_multiple_explanation">We\'re processing the transaction</string>
|
||||
<string name="wallet_connect_sending_multiple_tx">Sending your funds...</string>
|
||||
<string name="wallet_connect_sessions_title">WalletConnect Sessions</string>
|
||||
<string name="wallet_connect_subtitle">Connect to dApps</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ fun CardWithIcon(
|
|||
internal fun IconWithTitleAndDescription(
|
||||
title: String,
|
||||
description: String?,
|
||||
iconBackground: Color = TangemTheme.colors.background.secondary,
|
||||
icon: @Composable () -> Unit,
|
||||
additionalContent: @Composable () -> Unit = {},
|
||||
iconBackground: Color = TangemTheme.colors.background.secondary,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ fun Modifier.bottomFade(
|
|||
)
|
||||
|
||||
enum class FadePosition {
|
||||
TOP, BOTTOM, LEFT, RIGHT;
|
||||
TOP, BOTTOM, LEFT, RIGHT
|
||||
}
|
||||
|
||||
@Stable
|
||||
|
|
|
|||
|
|
@ -169,12 +169,12 @@ private fun Preview_Tree() {
|
|||
},
|
||||
content = {
|
||||
ArrowRowItems(
|
||||
itemPadding = PaddingValues(vertical = TangemTheme.dimens.spacing4),
|
||||
items = persistentListOf(
|
||||
stringReference("Fist item"),
|
||||
stringReference("Second item"),
|
||||
stringReference("Third item"),
|
||||
),
|
||||
itemPadding = PaddingValues(vertical = TangemTheme.dimens.spacing4),
|
||||
rootContent = {
|
||||
PreviewItem(stringReference("Root"))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
@Composable
|
||||
inline fun <T : Any> InformationBlockContentScope.ListItems(
|
||||
items: ImmutableList<T>,
|
||||
itemContent: @Composable BoxScope.(T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
itemPadding: PaddingValues = PaddingValues(all = TangemTheme.dimens.spacing0),
|
||||
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
|
||||
verticalArragement: Arrangement.Vertical = Arrangement.Top,
|
||||
itemContent: @Composable BoxScope.(T) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
|
|
@ -42,10 +42,10 @@ inline fun <T : Any> InformationBlockContentScope.ListItems(
|
|||
@Composable
|
||||
inline fun <T : Any> InformationBlockContentScope.GridItems(
|
||||
items: ImmutableList<T>,
|
||||
itemContent: @Composable BoxScope.(T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
verticalAlignment: Alignment.Vertical = Alignment.Top,
|
||||
horizontalArragement: Arrangement.Horizontal = Arrangement.Start,
|
||||
itemContent: @Composable BoxScope.(T) -> Unit,
|
||||
) {
|
||||
val rowItems by remember(items) {
|
||||
derivedStateOf {
|
||||
|
|
@ -81,10 +81,10 @@ inline fun <T : Any> InformationBlockContentScope.GridItems(
|
|||
@Composable
|
||||
inline fun <T : Any> InformationBlockContentScope.ArrowRowItems(
|
||||
items: ImmutableList<T>,
|
||||
rootContent: @Composable BoxScope.() -> Unit,
|
||||
itemContent: @Composable BoxScope.(T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
itemPadding: PaddingValues = PaddingValues(all = TangemTheme.dimens.spacing0),
|
||||
rootContent: @Composable BoxScope.() -> Unit,
|
||||
itemContent: @Composable BoxScope.(T) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ const val MODAL_SHEET_MAX_HEIGHT = 0.8f
|
|||
inline fun <reified T : TangemBottomSheetConfigContent> TangemModalBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
containerColor: Color = TangemTheme.colors.background.primary,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
skipPartiallyExpanded: Boolean = true,
|
||||
dismissOnClickOutside: Boolean = true,
|
||||
scrollableContent: Boolean = true,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
crossinline title: @Composable BoxScope.(T) -> Unit = {},
|
||||
crossinline content: @Composable ColumnScope.(T) -> Unit,
|
||||
) {
|
||||
|
|
@ -202,9 +202,9 @@ inline fun <reified T : TangemBottomSheetConfigContent> BsContent(
|
|||
inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
sheetState: SheetState,
|
||||
modifier: Modifier = Modifier,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
noinline bsContent: @Composable ColumnScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (onBack != null) {
|
||||
ModalBottomSheetWithBackHandling(
|
||||
|
|
|
|||
|
|
@ -143,11 +143,11 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
|
|||
config: TangemBottomSheetConfig,
|
||||
sheetState: SheetState,
|
||||
containerColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
crossinline title: @Composable BoxScope.(T) -> Unit,
|
||||
crossinline content: @Composable (T) -> Unit,
|
||||
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val model = config.content as? T ?: return
|
||||
|
||||
|
|
|
|||
|
|
@ -152,10 +152,10 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
|||
sheetState: SheetState,
|
||||
containerColor: Color,
|
||||
addBottomInsets: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
crossinline title: @Composable (BoxScope.(T) -> Unit),
|
||||
crossinline content: @Composable (ColumnScope.(T) -> Unit),
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val model = config.content as? T ?: return
|
||||
|
||||
|
|
|
|||
|
|
@ -111,10 +111,10 @@ fun ActionButton(
|
|||
fun ActionBaseButton(
|
||||
config: ActionButtonConfig,
|
||||
shape: RoundedCornerShape,
|
||||
content: @Composable (modifier: Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.button.secondary,
|
||||
containerColor: Color = TangemTheme.colors.background.secondary,
|
||||
content: @Composable (modifier: Modifier) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val backgroundColor by animateColorAsState(
|
||||
|
|
@ -163,9 +163,9 @@ fun ActionBaseButton(
|
|||
@Composable
|
||||
fun ActionButtonContent(
|
||||
config: ActionButtonConfig,
|
||||
text: @Composable (Color) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
paddingBetweenIconAndText: Dp = 8.dp,
|
||||
text: @Composable (Color) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ internal inline fun DefaultCurrencyIcon(
|
|||
size: Dp,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
crossinline errorIcon: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
crossinline errorIcon: @Composable () -> Unit,
|
||||
) {
|
||||
var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) }
|
||||
var isBackgroundColorDefined by remember { mutableStateOf(false) }
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ private fun TokenIcon(
|
|||
url: String?,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
errorIcon: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
errorIcon: @Composable () -> Unit,
|
||||
) {
|
||||
if (url == null) {
|
||||
errorIcon()
|
||||
|
|
|
|||
|
|
@ -103,11 +103,11 @@ fun SearchBar(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun DecorationBox(
|
||||
state: SearchBarUM,
|
||||
innerTextField: @Composable () -> Unit,
|
||||
interactionSource: MutableInteractionSource,
|
||||
colors: TextFieldColors,
|
||||
focusManager: FocusManager,
|
||||
keyboardController: SoftwareKeyboardController?,
|
||||
innerTextField: @Composable () -> Unit,
|
||||
) {
|
||||
TextFieldDefaults.DecorationBox(
|
||||
value = state.query,
|
||||
|
|
|
|||
|
|
@ -121,12 +121,12 @@ fun SimpleTextField(
|
|||
|
||||
@Composable
|
||||
private fun SimpleTextPlaceholder(
|
||||
placeholder: TextReference?,
|
||||
value: String,
|
||||
textStyle: TextStyle,
|
||||
centered: Boolean,
|
||||
textValue: @Composable () -> Unit,
|
||||
placeholder: TextReference?,
|
||||
color: Color = TangemTheme.colors.text.disabled,
|
||||
textValue: @Composable () -> Unit,
|
||||
) {
|
||||
Box(contentAlignment = if (centered) Alignment.Center else Alignment.TopStart) {
|
||||
if (value.isBlank() && placeholder != null) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.core.ui.components.icons
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
@Stable
|
||||
enum class IconTint {
|
||||
Accent,
|
||||
Warning,
|
||||
Inactive,
|
||||
}
|
||||
|
|
@ -58,8 +58,8 @@ internal data class Blockies(
|
|||
}
|
||||
|
||||
private fun dataFromSeed(seed: MutableList<Long>) = MutableList(SIZE * SIZE) { DEFAULT_VALUE_F }.apply {
|
||||
(0 until SIZE).forEach { row ->
|
||||
(0 until HALF_SIZE).forEach { column ->
|
||||
for (row in 0 until SIZE) {
|
||||
for (column in 0 until HALF_SIZE) {
|
||||
val value = floor(nextSeed(seed) * PROBABILITY_COLOR)
|
||||
this[row * SIZE + column] = value
|
||||
this[(row + 1) * SIZE - column - 1] = value
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import com.tangem.core.ui.utils.*
|
|||
@Composable
|
||||
inline fun ArrowRow(
|
||||
isLastItem: Boolean,
|
||||
content: @Composable() (BoxScope.() -> Unit),
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues(all = TangemTheme.dimens.spacing0),
|
||||
content: @Composable() (BoxScope.() -> Unit),
|
||||
) {
|
||||
val density = LocalDensity.current.density
|
||||
val defaultRowHeight = TangemTheme.dimens.size0
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ private const val DISABLED_ICON_ALPHA = 0.4f
|
|||
* [Figma Component](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2737-2800&t=ewlXfWwbDnRhjw4B-4)
|
||||
* */
|
||||
@Composable
|
||||
fun BlockchainRow(model: BlockchainRowUM, action: @Composable BoxScope.() -> Unit, modifier: Modifier = Modifier) {
|
||||
fun BlockchainRow(model: BlockchainRowUM, modifier: Modifier = Modifier, action: @Composable BoxScope.() -> Unit) {
|
||||
RowContentContainer(
|
||||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size52)
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ fun ChainRow(model: ChainRowUM, modifier: Modifier = Modifier, action: @Composab
|
|||
|
||||
@Composable
|
||||
inline fun ChainRowContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
icon: @Composable BoxScope.() -> Unit,
|
||||
text: @Composable BoxScope.() -> Unit,
|
||||
action: @Composable BoxScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
RowContentContainer(
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
*/
|
||||
@Composable
|
||||
fun NetworkTitle(
|
||||
title: @Composable BoxScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
title: @Composable BoxScope.() -> Unit,
|
||||
action: (@Composable BoxScope.() -> Unit)? = null,
|
||||
) {
|
||||
val minHeight = if (action == null) TangemTheme.dimens.size36 else TangemTheme.dimens.size40
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
|
||||
@Composable
|
||||
inline fun RowContentContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
icon: @Composable BoxScope.() -> Unit,
|
||||
text: @Composable BoxScope.() -> Unit,
|
||||
action: @Composable BoxScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.components.audits.AuditLabelUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.icons.IconTint
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.token.internal.*
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
|
|
@ -488,7 +489,14 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
fiatAmountState = FiatAmountState.Content(
|
||||
text = "3213123123321312312312312312 $",
|
||||
icons = persistentListOf(
|
||||
FiatAmountState.Content.IconUM(R.drawable.ic_error_sync_24, useAccentColor = false),
|
||||
FiatAmountState.Content.IconUM(
|
||||
iconRes = R.drawable.img_attention_20,
|
||||
tint = IconTint.Warning,
|
||||
),
|
||||
FiatAmountState.Content.IconUM(
|
||||
iconRes = R.drawable.ic_error_sync_24,
|
||||
tint = IconTint.Inactive,
|
||||
),
|
||||
stakingIcon,
|
||||
),
|
||||
isFlickering = true,
|
||||
|
|
@ -534,7 +542,10 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
TokenItemState.Draggable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
titleState = TokenItemState.TitleState.Content(
|
||||
text = stringReference(value = "Polygon"),
|
||||
earnApy = stringReference("Earn 5%"),
|
||||
),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "3 172,14 $"),
|
||||
),
|
||||
TokenItemState.Content(
|
||||
|
|
@ -603,7 +614,7 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
|
||||
companion object {
|
||||
|
||||
val stakingIcon = FiatAmountState.Content.IconUM(R.drawable.ic_staking_24, useAccentColor = true)
|
||||
val stakingIcon = FiatAmountState.Content.IconUM(R.drawable.ic_staking_24, tint = IconTint.Accent)
|
||||
|
||||
val coinIconState
|
||||
get() = CurrencyIconState.CoinIcon(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.icons.IconTint
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -75,10 +76,10 @@ private fun ContentFiatAmount(
|
|||
Icon(
|
||||
modifier = Modifier.size(12.dp),
|
||||
painter = rememberVectorPainter(image = ImageVector.vectorResource(icon.iconRes)),
|
||||
tint = if (icon.useAccentColor) {
|
||||
TangemTheme.colors.icon.accent
|
||||
} else {
|
||||
TangemTheme.colors.icon.inactive
|
||||
tint = when (icon.tint) {
|
||||
IconTint.Accent -> TangemTheme.colors.icon.accent
|
||||
IconTint.Warning -> TangemTheme.colors.icon.attention
|
||||
IconTint.Inactive -> TangemTheme.colors.icon.inactive
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package com.tangem.core.ui.components.token.internal
|
|||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -13,8 +15,10 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as TokenTitleState
|
||||
|
|
@ -56,6 +60,11 @@ private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Mo
|
|||
hasPending = state.hasPending,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterVertically),
|
||||
)
|
||||
|
||||
YieldSupplyApyLabel(
|
||||
apy = state.earnApy,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +80,25 @@ private fun CurrencyNameText(name: String, isAvailable: Boolean, modifier: Modif
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun YieldSupplyApyLabel(apy: TextReference?, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(visible = apy != null, modifier = modifier) {
|
||||
Box(
|
||||
modifier = modifier.background(
|
||||
color = TangemTheme.colors.text.accent.copy(alpha = 0.1f),
|
||||
shape = TangemTheme.shapes.roundedCornersSmall2,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = apy?.resolveReference().orEmpty(),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.accent,
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PendingTransactionImage(hasPending: Boolean, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(visible = hasPending, modifier = modifier) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.core.ui.components.token.state
|
|||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.audits.AuditLabelUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.icons.IconTint
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -167,6 +168,7 @@ sealed class TokenItemState {
|
|||
val text: TextReference,
|
||||
val hasPending: Boolean = false,
|
||||
val isAvailable: Boolean = true,
|
||||
val earnApy: TextReference? = null,
|
||||
) : TitleState()
|
||||
|
||||
data object Loading : TitleState()
|
||||
|
|
@ -204,7 +206,7 @@ sealed class TokenItemState {
|
|||
|
||||
data class IconUM(
|
||||
val iconRes: Int,
|
||||
val useAccentColor: Boolean,
|
||||
val tint: IconTint = IconTint.Inactive,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import kotlinx.coroutines.launch
|
|||
@Composable
|
||||
fun TangemTooltip(
|
||||
text: String,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
InternalTangemTooltip(
|
||||
modifier = modifier,
|
||||
|
|
@ -46,9 +46,9 @@ fun TangemTooltip(
|
|||
@Composable
|
||||
fun TangemTooltip(
|
||||
text: AnnotatedString,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
InternalTangemTooltip(
|
||||
modifier = modifier,
|
||||
|
|
@ -68,10 +68,10 @@ fun TangemTooltip(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun InternalTangemTooltip(
|
||||
tooltipContent: @Composable () -> Unit,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
tooltipContent: @Composable () -> Unit,
|
||||
content: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
val tooltipState = rememberTooltipState(isPersistent = true)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
|
|
|||
|
|
@ -10,13 +10,16 @@ interface ComposableBottomSheetComponent {
|
|||
|
||||
@Composable
|
||||
fun BottomSheet()
|
||||
}
|
||||
|
||||
fun getEmptyComposableBottomSheetComponent() = EmptyComposableBottomSheetComponent
|
||||
companion object {
|
||||
val EMPTY = EmptyComposableBottomSheetComponent
|
||||
}
|
||||
}
|
||||
|
||||
object EmptyComposableBottomSheetComponent : ComposableBottomSheetComponent {
|
||||
override fun dismiss() {}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {}
|
||||
override fun BottomSheet() {
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,11 @@ fun interface ComposableContentComponent {
|
|||
|
||||
@Composable
|
||||
fun Content(modifier: Modifier)
|
||||
}
|
||||
|
||||
fun getEmptyComposableContentComponent() = EmptyComposableContentComponent
|
||||
companion object {
|
||||
val EMPTY = EmptyComposableContentComponent
|
||||
}
|
||||
}
|
||||
|
||||
object EmptyComposableContentComponent : ComposableContentComponent {
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ interface ComposableModularContentComponent {
|
|||
|
||||
@Composable
|
||||
fun Footer()
|
||||
}
|
||||
|
||||
fun getEmptyComposableModularContentComponent() = EmptyComposableModularContentComponent
|
||||
companion object {
|
||||
val EMPTY = EmptyComposableModularContentComponent
|
||||
}
|
||||
}
|
||||
|
||||
object EmptyComposableModularContentComponent : ComposableModularContentComponent {
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -52,10 +52,10 @@ fun TangemTheme(
|
|||
|
||||
@Composable
|
||||
fun TangemTheme(
|
||||
isDark: Boolean = false,
|
||||
windowSize: WindowSize,
|
||||
typography: TangemTypography = TangemTheme.typography,
|
||||
dimens: TangemDimens = TangemTheme.dimens,
|
||||
isDark: Boolean = false,
|
||||
vibratorHapticManager: VibratorHapticManager? = null,
|
||||
eventMessageHandler: EventMessageHandler = remember { EventMessageHandler() },
|
||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||
|
|
|
|||
|
|
@ -1,11 +1,53 @@
|
|||
package com.tangem.core.ui.security
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.tangem.core.ui.utils.findActivity
|
||||
import timber.log.Timber
|
||||
|
||||
private val LocalSecureFlagController = staticCompositionLocalOf<SecureFlagController> {
|
||||
error("No SecureFlagController provided")
|
||||
}
|
||||
|
||||
private class SecureFlagController(private val activity: Activity) {
|
||||
private var count by mutableIntStateOf(0)
|
||||
|
||||
fun enable() {
|
||||
if (count == 0) {
|
||||
activity.window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
fun disable() {
|
||||
count--
|
||||
if (count == 0) {
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProvideSecureFlagController(content: @Composable () -> Unit) {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val controller = remember(activity) { SecureFlagController(activity) }
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalSecureFlagController provides controller,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables screenshots for the current composition.
|
||||
|
|
@ -14,18 +56,10 @@ import timber.log.Timber
|
|||
*/
|
||||
@Composable
|
||||
fun DisableScreenshotsDisposableEffect() {
|
||||
val activity = LocalContext.current.findActivity()
|
||||
val secureFlagController = LocalSecureFlagController.current
|
||||
|
||||
DisposableEffect(activity) {
|
||||
Timber.d("Security mode: enabled")
|
||||
activity.window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
|
||||
onDispose {
|
||||
Timber.d("Security mode: disabled")
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
DisposableEffect(secureFlagController) {
|
||||
secureFlagController.enable()
|
||||
onDispose { secureFlagController.disable() }
|
||||
}
|
||||
}
|
||||
|
|
@ -6,4 +6,6 @@ object StoriesScreenTestTags {
|
|||
const val ORDER_BUTTON = "STORIES_SCREEN_ORDER_BUTTON"
|
||||
const val CREATE_NEW_WALLET_BUTTON = "STORIES_SCREEN_CREATE_NEW_WALLET_BUTTON"
|
||||
const val ADD_EXISTING_WALLET_BUTTON = "STORIES_SCREEN_ADD_EXISTING_WALLET_BUTTON"
|
||||
const val TITLE = "STORIES_SCREEN_TITLE"
|
||||
const val TEXT = "STORIES_SCREEN_TEXT"
|
||||
}
|
||||
|
|
@ -64,9 +64,7 @@ fun DecimalFormat.getValidatedNumberWithFixedDecimals(text: String, decimals: In
|
|||
val beforeDecimal = filteredChars.substringBefore(decimalSeparator)
|
||||
val afterDecimal = filteredChars.substringAfter(decimalSeparator)
|
||||
decimals.getWithIntegerDecimals(beforeDecimal, decimalSeparator, afterDecimal)
|
||||
}
|
||||
// If there is no dot, just take all digits
|
||||
else {
|
||||
} else { // If there is no dot, just take all digits
|
||||
filteredChars
|
||||
}
|
||||
}
|
||||
|
|
@ -87,9 +85,7 @@ fun DecimalFormat.formatWithThousands(text: String, decimals: Int): String {
|
|||
.reversed()
|
||||
val afterDecimal = localizedText.substringAfter(decimalSeparator)
|
||||
decimals.getWithIntegerDecimals(beforeDecimal, decimalSeparator, afterDecimal)
|
||||
}
|
||||
// If there is no dot, just take all digits
|
||||
else {
|
||||
} else { // If there is no dot, just take all digits
|
||||
localizedText.reversed()
|
||||
.chunked(TEXT_CHUNK_THOUSAND)
|
||||
.joinToString(thousandsSeparator.toString())
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ class InputNumberFormatter(
|
|||
val beforeDecimal = filteredChars.substringBefore(decimalSeparator)
|
||||
val afterDecimal = filteredChars.substringAfter(decimalSeparator)
|
||||
beforeDecimal + decimalSeparator + afterDecimal.take(decimals)
|
||||
}
|
||||
// If there is no dot, just take all digits
|
||||
else {
|
||||
} else { // If there is no dot, just take all digits
|
||||
filteredChars
|
||||
}
|
||||
}
|
||||
|
|
@ -62,9 +60,7 @@ class InputNumberFormatter(
|
|||
.reversed()
|
||||
val afterDecimal = text.substringAfter(decimalSeparator)
|
||||
beforeDecimal + decimalSeparator + afterDecimal.take(decimals)
|
||||
}
|
||||
// If there is no dot, just take all digits
|
||||
else {
|
||||
} else { // If there is no dot, just take all digits
|
||||
text.reversed()
|
||||
.chunked(TEXT_CHUNK_THOUSAND)
|
||||
.joinToString(thousandsSeparator.toString())
|
||||
|
|
|
|||
9
core/ui/src/main/res/drawable/ic_attention_12.xml
Normal file
9
core/ui/src/main/res/drawable/ic_attention_12.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="12dp"
|
||||
android:height="12dp"
|
||||
android:viewportWidth="12"
|
||||
android:viewportHeight="12">
|
||||
<path
|
||||
android:pathData="M5.353,1.622C5.641,1.123 6.362,1.123 6.65,1.622L10.85,8.875C11.139,9.375 10.778,10.001 10.2,10.001H1.802C1.224,10.001 0.864,9.375 1.153,8.875L5.353,1.622ZM5.502,8.002V9.002H6.502V8.002H5.502ZM5.502,4.002V7.002H6.502V4.002H5.502Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
|
|
@ -20,7 +20,22 @@ fun <T> Collection<T>.copy(): Collection<T> {
|
|||
return this.map { it }
|
||||
}
|
||||
|
||||
inline fun <T> List<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
|
||||
val index = indexOfFirst(predicate)
|
||||
return if (index == -1) null else index
|
||||
/**
|
||||
* Adds an element to the mutable list if the specified condition is true.
|
||||
*
|
||||
* @param condition The condition to evaluate.
|
||||
* @param create A lambda function that creates the element to be added.
|
||||
*/
|
||||
inline fun <T> MutableCollection<T>.addIf(condition: Boolean, create: () -> T) {
|
||||
addIf(condition = condition, element = create())
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element to the mutable list if the specified condition is true.
|
||||
*
|
||||
* @param condition The condition to evaluate.
|
||||
* @param element The element to be added.
|
||||
*/
|
||||
fun <T> MutableCollection<T>.addIf(condition: Boolean, element: T) {
|
||||
if (condition) this.add(element)
|
||||
}
|
||||
|
|
@ -72,4 +72,9 @@ fun <T> List<T>.filterIf(condition: Boolean, predicate: (T) -> Boolean): List<T>
|
|||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> List<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
|
||||
val index = indexOfFirst(predicate)
|
||||
return if (index == -1) null else index
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue