Updated on 2026-08-14
This commit is contained in:
commit
d0b35bc331
680 changed files with 26556 additions and 2709 deletions
|
|
@ -328,6 +328,7 @@ sealed class AnalyticsParam {
|
|||
const val WALLET_TYPE = "Wallet Type"
|
||||
const val BACKUPED = "Backuped"
|
||||
const val MEMO = "Memo"
|
||||
const val VALUE = "Value"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
{
|
||||
"name": "APP_REDESIGN_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "5.40"
|
||||
},
|
||||
{
|
||||
"name": "GASLESS_APPROVAL_ENABLED",
|
||||
|
|
@ -55,6 +55,10 @@
|
|||
"name": "WALLET_CONNECT_BITCOIN_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "TWI_1326_YIELD_MODE_SWAP_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "ADDRESS_SYNC_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -64,7 +68,7 @@
|
|||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "SWAP_INTEGRATED_APPROVE",
|
||||
"name": "AND_15120_SWAP_INTEGRATED_APPROVE",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
|
|
@ -106,5 +110,21 @@
|
|||
{
|
||||
"name": "TWI_1512_HIDE_STORIES_FOR_REFERRAL_ENABLED",
|
||||
"version": "5.39"
|
||||
},
|
||||
{
|
||||
"name": "AND_15438_BACKEND_AUTHENTICATION_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15482_SURVEYSPARROW_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15258_QUICK_TOP_UP_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15368_VISA_PAY_REDESIGN",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.core.configtoggle.feature
|
||||
|
||||
/**
|
||||
* Feature toggle information exposed by [MutableFeatureTogglesManager].
|
||||
*
|
||||
* @property name raw toggle name
|
||||
* @property version release version from local config ("undefined" for permanently disabled toggles)
|
||||
* @property isEnabled current toggle state (may differ from default if overridden locally)
|
||||
*/
|
||||
data class FeatureToggleInfo(
|
||||
val name: String,
|
||||
val version: String,
|
||||
val isEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -2,6 +2,9 @@ package com.tangem.core.configtoggle.feature
|
|||
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
|
||||
/** Version value marking a feature toggle that has no planned release (permanently disabled). */
|
||||
const val DISABLED_FEATURE_TOGGLE_VERSION = "undefined"
|
||||
|
||||
/**
|
||||
* Component for getting information about the availability of feature toggles
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ interface MutableFeatureTogglesManager : FeatureTogglesManager {
|
|||
/** Check if the current state of the feature toggles matches the local config state. */
|
||||
fun isMatchLocalConfig(): Boolean
|
||||
|
||||
/** Get feature toggles */
|
||||
fun getFeatureToggles(): Map<String, Boolean>
|
||||
/** Get feature toggles with version info */
|
||||
fun getFeatureToggles(): List<FeatureToggleInfo>
|
||||
|
||||
/** Change availability [isEnabled] of toggle with name [name] */
|
||||
suspend fun changeToggle(name: String, isEnabled: Boolean)
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ package com.tangem.core.configtoggle.feature.impl
|
|||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.tangem.core.configtoggle.FeatureToggles
|
||||
import com.tangem.core.configtoggle.feature.FeatureToggleInfo
|
||||
import com.tangem.core.configtoggle.feature.MutableFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.provider.FeatureTogglesProvider
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.utils.defineTogglesAvailability
|
||||
import com.tangem.core.configtoggle.utils.toTableString
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Feature toggles manager implementation in dev or mocked build
|
||||
|
|
@ -24,54 +26,62 @@ internal class DevFeatureTogglesManager(
|
|||
private val featureTogglesLocalStorage: LocalTogglesStorage,
|
||||
) : MutableFeatureTogglesManager {
|
||||
|
||||
private val fileFeatureTogglesMap: Map<String, Boolean> = getFileFeatureToggles()
|
||||
private val fileFeatureToggles: List<FeatureToggleInfo> = buildFileFeatureToggles()
|
||||
|
||||
@Suppress("DoubleMutabilityForCollection")
|
||||
private var featureTogglesMap: MutableMap<String, Boolean> by Delegates.notNull()
|
||||
private val currentToggles: MutableStateFlow<List<FeatureToggleInfo>> = MutableStateFlow(buildInitialToggles())
|
||||
|
||||
init {
|
||||
val savedFeatureToggles = runBlocking { featureTogglesLocalStorage.getSyncOrEmpty() }
|
||||
|
||||
featureTogglesMap = fileFeatureTogglesMap
|
||||
.mapValues { resultToggle ->
|
||||
savedFeatureToggles[resultToggle.key] ?: resultToggle.value
|
||||
}
|
||||
.toMutableMap()
|
||||
}
|
||||
|
||||
override fun isFeatureEnabled(toggle: FeatureToggles): Boolean = featureTogglesMap[toggle.rawName] == true
|
||||
override fun isFeatureEnabled(toggle: FeatureToggles): Boolean =
|
||||
currentToggles.value.any { it.name == toggle.rawName && it.isEnabled }
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
fun isFeatureEnabledByName(name: String): Boolean = featureTogglesMap[name] == true
|
||||
fun isFeatureEnabledByName(name: String): Boolean = currentToggles.value.any { it.name == name && it.isEnabled }
|
||||
|
||||
override fun getFeatureToggles(): Map<String, Boolean> = featureTogglesMap
|
||||
override fun getFeatureToggles(): List<FeatureToggleInfo> = currentToggles.value
|
||||
|
||||
override fun isMatchLocalConfig(): Boolean = featureTogglesMap == fileFeatureTogglesMap
|
||||
override fun isMatchLocalConfig(): Boolean =
|
||||
currentToggles.value.associateBy { it.name } == fileFeatureToggles.associateBy { it.name }
|
||||
|
||||
override suspend fun changeToggle(name: String, isEnabled: Boolean) {
|
||||
featureTogglesMap[name] ?: return
|
||||
featureTogglesMap[name] = isEnabled
|
||||
featureTogglesLocalStorage.store(value = featureTogglesMap)
|
||||
if (currentToggles.value.none { it.name == name }) return
|
||||
currentToggles.update { toggles ->
|
||||
toggles.map { toggle ->
|
||||
if (toggle.name == name) toggle.copy(isEnabled = isEnabled) else toggle
|
||||
}
|
||||
}
|
||||
featureTogglesLocalStorage.store(value = currentToggles.value.toAvailabilityMap())
|
||||
}
|
||||
|
||||
override suspend fun recoverLocalConfig() {
|
||||
featureTogglesMap = fileFeatureTogglesMap.toMutableMap()
|
||||
featureTogglesLocalStorage.store(value = fileFeatureTogglesMap)
|
||||
currentToggles.value = fileFeatureToggles
|
||||
featureTogglesLocalStorage.store(value = currentToggles.value.toAvailabilityMap())
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return featureTogglesMap.toTableString(tableName = this@DevFeatureTogglesManager::class.java.simpleName)
|
||||
}
|
||||
|
||||
private fun getFileFeatureToggles(): Map<String, Boolean> {
|
||||
val appVersion = versionProvider.get()
|
||||
|
||||
return featureTogglesProvider.getToggles()
|
||||
.defineTogglesAvailability(appVersion = appVersion)
|
||||
return currentToggles.value.toAvailabilityMap()
|
||||
.toTableString(tableName = this@DevFeatureTogglesManager::class.java.simpleName)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
fun setFeatureToggles(map: MutableMap<String, Boolean>) {
|
||||
featureTogglesMap = map
|
||||
currentToggles.value = map.map { (name, isEnabled) ->
|
||||
val version = fileFeatureToggles.firstOrNull { it.name == name }?.version.orEmpty()
|
||||
FeatureToggleInfo(name = name, version = version, isEnabled = isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildInitialToggles(): List<FeatureToggleInfo> {
|
||||
val savedFeatureToggles = runBlocking { featureTogglesLocalStorage.getSyncOrEmpty() }
|
||||
return fileFeatureToggles.map { it.copy(isEnabled = savedFeatureToggles[it.name] ?: it.isEnabled) }
|
||||
}
|
||||
|
||||
private fun buildFileFeatureToggles(): List<FeatureToggleInfo> {
|
||||
val rawToggles = featureTogglesProvider.getToggles()
|
||||
val availability = rawToggles.defineTogglesAvailability(appVersion = versionProvider.get())
|
||||
return rawToggles.map { (name, version) ->
|
||||
FeatureToggleInfo(name = name, version = version, isEnabled = availability.getValue(name))
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<FeatureToggleInfo>.toAvailabilityMap(): Map<String, Boolean> =
|
||||
associate { it.name to it.isEnabled }
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.core.configtoggle.feature.impl
|
||||
|
||||
internal object FeatureTogglesConstants {
|
||||
|
||||
const val LOCAL_CONFIG_PATH: String = "configs/feature_toggles_config"
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import com.tangem.utils.logging.TangemLogger
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class Version private constructor(value: String) : Comparable<Version> {
|
||||
class Version private constructor(value: String) : Comparable<Version> {
|
||||
|
||||
private val major: Int
|
||||
private val minor: Int
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.core.configtoggle.version
|
||||
|
||||
import com.tangem.core.configtoggle.feature.DISABLED_FEATURE_TOGGLE_VERSION
|
||||
|
||||
/**
|
||||
* Version contract to evaluate availability of feature toggle
|
||||
*
|
||||
|
|
@ -7,8 +9,6 @@ package com.tangem.core.configtoggle.version
|
|||
*/
|
||||
internal object VersionAvailabilityContract {
|
||||
|
||||
private const val DISABLED_FEATURE_TOGGLE_VERSION = "undefined"
|
||||
|
||||
/** Evaluate availability of feature toggles using [currentVersion] and [localVersion] */
|
||||
operator fun invoke(currentVersion: String, localVersion: String): Boolean {
|
||||
if (localVersion == DISABLED_FEATURE_TOGGLE_VERSION) return false
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ internal class FeatureTogglesNamingConventionTest {
|
|||
"SOLANA_TX_HISTORY_ENABLED",
|
||||
"STAKING_ETH_ENABLED",
|
||||
"SWAP_AB_ENABLED",
|
||||
"SWAP_INTEGRATED_APPROVE",
|
||||
"USEDESK_ENABLED",
|
||||
"VIRTUAL_ACCOUNTS_ENABLED",
|
||||
"VISA_ONBOARDING_ENABLED",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.core.configtoggle.manager
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.configtoggle.feature.FeatureToggleInfo
|
||||
import com.tangem.core.configtoggle.feature.impl.DevFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.provider.FeatureTogglesProvider
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
|
|
@ -28,6 +29,14 @@ internal class DevFeatureTogglesManagerTest {
|
|||
version != "undefined" && !appVersion.isNullOrEmpty()
|
||||
}
|
||||
|
||||
private fun Map<String, Boolean>.toToggleInfoList(): List<FeatureToggleInfo> = map { (name, isEnabled) ->
|
||||
FeatureToggleInfo(
|
||||
name = name,
|
||||
version = testToggles.getValue(name),
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Initialization {
|
||||
|
|
@ -60,7 +69,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
// Assert
|
||||
val expected = savedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected.toToggleInfoList())
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -86,7 +95,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
// Assert
|
||||
val expected = savedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected.toToggleInfoList())
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -112,7 +121,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
// Assert
|
||||
val expected = savedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected.toToggleInfoList())
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -159,7 +168,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
// Assert
|
||||
val expected = getExpectedFileToggles(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected.toToggleInfoList())
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -185,7 +194,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
// Assert
|
||||
val expected = fileToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected.toToggleInfoList())
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -302,6 +311,59 @@ internal class DevFeatureTogglesManagerTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isMatchLocalConfig is true when toggles match but order differs`() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
// Same toggles and values as the local config, but in reversed order
|
||||
val reorderedToggles = getExpectedFileToggles(appVersion = "1.0.0")
|
||||
.entries.reversed()
|
||||
.associate { it.key to it.value }
|
||||
.toMutableMap()
|
||||
val manager = DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
).apply {
|
||||
setFeatureToggles(reorderedToggles)
|
||||
}
|
||||
|
||||
// Act
|
||||
val actual = manager.isMatchLocalConfig()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isMatchLocalConfig is false when a value differs despite reversed order`() = runTest {
|
||||
// Arrange
|
||||
every { versionProvider.get() } returns "1.0.0"
|
||||
coEvery { featureTogglesLocalStorage.getSyncOrEmpty() } returns emptyMap()
|
||||
|
||||
// Reversed order AND one toggle value flipped → must not match
|
||||
val reorderedChangedToggles = getExpectedFileToggles(appVersion = "1.0.0")
|
||||
.entries.reversed()
|
||||
.associate { it.key to it.value }
|
||||
.toMutableMap()
|
||||
.apply { this["ENABLED_TOGGLE"] = false }
|
||||
val manager = DevFeatureTogglesManager(
|
||||
versionProvider,
|
||||
featureTogglesProvider,
|
||||
featureTogglesLocalStorage,
|
||||
).apply {
|
||||
setFeatureToggles(reorderedChangedToggles)
|
||||
}
|
||||
|
||||
// Act
|
||||
val actual = manager.isMatchLocalConfig()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
private fun provideTestModels(): List<IsMatchLocalConfigModel> {
|
||||
val appVersion = "1.0.0"
|
||||
val fileToggles = getExpectedFileToggles(appVersion)
|
||||
|
|
@ -369,7 +431,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
// Assert
|
||||
val expected = fileToggles + savedToggles
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected.toToggleInfoList())
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -405,7 +467,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
val actual = manager.getFeatureToggles()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(model.expectedToggles)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(model.expectedToggles.toToggleInfoList())
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
@ -478,7 +540,7 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
// Assert
|
||||
val expected = getExpectedFileToggles(appVersion)
|
||||
Truth.assertThat(actual).containsExactlyEntriesIn(expected)
|
||||
Truth.assertThat(actual).containsExactlyElementsIn(expected.toToggleInfoList())
|
||||
|
||||
coVerifyOrder {
|
||||
versionProvider.get()
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ dependencies {
|
|||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.kotlin.coroutines.rx2)
|
||||
implementation(deps.kotlin.datetime)
|
||||
implementation(deps.kotlin.serialization)
|
||||
|
||||
/** Logging */
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.datasource.api.auth
|
||||
|
||||
import com.tangem.datasource.api.auth.models.request.AuthApiRequest
|
||||
import com.tangem.datasource.api.auth.models.request.NonceApiRequest
|
||||
import com.tangem.datasource.api.auth.models.request.RefreshApiRequest
|
||||
import com.tangem.datasource.api.auth.models.response.NonceApiResponse
|
||||
import com.tangem.datasource.api.auth.models.response.TokenApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Tangem Auth Service API (JWT session tokens / DPoP interceptor / refresh rotation)
|
||||
*/
|
||||
interface AuthApi {
|
||||
|
||||
/**
|
||||
* Request authentication nonce.
|
||||
*
|
||||
* Generates a nonce bound to the device public key for the authentication flow.
|
||||
*/
|
||||
@POST("api/v1/auth/nonce/auth")
|
||||
suspend fun requestAuthNonce(@Body request: NonceApiRequest): ApiResponse<NonceApiResponse>
|
||||
|
||||
/**
|
||||
* Authenticate device.
|
||||
*
|
||||
* Authenticates a previously registered device using a device-key signature. Issues a new
|
||||
* JWT access token with bound `walletIds[]` and risk tier. All subsequent auth after
|
||||
* registration uses this endpoint.
|
||||
*/
|
||||
@POST("api/v1/auth/authenticate")
|
||||
suspend fun authenticate(@Body request: AuthApiRequest): ApiResponse<TokenApiResponse>
|
||||
|
||||
/**
|
||||
* Refresh tokens.
|
||||
*
|
||||
* Rotates the refresh token and issues a new access token. Uses refresh-token rotation
|
||||
* with family-based reuse detection — replaying a consumed token revokes the entire token
|
||||
* family (SR-8). Sender-constraint is verified via the DPoP-proof header (`cnf.jkt`).
|
||||
*/
|
||||
@POST("api/v1/auth/refresh")
|
||||
@RequiresSessionAuth
|
||||
suspend fun refresh(@Body request: RefreshApiRequest): ApiResponse<TokenApiResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.api.auth
|
||||
|
||||
/**
|
||||
* Marks a Retrofit endpoint as requiring an authenticated session (DPoP, see
|
||||
* [RFC 9449](https://www.rfc-editor.org/rfc/rfc9449)).
|
||||
*
|
||||
* Read at runtime by the session-auth interceptor: only methods
|
||||
* carrying this annotation receive `Authorization: DPoP <access-token>` + `DPoP: <proof-jwt>`
|
||||
* headers; unannotated methods (e.g. public nonce endpoints) pass through unchanged.
|
||||
*
|
||||
* Mirrors the per-operation `security` blocks in the backend OpenAPI contract; follows the
|
||||
* same on-method annotation pattern as `@ReadTimeout` / `@ConnectTimeout`.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class RequiresSessionAuth
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.datasource.api.auth.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/** Authentication request — authenticates a previously registered device. */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AuthApiRequest(
|
||||
/** Signed authentication payload. */
|
||||
@Json(name = "payload") val payload: AuthenticationPayload,
|
||||
/** EC signature over the authentication payload, signed by the device private key (Base64). */
|
||||
@Json(name = "signature") val signature: String,
|
||||
)
|
||||
|
||||
/** Signed authentication payload — the data that is signed by the device private key. */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AuthenticationPayload(
|
||||
/** Base64-encoded EC public key of the device (e.g. `MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...`). */
|
||||
@Json(name = "devicePublicKey") val devicePublicKey: String,
|
||||
/** Deciphered nonce value from the nonce endpoint. */
|
||||
@Json(name = "nonce") val nonce: String,
|
||||
/** Platform attestation token (Play Integrity / App Attest). */
|
||||
@Json(name = "attestationToken") val attestationToken: String?,
|
||||
/** Client-reported device metadata. */
|
||||
@Json(name = "metadata") val metadata: DeviceMetadata,
|
||||
) {
|
||||
|
||||
/** Device metadata collection. */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DeviceMetadata(
|
||||
/** Device hardware model (e.g. `iPhone 15 Pro`). */
|
||||
@Json(name = "deviceModel") val deviceModel: String?,
|
||||
/** Operating system (`android` / `ios`). */
|
||||
@Json(name = "os") val os: String,
|
||||
/** OS version string (e.g. `17.4.1`). */
|
||||
@Json(name = "osVersion") val osVersion: String?,
|
||||
/** Application version (e.g. `5.8.0`). */
|
||||
@Json(name = "appVersion") val appVersion: String?,
|
||||
/** User-Agent header (e.g. `Tangem/5.8.0 (iPhone; iOS 17.4.1; Scale/3.00)`). */
|
||||
@Json(name = "userAgent") val userAgent: String?,
|
||||
/** Client locale (e.g. `en-US`). */
|
||||
@Json(name = "locale") val locale: String?,
|
||||
/** Client timezone (e.g. `Europe/Moscow`). */
|
||||
@Json(name = "timezone") val timezone: String?,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.auth.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/** Request body for nonce generation (auth, upgrade, wallet flows). */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NonceApiRequest(
|
||||
/** Base64-encoded EC public key of the device (e.g. `MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...`). */
|
||||
@Json(name = "devicePublicKey") val devicePublicKey: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.auth.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/** Token refresh request. */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefreshApiRequest(
|
||||
/** Refresh token from a previous token response. */
|
||||
@Json(name = "refreshToken") val refreshToken: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.datasource.api.auth.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/** Ciphered nonce response. */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NonceApiResponse(
|
||||
/** RSA-OAEP ciphered nonce value (Base64). */
|
||||
@Json(name = "cipheredNonce") val cipheredNonce: String,
|
||||
/** Nonce expiration timestamp (ISO-8601). */
|
||||
@Json(name = "expiresAt") val expiresAt: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.datasource.api.auth.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* RFC 9457 / RFC 7807 Problem Details response. Returned by Tangem Auth Service with
|
||||
* `Content-Type: application/problem+json` on every 4xx / 5xx response.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ProblemDetailResponse(
|
||||
/** URI identifying the problem type. */
|
||||
@Json(name = "type") val type: String,
|
||||
/** Short human-readable summary (e.g. `"Too Many Requests"`). */
|
||||
@Json(name = "title") val title: String,
|
||||
/** HTTP status code. */
|
||||
@Json(name = "status") val status: Int,
|
||||
/** Human-readable explanation. */
|
||||
@Json(name = "detail") val detail: String?,
|
||||
/** URI reference to this occurrence (e.g. `"/api/v1/auth/refresh"`). */
|
||||
@Json(name = "instance") val instance: String?,
|
||||
/** Application-specific error code. */
|
||||
@Json(name = "code") val code: String?,
|
||||
/** Retry delay for rate limiting (`429`). */
|
||||
@Json(name = "retryAfterSeconds") val retryAfterSeconds: Int?,
|
||||
)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.datasource.api.auth.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/** Token response — contains JWT access token and optional refresh token. */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenApiResponse(
|
||||
/** JWT access token (HMAC-SHA256 signed). */
|
||||
@Json(name = "accessToken") val accessToken: String,
|
||||
/** Access token expiration timestamp (ISO-8601). */
|
||||
@Json(name = "accessTokenExpiresAt") val accessTokenExpiresAt: String,
|
||||
/** Refresh token for token rotation. `null` for ORANGE tier (requires device challenge each time). */
|
||||
@Json(name = "refreshToken") val refreshToken: String?,
|
||||
/** Refresh token expiration timestamp (ISO-8601). `null` iff [refreshToken] is `null`. */
|
||||
@Json(name = "refreshTokenExpiresAt") val refreshTokenExpiresAt: String?,
|
||||
/** List of wallet IDs bound to this device. */
|
||||
@Json(name = "walletIds") val walletIds: List<String>,
|
||||
)
|
||||
|
|
@ -33,6 +33,7 @@ sealed class ApiConfig {
|
|||
News,
|
||||
GaslessTxService,
|
||||
SurveySparrow,
|
||||
Auth,
|
||||
}
|
||||
|
||||
private fun initializeId(): ID {
|
||||
|
|
@ -49,6 +50,7 @@ sealed class ApiConfig {
|
|||
is News -> ID.News
|
||||
is GaslessTxService -> ID.GaslessTxService
|
||||
is SurveySparrow -> ID.SurveySparrow
|
||||
is Auth -> ID.Auth
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
|
||||
/**
|
||||
* Tangem Auth Service [ApiConfig] — endpoints for device registration, authentication,
|
||||
* nonce issuance, refresh token rotation, and JWKS publication.
|
||||
*/
|
||||
internal class Auth : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environmentConfigs: List<ApiEnvironmentConfig> = listOf(
|
||||
createDevEnvironment(),
|
||||
createMockedEnvironment(),
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
|
||||
DEBUG_BUILD_TYPE,
|
||||
INTERNAL_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = DEV_BASE_URL,
|
||||
headers = emptyMap(),
|
||||
)
|
||||
|
||||
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = MOCK_BASE_URL,
|
||||
headers = emptyMap(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = PROD_BASE_URL,
|
||||
headers = emptyMap(),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
|
||||
// TODO Replace with real Auth Service hosts once the backend team confirms deployment.
|
||||
// Swagger currently only declares `http://localhost:8080` for local development.
|
||||
// [REDACTED_JIRA]
|
||||
private const val DEV_BASE_URL = "http://localhost:8080/"
|
||||
private const val MOCK_BASE_URL = "http://localhost:8080/"
|
||||
private const val PROD_BASE_URL = "http://localhost:8080/"
|
||||
}
|
||||
}
|
||||
|
|
@ -97,6 +97,12 @@ interface TangemPayApi {
|
|||
@Body body: ReissueCardRequest,
|
||||
): ApiResponse<ReissueCardResponse>
|
||||
|
||||
@POST("v1/customer/card/close")
|
||||
suspend fun closeCard(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: CloseCardRequest,
|
||||
): ApiResponse<CloseCardResponse>
|
||||
|
||||
@POST("v1/customer/card/withdraw/data")
|
||||
suspend fun getWithdrawData(
|
||||
@Header("Authorization") authHeader: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CloseCardRequest(
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CloseCardResponse(
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "status") val status: OrderResponse.Result.Status,
|
||||
)
|
||||
}
|
||||
|
|
@ -113,4 +113,10 @@ internal object ApiConfigsModule {
|
|||
fun provideSurveySparrowConfig(environmentConfig: EnvironmentConfig): ApiConfig {
|
||||
return SurveySparrow(environmentConfig)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideAuthConfig(): ApiConfig {
|
||||
return Auth()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.auth.AuthApi
|
||||
import com.tangem.datasource.api.common.blockaid.BlockAidApi
|
||||
import com.tangem.datasource.api.surveysparrow.SurveySparrowApi
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
|
|
@ -208,6 +209,15 @@ internal object NetworkModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthApi(retrofitApiBuilder: RetrofitApiBuilder): AuthApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.Auth,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGaslessTxServiceApi(retrofitApiBuilder: RetrofitApiBuilder): GaslessTxServiceApi {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ package com.tangem.datasource.di
|
|||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayCloseCardStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayReissueCardStore
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.TangemPayCloseCardStore
|
||||
import com.tangem.datasource.local.visa.TangemPayReissueCardStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -32,4 +34,12 @@ internal object TangemPayStoresModule {
|
|||
prefs = prefs,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayCloseCardStore(prefs: AppPreferencesStore): TangemPayCloseCardStore {
|
||||
return DefaultTangemPayCloseCardStore(
|
||||
prefs = prefs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import dagger.Module
|
|||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -26,6 +27,11 @@ internal object ConfigModule {
|
|||
return GeneratedEnvironmentConfigConverter.convert()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("authServiceKey")
|
||||
fun provideAuthServiceKey(environmentConfig: EnvironmentConfig): String? = environmentConfig.authServiceKey
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTestnetTokensStorage(assetLoader: AssetLoader): TestnetTokensStorage {
|
||||
|
|
|
|||
|
|
@ -16,11 +16,15 @@ import com.tangem.datasource.api.utils.ConnectTimeout
|
|||
import com.tangem.datasource.api.utils.ReadTimeout
|
||||
import com.tangem.datasource.api.utils.WriteTimeout
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.logs.SensitiveUrlMasker
|
||||
import com.tangem.datasource.utils.NetworkLogsSaveInterceptor
|
||||
import com.tangem.datasource.utils.WireMockRedirectInterceptor
|
||||
import com.tangem.datasource.utils.addHeaders
|
||||
import com.tangem.utils.JsonStringValuesExtractor
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Invocation
|
||||
|
|
@ -41,6 +45,7 @@ import javax.inject.Singleton
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Singleton
|
||||
internal class RetrofitApiBuilder @Inject constructor(
|
||||
private val apiConfigs: ApiConfigs,
|
||||
|
|
@ -49,10 +54,20 @@ internal class RetrofitApiBuilder @Inject constructor(
|
|||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val appLogsStore: AppLogsStore,
|
||||
private val environmentConfig: EnvironmentConfig,
|
||||
) {
|
||||
|
||||
private val configsBaseUrls: Map<ApiConfig.ID, Set<String>> = getConfigsBaseUrls()
|
||||
|
||||
private val sensitiveUrlMasker: SensitiveUrlMasker by lazy {
|
||||
val json = Json.encodeToJsonElement(EnvironmentConfig.serializer(), environmentConfig)
|
||||
// Drop URL-shaped values (e.g. public endpoint URLs from config); they are not secrets
|
||||
// and would obscure unrelated requests in logs.
|
||||
val values = JsonStringValuesExtractor.extract(json)
|
||||
.filter { it.isNotBlank() && !it.startsWith("http", ignoreCase = true) }
|
||||
SensitiveUrlMasker(values)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Retrofit API instance for the specified API configuration ID
|
||||
*
|
||||
|
|
@ -179,7 +194,7 @@ internal class RetrofitApiBuilder @Inject constructor(
|
|||
|
||||
private fun OkHttpClient.Builder.applyLogsSaving(): OkHttpClient.Builder {
|
||||
return addInterceptor(
|
||||
interceptor = NetworkLogsSaveInterceptor(appLogsStore),
|
||||
interceptor = NetworkLogsSaveInterceptor(appLogsStore, sensitiveUrlMasker),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
|
|||
import com.tangem.datasource.local.config.environment.models.ExpressModel
|
||||
import com.tangem.datasource.local.config.environment.models.P2PKeys
|
||||
import com.tangem.datasource.local.config.environment.models.SurveySparrowSwapRatingConfig
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
@Serializable
|
||||
data class EnvironmentConfig(
|
||||
val moonPayApiKey: String = "",
|
||||
val moonPayApiSecretKey: String = "",
|
||||
|
|
@ -32,5 +35,7 @@ data class EnvironmentConfig(
|
|||
val gaslessTxApiKey: String? = null,
|
||||
val customerIoCdpApiKey: String? = null,
|
||||
val surveySparrowToken: String? = null,
|
||||
@Transient
|
||||
val surveySparrowSwapRating: SurveySparrowSwapRatingConfig? = null,
|
||||
val authServiceKey: String? = null,
|
||||
)
|
||||
|
|
@ -56,6 +56,7 @@ internal object GeneratedEnvironmentConfigConverter {
|
|||
customerIoCdpApiKey = GeneratedEnvironmentConfig.CustomerIO.androidApiKey,
|
||||
surveySparrowToken = GeneratedEnvironmentConfig.SurveySparrow.apiKey,
|
||||
surveySparrowSwapRating = createSurveySparrowSwapRating(),
|
||||
authServiceKey = null, // TODO: provide service key [REDACTED_JIRA]
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
package com.tangem.datasource.local.config.environment.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ExpressModel(val apiKey: String, val signVerifierPublicKey: String)
|
||||
|
||||
@Serializable
|
||||
data class P2PKeys(val mainnet: String, val hoodi: String)
|
||||
|
||||
data class SurveySparrowSwapRatingConfig(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.local.logs
|
||||
|
||||
class SensitiveUrlMasker(sensitiveValues: Collection<String>) {
|
||||
|
||||
// Sorted by descending length so a value that is a prefix of another (e.g. "my-node" vs
|
||||
// "my-node-prod") cannot mask the shorter one first and leave the suffix in the log.
|
||||
private val sensitiveValues: List<String> = sensitiveValues
|
||||
.distinct()
|
||||
.sortedByDescending(String::length)
|
||||
|
||||
fun mask(url: String): String {
|
||||
var result = url
|
||||
for (value in sensitiveValues) {
|
||||
if (result.contains(value, ignoreCase = true)) {
|
||||
result = result.replace(value, MASKED_VALUE, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MASKED_VALUE = "******"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.domain.models.pay.TangemPayCardFrozenState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
internal class DefaultTangemPayCardFrozenStateStore(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
|
||||
internal class DefaultTangemPayCloseCardStore(
|
||||
private val prefs: AppPreferencesStore,
|
||||
) : TangemPayCloseCardStore {
|
||||
|
||||
override suspend fun setCloseOrderId(cardId: String, orderId: String?) {
|
||||
if (orderId == null) {
|
||||
prefs.edit { it.remove(getCloseKey(cardId)) }
|
||||
} else {
|
||||
prefs.store(
|
||||
key = getCloseKey(cardId),
|
||||
value = orderId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getOrderId(cardId: String): String? {
|
||||
return prefs.getSyncOrNull(key = getCloseKey(cardId))
|
||||
}
|
||||
|
||||
private fun getCloseKey(cardId: String) = stringPreferencesKey("tangem_pay_close_card_$cardId")
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.domain.models.pay.TangemPayCardFrozenState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TangemPayCardFrozenStateStore {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
interface TangemPayCloseCardStore {
|
||||
|
||||
suspend fun setCloseOrderId(cardId: String, orderId: String?)
|
||||
|
||||
suspend fun getOrderId(cardId: String): String?
|
||||
}
|
||||
|
|
@ -86,8 +86,8 @@ sealed interface PaymentAccountStatusValueDM {
|
|||
@Json(name = "display_name") val displayName: String?,
|
||||
@Json(name = "actual_daily_limit") val actualDailyLimit: SerializedBigDecimal?,
|
||||
@Json(name = "admin_daily_limit") val adminDailyLimit: SerializedBigDecimal?,
|
||||
@Json(name = "is_frozen") val isFrozen: Boolean,
|
||||
@Json(name = "frozen_state") val frozenState: String,
|
||||
@Json(name = "last_digits") val lastDigits: String,
|
||||
@Json(name = "is_reissuing") val isReissuing: Boolean,
|
||||
@Json(name = "state") val state: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.datasource.utils
|
||||
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.logs.SensitiveUrlMasker
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
|
@ -22,11 +24,15 @@ private const val JSON_INDENT_SPACES = 4
|
|||
* Interceptor for save network requests and responses logs
|
||||
*
|
||||
* @property appLogsStore app logs store
|
||||
* @property sensitiveUrlMasker masker for sensitive data in URLs
|
||||
* @property shouldCheckResponseBodySize whether to skip logging large response bodies
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class NetworkLogsSaveInterceptor(
|
||||
private val appLogsStore: AppLogsStore,
|
||||
private val sensitiveUrlMasker: SensitiveUrlMasker? = null,
|
||||
private val shouldCheckResponseBodySize: Boolean = false,
|
||||
) : Interceptor {
|
||||
|
||||
@Throws(IOException::class)
|
||||
|
|
@ -65,7 +71,7 @@ class NetworkLogsSaveInterceptor(
|
|||
val connection = chain.connection()
|
||||
val connectionProtocol = if (connection != null) " ${connection.protocol()}" else ""
|
||||
|
||||
saveLogMessage("--> ${request.method} ${request.url}$connectionProtocol\n")
|
||||
saveLogMessage("--> ${request.method} ${request.url.maskSensitiveInfo()}$connectionProtocol\n")
|
||||
}
|
||||
|
||||
private fun logRequestMessage(chain: Interceptor.Chain, request: Request) {
|
||||
|
|
@ -73,7 +79,7 @@ class NetworkLogsSaveInterceptor(
|
|||
val connectionProtocol = if (connection != null) " ${connection.protocol()}" else ""
|
||||
|
||||
saveLogMessage(
|
||||
"--> ${request.method} ${request.url}$connectionProtocol\n",
|
||||
"--> ${request.method} ${request.url.maskSensitiveInfo()}$connectionProtocol\n",
|
||||
createRequestEndMessage(request),
|
||||
)
|
||||
}
|
||||
|
|
@ -110,7 +116,7 @@ class NetworkLogsSaveInterceptor(
|
|||
val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
|
||||
saveLogMessage(
|
||||
"<-- ${response.code}",
|
||||
" ${response.request.url} (${tookMs}ms)\n",
|
||||
" ${response.request.url.maskSensitiveInfo()} (${tookMs}ms)\n",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -123,39 +129,45 @@ class NetworkLogsSaveInterceptor(
|
|||
"<-- END HTTP"
|
||||
} else if (bodyHasUnknownEncoding(response.headers)) {
|
||||
"<-- END HTTP (encoded body omitted)"
|
||||
} else if (shouldCheckResponseBodySize && contentLength > WRITE_LOG_THRESHOLD_BYTES_SIZE) {
|
||||
"Response size too large: $contentLength bytes \n<-- END HTTP"
|
||||
} else {
|
||||
val source = responseBody.source()
|
||||
source.request(Long.MAX_VALUE)
|
||||
var buffer = source.buffer
|
||||
|
||||
var gzippedLength: Long? = null
|
||||
if ("gzip".equals(responseHeaders["Content-Encoding"], ignoreCase = true)) {
|
||||
gzippedLength = buffer.size
|
||||
GzipSource(buffer.clone()).use { gzippedResponseBody ->
|
||||
buffer = Buffer()
|
||||
buffer.writeAll(gzippedResponseBody)
|
||||
}
|
||||
}
|
||||
|
||||
val contentType = responseBody.contentType()
|
||||
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
|
||||
|
||||
if (!buffer.isProbablyUtf8()) {
|
||||
"<-- END HTTP (binary ${buffer.size}-byte body omitted)"
|
||||
if (shouldCheckResponseBodySize && buffer.size > WRITE_LOG_THRESHOLD_BYTES_SIZE) {
|
||||
"Response size too large: ${buffer.size} bytes \n<-- END HTTP"
|
||||
} else {
|
||||
val json = if (contentLength != 0L) {
|
||||
buffer.clone().readString(charset).beautifyJson()
|
||||
} else {
|
||||
""
|
||||
var gzippedLength: Long? = null
|
||||
if ("gzip".equals(responseHeaders["Content-Encoding"], ignoreCase = true)) {
|
||||
gzippedLength = buffer.size
|
||||
GzipSource(buffer.clone()).use { gzippedResponseBody ->
|
||||
buffer = Buffer()
|
||||
buffer.writeAll(gzippedResponseBody)
|
||||
}
|
||||
}
|
||||
|
||||
val end = if (gzippedLength != null) {
|
||||
"<-- END HTTP (${buffer.size}-byte, $gzippedLength-gzipped-byte body)"
|
||||
} else {
|
||||
"<-- END HTTP (${buffer.size}-byte body)"
|
||||
}
|
||||
val contentType = responseBody.contentType()
|
||||
val charset: Charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
|
||||
|
||||
"$json\n$end"
|
||||
if (!buffer.isProbablyUtf8()) {
|
||||
"<-- END HTTP (binary ${buffer.size}-byte body omitted)"
|
||||
} else {
|
||||
val json = if (contentLength != 0L) {
|
||||
buffer.clone().readString(charset).beautifyJson()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val end = if (gzippedLength != null) {
|
||||
"<-- END HTTP (${buffer.size}-byte, $gzippedLength-gzipped-byte body)"
|
||||
} else {
|
||||
"<-- END HTTP (${buffer.size}-byte body)"
|
||||
}
|
||||
|
||||
"$json\n$end"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,12 +178,16 @@ class NetworkLogsSaveInterceptor(
|
|||
saveLogMessage(
|
||||
"<-- ${response.code}",
|
||||
spaceBeforeResponseMessage,
|
||||
response.message,
|
||||
" ${response.request.url} (${tookMs}ms)\n",
|
||||
" ${response.request.url.maskSensitiveInfo()} (${tookMs}ms)\n",
|
||||
message,
|
||||
)
|
||||
}
|
||||
|
||||
private fun HttpUrl.maskSensitiveInfo(): String {
|
||||
val url = toString()
|
||||
return sensitiveUrlMasker?.mask(url) ?: url
|
||||
}
|
||||
|
||||
private fun bodyHasUnknownEncoding(headers: Headers): Boolean {
|
||||
val contentEncoding = headers["Content-Encoding"] ?: return false
|
||||
return !contentEncoding.equals("identity", ignoreCase = true) &&
|
||||
|
|
@ -231,6 +247,9 @@ class NetworkLogsSaveInterceptor(
|
|||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
const val WRITE_LOG_THRESHOLD_BYTES_SIZE = 2_048_000L
|
||||
|
||||
/**
|
||||
* List of URLs (host + path) for which logging is restricted
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -14,21 +14,39 @@ class WireMockRedirectInterceptor : Interceptor {
|
|||
|
||||
val request = chain.request()
|
||||
val url = request.url.toString()
|
||||
val host = request.url.host
|
||||
val sanitizedOverride = override.trimEnd('/')
|
||||
|
||||
if (url.contains(WIREMOCK_REMOTE_URL)) {
|
||||
val newUrl = url.replace(WIREMOCK_REMOTE_URL, override.trimEnd('/'))
|
||||
if (host == WIREMOCK_REMOTE_HOST) {
|
||||
val newUrl = url.replace(WIREMOCK_REMOTE_URL, sanitizedOverride)
|
||||
TangemLogger.d("WireMockRedirect: $url -> $newUrl")
|
||||
val newRequest = request.newBuilder()
|
||||
.url(newUrl)
|
||||
.build()
|
||||
return chain.proceed(newRequest)
|
||||
return chain.proceed(request.newBuilder().url(newUrl).build())
|
||||
}
|
||||
|
||||
if (host in REDIRECTABLE_THIRD_PARTY_HOSTS) {
|
||||
val newUrl = url.replace("https://$host", "$sanitizedOverride/$host")
|
||||
TangemLogger.d("WireMockRedirect (3p): $url -> $newUrl")
|
||||
return chain.proceed(request.newBuilder().url(newUrl).build())
|
||||
}
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val WIREMOCK_REMOTE_URL = "[REDACTED_ENV_URL]"
|
||||
private const val WIREMOCK_REMOTE_HOST = "wiremock.tests-d.com"
|
||||
private const val WIREMOCK_REMOTE_URL = "https://$WIREMOCK_REMOTE_HOST"
|
||||
|
||||
/**
|
||||
* Upstream hosts that have no other override knob and should be funnelled into WireMock
|
||||
* when [overriddenBaseUrl] is set. Each matched URL becomes `<override>/<host>/<original-path>`,
|
||||
* so mock mappings should live under that host-prefixed path in tangem-api-mocks. Matching
|
||||
* is done against the request's parsed host (exact equality) — substring matching would
|
||||
* incorrectly redirect look-alikes such as `deep-index.moralis.io.evil.example`.
|
||||
*/
|
||||
private val REDIRECTABLE_THIRD_PARTY_HOSTS = setOf(
|
||||
"deep-index.moralis.io",
|
||||
"solana-gateway.moralis.io",
|
||||
)
|
||||
|
||||
/**
|
||||
* Override base URL for WireMock requests.
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ class ApiConfigTest {
|
|||
appInfoProvider = mockk(),
|
||||
)
|
||||
ApiConfig.ID.SurveySparrow -> SurveySparrow(environmentConfig = environmentConfig)
|
||||
ApiConfig.ID.Auth -> Auth()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ internal class ProdApiConfigsManagerTest {
|
|||
appInfoProvider = appInfoProvider,
|
||||
)
|
||||
ApiConfig.ID.SurveySparrow -> SurveySparrow(environmentConfig = environmentConfig)
|
||||
ApiConfig.ID.Auth -> Auth()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -148,9 +149,32 @@ internal class ProdApiConfigsManagerTest {
|
|||
ApiConfig.ID.News -> createNewsModel()
|
||||
ApiConfig.ID.GaslessTxService -> createGaslessTxServiceModel()
|
||||
ApiConfig.ID.SurveySparrow -> createSurveySparrowModel()
|
||||
ApiConfig.ID.Auth -> createAuthModel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAuthModel(): TestModel {
|
||||
val environment = when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
|
||||
DEBUG_BUILD_TYPE,
|
||||
INTERNAL_BUILD_TYPE,
|
||||
-> ApiEnvironment.DEV
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
|
||||
return TestModel(
|
||||
id = ApiConfig.ID.Auth,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = environment,
|
||||
baseUrl = "http://localhost:8080/",
|
||||
headers = emptyMap(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createExpressModel(): TestModel {
|
||||
val environment = when (BuildConfig.BUILD_TYPE) {
|
||||
DEBUG_BUILD_TYPE,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.datasource.local.logs
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.datasource.local.logs.SensitiveUrlMasker.Companion.MASKED_VALUE
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class SensitiveUrlMaskerTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun mask(model: TestModel) {
|
||||
// Arrange
|
||||
val masker = SensitiveUrlMasker(model.sensitiveValues)
|
||||
|
||||
// Act
|
||||
val actual = masker.mask(model.input)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mask returns url unchanged when no sensitive values provided`() {
|
||||
// Arrange
|
||||
val masker = SensitiveUrlMasker(emptyList())
|
||||
val url = "https://api.tangem.com/v1/cards/abc123"
|
||||
|
||||
// Act
|
||||
val actual = masker.mask(url)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo(url)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `constructor deduplicates input values`() {
|
||||
// Arrange — same secret repeated; if no dedup, replace would be invoked twice
|
||||
// (idempotent on already-masked string, but we assert behavior is identical
|
||||
// to a single-value masker as a smoke-check)
|
||||
val withDuplicates = SensitiveUrlMasker(listOf("secret123", "secret123", "secret123"))
|
||||
val withSingle = SensitiveUrlMasker(listOf("secret123"))
|
||||
val url = "https://api.tangem.com/?key=secret123"
|
||||
|
||||
// Act
|
||||
val withDup = withDuplicates.mask(url)
|
||||
val withSingleResult = withSingle.mask(url)
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(withDup).isEqualTo(withSingleResult)
|
||||
Truth.assertThat(withDup).isEqualTo("https://api.tangem.com/?key=$MASKED_VALUE")
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
TestModel(
|
||||
input = "https://api.tangem.com/?key=secret123",
|
||||
sensitiveValues = listOf("secret123"),
|
||||
expected = "https://api.tangem.com/?key=$MASKED_VALUE",
|
||||
),
|
||||
TestModel(
|
||||
input = "https://api.tangem.com/?a=alpha&b=beta",
|
||||
sensitiveValues = listOf("alpha", "beta"),
|
||||
expected = "https://api.tangem.com/?a=$MASKED_VALUE&b=$MASKED_VALUE",
|
||||
),
|
||||
TestModel(
|
||||
input = "https://api.tangem.com/?key=SECRET123",
|
||||
sensitiveValues = listOf("secret123"),
|
||||
expected = "https://api.tangem.com/?key=$MASKED_VALUE",
|
||||
),
|
||||
TestModel(
|
||||
input = "https://api.tangem.com/v1/balance",
|
||||
sensitiveValues = listOf("notInUrl"),
|
||||
expected = "https://api.tangem.com/v1/balance",
|
||||
),
|
||||
TestModel(
|
||||
input = "https://api.tangem.com/?key=secret123&other=secret123",
|
||||
sensitiveValues = listOf("secret123"),
|
||||
expected = "https://api.tangem.com/?key=$MASKED_VALUE&other=$MASKED_VALUE",
|
||||
),
|
||||
TestModel(
|
||||
input = "https://api.tangem.com/v1/cards",
|
||||
sensitiveValues = emptyList(),
|
||||
expected = "https://api.tangem.com/v1/cards",
|
||||
),
|
||||
// Regression: when one value is a prefix of another, the longer one must be masked first
|
||||
// regardless of input order, otherwise the suffix leaks (e.g. "my-node-prod" -> "******-prod").
|
||||
TestModel(
|
||||
input = "https://my-node-prod.example.com/v1",
|
||||
sensitiveValues = listOf("my-node", "my-node-prod"),
|
||||
expected = "https://$MASKED_VALUE.example.com/v1",
|
||||
),
|
||||
TestModel(
|
||||
input = "https://my-node-prod.example.com/v1",
|
||||
sensitiveValues = listOf("my-node-prod", "my-node"),
|
||||
expected = "https://$MASKED_VALUE.example.com/v1",
|
||||
),
|
||||
)
|
||||
|
||||
data class TestModel(
|
||||
val input: String,
|
||||
val sensitiveValues: List<String>,
|
||||
val expected: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 06d801c92ac499d787093c30783e9ccb1f7e43dc
|
||||
Subproject commit 0be9b5e9f7fe13c483f9d037617e36c8848f2842
|
||||
|
|
@ -93,6 +93,39 @@ fun Modifier.topFade(
|
|||
solidStop = solidStop,
|
||||
)
|
||||
|
||||
/**
|
||||
* Draws a vertical gradient fade over the top edge with custom [colorStops].
|
||||
*
|
||||
* Stops are defined relative to [height] (0f = top, 1f = bottom of the fade region).
|
||||
*
|
||||
* Note: the gradient is drawn over the entire content area, so the last color stop's color
|
||||
* extends below the fade region down to the bottom. Pass [Color.Transparent] as the last stop
|
||||
* (or a color matching the underlying content) to avoid covering the area outside the fade.
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.topFade(height: Dp, vararg colorStops: Pair<Float, Color>): Modifier = composed {
|
||||
drawWithContent {
|
||||
drawContent()
|
||||
|
||||
if (this.size.height <= 0f) return@drawWithContent
|
||||
val fraction = (height.toPx() / this.size.height).coerceIn(0f, 1f)
|
||||
if (fraction <= 0f) return@drawWithContent
|
||||
|
||||
val (start, end) = this.size.getFadeOffsets(FadePosition.TOP)
|
||||
|
||||
drawRect(
|
||||
brush = Brush.linearGradient(
|
||||
colorStops = colorStops
|
||||
.map { (stop, color) -> stop.coerceIn(0f, 1f) * fraction to color }
|
||||
.toTypedArray(),
|
||||
start = start,
|
||||
end = end,
|
||||
),
|
||||
size = this.size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class FadePosition {
|
||||
TOP, BOTTOM, LEFT, RIGHT
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -28,9 +29,10 @@ import androidx.compose.ui.unit.sp
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
enum class AccountIconSize {
|
||||
Default, Large, Medium, Small, ExtraSmall, RedesignedDefault
|
||||
Default, Large, Medium, Small, ExtraSmall, RedesignedDefault, RedesignExtraSmall
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -129,6 +131,7 @@ fun AccountCharIcon(char: Char, color: Color, size: AccountIconSize, modifier: M
|
|||
AccountIconSize.Small -> TangemTheme.typography.subtitle2
|
||||
AccountIconSize.ExtraSmall -> TangemTheme.typography.caption1
|
||||
AccountIconSize.RedesignedDefault -> TangemTheme.typography2.headingSemibold28
|
||||
AccountIconSize.RedesignExtraSmall -> TangemTheme.typography2.captionMedium11
|
||||
}
|
||||
|
||||
val textSize by animateFloatAsState(
|
||||
|
|
@ -148,6 +151,7 @@ fun AccountCharIcon(char: Char, color: Color, size: AccountIconSize, modifier: M
|
|||
text = char.uppercase(),
|
||||
style = textStyle.copy(fontSize = textSize.sp),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -161,6 +165,7 @@ private fun AccountIconSize.iconSizeInDp(): Dp = when (this) {
|
|||
AccountIconSize.Small -> 12.dp
|
||||
AccountIconSize.ExtraSmall -> 8.dp
|
||||
AccountIconSize.RedesignedDefault -> 20.dp
|
||||
AccountIconSize.RedesignExtraSmall -> 8.dp
|
||||
}
|
||||
|
||||
fun AccountIconSize.toBoxSize(): Dp = when (this) {
|
||||
|
|
@ -170,6 +175,7 @@ fun AccountIconSize.toBoxSize(): Dp = when (this) {
|
|||
AccountIconSize.Small -> 20.dp
|
||||
AccountIconSize.ExtraSmall -> 14.dp
|
||||
AccountIconSize.RedesignedDefault -> 40.dp
|
||||
AccountIconSize.RedesignExtraSmall -> 16.dp
|
||||
}
|
||||
|
||||
private fun AccountIconSize.boxShapeSizeInDp(): Dp = when (this) {
|
||||
|
|
@ -179,6 +185,7 @@ private fun AccountIconSize.boxShapeSizeInDp(): Dp = when (this) {
|
|||
AccountIconSize.Small -> 6.dp
|
||||
AccountIconSize.ExtraSmall -> 4.dp
|
||||
AccountIconSize.RedesignedDefault -> 12.dp
|
||||
AccountIconSize.RedesignExtraSmall -> 6.dp
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
|
@ -194,6 +201,19 @@ private fun Preview() {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewRedesigned() {
|
||||
TangemThemePreviewRedesign {
|
||||
Row(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
Sample()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Sample() {
|
||||
var sizeState by remember { mutableStateOf(AccountIconSize.ExtraSmall) }
|
||||
|
|
@ -206,8 +226,9 @@ private fun Sample() {
|
|||
AccountIconSize.Large -> AccountIconSize.Medium
|
||||
AccountIconSize.Medium -> AccountIconSize.Small
|
||||
AccountIconSize.Small -> AccountIconSize.ExtraSmall
|
||||
AccountIconSize.ExtraSmall -> AccountIconSize.Default
|
||||
AccountIconSize.RedesignedDefault -> AccountIconSize.Large
|
||||
AccountIconSize.ExtraSmall -> AccountIconSize.RedesignedDefault
|
||||
AccountIconSize.RedesignedDefault -> AccountIconSize.RedesignExtraSmall
|
||||
AccountIconSize.RedesignExtraSmall -> AccountIconSize.Default
|
||||
}
|
||||
}) { Text("Change") }
|
||||
|
||||
|
|
@ -225,5 +246,6 @@ private fun Sample() {
|
|||
AccountCharIcon(char = 'M', color = Color.Magenta, size = AccountIconSize.Medium)
|
||||
AccountCharIcon(char = 'S', color = Color.DarkGray, size = AccountIconSize.Small)
|
||||
AccountCharIcon(char = 'E', color = Color.Green, size = AccountIconSize.ExtraSmall)
|
||||
AccountCharIcon(char = 'D', color = Color.LightGray, size = AccountIconSize.RedesignExtraSmall)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -39,6 +40,7 @@ import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible
|
|||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.test.BaseBottomSheetTestTags
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
|
||||
/**
|
||||
|
|
@ -253,7 +255,8 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
|||
Column(
|
||||
modifier = contentModifier
|
||||
.background(containerColor)
|
||||
.heightIn(max = maxHeight),
|
||||
.heightIn(max = maxHeight)
|
||||
.testTag(BaseBottomSheetTestTags.CONTAINER),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
title(model)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -40,6 +41,7 @@ import com.tangem.core.ui.components.sheetscaffold.TangemSheetState
|
|||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue
|
||||
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.core.ui.test.BaseBottomSheetTestTags
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
|
||||
const val MODAL_SHEET_MAX_HEIGHT = 0.8f
|
||||
|
|
@ -205,7 +207,8 @@ inline fun <reified T : TangemBottomSheetConfigContent> BsContent(
|
|||
.background(containerColor)
|
||||
.heightIn(max = maxHeight.dp)
|
||||
.fillMaxWidth()
|
||||
.nestedScroll(nestedScrollConnection),
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
.testTag(BaseBottomSheetTestTags.CONTAINER),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
title(model)
|
||||
|
|
|
|||
|
|
@ -20,4 +20,14 @@ fun DividerWithPadding(start: Dp = 0.dp, end: Dp = 0.dp, top: Dp = 0.dp, bottom:
|
|||
thickness = TangemTheme.dimens.size1,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DividerWithPadding(horizontal: Dp = 0.dp, vertical: Dp = 0.dp) {
|
||||
DividerWithPadding(
|
||||
start = horizontal,
|
||||
end = horizontal,
|
||||
top = vertical,
|
||||
bottom = vertical,
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,9 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.disabled
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
|
@ -25,6 +28,7 @@ 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.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.test.BaseActionButtonsBlockTestTags
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -51,7 +55,10 @@ fun ActionButtons(buttons: ImmutableList<TangemButtonUM>, modifier: Modifier = M
|
|||
TangemTheme.colors2.text.status.disabled
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x2_5),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens2.x2_5)
|
||||
.testTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
.semantics { if (!button.isEnabled) disabled() },
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
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.platform.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.test.NotificationTestTags
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
|
||||
import com.tangem.core.ui.ds.button.*
|
||||
|
|
@ -66,7 +68,8 @@ fun TangemMessage(
|
|||
Alignment.Top
|
||||
},
|
||||
)
|
||||
.size(messageUM.iconSize),
|
||||
.size(messageUM.iconSize)
|
||||
.testTag(NotificationTestTags.ICON),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -115,14 +118,14 @@ fun TangemMessage(
|
|||
Image(
|
||||
painter = painterResource(config.iconResId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(config.iconSize),
|
||||
modifier = Modifier.size(config.iconSize).testTag(NotificationTestTags.ICON),
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(config.iconResId),
|
||||
contentDescription = null,
|
||||
tint = iconTint,
|
||||
modifier = Modifier.size(config.iconSize),
|
||||
modifier = Modifier.size(config.iconSize).testTag(NotificationTestTags.ICON),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
@ -166,7 +169,7 @@ fun TangemMessage(
|
|||
} else {
|
||||
Alignment.Start
|
||||
}
|
||||
Box(modifier = modifier) {
|
||||
Box(modifier = modifier.testTag(NotificationTestTags.CONTAINER)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
|
|
@ -244,6 +247,7 @@ private fun TangemMessageContent(
|
|||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
textAlign = textAlign,
|
||||
modifier = Modifier.testTag(NotificationTestTags.TITLE),
|
||||
)
|
||||
}
|
||||
if (subtitle != null) {
|
||||
|
|
@ -252,6 +256,7 @@ private fun TangemMessageContent(
|
|||
text = subtitle.resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography2.captionSemibold12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
modifier = Modifier.testTag(NotificationTestTags.MESSAGE),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.core.ui.ds2.button
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_arrow_left_20
|
||||
import com.tangem.core.ui.res.generated.icons.ic_cross_20
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun TangemButton.Back(modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
variant = TangemButton.Variant.Material,
|
||||
iconStart = TangemIconUM.Icon(Icons.ic_arrow_left_20),
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun TangemButton.Close(modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||
TangemButton(
|
||||
modifier = modifier,
|
||||
variant = TangemButton.Variant.Material,
|
||||
iconStart = TangemIconUM.Icon(Icons.ic_cross_20),
|
||||
onClick = onClick,
|
||||
)
|
||||
}
|
||||
166
core/ui/src/main/java/com/tangem/core/ui/ds2/fade/TangemFade.kt
Normal file
166
core/ui/src/main/java/com/tangem/core/ui/ds2/fade/TangemFade.kt
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
@file:Suppress("MagicNumber")
|
||||
|
||||
package com.tangem.core.ui.ds2.fade
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.res.LocalHazeState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import dev.chrisbanes.haze.HazeTint
|
||||
|
||||
private const val SOLID_RATIO = 40f / 96f
|
||||
private const val HARD_ALPHA = 0.95f
|
||||
private const val SOFT_ALPHA = 0.6f
|
||||
private val FADE_HEIGHT = 96.dp
|
||||
private val BLUR_RADIUS = 20.dp
|
||||
|
||||
/**
|
||||
* Design-system fade overlay used at the top or bottom edge of scrollable content.
|
||||
*
|
||||
* Renders a 96dp-tall gradient (and optional solid block in the [TangemFade.Variant.Hard]
|
||||
* variant) tinted with `colors3.bg.primary`, so it blends with the page background and masks
|
||||
* content as it scrolls under the edge. Place it inside a [Box] and align with
|
||||
* [androidx.compose.ui.Alignment.TopCenter] / [androidx.compose.ui.Alignment.BottomCenter]
|
||||
* depending on [position].
|
||||
*
|
||||
* @param position which edge the fade is anchored to — controls the gradient direction.
|
||||
* @param variant [TangemFade.Variant.Hard] adds an opaque solid block next to the edge for a
|
||||
* harder cut-off, [TangemFade.Variant.Soft] is gradient-only and gentler.
|
||||
* @param blur when `true`, the content under the fade is blurred via Haze (radius 20dp,
|
||||
* progressive intensity matching [position]).
|
||||
* @param backgroundColor base color of the fade gradient — defaults to `colors3.bg.primary` so
|
||||
* the fade blends with the standard page background.
|
||||
* @param modifier modifier applied to the fade's root.
|
||||
*/
|
||||
@Composable
|
||||
fun TangemFade(
|
||||
position: TangemFade.Position,
|
||||
modifier: Modifier = Modifier,
|
||||
variant: TangemFade.Variant = TangemFade.Variant.Soft,
|
||||
blur: Boolean = false,
|
||||
backgroundColor: Color = TangemTheme.colors3.bg.primary,
|
||||
) {
|
||||
val hazeState = LocalHazeState.current
|
||||
val brush = buildBrush(color = backgroundColor, position = position, variant = variant)
|
||||
|
||||
val blurModifier = if (blur) {
|
||||
Modifier.hazeEffectTangem(state = hazeState) {
|
||||
blurRadius = BLUR_RADIUS
|
||||
fallbackTint = HazeTint(Color.Transparent)
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = if (position == TangemFade.Position.Top) 1f else 0f,
|
||||
endIntensity = if (position == TangemFade.Position.Top) 0f else 1f,
|
||||
preferPerformance = true,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(FADE_HEIGHT)
|
||||
.then(blurModifier)
|
||||
.background(brush),
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildBrush(color: Color, position: TangemFade.Position, variant: TangemFade.Variant): Brush {
|
||||
val opaque = color.copy(alpha = HARD_ALPHA)
|
||||
val soft = color.copy(alpha = SOFT_ALPHA)
|
||||
val transparent = Color.Transparent
|
||||
|
||||
return when (variant) {
|
||||
TangemFade.Variant.Hard -> when (position) {
|
||||
TangemFade.Position.Top -> Brush.verticalGradient(
|
||||
colorStops = arrayOf(
|
||||
0f to opaque,
|
||||
SOLID_RATIO to opaque,
|
||||
1f to transparent,
|
||||
),
|
||||
)
|
||||
TangemFade.Position.Bottom -> Brush.verticalGradient(
|
||||
colorStops = arrayOf(
|
||||
0f to transparent,
|
||||
1f - SOLID_RATIO to opaque,
|
||||
1f to opaque,
|
||||
),
|
||||
)
|
||||
}
|
||||
TangemFade.Variant.Soft -> when (position) {
|
||||
TangemFade.Position.Top -> Brush.verticalGradient(colors = listOf(soft, transparent))
|
||||
TangemFade.Position.Bottom -> Brush.verticalGradient(colors = listOf(transparent, soft))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TangemFade {
|
||||
enum class Position { Top, Bottom }
|
||||
enum class Variant { Hard, Soft }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TangemFade_Preview() {
|
||||
TangemThemePreviewRedesign {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors3.bg.secondary)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
TangemFade.Variant.entries.forEach { variant ->
|
||||
TangemFade.Position.entries.forEach { position ->
|
||||
FadePreviewRow(variant = variant, position = position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FadePreviewRow(variant: TangemFade.Variant, position: TangemFade.Position) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(
|
||||
text = "${variant.name} / ${position.name}",
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(120.dp)
|
||||
.background(TangemTheme.colors3.bg.accent.blue),
|
||||
) {
|
||||
TangemFade(
|
||||
position = position,
|
||||
variant = variant,
|
||||
modifier = Modifier.align(
|
||||
when (position) {
|
||||
TangemFade.Position.Top -> Alignment.TopCenter
|
||||
TangemFade.Position.Bottom -> Alignment.BottomCenter
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
498
core/ui/src/main/java/com/tangem/core/ui/ds2/row/TangemRow.kt
Normal file
498
core/ui/src/main/java/com/tangem/core/ui/ds2/row/TangemRow.kt
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
package com.tangem.core.ui.ds2.row
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ripple.RippleAlpha
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalRippleConfiguration
|
||||
import androidx.compose.material3.RippleConfiguration
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
/** Controls how the title and value sides share horizontal space in a [TangemRow]. */
|
||||
@Immutable
|
||||
enum class TangemRowContentLead { Equal, Start, End }
|
||||
|
||||
/** Vertical alignment between the start slot, content labels, and end slot in a [TangemRow]. */
|
||||
@Immutable
|
||||
enum class TangemRowVerticalAlignment { Top, Center }
|
||||
|
||||
/**
|
||||
* Design-system v2 row: a list-item container with start / end slots, title+subtitle on the
|
||||
* leading side, value+subvalue on the trailing side, and an optional slot below the row.
|
||||
*
|
||||
* [Figma](https://www.figma.com/design/AsnJ5CPHib4Qxw12gszjMS/%F0%9F%92%A0-DS-Components?node-id=2344-1406)
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* TangemRow(
|
||||
* titleSlot = { TangemRowText("Network fee", TangemRowTextRole.Title) },
|
||||
* valueSlot = { TangemRowText("0.0001 ETH", TangemRowTextRole.Value) },
|
||||
* contentLead = TangemRowContentLead.Start,
|
||||
* onClick = { /* ... */ },
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @param modifier Modifier applied to the row container.
|
||||
* @param divider Draws an inset bottom divider.
|
||||
* @param includeInnerPaddings Applies the default row padding. Disable when wrapped in a container
|
||||
* that already handles padding.
|
||||
* @param contentLead Strategy for sharing space between the title and value sides.
|
||||
* See [TangemRowContentLead].
|
||||
* @param verticalAlignment Vertical alignment of the slots. See [TangemRowVerticalAlignment].
|
||||
* @param titleSlot Leading primary label.
|
||||
* @param subtitleSlot Leading secondary label rendered under [titleSlot].
|
||||
* @param valueSlot Trailing primary label.
|
||||
* @param subvalueSlot Trailing secondary label rendered under [valueSlot].
|
||||
* @param startSlot Leading icon / control slot.
|
||||
* @param endSlot Trailing icon / control slot.
|
||||
* @param extraBottomSlot Full-width content rendered below the row.
|
||||
* @param interactionSource Interaction source used when [onClick] is non-null.
|
||||
* @param onClick Click handler. `null` makes the row non-interactive.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun TangemRow(
|
||||
modifier: Modifier = Modifier,
|
||||
divider: Boolean = false,
|
||||
includeInnerPaddings: Boolean = true,
|
||||
contentLead: TangemRowContentLead = TangemRowContentLead.Equal,
|
||||
verticalAlignment: TangemRowVerticalAlignment = TangemRowVerticalAlignment.Top,
|
||||
titleSlot: (@Composable RowScope.() -> Unit)? = null,
|
||||
subtitleSlot: (@Composable RowScope.() -> Unit)? = null,
|
||||
valueSlot: (@Composable RowScope.() -> Unit)? = null,
|
||||
subvalueSlot: (@Composable RowScope.() -> Unit)? = null,
|
||||
startSlot: (@Composable BoxScope.() -> Unit)? = null,
|
||||
endSlot: (@Composable BoxScope.() -> Unit)? = null,
|
||||
extraBottomSlot: (@Composable () -> Unit)? = null,
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val resolvedInteractionSource: MutableInteractionSource? = if (onClick != null) {
|
||||
interactionSource ?: remember { MutableInteractionSource() }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val isFocused = if (resolvedInteractionSource != null) {
|
||||
val isFocusedByState by resolvedInteractionSource.collectIsFocusedAsState()
|
||||
isFocusedByState
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
WithRowRipple(enabled = onClick != null) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.conditionalCompose(isFocused) { focusBorder() }
|
||||
.then(
|
||||
if (onClick != null) {
|
||||
Modifier.clickable(
|
||||
interactionSource = resolvedInteractionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = onClick,
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.conditionalCompose(divider) {
|
||||
bottomDivider(
|
||||
color = TangemTheme.colors3.border.secondary,
|
||||
width = TangemTheme.dimens3.borderWidth.sm,
|
||||
horizontalInset = TangemTheme.dimens3.spacing.s200,
|
||||
)
|
||||
}
|
||||
.conditionalCompose(includeInnerPaddings) {
|
||||
padding(TangemTheme.dimens3.spacing.s200)
|
||||
},
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = verticalAlignment.toCompose(),
|
||||
) {
|
||||
SideSlot(slot = startSlot, position = SideSlotPosition.Start)
|
||||
ContentLabels(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentLead = contentLead,
|
||||
verticalAlignment = verticalAlignment,
|
||||
titleSlot = titleSlot,
|
||||
subtitleSlot = subtitleSlot,
|
||||
valueSlot = valueSlot,
|
||||
subvalueSlot = subvalueSlot,
|
||||
)
|
||||
SideSlot(slot = endSlot, position = SideSlotPosition.End)
|
||||
}
|
||||
if (extraBottomSlot != null) {
|
||||
SpacerH(TangemTheme.dimens3.spacing.s100)
|
||||
extraBottomSlot()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WithRowRipple(enabled: Boolean, content: @Composable () -> Unit) {
|
||||
if (enabled) {
|
||||
CompositionLocalProvider(LocalRippleConfiguration provides tangemRowRipple(), content = content)
|
||||
} else {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun tangemRowRipple(): RippleConfiguration = RippleConfiguration(
|
||||
color = TangemTheme.colors3.interaction.press.default,
|
||||
rippleAlpha = RippleAlpha(
|
||||
draggedAlpha = 0f,
|
||||
focusedAlpha = 0f,
|
||||
hoveredAlpha = 0.05f,
|
||||
pressedAlpha = 0.1f,
|
||||
),
|
||||
)
|
||||
|
||||
private fun TangemRowVerticalAlignment.toCompose(): Alignment.Vertical = when (this) {
|
||||
TangemRowVerticalAlignment.Top -> Alignment.Top
|
||||
TangemRowVerticalAlignment.Center -> Alignment.CenterVertically
|
||||
}
|
||||
|
||||
private enum class SideSlotPosition { Start, End }
|
||||
|
||||
@Composable
|
||||
private fun SideSlot(slot: (@Composable BoxScope.() -> Unit)?, position: SideSlotPosition) {
|
||||
if (slot == null) return
|
||||
val spacing = TangemTheme.dimens3.spacing.s150
|
||||
val padding = when (position) {
|
||||
SideSlotPosition.Start -> Modifier.padding(end = spacing)
|
||||
SideSlotPosition.End -> Modifier.padding(start = spacing)
|
||||
}
|
||||
Box(modifier = padding, content = slot)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays out the title/value columns side-by-side with [contentLead]-aware width sharing.
|
||||
*/
|
||||
@Suppress("UnnecessaryParentheses", "LongParameterList")
|
||||
@Composable
|
||||
private fun ContentLabels(
|
||||
contentLead: TangemRowContentLead,
|
||||
verticalAlignment: TangemRowVerticalAlignment,
|
||||
titleSlot: (@Composable RowScope.() -> Unit)?,
|
||||
subtitleSlot: (@Composable RowScope.() -> Unit)?,
|
||||
valueSlot: (@Composable RowScope.() -> Unit)?,
|
||||
subvalueSlot: (@Composable RowScope.() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hasLeft = titleSlot != null || subtitleSlot != null
|
||||
val hasRight = valueSlot != null || subvalueSlot != null
|
||||
if (!hasLeft && !hasRight) return
|
||||
|
||||
Layout(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
// Always emit two roots so `measurables` indices are stable in the measure block.
|
||||
LabelColumnContent(primary = titleSlot, secondary = subtitleSlot, alignment = Alignment.Start)
|
||||
LabelColumnContent(primary = valueSlot, secondary = subvalueSlot, alignment = Alignment.End)
|
||||
},
|
||||
) { measurables, constraints ->
|
||||
val titleMeasurable = measurables[0]
|
||||
val valueMeasurable = measurables[1]
|
||||
// Fall back to summed intrinsics when parent provides unbounded width — `layout()` cannot
|
||||
// report an infinite size.
|
||||
val rowWidth = if (constraints.hasBoundedWidth) {
|
||||
constraints.maxWidth
|
||||
} else {
|
||||
titleMeasurable.maxIntrinsicWidth(Int.MAX_VALUE) + valueMeasurable.maxIntrinsicWidth(Int.MAX_VALUE)
|
||||
}
|
||||
|
||||
val (titleSlotWidth, valueSlotWidth) = when {
|
||||
!hasLeft -> 0 to rowWidth
|
||||
!hasRight -> rowWidth to 0
|
||||
else -> when (contentLead) {
|
||||
TangemRowContentLead.Equal -> (rowWidth / 2) to (rowWidth - rowWidth / 2)
|
||||
TangemRowContentLead.Start -> {
|
||||
// Value hugs (intrinsic), title fills the rest. Cap the hugging side at half
|
||||
// the row so the filling label never collapses to zero on overflow — degrades
|
||||
// to an equal split when the hugging side wants more than half.
|
||||
val v = valueMeasurable.maxIntrinsicWidth(Int.MAX_VALUE).coerceAtMost(rowWidth / 2)
|
||||
(rowWidth - v) to v
|
||||
}
|
||||
TangemRowContentLead.End -> {
|
||||
// Title hugs (intrinsic), value fills the rest. Same half-row cap as above.
|
||||
val t = titleMeasurable.maxIntrinsicWidth(Int.MAX_VALUE).coerceAtMost(rowWidth / 2)
|
||||
t to (rowWidth - t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val titlePlaceable = titleMeasurable.measure(
|
||||
constraints.copy(minWidth = 0, maxWidth = titleSlotWidth),
|
||||
)
|
||||
val valuePlaceable = valueMeasurable.measure(
|
||||
constraints.copy(minWidth = 0, maxWidth = valueSlotWidth),
|
||||
)
|
||||
|
||||
val rowHeight = maxOf(titlePlaceable.height, valuePlaceable.height)
|
||||
val yFor: (Int) -> Int = when (verticalAlignment) {
|
||||
TangemRowVerticalAlignment.Top -> { _ -> 0 }
|
||||
TangemRowVerticalAlignment.Center -> { h -> (rowHeight - h) / 2 }
|
||||
}
|
||||
|
||||
layout(rowWidth, rowHeight) {
|
||||
titlePlaceable.placeRelative(x = 0, y = yFor(titlePlaceable.height))
|
||||
// Filling side is pinned to the row's trailing edge.
|
||||
valuePlaceable.placeRelative(
|
||||
x = rowWidth - valuePlaceable.width,
|
||||
y = yFor(valuePlaceable.height),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LabelColumnContent(
|
||||
primary: (@Composable RowScope.() -> Unit)?,
|
||||
secondary: (@Composable RowScope.() -> Unit)?,
|
||||
alignment: Alignment.Horizontal,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens3.spacing.s025),
|
||||
horizontalAlignment = alignment,
|
||||
) {
|
||||
if (primary != null) LabelRow(alignment = alignment, content = primary)
|
||||
if (secondary != null) LabelRow(alignment = alignment, content = secondary)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LabelRow(alignment: Alignment.Horizontal, content: @Composable RowScope.() -> Unit) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = TangemTheme.dimens3.spacing.s050,
|
||||
alignment = alignment,
|
||||
),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/** Semantic role of a label inside a [TangemRow], driving its typography, color and alignment. */
|
||||
@Immutable
|
||||
enum class TangemRowTextRole { Title, Subtitle, Value, Subvalue }
|
||||
|
||||
/**
|
||||
* Default text styling for [TangemRow] label slots.
|
||||
*
|
||||
* Usage:
|
||||
* ```
|
||||
* TangemRowText(text = "Network fee", role = TangemRowTextRole.Title)
|
||||
* ```
|
||||
*
|
||||
* @param text Label text.
|
||||
* @param role Semantic role. See [TangemRowTextRole].
|
||||
* @param modifier Modifier applied to the underlying [Text].
|
||||
* @param maxLines Maximum number of visible lines before truncation.
|
||||
* @param overflow Overflow behavior. Defaults to ellipsis.
|
||||
*/
|
||||
@Composable
|
||||
fun TangemRowText(
|
||||
text: String,
|
||||
role: TangemRowTextRole,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = 1,
|
||||
overflow: TextOverflow = TextOverflow.Ellipsis,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
color = rowTextColor(role),
|
||||
style = rowTextStyle(role),
|
||||
textAlign = rowTextAlign(role),
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default text styling for [TangemRow] label slots.
|
||||
*
|
||||
* @param text Label text.
|
||||
* @param role Semantic role. See [TangemRowTextRole].
|
||||
* @param modifier Modifier applied to the underlying [Text].
|
||||
* @param maxLines Maximum number of visible lines before truncation.
|
||||
* @param overflow Overflow behavior. Defaults to ellipsis.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun TangemRowText(
|
||||
text: TextReference,
|
||||
role: TangemRowTextRole,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = 1,
|
||||
overflow: TextOverflow = TextOverflow.Ellipsis,
|
||||
) {
|
||||
TangemRowText(
|
||||
text = text.resolveReference(),
|
||||
role = role,
|
||||
modifier = modifier,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rowTextStyle(role: TangemRowTextRole): TextStyle = when (role) {
|
||||
TangemRowTextRole.Title, TangemRowTextRole.Value -> TangemTheme.typography3.body.medium
|
||||
TangemRowTextRole.Subtitle, TangemRowTextRole.Subvalue -> TangemTheme.typography3.caption.medium
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rowTextColor(role: TangemRowTextRole): Color = when (role) {
|
||||
TangemRowTextRole.Title, TangemRowTextRole.Value -> TangemTheme.colors3.text.primary
|
||||
TangemRowTextRole.Subtitle, TangemRowTextRole.Subvalue -> TangemTheme.colors3.text.secondary
|
||||
}
|
||||
|
||||
private fun rowTextAlign(role: TangemRowTextRole): TextAlign = when (role) {
|
||||
TangemRowTextRole.Title, TangemRowTextRole.Subtitle -> TextAlign.Start
|
||||
TangemRowTextRole.Value, TangemRowTextRole.Subvalue -> TextAlign.End
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Modifier.focusBorder(): Modifier {
|
||||
val radius = TangemTheme.dimens3.borderRadius.b200
|
||||
val shape = remember(radius) { RoundedCornerShape(radius) }
|
||||
return border(
|
||||
width = TangemTheme.dimens3.borderWidth.md,
|
||||
color = TangemTheme.colors3.interaction.focusRing.default,
|
||||
shape = shape,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Modifier.bottomDivider(color: Color, width: Dp, horizontalInset: Dp): Modifier = drawWithContent {
|
||||
drawContent()
|
||||
val stroke = width.toPx()
|
||||
val inset = horizontalInset.toPx()
|
||||
val y = size.height - stroke / 2f
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(x = inset, y = y),
|
||||
end = Offset(x = size.width - inset, y = y),
|
||||
strokeWidth = stroke,
|
||||
)
|
||||
}
|
||||
|
||||
// region Previews
|
||||
|
||||
@Preview(name = "Light", showBackground = true)
|
||||
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
|
||||
@Composable
|
||||
private fun TangemRowPreview() {
|
||||
TangemThemePreviewRedesign {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors3.bg.primary)
|
||||
.padding(vertical = 16.dp),
|
||||
) {
|
||||
// Equal lead, text only, with divider
|
||||
TangemRow(
|
||||
divider = true,
|
||||
contentLead = TangemRowContentLead.Equal,
|
||||
titleSlot = { TangemRowText(text = "Title", role = TangemRowTextRole.Title) },
|
||||
subtitleSlot = { TangemRowText(text = "Subtitle", role = TangemRowTextRole.Subtitle) },
|
||||
valueSlot = { TangemRowText(text = "Value", role = TangemRowTextRole.Value) },
|
||||
subvalueSlot = { TangemRowText(text = "Subvalue", role = TangemRowTextRole.Subvalue) },
|
||||
)
|
||||
// Start lead — title hugs, value side fills
|
||||
TangemRow(
|
||||
divider = true,
|
||||
contentLead = TangemRowContentLead.Start,
|
||||
titleSlot = { TangemRowText(text = "Network fee", role = TangemRowTextRole.Title) },
|
||||
valueSlot = { TangemRowText(text = "0.0001 ETH", role = TangemRowTextRole.Value) },
|
||||
subvalueSlot = { TangemRowText(text = "≈ $0.32", role = TangemRowTextRole.Subvalue) },
|
||||
)
|
||||
// End lead — value hugs, title side fills
|
||||
TangemRow(
|
||||
divider = true,
|
||||
contentLead = TangemRowContentLead.End,
|
||||
titleSlot = {
|
||||
TangemRowText(
|
||||
text = "Recipient with a very long address that should ellipsize",
|
||||
role = TangemRowTextRole.Title,
|
||||
)
|
||||
},
|
||||
valueSlot = { TangemRowText(text = "0x1234…abcd", role = TangemRowTextRole.Value) },
|
||||
)
|
||||
// Side slots + clickable + centered alignment
|
||||
TangemRow(
|
||||
verticalAlignment = TangemRowVerticalAlignment.Center,
|
||||
startSlot = {
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(id = R.drawable.ic_chevron_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors3.icon.primary,
|
||||
)
|
||||
},
|
||||
titleSlot = { TangemRowText(text = "Settings", role = TangemRowTextRole.Title) },
|
||||
subtitleSlot = {
|
||||
TangemRowText(text = "Tap to configure", role = TangemRowTextRole.Subtitle)
|
||||
},
|
||||
endSlot = {
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
painter = painterResource(id = R.drawable.ic_chevron_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors3.icon.secondary,
|
||||
)
|
||||
},
|
||||
onClick = {},
|
||||
)
|
||||
// Title only — exercises nullable slots / single LabelRow path
|
||||
TangemRow(
|
||||
titleSlot = { TangemRowText(text = "Single-line row", role = TangemRowTextRole.Title) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
@ -110,7 +110,12 @@ fun TangemSurface(
|
|||
|
||||
// region material rendering
|
||||
|
||||
/** Drop shadow for the material variant. */
|
||||
/**
|
||||
* Drop shadow for the material variant.
|
||||
*
|
||||
* The material fill is translucent, so the shadow is clipped to the area outside [shape] (via
|
||||
* `isAlphaContentClip`) to avoid the dark blur bleeding through the surface.
|
||||
*/
|
||||
@Composable
|
||||
private fun Modifier.materialShadow(shape: Shape): Modifier = softLayerShadow(
|
||||
radius = 40.dp,
|
||||
|
|
@ -118,6 +123,7 @@ private fun Modifier.materialShadow(shape: Shape): Modifier = softLayerShadow(
|
|||
shape = shape,
|
||||
spread = 0.dp,
|
||||
offset = DpOffset(x = 0.dp, y = 8.dp),
|
||||
isAlphaContentClip = true,
|
||||
)
|
||||
|
||||
/** Diagonal gradient stroke that wraps the material variant. */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.core.ui.ds2.topnavigation
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Density
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Text component for [TangemTopNavigation] title / subtitle slots.
|
||||
*
|
||||
* Unlike a regular [Text], this composable pins the font scale to `1f` for its subtree so that the
|
||||
* system accessibility "font size" setting cannot stretch the top navigation vertically.
|
||||
*
|
||||
* @param text Label text.
|
||||
* @param role Semantic role. See [TangemNavigationText.Role].
|
||||
* @param modifier Modifier applied to the underlying [Text].
|
||||
* @param maxLines Maximum visible lines before truncation.
|
||||
* @param overflow Overflow behavior. Defaults to ellipsis.
|
||||
*/
|
||||
@Composable
|
||||
fun TangemNavigationText(
|
||||
text: String,
|
||||
role: TangemNavigationText.Role,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = 1,
|
||||
overflow: TextOverflow = TextOverflow.Ellipsis,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val fixedFontScaleDensity = remember(density) {
|
||||
Density(density = density.density, fontScale = 1f)
|
||||
}
|
||||
CompositionLocalProvider(LocalDensity provides fixedFontScaleDensity) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
color = navigationTextColor(role),
|
||||
style = navigationTextStyle(role),
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TangemNavigationText(
|
||||
text: AnnotatedString,
|
||||
role: TangemNavigationText.Role,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = 1,
|
||||
overflow: TextOverflow = TextOverflow.Ellipsis,
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val fixedFontScaleDensity = remember(density) {
|
||||
Density(density = density.density, fontScale = 1f)
|
||||
}
|
||||
CompositionLocalProvider(LocalDensity provides fixedFontScaleDensity) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
color = navigationTextColor(role),
|
||||
style = navigationTextStyle(role),
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun TangemNavigationText(
|
||||
text: TextReference,
|
||||
role: TangemNavigationText.Role,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = 1,
|
||||
overflow: TextOverflow = TextOverflow.Ellipsis,
|
||||
) {
|
||||
TangemNavigationText(
|
||||
text = text.resolveAnnotatedReference(),
|
||||
role = role,
|
||||
modifier = modifier,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun navigationTextStyle(role: TangemNavigationText.Role): TextStyle = when (role) {
|
||||
TangemNavigationText.Role.Title -> TangemTheme.typography3.body.medium
|
||||
TangemNavigationText.Role.Subtitle -> TangemTheme.typography3.caption.medium
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun navigationTextColor(role: TangemNavigationText.Role): Color = when (role) {
|
||||
TangemNavigationText.Role.Title -> TangemTheme.colors3.text.primary
|
||||
TangemNavigationText.Role.Subtitle -> TangemTheme.colors3.text.secondary
|
||||
}
|
||||
|
||||
object TangemNavigationText {
|
||||
|
||||
/** Semantic role of a label inside [TangemTopNavigation], driving its typography and color. */
|
||||
@Immutable
|
||||
enum class Role { Title, Subtitle }
|
||||
}
|
||||
|
|
@ -0,0 +1,369 @@
|
|||
package com.tangem.core.ui.ds2.topnavigation
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkHorizontally
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.ds2.button.Back
|
||||
import com.tangem.core.ui.ds2.button.Close
|
||||
import com.tangem.core.ui.ds2.button.TangemButton
|
||||
import com.tangem.core.ui.ds2.fade.TangemFade
|
||||
import com.tangem.core.ui.ds2.surface.TangemSurface
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.rememberLastNonNull
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
||||
private enum class SlotId { Start, Content, Group, End }
|
||||
|
||||
/**
|
||||
* Top navigation bar from the redesigned design system.
|
||||
*
|
||||
* [Figma](https://www.figma.com/design/AsnJ5CPHib4Qxw12gszjMS/%F0%9F%92%A0-DS-Components?node-id=2270-3&m=dev)
|
||||
*
|
||||
* @param contentAlign How [contentColumn] is aligned horizontally within the bar.
|
||||
* @param windowInsets Top inset applied above the row. Pass `WindowInsets(0)` inside a bottom
|
||||
* sheet / modal.
|
||||
* @param blurBackground Whether the fade behind the row should blur the content below.
|
||||
* @param startButton Leading slot. Typically a back button (see [TangemButton.Back]).
|
||||
* @param endButtonsGroup Optional pill-grouped secondary actions placed just before [endButton].
|
||||
* @param endButton Trailing slot. Typically a close button (see [TangemButton.Close]).
|
||||
* @param contentColumn Center slot. Place title/subtitle children here.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun TangemTopNavigation(
|
||||
modifier: Modifier = Modifier,
|
||||
contentAlign: TangemTopNavigation.ContentAlign = TangemTopNavigation.ContentAlign.Start,
|
||||
windowInsets: WindowInsets = WindowInsets.statusBars,
|
||||
blurBackground: Boolean = true,
|
||||
startButton: (@Composable () -> Unit)? = null,
|
||||
endButtonsGroup: (@Composable RowScope.() -> Unit)? = null,
|
||||
endButton: (@Composable () -> Unit)? = null,
|
||||
contentColumn: (@Composable ColumnScope.() -> Unit)? = null,
|
||||
) {
|
||||
// Shared, snappy specs so size and alpha animations stay in sync across all top-nav slots,
|
||||
// mirroring the convention used by TangemButtonInternal.
|
||||
val slotSizeSpec = remember { spring<IntSize>(stiffness = Spring.StiffnessMediumLow) }
|
||||
val slotAlphaSpec = remember { spring<Float>(stiffness = Spring.StiffnessMediumLow) }
|
||||
val slotEnter = remember(slotSizeSpec, slotAlphaSpec) {
|
||||
fadeIn(animationSpec = slotAlphaSpec) + expandHorizontally(animationSpec = slotSizeSpec)
|
||||
}
|
||||
val slotExit = remember(slotSizeSpec, slotAlphaSpec) {
|
||||
fadeOut(animationSpec = slotAlphaSpec) + shrinkHorizontally(animationSpec = slotSizeSpec)
|
||||
}
|
||||
|
||||
Box(modifier) {
|
||||
TangemFade(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
position = TangemFade.Position.Top,
|
||||
variant = TangemFade.Variant.Soft,
|
||||
blur = blurBackground,
|
||||
)
|
||||
|
||||
val groupSpacing = TangemTheme.dimens3.spacing.s100
|
||||
Layout(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.windowInsetsPadding(windowInsets)
|
||||
.padding(
|
||||
top = TangemTheme.dimens3.spacing.s100,
|
||||
bottom = TangemTheme.dimens3.spacing.s200,
|
||||
start = TangemTheme.dimens3.spacing.s200,
|
||||
end = TangemTheme.dimens3.spacing.s200,
|
||||
),
|
||||
content = {
|
||||
// Each optional slot caches its last non-null content so the spring exit transition
|
||||
// still has something to render after the caller flips it back to `null`.
|
||||
val displayedStart = rememberLastNonNull(startButton)
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.layoutId(SlotId.Start),
|
||||
visible = startButton != null,
|
||||
enter = slotEnter,
|
||||
exit = slotExit,
|
||||
) {
|
||||
displayedStart?.invoke()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens3.spacing.s150)
|
||||
.layoutId(SlotId.Content),
|
||||
horizontalAlignment = when (contentAlign) {
|
||||
TangemTopNavigation.ContentAlign.Start -> Alignment.Start
|
||||
TangemTopNavigation.ContentAlign.Center -> Alignment.CenterHorizontally
|
||||
},
|
||||
) {
|
||||
contentColumn?.invoke(this)
|
||||
}
|
||||
|
||||
val displayedGroup = rememberLastNonNull(endButtonsGroup)
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.layoutId(SlotId.Group),
|
||||
visible = endButtonsGroup != null,
|
||||
enter = slotEnter,
|
||||
exit = slotExit,
|
||||
) {
|
||||
displayedGroup?.let { group ->
|
||||
TangemSurface(isMaterial = true, shape = CircleShape) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens3.spacing.s050),
|
||||
content = group,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val displayedEnd = rememberLastNonNull(endButton)
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.layoutId(SlotId.End),
|
||||
visible = endButton != null,
|
||||
enter = slotEnter,
|
||||
exit = slotExit,
|
||||
) {
|
||||
displayedEnd?.invoke()
|
||||
}
|
||||
},
|
||||
) { measurables, constraints ->
|
||||
val groupSpacingPx = groupSpacing.roundToPx()
|
||||
val totalWidth = constraints.maxWidth
|
||||
|
||||
val startM = measurables.first { it.layoutId == SlotId.Start }
|
||||
val contentM = measurables.first { it.layoutId == SlotId.Content }
|
||||
val groupM = measurables.first { it.layoutId == SlotId.Group }
|
||||
val endM = measurables.first { it.layoutId == SlotId.End }
|
||||
|
||||
val slotConstraints = constraints.copy(minWidth = 0)
|
||||
val startP = startM.measure(slotConstraints)
|
||||
val endP = endM.measure(slotConstraints)
|
||||
val groupP = groupM.measure(slotConstraints)
|
||||
|
||||
val contentMaxWidth = when (contentAlign) {
|
||||
// Symmetric band so the content can be visually centered within `totalWidth`
|
||||
// without colliding with the start/end slots.
|
||||
TangemTopNavigation.ContentAlign.Center ->
|
||||
(totalWidth - 2 * maxOf(startP.width, endP.width)).coerceAtLeast(0)
|
||||
TangemTopNavigation.ContentAlign.Start ->
|
||||
(totalWidth - startP.width - endP.width).coerceAtLeast(0)
|
||||
}
|
||||
val contentP = contentM.measure(slotConstraints.copy(maxWidth = contentMaxWidth))
|
||||
|
||||
val rowHeight = maxOf(startP.height, contentP.height, endP.height, groupP.height)
|
||||
|
||||
layout(totalWidth, rowHeight) {
|
||||
fun centerY(h: Int) = (rowHeight - h) / 2
|
||||
startP.placeRelative(x = 0, y = centerY(startP.height))
|
||||
|
||||
val contentX = when (contentAlign) {
|
||||
TangemTopNavigation.ContentAlign.Start -> startP.width
|
||||
TangemTopNavigation.ContentAlign.Center ->
|
||||
((totalWidth - contentP.width) / 2)
|
||||
.coerceIn(
|
||||
startP.width,
|
||||
(totalWidth - endP.width - contentP.width).coerceAtLeast(startP.width),
|
||||
)
|
||||
}
|
||||
contentP.placeRelative(x = contentX, y = centerY(contentP.height))
|
||||
|
||||
endP.placeRelative(x = totalWidth - endP.width, y = centerY(endP.height))
|
||||
// Group floats to the left of endButton with `groupSpacing` gap, overlaying the
|
||||
// tail of the content band if necessary.
|
||||
val groupX = (totalWidth - endP.width - groupSpacingPx - groupP.width)
|
||||
.coerceAtLeast(0)
|
||||
groupP.placeRelative(x = groupX, y = centerY(groupP.height))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** [TangemTopNavigation] with predefined back / close buttons and a title / subtitle center. */
|
||||
@Composable
|
||||
fun TangemTopNavigation(
|
||||
title: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
subtitle: TextReference? = null,
|
||||
contentAlign: TangemTopNavigation.ContentAlign = TangemTopNavigation.ContentAlign.Start,
|
||||
windowInsets: WindowInsets = WindowInsets.statusBars,
|
||||
blurBackground: Boolean = true,
|
||||
onBack: (() -> Unit)? = null,
|
||||
endButtonsGroup: (@Composable RowScope.() -> Unit)? = null,
|
||||
onClose: (() -> Unit)? = null,
|
||||
) {
|
||||
TangemTopNavigation(
|
||||
modifier = modifier,
|
||||
contentAlign = contentAlign,
|
||||
windowInsets = windowInsets,
|
||||
blurBackground = blurBackground,
|
||||
startButton = onBack?.let { { TangemButton.Back(onClick = it) } },
|
||||
endButtonsGroup = endButtonsGroup,
|
||||
endButton = onClose?.let { { TangemButton.Close(onClick = it) } },
|
||||
contentColumn = { TitleSubtitle(title = title, subtitle = subtitle) },
|
||||
)
|
||||
}
|
||||
|
||||
/** [TangemTopNavigation] with a custom [startButton], title / subtitle center, and predefined close. */
|
||||
@Composable
|
||||
fun TangemTopNavigation(
|
||||
title: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
subtitle: TextReference? = null,
|
||||
contentAlign: TangemTopNavigation.ContentAlign = TangemTopNavigation.ContentAlign.Start,
|
||||
windowInsets: WindowInsets = WindowInsets.statusBars,
|
||||
blurBackground: Boolean = true,
|
||||
endButtonsGroup: (@Composable RowScope.() -> Unit)? = null,
|
||||
onClose: (() -> Unit)? = null,
|
||||
startButton: @Composable () -> Unit,
|
||||
) {
|
||||
TangemTopNavigation(
|
||||
modifier = modifier,
|
||||
contentAlign = contentAlign,
|
||||
windowInsets = windowInsets,
|
||||
blurBackground = blurBackground,
|
||||
startButton = startButton,
|
||||
endButtonsGroup = endButtonsGroup,
|
||||
endButton = onClose?.let { { TangemButton.Close(onClick = it) } },
|
||||
contentColumn = { TitleSubtitle(title = title, subtitle = subtitle) },
|
||||
)
|
||||
}
|
||||
|
||||
/** [TangemTopNavigation] with predefined back, title / subtitle center, and a custom [endButton]. */
|
||||
@Composable
|
||||
fun TangemTopNavigation(
|
||||
title: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
subtitle: TextReference? = null,
|
||||
contentAlign: TangemTopNavigation.ContentAlign = TangemTopNavigation.ContentAlign.Start,
|
||||
windowInsets: WindowInsets = WindowInsets.statusBars,
|
||||
blurBackground: Boolean = true,
|
||||
onBack: (() -> Unit)? = null,
|
||||
endButton: @Composable () -> Unit,
|
||||
) {
|
||||
TangemTopNavigation(
|
||||
modifier = modifier,
|
||||
contentAlign = contentAlign,
|
||||
windowInsets = windowInsets,
|
||||
blurBackground = blurBackground,
|
||||
startButton = onBack?.let { { TangemButton.Back(onClick = it) } },
|
||||
endButton = endButton,
|
||||
contentColumn = { TitleSubtitle(title = title, subtitle = subtitle) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.TitleSubtitle(title: TextReference, subtitle: TextReference?) {
|
||||
val sizeSpec = remember { spring<IntSize>(stiffness = Spring.StiffnessMediumLow) }
|
||||
val alphaSpec = remember { spring<Float>(stiffness = Spring.StiffnessMediumLow) }
|
||||
|
||||
// Title swaps in place (no size change), so a pure cross-fade reads better than expand/shrink.
|
||||
val titleEnter = remember(alphaSpec) { fadeIn(animationSpec = alphaSpec) }
|
||||
val titleExit = remember(alphaSpec) { fadeOut(animationSpec = alphaSpec) }
|
||||
|
||||
// Subtitle pushes the bar down/up, so animate height instead of width.
|
||||
val subtitleEnter = remember(sizeSpec, alphaSpec) {
|
||||
fadeIn(animationSpec = alphaSpec) + expandVertically(animationSpec = sizeSpec)
|
||||
}
|
||||
val subtitleExit = remember(sizeSpec, alphaSpec) {
|
||||
fadeOut(animationSpec = alphaSpec) + shrinkVertically(animationSpec = sizeSpec)
|
||||
}
|
||||
|
||||
// Title swaps via cross-fade whenever the reference changes (e.g. step-driven flows).
|
||||
AnimatedContent(
|
||||
targetState = title,
|
||||
transitionSpec = { titleEnter togetherWith titleExit },
|
||||
label = "TangemTopNavigation.title",
|
||||
) { current ->
|
||||
TangemNavigationText(text = current, role = TangemNavigationText.Role.Title)
|
||||
}
|
||||
|
||||
// Subtitle expands/shrinks vertically so the title doesn't visually jump when toggled.
|
||||
val displayedSubtitle = rememberLastNonNull(subtitle)
|
||||
AnimatedVisibility(
|
||||
visible = subtitle != null,
|
||||
enter = subtitleEnter,
|
||||
exit = subtitleExit,
|
||||
) {
|
||||
displayedSubtitle?.let { text ->
|
||||
Column {
|
||||
Spacer(Modifier.height(TangemTheme.dimens3.spacing.s025))
|
||||
TangemNavigationText(text = text, role = TangemNavigationText.Role.Subtitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TangemTopNavigation {
|
||||
|
||||
/** Horizontal alignment of the center content slot. */
|
||||
enum class ContentAlign {
|
||||
Start, Center
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(
|
||||
showBackground = true,
|
||||
device = Devices.PIXEL_7_PRO,
|
||||
)
|
||||
@Preview(
|
||||
showBackground = true,
|
||||
device = Devices.PIXEL_7_PRO,
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreviewRedesign {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.hazeSourceTangem()
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
) {
|
||||
Spacer(Modifier.height(32.dp))
|
||||
// Screen-level usage: default insets reserve space for the system status bar.
|
||||
TangemTopNavigation(
|
||||
startButton = { TangemButton.Back { } },
|
||||
contentColumn = {
|
||||
TangemNavigationText(text = "Title", role = TangemNavigationText.Role.Title)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
TangemNavigationText(text = "Subtitle", role = TangemNavigationText.Role.Subtitle)
|
||||
},
|
||||
endButtonsGroup = {
|
||||
TangemButton(variant = TangemButton.Variant.Ghost) { }
|
||||
TangemButton(variant = TangemButton.Variant.Ghost) { }
|
||||
},
|
||||
endButton = { TangemButton.Close { } },
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
// Sheet/modal usage via the predefined-buttons overload: zero top inset.
|
||||
TangemTopNavigation(
|
||||
title = stringReference("Title"),
|
||||
subtitle = stringReference("Subtitle"),
|
||||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
windowInsets = WindowInsets(0),
|
||||
onClose = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -464,6 +464,10 @@ val LocalRedesignEnabled = staticCompositionLocalOf<Boolean> {
|
|||
false
|
||||
}
|
||||
|
||||
val LocalVisaRedesignEnabled = staticCompositionLocalOf<Boolean> {
|
||||
false
|
||||
}
|
||||
|
||||
val LocalPowerSavingState = compositionLocalOf<PowerSavingState> {
|
||||
error("No PowerSavingState provided")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ fun TangemThemeRedesign(content: @Composable () -> Unit) {
|
|||
val tangemDimens3 = remember { TangemDimens3() }
|
||||
val tangemTypography3 = remember { TangemTypography3(InterFamily) }
|
||||
val tangemTypography2 = remember { TangemTypography2(InterFamily) }
|
||||
val tangemTypography = remember { TangemTypography(InterFamily) }
|
||||
val tangemTypography = remember { TangemTypography(InterFamily, useMediumForRegular = true) }
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = tangemColorScheme(colors = rememberedColors),
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ internal val RobotoFamily = FontFamily(
|
|||
@Immutable
|
||||
class TangemTypography internal constructor(
|
||||
fontFamily: FontFamily,
|
||||
useMediumForRegular: Boolean = false,
|
||||
) {
|
||||
private val regularWeight: FontWeight = if (useMediumForRegular) FontWeight.Medium else FontWeight.Normal
|
||||
|
||||
val head: TextStyle = TextStyle(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = 34.sp,
|
||||
|
|
@ -34,7 +37,7 @@ class TangemTypography internal constructor(
|
|||
val h1: TextStyle = TextStyle(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = 34.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = regularWeight,
|
||||
letterSpacing = TextUnit(value = 0f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 44f, type = TextUnitType.Sp),
|
||||
lineHeightStyle = LineHeightStyle(
|
||||
|
|
@ -89,7 +92,7 @@ class TangemTypography internal constructor(
|
|||
val body1: TextStyle = TextStyle(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = regularWeight,
|
||||
letterSpacing = TextUnit(value = 0.5f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 24f, type = TextUnitType.Sp),
|
||||
lineHeightStyle = LineHeightStyle(
|
||||
|
|
@ -100,7 +103,7 @@ class TangemTypography internal constructor(
|
|||
val body2: TextStyle = TextStyle(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = regularWeight,
|
||||
letterSpacing = TextUnit(value = 0.25f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp),
|
||||
lineHeightStyle = LineHeightStyle(
|
||||
|
|
@ -133,7 +136,7 @@ class TangemTypography internal constructor(
|
|||
val caption2: TextStyle = TextStyle(
|
||||
fontFamily = fontFamily,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontWeight = regularWeight,
|
||||
letterSpacing = TextUnit(value = 0.4f, type = TextUnitType.Sp),
|
||||
lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp),
|
||||
lineHeightStyle = LineHeightStyle(
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2eb71d4ac556a6608e34adac157e599b5250677fe1b1727363fcb82218320be1
|
||||
058e1ecd83440d5fda33512248f67176db593029c9b398e4a2692816703e451c
|
||||
|
|
|
|||
|
|
@ -151,8 +151,8 @@ internal fun lightColors3() =
|
|||
),
|
||||
fill = TangemColors3.Material.Fill(
|
||||
glass = Color(0x00000000),
|
||||
blur = Color(0x99FFFFFF),
|
||||
solid = Color(0xE6FFFFFF),
|
||||
blur = Color(0x99F4F4F4),
|
||||
solid = Color(0xF2F4F4F4),
|
||||
),
|
||||
lighten = TangemColors3.Material.Lighten(
|
||||
glass = Color(0x80F7F7F7),
|
||||
|
|
@ -165,9 +165,9 @@ internal fun lightColors3() =
|
|||
solid = Color(0x00000000),
|
||||
),
|
||||
border = TangemColors3.Material.Border(
|
||||
start = Color(0x26000000),
|
||||
mid = Color(0x00000000),
|
||||
end = Color(0x1A000000),
|
||||
start = Color(0xCCFFFFFF),
|
||||
mid = Color(0x00FFFFFF),
|
||||
end = Color(0x99FFFFFF),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -27,6 +27,8 @@ class TangemDimens3 internal constructor(
|
|||
class Blur internal constructor(
|
||||
val card: Dp = 48.dp,
|
||||
val Button: Dp = 32.dp,
|
||||
val Fade: Dp = 8.dp,
|
||||
val None: Dp = 0.dp,
|
||||
)
|
||||
|
||||
@Stable
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class TangemTypography3 internal constructor(fontFamily: FontFamily) {
|
|||
fontFamily = fontFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 17.sp,
|
||||
lineHeight = 18.sp,
|
||||
letterSpacing = 0.07.sp,
|
||||
lineHeightStyle = LineHeightStyle(
|
||||
alignment = LineHeightStyle.Alignment.Center,
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
c1f3db82744567cdd0c59a29761e1b3b4bd4bb81acce832fb0391c7ab24be491
|
||||
f264a99d653eca57bedd4b49ff9ce5171ba9615770a57d574824e387f6cb1d5c
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_address_polygon_16: ImageVector? = null
|
||||
|
||||
val Icons.ic_address_polygon_16: ImageVector
|
||||
get() {
|
||||
if (_ic_address_polygon_16 != null) return _ic_address_polygon_16!!
|
||||
_ic_address_polygon_16 = ImageVector.Builder(
|
||||
name = "ic_address_polygon_16",
|
||||
defaultWidth = 16.dp,
|
||||
defaultHeight = 16.dp,
|
||||
viewportWidth = 16f,
|
||||
viewportHeight = 16f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M4.53711 2.69414C5.02195 2.43596 5.60404 2.43592 6.08887 2.69414L7.75195 3.57988C8.28964 3.86657 8.62591 4.42656 8.62598 5.03594V5.49492C8.62547 5.83958 8.34573 6.11979 8.00098 6.11992C7.65613 6.1199 7.37649 5.83965 7.37598 5.49492V5.03594C7.37591 4.88841 7.29414 4.75294 7.16406 4.6834L5.50098 3.79766C5.38347 3.73507 5.24252 3.73512 5.125 3.79766L3.46191 4.6834C3.3317 4.7529 3.25007 4.88833 3.25 5.03594V7.38457C3.2502 7.53207 3.33176 7.66767 3.46191 7.73711L5.125 8.62285C5.24241 8.68521 5.3836 8.6853 5.50098 8.62285L9.91309 6.27324C10.3979 6.01506 10.98 6.01502 11.4648 6.27324L13.1279 7.15898C13.6656 7.44565 14.0019 8.00568 14.002 8.61504V10.9637C14.0018 11.573 13.6656 12.1331 13.1279 12.4197L11.4648 13.3055C10.9801 13.5636 10.3978 13.5635 9.91309 13.3055L8.25 12.4197C7.71226 12.1331 7.37617 11.573 7.37598 10.9637V10.5057C7.37598 10.1605 7.65581 9.88068 8.00098 9.88066C8.34604 9.88079 8.62598 10.1606 8.62598 10.5057V10.9637C8.62617 11.1113 8.70759 11.2478 8.83789 11.3172L10.501 12.2029C10.6183 12.2652 10.7597 12.2652 10.877 12.2029L12.54 11.3172C12.6703 11.2478 12.7518 11.1112 12.752 10.9637V8.61504C12.7519 8.46752 12.6701 8.33202 12.54 8.2625L10.877 7.37676C10.7595 7.31417 10.6185 7.31422 10.501 7.37676L6.08887 9.72637C5.60417 9.98446 5.02184 9.98437 4.53711 9.72637L2.87402 8.84062C2.33626 8.55404 2.0002 7.99392 2 7.38457V5.03594C2.00007 4.42648 2.3362 3.86654 2.87402 3.57988L4.53711 2.69414Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_address_polygon_16!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcAddressPolygon16Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_address_polygon_16,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_address_polygon_20: ImageVector? = null
|
||||
|
||||
val Icons.ic_address_polygon_20: ImageVector
|
||||
get() {
|
||||
if (_ic_address_polygon_20 != null) return _ic_address_polygon_20!!
|
||||
_ic_address_polygon_20 = ImageVector.Builder(
|
||||
name = "ic_address_polygon_20",
|
||||
defaultWidth = 20.dp,
|
||||
defaultHeight = 20.dp,
|
||||
viewportWidth = 20f,
|
||||
viewportHeight = 20f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M5.78125 3.21402C6.30813 2.92909 6.94286 2.92905 7.46973 3.21402L9.82031 4.48453C10.3935 4.79465 10.751 5.39437 10.751 6.04605V6.80484C10.7504 7.21849 10.4147 7.55467 10.001 7.55484C9.5871 7.55484 9.25152 7.21859 9.25098 6.80484V6.04605C9.25095 5.94518 9.19511 5.85194 9.10645 5.80386L6.75586 4.53335C6.67424 4.48924 6.57575 4.48921 6.49414 4.53335L4.14453 5.80386C4.05587 5.85194 4.00002 5.94518 4 6.04605V9.38882C4.00037 9.48941 4.056 9.58215 4.14453 9.63003L6.49414 10.9015C6.57557 10.9455 6.6744 10.9454 6.75586 10.9015L12.5312 7.7775C13.0582 7.4925 13.6938 7.4925 14.2207 7.7775L16.5703 9.04898C17.1435 9.35911 17.501 9.9588 17.501 10.6105V13.9523C17.5008 14.6039 17.1434 15.2038 16.5703 15.5138L14.2207 16.7853C13.6939 17.0701 13.058 17.0701 12.5312 16.7853L10.1816 15.5138C9.6085 15.2038 9.25119 14.6039 9.25098 13.9523V13.1945C9.25098 12.7803 9.58676 12.4445 10.001 12.4445C10.415 12.4447 10.751 12.7804 10.751 13.1945V13.9523C10.7512 14.053 10.806 14.1465 10.8945 14.1945L13.2451 15.466C13.3266 15.5099 13.4254 15.5099 13.5068 15.466L15.8574 14.1945C15.946 14.1465 16.0008 14.053 16.001 13.9523V10.6105C16.001 10.5097 15.946 10.4164 15.8574 10.3683L13.5068 9.09683C13.4253 9.05275 13.3267 9.05274 13.2451 9.09683L7.46973 12.2209C6.94305 12.5056 6.30796 12.5055 5.78125 12.2209L3.43066 10.9494C2.85767 10.6394 2.50037 10.0402 2.5 9.38882V6.04605C2.50002 5.39437 2.85752 4.79465 3.43066 4.48453L5.78125 3.21402Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_address_polygon_20!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcAddressPolygon20Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_address_polygon_20,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_address_polygon_24: ImageVector? = null
|
||||
|
||||
val Icons.ic_address_polygon_24: ImageVector
|
||||
get() {
|
||||
if (_ic_address_polygon_24 != null) return _ic_address_polygon_24!!
|
||||
_ic_address_polygon_24 = ImageVector.Builder(
|
||||
name = "ic_address_polygon_24",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M7.09277 4.21716C7.66252 3.92737 8.33748 3.92737 8.90723 4.21716L11.9072 5.74353C12.5777 6.08477 12.9999 6.7744 13 7.52673V8.43884C12.9999 8.99101 12.5522 9.43884 12 9.43884C11.4478 9.43884 11.0001 8.99101 11 8.43884V7.52673L8 6.00037L5 7.52673V11.3871L8 12.9125L15.0928 9.30505C15.6625 9.01526 16.3375 9.01526 16.9072 9.30505L19.9072 10.8304C20.5777 11.1717 20.9999 11.8613 21 12.6136V16.474C20.9998 17.2262 20.5776 17.915 19.9072 18.2562L16.9072 19.7826C16.3375 20.0724 15.6625 20.0724 15.0928 19.7826L12.0928 18.2562C11.4224 17.915 11.0002 17.2262 11 16.474V15.5609C11.0001 15.0087 11.4478 14.5609 12 14.5609C12.5522 14.5609 12.9999 15.0087 13 15.5609V16.474L16 17.9994L19 16.474V12.6136L16 11.0873L8.90723 14.6957C8.33749 14.9855 7.66251 14.9855 7.09277 14.6957L4.09277 13.1693C3.42245 12.8281 3.00021 12.1392 3 11.3871V7.52673C3.00008 6.7744 3.42229 6.08477 4.09277 5.74353L7.09277 4.21716Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_address_polygon_24!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcAddressPolygon24Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_address_polygon_24,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ val Icons.ic_arrow_down_12: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M5.5 2L5.5 9.04297L2.35352 5.89648C2.15825 5.70122 1.84175 5.70122 1.64649 5.89648C1.45122 6.09175 1.45122 6.40825 1.64649 6.60352L5.64648 10.6035L5.72266 10.666C5.80419 10.7204 5.90056 10.75 6 10.75C6.13261 10.75 6.25975 10.6973 6.35352 10.6035L10.3535 6.60352C10.5488 6.40826 10.5488 6.09175 10.3535 5.89649C10.1583 5.70122 9.84175 5.70122 9.64649 5.89649L6.5 9.04297L6.5 2C6.5 1.72386 6.27614 1.5 6 1.5C5.72386 1.5 5.5 1.72386 5.5 2Z"),
|
||||
pathData = addPathNodes("M2.14582 6.64587C1.95126 6.84117 1.95085 7.15787 2.14582 7.35291L5.6468 10.8539C5.84183 11.0489 6.15852 11.0485 6.35383 10.8539L9.85481 7.35291C10.05 7.15764 10.05 6.84112 9.85481 6.64587C9.65955 6.45076 9.343 6.45072 9.14777 6.64587L6.50031 9.29333L6.50031 1.50232C6.50031 1.22622 6.2764 1.00238 6.00031 1.00232C5.72451 1.00272 5.50031 1.22642 5.50031 1.50232L5.50031 9.29333L2.85285 6.64587C2.65765 6.45075 2.34107 6.45083 2.14582 6.64587Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_down_12!!
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ val Icons.ic_arrow_down_16: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M7.4997 2.66669L7.4997 12.4597L3.02021 7.98016C2.82494 7.7849 2.50844 7.7849 2.31318 7.98016C2.11808 8.17544 2.11797 8.49199 2.31318 8.68719L7.64618 14.0202C7.73987 14.1139 7.86721 14.1666 7.9997 14.1667C8.13223 14.1667 8.25946 14.1139 8.35321 14.0202L13.6872 8.6872C13.8824 8.49204 13.8821 8.17545 13.6872 7.98017C13.4919 7.7849 13.1754 7.7849 12.9802 7.98017L8.4997 12.4606L8.4997 2.66669C8.4997 2.39055 8.27584 2.16669 7.9997 2.16669C7.72371 2.16686 7.4997 2.39065 7.4997 2.66669Z"),
|
||||
pathData = addPathNodes("M12.8149 8.74725C13.0583 8.99129 13.0585 9.38713 12.8149 9.63104L8.44185 14.0041C8.19795 14.2476 7.80207 14.2474 7.55806 14.0041L3.18502 9.63104C2.94151 9.38704 2.94143 8.9912 3.18502 8.74725C3.42903 8.50336 3.82573 8.50343 4.06978 8.74725L7.37545 12.0529L7.37545 2.50018C7.37555 2.15518 7.65545 1.87532 8.00045 1.87518C8.34515 1.87567 8.62534 2.15539 8.62545 2.50018L8.62545 12.0519L11.9311 8.74725C12.1751 8.50352 12.5709 8.50348 12.8149 8.74725Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_down_16!!
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ val Icons.ic_arrow_down_20: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M9.25031 3.33331L9.2503 15.2728L3.86359 9.88605C3.57073 9.59337 3.09589 9.59337 2.80304 9.88605C2.5102 10.1789 2.51031 10.6537 2.80304 10.9466L9.47003 17.6136L9.58429 17.7073C9.70654 17.7888 9.85124 17.8333 10.0003 17.8333C10.1991 17.8332 10.39 17.7542 10.5306 17.6136L17.1966 10.9466C17.4895 10.6537 17.4895 10.1789 17.1966 9.88605C16.9037 9.59348 16.4288 9.59326 16.136 9.88605L10.7503 15.2728L10.7503 3.33331C10.7503 2.91921 10.4144 2.58349 10.0003 2.58331C9.58609 2.58331 9.25031 2.9191 9.25031 3.33331Z"),
|
||||
pathData = addPathNodes("M16.7808 10.4873C17.0729 10.7801 17.073 11.2551 16.7808 11.5479L10.5298 17.7988C10.2371 18.0915 9.76117 18.0912 9.46825 17.7988L3.21728 11.5479C2.92445 11.255 2.92458 10.7802 3.21728 10.4873C3.51021 10.1948 3.98508 10.1946 4.27782 10.4873L9.2495 15.458L9.2495 2.75C9.24962 2.33588 9.58536 2 9.9995 2C10.413 2.00075 10.7494 2.33635 10.7495 2.75L10.7495 15.457L15.7202 10.4873C16.0131 10.1948 16.488 10.1946 16.7808 10.4873Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_down_20!!
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ val Icons.ic_arrow_down_24: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M11 4L11 18.0859L4.70703 11.793C4.31651 11.4024 3.6835 11.4024 3.29297 11.793C2.90245 12.1835 2.90245 12.8165 3.29297 13.207L11.293 21.207L11.3662 21.2734C11.5442 21.4193 11.7679 21.5 12 21.5C12.2652 21.5 12.5195 21.3946 12.707 21.207L20.707 13.207C21.0976 12.8165 21.0976 12.1835 20.707 11.793C20.3165 11.4024 19.6835 11.4024 19.293 11.793L13 18.0859L13 4C13 3.44772 12.5523 3 12 3C11.4477 3 11 3.44772 11 4Z"),
|
||||
pathData = addPathNodes("M4.29244 14.707C3.90252 14.3167 3.90261 13.6834 4.29244 13.293C4.68283 12.9027 5.31598 12.9029 5.7065 13.293L10.9985 18.585L10.9985 3C10.9986 2.44808 11.4466 2.00042 11.9985 2C12.5507 2.00003 12.9984 2.44784 12.9985 3L12.9985 18.585L18.2905 13.293C18.6809 12.9027 19.314 12.9029 19.7045 13.293C20.0949 13.6835 20.0949 14.3165 19.7045 14.707L12.7055 21.7061C12.5181 21.8932 12.2634 21.999 11.9985 21.999C11.7336 21.9988 11.4788 21.8933 11.2915 21.7061L4.29244 14.707Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_down_24!!
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ val Icons.ic_arrow_down_28: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M12.7496 4.66669L12.7496 20.8991L5.55042 13.6999C5.06226 13.2117 4.27099 13.2117 3.78284 13.6999C3.29485 14.1881 3.29474 14.9794 3.78284 15.4675L13.1158 24.8005C13.3502 25.0348 13.6682 25.1666 13.9996 25.1667C14.3311 25.1667 14.649 25.0348 14.8834 24.8005L24.2174 15.4675C24.7055 14.9794 24.7052 14.1881 24.2174 13.6999C23.7293 13.2117 22.938 13.2117 22.4498 13.6999L15.2496 20.9001L15.2496 4.66669C15.2496 3.97633 14.69 3.41669 13.9996 3.41669C13.3094 3.41686 12.7496 3.97644 12.7496 4.66669Z"),
|
||||
pathData = addPathNodes("M4.35954 16.751C3.87516 16.2595 3.88101 15.468 4.37224 14.9834C4.86381 14.4991 5.65524 14.5048 6.13981 14.9961L12.7511 21.7021V3.25C12.7513 2.5599 13.3111 2.00024 14.0011 2C14.6911 2.00033 15.251 2.55995 15.2511 3.25V21.7012L21.8615 14.9961C22.3461 14.5048 23.1385 14.499 23.63 14.9834C24.1212 15.468 24.1269 16.2594 23.6427 16.751L14.8908 25.6279C14.6561 25.8657 14.3353 25.9998 14.0011 26C13.6668 25.9999 13.3453 25.866 13.1105 25.6279L4.35954 16.751Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_down_28!!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_download_16: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_download_16: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_download_16 != null) return _ic_arrow_download_16!!
|
||||
_ic_arrow_download_16 = ImageVector.Builder(
|
||||
name = "ic_arrow_download_16",
|
||||
defaultWidth = 16.dp,
|
||||
defaultHeight = 16.dp,
|
||||
viewportWidth = 16f,
|
||||
viewportHeight = 16f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M13.3779 12.7451C13.7228 12.7453 14.0027 13.0252 14.0029 13.3701C14.0027 13.715 13.7228 13.9949 13.3779 13.9951H2.625C2.27994 13.9951 2.00018 13.7151 2 13.3701C2.00018 13.0251 2.27993 12.7451 2.625 12.7451H13.3779Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M8.00098 2C8.34615 2 8.62598 2.27982 8.62598 2.625V9.59082L10.9463 7.53027C11.2044 7.30141 11.6 7.3241 11.8291 7.58203C12.0581 7.83997 12.034 8.23462 11.7764 8.46387L8.41602 11.4502C8.40238 11.4623 8.38548 11.4707 8.37109 11.4814C8.36061 11.4892 8.35082 11.4978 8.33984 11.5049C8.31406 11.5217 8.28723 11.5353 8.25977 11.5479C8.25221 11.5513 8.24502 11.5554 8.2373 11.5586C8.20496 11.5719 8.1724 11.5832 8.13867 11.5908C8.13375 11.5919 8.12899 11.5937 8.12402 11.5947C8.04367 11.6109 7.9613 11.6104 7.88086 11.5947C7.8712 11.5928 7.86203 11.5892 7.85254 11.5869C7.82143 11.5793 7.79062 11.571 7.76074 11.5586C7.75671 11.5569 7.75301 11.5545 7.74902 11.5527C7.71757 11.5389 7.6875 11.5222 7.6582 11.5029C7.6498 11.4974 7.6419 11.4913 7.63379 11.4854C7.61791 11.4737 7.60092 11.4635 7.58594 11.4502L4.22559 8.46387C3.9677 8.23458 3.94461 7.84002 4.17383 7.58203C4.40316 7.3245 4.7978 7.30117 5.05566 7.53027L7.37598 9.59082V2.625C7.37598 2.27985 7.65584 2.00005 8.00098 2Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_download_16!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowDownload16Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_download_16,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_download_20: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_download_20: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_download_20 != null) return _ic_arrow_download_20!!
|
||||
_ic_arrow_download_20 = ImageVector.Builder(
|
||||
name = "ic_arrow_download_20",
|
||||
defaultWidth = 20.dp,
|
||||
defaultHeight = 20.dp,
|
||||
viewportWidth = 20f,
|
||||
viewportHeight = 20f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M16.7549 15.502C17.1688 15.5023 17.5049 15.838 17.5049 16.252C17.5047 16.6657 17.1686 17.0016 16.7549 17.002H3.25C2.83593 17.002 2.50023 16.666 2.5 16.252C2.5 15.8377 2.83579 15.502 3.25 15.502H16.7549Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M10.002 3C10.4161 3 10.7518 3.33589 10.752 3.75V11.6631L13.4717 8.94336C13.7646 8.65082 14.2395 8.65059 14.5322 8.94336C14.8249 9.23615 14.8248 9.71102 14.5322 10.0039L10.5322 14.0039C10.4383 14.0978 10.3205 14.1605 10.1943 14.1943C10.1793 14.1983 10.1648 14.2049 10.1494 14.208C10.1468 14.2085 10.1442 14.2085 10.1416 14.209C10.1025 14.2164 10.0625 14.2197 10.0215 14.2207C10.015 14.2209 10.0085 14.2236 10.002 14.2236C9.99521 14.2236 9.98817 14.2209 9.98145 14.2207C9.94114 14.2196 9.90176 14.2162 9.86328 14.209C9.85944 14.2083 9.85538 14.2088 9.85156 14.208C9.84522 14.2067 9.83929 14.2036 9.83301 14.2021C9.69737 14.1707 9.57171 14.1038 9.47168 14.0039L5.47168 10.0039C5.17881 9.71101 5.1788 9.23624 5.47168 8.94336C5.76461 8.65082 6.23946 8.65059 6.53223 8.94336L9.25195 11.6631V3.75C9.25208 3.33594 9.58788 3.00007 10.002 3Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_download_20!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowDownload20Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_download_20,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_download_24: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_download_24: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_download_24 != null) return _ic_arrow_download_24!!
|
||||
_ic_arrow_download_24 = ImageVector.Builder(
|
||||
name = "ic_arrow_download_24",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M20 18.501C20.5523 18.501 21 18.9487 21 19.501C20.9999 20.0532 20.5522 20.501 20 20.501H4C3.44777 20.501 3.00009 20.0532 3 19.501C3 18.9487 3.44772 18.501 4 18.501H20Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M12 3.5C12.5523 3.5 13 3.94772 13 4.5V14.0303L16.3594 11.2314C16.7836 10.878 17.415 10.9353 17.7686 11.3594C18.1221 11.7836 18.0648 12.415 17.6406 12.7686L12.6396 16.9355C12.6146 16.9565 12.588 16.9751 12.5615 16.9932C12.5553 16.9974 12.5493 17.0018 12.543 17.0059C12.5221 17.0194 12.501 17.0321 12.4795 17.0439C12.4754 17.0462 12.4709 17.0476 12.4668 17.0498C12.4434 17.0622 12.4196 17.0736 12.3955 17.084C12.3877 17.0873 12.3799 17.0906 12.3721 17.0938C12.3562 17.1001 12.3403 17.1058 12.3242 17.1113C12.2796 17.1267 12.2338 17.1395 12.1865 17.1484C12.1697 17.1516 12.1527 17.1529 12.1357 17.1553C12.101 17.16 12.066 17.164 12.0303 17.165C12.0078 17.1657 11.9854 17.1649 11.9629 17.1641C11.9302 17.1629 11.898 17.1605 11.8662 17.1562C11.8469 17.1537 11.8277 17.1512 11.8086 17.1475C11.7645 17.1389 11.7215 17.1274 11.6797 17.1133C11.5653 17.0747 11.4557 17.0167 11.3584 16.9355L6.3584 12.7686C5.93422 12.4149 5.87687 11.7836 6.23047 11.3594C6.58408 10.9352 7.21544 10.8779 7.63965 11.2314L11 14.0322V4.5C11 3.94772 11.4477 3.5 12 3.5Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_download_24!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowDownload24Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_download_24,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_left_12: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_left_12: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_left_12 != null) return _ic_arrow_left_12!!
|
||||
_ic_arrow_left_12 = ImageVector.Builder(
|
||||
name = "ic_arrow_left_12",
|
||||
defaultWidth = 12.dp,
|
||||
defaultHeight = 12.dp,
|
||||
viewportWidth = 12f,
|
||||
viewportHeight = 12f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M4.64638 2.14623C4.84125 1.95157 5.15811 1.95224 5.35341 2.14623C5.54826 2.34146 5.54835 2.6581 5.35341 2.85327L2.70595 5.50073H10.497C10.7726 5.50073 10.9962 5.72523 10.997 6.00073C10.9966 6.27659 10.7729 6.50073 10.497 6.50073H2.70595L5.35341 9.14819C5.54837 9.34338 5.54834 9.66001 5.35341 9.85522C5.15819 10.0501 4.84154 10.0502 4.64638 9.85522L1.1454 6.35424C0.951454 6.159 0.950881 5.8421 1.1454 5.64721L4.64638 2.14623Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_left_12!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowLeft12Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_left_12,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_left_16: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_left_16: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_left_16 != null) return _ic_arrow_left_16!!
|
||||
_ic_arrow_left_16 = ImageVector.Builder(
|
||||
name = "ic_arrow_left_16",
|
||||
defaultWidth = 16.dp,
|
||||
defaultHeight = 16.dp,
|
||||
viewportWidth = 16f,
|
||||
viewportHeight = 16f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M7.25277 3.18508C7.00873 2.94166 6.61289 2.94146 6.36898 3.18508L1.99593 7.55813C1.75241 7.80203 1.75258 8.19791 1.99593 8.44192L6.36898 12.815C6.61298 13.0585 7.00881 13.0586 7.25277 12.815C7.49666 12.571 7.49659 12.1743 7.25277 11.9302L3.9471 8.62453H13.4998C13.8448 8.62443 14.1247 8.34453 14.1248 7.99953C14.1244 7.65483 13.8446 7.37464 13.4998 7.37453H3.94808L7.25277 4.06887C7.4965 3.82484 7.49654 3.42909 7.25277 3.18508Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_left_16!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowLeft16Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_left_16,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_left_20: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_left_20: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_left_20 != null) return _ic_arrow_left_20!!
|
||||
_ic_arrow_left_20 = ImageVector.Builder(
|
||||
name = "ic_arrow_left_20",
|
||||
defaultWidth = 20.dp,
|
||||
defaultHeight = 20.dp,
|
||||
viewportWidth = 20f,
|
||||
viewportHeight = 20f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M9.51269 3.21852C9.21989 2.92641 8.74486 2.92628 8.45214 3.21852L2.20117 9.4695C1.90849 9.76222 1.90881 10.2381 2.20117 10.531L8.45214 16.782C8.74497 17.0748 9.21978 17.0747 9.51269 16.782C9.80515 16.4891 9.80544 16.0142 9.51269 15.7215L4.54199 10.7498H17.25C17.6641 10.7497 18 10.4139 18 9.99977C17.9992 9.58627 17.6636 9.24989 17.25 9.24977H4.54296L9.51269 4.27907C9.80515 3.98614 9.80544 3.51127 9.51269 3.21852Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_left_20!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowLeft20Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_left_20,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_left_24: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_left_24: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_left_24 != null) return _ic_arrow_left_24!!
|
||||
_ic_arrow_left_24 = ImageVector.Builder(
|
||||
name = "ic_arrow_left_24",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M9.29199 4.29265C9.68241 3.90259 10.3156 3.90264 10.7061 4.29265C11.0962 4.68308 11.0962 5.31624 10.7061 5.70671L5.41406 10.9987H20.999C21.551 10.9988 21.9987 11.4467 21.999 11.9987C21.9988 12.5507 21.5511 12.9986 20.999 12.9987H5.41406L10.7061 18.2907C11.0962 18.6811 11.0962 19.3143 10.7061 19.7048C10.3156 20.0949 9.68242 20.0949 9.29199 19.7048L2.29297 12.7057C2.10577 12.5183 2.00009 12.2636 2 11.9987C2.00014 11.7338 2.10575 11.479 2.29297 11.2917L9.29199 4.29265Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_left_24!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowLeft24Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_left_24,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_left_28: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_left_28: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_left_28 != null) return _ic_arrow_left_28!!
|
||||
_ic_arrow_left_28 = ImageVector.Builder(
|
||||
name = "ic_arrow_left_28",
|
||||
defaultWidth = 28.dp,
|
||||
defaultHeight = 28.dp,
|
||||
viewportWidth = 28f,
|
||||
viewportHeight = 28f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M11.249 4.35978C11.7405 3.8754 12.532 3.88125 13.0166 4.37248C13.5009 4.86406 13.4952 5.65549 13.0039 6.14006L6.29785 12.7514H24.75C25.4401 12.7515 25.9998 13.3113 26 14.0014C25.9997 14.6914 25.4401 15.2513 24.75 15.2514H6.29883L13.0039 21.8617C13.4952 22.3464 13.501 23.1387 13.0166 23.6303C12.532 24.1214 11.7406 24.1271 11.249 23.643L2.37207 14.891C2.13429 14.6563 2.00016 14.3355 2 14.0014C2.00012 13.667 2.13402 13.3455 2.37207 13.1108L11.249 4.35978Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_left_28!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowLeft28Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_left_28,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_refresh_12: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_refresh_12: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_refresh_12 != null) return _ic_arrow_refresh_12!!
|
||||
_ic_arrow_refresh_12 = ImageVector.Builder(
|
||||
name = "ic_arrow_refresh_12",
|
||||
defaultWidth = 12.dp,
|
||||
defaultHeight = 12.dp,
|
||||
viewportWidth = 12f,
|
||||
viewportHeight = 12f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M9.96777 5.4668C10.2623 5.4668 10.501 5.70545 10.501 6C10.501 8.48579 8.48581 10.501 6 10.501C4.62026 10.5007 3.39017 9.87706 2.56641 8.90039V9.30664C2.56606 9.6009 2.32754 9.83984 2.0332 9.83984C1.73913 9.83953 1.50035 9.6007 1.5 9.30664V7.65332C1.5 7.35896 1.73892 7.12043 2.0332 7.12012H3.68652C3.98107 7.12012 4.21973 7.35877 4.21973 7.65332C4.21954 7.94772 3.98096 8.18652 3.68652 8.18652H3.35938C3.98887 8.94784 4.93716 9.43331 6 9.43359C7.89672 9.43359 9.43457 7.89668 9.43457 6C9.43457 5.7056 9.67343 5.46705 9.96777 5.4668Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M6.00098 1.5C7.38045 1.50006 8.61056 2.12258 9.43457 3.09863V2.69434C9.43457 2.39994 9.67344 2.16138 9.96777 2.16113C10.2623 2.16113 10.501 2.39978 10.501 2.69434V4.34766C10.5006 4.64187 10.2621 4.88086 9.96777 4.88086H8.31445C8.02049 4.88046 7.78164 4.64162 7.78125 4.34766C7.78125 4.05335 8.02024 3.81486 8.31445 3.81445H8.64355C8.01382 3.05222 7.06456 2.56647 6.00098 2.56641C4.10455 2.56659 2.56762 4.10363 2.56738 6C2.56713 6.29422 2.32841 6.53399 2.03418 6.53418C1.73978 6.53418 1.50123 6.29434 1.50098 6C1.50121 3.51452 3.51547 1.50018 6.00098 1.5Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_refresh_12!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRefresh12Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_refresh_12,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_refresh_16: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_refresh_16: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_refresh_16 != null) return _ic_arrow_refresh_16!!
|
||||
_ic_arrow_refresh_16 = ImageVector.Builder(
|
||||
name = "ic_arrow_refresh_16",
|
||||
defaultWidth = 16.dp,
|
||||
defaultHeight = 16.dp,
|
||||
viewportWidth = 16f,
|
||||
viewportHeight = 16f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M12.8789 7.37695C13.224 7.37704 13.5039 7.65683 13.5039 8.00195C13.5039 11.0407 11.0407 13.5038 8.00195 13.5039C6.28439 13.5039 4.7571 12.713 3.75 11.4814V12.0664C3.74971 12.4113 3.46988 12.6913 3.125 12.6914C2.7801 12.6913 2.50029 12.4113 2.5 12.0664V10.0342C2.5 9.68908 2.77992 9.4093 3.125 9.40918H5.15723C5.50229 9.40932 5.78223 9.68909 5.78223 10.0342C5.78207 10.3791 5.50219 10.659 5.15723 10.6592H4.69141C5.47041 11.6307 6.66264 12.2539 8.00195 12.2539C10.3503 12.2538 12.2539 10.3503 12.2539 8.00195C12.2539 7.65678 12.5337 7.37695 12.8789 7.37695Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M8.00195 2.5C9.71877 2.50008 11.2466 3.28982 12.2539 4.52051V3.93848C12.2539 3.5933 12.5337 3.31348 12.8789 3.31348C13.224 3.31356 13.5039 3.59335 13.5039 3.93848V5.9707C13.5036 6.31553 13.2238 6.59562 12.8789 6.5957H10.8467C10.5018 6.59558 10.222 6.31551 10.2217 5.9707C10.2217 5.6256 10.5016 5.34582 10.8467 5.3457H11.3135C10.5345 4.37354 9.34185 3.75008 8.00195 3.75C5.65376 3.75025 3.75106 5.65377 3.75098 8.00195C3.75087 8.34682 3.47079 8.6266 3.12598 8.62695C2.78087 8.62695 2.50109 8.34704 2.50098 8.00195C2.50106 4.9634 4.96342 2.50025 8.00195 2.5Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_refresh_16!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRefresh16Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_refresh_16,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_refresh_20: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_refresh_20: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_refresh_20 != null) return _ic_arrow_refresh_20!!
|
||||
_ic_arrow_refresh_20 = ImageVector.Builder(
|
||||
name = "ic_arrow_refresh_20",
|
||||
defaultWidth = 20.dp,
|
||||
defaultHeight = 20.dp,
|
||||
viewportWidth = 20f,
|
||||
viewportHeight = 20f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M15.75 9.25C16.1642 9.25 16.5 9.58579 16.5 10C16.5 13.5902 13.5901 16.4999 10 16.5C7.98553 16.5 6.19017 15.5813 5 14.1445V14.791C5 15.2051 4.66404 15.5408 4.25 15.541C3.83581 15.541 3.5 15.2052 3.5 14.791V12.3955C3.50015 11.9814 3.8359 11.6455 4.25 11.6455H6.64551C7.05963 11.6455 7.39536 11.9814 7.39551 12.3955C7.39551 12.8097 7.05972 13.1455 6.64551 13.1455H6.12305C7.0395 14.2763 8.43424 15 10 15C12.7617 14.9999 15 12.7617 15 10C15 9.58584 15.3359 9.25008 15.75 9.25Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M10 3.5C12.0145 3.50009 13.8099 4.41862 15 5.85547V5.20801C15.0001 4.79393 15.3359 4.45809 15.75 4.45801C16.1641 4.45801 16.4999 4.79388 16.5 5.20801V7.60352C16.5 8.01773 16.1642 8.35352 15.75 8.35352H13.3545C12.9403 8.35343 12.6045 8.01768 12.6045 7.60352C12.6048 7.18962 12.9405 6.8536 13.3545 6.85352H13.876C12.9595 5.72327 11.5652 5.00009 10 5C7.23838 5.00014 5.00007 7.23837 5 10C4.99989 10.414 4.664 10.7498 4.25 10.75C3.83591 10.7499 3.50011 10.4141 3.5 10C3.50007 6.40994 6.40996 3.50014 10 3.5Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_refresh_20!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRefresh20Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_refresh_20,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_refresh_24: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_refresh_24: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_refresh_24 != null) return _ic_arrow_refresh_24!!
|
||||
_ic_arrow_refresh_24 = ImageVector.Builder(
|
||||
name = "ic_arrow_refresh_24",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M20 11C20.5523 11 21 11.4477 21 12C21 16.971 16.971 21 12 21C9.16729 21 6.64691 19.6886 5 17.6455V18.666C5 19.2182 4.55214 19.6658 4 19.666C3.44783 19.6659 3 19.2182 3 18.666V15.333C3.0002 14.781 3.44795 14.3331 4 14.333H7.33301C7.88517 14.333 8.33281 14.7809 8.33301 15.333C8.33301 15.8853 7.88529 16.333 7.33301 16.333H6.51172C7.79335 17.9577 9.77446 19 12 19C15.8664 19 19 15.8664 19 12C19 11.4477 19.4477 11 20 11Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M12 3C14.8327 3.00005 17.3533 4.31128 19 6.35449V5.33301C19.0002 4.78089 19.4478 4.33301 20 4.33301C20.5522 4.33301 20.9998 4.78089 21 5.33301V8.66602C21 9.2183 20.5523 9.66602 20 9.66602H16.667C16.1147 9.66602 15.667 9.2183 15.667 8.66602C15.6674 8.11405 16.1149 7.66602 16.667 7.66602H17.4883C16.2066 6.04172 14.2252 5.00005 12 5C8.13373 5.00013 5 8.1337 5 12C5 12.5522 4.55221 12.9999 4 13C3.44783 12.9999 3 12.5522 3 12C3 7.02913 7.02916 3.00013 12 3Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_refresh_24!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRefresh24Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_refresh_24,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_refresh_32: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_refresh_32: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_refresh_32 != null) return _ic_arrow_refresh_32!!
|
||||
_ic_arrow_refresh_32 = ImageVector.Builder(
|
||||
name = "ic_arrow_refresh_32",
|
||||
defaultWidth = 32.dp,
|
||||
defaultHeight = 32.dp,
|
||||
viewportWidth = 32f,
|
||||
viewportHeight = 32f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M26.751 14.75C27.4413 14.75 28.001 15.3096 28.001 16C28.001 22.628 22.6289 27.9999 16.001 28C12.1256 28 8.69027 26.1578 6.5 23.3105V24.959C6.49973 25.6491 5.94019 26.209 5.25 26.209C4.56008 26.2087 4.00027 25.6489 4 24.959V20.4795C4 19.7893 4.55991 19.2298 5.25 19.2295H9.72949C10.4198 19.2295 10.9795 19.7891 10.9795 20.4795C10.9794 21.1697 10.4198 21.7295 9.72949 21.7295H8.43555C10.1698 24.0207 12.9119 25.5 16.001 25.5C21.2482 25.4999 25.501 21.2473 25.501 16C25.501 15.3096 26.0606 14.75 26.751 14.75Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M16.001 4C19.8754 4.00001 23.3106 5.84055 25.501 8.68652V7.04102C25.5012 6.35088 26.0608 5.79102 26.751 5.79102C27.4412 5.79102 28.0007 6.35088 28.001 7.04102V11.5205C28.0008 12.2107 27.4412 12.7705 26.751 12.7705H22.2715C21.5813 12.7704 21.0216 12.2106 21.0215 11.5205C21.0217 10.8305 21.5814 10.2707 22.2715 10.2705H23.5664C21.8322 7.97924 19.0901 6.50001 16.001 6.5C10.7538 6.5001 6.50106 10.7528 6.50098 16C6.50088 16.6903 5.94126 17.25 5.25098 17.25C4.56071 17.25 4.00108 16.6902 4.00098 16C4.00106 9.37211 9.3731 4.0001 16.001 4Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_refresh_32!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRefresh32Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_refresh_32,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_right_12: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_right_12: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_right_12 != null) return _ic_arrow_right_12!!
|
||||
_ic_arrow_right_12 = ImageVector.Builder(
|
||||
name = "ic_arrow_right_12",
|
||||
defaultWidth = 12.dp,
|
||||
defaultHeight = 12.dp,
|
||||
viewportWidth = 12f,
|
||||
viewportHeight = 12f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M6.64418 2.14284C6.83946 1.9481 7.15611 1.94781 7.35121 2.14284L10.8522 5.64381C11.0469 5.83894 11.0468 6.15566 10.8522 6.35084L7.35121 9.85182C7.15605 10.0468 6.8394 10.0467 6.64418 9.85182C6.44912 9.65662 6.44917 9.34002 6.64418 9.14479L9.29164 6.49733H1.50063C1.22468 6.49733 1.00095 6.27319 1.00063 5.99733C1.00093 5.72145 1.22467 5.49733 1.50063 5.49733H9.29164L6.64418 2.84987C6.4491 2.65471 6.44928 2.3381 6.64418 2.14284Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_right_12!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRight12Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_right_12,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_right_16: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_right_16: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_right_16 != null) return _ic_arrow_right_16!!
|
||||
_ic_arrow_right_16 = ImageVector.Builder(
|
||||
name = "ic_arrow_right_16",
|
||||
defaultWidth = 16.dp,
|
||||
defaultHeight = 16.dp,
|
||||
viewportWidth = 16f,
|
||||
viewportHeight = 16f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M8.74725 3.18508C8.99129 2.94166 9.38713 2.94146 9.63104 3.18508L14.0041 7.55813C14.2476 7.80203 14.2474 8.19791 14.0041 8.44192L9.63104 12.815C9.38704 13.0585 8.9912 13.0586 8.74725 12.815C8.50336 12.571 8.50343 12.1743 8.74725 11.9302L12.0529 8.62453H2.50018C2.15518 8.62443 1.87532 8.34453 1.87518 7.99953C1.87567 7.65483 2.15539 7.37464 2.50018 7.37453H12.0519L8.74725 4.06887C8.50352 3.82484 8.50348 3.42909 8.74725 3.18508Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_right_16!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRight16Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_right_16,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_right_20: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_right_20: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_right_20 != null) return _ic_arrow_right_20!!
|
||||
_ic_arrow_right_20 = ImageVector.Builder(
|
||||
name = "ic_arrow_right_20",
|
||||
defaultWidth = 20.dp,
|
||||
defaultHeight = 20.dp,
|
||||
viewportWidth = 20f,
|
||||
viewportHeight = 20f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M10.4874 3.21724C10.7802 2.92513 11.2553 2.925 11.548 3.21724L17.799 9.46822C18.0916 9.76094 18.0913 10.2368 17.799 10.5297L11.548 16.7807C11.2551 17.0735 10.7803 17.0734 10.4874 16.7807C10.195 16.4878 10.1947 16.0129 10.4874 15.7202L15.4581 10.7485H2.75012C2.336 10.7484 2.00012 10.4126 2.00012 9.99849C2.00087 9.58499 2.33647 9.24861 2.75012 9.24849H15.4572L10.4874 4.27779C10.195 3.98486 10.1947 3.50999 10.4874 3.21724Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_right_20!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRight20Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_right_20,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_right_24: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_right_24: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_right_24 != null) return _ic_arrow_right_24!!
|
||||
_ic_arrow_right_24 = ImageVector.Builder(
|
||||
name = "ic_arrow_right_24",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 24f,
|
||||
viewportHeight = 24f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M13.2929 4.29263C13.6833 3.9029 14.3166 3.90283 14.7069 4.29263L21.706 11.2916C21.8932 11.4789 21.9987 11.7339 21.9989 11.9987C21.9989 12.2636 21.8931 12.5183 21.706 12.7057L14.7069 19.7047C14.3165 20.0951 13.6834 20.0951 13.2929 19.7047C12.9029 19.3142 12.9027 18.681 13.2929 18.2907L18.5849 12.9987H2.99991C2.44775 12.9986 1.99994 12.5509 1.99991 11.9987C2.00043 11.4469 2.44805 10.9988 2.99991 10.9987H18.5849L13.2929 5.70669C12.9029 5.31616 12.9027 4.68299 13.2929 4.29263Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_right_24!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRight24Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_right_24,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
@file:Suppress("all")
|
||||
|
||||
package com.tangem.core.ui.res.generated.icons
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.addPathNodes
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Auto-generated from design tokens. Do not edit manually.
|
||||
*/
|
||||
|
||||
private var _ic_arrow_right_28: ImageVector? = null
|
||||
|
||||
val Icons.ic_arrow_right_28: ImageVector
|
||||
get() {
|
||||
if (_ic_arrow_right_28 != null) return _ic_arrow_right_28!!
|
||||
_ic_arrow_right_28 = ImageVector.Builder(
|
||||
name = "ic_arrow_right_28",
|
||||
defaultWidth = 28.dp,
|
||||
defaultHeight = 28.dp,
|
||||
viewportWidth = 28f,
|
||||
viewportHeight = 28f,
|
||||
).apply {
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M14.9832 4.36926C15.4679 3.87819 16.2593 3.87231 16.7508 4.35657L25.6278 13.1075C25.8658 13.3423 25.9997 13.6638 25.9998 13.9982C25.9997 14.3322 25.8655 14.6531 25.6278 14.8878L16.7508 23.6398C16.2593 24.1238 15.4678 24.1182 14.9832 23.6271C14.499 23.1356 14.5048 22.3431 14.9959 21.8585L21.701 15.2482H3.24985C2.5599 15.2479 2.00017 14.6881 1.99985 13.9982C2.00016 13.3082 2.5599 12.7484 3.24985 12.7482H21.702L14.9959 6.13684C14.5048 5.6523 14.4991 4.86082 14.9832 4.36926Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_right_28!!
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
private fun IcArrowRight28Preview() {
|
||||
Icon(
|
||||
imageVector = Icons.ic_arrow_right_28,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -31,12 +31,12 @@ val Icons.ic_arrow_swap_horizontal_12: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M10.5 5.5C10.7761 5.5 11 5.72386 11 6C10.9999 8.18874 9.37753 9.75 7.25 9.75H2.61035C2.70332 9.82714 2.78927 9.90003 2.8623 9.95703C2.91927 10.0015 2.98043 10.0639 3.0459 10.0967C3.2682 10.2604 3.31608 10.5745 3.15234 10.7969C3.00907 10.991 2.7519 11.0514 2.54102 10.9541L2.45312 10.9023L2.39453 10.8584C2.28672 10.777 2.04429 10.5908 1.79688 10.376C1.63446 10.235 1.45893 10.0719 1.32031 9.91504C1.25161 9.83725 1.18177 9.74877 1.12598 9.65625C1.07837 9.57728 1.00003 9.43037 1 9.25C1.00001 8.78513 1.48359 8.39606 1.79688 8.12402C2.04428 7.90921 2.28669 7.72306 2.39453 7.6416L2.45312 7.59765C2.67535 7.43398 2.98852 7.48108 3.15234 7.70312C3.31603 7.92547 3.26822 8.23958 3.0459 8.40332C2.98043 8.43605 2.91927 8.49851 2.8623 8.54297C2.78925 8.59998 2.70334 8.67284 2.61035 8.75H7.25C8.83566 8.75 9.99994 7.6261 10 6C10 5.72386 10.2239 5.5 10.5 5.5Z"),
|
||||
pathData = addPathNodes("M10 5.50004C10.2761 5.50009 10.5 5.72393 10.5 6.00004C10.4999 7.97667 9.03239 9.38859 7.11133 9.38871H3.14258C3.17968 9.41851 3.2133 9.44786 3.24512 9.47269C3.29449 9.51123 3.33569 9.54291 3.36426 9.56449C3.37832 9.57511 3.39012 9.58343 3.39746 9.5889C3.40102 9.59155 3.40356 9.59441 3.40527 9.59574L3.40723 9.59671C3.62953 9.76043 3.67729 10.0736 3.51367 10.2959C3.37035 10.4905 3.1125 10.5518 2.90137 10.4541L2.81445 10.4024L2.81348 10.4014C2.81281 10.4009 2.81152 10.4002 2.81055 10.3994C2.80809 10.3976 2.80409 10.3948 2.7998 10.3916C2.79087 10.385 2.77772 10.3744 2.76172 10.3623C2.72962 10.3381 2.68391 10.3039 2.62988 10.2618C2.52156 10.1772 2.37507 10.0598 2.22754 9.93168C2.08245 9.80569 1.92445 9.65881 1.79883 9.51664C1.73656 9.44615 1.67118 9.36467 1.61914 9.27836C1.58631 9.22384 1.53602 9.13061 1.5127 9.01371L1.5 8.88871L1.5127 8.76371C1.53605 8.64638 1.58629 8.55258 1.61914 8.49808C1.67121 8.41177 1.73656 8.33027 1.79883 8.2598C1.9244 8.11773 2.08255 7.97163 2.22754 7.84574C2.37514 7.71758 2.52154 7.60021 2.62988 7.51566C2.68422 7.47326 2.72956 7.43839 2.76172 7.4141C2.77779 7.40196 2.79087 7.39244 2.7998 7.38578C2.80426 7.38246 2.80807 7.3798 2.81055 7.37796C2.81169 7.37712 2.8128 7.37651 2.81348 7.37601L2.81445 7.37504C3.03666 7.21141 3.34984 7.25852 3.51367 7.4805C3.67742 7.70284 3.62955 8.01596 3.40723 8.17972C3.40688 8.17998 3.40612 8.18104 3.40527 8.18168C3.40356 8.18295 3.40093 8.18495 3.39746 8.18754C3.39013 8.193 3.37852 8.20117 3.36426 8.21195C3.33571 8.23351 3.29464 8.2651 3.24512 8.30375C3.21302 8.32879 3.1791 8.35858 3.1416 8.38871H7.11133C8.49051 8.38859 9.49991 7.41403 9.5 6.00004C9.5 5.72389 9.72386 5.50004 10 5.50004Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M8.84766 1.20312C9.01149 0.981078 9.32465 0.933983 9.54688 1.09765L9.60547 1.1416C9.71331 1.22306 9.95572 1.40921 10.2031 1.62402C10.3655 1.76505 10.5411 1.92809 10.6797 2.08496C10.7484 2.16275 10.8182 2.25123 10.874 2.34375C10.9216 2.42275 11 2.56965 11 2.75C10.9999 3.2149 10.5165 3.6039 10.2031 3.87597C9.95571 4.09078 9.71328 4.27696 9.60547 4.3584L9.54688 4.40234C9.32468 4.56593 9.01149 4.51882 8.84766 4.29687C8.68392 4.07454 8.7318 3.76044 8.9541 3.59668C9.01957 3.56394 9.08073 3.50148 9.1377 3.45703C9.21073 3.40003 9.29668 3.32714 9.38965 3.25H4.75C3.1643 3.25 2 4.37383 2 6C1.99993 6.27608 1.7761 6.5 1.5 6.5C1.2239 6.5 1.00007 6.27608 1 6C1 3.8112 2.62242 2.25 4.75 2.25H9.38965C9.29666 2.17284 9.21075 2.09998 9.1377 2.04297C9.08073 1.99851 9.01957 1.93605 8.9541 1.90332C8.73178 1.73958 8.68397 1.42547 8.84766 1.20312Z"),
|
||||
pathData = addPathNodes("M8.48633 1.70316C8.65014 1.48113 8.96332 1.43406 9.18555 1.59769L9.18652 1.59867C9.18722 1.59918 9.1883 1.59977 9.18945 1.60062C9.19193 1.60245 9.19575 1.60512 9.2002 1.60843C9.20906 1.61504 9.22149 1.62481 9.2373 1.63675C9.26947 1.66105 9.3157 1.69585 9.37012 1.73832C9.47844 1.82286 9.62492 1.94029 9.77246 2.06839C9.91746 2.19431 10.0756 2.34038 10.2012 2.48246C10.2634 2.55287 10.3278 2.63451 10.3799 2.72074C10.4237 2.79342 10.5 2.93552 10.5 3.11136C10.4999 3.28706 10.4237 3.42837 10.3799 3.50101C10.3279 3.58726 10.2634 3.66886 10.2012 3.73929C10.0756 3.88145 9.91753 4.02838 9.77246 4.15433C9.62489 4.28246 9.47844 4.39988 9.37012 4.48441C9.31577 4.52682 9.26943 4.56073 9.2373 4.585C9.22131 4.59709 9.2091 4.60766 9.2002 4.61429C9.1958 4.61756 9.1919 4.62029 9.18945 4.62211C9.18837 4.62289 9.18717 4.62358 9.18652 4.62406L9.18555 4.62504C8.96322 4.78865 8.65006 4.74085 8.48633 4.51859C8.32262 4.29629 8.37055 3.98315 8.59277 3.81937L8.59473 3.81839C8.59643 3.81713 8.59905 3.81418 8.60254 3.81156C8.60985 3.80611 8.62078 3.79771 8.63477 3.78714C8.66333 3.76557 8.70522 3.7341 8.75488 3.69535C8.78665 3.67055 8.82036 3.64113 8.85742 3.61136H4.88867C3.50948 3.61148 2.50007 4.58602 2.5 6.00004C2.49982 6.27602 2.27603 6.50004 2 6.50004C1.72402 6.49998 1.50018 6.27599 1.5 6.00004C1.50007 4.02338 2.9676 2.61148 4.88867 2.61136H8.8584C8.82091 2.58124 8.78698 2.55145 8.75488 2.5264C8.70518 2.48761 8.66335 2.45619 8.63477 2.43461C8.6207 2.42398 8.60982 2.41562 8.60254 2.41019C8.59916 2.40767 8.59645 2.40561 8.59473 2.40433C8.59389 2.4037 8.59312 2.40263 8.59277 2.40238C8.37051 2.23864 8.3227 1.92548 8.48633 1.70316Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_swap_horizontal_12!!
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ val Icons.ic_arrow_swap_horizontal_16: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M14.5038 7.50422C14.7798 7.50444 15.0038 7.72822 15.0038 8.00422C15.0037 11.0405 12.7618 13.1984 9.80948 13.1986H2.51163C2.58365 13.2652 2.658 13.3346 2.73526 13.4017C3.01657 13.6459 3.3084 13.8774 3.60635 14.1009C3.82835 14.2646 3.87617 14.5779 3.7128 14.8001C3.5517 15.0188 3.22177 15.0713 3.00772 14.9017C2.68924 14.6658 2.37925 14.4174 2.07999 14.1575C1.84813 13.9562 1.60275 13.7279 1.41202 13.512C1.31737 13.4049 1.22518 13.2888 1.15421 13.1712C1.10709 13.0931 1.04672 12.9776 1.01944 12.8411L1.00479 12.6986L1.01944 12.555C1.04678 12.4189 1.10716 12.3039 1.15421 12.2259C1.22525 12.1081 1.31723 11.9914 1.41202 11.8841C1.60273 11.6683 1.84818 11.4408 2.07999 11.2396C2.37597 10.9826 2.73594 10.7672 3.0126 10.4905C3.23486 10.3271 3.54905 10.3739 3.7128 10.596C4.12667 11.1585 3.02979 11.7387 2.73526 11.9945C2.65746 12.062 2.58217 12.1314 2.50967 12.1986H9.80948C12.2199 12.1984 14.0037 10.4779 14.0038 8.00422C14.0038 7.72809 14.2277 7.50422 14.5038 7.50422Z"),
|
||||
pathData = addPathNodes("M12.8707 7.37361C13.2157 7.37372 13.4956 7.65363 13.4957 7.99861C13.4957 10.4151 11.7006 12.1421 9.35217 12.1422H4.56311C4.59562 12.168 4.62521 12.194 4.65393 12.2164C4.71423 12.2634 4.76473 12.3015 4.79944 12.3277C4.81669 12.3407 4.83058 12.3513 4.83948 12.358C4.84361 12.3611 4.84721 12.3633 4.84924 12.3648L4.8512 12.3668L4.94495 12.4517C5.13976 12.6675 5.16297 12.9976 4.98401 13.2408C4.77931 13.5186 4.38788 13.5782 4.10999 13.3736L4.10803 13.3717C4.10729 13.3711 4.10617 13.3705 4.1051 13.3697C4.10212 13.3675 4.09768 13.3639 4.09241 13.3599C4.08149 13.3518 4.0651 13.3396 4.04553 13.3248C4.00632 13.2952 3.95045 13.2533 3.8844 13.2017C3.75233 13.0987 3.57412 12.9556 3.39417 12.7994C3.21725 12.6458 3.02442 12.4665 2.87073 12.2926C2.79453 12.2063 2.71413 12.1059 2.65002 11.9996C2.60287 11.9213 2.52507 11.7772 2.50647 11.5963L2.50256 11.5172L2.50647 11.4371C2.52522 11.2561 2.60295 11.1119 2.65002 11.0338C2.71414 10.9275 2.79455 10.827 2.87073 10.7408C3.02432 10.567 3.21743 10.3884 3.39417 10.2349C3.57398 10.0788 3.75239 9.93562 3.8844 9.83259C3.95053 9.78099 4.00634 9.73817 4.04553 9.70857C4.06509 9.6938 4.08153 9.68152 4.09241 9.67341C4.0976 9.66956 4.10217 9.66679 4.1051 9.66462C4.10634 9.66365 4.10722 9.66229 4.10803 9.6617L4.10999 9.66072C4.38775 9.45629 4.77926 9.51508 4.98401 9.79255C5.18865 10.0704 5.12896 10.4618 4.8512 10.6666L4.84924 10.6685C4.84721 10.67 4.84377 10.6731 4.83948 10.6763C4.83064 10.6829 4.81662 10.6927 4.79944 10.7056C4.76471 10.7319 4.71434 10.7708 4.65393 10.8179C4.62525 10.8403 4.59558 10.8664 4.56311 10.8922H9.35217C11.0233 10.8921 12.2457 9.71179 12.2457 7.99861C12.2459 7.65357 12.5256 7.37361 12.8707 7.37361Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M12.2958 1.20735C12.4792 0.958959 12.7528 0.98075 12.997 1.10285C13.3075 1.35229 13.6272 1.58922 13.9286 1.8509C14.1605 2.05225 14.4059 2.27956 14.5966 2.49543C14.6914 2.60277 14.7833 2.71937 14.8544 2.83723C14.9172 2.94141 15.0038 3.11122 15.0038 3.30988C15.0038 3.50848 14.9172 3.67838 14.8544 3.78254C14.7834 3.90019 14.6912 4.01619 14.5966 4.12336C14.4059 4.33923 14.1605 4.56752 13.9286 4.76887C13.6944 4.97226 13.4614 5.15895 13.288 5.29426C13.1919 5.36928 13.0916 5.44026 12.997 5.51692C12.7247 5.51692 12.5298 5.72903 12.2958 5.41145C12.1321 5.18912 12.179 4.87598 12.4013 4.71223C12.7002 4.48979 12.9919 4.25737 13.2733 4.01301C13.3507 3.94586 13.4249 3.87662 13.497 3.80988H6.19913C3.78848 3.80988 2.00479 5.53034 2.00479 8.00422C2.0047 8.28016 1.7807 8.50401 1.50479 8.50422C1.2287 8.50422 1.00488 8.28029 1.00479 8.00422C1.00479 4.96775 3.24656 2.80988 6.19913 2.80988H13.4989C13.4264 2.74268 13.3512 2.67341 13.2733 2.60578C13.0565 2.41748 12.8378 2.24241 12.6728 2.1136C12.586 2.04586 12.4794 1.98464 12.4013 1.90656C12.1792 1.74273 12.1321 1.42957 12.2958 1.20735Z"),
|
||||
pathData = addPathNodes("M11.0143 2.75642C11.2191 2.47888 11.6105 2.42002 11.8883 2.62459L11.8893 2.62556C11.8902 2.62621 11.8917 2.62737 11.8932 2.62849C11.8962 2.6307 11.9006 2.63338 11.9059 2.63728C11.9168 2.6454 11.9322 2.65766 11.9518 2.67244C11.991 2.70207 12.0465 2.74467 12.1129 2.79646C12.245 2.89953 12.4231 3.0425 12.6031 3.1988C12.7801 3.35243 12.9738 3.53069 13.1276 3.70466C13.2038 3.79095 13.2832 3.89124 13.3473 3.99763C13.4011 4.08687 13.4957 4.26241 13.4957 4.48103C13.4956 4.69928 13.4011 4.8742 13.3473 4.96345C13.2832 5.06976 13.2037 5.1702 13.1276 5.25642C12.9738 5.43038 12.7801 5.60965 12.6031 5.76326C12.4233 5.91943 12.2449 6.06261 12.1129 6.1656C12.0467 6.21724 11.9909 6.25907 11.9518 6.28865C11.9322 6.30344 11.9168 6.31569 11.9059 6.3238C11.9005 6.32782 11.8962 6.33134 11.8932 6.33357C11.8918 6.33457 11.8901 6.33491 11.8893 6.33552L11.8883 6.3365C11.6105 6.54102 11.219 6.48233 11.0143 6.20466C10.8096 5.92676 10.8693 5.53535 11.1471 5.33064C11.1475 5.33034 11.1481 5.32943 11.149 5.32869C11.1511 5.32719 11.1546 5.32501 11.1588 5.32185C11.1677 5.31523 11.1815 5.30465 11.1989 5.29158C11.2336 5.26535 11.284 5.22733 11.3444 5.18025C11.3729 5.15799 11.402 5.13172 11.4342 5.10603H6.64612C4.97486 5.10603 3.75256 6.28631 3.75256 7.99959C3.75217 8.34442 3.47248 8.62456 3.12756 8.62459C2.78263 8.62459 2.50295 8.34443 2.50256 7.99959C2.50256 5.583 4.29754 3.85603 6.64612 3.85603H11.4352C11.4027 3.83019 11.3731 3.80424 11.3444 3.78181C11.2839 3.73462 11.2336 3.69576 11.1989 3.66951C11.1815 3.65643 11.1677 3.64685 11.1588 3.64021C11.1544 3.6369 11.1511 3.63394 11.149 3.6324L11.1471 3.63142C10.8692 3.42675 10.8097 3.03434 11.0143 2.75642Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_swap_horizontal_16!!
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ val Icons.ic_arrow_swap_horizontal_20: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M17.2486 9.25041C17.6626 9.25059 17.9986 9.58634 17.9986 10.0004C17.9984 13.4943 15.4106 15.9844 12.0142 15.9848H4.38239C4.64562 16.2075 4.91445 16.4234 5.19098 16.6293C5.5247 16.878 5.62195 17.3488 5.36871 17.6928C5.13841 18.0054 4.71159 18.0873 4.38434 17.894L4.31989 17.852C4.00747 17.5396 3.60039 17.2964 3.26617 17.0063C3.00544 16.7799 2.72524 16.5198 2.50446 16.2699C2.39484 16.1459 2.28339 16.0059 2.19586 15.8608C2.13005 15.7516 2.03019 15.5645 2.00641 15.3354L2.00153 15.2348L2.00641 15.1342C2.03017 14.9049 2.13004 14.718 2.19586 14.6088C2.28347 14.4635 2.39475 14.3238 2.50446 14.1996C2.72541 13.9496 3.00522 13.6889 3.26617 13.4623C3.59999 13.1725 4.00686 12.9296 4.31891 12.6176C4.65232 12.3721 5.12302 12.4435 5.36871 12.7768C5.61412 13.1102 5.54289 13.579 5.20953 13.8246C4.96546 14.0687 4.64515 14.2617 4.38141 14.4848H12.0142C14.5978 14.4844 16.4984 12.6503 16.4986 10.0004C16.4986 9.58623 16.8344 9.25041 17.2486 9.25041Z"),
|
||||
pathData = addPathNodes("M16.7457 9.24631C17.1599 9.24631 17.4957 9.5821 17.4957 9.99631C17.4955 13.2795 15.062 15.6213 11.8707 15.6213H4.90485C5.04583 15.7382 5.17904 15.8456 5.28961 15.9319C5.37369 15.9975 5.44327 16.0515 5.49176 16.0881C5.51566 16.1062 5.53492 16.1198 5.54742 16.1291C5.55362 16.1337 5.55912 16.1377 5.56207 16.1399C5.56327 16.1407 5.56442 16.1414 5.565 16.1418L5.56598 16.1428C5.89901 16.3885 5.96968 16.8583 5.72418 17.1916C5.47861 17.5246 5.00968 17.5958 4.67633 17.3508L4.67535 17.3498L4.6734 17.3489C4.67228 17.348 4.67039 17.3463 4.66852 17.3449C4.66451 17.342 4.65894 17.3375 4.65192 17.3322C4.63702 17.3212 4.61532 17.3047 4.58844 17.2844C4.53459 17.2437 4.45795 17.1856 4.36676 17.1145C4.18473 16.9724 3.93841 16.7756 3.69098 16.5608C3.44733 16.3492 3.18409 16.1047 2.97614 15.8694C2.873 15.7526 2.76789 15.6196 2.68414 15.4807C2.61272 15.3622 2.4957 15.1417 2.49567 14.8713C2.49571 14.6008 2.61273 14.3804 2.68414 14.2619C2.76794 14.1229 2.87292 13.9901 2.97614 13.8733C3.18415 13.6378 3.44725 13.3935 3.69098 13.1819C3.9384 12.967 4.18374 12.7702 4.36578 12.6281C4.45719 12.5568 4.53449 12.499 4.58844 12.4582C4.61519 12.438 4.63606 12.4215 4.65094 12.4104C4.65824 12.4049 4.66442 12.4007 4.66852 12.3977C4.67051 12.3962 4.67224 12.3946 4.6734 12.3938L4.67535 12.3928C5.00886 12.1473 5.47858 12.2185 5.72418 12.552C5.96945 12.8853 5.89892 13.3541 5.56598 13.5998L5.565 13.6008C5.56442 13.6012 5.56335 13.6018 5.56207 13.6028C5.5591 13.605 5.55367 13.6088 5.54742 13.6135C5.5349 13.6228 5.51578 13.6373 5.49176 13.6555C5.44329 13.6921 5.37349 13.7453 5.28961 13.8108C5.17903 13.8971 5.04588 14.0043 4.90485 14.1213H11.8707C14.2491 14.1213 15.9955 12.4356 15.9957 9.99631C15.9957 9.58211 16.3315 9.24633 16.7457 9.24631Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M14.6314 2.30705C14.931 1.90064 15.293 2.0191 15.6822 2.14885C16.0261 2.43744 16.3931 2.69754 16.733 2.9926C16.994 3.2192 17.2747 3.47982 17.4957 3.7299C17.6054 3.85404 17.7167 3.99383 17.8043 4.13908C17.8794 4.26384 17.9985 4.49026 17.9986 4.76506C17.9985 5.03992 17.8795 5.26625 17.8043 5.39103C17.7167 5.53626 17.6053 5.67609 17.4957 5.80021C17.2748 6.05019 16.9939 6.31001 16.733 6.53654C16.3358 6.88138 15.9458 7.18139 15.773 7.31193C15.743 7.33459 15.706 7.35553 15.6793 7.38225C15.3458 7.62751 14.877 7.55625 14.6314 7.22307C14.3776 6.87826 14.475 6.40861 14.8091 6.15959C15.0856 5.95362 15.3546 5.7379 15.6177 5.51506H7.9859C5.40232 5.51539 3.5018 7.34953 3.50153 9.99943C3.50153 10.4136 3.16574 10.7494 2.75153 10.7494C2.33741 10.7493 2.00153 10.4136 2.00153 9.99943C2.00181 6.50557 4.58949 4.0154 7.9859 4.01506H15.6177C15.306 3.75105 15.008 3.51895 14.8697 3.41447C14.8435 3.39469 14.8168 3.37557 14.7906 3.35588C14.4572 3.11024 14.3859 2.64052 14.6314 2.30705Z"),
|
||||
pathData = addPathNodes("M14.2672 2.801C14.5128 2.4677 14.9816 2.39643 15.315 2.64182L15.3179 2.6428C15.3191 2.64365 15.3208 2.64523 15.3228 2.6467C15.3269 2.64973 15.3331 2.65398 15.3404 2.6594C15.3553 2.67048 15.3762 2.68707 15.4029 2.70725C15.4568 2.74799 15.5342 2.80586 15.6255 2.87717C15.8076 3.01924 16.053 3.21607 16.3004 3.43088C16.544 3.64248 16.8072 3.88689 17.0152 4.12229C17.1184 4.23907 17.2234 4.37202 17.3072 4.51096C17.3786 4.62939 17.4956 4.84988 17.4957 5.12034C17.4957 5.39069 17.3786 5.61114 17.3072 5.72971C17.2235 5.86861 17.1183 6.00161 17.0152 6.11838C16.8072 6.35374 16.544 6.59821 16.3004 6.80979C16.053 7.02454 15.8076 7.22143 15.6255 7.3635C15.5342 7.4348 15.4568 7.49267 15.4029 7.53342C15.3762 7.55358 15.3553 7.57016 15.3404 7.58127C15.3332 7.58663 15.3269 7.59094 15.3228 7.59397C15.3209 7.59536 15.3191 7.597 15.3179 7.59787L15.316 7.59885L15.315 7.59983C14.9816 7.84497 14.5128 7.77369 14.2672 7.44065C14.0218 7.10728 14.0922 6.6375 14.4254 6.39182L14.4263 6.39084C14.4269 6.39041 14.4281 6.38978 14.4293 6.38889C14.4323 6.38667 14.4377 6.38276 14.4439 6.37815C14.4564 6.36881 14.4757 6.35519 14.4996 6.33713C14.5481 6.30049 14.6176 6.24651 14.7017 6.18088C14.8123 6.09455 14.9454 5.98732 15.0865 5.87034H8.12067C5.74237 5.87037 3.99599 7.55626 3.99567 9.99534C3.99567 10.4095 3.6598 10.7452 3.24567 10.7453C2.83145 10.7453 2.49567 10.4095 2.49567 9.99534C2.496 6.71236 4.92948 4.37037 8.12067 4.37034H15.0865C14.9458 4.25344 14.8132 4.14602 14.7027 4.05979C14.6187 3.9942 14.5481 3.94119 14.4996 3.90452C14.4756 3.88639 14.4564 3.87185 14.4439 3.86252C14.4377 3.85789 14.4322 3.85397 14.4293 3.85178C14.428 3.85087 14.4269 3.85024 14.4263 3.84983L14.4254 3.84885C14.0924 3.60311 14.0218 3.13429 14.2672 2.801Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_swap_horizontal_20!!
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ val Icons.ic_arrow_swap_horizontal_24: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M21 11.0045C21.5523 11.0045 22 11.4522 22 12.0045C22 16.3821 18.7551 19.5045 14.5 19.5045H5.21191C5.49969 19.743 5.79278 19.9745 6.0918 20.1988C6.53636 20.5262 6.6328 21.1526 6.30566 21.5972C5.96497 22.0598 5.32136 22.1191 4.87402 21.7857C4.43528 21.4587 4.00784 21.1151 3.59473 20.7564C3.26986 20.4744 2.91892 20.1493 2.6416 19.8355C2.50393 19.6797 2.36275 19.5024 2.25098 19.317C2.15573 19.1589 2 18.8651 2 18.5045C2.00003 18.1439 2.15574 17.85 2.25098 17.692C2.36275 17.5066 2.50394 17.3292 2.6416 17.1734C2.91892 16.8596 3.26988 16.5346 3.59473 16.2525C4.00784 15.8938 4.43528 15.5502 4.87402 15.2232C4.88489 15.2151 4.8976 15.2084 4.90723 15.1988C5.35185 14.8717 5.97725 14.9672 6.30469 15.4117C6.63219 15.8564 6.53744 16.4826 6.09277 16.8101L6.0918 16.8092C5.79136 17.0311 5.4995 17.2661 5.21191 17.5045H14.5C17.6714 17.5045 20 15.2568 20 12.0045C20 11.4522 20.4477 11.0045 21 11.0045Z"),
|
||||
pathData = addPathNodes("M20.4955 10.9976C21.0478 10.9976 21.4955 11.4453 21.4955 11.9976C21.4953 16.1623 18.4059 19.1351 14.3578 19.1353H5.74261C5.87496 19.243 5.99883 19.3418 6.1059 19.4253C6.21181 19.5079 6.29979 19.5755 6.36078 19.6216C6.39118 19.6446 6.4154 19.6617 6.43109 19.6734C6.43892 19.6792 6.44496 19.6843 6.44867 19.687L6.45258 19.69C6.89726 20.0173 6.99272 20.6437 6.66547 21.0884C6.35855 21.5052 5.78936 21.6145 5.35297 21.3569L5.26703 21.3003L5.2641 21.2984C5.26268 21.2973 5.26057 21.2962 5.25824 21.2944C5.25301 21.2906 5.24522 21.2849 5.23578 21.2778C5.21688 21.2638 5.18975 21.243 5.1557 21.2173C5.08762 21.1659 4.99152 21.0923 4.8764 21.0025C4.64664 20.8232 4.33651 20.5746 4.02386 20.3032C3.71627 20.0363 3.38294 19.7273 3.11859 19.4282C2.98742 19.2798 2.85158 19.1102 2.74359 18.9312C2.65214 18.7795 2.49847 18.4906 2.49847 18.1343C2.49865 17.7783 2.65219 17.49 2.74359 17.3384C2.85158 17.1594 2.98745 16.9897 3.11859 16.8413C3.38296 16.5423 3.71627 16.2323 4.02386 15.9653C4.33618 15.6943 4.64582 15.4462 4.87543 15.2671C4.99055 15.1773 5.0876 15.1037 5.1557 15.0523C5.18962 15.0266 5.21692 15.0058 5.23578 14.9917C5.24515 14.9847 5.25305 14.979 5.25824 14.9751C5.2605 14.9735 5.2627 14.9722 5.2641 14.9712L5.26605 14.9692C5.26892 14.973 5.30307 15.0175 5.6889 15.5415L5.26703 14.9683C5.71173 14.6411 6.33809 14.7366 6.66547 15.1812C6.99271 15.6258 6.89711 16.2522 6.45258 16.5796L6.44867 16.5825C6.44503 16.5852 6.43871 16.5896 6.43109 16.5952C6.41543 16.6069 6.39116 16.625 6.36078 16.648C6.29983 16.694 6.21167 16.7617 6.1059 16.8442C5.99844 16.9281 5.87356 17.027 5.74066 17.1353H14.3578C17.3221 17.1351 19.4953 15.0369 19.4955 11.9976C19.4955 11.4454 19.9434 10.9977 20.4955 10.9976Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M17.6953 2.41169C18.036 1.94929 18.6787 1.88996 19.126 2.22321C19.5647 2.55021 19.9922 2.89383 20.4053 3.25251C20.7301 3.53456 21.0811 3.85962 21.3584 4.17341C21.4961 4.32922 21.6373 4.50657 21.749 4.69196C21.8443 4.84998 22 5.14386 22 5.50446C22 5.86506 21.8443 6.15891 21.749 6.31696C21.6373 6.50237 21.4961 6.67971 21.3584 6.83552C21.0811 7.14932 20.7301 7.47436 20.4053 7.75642C20.0754 8.04283 19.7486 8.30528 19.5059 8.4947C19.3736 8.59794 19.212 8.69092 19.0928 8.81013C18.6481 9.13731 18.0218 9.04181 17.6943 8.59724C17.3672 8.15256 17.4627 7.52621 17.9072 7.1988C18.2219 7.01 18.5075 6.73711 18.7881 6.50446H9.5C6.32862 6.50446 4.00003 8.75217 4 12.0045C4 12.5567 3.55228 13.0045 3 13.0045C2.44772 13.0045 2 12.5567 2 12.0045C2.00004 7.6269 5.24487 4.50446 9.5 4.50446H18.7881C18.5017 4.26707 18.2111 4.0345 17.9121 3.81306C17.4729 3.48759 17.3691 2.85468 17.6953 2.41169Z"),
|
||||
pathData = addPathNodes("M17.3295 2.90577C17.6569 2.46114 18.2832 2.36568 18.728 2.69288L18.7309 2.69581C18.7323 2.69683 18.7345 2.69804 18.7368 2.69972C18.742 2.70359 18.7498 2.70934 18.7592 2.71632C18.7781 2.7304 18.8053 2.75119 18.8393 2.77687C18.9074 2.82832 19.0044 2.90184 19.1196 2.99171C19.3492 3.17086 19.6588 3.41888 19.9711 3.68995C20.2788 3.95699 20.613 4.26683 20.8774 4.56593C21.0085 4.71425 21.1435 4.88407 21.2514 5.063C21.3428 5.21463 21.4964 5.50286 21.4965 5.8589C21.4965 6.21521 21.3428 6.50414 21.2514 6.65577C21.1435 6.83475 21.0085 7.0045 20.8774 7.15284C20.613 7.45197 20.2788 7.7608 19.9711 8.02784C19.6586 8.29911 19.3493 8.54784 19.1196 8.72706C19.0043 8.81701 18.9074 8.89045 18.8393 8.94191C18.8052 8.96763 18.7781 8.98837 18.7592 9.00245C18.7498 9.00944 18.742 9.0152 18.7368 9.01905C18.7344 9.02077 18.7323 9.02193 18.7309 9.02296L18.7289 9.02491C18.2842 9.35232 17.657 9.25765 17.3295 8.813C17.0022 8.36825 17.0977 7.74198 17.5424 7.41456C17.5432 7.41398 17.5456 7.4129 17.5473 7.41163C17.551 7.40888 17.5562 7.40369 17.5639 7.39796C17.5796 7.38628 17.6038 7.36916 17.6342 7.3462C17.6952 7.30015 17.7832 7.23256 17.8891 7.14991C17.9966 7.06604 18.1214 6.96718 18.2543 6.8589H9.63617C6.67185 6.85903 4.49868 8.95717 4.49847 11.9966C4.49847 12.5488 4.0507 12.9965 3.49847 12.9966C2.94619 12.9966 2.49847 12.5489 2.49847 11.9966C2.49869 7.83185 5.58813 4.85903 9.63617 4.8589H18.2524C18.12 4.75115 17.9962 4.65239 17.8891 4.56886C17.7833 4.48628 17.6952 4.41862 17.6342 4.37257C17.6038 4.34963 17.5796 4.33153 17.5639 4.31984C17.5565 4.31433 17.551 4.30986 17.5473 4.30714C17.5457 4.30597 17.5442 4.30482 17.5434 4.30421C17.0987 3.97683 17.0023 3.3505 17.3295 2.90577Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_swap_horizontal_24!!
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ val Icons.ic_arrow_swap_horizontal_28: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M24.7474 12.7524C25.4377 12.7525 25.9974 13.3121 25.9974 14.0024C25.997 19.2619 22.0959 23.0158 16.9837 23.0161H6.04327C6.34685 23.2645 6.65476 23.5082 6.97003 23.7417C7.00663 23.7671 7.04005 23.7983 7.07452 23.8266C7.54368 24.2519 7.6241 24.9726 7.24054 25.4936C6.83126 26.049 6.04922 26.167 5.49347 25.7583C4.9236 25.4733 4.39161 24.9068 3.9212 24.4985C3.53236 24.161 3.11138 23.7696 2.77765 23.392C2.61218 23.2048 2.44089 22.9913 2.30499 22.7661C2.20416 22.5989 2.04466 22.3001 2.00616 21.9292L1.99738 21.7661L2.00616 21.603C2.04454 21.2318 2.2041 20.9334 2.30499 20.7661C2.44095 20.5406 2.61202 20.3265 2.77765 20.1391C3.11135 19.7616 3.53239 19.3712 3.9212 19.0337C4.42705 18.5946 4.95059 18.1738 5.48956 17.7758C6.02099 17.3469 6.84799 17.5056 7.24054 18.0385C7.6494 18.5943 7.53135 19.3773 6.97589 19.7866C6.65821 20.0211 6.34807 20.2662 6.0423 20.5161H16.9837C20.7412 20.5159 23.497 17.8553 23.4974 14.0024C23.4974 13.312 24.057 12.7524 24.7474 12.7524Z"),
|
||||
pathData = addPathNodes("M24.2441 12.7517C24.9345 12.7517 25.4941 13.3113 25.4941 14.0017C25.4939 19.0495 21.7491 22.6547 16.8438 22.655H6.5791C6.70293 22.7548 6.81889 22.8471 6.92188 22.9274C7.04931 23.0269 7.1552 23.1084 7.22852 23.1638C7.26498 23.1913 7.29372 23.2123 7.3125 23.2263C7.32174 23.2332 7.32859 23.2386 7.33301 23.2419C7.33517 23.2435 7.337 23.2451 7.33789 23.2458C7.89346 23.6552 8.01271 24.4381 7.60352 24.9938C7.21978 25.5147 6.50827 25.6517 5.96289 25.3298L5.85547 25.2585C5.85495 25.2581 5.85333 25.2572 5.85254 25.2565C5.85073 25.2552 5.84784 25.253 5.84473 25.2507C5.83846 25.246 5.82952 25.2395 5.81836 25.2311C5.79552 25.2141 5.76282 25.189 5.72168 25.1579C5.63937 25.0957 5.52211 25.0069 5.38281 24.8981C5.10561 24.6818 4.73184 24.382 4.35449 24.0544C3.983 23.7318 3.57848 23.3568 3.25781 22.9938C3.09888 22.8139 2.93379 22.6074 2.80176 22.3884C2.70422 22.2266 2.54669 21.933 2.50879 21.5661L2.5 21.405L2.50879 21.2438C2.54669 20.877 2.70422 20.5834 2.80176 20.4216C2.93376 20.2026 3.09891 19.996 3.25781 19.8161C3.57843 19.4532 3.98209 19.0781 4.35352 18.7556C4.73097 18.4278 5.10552 18.1283 5.38281 17.9118C5.52208 17.8031 5.63939 17.7132 5.72168 17.6511C5.76258 17.6202 5.79561 17.5958 5.81836 17.5788C5.82947 17.5705 5.83847 17.5639 5.84473 17.5593C5.84781 17.557 5.85075 17.5547 5.85254 17.5534L5.85547 17.5505C6.4113 17.1412 7.19414 17.2603 7.60352 17.8161C8.0128 18.3719 7.89351 19.1537 7.33789 19.5632C7.33701 19.5638 7.33513 19.5665 7.33301 19.5681C7.32859 19.5713 7.32167 19.5768 7.3125 19.5837C7.29372 19.5977 7.26496 19.6187 7.22852 19.6462C7.1552 19.7016 7.0493 19.7831 6.92188 19.8825C6.81891 19.9629 6.70289 20.0552 6.5791 20.155H16.8438C20.3935 20.1548 22.994 17.6438 22.9941 14.0017C22.9941 13.3114 23.5539 12.7518 24.2441 12.7517Z"),
|
||||
)
|
||||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M20.7542 2.51021C21.1635 1.95435 21.9464 1.83538 22.5023 2.24459C22.6445 2.38682 22.8384 2.49824 22.9964 2.62154C23.2868 2.84813 23.6785 3.16239 24.0735 3.50533C24.4625 3.84293 24.8843 4.23318 25.2181 4.6108C25.3836 4.79814 25.5538 5.01237 25.6898 5.23775C25.805 5.42891 25.9983 5.79119 25.9984 6.23775C25.9983 6.68425 25.805 7.04662 25.6898 7.23775C25.5539 7.46296 25.3835 7.67651 25.2181 7.86373C24.8843 8.24139 24.4625 8.63254 24.0735 8.97018C23.4801 9.48529 22.9005 9.93091 22.6429 10.1254L22.5062 10.228C21.9463 10.6178 21.168 10.5271 20.7542 9.96529C20.3449 9.40939 20.4649 8.62559 21.0208 8.21627C21.3376 7.98225 21.6468 7.73745 21.9515 7.48775H11.011C7.2533 7.48792 4.49754 10.1482 4.49738 14.0014C4.49728 14.6915 3.93745 15.2512 3.24738 15.2514C2.55708 15.2514 1.99747 14.6917 1.99738 14.0014C1.99754 8.74159 5.89861 4.98792 11.011 4.98775H21.9525C21.6489 4.73936 21.341 4.49564 21.0257 4.26217C20.4769 3.85506 20.3464 3.06423 20.7542 2.51021Z"),
|
||||
pathData = addPathNodes("M20.3916 3.00849C20.801 2.45311 21.584 2.33379 22.1396 2.74287L22.1406 2.74385C22.1413 2.7443 22.1427 2.74518 22.1436 2.7458C22.1453 2.74714 22.1475 2.74949 22.1504 2.75166C22.1567 2.75634 22.1664 2.76274 22.1777 2.77119C22.2006 2.7882 22.2335 2.81251 22.2744 2.84345C22.3567 2.90564 22.473 2.99549 22.6123 3.1042C22.8896 3.32065 23.2641 3.62011 23.6416 3.94795C24.0131 4.27054 24.4167 4.64555 24.7373 5.00849C24.8962 5.18841 25.0613 5.39498 25.1934 5.61396C25.3048 5.7988 25.496 6.15564 25.4961 6.59736C25.4961 7.03887 25.3048 7.39576 25.1934 7.58076C25.0614 7.79961 24.8962 8.00639 24.7373 8.18623C24.4167 8.54907 24.013 8.92424 23.6416 9.24678C23.2642 9.57449 22.8896 9.8741 22.6123 10.0905C22.4734 10.199 22.3567 10.2881 22.2744 10.3503C22.2335 10.3812 22.2006 10.4065 22.1777 10.4235C22.1666 10.4319 22.1567 10.4384 22.1504 10.4431C22.1476 10.4451 22.1452 10.4476 22.1436 10.4489L22.1406 10.4509C21.5848 10.8603 20.801 10.7421 20.3916 10.1862C19.9823 9.63039 20.1014 8.84757 20.6572 8.43818C20.658 8.43737 20.6604 8.43556 20.6621 8.43428C20.6666 8.43097 20.6745 8.42544 20.6836 8.41865C20.7024 8.4046 20.7314 8.38351 20.7676 8.35615C20.8409 8.30073 20.9469 8.21918 21.0742 8.11982C21.1771 8.03952 21.2924 7.94701 21.416 7.84736H11.1504C7.60071 7.84759 5.00029 10.3587 5 14.0007C5 14.6909 4.44024 15.2505 3.75 15.2507C3.05964 15.2507 2.5 14.691 2.5 14.0007C2.5003 8.95295 6.24511 5.34759 11.1504 5.34736H21.416C21.2924 5.24771 21.1771 5.15519 21.0742 5.0749C20.9468 4.97544 20.8409 4.894 20.7676 4.83857C20.7312 4.81111 20.7024 4.79012 20.6836 4.77607C20.6743 4.76913 20.6666 4.76374 20.6621 4.76045C20.6602 4.75903 20.6591 4.75727 20.6582 4.75654C20.1025 4.34718 19.9824 3.56432 20.3916 3.00849Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_swap_horizontal_28!!
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ val Icons.ic_arrow_up_12: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M5.5 10.25C5.5 10.5261 5.72386 10.75 6 10.75C6.27614 10.75 6.5 10.5261 6.5 10.25V3.20703L9.64648 6.35352C9.84175 6.54878 10.1583 6.54878 10.3535 6.35352C10.5488 6.15825 10.5488 5.84175 10.3535 5.64648L6.35352 1.64648C6.15825 1.45122 5.84175 1.45122 5.64648 1.64648L1.64648 5.64648C1.45122 5.84175 1.45122 6.15825 1.64648 6.35352C1.84175 6.54878 2.15825 6.54878 2.35352 6.35352L5.5 3.20703V10.25Z"),
|
||||
pathData = addPathNodes("M2.14681 5.35641C1.95207 5.16113 1.95178 4.84448 2.14681 4.64938L5.64779 1.14841C5.84291 0.953653 6.15963 0.953757 6.35482 1.14841L9.8558 4.64938C10.0507 4.84455 10.0506 5.16119 9.8558 5.35641C9.66059 5.55147 9.344 5.55142 9.14876 5.35641L6.5013 2.70895V10.5C6.5013 10.7759 6.27717 10.9996 6.0013 11C5.72542 10.9997 5.5013 10.7759 5.5013 10.5L5.5013 2.70895L2.85384 5.35641C2.65869 5.55149 2.34208 5.55131 2.14681 5.35641Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_up_12!!
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ val Icons.ic_arrow_up_16: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M7.49966 13.6667C7.49966 13.9427 7.72367 14.1665 7.99966 14.1667C8.27581 14.1667 8.49966 13.9428 8.49966 13.6667V3.87372L12.9801 8.35321C13.1754 8.54847 13.4919 8.54847 13.6872 8.35321C13.8821 8.15792 13.8823 7.84133 13.6872 7.64618L8.35318 2.31317C8.1579 2.11807 7.84136 2.11796 7.64615 2.31317L2.31314 7.64618C2.11793 7.84139 2.11804 8.15793 2.31314 8.35321C2.5084 8.54847 2.82491 8.54847 3.02017 8.35321L7.49966 3.87372V13.6667Z"),
|
||||
pathData = addPathNodes("M3.1851 7.25277C2.94168 7.00873 2.94149 6.61289 3.1851 6.36898L7.55815 1.99593C7.80205 1.75241 8.19793 1.75258 8.44194 1.99593L12.815 6.36898C13.0585 6.61298 13.0586 7.00881 12.815 7.25277C12.571 7.49666 12.1743 7.49659 11.9302 7.25277L8.62456 3.9471V13.4998C8.62445 13.8448 8.34455 14.1247 7.99956 14.1248C7.65486 14.1244 7.37466 13.8446 7.37456 13.4998V3.94808L4.06889 7.25277C3.82486 7.4965 3.42911 7.49654 3.1851 7.25277Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_up_16!!
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ val Icons.ic_arrow_up_20: ImageVector
|
|||
addPath(
|
||||
fill = SolidColor(Color.Black),
|
||||
pathFillType = PathFillType.NonZero,
|
||||
pathData = addPathNodes("M9.25034 17.0833C9.25034 17.4975 9.58612 17.8333 10.0003 17.8333C10.4144 17.8331 10.7503 17.4974 10.7503 17.0833V5.14484L16.1361 10.5306C16.4289 10.8234 16.9037 10.8231 17.1966 10.5306C17.4895 10.2377 17.4895 9.76293 17.1966 9.47003L10.5306 2.80304C10.2378 2.5102 9.76297 2.51031 9.47006 2.80304L2.80307 9.47003C2.51034 9.76294 2.51023 10.2377 2.80307 10.5306C3.09592 10.8233 3.57076 10.8233 3.86362 10.5306L9.25034 5.14386V17.0833Z"),
|
||||
pathData = addPathNodes("M3.21926 9.51272C2.92715 9.21992 2.92701 8.74489 3.21926 8.45217L9.47023 2.2012C9.76296 1.90852 10.2388 1.90884 10.5318 2.2012L16.7827 8.45217C17.0756 8.745 17.0754 9.21981 16.7827 9.51272C16.4898 9.80518 16.0149 9.80547 15.7222 9.51272L10.7505 4.54202L10.7505 17.25C10.7504 17.6641 10.4147 18 10.0005 18C9.587 17.9993 9.25062 17.6637 9.25051 17.25L9.25051 4.54299L4.2798 9.51272C3.98687 9.80518 3.512 9.80547 3.21926 9.51272Z"),
|
||||
)
|
||||
}.build()
|
||||
return _ic_arrow_up_20!!
|
||||
|
|
|
|||
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