Updated on 2026-08-14
This commit is contained in:
commit
bc84d8f5f8
579 changed files with 13604 additions and 3422 deletions
|
|
@ -25,6 +25,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.staking) // TODO staking create staking models module
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(deps.tangem.blockchain)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,40 @@ package com.tangem.datasource.api.common.adapter
|
|||
|
||||
import com.squareup.moshi.*
|
||||
import com.squareup.moshi.adapters.EnumJsonAdapter
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.*
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionStatusDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionTypeDTO
|
||||
|
||||
/**
|
||||
* Object to create a adapter for enum types with support for unknown enum values.
|
||||
*/
|
||||
object UnknownEnumMoshiAdapter {
|
||||
|
||||
fun <T : Enum<T>> create(enumType: Class<T>, defaultValue: T): JsonAdapter<T> =
|
||||
EnumJsonAdapter.create(enumType).withUnknownFallback(defaultValue)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Enum<T>> create(enumType: Class<out Enum<*>>, defaultValue: Enum<*>): JsonAdapter<out Enum<*>> {
|
||||
return EnumJsonAdapter.create(enumType as Class<T>).withUnknownFallback(defaultValue as T)
|
||||
}
|
||||
}
|
||||
|
||||
fun Moshi.Builder.addStakeKitEnumFallbackAdapters(): Moshi.Builder {
|
||||
val map = mapOf(
|
||||
NetworkTypeDTO::class.java to NetworkTypeDTO.UNKNOWN,
|
||||
StakingActionTypeDTO::class.java to StakingActionTypeDTO.UNKNOWN,
|
||||
YieldDTO.RewardTypeDTO::class.java to YieldDTO.RewardTypeDTO.UNKNOWN,
|
||||
YieldBalanceWrapperDTO.BalanceDTO.BalanceType::class.java to
|
||||
YieldBalanceWrapperDTO.BalanceDTO.BalanceType.UNKNOWN,
|
||||
StakingTransactionTypeDTO::class.java to StakingTransactionTypeDTO.UNKNOWN,
|
||||
StakingTransactionStatusDTO::class.java to StakingTransactionStatusDTO.UNKNOWN,
|
||||
StakingActionStatusDTO::class.java to StakingActionStatusDTO.UNKNOWN,
|
||||
)
|
||||
|
||||
return apply {
|
||||
map.forEach { entry ->
|
||||
val enumClass = entry.key
|
||||
val unknownValue = entry.value
|
||||
add(enumClass, UnknownEnumMoshiAdapter.create(enumClass, unknownValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +1,60 @@
|
|||
package com.tangem.datasource.api.stakekit
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody
|
||||
import com.tangem.datasource.api.stakekit.models.request.RevenueOption
|
||||
import com.tangem.datasource.api.stakekit.models.request.YieldType
|
||||
import com.tangem.datasource.api.stakekit.models.request.*
|
||||
import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYield
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.Yield
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapper
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenWithYieldDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import retrofit2.http.*
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
interface StakeKitApi {
|
||||
|
||||
@GET("yields/enabled")
|
||||
suspend fun getMultipleYields(
|
||||
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean,
|
||||
@Query("type") type: YieldType,
|
||||
@Query("revenueOption") revenueOption: RevenueOption,
|
||||
@Query("page") page: Int,
|
||||
@Query("network") network: String,
|
||||
@Query("limit") limit: Int,
|
||||
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean? = null,
|
||||
@Query("type") type: YieldType? = null,
|
||||
@Query("revenueOption") revenueOption: RevenueOption? = null,
|
||||
@Query("page") page: Int? = null,
|
||||
@Query("network") network: String? = null,
|
||||
@Query("limit") limit: Int? = null,
|
||||
): ApiResponse<EnabledYieldsResponse>
|
||||
|
||||
@GET("yields/{integrationId}")
|
||||
suspend fun getSingleYield(
|
||||
@Path("integrationId") integrationId: String,
|
||||
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean = false,
|
||||
): ApiResponse<Yield>
|
||||
): ApiResponse<YieldDTO>
|
||||
|
||||
@GET("yields/balances")
|
||||
suspend fun getMultipleYieldBalances(
|
||||
@Body body: List<YieldBalanceRequestBody>,
|
||||
): ApiResponse<List<YieldBalanceWrapper>>
|
||||
): ApiResponse<List<YieldBalanceWrapperDTO>>
|
||||
|
||||
@GET("yields/{integrationId}/balances")
|
||||
suspend fun getSingleYieldBalance(
|
||||
@Path("integrationId") integrationId: String,
|
||||
@Body body: YieldBalanceRequestBody,
|
||||
): ApiResponse<YieldBalanceWrapper>
|
||||
): ApiResponse<YieldBalanceWrapperDTO>
|
||||
|
||||
@GET("tokens")
|
||||
suspend fun getTokens(): ApiResponse<List<TokenWithYield>>
|
||||
suspend fun getTokens(): ApiResponse<List<TokenWithYieldDTO>>
|
||||
|
||||
@POST("actions/enter")
|
||||
suspend fun createEnterAction(@Body body: EnterActionRequestBody): ApiResponse<EnterActionResponse>
|
||||
|
||||
@PATCH("transactions/{transactionId}")
|
||||
suspend fun constructTransaction(
|
||||
@Path("transactionId") transactionId: String,
|
||||
@Body body: ConstructTransactionRequestBody,
|
||||
): ApiResponse<StakingTransactionDTO>
|
||||
|
||||
@POST("transactions/{transactionId}/submit_hash")
|
||||
suspend fun submitTransactionHash(
|
||||
@Path("transactionId") transactionId: String,
|
||||
@Body body: SubmitTransactionHashRequestBody,
|
||||
): ApiResponse<Unit>
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
data class Address(
|
||||
@Json(name = "address")
|
||||
val address: String,
|
||||
@Json(name = "additionalAddresses")
|
||||
val additionalAddresses: AdditionalAddresses? = null,
|
||||
@Json(name = "explorerUrl")
|
||||
val explorerUrl: String? = null,
|
||||
) {
|
||||
|
||||
data class AdditionalAddresses(
|
||||
// cosmos-specific
|
||||
@Json(name = "cosmosPubKey")
|
||||
val cosmosPubKey: String? = null,
|
||||
|
||||
// binance-specific
|
||||
@Json(name = "binanceBeaconAddress")
|
||||
val binanceBeaconAddress: String? = null,
|
||||
|
||||
// solana-specific
|
||||
@Json(name = "stakeAccounts")
|
||||
val stakeAccounts: List<String>? = null,
|
||||
@Json(name = "lidoStakeAccounts")
|
||||
val lidoStakeAccounts: List<String>? = null,
|
||||
|
||||
// tezos-specific
|
||||
@Json(name = "tezosPubKey")
|
||||
val tezosPubKey: String? = null,
|
||||
|
||||
// avalanche-specific
|
||||
@Json(name = "cAddressBech")
|
||||
val cAddressBech: String? = null,
|
||||
@Json(name = "pAddressBech")
|
||||
val pAddressBech: String? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class ConstructTransactionRequestBody(
|
||||
@Json(name = "gasArgs")
|
||||
val gasArgs: GasArgs? = null,
|
||||
@Json(name = "ledgerWalletAPICompatible")
|
||||
val ledgerWalletAPICompatible: Boolean? = null,
|
||||
) {
|
||||
data class GasArgs(
|
||||
// cosmos-specific
|
||||
@Json(name = "gasPrice")
|
||||
val gasPrice: BigDecimal? = null,
|
||||
// EVM eip 1559 specific
|
||||
@Json(name = "type")
|
||||
val type: Int? = null,
|
||||
@Json(name = "maxFeePerGas")
|
||||
val maxFeePerGas: BigDecimal? = null,
|
||||
@Json(name = "maxPriorityFeePerGas")
|
||||
val maxPriorityFeePerGas: BigDecimal? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
|
||||
data class EnterActionRequestBody(
|
||||
@Json(name = "integrationId")
|
||||
val integrationId: String,
|
||||
@Json(name = "addresses")
|
||||
val addresses: Address,
|
||||
@Json(name = "args")
|
||||
val args: EnterActionRequestBodyArgs,
|
||||
@Json(name = "referralCode")
|
||||
val referralCode: String? = null,
|
||||
) {
|
||||
|
||||
data class EnterActionRequestBodyArgs(
|
||||
@Json(name = "amount")
|
||||
val amount: String,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: String? = null,
|
||||
@Json(name = "validatorAddresses")
|
||||
val validatorAddresses: List<String>? = null,
|
||||
@Json(name = "providerId")
|
||||
val providerId: String? = null,
|
||||
@Json(name = "duration")
|
||||
val duration: String? = null,
|
||||
@Json(name = "nfts")
|
||||
val nfts: List<YieldBalanceWrapperDTO.BalanceDTO.PendingAction.PendingActionArgs.Nft>? = null,
|
||||
@Json(name = "ledgerWalletAPICompatible")
|
||||
val ledgerWalletAPICompatible: Boolean? = null,
|
||||
@Json(name = "tronResource")
|
||||
val tronResource: String? = null,
|
||||
@Json(name = "signatureVerification")
|
||||
val signatureVerification: SignatureVerification? = null,
|
||||
@Json(name = "inputToken")
|
||||
val inputToken: TokenDTO? = null,
|
||||
)
|
||||
|
||||
data class SignatureVerification(
|
||||
@Json(name = "message")
|
||||
val message: String,
|
||||
@Json(name = "signed")
|
||||
val signed: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.datasource.api.stakekit.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
data class SubmitTransactionHashRequestBody(
|
||||
@Json(name = "hash")
|
||||
val hash: String,
|
||||
)
|
||||
|
|
@ -8,23 +8,6 @@ data class YieldBalanceRequestBody(
|
|||
@Json(name = "integrationId") val integrationId: String? = null,
|
||||
) {
|
||||
|
||||
data class Address(
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "additionalAddresses") val additionalAddresses: AdditionalAddresses? = null,
|
||||
@Json(name = "explorerUrl") val explorerUrl: String,
|
||||
) {
|
||||
|
||||
data class AdditionalAddresses(
|
||||
@Json(name = "cosmosPubKey") val cosmosPubKey: String? = null,
|
||||
@Json(name = "binanceBeaconAddress") val binanceBeaconAddress: String? = null,
|
||||
@Json(name = "stakeAccounts") val stakeAccounts: List<String>? = null,
|
||||
@Json(name = "lidoStakeAccounts") val lidoStakeAccounts: List<String>? = null,
|
||||
@Json(name = "tezosPubKey") val tezosPubKey: String? = null,
|
||||
@Json(name = "cAddressBech") val cAddressBech: String? = null,
|
||||
@Json(name = "pAddressBech") val pAddressBech: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
data class YieldBalanceRequestArgs(
|
||||
@Json(name = "validatorAddresses") val validatorAddresses: List<String>,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package com.tangem.datasource.api.stakekit.models.response
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.Yield
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EnabledYieldsResponse(
|
||||
@Json(name = "data")
|
||||
val data: List<Yield>,
|
||||
val data: List<YieldDTO>,
|
||||
@Json(name = "hasNextPage")
|
||||
val hasNextPage: Boolean,
|
||||
@Json(name = "limit")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EnterActionResponse(
|
||||
@Json(name = "id")
|
||||
val id: String,
|
||||
@Json(name = "integrationId")
|
||||
val integrationId: String,
|
||||
@Json(name = "status")
|
||||
val status: StakingActionStatusDTO,
|
||||
@Json(name = "type")
|
||||
val type: StakingActionTypeDTO,
|
||||
@Json(name = "currentStepIndex")
|
||||
val currentStepIndex: Int,
|
||||
@Json(name = "amount")
|
||||
val amount: BigDecimal,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: String?,
|
||||
@Json(name = "validatorAddresses")
|
||||
val validatorAddresses: List<String>?,
|
||||
@Json(name = "transactions")
|
||||
val transactions: List<StakingTransactionDTO>?,
|
||||
@Json(name = "createdAt")
|
||||
val createdAt: DateTime,
|
||||
)
|
||||
|
|
@ -4,13 +4,13 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AddressArgument(
|
||||
data class AddressArgumentDTO(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "network")
|
||||
val network: String? = null,
|
||||
@Json(name = "minimum")
|
||||
val minimum: Int? = null,
|
||||
val minimum: Double? = null,
|
||||
@Json(name = "maximum")
|
||||
val maximum: Int? = null,
|
||||
val maximum: Double? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
enum class NetworkTypeDTO {
|
||||
@Json(name = "avalanche-c")
|
||||
AVALANCHE_C,
|
||||
|
||||
@Json(name = "avalanche-atomic")
|
||||
AVALANCHE_ATOMIC,
|
||||
|
||||
@Json(name = "avalanche-p")
|
||||
AVALANCHE_P,
|
||||
|
||||
@Json(name = "arbitrum")
|
||||
ARBITRUM,
|
||||
|
||||
@Json(name = "binance")
|
||||
BINANCE,
|
||||
|
||||
@Json(name = "celo")
|
||||
CELO,
|
||||
|
||||
@Json(name = "ethereum")
|
||||
ETHEREUM,
|
||||
|
||||
@Json(name = "ethereum-goerli")
|
||||
ETHEREUM_GOERLI,
|
||||
|
||||
@Json(name = "ethereum-holesky")
|
||||
ETHEREUM_HOLESKY,
|
||||
|
||||
@Json(name = "fantom")
|
||||
FANTOM,
|
||||
|
||||
@Json(name = "harmony")
|
||||
HARMONY,
|
||||
|
||||
@Json(name = "optimism")
|
||||
OPTIMISM,
|
||||
|
||||
@Json(name = "polygon")
|
||||
POLYGON,
|
||||
|
||||
@Json(name = "gnosis")
|
||||
GNOSIS,
|
||||
|
||||
@Json(name = "moonriver")
|
||||
MOONRIVER,
|
||||
|
||||
@Json(name = "okc")
|
||||
OKC,
|
||||
|
||||
@Json(name = "zksync")
|
||||
ZKSYNC,
|
||||
|
||||
@Json(name = "viction")
|
||||
VICTION,
|
||||
|
||||
@Json(name = "agoric")
|
||||
AGORIC,
|
||||
|
||||
@Json(name = "akash")
|
||||
AKASH,
|
||||
|
||||
@Json(name = "axelar")
|
||||
AXELAR,
|
||||
|
||||
@Json(name = "band-protocol")
|
||||
BAND_PROTOCOL,
|
||||
|
||||
@Json(name = "bitsong")
|
||||
BITSONG,
|
||||
|
||||
@Json(name = "canto")
|
||||
CANTO,
|
||||
|
||||
@Json(name = "chihuahua")
|
||||
CHIHUAHUA,
|
||||
|
||||
@Json(name = "comdex")
|
||||
COMDEX,
|
||||
|
||||
@Json(name = "coreum")
|
||||
COREUM,
|
||||
|
||||
@Json(name = "cosmos")
|
||||
COSMOS,
|
||||
|
||||
@Json(name = "crescent")
|
||||
CRESCENT,
|
||||
|
||||
@Json(name = "cronos")
|
||||
CRONOS,
|
||||
|
||||
@Json(name = "cudos")
|
||||
CUDOS,
|
||||
|
||||
@Json(name = "desmos")
|
||||
DESMOS,
|
||||
|
||||
@Json(name = "dydx")
|
||||
DYDX,
|
||||
|
||||
@Json(name = "evmos")
|
||||
EVMOS,
|
||||
|
||||
@Json(name = "fetch-ai")
|
||||
FETCH_AI,
|
||||
|
||||
@Json(name = "gravity-bridge")
|
||||
GRAVITY_BRIDGE,
|
||||
|
||||
@Json(name = "injective")
|
||||
INJECTIVE,
|
||||
|
||||
@Json(name = "irisnet")
|
||||
IRISNET,
|
||||
|
||||
@Json(name = "juno")
|
||||
JUNO,
|
||||
|
||||
@Json(name = "kava")
|
||||
KAVA,
|
||||
|
||||
@Json(name = "ki-network")
|
||||
KI_NETWORK,
|
||||
|
||||
@Json(name = "mars-protocol")
|
||||
MARS_PROTOCOL,
|
||||
|
||||
@Json(name = "nym")
|
||||
NYM,
|
||||
|
||||
@Json(name = "okex-chain")
|
||||
OKEX_CHAIN,
|
||||
|
||||
@Json(name = "onomy")
|
||||
ONOMY,
|
||||
|
||||
@Json(name = "osmosis")
|
||||
OSMOSIS,
|
||||
|
||||
@Json(name = "persistence")
|
||||
PERSISTENCE,
|
||||
|
||||
@Json(name = "quicksilver")
|
||||
QUICKSILVER,
|
||||
|
||||
@Json(name = "regen")
|
||||
REGEN,
|
||||
|
||||
@Json(name = "secret")
|
||||
SECRET,
|
||||
|
||||
@Json(name = "sentinel")
|
||||
SENTINEL,
|
||||
|
||||
@Json(name = "sommelier")
|
||||
SOMMELIER,
|
||||
|
||||
@Json(name = "stafi")
|
||||
STAFI,
|
||||
|
||||
@Json(name = "stargaze")
|
||||
STARGAZE,
|
||||
|
||||
@Json(name = "stride")
|
||||
STRIDE,
|
||||
|
||||
@Json(name = "teritori")
|
||||
TERITORI,
|
||||
|
||||
@Json(name = "tgrade")
|
||||
TGRADE,
|
||||
|
||||
@Json(name = "umee")
|
||||
UMEE,
|
||||
|
||||
@Json(name = "polkadot")
|
||||
POLKADOT,
|
||||
|
||||
@Json(name = "kusama")
|
||||
KUSAMA,
|
||||
|
||||
@Json(name = "westend")
|
||||
WESTEND,
|
||||
|
||||
@Json(name = "binancebeacon")
|
||||
BINANCEBEACON,
|
||||
|
||||
@Json(name = "near")
|
||||
NEAR,
|
||||
|
||||
@Json(name = "solana")
|
||||
SOLANA,
|
||||
|
||||
@Json(name = "tezos")
|
||||
TEZOS,
|
||||
|
||||
@Json(name = "tron")
|
||||
TRON,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Token(
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "network") val network: NetworkType,
|
||||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "decimals") val decimals: Int,
|
||||
@Json(name = "address") val address: String?,
|
||||
@Json(name = "coinGeckoId") val coinGeckoId: String?,
|
||||
@Json(name = "logoURI") val logoURI: String?,
|
||||
@Json(name = "isPoints") val isPoints: Boolean?,
|
||||
) {
|
||||
enum class NetworkType {
|
||||
@Json(name = "avalanche-c")
|
||||
AVALANCHE_C,
|
||||
|
||||
@Json(name = "avalanche-atomic")
|
||||
AVALANCHE_ATOMIC,
|
||||
|
||||
@Json(name = "avalanche-p")
|
||||
AVALANCHE_P,
|
||||
|
||||
@Json(name = "arbitrum")
|
||||
ARBITRUM,
|
||||
|
||||
@Json(name = "binance")
|
||||
BINANCE,
|
||||
|
||||
@Json(name = "celo")
|
||||
CELO,
|
||||
|
||||
@Json(name = "ethereum")
|
||||
ETHEREUM,
|
||||
|
||||
@Json(name = "ethereum-goerli")
|
||||
ETHEREUM_GOERLI,
|
||||
|
||||
@Json(name = "ethereum-holesky")
|
||||
ETHEREUM_HOLESKY,
|
||||
|
||||
@Json(name = "fantom")
|
||||
FANTOM,
|
||||
|
||||
@Json(name = "harmony")
|
||||
HARMONY,
|
||||
|
||||
@Json(name = "optimism")
|
||||
OPTIMISM,
|
||||
|
||||
@Json(name = "polygon")
|
||||
POLYGON,
|
||||
|
||||
@Json(name = "gnosis")
|
||||
GNOSIS,
|
||||
|
||||
@Json(name = "moonriver")
|
||||
MOONRIVER,
|
||||
|
||||
@Json(name = "okc")
|
||||
OKC,
|
||||
|
||||
@Json(name = "zksync")
|
||||
ZKSYNC,
|
||||
|
||||
@Json(name = "viction")
|
||||
VICTION,
|
||||
|
||||
@Json(name = "agoric")
|
||||
AGORIC,
|
||||
|
||||
@Json(name = "akash")
|
||||
AKASH,
|
||||
|
||||
@Json(name = "axelar")
|
||||
AXELAR,
|
||||
|
||||
@Json(name = "band-protocol")
|
||||
BAND_PROTOCOL,
|
||||
|
||||
@Json(name = "bitsong")
|
||||
BITSONG,
|
||||
|
||||
@Json(name = "canto")
|
||||
CANTO,
|
||||
|
||||
@Json(name = "chihuahua")
|
||||
CHIHUAHUA,
|
||||
|
||||
@Json(name = "comdex")
|
||||
COMDEX,
|
||||
|
||||
@Json(name = "coreum")
|
||||
COREUM,
|
||||
|
||||
@Json(name = "cosmos")
|
||||
COSMOS,
|
||||
|
||||
@Json(name = "crescent")
|
||||
CRESCENT,
|
||||
|
||||
@Json(name = "cronos")
|
||||
CRONOS,
|
||||
|
||||
@Json(name = "cudos")
|
||||
CUDOS,
|
||||
|
||||
@Json(name = "desmos")
|
||||
DESMOS,
|
||||
|
||||
@Json(name = "dydx")
|
||||
DYDX,
|
||||
|
||||
@Json(name = "evmos")
|
||||
EVMOS,
|
||||
|
||||
@Json(name = "fetch-ai")
|
||||
FETCH_AI,
|
||||
|
||||
@Json(name = "gravity-bridge")
|
||||
GRAVITY_BRIDGE,
|
||||
|
||||
@Json(name = "injective")
|
||||
INJECTIVE,
|
||||
|
||||
@Json(name = "irisnet")
|
||||
IRISNET,
|
||||
|
||||
@Json(name = "juno")
|
||||
JUNO,
|
||||
|
||||
@Json(name = "kava")
|
||||
KAVA,
|
||||
|
||||
@Json(name = "ki-network")
|
||||
KI_NETWORK,
|
||||
|
||||
@Json(name = "mars-protocol")
|
||||
MARS_PROTOCOL,
|
||||
|
||||
@Json(name = "nym")
|
||||
NYM,
|
||||
|
||||
@Json(name = "okex-chain")
|
||||
OKEX_CHAIN,
|
||||
|
||||
@Json(name = "onomy")
|
||||
ONOMY,
|
||||
|
||||
@Json(name = "osmosis")
|
||||
OSMOSIS,
|
||||
|
||||
@Json(name = "persistence")
|
||||
PERSISTENCE,
|
||||
|
||||
@Json(name = "quicksilver")
|
||||
QUICKSILVER,
|
||||
|
||||
@Json(name = "regen")
|
||||
REGEN,
|
||||
|
||||
@Json(name = "secret")
|
||||
SECRET,
|
||||
|
||||
@Json(name = "sentinel")
|
||||
SENTINEL,
|
||||
|
||||
@Json(name = "sommelier")
|
||||
SOMMELIER,
|
||||
|
||||
@Json(name = "stafi")
|
||||
STAFI,
|
||||
|
||||
@Json(name = "stargaze")
|
||||
STARGAZE,
|
||||
|
||||
@Json(name = "stride")
|
||||
STRIDE,
|
||||
|
||||
@Json(name = "teritori")
|
||||
TERITORI,
|
||||
|
||||
@Json(name = "tgrade")
|
||||
TGRADE,
|
||||
|
||||
@Json(name = "umee")
|
||||
UMEE,
|
||||
|
||||
@Json(name = "polkadot")
|
||||
POLKADOT,
|
||||
|
||||
@Json(name = "kusama")
|
||||
KUSAMA,
|
||||
|
||||
@Json(name = "westend")
|
||||
WESTEND,
|
||||
|
||||
@Json(name = "binancebeacon")
|
||||
BINANCEBEACON,
|
||||
|
||||
@Json(name = "near")
|
||||
NEAR,
|
||||
|
||||
@Json(name = "solana")
|
||||
SOLANA,
|
||||
|
||||
@Json(name = "tezos")
|
||||
TEZOS,
|
||||
|
||||
@Json(name = "tron")
|
||||
TRON,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenDTO(
|
||||
@Json(name = "name")
|
||||
val name: String,
|
||||
@Json(name = "network")
|
||||
val network: NetworkTypeDTO,
|
||||
@Json(name = "symbol")
|
||||
val symbol: String,
|
||||
@Json(name = "decimals")
|
||||
val decimals: Int,
|
||||
@Json(name = "address")
|
||||
val address: String?,
|
||||
@Json(name = "coinGeckoId")
|
||||
val coinGeckoId: String?,
|
||||
@Json(name = "logoURI")
|
||||
val logoURI: String?,
|
||||
@Json(name = "isPoints")
|
||||
val isPoints: Boolean?,
|
||||
)
|
||||
|
|
@ -4,7 +4,7 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenWithYield(
|
||||
@Json(name = "token") val token: Token,
|
||||
data class TokenWithYieldDTO(
|
||||
@Json(name = "token") val token: TokenDTO,
|
||||
@Json(name = "availableYields") val availableYieldIds: List<String>,
|
||||
)
|
||||
|
|
@ -2,19 +2,20 @@ package com.tangem.datasource.api.stakekit.models.response.model
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class YieldBalanceWrapper(
|
||||
data class YieldBalanceWrapperDTO(
|
||||
@Json(name = "balances")
|
||||
val balances: List<Balance>,
|
||||
val balances: List<BalanceDTO>,
|
||||
@Json(name = "integrationId")
|
||||
val integrationId: String?,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Balance(
|
||||
data class BalanceDTO(
|
||||
@Json(name = "groupId")
|
||||
val groupId: String,
|
||||
@Json(name = "type")
|
||||
|
|
@ -28,7 +29,7 @@ data class YieldBalanceWrapper(
|
|||
@Json(name = "pendingActions")
|
||||
val pendingActions: List<PendingAction>,
|
||||
@Json(name = "token")
|
||||
val token: Token,
|
||||
val tokenDTO: TokenDTO,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: String?,
|
||||
@Json(name = "validatorAddresses")
|
||||
|
|
@ -61,12 +62,14 @@ data class YieldBalanceWrapper(
|
|||
|
||||
@Json(name = "unlocking")
|
||||
UNLOCKING,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PendingAction(
|
||||
@Json(name = "type")
|
||||
val type: StakingActionType,
|
||||
val type: StakingActionTypeDTO,
|
||||
@Json(name = "passthrough")
|
||||
val passthrough: String,
|
||||
@Json(name = "args")
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class YieldBalances(
|
||||
@Json(name = "balances")
|
||||
val balances: List<Balance>,
|
||||
@Json(name = "integrationId")
|
||||
val integrationId: String,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Balance(
|
||||
@Json(name = "groupId")
|
||||
val groupId: String,
|
||||
@Json(name = "type")
|
||||
val type: BalanceType,
|
||||
@Json(name = "amount")
|
||||
val amount: BigDecimal,
|
||||
@Json(name = "date")
|
||||
val date: DateTime?,
|
||||
@Json(name = "pricePerShare")
|
||||
val pricePerShare: BigDecimal,
|
||||
@Json(name = "pendingActions")
|
||||
val pendingActions: List<PendingAction>,
|
||||
@Json(name = "token")
|
||||
val token: Token,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: String?,
|
||||
@Json(name = "validatorAddresses")
|
||||
val validatorAddresses: List<String>?,
|
||||
@Json(name = "providerId")
|
||||
val providerId: String?,
|
||||
) {
|
||||
|
||||
enum class BalanceType {
|
||||
@Json(name = "available")
|
||||
AVAILABLE,
|
||||
|
||||
@Json(name = "staked")
|
||||
STAKED,
|
||||
|
||||
@Json(name = "unstaking")
|
||||
UNSTAKING,
|
||||
|
||||
@Json(name = "unstaked")
|
||||
UNSTAKED,
|
||||
|
||||
@Json(name = "preparing")
|
||||
PREPARING,
|
||||
|
||||
@Json(name = "rewards")
|
||||
REWARDS,
|
||||
|
||||
@Json(name = "locked")
|
||||
LOCKED,
|
||||
|
||||
@Json(name = "unlocking")
|
||||
UNLOCKING,
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PendingAction(
|
||||
@Json(name = "type")
|
||||
val type: StakingActionType,
|
||||
@Json(name = "passthrough")
|
||||
val passthrough: String,
|
||||
@Json(name = "args")
|
||||
val args: PendingActionArgs?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PendingActionArgs(
|
||||
@Json(name = "amount")
|
||||
val amount: Amount?,
|
||||
@Json(name = "duration")
|
||||
val duration: Duration?,
|
||||
@Json(name = "validatorAddress")
|
||||
val validatorAddress: Required?,
|
||||
@Json(name = "validatorAddresses")
|
||||
val validatorAddresses: Required?,
|
||||
@Json(name = "nfts")
|
||||
val nfts: List<Nft>?,
|
||||
@Json(name = "tronResource")
|
||||
val tronResource: TronResource?,
|
||||
@Json(name = "signatureVerification")
|
||||
val signatureVerification: Required?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Amount(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "minimum")
|
||||
val minimum: BigDecimal?,
|
||||
@Json(name = "maximum")
|
||||
val maximum: BigDecimal?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Duration(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "minimum")
|
||||
val minimum: Int?,
|
||||
@Json(name = "maximum")
|
||||
val maximum: Int?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Nft(
|
||||
@Json(name = "baycId")
|
||||
val baycId: Required?,
|
||||
@Json(name = "maycId")
|
||||
val maycId: Required?,
|
||||
@Json(name = "bakcId")
|
||||
val bakcId: Required?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TronResource(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
@Json(name = "options")
|
||||
val options: List<String>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Required(
|
||||
@Json(name = "required")
|
||||
val required: Boolean,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,33 +5,33 @@ import com.squareup.moshi.JsonClass
|
|||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Yield(
|
||||
data class YieldDTO(
|
||||
@Json(name = "id")
|
||||
val id: String,
|
||||
@Json(name = "token")
|
||||
val token: Token,
|
||||
val token: TokenDTO,
|
||||
@Json(name = "tokens")
|
||||
val tokens: List<Token>,
|
||||
val tokens: List<TokenDTO>,
|
||||
@Json(name = "args")
|
||||
val args: Args,
|
||||
val args: ArgsDTO,
|
||||
@Json(name = "status")
|
||||
val status: Status,
|
||||
val status: StatusDTO,
|
||||
@Json(name = "apy")
|
||||
val apy: BigDecimal,
|
||||
@Json(name = "rewardRate")
|
||||
val rewardRate: Double,
|
||||
@Json(name = "rewardType")
|
||||
val rewardType: RewardType,
|
||||
val rewardType: RewardTypeDTO,
|
||||
@Json(name = "metadata")
|
||||
val metadata: Metadata,
|
||||
val metadata: MetadataDTO,
|
||||
@Json(name = "validators")
|
||||
val validators: List<Validator>,
|
||||
val validators: List<ValidatorDTO>,
|
||||
@Json(name = "isAvailable")
|
||||
val isAvailable: Boolean,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Status(
|
||||
data class StatusDTO(
|
||||
@Json(name = "enter")
|
||||
val enter: Boolean,
|
||||
@Json(name = "exit")
|
||||
|
|
@ -39,7 +39,7 @@ data class Yield(
|
|||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Args(
|
||||
data class ArgsDTO(
|
||||
@Json(name = "enter")
|
||||
val enter: Enter,
|
||||
@Json(name = "exit")
|
||||
|
|
@ -50,20 +50,20 @@ data class Yield(
|
|||
@Json(name = "addresses")
|
||||
val addresses: Addresses,
|
||||
@Json(name = "args")
|
||||
val args: Map<String, AddressArgument>,
|
||||
val args: Map<String, AddressArgumentDTO>,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Addresses(
|
||||
@Json(name = "address")
|
||||
val address: AddressArgument,
|
||||
val address: AddressArgumentDTO,
|
||||
@Json(name = "additionalAddresses")
|
||||
val additionalAddresses: Map<String, AddressArgument>? = null,
|
||||
val additionalAddresses: Map<String, AddressArgumentDTO>? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Validator(
|
||||
data class ValidatorDTO(
|
||||
@Json(name = "address")
|
||||
val address: String,
|
||||
@Json(name = "status")
|
||||
|
|
@ -75,7 +75,7 @@ data class Yield(
|
|||
@Json(name = "website")
|
||||
val website: String?,
|
||||
@Json(name = "apr")
|
||||
val apr: Double?,
|
||||
val apr: BigDecimal?,
|
||||
@Json(name = "commission")
|
||||
val commission: Double?,
|
||||
@Json(name = "stakedBalance")
|
||||
|
|
@ -87,7 +87,7 @@ data class Yield(
|
|||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Metadata(
|
||||
data class MetadataDTO(
|
||||
@Json(name = "name")
|
||||
val name: String,
|
||||
@Json(name = "logoURI")
|
||||
|
|
@ -97,19 +97,19 @@ data class Yield(
|
|||
@Json(name = "documentation")
|
||||
val documentation: String?,
|
||||
@Json(name = "gasFeeToken")
|
||||
val gasFeeToken: Token,
|
||||
val gasFeeTokenDTO: TokenDTO,
|
||||
@Json(name = "token")
|
||||
val token: Token,
|
||||
val tokenDTO: TokenDTO,
|
||||
@Json(name = "tokens")
|
||||
val tokens: List<Token>,
|
||||
val tokensDTO: List<TokenDTO>,
|
||||
@Json(name = "type")
|
||||
val type: String,
|
||||
@Json(name = "rewardSchedule")
|
||||
val rewardSchedule: String,
|
||||
@Json(name = "cooldownPeriod")
|
||||
val cooldownPeriod: Period,
|
||||
val cooldownPeriod: PeriodDTO,
|
||||
@Json(name = "warmupPeriod")
|
||||
val warmupPeriod: Period,
|
||||
val warmupPeriod: PeriodDTO,
|
||||
@Json(name = "rewardClaiming")
|
||||
val rewardClaiming: String,
|
||||
@Json(name = "defaultValidator")
|
||||
|
|
@ -119,28 +119,30 @@ data class Yield(
|
|||
@Json(name = "supportsMultipleValidators")
|
||||
val supportsMultipleValidators: Boolean,
|
||||
@Json(name = "revshare")
|
||||
val revshare: Enabled,
|
||||
val revshare: EnabledDTO,
|
||||
@Json(name = "fee")
|
||||
val fee: Enabled,
|
||||
val fee: EnabledDTO,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Period(
|
||||
data class PeriodDTO(
|
||||
@Json(name = "days")
|
||||
val days: Int,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Enabled(
|
||||
data class EnabledDTO(
|
||||
@Json(name = "enabled")
|
||||
val enabled: Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
enum class RewardType {
|
||||
enum class RewardTypeDTO {
|
||||
@Json(name = "apy")
|
||||
APY, // auto
|
||||
APY, // compound rate
|
||||
@Json(name = "apr")
|
||||
APR, // manual
|
||||
APR, // simple rate,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.action
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
enum class StakingActionStatusDTO {
|
||||
@Json(name = "CANCELED")
|
||||
CANCELED,
|
||||
|
||||
@Json(name = "CREATED")
|
||||
CREATED,
|
||||
|
||||
@Json(name = "WAITING_FOR_NEXT")
|
||||
WAITING_FOR_NEXT,
|
||||
|
||||
@Json(name = "PROCESSING")
|
||||
PROCESSING,
|
||||
|
||||
@Json(name = "FAILED")
|
||||
FAILED,
|
||||
|
||||
@Json(name = "SUCCESS")
|
||||
SUCCESS,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model
|
||||
package com.tangem.datasource.api.stakekit.models.response.model.action
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
enum class StakingActionType {
|
||||
enum class StakingActionTypeDTO {
|
||||
@Json(name = "STAKE")
|
||||
STAKE,
|
||||
|
||||
|
|
@ -47,4 +47,6 @@ enum class StakingActionType {
|
|||
|
||||
@Json(name = "MIGRATE")
|
||||
MIGRATE,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class StakingGasEstimateDTO(
|
||||
@Json(name = "amount")
|
||||
val amount: BigDecimal,
|
||||
@Json(name = "token")
|
||||
val token: TokenDTO,
|
||||
@Json(name = "gasLimit")
|
||||
val gasLimit: BigDecimal,
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class StakingTransactionDTO(
|
||||
@Json(name = "id")
|
||||
val id: String,
|
||||
@Json(name = "network")
|
||||
val network: NetworkTypeDTO,
|
||||
@Json(name = "status")
|
||||
val status: StakingTransactionStatusDTO,
|
||||
@Json(name = "type")
|
||||
val type: StakingTransactionTypeDTO,
|
||||
@Json(name = "hash")
|
||||
val hash: String?,
|
||||
@Json(name = "signedTransaction")
|
||||
val signedTransaction: String?,
|
||||
@Json(name = "unsignedTransaction")
|
||||
val unsignedTransaction: String?,
|
||||
@Json(name = "stepIndex")
|
||||
val stepIndex: Int,
|
||||
@Json(name = "error")
|
||||
val error: String?,
|
||||
@Json(name = "gasEstimate")
|
||||
val gasEstimate: StakingGasEstimateDTO?,
|
||||
@Json(name = "stakeId")
|
||||
val stakeId: String?,
|
||||
@Json(name = "explorerUrl")
|
||||
val explorerUrl: String?,
|
||||
@Json(name = "ledgerHwAppId")
|
||||
val ledgerHwAppId: String?,
|
||||
@Json(name = "isMessage")
|
||||
val isMessage: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
enum class StakingTransactionStatusDTO {
|
||||
@Json(name = "NOT_FOUND")
|
||||
NOT_FOUND,
|
||||
|
||||
@Json(name = "CREATED")
|
||||
CREATED,
|
||||
|
||||
@Json(name = "BLOCKED")
|
||||
BLOCKED,
|
||||
|
||||
@Json(name = "WAITING_FOR_SIGNATURE")
|
||||
WAITING_FOR_SIGNATURE,
|
||||
|
||||
@Json(name = "SIGNED")
|
||||
SIGNED,
|
||||
|
||||
@Json(name = "BROADCASTED")
|
||||
BROADCASTED,
|
||||
|
||||
@Json(name = "PENDING")
|
||||
PENDING,
|
||||
|
||||
@Json(name = "CONFIRMED")
|
||||
CONFIRMED,
|
||||
|
||||
@Json(name = "FAILED")
|
||||
FAILED,
|
||||
|
||||
@Json(name = "SKIPPED")
|
||||
SKIPPED,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.transaction
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
||||
enum class StakingTransactionTypeDTO {
|
||||
@Json(name = "SWAP")
|
||||
SWAP,
|
||||
|
||||
@Json(name = "DEPOSIT")
|
||||
DEPOSIT,
|
||||
|
||||
@Json(name = "APPROVAL")
|
||||
APPROVAL,
|
||||
|
||||
@Json(name = "STAKE")
|
||||
STAKE,
|
||||
|
||||
@Json(name = "CLAIM_UNSTAKED")
|
||||
CLAIM_UNSTAKED,
|
||||
|
||||
@Json(name = "CLAIM_REWARDS")
|
||||
CLAIM_REWARDS,
|
||||
|
||||
@Json(name = "RESTAKE_REWARDS")
|
||||
RESTAKE_REWARDS,
|
||||
|
||||
@Json(name = "UNSTAKE")
|
||||
UNSTAKE,
|
||||
|
||||
@Json(name = "SPLIT")
|
||||
SPLIT,
|
||||
|
||||
@Json(name = "MERGE")
|
||||
MERGE,
|
||||
|
||||
@Json(name = "LOCK")
|
||||
LOCK,
|
||||
|
||||
@Json(name = "UNLOCK")
|
||||
UNLOCK,
|
||||
|
||||
@Json(name = "SUPPLY")
|
||||
SUPPLY,
|
||||
|
||||
@Json(name = "BRIDGE")
|
||||
BRIDGE,
|
||||
|
||||
@Json(name = "VOTE")
|
||||
VOTE,
|
||||
|
||||
@Json(name = "REVOKE")
|
||||
REVOKE,
|
||||
|
||||
@Json(name = "RESTAKE")
|
||||
RESTAKE,
|
||||
|
||||
@Json(name = "REBOND")
|
||||
REBOND,
|
||||
|
||||
@Json(name = "WITHDRAW")
|
||||
WITHDRAW,
|
||||
|
||||
@Json(name = "CREATE_ACCOUNT")
|
||||
CREATE_ACCOUNT,
|
||||
|
||||
@Json(name = "REVEAL")
|
||||
REVEAL,
|
||||
|
||||
@Json(name = "MIGRATE")
|
||||
MIGRATE,
|
||||
|
||||
@Json(name = "UTXO_P_TO_C_IMPORT")
|
||||
UTXO_P_TO_C_IMPORT,
|
||||
|
||||
@Json(name = "UTXO_C_TO_P_IMPORT")
|
||||
UTXO_C_TO_P_IMPORT,
|
||||
|
||||
@Json(name = "UNFREEZE_LEGACY")
|
||||
UNFREEZE_LEGACY,
|
||||
|
||||
@Json(name = "UNFREEZE_LEGACY_BANDWIDTH")
|
||||
UNFREEZE_LEGACY_BANDWIDTH,
|
||||
|
||||
@Json(name = "UNFREEZE_LEGACY_ENERGY")
|
||||
UNFREEZE_LEGACY_ENERGY,
|
||||
|
||||
@Json(name = "UNFREEZE_BANDWIDTH")
|
||||
UNFREEZE_BANDWIDTH,
|
||||
|
||||
@Json(name = "UNFREEZE_ENERGY")
|
||||
UNFREEZE_ENERGY,
|
||||
|
||||
@Json(name = "FREEZE_BANDWIDTH")
|
||||
FREEZE_BANDWIDTH,
|
||||
|
||||
@Json(name = "FREEZE_ENERGY")
|
||||
FREEZE_ENERGY,
|
||||
|
||||
@Json(name = "UNDELEGATE_BANDWIDTH")
|
||||
UNDELEGATE_BANDWIDTH,
|
||||
|
||||
@Json(name = "UNDELEGATE_ENERGY")
|
||||
UNDELEGATE_ENERGY,
|
||||
|
||||
@Json(name = "P2P_NODE_REQUEST")
|
||||
P2P_NODE_REQUEST,
|
||||
|
||||
@Json(name = "LUGANODES_PROVISION")
|
||||
LUGANODES_PROVISION,
|
||||
|
||||
@Json(name = "LUGANODES_EXIT_REQUEST")
|
||||
LUGANODES_EXIT_REQUEST,
|
||||
|
||||
@Json(name = "INFSTONES_PROVISION")
|
||||
INFSTONES_PROVISION,
|
||||
|
||||
@Json(name = "INFSTONES_EXIT_REQUEST")
|
||||
INFSTONES_EXIT_REQUEST,
|
||||
|
||||
@Json(name = "INFSTONES_CLAIM_REQUEST")
|
||||
INFSTONES_CLAIM_REQUEST,
|
||||
|
||||
UNKNOWN,
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.token.DefaultAssetsStore
|
||||
import com.tangem.datasource.local.token.AssetsStore
|
||||
import com.tangem.datasource.local.token.DefaultExpressAssetsStore
|
||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -11,11 +11,11 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object AssetsStoreModule {
|
||||
internal object ExpressAssetsStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAssetsStore(): AssetsStore {
|
||||
return DefaultAssetsStore(dataStore = RuntimeDataStore())
|
||||
fun provideExpressAssetsStore(): ExpressAssetsStore {
|
||||
return DefaultExpressAssetsStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,10 @@ import com.squareup.moshi.Moshi
|
|||
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.datasource.api.common.adapter.*
|
||||
import com.tangem.datasource.api.common.adapter.BigDecimalAdapter
|
||||
import com.tangem.datasource.api.common.adapter.DateTimeAdapter
|
||||
import com.tangem.datasource.api.common.adapter.LocalDateAdapter
|
||||
import com.tangem.datasource.api.common.adapter.UnknownEnumMoshiAdapter
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.Token
|
||||
import com.tangem.datasource.config.models.ProviderModel
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -35,10 +34,7 @@ class MoshiModule {
|
|||
.add(LocalDateAdapter())
|
||||
.add(DateTimeAdapter())
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.add(
|
||||
Token.NetworkType::class.java,
|
||||
UnknownEnumMoshiAdapter.create(Token.NetworkType::class.java, Token.NetworkType.UNKNOWN),
|
||||
)
|
||||
.addStakeKitEnumFallbackAdapters()
|
||||
.build()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.token.DefaultStakingYieldsStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object StakingTokensStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingTokensStore(): StakingYieldsStore {
|
||||
return DefaultStakingYieldsStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
}
|
||||
|
|
@ -94,6 +94,20 @@ object PreferencesKeys {
|
|||
val IS_WALLET_NAMES_MIGRATION_DONE_KEY by lazy { booleanPreferencesKey(name = "isWalletNamesMigrationDone") }
|
||||
|
||||
fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region")
|
||||
|
||||
// region Permission
|
||||
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")
|
||||
|
||||
fun getShouldShowInitialPermissionScreen(permission: String) =
|
||||
booleanPreferencesKey("shouldShowInitialPushPermissionScreen_$permission")
|
||||
|
||||
fun getIsFirstTimeAskingPermission(permission: String) =
|
||||
booleanPreferencesKey("shouldAskInitialPushPermission_$permission")
|
||||
|
||||
fun getPermissionLaunchCount(permission: String) = intPreferencesKey("pushPermissionLaunchCount_$permission")
|
||||
|
||||
fun getPermissionDaysCount(permission: String) = longPreferencesKey("pushPermissionDaysCount_$permission")
|
||||
// endregion
|
||||
}
|
||||
|
||||
/** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import com.tangem.datasource.api.express.models.response.Asset
|
|||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
internal class DefaultAssetsStore(
|
||||
internal class DefaultExpressAssetsStore(
|
||||
private val dataStore: StringKeyDataStore<List<Asset>>,
|
||||
) : AssetsStore {
|
||||
) : ExpressAssetsStore {
|
||||
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId): List<Asset>? {
|
||||
return dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.staking.model.StakingTokenWithYield
|
||||
|
||||
internal class DefaultStakingTokensStore(
|
||||
private val dataStore: StringKeyDataStore<List<StakingTokenWithYield>>,
|
||||
) : StakingTokensStore {
|
||||
|
||||
override suspend fun getSyncOrNull(): List<StakingTokenWithYield>? {
|
||||
return dataStore.getSyncOrNull(STAKING_TOKENS_KEY)
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<StakingTokenWithYield>) {
|
||||
dataStore.store(STAKING_TOKENS_KEY, items)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STAKING_TOKENS_KEY = "STAKING_TOKENS_KEY"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
|
||||
internal class DefaultStakingYieldsStore(
|
||||
private val dataStore: StringKeyDataStore<List<YieldDTO>>,
|
||||
) : StakingYieldsStore {
|
||||
|
||||
override suspend fun getSyncOrNull(): List<YieldDTO>? {
|
||||
return dataStore.getSyncOrNull(STAKING_YIELDS_KEY)
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<YieldDTO>) {
|
||||
dataStore.store(STAKING_YIELDS_KEY, items)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STAKING_YIELDS_KEY = "STAKING_YIELDS_KEY"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.datasource.local.token
|
|||
import com.tangem.datasource.api.express.models.response.Asset
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface AssetsStore {
|
||||
interface ExpressAssetsStore {
|
||||
|
||||
suspend fun getSyncOrNull(userWalletId: UserWalletId): List<Asset>?
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.domain.staking.model.StakingTokenWithYield
|
||||
|
||||
interface StakingTokensStore {
|
||||
|
||||
suspend fun getSyncOrNull(): List<StakingTokenWithYield>?
|
||||
|
||||
suspend fun store(items: List<StakingTokenWithYield>)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
|
||||
interface StakingYieldsStore {
|
||||
|
||||
suspend fun getSyncOrNull(): List<YieldDTO>?
|
||||
|
||||
suspend fun store(items: List<YieldDTO>)
|
||||
}
|
||||
|
|
@ -35,5 +35,6 @@ sealed class RequestHeader(vararg pairs: Pair<String, () -> String>) {
|
|||
|
||||
class StakeKit(stakeKitAuthProvider: StakeKitAuthProvider) : RequestHeader(
|
||||
"X-API-KEY" to { stakeKitAuthProvider.getApiKey() },
|
||||
"accept" to { "application/json" },
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.core.decompose.di
|
||||
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import dagger.BindsInstance
|
||||
|
|
@ -37,6 +38,14 @@ interface DecomposeComponent {
|
|||
*/
|
||||
fun uiMessageSender(@BindsInstance uiMessageSender: UiMessageSender): Builder
|
||||
|
||||
/**
|
||||
* Sets the parameters container for the component.
|
||||
*
|
||||
* @param container The parameters container to set.
|
||||
* @return The builder instance.
|
||||
*/
|
||||
fun paramsContainer(@BindsInstance container: ParamsContainer): Builder
|
||||
|
||||
/**
|
||||
* Builds the Decompose component.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.decompose.factory
|
||||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
||||
interface ComponentFactory<P : Any, C : Any> {
|
||||
|
||||
fun create(context: AppComponentContext, params: P): C
|
||||
}
|
||||
|
|
@ -22,24 +22,43 @@ interface ModelsEntryPoint {
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets or creates a component's [Model].
|
||||
* Gets or creates a component's [Model] with no parameters.
|
||||
*/
|
||||
inline fun <reified M : Model> AppComponentContext.getOrCreateModel(): M {
|
||||
val modelKey = "model_${M::class.simpleName}"
|
||||
inline fun <reified M : Model> AppComponentContext.getOrCreateModel(): M = getOrCreateModel(params = null)
|
||||
|
||||
/**
|
||||
* Gets or creates a component's [Model].
|
||||
*
|
||||
* Be careful with objects you pass in parameters as they will be used in [Model] lifecycle.
|
||||
* If you pass a object with different lifecycle than the [Model] then you can face memory leaks.
|
||||
*
|
||||
* @param params The parameters to store in the [ParamsContainer],
|
||||
|
||||
*/
|
||||
inline fun <reified M : Model, reified P : Any> AppComponentContext.getOrCreateModel(params: P?): M {
|
||||
val entryPoint = instanceKeeper.getOrCreateSimple(key = "modelsEntryPoint") {
|
||||
val hiltComponent = hiltComponentBuilder
|
||||
.router(router)
|
||||
.uiMessageSender(messageSender)
|
||||
.let { builder ->
|
||||
if (params != null) {
|
||||
val container = MutableParamsContainer(params)
|
||||
|
||||
builder.paramsContainer(container)
|
||||
} else {
|
||||
builder
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
EntryPoints.get(hiltComponent, ModelsEntryPoint::class.java)
|
||||
}
|
||||
|
||||
val modelKey = "model_${M::class.simpleName}"
|
||||
val model = instanceKeeper.getOrCreate(modelKey) {
|
||||
requireNotNull(entryPoint.models()[M::class.java]?.get()) {
|
||||
"Model ${M::class.simpleName} is not provided"
|
||||
}
|
||||
} as M
|
||||
}
|
||||
|
||||
val isModelExist = tags.getOrElse(modelKey) { false } as Boolean
|
||||
|
|
@ -47,5 +66,5 @@ inline fun <reified M : Model> AppComponentContext.getOrCreateModel(): M {
|
|||
tags[modelKey] = true
|
||||
}
|
||||
|
||||
return model as M
|
||||
return model
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.core.decompose.model
|
||||
|
||||
/**
|
||||
* Lazy container for [Model] params.
|
||||
*
|
||||
* This contrainer can be accessed by DI because it's provided via [com.tangem.core.decompose.di.DecomposeComponent].
|
||||
* */
|
||||
interface ParamsContainer {
|
||||
|
||||
/** Returns stored value if it is of type [T], otherwise returns null. */
|
||||
fun <T> get(): T?
|
||||
|
||||
/** Returns stored value if it is of type [T], otherwise throws an exception. */
|
||||
fun <T> require(): T
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutable implementation of [ParamsContainer].
|
||||
*
|
||||
* ***You should not use this class directly in other modules, use immutable [ParamsContainer] instead.***
|
||||
* */
|
||||
class MutableParamsContainer private constructor() : ParamsContainer {
|
||||
|
||||
private var value: Any? = null
|
||||
|
||||
/** Stores [value] inside a container, replaces any previous stored value. */
|
||||
fun set(value: Any) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T> get(): T? = value as? T
|
||||
|
||||
override fun <T> require(): T = get() ?: error("Contrainer is empty or contains a value of a different type.")
|
||||
|
||||
companion object {
|
||||
|
||||
/** Creates a new [MutableParamsContainer] and stores [value] inside it. */
|
||||
operator fun <T : Any> invoke(value: T): MutableParamsContainer {
|
||||
return MutableParamsContainer().apply {
|
||||
set(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.arkivanov.decompose.router.stack.pop
|
|||
import com.arkivanov.decompose.router.stack.popWhile
|
||||
import com.arkivanov.decompose.router.stack.pushNew
|
||||
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal class DefaultRouter(
|
||||
private val navigationProvider: AppNavigationProvider,
|
||||
|
|
@ -19,6 +20,15 @@ internal class DefaultRouter(
|
|||
navigation.pushNew(route, onComplete)
|
||||
}
|
||||
|
||||
override fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
val newRoutes = routes.toList()
|
||||
|
||||
navigation.navigate(
|
||||
transformer = { newRoutes },
|
||||
onComplete = { newStack, _ -> onComplete(newStack.size == newRoutes.size) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
navigation.pop(onComplete)
|
||||
}
|
||||
|
|
@ -29,4 +39,11 @@ internal class DefaultRouter(
|
|||
onComplete = onComplete,
|
||||
)
|
||||
}
|
||||
|
||||
override fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
navigation.popWhile(
|
||||
predicate = { it::class != routeClass },
|
||||
onComplete = onComplete,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.core.decompose.navigation
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class DummyRouter : Router {
|
||||
|
||||
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
onComplete(true)
|
||||
}
|
||||
|
||||
override fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
onComplete(true)
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
onComplete(true)
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
onComplete(true)
|
||||
}
|
||||
|
||||
override fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
onComplete(true)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.core.decompose.navigation
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Interface for a router in the application.
|
||||
* It provides methods for navigating through the application.
|
||||
* It provides methods for navigating through the application by stack.
|
||||
*/
|
||||
interface Router {
|
||||
|
||||
|
|
@ -14,6 +16,14 @@ interface Router {
|
|||
*/
|
||||
fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit = {})
|
||||
|
||||
/**
|
||||
* Replaces ***all*** routes in the navigation stack with the specified [routes].
|
||||
*
|
||||
* @param routes The routes to replace the current stack with.
|
||||
* @param onComplete The callback to be invoked when the operation is complete.
|
||||
*/
|
||||
fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit = {})
|
||||
|
||||
/**
|
||||
* Pops the top route from the navigation stack.
|
||||
*
|
||||
|
|
@ -22,10 +32,18 @@ interface Router {
|
|||
fun pop(onComplete: (isSuccess: Boolean) -> Unit = {})
|
||||
|
||||
/**
|
||||
* Pops routes from the navigation stack until the specified route is found.
|
||||
* Pops routes from the navigation stack until the specified [route] is found.
|
||||
*
|
||||
* @param route The route to pop to.
|
||||
* @param onComplete The callback to be invoked when the operation is complete.
|
||||
*/
|
||||
fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit = {})
|
||||
|
||||
/**
|
||||
* Pops routes from the navigation stack until the ***first*** specified [routeClass] is found.
|
||||
*
|
||||
* @param routeClass The route class to pop to.
|
||||
* @param onComplete The callback to be invoked when the operation is complete.
|
||||
*/
|
||||
fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit = {})
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.core.decompose.navigation
|
||||
|
||||
/**
|
||||
* Pops routes from the navigation stack until the specified route [R] is found.
|
||||
*
|
||||
* @param R The route to pop to.
|
||||
* @param onComplete The callback to be invoked when the operation is complete.
|
||||
*/
|
||||
inline fun <reified R : Route> Router.popTo(noinline onComplete: (isSuccess: Boolean) -> Unit = {}) {
|
||||
popTo(R::class, onComplete)
|
||||
}
|
||||
|
|
@ -42,5 +42,9 @@
|
|||
{
|
||||
"name": "DETAILS_REDESIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "PUSH_NOTIFICATIONS_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.core.navigation
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.rekotlin.Action
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
sealed class NavigationAction : Action {
|
||||
|
||||
data class NavigateTo(
|
||||
val screen: AppScreen,
|
||||
val fragmentShareTransition: FragmentShareTransition? = null,
|
||||
val addToBackstack: Boolean = true,
|
||||
val bundle: Bundle? = null,
|
||||
) : NavigationAction()
|
||||
|
||||
data class PopBackTo(val screen: AppScreen? = null, val inclusive: Boolean = false) : NavigationAction()
|
||||
|
||||
data class OpenUrl(val url: String) : NavigationAction()
|
||||
|
||||
data class OpenDocument(val url: Uri) : NavigationAction()
|
||||
|
||||
object OpenBiometricsSettings : NavigationAction()
|
||||
|
||||
data class OpenDialog(val stateDialog: StateDialog) : NavigationAction()
|
||||
|
||||
data class Share(val data: String) : NavigationAction()
|
||||
|
||||
data class ActivityCreated(val activity: WeakReference<AppCompatActivity>) : NavigationAction()
|
||||
|
||||
data class ActivityDestroyed(val activity: WeakReference<AppCompatActivity>) : NavigationAction()
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package com.tangem.core.navigation
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.rekotlin.StateType
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
data class NavigationState(
|
||||
val backStack: List<AppScreen> = emptyList(),
|
||||
val activity: WeakReference<AppCompatActivity>? = null,
|
||||
) : StateType
|
||||
|
||||
enum class AppScreen(val isDialogFragment: Boolean = false) {
|
||||
Home,
|
||||
Disclaimer,
|
||||
OnboardingNote,
|
||||
OnboardingWallet,
|
||||
OnboardingTwins,
|
||||
OnboardingOther,
|
||||
Wallet,
|
||||
WalletDetails,
|
||||
Send(isDialogFragment = true),
|
||||
Details,
|
||||
DetailsSecurity,
|
||||
CardSettings,
|
||||
AppSettings,
|
||||
ResetToFactory,
|
||||
AccessCodeRecovery,
|
||||
ManageTokens,
|
||||
AddCustomToken,
|
||||
WalletConnectSessions,
|
||||
QrScanning,
|
||||
ReferralProgram,
|
||||
Swap,
|
||||
Welcome,
|
||||
SaveWallet(isDialogFragment = true),
|
||||
AppCurrencySelector,
|
||||
ModalNotification(isDialogFragment = true),
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.core.navigation
|
||||
|
||||
/**
|
||||
* Navigation controller that based on redux actions
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface ReduxNavController {
|
||||
|
||||
/** Navigate by [action] */
|
||||
fun navigate(action: NavigationAction)
|
||||
|
||||
fun popBackStack(screen: AppScreen? = null)
|
||||
|
||||
fun getBackStack(): List<AppScreen>
|
||||
}
|
||||
|
|
@ -1,18 +1,8 @@
|
|||
package com.tangem.core.navigation
|
||||
|
||||
import android.view.View
|
||||
import androidx.transition.TransitionSet
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class FragmentShareTransition(
|
||||
val shareElements: List<ShareElement>,
|
||||
val enterTransitionSet: TransitionSet,
|
||||
val exitTransitionSet: TransitionSet,
|
||||
)
|
||||
|
||||
/**
|
||||
* For ease of use, the name is used as transitionName\name into the FragmentTransaction.addSharedElement
|
||||
*/
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.core.navigation
|
||||
|
||||
interface StateDialog {
|
||||
|
||||
data class ScanFailsDialog(val source: ScanFailsSource) : StateDialog
|
||||
|
||||
enum class ScanFailsSource {
|
||||
MAIN, SIGN_IN, SETTINGS, INTRO;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.navigation.feedback
|
||||
|
||||
class DummyFeedbackManager : FeedbackManager {
|
||||
|
||||
override fun sendEmail(type: FeedbackType) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.navigation.feedback
|
||||
|
||||
interface FeedbackManager {
|
||||
|
||||
fun sendEmail(type: FeedbackType)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.core.navigation.feedback
|
||||
|
||||
sealed class FeedbackType {
|
||||
data object RateCanBeBetter : FeedbackType()
|
||||
|
||||
data object ScanFails : FeedbackType()
|
||||
|
||||
data class SendTransactionFailed(val error: String) : FeedbackType()
|
||||
|
||||
data object Feedback : FeedbackType()
|
||||
|
||||
data object Support : FeedbackType()
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.navigation.finisher
|
||||
|
||||
interface AppFinisher {
|
||||
|
||||
fun finish()
|
||||
|
||||
fun restart()
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.core.navigation.settings
|
||||
|
||||
class DummySettingsManager : SettingsManager {
|
||||
override fun openSettings() { /* no-op */ }
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.core.navigation.settings
|
||||
|
||||
interface SettingsManager {
|
||||
fun openSettings()
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.navigation.share
|
||||
|
||||
class DummyShareManager : ShareManager {
|
||||
|
||||
override fun shareText(text: String) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.navigation.share
|
||||
|
||||
interface ShareManager {
|
||||
|
||||
fun shareText(text: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.navigation.url
|
||||
|
||||
class DummyUrlOpener : UrlOpener {
|
||||
|
||||
override fun openUrl(url: String) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.navigation.url
|
||||
|
||||
interface UrlOpener {
|
||||
|
||||
fun openUrl(url: String)
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="add_custom_token_choose_network">Выберите сеть</string>
|
||||
<string name="add_custom_token_title">Добавить токен</string>
|
||||
<string name="add_tokens_title">Валюты</string>
|
||||
<string name="address_qr_code_message_format">Отправляйте только %1$s (%2$s) в сети %3$s на этот адрес. Использование другой сети может привести к утрате средств.</string>
|
||||
<string name="alert_button_how_to_scan">Как сканировать</string>
|
||||
<string name="alert_button_request_support">Обратиться в поддержку</string>
|
||||
<string name="alert_button_try_again">Попробовать снова</string>
|
||||
<string name="alert_demo_feature_disabled">Эта функция недоступна в демонстрационном режиме</string>
|
||||
<string name="alert_failed_to_send_transaction_message">Причина: %s</string>
|
||||
<string name="alert_failed_to_send_transaction_title">Не могу отправить транзакцию</string>
|
||||
|
|
@ -81,6 +84,7 @@
|
|||
<string name="common_copy">Копировать</string>
|
||||
<string name="common_copy_address">Скопировать адрес</string>
|
||||
<string name="common_create">Создать</string>
|
||||
<string name="common_custom">Свое</string>
|
||||
<string name="common_delete">Удалить</string>
|
||||
<string name="common_disabled">Отключено</string>
|
||||
<string name="common_done">Готово</string>
|
||||
|
|
@ -92,7 +96,6 @@
|
|||
<string name="common_explorer">Обозреватель</string>
|
||||
<string name="common_fee_label">Комиссия</string>
|
||||
<string name="common_fee_selector_footer">Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s</string>
|
||||
<string name="common_fee_selector_option_custom">Свое</string>
|
||||
<string name="common_fee_selector_option_fast">Быстро</string>
|
||||
<string name="common_fee_selector_option_market">По рынку</string>
|
||||
<string name="common_fee_selector_option_slow">Медленно</string>
|
||||
|
|
@ -117,6 +120,7 @@
|
|||
<string name="common_reject">Отклонить</string>
|
||||
<string name="common_reload">Перезагрузить</string>
|
||||
<string name="common_rename">Переименовать</string>
|
||||
<string name="common_save">Сохранить</string>
|
||||
<string name="common_save_changes">Сохранить изменения</string>
|
||||
<string name="common_search">Искать</string>
|
||||
<string name="common_search_tokens">Поиск токенов</string>
|
||||
|
|
@ -128,6 +132,7 @@
|
|||
<string name="common_share">Поделиться</string>
|
||||
<string name="common_sign">Подписать</string>
|
||||
<string name="common_sign_and_send">Подписать и отправить</string>
|
||||
<string name="common_staking">Стейкинг</string>
|
||||
<string name="common_start">Начать</string>
|
||||
<string name="common_submit">Отправить</string>
|
||||
<string name="common_success">Успешно</string>
|
||||
|
|
@ -169,6 +174,7 @@
|
|||
<string name="custom_token_validation_error_not_found">Токены могут быть созданы кем угодно. Остерегайтесь мошеннических токенов, они могут ничего не стоить</string>
|
||||
<string name="custom_token_validation_error_not_found_description">Остерегайтесь мошеннических токенов, они могут ничего не стоить</string>
|
||||
<string name="custom_token_validation_error_not_found_title">Токены могут быть созданы кем угодно</string>
|
||||
<string name="details_buy_wallet">Купить кошелек Tangem</string>
|
||||
<string name="details_chat">Чат</string>
|
||||
<string name="details_manage_security_access_code">Код доступа</string>
|
||||
<string name="details_manage_security_access_code_description">Перед сканированием карты вам нужно будет ввести правильный код доступа.</string>
|
||||
|
|
@ -186,6 +192,7 @@
|
|||
<string name="details_row_title_flip_to_hide">Скрывать балансы жестом переворота</string>
|
||||
<string name="details_row_title_issuer">Эмитент</string>
|
||||
<string name="details_row_title_signed_hashes">Подписано</string>
|
||||
<string name="details_send_feedback">Отправить отзыв</string>
|
||||
<string name="details_title">Подробности</string>
|
||||
<string name="disclaimer_error_loading">Проверьте подключение с интернетом или переключитесь на другую сеть</string>
|
||||
<string name="disclaimer_title">Условия использования</string>
|
||||
|
|
@ -295,7 +302,9 @@
|
|||
<item quantity="many">%1$d из %2$d кошельков</item>
|
||||
<item quantity="other">%1$d из %2$d кошельков</item>
|
||||
</plurals>
|
||||
<string name="manage_tokens_remove">Удалить</string>
|
||||
<string name="manage_tokens_search_placeholder">например Bitcoin</string>
|
||||
<string name="manage_tokens_toast_portfolio_updated">Ваш портфель был обновлен</string>
|
||||
<string name="manage_tokens_unavailable_description">Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление.</string>
|
||||
<string name="manage_tokens_unavailable_vote">Голосовать</string>
|
||||
<string name="manage_tokens_wallet_selector_title">Выберите кошелек</string>
|
||||
|
|
@ -535,9 +544,12 @@
|
|||
<string name="send_transaction_success">Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время</string>
|
||||
<string name="send_validation_invalid_address">Неверный адрес</string>
|
||||
<string name="sent_transaction_sent_title">Транзакция отправлена</string>
|
||||
<string name="settings_card_settings_footer">Подготовьтесь к сканированию карты, которую вы хотите настроить.</string>
|
||||
<string name="settings_forget_wallet">Забыть кошелек</string>
|
||||
<string name="settings_forget_wallet_footer">Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова.</string>
|
||||
<string name="settings_wallet_name_title">Имя</string>
|
||||
<string name="staking_details_available">Доступно</string>
|
||||
<string name="staking_details_title">Стейкинг %s</string>
|
||||
<string name="story_awe_description">Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.</string>
|
||||
<string name="story_awe_title">Революционный аппаратный кошелек</string>
|
||||
<string name="story_backup_description">До трех карт с одним кошельком</string>
|
||||
|
|
@ -579,9 +591,10 @@
|
|||
<string name="token_button_unavailability_reason_empty_balance_sell">У вас нет средств для продажи. Пополните счет, чтобы иметь возможность продать с него средства.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_send">У вас нет средств для отправки. Пополните счет, чтобы иметь возможность отправить с него средства.</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">В данный момент обмен монеты %s недоступен. Следите за нашими обновлениями.</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">Продажа средств станет доступной после завершения транзакции(-ий) в сети %s</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">Продажа средств станет доступной после завершения транзакции(-ий) в сети %s</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">Отправка средств станет доступной после завершения транзакции(-ий) в сети %s</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">В данный момент продажа %s недоступна. Следите за нашими обновлениями.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">В данный момент стейкинг монеты %s недоступен. Следите за нашими обновлениями.</string>
|
||||
<string name="token_details_generate_xpub">Сгенерировать XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Скрыть</string>
|
||||
<string name="token_details_hide_alert_message">Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно через страницу управления токенами.</string>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="add_custom_token_choose_network">Choose network</string>
|
||||
<string name="add_custom_token_title">Add custom token</string>
|
||||
<string name="add_tokens_title">Manage tokens</string>
|
||||
<string name="address_qr_code_message_format">Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds.</string>
|
||||
<string name="alert_button_how_to_scan">How to scan</string>
|
||||
<string name="alert_button_request_support">Request support</string>
|
||||
<string name="alert_button_try_again">Try again</string>
|
||||
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
|
||||
<string name="alert_failed_to_send_transaction_message">Reason: %s</string>
|
||||
<string name="alert_failed_to_send_transaction_title">Can\'t send a transaction</string>
|
||||
|
|
@ -64,6 +67,8 @@
|
|||
<string name="cardano_max_amount_has_token_title">Not enough ADA</string>
|
||||
<string name="common_accept">Accept</string>
|
||||
<string name="common_access_denied">Access denied</string>
|
||||
<string name="common_all">All</string>
|
||||
<string name="common_allow">Allow</string>
|
||||
<string name="common_apply">Apply</string>
|
||||
<string name="common_approval">Approval</string>
|
||||
<string name="common_attention">Attention</string>
|
||||
|
|
@ -80,6 +85,7 @@
|
|||
<string name="common_copy">Copy</string>
|
||||
<string name="common_copy_address">Copy address</string>
|
||||
<string name="common_create">Create</string>
|
||||
<string name="common_custom">Custom</string>
|
||||
<string name="common_delete">Delete</string>
|
||||
<string name="common_disabled">Disabled</string>
|
||||
<string name="common_done">Done</string>
|
||||
|
|
@ -91,7 +97,6 @@
|
|||
<string name="common_explorer">Explorer</string>
|
||||
<string name="common_fee_label">Fee</string>
|
||||
<string name="common_fee_selector_footer">Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s</string>
|
||||
<string name="common_fee_selector_option_custom">Custom</string>
|
||||
<string name="common_fee_selector_option_fast">Fast</string>
|
||||
<string name="common_fee_selector_option_market">Market</string>
|
||||
<string name="common_fee_selector_option_slow">Slow</string>
|
||||
|
|
@ -111,11 +116,13 @@
|
|||
<string name="common_origin_card">Primary Card</string>
|
||||
<string name="common_passphrase">Passphrase</string>
|
||||
<string name="common_paste">Paste</string>
|
||||
<string name="common_range">%1$s-%2$s</string>
|
||||
<string name="common_read_more">Read more</string>
|
||||
<string name="common_receive">Receive</string>
|
||||
<string name="common_reject">Reject</string>
|
||||
<string name="common_reload">Reload</string>
|
||||
<string name="common_rename">Rename</string>
|
||||
<string name="common_save">Save</string>
|
||||
<string name="common_save_changes">Save changes</string>
|
||||
<string name="common_search">Search</string>
|
||||
<string name="common_search_tokens">Search tokens</string>
|
||||
|
|
@ -127,6 +134,8 @@
|
|||
<string name="common_share">Share</string>
|
||||
<string name="common_sign">Sign</string>
|
||||
<string name="common_sign_and_send">Sign and send</string>
|
||||
<string name="common_stake">Stake</string>
|
||||
<string name="common_staking">Staking</string>
|
||||
<string name="common_start">Start</string>
|
||||
<string name="common_submit">Submit</string>
|
||||
<string name="common_success">Success</string>
|
||||
|
|
@ -168,6 +177,7 @@
|
|||
<string name="custom_token_validation_error_not_found">Note that tokens can be created by anyone. Be aware of adding scam tokens, they can cost nothing.</string>
|
||||
<string name="custom_token_validation_error_not_found_description">Be aware of adding scam tokens, they can cost nothing</string>
|
||||
<string name="custom_token_validation_error_not_found_title">Note that tokens can be created by anyone</string>
|
||||
<string name="details_buy_wallet">Buy Tangem Wallet</string>
|
||||
<string name="details_chat">Chat</string>
|
||||
<string name="details_manage_security_access_code">Access code</string>
|
||||
<string name="details_manage_security_access_code_description">You will have to submit the correct access code before scanning the card</string>
|
||||
|
|
@ -185,6 +195,7 @@
|
|||
<string name="details_row_title_flip_to_hide">Flip-to-Hide Balances</string>
|
||||
<string name="details_row_title_issuer">Issuer</string>
|
||||
<string name="details_row_title_signed_hashes">Signed</string>
|
||||
<string name="details_send_feedback">Send feedback</string>
|
||||
<string name="details_title">Details</string>
|
||||
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
|
||||
<string name="disclaimer_title">Terms of service</string>
|
||||
|
|
@ -294,11 +305,20 @@
|
|||
<item quantity="one">%1$d of %2$d wallet</item>
|
||||
<item quantity="other">%1$d of %2$d wallets</item>
|
||||
</plurals>
|
||||
<string name="manage_tokens_remove">Remove</string>
|
||||
<string name="manage_tokens_search_placeholder">e.g. BTC I trust, hodl I must</string>
|
||||
<string name="manage_tokens_toast_portfolio_updated">Your portfolio has been updated</string>
|
||||
<string name="manage_tokens_unavailable_description">The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it.</string>
|
||||
<string name="manage_tokens_unavailable_vote">Upvote</string>
|
||||
<string name="manage_tokens_wallet_selector_title">Choose wallet</string>
|
||||
<string name="manage_tokens_wallet_support_only_one_network_title">The wallet doesn\'t support more than one network</string>
|
||||
<string name="markets_add_to_my_portfolio_description">To start buying, exchanging or receiving this asset, add this token to at least 1 network</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_description">This asset is not available</string>
|
||||
<string name="markets_add_to_portfolio_button">Add to portfolio</string>
|
||||
<string name="markets_common_my_portfolio">My portfolio</string>
|
||||
<string name="markets_common_title">Market</string>
|
||||
<string name="markets_select_wallet">Select wallet</string>
|
||||
<string name="markets_sort_by_title">Sort By</string>
|
||||
<string name="onboarding_access_code_feature_1_description">You have to set up a single access code to protect all your cards</string>
|
||||
<string name="onboarding_access_code_feature_1_title">Protect</string>
|
||||
<string name="onboarding_access_code_feature_2_description">You can set up an individual access code on each card later</string>
|
||||
|
|
@ -528,9 +548,31 @@
|
|||
<string name="send_validation_invalid_address">Invalid address</string>
|
||||
<string name="send_wallet_balance_format">%1$s (%2$s)</string>
|
||||
<string name="sent_transaction_sent_title">Transaction sent</string>
|
||||
<string name="settings_card_settings_footer">Prepare to scan card you want to setup.</string>
|
||||
<string name="settings_forget_wallet">Forget wallet</string>
|
||||
<string name="settings_forget_wallet_footer">This will remove the wallet from the application. The wallet itself can be added again.</string>
|
||||
<string name="settings_wallet_name_title">Name</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
<string name="staking_details_apy">APY</string>
|
||||
<string name="staking_details_available">Available</string>
|
||||
<string name="staking_details_average_reward_rate">Average Reward Rate</string>
|
||||
<string name="staking_details_estimated_profit">%s est. profit</string>
|
||||
<string name="staking_details_market_rating">Market rating</string>
|
||||
<string name="staking_details_metrics_block_header">Metrics</string>
|
||||
<string name="staking_details_minimum_requirement">Minimum Requirement</string>
|
||||
<string name="staking_details_no_rewards_to_claim">No rewards to claim</string>
|
||||
<string name="staking_details_on_stake">On stake</string>
|
||||
<string name="staking_details_reward_claiming">Reward claiming</string>
|
||||
<string name="staking_details_reward_schedule">Reward schedule</string>
|
||||
<string name="staking_details_rewards_to_claim">Rewards to claim: %s</string>
|
||||
<string name="staking_details_title">Staking %s</string>
|
||||
<string name="staking_details_unbonding_period">Unbonding Period</string>
|
||||
<string name="staking_details_warmup_period">Warmup period</string>
|
||||
<string name="staking_native">Native staking</string>
|
||||
<string name="staking_notification_earn_rewards_text" formatted="false">Staking allow you to earn %1s. Your staking rewards arrive every ~%2s days.</string>
|
||||
<string name="staking_notification_earn_rewards_title">Earn staking rewards</string>
|
||||
<string name="staking_rewards">Rewards</string>
|
||||
<string name="staking_validator">Validator</string>
|
||||
<string name="story_awe_description">Store your crypto assets secure while keeping private keys contained in your card</string>
|
||||
<string name="story_awe_title">Revolutionary Hardware Wallet</string>
|
||||
<string name="story_backup_description">Up to 3 physical cards to one wallet</string>
|
||||
|
|
@ -575,6 +617,7 @@
|
|||
<string name="token_button_unavailability_reason_pending_transaction_sell">Selling funds will be available once the pending transaction(s) in network %s is complete</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_send">Sending funds will be available once the pending transaction(s) in network %s is complete</string>
|
||||
<string name="token_button_unavailability_reason_sell_unavailable">Selling %s is not available at the moment. Please check our updates.</string>
|
||||
<string name="token_button_unavailability_reason_staking_unavailable">Staking %s is not available at the moment. Please check our updates.</string>
|
||||
<string name="token_details_generate_xpub">Generate XPUB</string>
|
||||
<string name="token_details_hide_alert_hide">Hide</string>
|
||||
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
|
||||
|
|
@ -609,6 +652,9 @@
|
|||
<string name="twins_recreate_warning">This action is irreversible. You will not have access to the old wallet.</string>
|
||||
<string name="twins_scan_twin_with_number">Tap the twin card with number %s and do not remove until the end of the operation</string>
|
||||
<string name="unlock_wallet_description_full">Use %s or scan a card to have an access to your wallet</string>
|
||||
<string name="user_push_notification_agreement_argument_one">Stay up to date with the latest features and news</string>
|
||||
<string name="user_push_notification_agreement_argument_two">Be able to get important market notifications</string>
|
||||
<string name="user_push_notification_agreement_header">Would you like to use
Push-notifications?</string>
|
||||
<string name="user_wallet_list_add_button">Add new wallet</string>
|
||||
<string name="user_wallet_list_delete_prompt">Are you sure you want to delete this wallet?</string>
|
||||
<string name="user_wallet_list_error_unable_to_unlock">An error has occurred, please scan your card to log in</string>
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** Project - Common */
|
||||
implementation(projects.common)
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
|
|
@ -25,6 +22,12 @@ dependencies {
|
|||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
implementation(deps.androidx.palette)
|
||||
implementation(deps.androidx.windowManager) {
|
||||
exclude(
|
||||
deps.kotlin.coroutines.android.get().module.group,
|
||||
deps.kotlin.coroutines.android.get().module.name
|
||||
)
|
||||
}
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.constraintLayout)
|
||||
|
|
@ -40,11 +43,12 @@ dependencies {
|
|||
|
||||
/** Other libraries */
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.accompanist.permission)
|
||||
implementation(deps.material)
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.jodatime)
|
||||
api(deps.jodatime)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.markdown)
|
||||
}
|
||||
|
|
@ -1,11 +1,19 @@
|
|||
package com.tangem.core.ui
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.message.EventMessageHandler
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
|
||||
@Stable
|
||||
interface UiDependencies {
|
||||
|
||||
val hapticManager: HapticManager
|
||||
|
||||
val appThemeModeHolder: AppThemeModeHolder
|
||||
|
||||
val globalSnackbarHostState: SnackbarHostState
|
||||
|
||||
val eventMessageHandler: EventMessageHandler
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* A composable that draws a fade effect at the bottom of the screen. Used on screens with a list of repeating
|
||||
* elements and floating button at the bottom of the screen.
|
||||
*/
|
||||
@Composable
|
||||
fun BottomFade(modifier: Modifier = Modifier, backgroundColor: Color = TangemTheme.colors.background.secondary) {
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size100 + bottomBarHeight)
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
backgroundColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun BoxWithGradient(
|
||||
modifier: Modifier = Modifier,
|
||||
gradient: Brush = BottomGradient,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val bottomInsetsPx = WindowInsets.navigationBars.getBottom(LocalDensity.current)
|
||||
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
content()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size164 + bottomInsetsPx.dp)
|
||||
.background(gradient),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val BottomGradient: Brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
TangemColorPalette.Black.copy(alpha = 0f),
|
||||
TangemColorPalette.Black.copy(alpha = 0.75f),
|
||||
TangemColorPalette.Black.copy(alpha = 0.95f),
|
||||
TangemColorPalette.Black,
|
||||
),
|
||||
)
|
||||
|
|
@ -86,6 +86,7 @@ fun PrimaryButton(
|
|||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
size: TangemButtonSize = TangemButtonSize.Default,
|
||||
colors: ButtonColors = TangemButtonsDefaults.primaryButtonColors,
|
||||
showProgress: Boolean = false,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
|
|
@ -94,7 +95,7 @@ fun PrimaryButton(
|
|||
text = text,
|
||||
icon = TangemButtonIconPosition.None,
|
||||
onClick = onClick,
|
||||
colors = TangemButtonsDefaults.primaryButtonColors,
|
||||
colors = colors,
|
||||
enabled = enabled,
|
||||
showProgress = showProgress,
|
||||
size = size,
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.core.ui.components.SelctorDialogParamsProvider.SelectorDialogParams
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.fields.SimpleDialogTextField
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -100,7 +100,7 @@ fun TextInputDialog(
|
|||
confirmButton: DialogButton,
|
||||
onDismissDialog: () -> Unit,
|
||||
onValueChange: (TextFieldValue) -> Unit,
|
||||
textFieldParams: AdditionalTextInputDialogParams,
|
||||
textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
isDismissable: Boolean = true,
|
||||
|
|
@ -128,7 +128,7 @@ fun TextInputDialog(
|
|||
confirmButton: DialogButton,
|
||||
onDismissDialog: () -> Unit,
|
||||
onValueChange: (String) -> Unit,
|
||||
textFieldParams: AdditionalTextInputDialogParams,
|
||||
textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
isDismissable: Boolean = true,
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import com.google.accompanist.systemuicontroller.SystemUiController
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
|
||||
@Composable
|
||||
fun SystemBarsEffect(block: SystemUiController.() -> Unit) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
SideEffect { block(systemUiController) }
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.tangem.core.ui.res.LocalIsInDarkTheme
|
||||
|
||||
/**
|
||||
* Provides the ability to set a scrim for 3-button navigation
|
||||
*
|
||||
* Automatically makes navigation bar transparent when the composable is disposed.
|
||||
*
|
||||
* Usage example: [com.tangem.feature.tokendetails.presentation.TokenDetailsFragment]
|
||||
*/
|
||||
@Composable
|
||||
fun NavigationBar3ButtonsScrim() {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val isDarkTheme = LocalIsInDarkTheme.current
|
||||
SideEffect {
|
||||
systemUiController.isNavigationBarContrastEnforced = true
|
||||
}
|
||||
LaunchedEffect(systemUiController.isNavigationBarContrastEnforced) {
|
||||
if (systemUiController.isNavigationBarContrastEnforced.not()) {
|
||||
systemUiController.isNavigationBarContrastEnforced = true
|
||||
}
|
||||
}
|
||||
DisposableEffect(isDarkTheme) {
|
||||
onDispose {
|
||||
systemUiController.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the ability to set dark/light icons in cases where the darkness of the screen differs from that
|
||||
* provided in [TangemTheme].
|
||||
*
|
||||
* Automatically returns the icon colors to their original state when the composable is disposed.
|
||||
*
|
||||
* Usage example: [com.tangem.feature.qrscanning.QrScanningFragment]
|
||||
*/
|
||||
@Composable
|
||||
fun SystemBarsIconsDisposable(darkIcons: Boolean, isNavigationBarContrastEnforced: Boolean = false) {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
|
||||
SideEffect {
|
||||
systemUiController.systemBarsDarkContentEnabled = darkIcons
|
||||
systemUiController.isNavigationBarContrastEnforced = isNavigationBarContrastEnforced
|
||||
}
|
||||
|
||||
val isDarkTheme = LocalIsInDarkTheme.current
|
||||
|
||||
DisposableEffect(isDarkTheme) {
|
||||
onDispose {
|
||||
systemUiController.systemBarsDarkContentEnabled = !isDarkTheme
|
||||
systemUiController.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,57 @@
|
|||
package com.tangem.core.ui.components.appbar
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.appbar.models.AdditionalButton
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
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
|
||||
|
||||
/**
|
||||
* App bar with title and two additional buttons
|
||||
*
|
||||
* @param startButton button information attached to the left edge
|
||||
* @param endButton button information attached to the right edge
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=50%3A403&t=tRQ4KlzkjV7TCLZl-4">
|
||||
* Figma Component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun AppBarWithAdditionalButtons(
|
||||
text: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
startButton: AdditionalButton? = null,
|
||||
endButton: AdditionalButton? = null,
|
||||
textColor: Color = TangemTheme.colors.text.primary1,
|
||||
iconColor: Color = TangemTheme.colors.icon.primary1,
|
||||
) {
|
||||
AppBarWithAdditionalButtons(
|
||||
text = text.resolveReference(),
|
||||
startButton = startButton,
|
||||
endButton = endButton,
|
||||
modifier = modifier,
|
||||
textColor = textColor,
|
||||
iconColor = iconColor,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* App bar with title and two additional buttons
|
||||
|
|
@ -31,43 +65,63 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
@Composable
|
||||
fun AppBarWithAdditionalButtons(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
startButton: AdditionalButton? = null,
|
||||
endButton: AdditionalButton? = null,
|
||||
textColor: Color = TangemTheme.colors.text.primary1,
|
||||
iconColor: Color = TangemTheme.colors.icon.primary1,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(TangemTheme.dimens.size56)
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
if (startButton != null) {
|
||||
IconButton(modifier = Modifier.align(Alignment.CenterStart), onClick = startButton.onIconClicked) {
|
||||
Icon(
|
||||
painter = painterResource(id = startButton.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = painterResource(id = startButton.iconRes),
|
||||
contentDescription = null,
|
||||
tint = iconColor,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size24)
|
||||
.align(Alignment.CenterStart)
|
||||
.clickable(
|
||||
onClick = startButton.onIconClicked,
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = androidx.compose.material.ripple.rememberRipple(
|
||||
bounded = false,
|
||||
radius = TangemTheme.dimens.size24 / 2,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
)
|
||||
|
||||
if (endButton != null) {
|
||||
IconButton(modifier = Modifier.align(Alignment.CenterEnd), onClick = endButton.onIconClicked) {
|
||||
Icon(
|
||||
painter = painterResource(id = endButton.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
painter = painterResource(id = endButton.iconRes),
|
||||
contentDescription = null,
|
||||
tint = iconColor,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size24)
|
||||
.align(Alignment.CenterEnd)
|
||||
.clickable(
|
||||
onClick = endButton.onIconClicked,
|
||||
role = Role.Button,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = androidx.compose.material.ripple.rememberRipple(
|
||||
bounded = false,
|
||||
radius = TangemTheme.dimens.size24 / 2,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +141,7 @@ private fun Preview_AppBarWithAdditionalButtons() {
|
|||
iconRes = R.drawable.ic_more_vertical_24,
|
||||
onIconClicked = {},
|
||||
),
|
||||
modifier = Modifier.background(TangemTheme.colors.background.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -102,6 +157,7 @@ private fun Preview_AppBarWithOnlyStartButtons() {
|
|||
iconRes = R.drawable.ic_scan_24,
|
||||
onIconClicked = {},
|
||||
),
|
||||
modifier = Modifier.background(TangemTheme.colors.background.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -117,6 +173,7 @@ private fun Preview_AppBarWithOnlyEndButtons() {
|
|||
iconRes = R.drawable.ic_more_vertical_24,
|
||||
onIconClicked = {},
|
||||
),
|
||||
modifier = Modifier.background(TangemTheme.colors.background.secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.core.ui.components.appbar.models
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TopAppBarMedium(
|
||||
title: TextReference,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
navigationIconResId: Int = R.drawable.ic_back_24,
|
||||
colors: TopAppBarColors = TangemTopAppBarColors,
|
||||
) {
|
||||
MediumTopAppBar(
|
||||
modifier = modifier,
|
||||
scrollBehavior = scrollBehavior,
|
||||
colors = colors,
|
||||
title = {
|
||||
val collapsedStyle = TangemTheme.typography.subtitle1
|
||||
val expandedStyle = TangemTheme.typography.h1
|
||||
val style by remember(scrollBehavior.state.collapsedFraction) {
|
||||
derivedStateOf {
|
||||
if (scrollBehavior.state.collapsedFraction >= COLLAPSED_APP_BAR_THRESHOLD) {
|
||||
collapsedStyle
|
||||
} else {
|
||||
expandedStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = style,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size32),
|
||||
onClick = onBackClick,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = navigationIconResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
internal val TangemTopAppBarColors: TopAppBarColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TopAppBarColors(
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
scrolledContainerColor = TangemTheme.colors.background.secondary,
|
||||
navigationIconContentColor = TangemTheme.colors.icon.primary1,
|
||||
titleContentColor = TangemTheme.colors.text.primary1,
|
||||
actionIconContentColor = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package com.tangem.core.ui.components.atoms.radiobutton
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* [Radio button](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=101-119&t=FH84ljLBk1vmGAei-4)
|
||||
*
|
||||
* @param isSelected Whether the radio button is selected
|
||||
* @param onClick Called when the user clicks the button
|
||||
* @param modifier Modifier to be applied to the button
|
||||
* @param isEnabled Whether the button click is enabled
|
||||
*/
|
||||
@Composable
|
||||
fun TangemRadioButton(
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.clickable(
|
||||
enabled = isEnabled,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false, radius = TangemTheme.dimens.size16),
|
||||
onClick = onClick,
|
||||
),
|
||||
) {
|
||||
val color = TangemTheme.colors.stroke.secondary
|
||||
val radius = with(LocalDensity.current) { TangemTheme.dimens.size9.toPx() }
|
||||
val width = with(LocalDensity.current) { TangemTheme.dimens.size2.toPx() }
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size24)
|
||||
.padding(TangemTheme.dimens.spacing2),
|
||||
) {
|
||||
drawCircle(
|
||||
color = color,
|
||||
radius = radius,
|
||||
style = Stroke(width),
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isSelected,
|
||||
label = "Radio button animation",
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size24),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_check_circle_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.control.checked,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TangemRadioButton_Preview() {
|
||||
var isSelected by remember { mutableStateOf(false) }
|
||||
TangemThemePreview {
|
||||
TangemRadioButton(isSelected, onClick = { isSelected = !isSelected })
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.core.ui.components.block
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun BlockCard(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
colors: CardColors = TangemBlockCardColors,
|
||||
onClick: () -> Unit = {},
|
||||
content: @Composable ColumnScope.() -> Unit = {},
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
onClick = onClick,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
colors = colors,
|
||||
enabled = enabled,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
val TangemBlockCardColors: CardColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = CardColors(
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
contentColor = TangemTheme.colors.text.primary1,
|
||||
disabledContainerColor = TangemTheme.colors.button.disabled,
|
||||
disabledContentColor = TangemTheme.colors.text.disabled,
|
||||
)
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.core.ui.components.block
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.text.style.TextOverflow
|
||||
import com.tangem.core.ui.components.block.model.BlockUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun BlockItem(model: BlockUM, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
onClick = model.onClick,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12, Alignment.Start),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = model.iconRes),
|
||||
tint = when (model.accentType) {
|
||||
BlockUM.AccentType.NONE -> TangemTheme.colors.icon.secondary
|
||||
BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent
|
||||
BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = model.text.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = when (model.accentType) {
|
||||
BlockUM.AccentType.NONE -> TangemTheme.colors.text.primary1
|
||||
BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent
|
||||
BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.core.ui.components.block.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class BlockUM(
|
||||
val text: TextReference,
|
||||
@DrawableRes val iconRes: Int,
|
||||
val onClick: () -> Unit,
|
||||
val accentType: AccentType = AccentType.NONE,
|
||||
) {
|
||||
|
||||
enum class AccentType {
|
||||
NONE, ACCENT, WARNING,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
package com.tangem.core.ui.components.bottomsheets
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ import kotlinx.coroutines.launch
|
|||
* Tangem bottom sheet with custom draggable header and config
|
||||
*
|
||||
* @param config data model containing logic and ui models
|
||||
* @param
|
||||
* @param content custom bottom sheet content
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -22,20 +24,33 @@ import kotlinx.coroutines.launch
|
|||
inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
containerColor: Color = TangemTheme.colors.background.primary,
|
||||
addBottomInsets: Boolean = true,
|
||||
crossinline content: @Composable ColumnScope.(T) -> Unit,
|
||||
) {
|
||||
var isVisible by remember { mutableStateOf(value = config.isShow) }
|
||||
|
||||
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
if (isVisible && config.content is T) {
|
||||
ModalBottomSheet(
|
||||
// FIXME temporary solution to fix height of the bottom sheet
|
||||
modifier = Modifier.sizeIn(maxHeight = LocalWindowSize.current.height - statusBarHeight),
|
||||
onDismissRequest = config.onDismissRequest,
|
||||
sheetState = sheetState,
|
||||
containerColor = containerColor,
|
||||
shape = TangemTheme.shapes.bottomSheetLarge,
|
||||
windowInsets = WindowInsetsZero,
|
||||
dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) },
|
||||
) {
|
||||
content(config.content)
|
||||
if (addBottomInsets) {
|
||||
Column(
|
||||
// FIXME temporary solution to fix height of the bottom sheet
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
) {
|
||||
content(config.content)
|
||||
}
|
||||
} else {
|
||||
content(config.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import androidx.compose.foundation.LocalIndication
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
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.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
|
|
@ -21,8 +18,9 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -42,7 +40,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
*/
|
||||
@Composable
|
||||
inline fun <reified T> SegmentedButtons(
|
||||
config: PersistentList<T>,
|
||||
config: ImmutableList<T>,
|
||||
crossinline onClick: (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.background.tertiary,
|
||||
|
|
@ -51,7 +49,7 @@ inline fun <reified T> SegmentedButtons(
|
|||
showIndication: Boolean = true,
|
||||
initialSelectedItem: T? = null,
|
||||
isEnabled: Boolean = true,
|
||||
crossinline buttonContent: @Composable (T) -> Unit,
|
||||
crossinline buttonContent: @Composable BoxScope.(T) -> Unit,
|
||||
) {
|
||||
if (config.isEmpty() || config.size == 1) return
|
||||
|
||||
|
|
@ -97,7 +95,7 @@ inline fun <reified T> SegmentedButtons(
|
|||
onClick(config[index])
|
||||
},
|
||||
) {
|
||||
buttonContent.invoke(config[index])
|
||||
buttonContent.invoke(this, config[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -14,18 +13,15 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -63,7 +59,7 @@ fun InputRowBestRate(
|
|||
modifier = Modifier
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
InnerIcon(imageUrl = imageUrl)
|
||||
InputRowAsyncImage(imageUrl = imageUrl, modifier = Modifier.size(TangemTheme.dimens.spacing40))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing12),
|
||||
|
|
@ -131,29 +127,6 @@ private fun InnerTitle(title: TextReference, titleExtra: TextReference, showTag:
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InnerIcon(imageUrl: String) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size40),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(enable = false)
|
||||
.build(),
|
||||
loading = { LoadingIcon() },
|
||||
error = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.tertiary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.core.ui.components.inputrow
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
internal fun InputRowImageBase(
|
||||
subtitle: TextReference,
|
||||
caption: TextReference,
|
||||
imageUrl: String,
|
||||
modifier: Modifier = Modifier,
|
||||
subtitleColor: Color = TangemTheme.colors.text.primary1,
|
||||
captionColor: Color = TangemTheme.colors.text.tertiary,
|
||||
extraContent: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier,
|
||||
) {
|
||||
InputRowAsyncImage(
|
||||
imageUrl = imageUrl,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.spacing36)
|
||||
.padding(vertical = TangemTheme.dimens.size1),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = subtitleColor,
|
||||
)
|
||||
Text(
|
||||
text = caption.resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = captionColor,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing2),
|
||||
)
|
||||
}
|
||||
extraContent()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.core.ui.components.inputrow
|
||||
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Input row component with selector
|
||||
* [Input Row Image](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2100-842&t=hoBXmDX8NeLrp4p6-4)
|
||||
*
|
||||
* @param subtitle subtitle text
|
||||
* @param caption caption text
|
||||
* @param imageUrl icon to load
|
||||
* @param modifier modifier
|
||||
* @param subtitleColor subtitle text color
|
||||
* @param captionColor caption text color
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowImageChevron(
|
||||
subtitle: TextReference,
|
||||
caption: TextReference,
|
||||
imageUrl: String,
|
||||
modifier: Modifier = Modifier,
|
||||
subtitleColor: Color = TangemTheme.colors.text.primary1,
|
||||
captionColor: Color = TangemTheme.colors.text.tertiary,
|
||||
) {
|
||||
InputRowImageBase(
|
||||
subtitle = subtitle,
|
||||
caption = caption,
|
||||
imageUrl = imageUrl,
|
||||
modifier = modifier,
|
||||
subtitleColor = subtitleColor,
|
||||
captionColor = captionColor,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_chevron_right_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package com.tangem.core.ui.components.inputrow
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.atoms.radiobutton.TangemRadioButton
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.annotatedReference
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Input row component with selector
|
||||
* [Input Row Image](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=2772-905&t=FH84ljLBk1vmGAei-4)
|
||||
*
|
||||
* @param subtitle subtitle text
|
||||
* @param caption caption text
|
||||
* @param imageUrl icon to load
|
||||
* @param onSelect callback when selected
|
||||
* @param modifier modifier
|
||||
* @param subtitleColor subtitle text color
|
||||
* @param captionColor caption text color
|
||||
* @param isSelected true if selected
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowImageSelector(
|
||||
subtitle: TextReference,
|
||||
caption: TextReference,
|
||||
imageUrl: String,
|
||||
onSelect: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
subtitleColor: Color = TangemTheme.colors.text.primary1,
|
||||
captionColor: Color = TangemTheme.colors.text.tertiary,
|
||||
isSelected: Boolean = false,
|
||||
) {
|
||||
InputRowImageBase(
|
||||
subtitle = subtitle,
|
||||
caption = caption,
|
||||
imageUrl = imageUrl,
|
||||
subtitleColor = subtitleColor,
|
||||
captionColor = captionColor,
|
||||
modifier = modifier
|
||||
.clickable(
|
||||
onClick = onSelect,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(),
|
||||
)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
TangemRadioButton(isSelected = isSelected, isEnabled = false, onClick = onSelect)
|
||||
}
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun InputRowImageSelectorPreview(
|
||||
@PreviewParameter(InputRowImageSelectorPreviewDataProvider::class) data: InputRowImageSelectorPreviewData,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
InputRowImageSelector(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
subtitle = data.subtitle,
|
||||
caption = combinedReference(
|
||||
resourceReference(R.string.staking_details_apr),
|
||||
annotatedReference(
|
||||
buildAnnotatedString {
|
||||
append(" ")
|
||||
withStyle(style = SpanStyle(color = TangemTheme.colors.text.accent)) {
|
||||
append(
|
||||
BigDecimalFormatter.formatPercent(BigDecimal.ZERO, true),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
imageUrl = "",
|
||||
isSelected = false,
|
||||
onSelect = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class InputRowImageSelectorPreviewData(
|
||||
val subtitle: TextReference,
|
||||
val caption: TextReference,
|
||||
val showDivider: Boolean,
|
||||
val actionIconRes: Int?,
|
||||
val isSelected: Boolean,
|
||||
)
|
||||
|
||||
private class InputRowImageSelectorPreviewDataProvider :
|
||||
PreviewParameterProvider<InputRowImageSelectorPreviewData> {
|
||||
override val values: Sequence<InputRowImageSelectorPreviewData>
|
||||
get() = sequenceOf(
|
||||
InputRowImageSelectorPreviewData(
|
||||
subtitle = TextReference.Str("subtitle"),
|
||||
caption = TextReference.Str("caption"),
|
||||
actionIconRes = null,
|
||||
showDivider = false,
|
||||
isSelected = false,
|
||||
),
|
||||
InputRowImageSelectorPreviewData(
|
||||
subtitle = TextReference.Str("subtitle"),
|
||||
caption = TextReference.Str("caption"),
|
||||
actionIconRes = R.drawable.ic_chevron_right_24,
|
||||
showDivider = true,
|
||||
isSelected = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.core.ui.components.inputrow.inner
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Loads image by url for icon in the input row
|
||||
*
|
||||
* @param imageUrl url of the image
|
||||
* @param modifier modifier
|
||||
*/
|
||||
@Composable
|
||||
internal fun InputRowAsyncImage(imageUrl: String, modifier: Modifier = Modifier) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = modifier,
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(enable = false)
|
||||
.build(),
|
||||
loading = { LoadingIcon() },
|
||||
error = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.tertiary,
|
||||
shape = CircleShape,
|
||||
),
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemColorPalette.White
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Travala notification with image background
|
||||
|
|
@ -192,7 +193,7 @@ private fun formatSubtitle(subtitle: String): AnnotatedString {
|
|||
@Preview
|
||||
@Composable
|
||||
private fun TravalaNotificationWithBackgroundPreview() {
|
||||
TangemTheme {
|
||||
TangemThemePreview {
|
||||
TravalaNotificationWithBackground(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
package com.tangem.core.ui.components.rows
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.R
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun RoundableCornersRow(
|
||||
startText: String,
|
||||
startTextColor: Color,
|
||||
startTextStyle: TextStyle,
|
||||
endText: String,
|
||||
endTextColor: Color,
|
||||
endTextStyle: TextStyle,
|
||||
cornersToRound: CornersToRound,
|
||||
iconResId: Int? = null,
|
||||
) {
|
||||
Surface(
|
||||
shape = cornersToRound.getShape(),
|
||||
color = TangemTheme.colors.background.primary,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(TangemTheme.dimens.size48)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = startText,
|
||||
color = startTextColor,
|
||||
maxLines = 1,
|
||||
style = startTextStyle,
|
||||
)
|
||||
if (iconResId != null) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(TangemTheme.dimens.spacing4)
|
||||
.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = R.drawable.ic_alert_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
text = endText,
|
||||
color = endTextColor,
|
||||
maxLines = 1,
|
||||
style = endTextStyle,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class CornersToRound {
|
||||
|
||||
ALL_4,
|
||||
TOP_2,
|
||||
BOTTOM_2,
|
||||
ZERO,
|
||||
;
|
||||
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
fun getShape(): RoundedCornerShape {
|
||||
val radius = TangemTheme.dimens.radius12
|
||||
return when (this) {
|
||||
ALL_4 -> RoundedCornerShape(radius)
|
||||
TOP_2 -> RoundedCornerShape(topStart = radius, topEnd = radius)
|
||||
BOTTOM_2 -> RoundedCornerShape(bottomStart = radius, bottomEnd = radius)
|
||||
ZERO -> RoundedCornerShape(0.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_RoundableCornersRow(
|
||||
@PreviewParameter(RoundableCornersRowDataProvider::class) previewData: RoundableCornersRowPreviewData,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
Box(modifier = Modifier.background(color = TangemTheme.colors.icon.attention)) {
|
||||
RoundableCornersRow(
|
||||
startText = previewData.startText,
|
||||
startTextColor = TangemTheme.colors.text.tertiary,
|
||||
startTextStyle = TangemTheme.typography.subtitle2,
|
||||
endText = previewData.endText,
|
||||
endTextColor = TangemTheme.colors.text.primary1,
|
||||
endTextStyle = TangemTheme.typography.subtitle2,
|
||||
cornersToRound = previewData.cornersToRound,
|
||||
iconResId = previewData.iconResId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class RoundableCornersRowPreviewData(
|
||||
val startText: String,
|
||||
val endText: String,
|
||||
val cornersToRound: CornersToRound,
|
||||
val iconResId: Int? = null,
|
||||
)
|
||||
|
||||
private class RoundableCornersRowDataProvider :
|
||||
PreviewParameterProvider<RoundableCornersRowPreviewData> {
|
||||
|
||||
override val values: Sequence<RoundableCornersRowPreviewData>
|
||||
get() = sequenceOf(
|
||||
getPreviewData(cornersToRound = CornersToRound.ZERO),
|
||||
getPreviewData(cornersToRound = CornersToRound.TOP_2),
|
||||
getPreviewData(cornersToRound = CornersToRound.BOTTOM_2),
|
||||
getPreviewData(cornersToRound = CornersToRound.ZERO, iconResId = R.drawable.ic_alert_24),
|
||||
getPreviewData(cornersToRound = CornersToRound.TOP_2, iconResId = R.drawable.ic_alert_24),
|
||||
getPreviewData(cornersToRound = CornersToRound.BOTTOM_2, iconResId = R.drawable.ic_alert_24),
|
||||
)
|
||||
|
||||
private fun getPreviewData(cornersToRound: CornersToRound, iconResId: Int? = null) = RoundableCornersRowPreviewData(
|
||||
startText = "startText",
|
||||
endText = "endText",
|
||||
cornersToRound = cornersToRound,
|
||||
iconResId = iconResId,
|
||||
)
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.utils.Strings
|
||||
|
||||
@Composable
|
||||
fun SelectorRowItem(
|
||||
|
|
@ -127,7 +128,7 @@ private fun RowScope.SelectorValueContent(
|
|||
)
|
||||
if (postDot != null) {
|
||||
Text(
|
||||
text = "•",
|
||||
text = Strings.DOT,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
package com.tangem.core.ui.components.showcase
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.showcase.model.ShowcaseButtonModel
|
||||
import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* @param headerIconRes big header icon
|
||||
* @param headerText header text
|
||||
* @param showcaseItems list of bullet points
|
||||
* @param primaryButton primary button
|
||||
* @param secondaryButton secondary button
|
||||
* @param modifier compose modifier
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/design/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?node-id=12620-90568&t=WeCOVTgeYfkr24Ff-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun Showcase(
|
||||
@DrawableRes headerIconRes: Int,
|
||||
headerText: TextReference,
|
||||
showcaseItems: ImmutableList<ShowcaseItemModel>,
|
||||
primaryButton: ShowcaseButtonModel,
|
||||
secondaryButton: ShowcaseButtonModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
ShowcaseContent(
|
||||
headerIconRes = headerIconRes,
|
||||
headerText = headerText,
|
||||
showcaseItems = showcaseItems,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
ShowcaseButtons(
|
||||
primaryButtonText = primaryButton.buttonText,
|
||||
onPrimaryClick = primaryButton.onClick,
|
||||
secondaryButtonText = secondaryButton.buttonText,
|
||||
onSecondaryClick = secondaryButton.onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowcaseButtons(
|
||||
primaryButtonText: TextReference,
|
||||
secondaryButtonText: TextReference,
|
||||
onPrimaryClick: () -> Unit,
|
||||
onSecondaryClick: () -> Unit,
|
||||
hint: TextReference? = null,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
PrimaryButton(
|
||||
text = primaryButtonText.resolveReference(),
|
||||
onClick = onPrimaryClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
)
|
||||
SecondaryButton(
|
||||
text = secondaryButtonText.resolveReference(),
|
||||
onClick = onSecondaryClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
)
|
||||
hint?.let {
|
||||
Text(
|
||||
text = it.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 720)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Showcase_Preview() {
|
||||
TangemThemePreview {
|
||||
Showcase(
|
||||
headerIconRes = R.drawable.ic_notifications_unread_24,
|
||||
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),
|
||||
),
|
||||
ShowcaseItemModel(
|
||||
R.drawable.ic_storefront_24,
|
||||
resourceReference(R.string.user_push_notification_agreement_argument_two),
|
||||
),
|
||||
),
|
||||
primaryButton = ShowcaseButtonModel(resourceReference(R.string.common_allow), {}),
|
||||
secondaryButton = ShowcaseButtonModel(resourceReference(R.string.common_later), {}),
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.core.ui.components.showcase
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.text.style.TextAlign
|
||||
import com.tangem.core.ui.components.showcase.model.ShowcaseItemModel
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun ShowcaseContent(
|
||||
@DrawableRes headerIconRes: Int,
|
||||
headerText: TextReference,
|
||||
showcaseItems: ImmutableList<ShowcaseItemModel>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = headerIconRes),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.size(TangemTheme.dimens.size56),
|
||||
)
|
||||
Text(
|
||||
text = headerText.resolveReference(),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing34,
|
||||
end = TangemTheme.dimens.spacing34,
|
||||
top = TangemTheme.dimens.spacing28,
|
||||
),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing34,
|
||||
end = TangemTheme.dimens.spacing34,
|
||||
top = TangemTheme.dimens.spacing28,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
repeat(showcaseItems.size) { index ->
|
||||
ShowcaseItem(
|
||||
iconRes = showcaseItems[index].iconRes,
|
||||
text = showcaseItems[index].text,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.core.ui.components.showcase
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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 {
|
||||
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,
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing20),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.ui.components.showcase.model
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class ShowcaseButtonModel(
|
||||
val buttonText: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.core.ui.components.showcase.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class ShowcaseItemModel(
|
||||
@DrawableRes val iconRes: Int,
|
||||
val text: TextReference,
|
||||
)
|
||||
|
|
@ -19,6 +19,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
/**
|
||||
* Snackbar to inform the user about copying text to the clipboard
|
||||
|
|
@ -100,7 +101,7 @@ private fun MessageText(text: TextReference, modifier: Modifier = Modifier) {
|
|||
private fun Preview_CopiedTextSnackbar(
|
||||
@PreviewParameter(CopiedTextSnackbarDataProvider::class) message: TextReference,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
CopiedTextSnackbar(message = message)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import com.tangem.common.Strings
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
|
|
@ -35,6 +34,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.utils.Strings
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.core.ui.decompose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Stable
|
||||
fun interface ComposableContentComponent {
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun Content(modifier: Modifier)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.core.ui.decompose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
@Stable
|
||||
interface ComposableDialogComponent {
|
||||
|
||||
val doOnDismiss: () -> Unit
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun Dialog()
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* Utility class for creating text as [String] or [StringRes].
|
||||
|
|
@ -46,6 +48,13 @@ sealed interface TextReference {
|
|||
*/
|
||||
data class Str(val value: String) : TextReference
|
||||
|
||||
/**
|
||||
* Annotated text
|
||||
*
|
||||
* @property value annotated string
|
||||
*/
|
||||
data class Annotated(val value: AnnotatedString) : TextReference
|
||||
|
||||
/**
|
||||
* Combined reference. It concatenates all [refs].
|
||||
*
|
||||
|
|
@ -81,6 +90,16 @@ fun stringReference(value: String): TextReference {
|
|||
return TextReference.Str(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [TextReference] using an annotated string value.
|
||||
*
|
||||
* @param value The annotated string value.
|
||||
* @return A [TextReference] representing the provided annotated string value.
|
||||
*/
|
||||
fun annotatedReference(value: AnnotatedString): TextReference {
|
||||
return TextReference.Annotated(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [TextReference] using a plural string resource ID with count and optional format arguments.
|
||||
*
|
||||
|
|
@ -131,6 +150,7 @@ fun TextReference.resolveReference(): String {
|
|||
}
|
||||
is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray())
|
||||
is TextReference.Str -> value
|
||||
is TextReference.Annotated -> value.text
|
||||
is TextReference.Combined -> {
|
||||
buildString {
|
||||
refs.forEach {
|
||||
|
|
@ -153,6 +173,7 @@ fun TextReference.resolveReference(resources: Resources): String {
|
|||
}
|
||||
is TextReference.PluralRes -> resources.getQuantityString(id, count, *formatArgs.toTypedArray())
|
||||
is TextReference.Str -> value
|
||||
is TextReference.Annotated -> value.text
|
||||
is TextReference.Combined -> {
|
||||
buildString {
|
||||
refs.forEach {
|
||||
|
|
@ -178,9 +199,10 @@ fun TextReference.resolveAnnotatedReference(): AnnotatedString {
|
|||
pluralStringResource(id, count, *formatArgs.toTypedArray()),
|
||||
)
|
||||
is TextReference.Str -> formatAnnotated(value)
|
||||
is TextReference.Annotated -> value
|
||||
is TextReference.Combined -> buildAnnotatedString {
|
||||
refs.forEach {
|
||||
append(formatAnnotated(it.resolveReference()))
|
||||
append(it.resolveAnnotatedReference())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -193,10 +215,21 @@ operator fun TextReference.plus(ref: TextReference): TextReference {
|
|||
is TextReference.PluralRes,
|
||||
is TextReference.Res,
|
||||
is TextReference.Str,
|
||||
is TextReference.Annotated,
|
||||
-> TextReference.Combined(refs = wrappedList(this, ref))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
inline fun TextReference?.isNullOrEmpty(): Boolean {
|
||||
contract {
|
||||
returns(false) implies (this@isNullOrEmpty != null)
|
||||
}
|
||||
|
||||
return this == null || this == TextReference.EMPTY
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun formatAnnotated(rawString: String): AnnotatedString {
|
||||
val markdownDescriptor = rememberMarkdownParser()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.core.ui.message
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.ui.UiMessage
|
||||
import com.tangem.core.decompose.ui.UiMessageHandler
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
|
|
@ -11,6 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
/**
|
||||
* Message handler that is used to show or remove an [EventMessage] in the UI.
|
||||
*/
|
||||
@Stable
|
||||
class EventMessageHandler(
|
||||
private val events: MutableStateFlow<StateEvent<EventMessage>> = MutableStateFlow(consumedEvent()),
|
||||
) : UiMessageHandler, StateFlow<StateEvent<EventMessage>> by events {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ class TangemColors internal constructor(
|
|||
warning: Color,
|
||||
attention: Color,
|
||||
accent: Color = TangemColorPalette.Azure,
|
||||
constant: Color = TangemColorPalette.White,
|
||||
) {
|
||||
var primary1 by mutableStateOf(primary1)
|
||||
private set
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ data class TangemDimens internal constructor(
|
|||
val size5: Dp = 5.dp,
|
||||
val size7: Dp = 7.dp,
|
||||
val size8: Dp = 8.dp,
|
||||
val size9: Dp = 9.dp,
|
||||
val size10: Dp = 10.dp,
|
||||
val size11: Dp = 11.dp,
|
||||
val size12: Dp = 12.dp,
|
||||
|
|
@ -81,8 +82,10 @@ data class TangemDimens internal constructor(
|
|||
val size90: Dp = 90.dp,
|
||||
val size93: Dp = 93.dp,
|
||||
val size96: Dp = 96.dp,
|
||||
val size100: Dp = 100.dp,
|
||||
val size102: Dp = 102.dp,
|
||||
val size108: Dp = 108.dp,
|
||||
val size110: Dp = 110.dp,
|
||||
val size116: Dp = 116.dp,
|
||||
val size120: Dp = 120.dp,
|
||||
val size142: Dp = 142.dp,
|
||||
|
|
@ -99,9 +102,11 @@ data class TangemDimens internal constructor(
|
|||
val spacing2: Dp = 2.dp,
|
||||
val spacing3: Dp = 3.dp,
|
||||
val spacing4: Dp = 4.dp,
|
||||
val spacing5: Dp = 5.dp,
|
||||
val spacing6: Dp = 6.dp,
|
||||
val spacing8: Dp = 8.dp,
|
||||
val spacing10: Dp = 10.dp,
|
||||
val spacing11: Dp = 11.dp,
|
||||
val spacing12: Dp = 12.dp,
|
||||
val spacing14: Dp = 14.dp,
|
||||
val spacing15: Dp = 15.dp,
|
||||
|
|
|
|||
|
|
@ -3,19 +3,22 @@ package com.tangem.core.ui.res
|
|||
import androidx.compose.material.Colors
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ProvideTextStyle
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.tangem.core.ui.haptic.HapticManager
|
||||
import com.tangem.core.ui.haptic.MockHapticManager
|
||||
|
||||
// TODO: use isSystemInDarkTheme() for automatic color detection
|
||||
internal const val IS_SYSTEM_IN_DARK_THEME: Boolean = false
|
||||
import com.tangem.core.ui.windowsize.WindowSize
|
||||
|
||||
@Composable
|
||||
fun TangemTheme(
|
||||
isDark: Boolean = false,
|
||||
windowSize: WindowSize,
|
||||
typography: TangemTypography = TangemTheme.typography,
|
||||
dimens: TangemDimens = TangemTheme.dimens,
|
||||
hapticManager: HapticManager = MockHapticManager,
|
||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
|
||||
|
|
@ -23,6 +26,16 @@ fun TangemTheme(
|
|||
.also { it.update(themeColors) }
|
||||
|
||||
val shapes = remember { TangemShapes(dimens) }
|
||||
val systemUiController = rememberSystemUiController()
|
||||
|
||||
SideEffect {
|
||||
systemUiController.setSystemBarsColor(
|
||||
color = Color.Transparent,
|
||||
darkIcons = !isDark,
|
||||
isNavigationBarContrastEnforced = false,
|
||||
)
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colors = materialThemeColors(colors = themeColors, isDark = isDark),
|
||||
) {
|
||||
|
|
@ -33,6 +46,8 @@ fun TangemTheme(
|
|||
LocalTangemShapes provides shapes,
|
||||
LocalIsInDarkTheme provides isDark,
|
||||
LocalHapticManager provides hapticManager,
|
||||
LocalSnackbarHostState provides snackbarHostState,
|
||||
LocalWindowSize provides windowSize,
|
||||
) {
|
||||
ProvideTextStyle(
|
||||
value = TangemTheme.typography.body1,
|
||||
|
|
@ -204,4 +219,12 @@ val LocalIsInDarkTheme = staticCompositionLocalOf { false }
|
|||
|
||||
val LocalHapticManager = staticCompositionLocalOf<HapticManager> {
|
||||
error("No HapticManager provided")
|
||||
}
|
||||
|
||||
val LocalSnackbarHostState = staticCompositionLocalOf<SnackbarHostState> {
|
||||
error("No SnackbarHostState provided")
|
||||
}
|
||||
|
||||
val LocalWindowSize = staticCompositionLocalOf<WindowSize> {
|
||||
error("No WindowSize provided")
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.core.ui.res
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.core.ui.windowsize.rememberWindowSizePreview
|
||||
|
||||
@Composable
|
||||
fun TangemThemePreview(
|
||||
|
|
@ -12,10 +14,13 @@ fun TangemThemePreview(
|
|||
) {
|
||||
val isDarkTheme = isDark ?: isSystemInDarkTheme()
|
||||
|
||||
TangemTheme(
|
||||
isDark = isDarkTheme,
|
||||
typography = typography,
|
||||
dimens = dimens,
|
||||
content = content,
|
||||
)
|
||||
BoxWithConstraints {
|
||||
TangemTheme(
|
||||
isDark = isDarkTheme,
|
||||
typography = typography,
|
||||
dimens = dimens,
|
||||
windowSize = rememberWindowSizePreview(maxWidth, maxHeight),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
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