Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-22 17:20:18 +03:00
commit 7db00540c5
351 changed files with 6478 additions and 1691 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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>

View file

@ -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,
)
}

View file

@ -198,6 +198,7 @@ data class YieldDTO(
enum class RewardTypeDTO {
@Json(name = "apy")
APY, // compound rate
@Json(name = "apr")
APR, // simple rate,

View file

@ -24,7 +24,5 @@ data class SeedPhraseNotificationDTO(val status: Status) {
@Json(name = "accepted")
ACCEPTED,
;
}
}

View file

@ -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(),
)
}
}

View file

@ -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)
}
}

View file

@ -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>)
}

View file

@ -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
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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. Youll 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">Youre 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 well notify you instantly when funds arrive\n</string>
<string name="user_push_notification_banner_title">Dont 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">Youll 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>

View file

@ -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

View file

@ -75,7 +75,7 @@ fun Modifier.bottomFade(
)
enum class FadePosition {
TOP, BOTTOM, LEFT, RIGHT;
TOP, BOTTOM, LEFT, RIGHT
}
@Stable

View file

@ -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"))
},

View file

@ -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(),

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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) }

View file

@ -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()

View file

@ -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,

View file

@ -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) {

View file

@ -0,0 +1,10 @@
package com.tangem.core.ui.components.icons
import androidx.compose.runtime.Stable
@Stable
enum class IconTint {
Accent,
Warning,
Inactive,
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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(),

View file

@ -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(

View file

@ -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,
)

View file

@ -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) {

View file

@ -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,
)
}

View file

@ -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()

View file

@ -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() {
}
}

View file

@ -9,9 +9,11 @@ fun interface ComposableContentComponent {
@Composable
fun Content(modifier: Modifier)
}
fun getEmptyComposableContentComponent() = EmptyComposableContentComponent
companion object {
val EMPTY = EmptyComposableContentComponent
}
}
object EmptyComposableContentComponent : ComposableContentComponent {
@Composable

View file

@ -15,9 +15,11 @@ interface ComposableModularContentComponent {
@Composable
fun Footer()
}
fun getEmptyComposableModularContentComponent() = EmptyComposableModularContentComponent
companion object {
val EMPTY = EmptyComposableModularContentComponent
}
}
object EmptyComposableModularContentComponent : ComposableModularContentComponent {
@Composable

View file

@ -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() },

View file

@ -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() }
}
}

View file

@ -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"
}

View file

@ -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())

View file

@ -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())

View 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>

View file

@ -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)
}

View file

@ -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
}