Updated on 2026-08-14
This commit is contained in:
commit
c32a5fa18d
711 changed files with 18558 additions and 4312 deletions
|
|
@ -40,6 +40,7 @@ dependencies {
|
|||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.nft.models)
|
||||
implementation(projects.domain.walletConnect.models)
|
||||
implementation(projects.domain.yieldSupply.models)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.datasource.api.common.blockaid
|
||||
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionBulkScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.GasEstimationResponse
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.SolanaTransactionResponse
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse
|
||||
import retrofit2.http.Body
|
||||
|
|
@ -19,4 +21,7 @@ interface BlockAidApi {
|
|||
|
||||
@POST("solana/message/scan")
|
||||
suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): SolanaTransactionResponse
|
||||
|
||||
@POST("evm/transaction-bulk/scan")
|
||||
suspend fun scanEvmTransactionBulk(@Body request: EvmTransactionBulkScanRequest): List<GasEstimationResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
enum class BlockAidScanOptions(val value: String) {
|
||||
Simulation("simulation"),
|
||||
Validation("validation"),
|
||||
GasEstimation("gas_estimation"),
|
||||
}
|
||||
|
|
@ -14,9 +14,25 @@ data class EvmTransactionScanRequest(
|
|||
@Json(name = "metadata") val metadata: TransactionMetadata,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EvmTransactionBulkScanRequest(
|
||||
@Json(name = "chain") val chain: String,
|
||||
@Json(name = "options") val options: List<String>,
|
||||
@Json(name = "metadata") val metadata: TransactionMetadata,
|
||||
@Json(name = "data") val data: List<Data>,
|
||||
@Json(name = "aggregated") val aggregated: Boolean = false,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RpcData(
|
||||
@Json(name = "jsonrpc") val jsonrpc: String = "2.0",
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "params") val params: List<Map<String, String>>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "from") val from: String,
|
||||
@Json(name = "to") val to: String,
|
||||
@Json(name = "data") val data: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GasEstimationResponse(
|
||||
@Json(name = "gas_estimation") val gasEstimation: GasEstimationItem,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GasEstimationItem(
|
||||
@Json(name = "estimate") val estimate: String,
|
||||
)
|
||||
|
|
@ -26,6 +26,7 @@ sealed class ApiConfig {
|
|||
StakeKit,
|
||||
TangemPay,
|
||||
BlockAid,
|
||||
YieldSupply,
|
||||
}
|
||||
|
||||
private fun initializeId(): ID {
|
||||
|
|
@ -35,6 +36,7 @@ sealed class ApiConfig {
|
|||
is StakeKit -> ID.StakeKit
|
||||
is TangemPay -> ID.TangemPay
|
||||
is BlockAid -> ID.BlockAid
|
||||
is YieldSupply -> ID.YieldSupply
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@ package com.tangem.datasource.api.common.config
|
|||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
|
||||
/** TangemTech [ApiConfig] */
|
||||
internal class TangemTech(
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
private val authProvider: AuthProvider,
|
||||
private val appInfoProvider: AppInfoProvider,
|
||||
|
|
@ -38,29 +41,41 @@ internal class TangemTech(
|
|||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
headers = createHeaders(ApiEnvironment.DEV),
|
||||
)
|
||||
|
||||
private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
headers = createHeaders(ApiEnvironment.STAGE),
|
||||
)
|
||||
|
||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
headers = createHeaders(ApiEnvironment.MOCK),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.tangem.org/",
|
||||
headers = createHeaders(),
|
||||
headers = createHeaders(ApiEnvironment.PROD),
|
||||
)
|
||||
|
||||
private fun createHeaders() = buildMap {
|
||||
private fun createHeaders(apiEnvironment: ApiEnvironment) = buildMap {
|
||||
put(key = "api-key", value = ProviderSuspend { getApiKey(apiEnvironment) })
|
||||
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
|
||||
putAll(from = RequestHeader.AuthenticationHeader(authProvider).values)
|
||||
}
|
||||
|
||||
private fun getApiKey(apiEnvironment: ApiEnvironment): String {
|
||||
return when (apiEnvironment) {
|
||||
ApiEnvironment.MOCK,
|
||||
ApiEnvironment.DEV,
|
||||
ApiEnvironment.DEV_2,
|
||||
-> environmentConfigStorage.getConfigSync().tangemApiKeyDev
|
||||
ApiEnvironment.STAGE -> environmentConfigStorage.getConfigSync().tangemApiKeyStage
|
||||
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().tangemApiKey
|
||||
} ?: error("No tangem tech api config provided")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.utils.RequestHeader
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
|
||||
/** YieldSupply [ApiConfig] */
|
||||
internal class YieldSupply(
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
private val authProvider: AuthProvider,
|
||||
private val appInfoProvider: AppInfoProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environmentConfigs = listOf(
|
||||
createDevEnvironment(),
|
||||
createStageEnvironment(),
|
||||
createMockedEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.MOCK
|
||||
DEBUG_BUILD_TYPE,
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(ApiEnvironment.DEV),
|
||||
)
|
||||
|
||||
private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(ApiEnvironment.STAGE),
|
||||
)
|
||||
|
||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(ApiEnvironment.MOCK),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://yield.tangem.org/",
|
||||
headers = createHeaders(ApiEnvironment.PROD),
|
||||
)
|
||||
|
||||
private fun createHeaders(apiEnvironment: ApiEnvironment) = buildMap {
|
||||
put(key = "api-key", value = ProviderSuspend { getApiKey(apiEnvironment) })
|
||||
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
|
||||
putAll(from = RequestHeader.AuthenticationHeader(authProvider).values)
|
||||
}
|
||||
|
||||
private fun getApiKey(apiEnvironment: ApiEnvironment): String {
|
||||
return when (apiEnvironment) {
|
||||
ApiEnvironment.MOCK,
|
||||
ApiEnvironment.DEV,
|
||||
ApiEnvironment.DEV_2,
|
||||
-> environmentConfigStorage.getConfigSync().tangemApiKeyDev
|
||||
ApiEnvironment.STAGE -> environmentConfigStorage.getConfigSync().tangemApiKeyStage
|
||||
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().tangemApiKey
|
||||
} ?: error("No tangem tech api config provided")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ import retrofit2.http.Body
|
|||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
private const val TX_HISTORY_PAGING_DEFAULT_LIMIT = 20
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface TangemPayApi {
|
||||
|
||||
|
|
@ -107,6 +110,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>
|
||||
|
||||
|
|
@ -115,4 +125,16 @@ interface TangemPayApi {
|
|||
|
||||
@POST("v1/deeplink/validate")
|
||||
suspend fun validateDeeplink(@Body body: DeeplinkValidityRequest): ApiResponse<DeeplinkValidityResponse>
|
||||
|
||||
@GET("v1/order/{order_id}")
|
||||
suspend fun getOrder(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("order_id") orderId: String,
|
||||
): ApiResponse<OrderResponse>
|
||||
|
||||
@POST("v1/order")
|
||||
suspend fun createOrder(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: OrderRequest,
|
||||
): ApiResponse<OrderResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OrderRequest(
|
||||
@Json(name = "wallet_address") val walletAddress: String,
|
||||
)
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.api.pay.models.response
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerMeResponse(
|
||||
|
|
@ -16,6 +17,8 @@ data class CustomerMeResponse(
|
|||
@Json(name = "product_instance") val productInstance: ProductInstance?,
|
||||
@Json(name = "payment_account") val paymentAccount: PaymentAccount?,
|
||||
@Json(name = "kyc") val kyc: Kyc?,
|
||||
@Json(name = "card") val card: Card?,
|
||||
@Json(name = "balance") val balance: Balance?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
@ -45,4 +48,25 @@ data class CustomerMeResponse(
|
|||
@Json(name = "review_answer") val reviewAnswer: String,
|
||||
@Json(name = "created_at") val createdAt: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Card(
|
||||
@Json(name = "token") val token: String,
|
||||
@Json(name = "expiration_month") val expirationMonth: Int,
|
||||
@Json(name = "expiration_year") val expirationYear: Int,
|
||||
@Json(name = "emboss_name") val embossName: String,
|
||||
@Json(name = "card_type") val cardType: String,
|
||||
@Json(name = "card_status") val cardStatus: String,
|
||||
@Json(name = "card_number_end") val cardNumberEnd: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Balance(
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "available_balance") val availableBalance: BigDecimal,
|
||||
@Json(name = "credit_limit") val creditLimit: BigDecimal,
|
||||
@Json(name = "pending_charges") val pendingCharges: BigDecimal,
|
||||
@Json(name = "posted_charges") val postedCharges: BigDecimal,
|
||||
@Json(name = "balance_due") val balanceDue: BigDecimal,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OrderResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "customer_id") val customerId: String?,
|
||||
@Json(name = "type") val type: String?,
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "step") val step: String?,
|
||||
@Json(name = "data") val data: Data,
|
||||
@Json(name = "step_change_code") val stepChangeCode: Int?,
|
||||
@Json(name = "created_at") val createdAt: String?,
|
||||
@Json(name = "updated_at") val updatedAt: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "type") val type: String?,
|
||||
@Json(name = "specification_name") val specificationName: String?,
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
@Json(name = "emboss_name") val embossName: String?,
|
||||
@Json(name = "product_instance_id") val productInstanceId: String?,
|
||||
@Json(name = "payment_account_id") val paymentAccountId: String?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.api.tangemTech
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.YieldTokenStatusResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface YieldSupplyApi {
|
||||
|
||||
@GET("v1/yield/markets")
|
||||
suspend fun getYieldMarkets(): ApiResponse<YieldMarketsResponse>
|
||||
|
||||
@GET("v1/yield/token/{tokenAddress}")
|
||||
suspend fun getYieldTokenStatus(@Path("tokenAddress") tokenAddress: String): ApiResponse<YieldTokenStatusResponse>
|
||||
}
|
||||
|
|
@ -24,7 +24,5 @@ data class SeedPhraseNotificationDTO(val status: Status) {
|
|||
|
||||
@Json(name = "accepted")
|
||||
ACCEPTED,
|
||||
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class YieldMarketsResponse(
|
||||
@Json(name = "tokens") val marketDtos: List<MarketDto>,
|
||||
@Json(name = "lastUpdatedAt") val lastUpdated: String,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MarketDto(
|
||||
@Json(name = "tokenAddress") val tokenAddress: String,
|
||||
@Json(name = "tokenSymbol") val tokenSymbol: String,
|
||||
@Json(name = "tokenName") val tokenName: String,
|
||||
@Json(name = "apy") val apy: BigDecimal,
|
||||
@Json(name = "totalSupplied") val totalSupplied: String,
|
||||
@Json(name = "totalBorrowed") val totalBorrowed: String,
|
||||
@Json(name = "liquidityRate") val liquidityRate: String,
|
||||
@Json(name = "borrowRate") val borrowRate: String,
|
||||
@Json(name = "utilizationRate") val utilizationRate: BigDecimal,
|
||||
@Json(name = "isActive") val isActive: Boolean,
|
||||
@Json(name = "ltv") val ltv: BigDecimal,
|
||||
@Json(name = "liquidationThreshold") val liquidationThreshold: BigDecimal,
|
||||
@Json(name = "decimals") val decimals: Int,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class YieldTokenStatusResponse(
|
||||
@Json(name = "tokenAddress") val tokenAddress: String,
|
||||
@Json(name = "tokenSymbol") val tokenSymbol: String,
|
||||
@Json(name = "userBalance") val userBalance: String,
|
||||
@Json(name = "earnedYield") val earnedYield: String,
|
||||
@Json(name = "currentApy") val currentApy: BigDecimal,
|
||||
@Json(name = "totalDeposited") val totalDeposited: String,
|
||||
@Json(name = "moduleAddress") val moduleAddress: String,
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "lastUpdateAt") val lastUpdateAt: String,
|
||||
)
|
||||
|
|
@ -42,10 +42,12 @@ internal object ApiConfigsModule {
|
|||
@Provides
|
||||
@IntoSet
|
||||
fun provideTangemTechConfig(
|
||||
environmentConfigStorage: EnvironmentConfigStorage,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
authProvider: AuthProvider,
|
||||
appInfoProvider: AppInfoProvider,
|
||||
): ApiConfig = TangemTech(
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
appVersionProvider = appVersionProvider,
|
||||
authProvider = authProvider,
|
||||
appInfoProvider = appInfoProvider,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.datasource.api.onramp.OnrampApi
|
|||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.YieldSupplyApi
|
||||
import com.tangem.datasource.di.utils.RetrofitApiBuilder
|
||||
import com.tangem.datasource.di.utils.RetrofitApiBuilder.Timeouts
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
|
|
@ -88,6 +89,15 @@ internal object NetworkModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyApi(retrofitApiBuilder: RetrofitApiBuilder): YieldSupplyApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.YieldSupply,
|
||||
applyTimeoutAnnotations = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemTechMarketsApi(retrofitApiBuilder: RetrofitApiBuilder): TangemTechMarketsApi {
|
||||
|
|
|
|||
|
|
@ -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,45 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.local.yieldsupply.DefaultYieldMarketsStore
|
||||
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
|
||||
import com.tangem.datasource.utils.MoshiDataStoreSerializer
|
||||
import com.tangem.datasource.utils.listTypes
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object YieldSupplyModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldMarketsStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): YieldMarketsStore {
|
||||
return DefaultYieldMarketsStore(
|
||||
persistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = listTypes<YieldMarketToken>(),
|
||||
defaultValue = emptyList(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "yield_markets_cache") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,4 +17,7 @@ data class EnvironmentConfig(
|
|||
val devExpress: ExpressModel? = null,
|
||||
val stakeKitApiKey: String? = null,
|
||||
val blockAidApiKey: String? = null,
|
||||
val tangemApiKey: String? = null,
|
||||
val tangemApiKeyDev: String? = null,
|
||||
val tangemApiKeyStage: String? = null,
|
||||
)
|
||||
|
|
@ -26,6 +26,9 @@ internal object EnvironmentConfigConverter : Converter<EnvironmentConfigModel, E
|
|||
devExpress = value.devExpress,
|
||||
stakeKitApiKey = value.stakeKitApiKey,
|
||||
blockAidApiKey = value.blockaidApiKey,
|
||||
tangemApiKey = value.tangemApiKey,
|
||||
tangemApiKeyDev = value.tangemApiKeyDev,
|
||||
tangemApiKeyStage = value.tangemApiKeyStage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,9 @@ class EnvironmentConfigModel(
|
|||
@Json(name = "moralisApiKey") val moralisApiKey: String?,
|
||||
@Json(name = "nftScanApiKey") val nftScanApiKey: String?,
|
||||
@Json(name = "blockaidApiKey") val blockaidApiKey: String?,
|
||||
@Json(name = "tangemApiKey") val tangemApiKey: String?,
|
||||
@Json(name = "tangemApiKeyDev") val tangemApiKeyDev: String?,
|
||||
@Json(name = "tangemApiKeyStage") val tangemApiKeyStage: String?,
|
||||
@Json(name = "etherscanApiKey") val etherScanApiKey: String?,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@ object PreferencesKeys {
|
|||
|
||||
val WALLETS_NFT_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "walletsNftEnabledStates") }
|
||||
|
||||
val YIELD_SUPPLY_WARNINGS_STATES_KEY by lazy { stringPreferencesKey(name = "yieldSupplyWarningsStates") }
|
||||
|
||||
// region Notifications
|
||||
val NOTIFICATIONS_APPLICATION_ID_KEY by lazy { stringPreferencesKey(name = "notificationsApplicationId") }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
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.visa.model.TangemPayTxHistoryItem
|
||||
|
||||
internal class DefaultTangemPayTxHistoryItemsStore(
|
||||
dataStore: StringKeyDataStore<Map<String, List<TangemPayTxHistoryItem>>>,
|
||||
) : TangemPayTxHistoryItemsStore,
|
||||
StringKeyDataStoreDecorator<String, Map<String, List<TangemPayTxHistoryItem>>>(dataStore) {
|
||||
override fun provideStringKey(key: String): String = key
|
||||
|
||||
override suspend fun getSyncOrNull(key: String, cursor: String): List<TangemPayTxHistoryItem>? {
|
||||
val storedValue = getSyncOrNull(key)
|
||||
return storedValue?.get(cursor)
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, cursor: String, value: List<TangemPayTxHistoryItem>) {
|
||||
val oldValue = getSyncOrNull(key).orEmpty()
|
||||
val newValue = oldValue.toMutableMap().apply { put(cursor, value) }
|
||||
store(key, newValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,9 @@ interface TangemPayStorage {
|
|||
|
||||
suspend fun getAuthTokens(customerWalletAddress: String): VisaAuthTokens?
|
||||
|
||||
suspend fun storeCustomerWalletAddress(customerWalletAddress: String)
|
||||
suspend fun storeOrderId(customerWalletAddress: String, orderId: String)
|
||||
|
||||
suspend fun getCustomerWalletAddress(): String?
|
||||
suspend fun getOrderId(customerWalletAddress: String): String?
|
||||
|
||||
suspend fun clear(customerWalletAddress: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
|
||||
interface TangemPayTxHistoryItemsStore {
|
||||
|
||||
suspend fun getSyncOrNull(key: String, cursor: String): List<TangemPayTxHistoryItem>?
|
||||
|
||||
suspend fun remove(key: String)
|
||||
|
||||
suspend fun store(key: String, cursor: String, value: List<TangemPayTxHistoryItem>)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.yieldsupply
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
internal class DefaultYieldMarketsStore(
|
||||
private val persistenceStore: DataStore<List<YieldMarketToken>>,
|
||||
) : YieldMarketsStore {
|
||||
|
||||
override fun get(): Flow<List<YieldMarketToken>> = persistenceStore.data
|
||||
|
||||
override suspend fun getSyncOrNull(): List<YieldMarketToken>? {
|
||||
return persistenceStore.data.firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<YieldMarketToken>) {
|
||||
persistenceStore.updateData { _ -> items }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.datasource.local.yieldsupply
|
||||
|
||||
import com.tangem.domain.yield.supply.models.YieldMarketToken
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface YieldMarketsStore {
|
||||
|
||||
fun get(): Flow<List<YieldMarketToken>>
|
||||
|
||||
suspend fun getSyncOrNull(): List<YieldMarketToken>?
|
||||
|
||||
suspend fun store(items: List<YieldMarketToken>)
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ class ApiConfigTest {
|
|||
}
|
||||
ApiConfig.ID.TangemTech -> {
|
||||
TangemTech(
|
||||
environmentConfigStorage = mockk(),
|
||||
appVersionProvider = mockk(),
|
||||
authProvider = mockk(),
|
||||
appInfoProvider = mockk(),
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
}
|
||||
ApiConfig.ID.TangemTech -> {
|
||||
TangemTech(
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
appVersionProvider = appVersionProvider,
|
||||
authProvider = appAuthProvider,
|
||||
appInfoProvider = appInfoProvider,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1740,7 +1740,6 @@
|
|||
<string name="yield_module_earn_sheet_total_earnings_title">Total earnings</string>
|
||||
<string name="yield_module_earn_sheet_transfers_title">Transfers to Aave</string>
|
||||
<string name="yield_module_explore_sheet_explore_aave_button_title">Explore Aave</string>
|
||||
<string name="yield_module_explore_sheet_title">Your %s is deposited in Aave </string>
|
||||
<string name="yield_module_fee_policy_sheet_current_fee_note">This is the current supply fee on %s. The live cost will be shown on the Receive Screen.</string>
|
||||
<string name="yield_module_fee_policy_sheet_current_fee_title">Current fee</string>
|
||||
<string name="yield_module_fee_policy_sheet_description">All future %s top-ups will be supplied to Aave automatically, with the transaction fee deducted.</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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.tangem.core.ui.res.LocalIsInDarkTheme
|
||||
|
||||
@Composable
|
||||
fun DialogFullScreen(
|
||||
onDismissRequest: () -> Unit,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = properties.dismissOnBackPress,
|
||||
dismissOnClickOutside = properties.dismissOnClickOutside,
|
||||
securePolicy = properties.securePolicy,
|
||||
usePlatformDefaultWidth = true, // must be true as a part of work around
|
||||
decorFitsSystemWindows = false,
|
||||
),
|
||||
content = {
|
||||
val activityWindow = getActivityWindow()
|
||||
val dialogWindow = getDialogWindow()
|
||||
val parentView = LocalView.current.parent as View
|
||||
SideEffect {
|
||||
if (activityWindow != null && dialogWindow != null) {
|
||||
val attributes = WindowManager.LayoutParams().apply {
|
||||
copyFrom(activityWindow.attributes)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
} else {
|
||||
flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
|
||||
}
|
||||
type = dialogWindow.attributes.type
|
||||
}
|
||||
|
||||
dialogWindow.attributes = attributes
|
||||
parentView.layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
activityWindow.decorView.width,
|
||||
activityWindow.decorView.height,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
val systemUiController = rememberSystemUiController(getActivityWindow())
|
||||
val dialogSystemUiController = rememberSystemUiController(getDialogWindow())
|
||||
|
||||
SideEffect {
|
||||
systemUiController.setSystemBarsColor(color = Color.Transparent)
|
||||
dialogSystemUiController.setSystemBarsColor(color = Color.Transparent)
|
||||
}
|
||||
}
|
||||
|
||||
SystemBarsIconsDisposable(darkIcons = LocalIsInDarkTheme.current.not())
|
||||
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = Color.Transparent) {
|
||||
content()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Window utils
|
||||
@Composable
|
||||
private fun getDialogWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window
|
||||
|
||||
@Composable
|
||||
private fun getActivityWindow(): Window? = LocalView.current.context.getActivityWindow()
|
||||
|
||||
private tailrec fun Context.getActivityWindow(): Window? = when (this) {
|
||||
is Activity -> window
|
||||
is ContextWrapper -> baseContext.getActivityWindow()
|
||||
else -> null
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.WalletConnectDetailsBottomSheetTestTags
|
||||
import com.tangem.core.ui.test.BaseBottomSheetTestTags
|
||||
|
||||
/**
|
||||
* Title component for [TangemModalBottomSheet] with [TangemIconButton] for buttons.
|
||||
|
|
@ -58,7 +58,7 @@ fun TangemModalBottomSheetTitle(
|
|||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.TITLE),
|
||||
.testTag(BaseBottomSheetTestTags.TITLE),
|
||||
)
|
||||
}
|
||||
if (subtitle != null) {
|
||||
|
|
@ -68,7 +68,7 @@ fun TangemModalBottomSheetTitle(
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.DATE),
|
||||
.testTag(BaseBottomSheetTestTags.SUBTITLE),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ fun TangemModalBottomSheetTitle(
|
|||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.align(Alignment.CenterEnd)
|
||||
.testTag(WalletConnectDetailsBottomSheetTestTags.CLOSE_BUTTON),
|
||||
.testTag(BaseBottomSheetTestTags.CLOSE_BUTTON),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
|
||||
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ fun HorizontalActionChips(
|
|||
LazyRow(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TokenDetailsScreenTestTags.HORIZONTAL_ACTION_CHIPS),
|
||||
.testTag(BaseActionButtonsBlockTestTags.HORIZONTAL_ACTION_CHIPS),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
contentPadding = contentPadding,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ 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.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
|
||||
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
|
||||
|
||||
/**
|
||||
* Rounded action button
|
||||
|
|
@ -100,7 +100,7 @@ fun ActionButton(
|
|||
),
|
||||
)
|
||||
},
|
||||
modifier = modifier.testTag(TokenDetailsScreenTestTags.ACTION_BUTTON),
|
||||
modifier = modifier,
|
||||
color = color,
|
||||
containerColor = containerColor,
|
||||
)
|
||||
|
|
@ -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(
|
||||
|
|
@ -145,7 +145,8 @@ fun ActionBaseButton(
|
|||
}
|
||||
},
|
||||
)
|
||||
.background(color = backgroundColor),
|
||||
.background(color = backgroundColor)
|
||||
.testTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON),
|
||||
) {
|
||||
content(Modifier.align(Alignment.Center))
|
||||
|
||||
|
|
@ -163,9 +164,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
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButt
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
iconResId = config.iconResId,
|
||||
enabled = isEnabled,
|
||||
showProgress = config.showProgress,
|
||||
)
|
||||
} else {
|
||||
SecondaryButton(
|
||||
|
|
@ -264,6 +265,7 @@ private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButt
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
size = TangemButtonSize.WideAction,
|
||||
enabled = isEnabled,
|
||||
showProgress = config.showProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ data class NotificationConfig(
|
|||
val text: TextReference,
|
||||
@DrawableRes val iconResId: Int? = null,
|
||||
val onClick: () -> Unit,
|
||||
val showProgress: Boolean = false,
|
||||
) : ButtonsState()
|
||||
|
||||
data class PairButtonsConfig(
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -122,16 +122,18 @@ fun ShowcaseButtons(
|
|||
private fun Showcase_Preview() {
|
||||
TangemThemePreview {
|
||||
Showcase(
|
||||
headerIconRes = R.drawable.ic_notifications_unread_24,
|
||||
headerIconRes = R.drawable.ic_notification_56,
|
||||
headerText = resourceReference(R.string.user_push_notification_agreement_header),
|
||||
showcaseItems = persistentListOf(
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_rocket_launch_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_one),
|
||||
iconRes = R.drawable.ic_rocket_launch_24,
|
||||
title = resourceReference(R.string.user_push_notification_agreement_argument_one_title),
|
||||
subTitle = resourceReference(R.string.user_push_notification_agreement_argument_one_subtitle),
|
||||
),
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_storefront_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_two),
|
||||
iconRes = R.drawable.ic_storefront_24,
|
||||
title = resourceReference(R.string.user_push_notification_agreement_argument_two_title),
|
||||
subTitle = resourceReference(R.string.user_push_notification_agreement_argument_two_subtitle),
|
||||
),
|
||||
),
|
||||
primaryButton = ShowcaseButtonModel(resourceReference(R.string.common_allow), {}),
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ fun ShowcaseContent(
|
|||
repeat(showcaseItems.size) { index ->
|
||||
ShowcaseItem(
|
||||
iconRes = showcaseItems[index].iconRes,
|
||||
text = showcaseItems[index].text,
|
||||
title = showcaseItems[index].title,
|
||||
subtitle = showcaseItems[index].subTitle,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,47 @@
|
|||
package com.tangem.core.ui.components.showcase
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun ShowcaseItem(@DrawableRes iconRes: Int, text: TextReference) {
|
||||
Row {
|
||||
internal fun ShowcaseItem(@DrawableRes iconRes: Int, title: TextReference, subtitle: TextReference) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = TangemTheme.dimens.spacing20),
|
||||
)
|
||||
verticalArrangement = Arrangement.spacedBy(3.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,5 +5,6 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
|
||||
data class ShowcaseItemModel(
|
||||
@DrawableRes val iconRes: Int,
|
||||
val text: TextReference,
|
||||
val title: TextReference,
|
||||
val subTitle: TextReference,
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -18,6 +19,7 @@ import com.tangem.core.ui.components.buttons.actions.ActionButton
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.test.EmptyTransactionBlockTestTags
|
||||
|
||||
/**
|
||||
* Placeholder for transaction's block without content
|
||||
|
|
@ -31,19 +33,24 @@ fun EmptyTransactionBlock(state: EmptyTransactionsBlockState, modifier: Modifier
|
|||
modifier = modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.padding(vertical = TangemTheme.dimens.spacing24),
|
||||
.padding(vertical = TangemTheme.dimens.spacing24)
|
||||
.testTag(EmptyTransactionBlockTestTags.BLOCK),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size64),
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size64)
|
||||
.testTag(EmptyTransactionBlockTestTags.ICON),
|
||||
painter = painterResource(id = state.iconRes),
|
||||
tint = TangemTheme.colors.icon.inactive,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing32),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing32)
|
||||
.testTag(EmptyTransactionBlockTestTags.TEXT),
|
||||
textAlign = TextAlign.Center,
|
||||
text = state.text.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
|
|
@ -51,7 +58,9 @@ fun EmptyTransactionBlock(state: EmptyTransactionsBlockState, modifier: Modifier
|
|||
)
|
||||
|
||||
Buttons(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing18),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing18)
|
||||
.testTag(EmptyTransactionBlockTestTags.EXPLORE_BUTTON),
|
||||
state = state.buttonsState,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object BaseActionButtonsBlockTestTags {
|
||||
const val HORIZONTAL_ACTION_CHIPS = "BASE_ACTION_BUTTONS_BLOCK_HORIZONTAL_ACTION_CHIPS"
|
||||
const val ACTION_BUTTON = "BASE_ACTION_BUTTONS_BLOCK_ACTION_BUTTON"
|
||||
}
|
||||
|
|
@ -2,4 +2,7 @@ package com.tangem.core.ui.test
|
|||
|
||||
object BaseBottomSheetTestTags {
|
||||
const val ACTION_TITLE = "BASE_BOTTOM_SHEET_ACTION_TITLE"
|
||||
const val TITLE = "BASE_BOTTOM_SHEET_TITLE"
|
||||
const val SUBTITLE = "BASE_BOTTOM_SHEET_SUBTITLE"
|
||||
const val CLOSE_BUTTON = "BASE_BOTTOM_SHEET_CLOSE_BUTTON"
|
||||
}
|
||||
|
|
@ -5,12 +5,4 @@ object BuyTokenDetailsScreenTestTags {
|
|||
const val FIAT_CURRENCY_ICON = "BUY_TOKEN_DETAILS_SCREEN_FIAT_CURRENCY_ICON"
|
||||
const val FIAT_AMOUNT_TEXT_FIELD = "BUY_TOKEN_DETAILS_SCREEN_FIAT_AMOUNT_TEXT_FIELD"
|
||||
const val TOKEN_AMOUNT = "BUY_TOKEN_DETAILS_SCREEN_TOKEN_AMOUNT"
|
||||
|
||||
const val PROVIDER_LOADING_TITLE = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_LOADING_TITLE"
|
||||
const val PROVIDER_LOADING_TEXT = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_LOADING_TITLE"
|
||||
|
||||
const val PROVIDER_TITLE = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_TITLE"
|
||||
const val PROVIDER_TEXT = "BUY_TOKEN_DETAILS_SCREEN_PROVIDER_TEXT"
|
||||
|
||||
const val TOS_BLOCK = "BUY_TOKEN_DETAILS_SCREEN_TOS_BLOCK"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object EmptyTransactionBlockTestTags {
|
||||
const val BLOCK = "EMPTY_TRANSACTION_BLOCK"
|
||||
const val ICON = "EMPTY_TRANSACTION_BLOCK_ICON"
|
||||
const val TEXT = "EMPTY_TRANSACTION_BLOCK_TEXT"
|
||||
const val EXPLORE_BUTTON = "EMPTY_TRANSACTION_BLOCK_EXPLORE_BUTTON"
|
||||
}
|
||||
|
|
@ -7,9 +7,9 @@ object MainScreenTestTags {
|
|||
const val TOKEN_LIST_ITEM = "MAIN_SCREEN_TOKEN_LIST_ITEM"
|
||||
const val WALLET_LIST_ITEM = "MAIN_SCREEN_WALLET_LIST_ITEM"
|
||||
const val ORGANIZE_TOKENS_BUTTON = "MAIN_SCREEN_ORGANIZE_TOKENS_BUTTON"
|
||||
const val MULTI_CURRENCY_ACTION_BUTTON = "MAIN_SCREEN_MULTI_CURRENCY_ACTION_BUTTON"
|
||||
const val CARD_TITLE = "MAIN_SCREEN_CARD_TITLE"
|
||||
const val CARD_IMAGE = "MAIN_SCREEN_CARD_IMAGE"
|
||||
const val DEVICES_COUNT = "MAIN_SCREEN_DEVICES_COUNT"
|
||||
|
||||
const val WALLET_BALANCE = "MAIN_SCREEN_WALLET_BALANCE"
|
||||
const val TOTAL_BALANCE_MENU_ITEM = "MAIN_SCREEN_TOTAL_BALANCE_MENU_ITEM"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object OnrampOffersBlockTestTags {
|
||||
const val PROVIDER_NAME = "ONRAMP_OFFERS_BLOCK_PROVIDER_TITLE"
|
||||
const val BEST_RATE_ICON = "ONRAMP_OFFERS_BLOCK_BEST_RATE_ICON"
|
||||
const val BEST_RATE_TITLE = "ONRAMP_OFFERS_BLOCK_BEST_RATE_TITLE"
|
||||
const val OFFER_TOKEN_AMOUNT = "ONRAMP_OFFERS_BLOCK_OFFER_TOKEN_AMOUNT"
|
||||
const val TIMING_ICON = "ONRAMP_OFFERS_BLOCK_TIMING_ICON"
|
||||
const val PAY_WITH = "ONRAMP_OFFERS_BLOCK_PAY_WITH"
|
||||
const val PAYMENT_METHOD_ICON = "ONRAMP_OFFERS_BLOCK_PAYMENT_METHOD_ICON"
|
||||
const val TIMING_TEXT = "ONRAMP_OFFERS_BLOCK_TIMING_TEXT"
|
||||
}
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object SelectPaymentMethodBottomSheetTestTags {
|
||||
|
||||
const val LAZY_LIST = "SELECT_PAYMENT_METHOD_LAZY_LIST"
|
||||
const val PAYMENT_METHOD_ICON = "PAYMENT_METHOD_NAME"
|
||||
const val PAYMENT_METHOD = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PAYMENT_METHOD"
|
||||
const val PAYMENT_METHOD_ICON = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PAYMENT_METHOD_ICON"
|
||||
const val PAYMENT_METHOD_NAME = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PAYMENT_METHOD_NAME"
|
||||
const val UP_TO_TEXT = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_UP_TO_TEXT"
|
||||
const val TOKEN_AMOUNT = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_TOKEN_AMOUNT"
|
||||
const val BEST_RATE_ICON = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_BEST_RATE_ICON"
|
||||
const val PROVIDER_COUNT_ICON = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PROVIDER_COUNT_ICON"
|
||||
const val PROVIDER_COUNT_TEXT = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_PROVIDER_COUNT_TEXT"
|
||||
const val TIMING_ICON = "SELECT_PAYMENT_METHOD_BOTTOM_SHEET_TIMING_ICON"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@ object TokenDetailsScreenTestTags {
|
|||
const val SCREEN_CONTAINER = "TOKEN_DETAILS_SCREEN_CONTAINER"
|
||||
|
||||
const val TOKEN_TITLE = "TOKEN_DETAILS_SCREEN_TOKEN_TITLE"
|
||||
const val ACTION_BUTTON = "TOKEN_DETAILS_SCREEN_ACTION_BUTTON"
|
||||
const val HORIZONTAL_ACTION_CHIPS = "TOKEN_DETAILS_SCREEN_HORIZONTAL_ACTION_CHIPS"
|
||||
|
||||
const val STAKING_BLOCK = "TOKEN_DETAILS_SCREEN_STAKING_BLOCK"
|
||||
const val STAKING_AVAILABLE_BLOCK = "TOKEN_DETAILS_SCREEN_STAKING_AVAILABLE_BLOCK"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package com.tangem.core.ui.test
|
||||
|
||||
object WalletConnectDetailsBottomSheetTestTags {
|
||||
const val TITLE = "WALLET_CONNECT_DETAILS_BOTTOM_SHEET_TITLE"
|
||||
const val DATE = "WALLET_CONNECT_DETAILS_BOTTOM_SHEET_DATE"
|
||||
const val CLOSE_BUTTON = "WALLET_CONNECT_DETAILS_BOTTOM_CLOSE_BUTTON"
|
||||
const val DISCONNECT_BUTTON = "WALLET_CONNECT_DETAILS_BOTTOM_SHEET_DISCONNECT_BUTTON"
|
||||
const val WALLET_ICON = "WALLET_CONNECT_DETAILS_BOTTOM_WALLET_ICON"
|
||||
const val WALLET_TITLE = "WALLET_CONNECT_DETAILS_BOTTOM_WALLET"
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
BIN
core/ui/src/main/res/drawable-hdpi/img_push_reminder.webp
Normal file
BIN
core/ui/src/main/res/drawable-hdpi/img_push_reminder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
core/ui/src/main/res/drawable-mdpi/img_push_reminder.webp
Normal file
BIN
core/ui/src/main/res/drawable-mdpi/img_push_reminder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
core/ui/src/main/res/drawable-xhdpi/img_push_reminder.webp
Normal file
BIN
core/ui/src/main/res/drawable-xhdpi/img_push_reminder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4 KiB |
BIN
core/ui/src/main/res/drawable-xxhdpi/img_push_reminder.webp
Normal file
BIN
core/ui/src/main/res/drawable-xxhdpi/img_push_reminder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
BIN
core/ui/src/main/res/drawable-xxxhdpi/img_push_reminder.webp
Normal file
BIN
core/ui/src/main/res/drawable-xxxhdpi/img_push_reminder.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
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>
|
||||
17
core/ui/src/main/res/drawable/ic_notification_56.xml
Normal file
17
core/ui/src/main/res/drawable/ic_notification_56.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="56dp"
|
||||
android:height="56dp"
|
||||
android:viewportWidth="56"
|
||||
android:viewportHeight="56">
|
||||
<path
|
||||
android:pathData="M10.289,26.72C10.784,17.3 18.567,9.917 28,9.917C37.433,9.917 45.216,17.3 45.711,26.72C45.733,27.123 45.748,27.508 45.764,27.879L45.764,27.88C45.815,29.143 45.86,30.243 46.089,31.298C46.363,32.557 46.869,33.548 47.898,34.32C49.694,35.666 50.75,37.779 50.75,40.023C50.75,43.246 48.213,46.083 44.8,46.083H11.2C7.787,46.083 5.25,43.246 5.25,40.023C5.25,37.779 6.306,35.666 8.102,34.32C9.131,33.548 9.637,32.557 9.911,31.298C10.14,30.243 10.185,29.143 10.236,27.88L10.236,27.88C10.252,27.508 10.267,27.123 10.289,26.72Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M24.567,3.207C25.605,2.527 26.873,2.333 28,2.333C29.127,2.333 30.395,2.527 31.433,3.207C32.586,3.961 33.25,5.182 33.25,6.708C33.25,8.099 32.706,9.552 31.879,10.665C31.065,11.76 29.73,12.833 28,12.833C26.27,12.833 24.935,11.76 24.121,10.665C23.294,9.552 22.75,8.099 22.75,6.708C22.75,5.182 23.414,3.961 24.567,3.207Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M21,42C22.288,42 23.333,43.045 23.333,44.333C23.333,46.911 25.423,49 28,49C30.577,49 32.667,46.911 32.667,44.333C32.667,43.045 33.711,42 35,42C36.289,42 37.333,43.045 37.333,44.333C37.333,49.488 33.154,53.667 28,53.667C22.845,53.667 18.667,49.488 18.667,44.333C18.667,43.045 19.711,42 21,42Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
12
core/ui/src/main/res/drawable/ic_notification_square_24.xml
Normal file
12
core/ui/src/main/res/drawable/ic_notification_square_24.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="25dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="25">
|
||||
<path
|
||||
android:pathData="M14.75,5.75C14.75,3.541 16.541,1.75 18.75,1.75C20.959,1.75 22.75,3.541 22.75,5.75C22.75,7.959 20.959,9.75 18.75,9.75C16.541,9.75 14.75,7.959 14.75,5.75Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M12.663,2.75C13.279,2.75 13.587,2.75 13.707,2.914C13.827,3.078 13.72,3.412 13.508,4.08C13.34,4.607 13.25,5.168 13.25,5.75C13.25,8.788 15.712,11.25 18.75,11.25C19.339,11.25 19.907,11.157 20.44,10.985C21.091,10.775 21.418,10.671 21.582,10.79C21.746,10.91 21.747,11.209 21.749,11.809C21.75,12.172 21.75,12.55 21.75,12.942V13.058C21.75,15.248 21.75,16.968 21.569,18.312C21.384,19.688 20.997,20.781 20.139,21.639C19.281,22.497 18.188,22.884 16.812,23.069C15.468,23.25 13.748,23.25 11.558,23.25H11.442C9.252,23.25 7.532,23.25 6.188,23.069C4.812,22.884 3.72,22.497 2.861,21.639C2.003,20.781 1.616,19.688 1.431,18.312C1.25,16.968 1.25,15.248 1.25,13.058V12.942C1.25,10.752 1.25,9.032 1.431,7.688C1.616,6.312 2.003,5.22 2.861,4.361C3.72,3.503 4.812,3.116 6.188,2.931C7.532,2.75 9.252,2.75 11.442,2.75H12.663ZM7,15.75C6.586,15.75 6.25,16.086 6.25,16.5C6.25,16.914 6.586,17.25 7,17.25H15C15.414,17.25 15.75,16.914 15.75,16.5C15.75,16.086 15.414,15.75 15,15.75H7ZM7,10.75C6.586,10.75 6.25,11.086 6.25,11.5C6.25,11.914 6.586,12.25 7,12.25H11C11.414,12.25 11.75,11.914 11.75,11.5C11.75,11.086 11.414,10.75 11,10.75H7Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
23
core/ui/src/main/res/drawable/ic_promo_kyc_36.xml
Normal file
23
core/ui/src/main/res/drawable/ic_promo_kyc_36.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="36dp"
|
||||
android:height="36dp"
|
||||
android:viewportWidth="36"
|
||||
android:viewportHeight="36">
|
||||
<path
|
||||
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
|
||||
android:strokeAlpha="0.1"
|
||||
android:fillColor="#0099FF"
|
||||
android:fillAlpha="0.1"/>
|
||||
<path
|
||||
android:pathData="M17.758,21.123C18.172,21.12 18.51,21.453 18.514,21.867C18.517,22.281 18.184,22.619 17.771,22.623C16.646,22.633 15.542,23.074 14.779,23.887C14.496,24.189 14.021,24.203 13.719,23.92C13.417,23.636 13.402,23.161 13.686,22.859C14.756,21.719 16.265,21.136 17.758,21.123ZM18.982,17.373C18.982,16.671 18.425,16.123 17.764,16.123C17.102,16.123 16.546,16.672 16.546,17.373C16.546,18.075 17.102,18.623 17.764,18.623C18.425,18.623 18.982,18.075 18.982,17.373ZM20.482,17.373C20.482,18.881 19.276,20.123 17.764,20.123C16.251,20.123 15.046,18.881 15.046,17.373C15.046,15.866 16.251,14.623 17.764,14.623C19.276,14.623 20.482,15.866 20.482,17.373Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M8.482,20.742V16.006C8.482,14.465 8.481,13.23 8.618,12.262C8.759,11.268 9.06,10.447 9.742,9.801C10.346,9.229 11.088,8.946 11.974,8.799C12.841,8.655 13.915,8.63 15.229,8.625C15.644,8.624 15.981,8.958 15.982,9.372C15.984,9.786 15.649,10.123 15.235,10.125C13.905,10.13 12.95,10.157 12.22,10.278C11.508,10.397 11.086,10.594 10.773,10.89C10.431,11.215 10.219,11.661 10.104,12.473C9.985,13.311 9.982,14.421 9.982,16.006V20.742C9.982,22.328 9.985,23.437 10.104,24.275C10.219,25.087 10.431,25.534 10.773,25.858C11.122,26.189 11.61,26.397 12.486,26.509C13.381,26.623 14.562,26.624 16.232,26.624H18.232C18.647,26.624 18.982,26.96 18.982,27.374C18.982,27.788 18.647,28.124 18.232,28.124H16.232C14.603,28.124 13.308,28.125 12.297,27.996C11.268,27.865 10.418,27.588 9.742,26.947C9.06,26.301 8.759,25.48 8.618,24.486C8.481,23.519 8.482,22.283 8.482,20.742ZM25.482,16.874V16.006C25.482,14.421 25.48,13.311 25.361,12.473C25.246,11.661 25.034,11.215 24.691,10.89C24.379,10.594 23.956,10.397 23.245,10.278C22.515,10.157 21.56,10.13 20.229,10.125C19.815,10.123 19.481,9.786 19.482,9.372C19.484,8.958 19.821,8.624 20.235,8.625C21.549,8.63 22.624,8.655 23.491,8.799C24.377,8.946 25.119,9.229 25.723,9.801C26.405,10.447 26.706,11.268 26.847,12.262C26.984,13.23 26.982,14.465 26.982,16.006V16.874C26.982,17.288 26.647,17.624 26.232,17.624C25.818,17.624 25.483,17.288 25.482,16.874Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M17.733,6.623C18.164,6.623 18.503,6.618 18.81,6.689C19.473,6.841 20.057,7.255 20.383,7.855C20.536,8.136 20.606,8.466 20.691,8.836L20.774,9.195C20.856,9.547 20.931,9.869 20.964,10.138C20.998,10.417 21,10.74 20.854,11.062C20.704,11.391 20.455,11.659 20.153,11.843C19.867,12.016 19.558,12.073 19.27,12.099C18.986,12.124 18.634,12.123 18.231,12.123H17.235C16.831,12.123 16.48,12.124 16.195,12.099C15.907,12.073 15.598,12.016 15.313,11.843C15.01,11.659 14.761,11.391 14.611,11.062C14.465,10.74 14.467,10.417 14.501,10.138C14.534,9.869 14.609,9.547 14.691,9.195L14.774,8.836C14.859,8.466 14.929,8.136 15.082,7.855C15.408,7.255 15.992,6.841 16.655,6.689C16.962,6.618 17.301,6.623 17.733,6.623ZM17.733,8.123C17.223,8.123 17.092,8.128 16.992,8.15C16.71,8.215 16.503,8.382 16.399,8.571C16.368,8.629 16.342,8.714 16.236,9.174L16.153,9.534C16.064,9.918 16.011,10.148 15.99,10.318C15.981,10.391 15.982,10.432 15.983,10.45C16.002,10.486 16.035,10.527 16.091,10.562C16.092,10.562 16.095,10.563 16.098,10.564C16.104,10.567 16.115,10.571 16.133,10.575C16.17,10.585 16.23,10.596 16.326,10.605C16.53,10.622 16.804,10.623 17.235,10.623H18.231C18.661,10.623 18.935,10.622 19.139,10.605C19.235,10.596 19.295,10.585 19.332,10.575C19.35,10.571 19.361,10.567 19.367,10.564C19.37,10.563 19.372,10.562 19.373,10.562C19.429,10.528 19.462,10.486 19.481,10.45C19.482,10.432 19.484,10.392 19.475,10.318C19.454,10.148 19.401,9.918 19.313,9.534L19.23,9.174C19.123,8.714 19.097,8.629 19.066,8.571C18.962,8.382 18.755,8.215 18.473,8.15C18.373,8.128 18.242,8.123 17.733,8.123Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M27.483,23.373C27.483,21.578 26.028,20.123 24.233,20.123C22.438,20.123 20.983,21.578 20.983,23.373C20.983,25.168 22.438,26.623 24.233,26.623C26.028,26.623 27.483,25.168 27.483,23.373ZM22.572,24.493H22.574L22.575,24.494C22.202,24.316 22.045,23.868 22.223,23.494C22.401,23.121 22.848,22.963 23.222,23.141L23.224,23.142L23.227,23.143C23.228,23.143 23.23,23.145 23.232,23.146C23.235,23.147 23.24,23.149 23.244,23.151C23.253,23.156 23.265,23.162 23.278,23.169C23.303,23.183 23.337,23.202 23.376,23.226C23.451,23.272 23.549,23.338 23.658,23.426C23.669,23.409 23.68,23.393 23.691,23.376C24.021,22.875 24.552,22.178 25.21,21.824C25.575,21.628 26.03,21.764 26.227,22.129C26.423,22.494 26.286,22.948 25.921,23.145C25.618,23.308 25.26,23.722 24.942,24.203C24.795,24.427 24.673,24.637 24.589,24.791C24.547,24.868 24.515,24.93 24.493,24.972C24.483,24.993 24.475,25.009 24.47,25.019C24.467,25.024 24.466,25.027 24.465,25.029C24.345,25.279 24.097,25.444 23.82,25.456C23.544,25.468 23.282,25.326 23.142,25.087C23.005,24.855 22.847,24.698 22.729,24.603C22.67,24.555 22.622,24.523 22.593,24.505L22.572,24.493ZM28.983,23.373C28.983,25.997 26.856,28.123 24.233,28.123C21.609,28.123 19.483,25.997 19.483,23.373C19.483,20.75 21.609,18.623 24.233,18.623C26.856,18.623 28.983,20.75 28.983,23.373Z"
|
||||
android:fillColor="#0099FF"/>
|
||||
</vector>
|
||||
15
core/ui/src/main/res/drawable/ic_stars_24.xml
Normal file
15
core/ui/src/main/res/drawable/ic_stars_24.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="25dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="25">
|
||||
<path
|
||||
android:pathData="M12,2.75C12.414,2.75 12.75,3.086 12.75,3.5C12.75,5.506 13.761,7.575 15.343,9.157C16.925,10.739 18.994,11.75 21,11.75C21.414,11.75 21.75,12.086 21.75,12.5C21.75,12.914 21.414,13.25 21,13.25C18.994,13.25 16.925,14.261 15.343,15.843C13.761,17.425 12.75,19.494 12.75,21.5C12.75,21.914 12.414,22.25 12,22.25C11.586,22.25 11.25,21.914 11.25,21.5C11.25,19.494 10.239,17.425 8.657,15.843C7.075,14.261 5.006,13.25 3,13.25C2.586,13.25 2.25,12.914 2.25,12.5C2.25,12.086 2.586,11.75 3,11.75C5.006,11.75 7.075,10.739 8.657,9.157C10.239,7.575 11.25,5.506 11.25,3.5C11.25,3.086 11.586,2.75 12,2.75Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M19.25,1.75C19.463,1.75 19.648,1.898 19.695,2.106L19.93,3.146C20.09,3.856 20.644,4.41 21.354,4.57L22.394,4.805C22.602,4.852 22.75,5.037 22.75,5.25C22.75,5.463 22.602,5.648 22.394,5.695L21.354,5.93C20.644,6.09 20.09,6.644 19.93,7.354L19.695,8.394C19.648,8.602 19.463,8.75 19.25,8.75C19.037,8.75 18.852,8.602 18.805,8.394L18.57,7.354C18.41,6.644 17.856,6.09 17.146,5.93L16.106,5.695C15.898,5.648 15.75,5.463 15.75,5.25C15.75,5.037 15.898,4.852 16.106,4.805L17.146,4.57C17.856,4.41 18.41,3.856 18.57,3.146L18.805,2.106C18.852,1.898 19.037,1.75 19.25,1.75Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M4.75,16.25C4.963,16.25 5.148,16.398 5.195,16.606L5.43,17.646C5.59,18.356 6.144,18.91 6.854,19.07L7.894,19.305C8.102,19.352 8.25,19.537 8.25,19.75C8.25,19.963 8.102,20.148 7.894,20.195L6.854,20.43C6.144,20.59 5.59,21.144 5.43,21.854L5.195,22.894C5.148,23.102 4.963,23.25 4.75,23.25C4.537,23.25 4.352,23.102 4.305,22.894L4.07,21.854C3.91,21.144 3.356,20.59 2.646,20.43L1.606,20.195C1.398,20.148 1.25,19.963 1.25,19.75C1.25,19.537 1.398,19.352 1.606,19.305L2.646,19.07C3.356,18.91 3.91,18.356 4.07,17.646L4.305,16.606C4.352,16.398 4.537,16.25 4.75,16.25Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="36dp"
|
||||
android:height="36dp"
|
||||
android:viewportWidth="36"
|
||||
android:viewportHeight="36">
|
||||
<path
|
||||
android:pathData="M18,18m-18,0a18,18 0,1 1,36 0a18,18 0,1 1,-36 0"
|
||||
android:strokeAlpha="0.1"
|
||||
android:fillColor="#0099FF"
|
||||
android:fillAlpha="0.1"/>
|
||||
<path
|
||||
android:pathData="M9,10L27,10A3,3 0,0 1,30 13L30,23A3,3 0,0 1,27 26L9,26A3,3 0,0 1,6 23L6,13A3,3 0,0 1,9 10z"
|
||||
android:fillColor="#0099FF"/>
|
||||
<path
|
||||
android:pathData="M13.051,20.582H11.502L10.34,16.5C10.285,16.312 10.167,16.146 9.995,16.068C9.565,15.871 9.092,15.715 8.575,15.636V15.479H11.071C11.415,15.479 11.674,15.715 11.717,15.989L12.32,18.933L13.868,15.479H15.375L13.051,20.582ZM16.235,20.58H14.771L15.976,15.477H17.44L16.235,20.58ZM19.335,16.891C19.378,16.616 19.636,16.459 19.938,16.459C20.412,16.419 20.927,16.498 21.358,16.694L21.617,15.595C21.186,15.438 20.712,15.359 20.282,15.359C18.862,15.359 17.829,16.066 17.829,17.047C17.829,17.794 18.561,18.185 19.077,18.422C19.636,18.657 19.852,18.814 19.809,19.049C19.809,19.403 19.378,19.56 18.948,19.56C18.432,19.56 17.915,19.442 17.442,19.246L17.184,20.345C17.7,20.541 18.259,20.62 18.776,20.62C20.368,20.659 21.358,19.952 21.358,18.892C21.358,17.558 19.335,17.479 19.335,16.891ZM26.48,20.58L25.318,15.477H24.07C23.811,15.477 23.553,15.634 23.467,15.869L21.315,20.58H22.822L23.122,19.834H24.973L25.146,20.58H26.48ZM24.283,16.852L24.713,18.775H23.508L24.283,16.852Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue