Updated on 2026-08-14
This commit is contained in:
parent
acf110dbcc
commit
6910f74fd9
242 changed files with 866 additions and 732 deletions
|
|
@ -22,7 +22,7 @@ internal fun SettingsAlertDialog(dialog: Dialog.Alert) {
|
||||||
isDismissable = false,
|
isDismissable = false,
|
||||||
confirmButton = DialogButtonUM(
|
confirmButton = DialogButtonUM(
|
||||||
title = dialog.confirmText.resolveReference(),
|
title = dialog.confirmText.resolveReference(),
|
||||||
warning = true,
|
isWarning = true,
|
||||||
onClick = dialog.onConfirm,
|
onClick = dialog.onConfirm,
|
||||||
),
|
),
|
||||||
dismissButton = DialogButtonUM(
|
dismissButton = DialogButtonUM(
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
|
||||||
),
|
),
|
||||||
confirmButton = DialogButtonUM(
|
confirmButton = DialogButtonUM(
|
||||||
title = stringResourceSafe(id = R.string.card_settings_action_sheet_reset),
|
title = stringResourceSafe(id = R.string.card_settings_action_sheet_reset),
|
||||||
warning = true,
|
isWarning = true,
|
||||||
onClick = dialog.onConfirmClick,
|
onClick = dialog.onConfirmClick,
|
||||||
),
|
),
|
||||||
onDismissDialog = dialog.onDismiss,
|
onDismissDialog = dialog.onDismiss,
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ internal class MainViewModel @Inject constructor(
|
||||||
private fun launchAPIRequests(function: suspend CoroutineScope.() -> Unit) {
|
private fun launchAPIRequests(function: suspend CoroutineScope.() -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||||
apiConfigsManager.isInitialized
|
apiConfigsManager.initializedState
|
||||||
.filter { it }
|
.filter { it }
|
||||||
.first() // wait until isInitialized becomes true
|
.first() // wait until isInitialized becomes true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
params = MarketsTokenDetailsComponent.Params(
|
params = MarketsTokenDetailsComponent.Params(
|
||||||
token = route.token,
|
token = route.token,
|
||||||
appCurrency = route.appCurrency,
|
appCurrency = route.appCurrency,
|
||||||
showPortfolio = route.showPortfolio,
|
shouldShowPortfolio = route.shouldShowPortfolio,
|
||||||
analyticsParams = route.analyticsParams?.let { params ->
|
analyticsParams = route.analyticsParams?.let { params ->
|
||||||
MarketsTokenDetailsComponent.AnalyticsParams(
|
MarketsTokenDetailsComponent.AnalyticsParams(
|
||||||
blockchain = params.blockchain,
|
blockchain = params.blockchain,
|
||||||
|
|
@ -203,7 +203,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
|
||||||
cryptoCurrency = route.currency,
|
cryptoCurrency = route.currency,
|
||||||
source = route.source,
|
source = route.source,
|
||||||
launchSepa = route.launchSepa,
|
shouldLaunchSepa = route.shouldLaunchSepa,
|
||||||
),
|
),
|
||||||
componentFactory = onrampComponentFactory,
|
componentFactory = onrampComponentFactory,
|
||||||
)
|
)
|
||||||
|
|
@ -462,7 +462,7 @@ internal class ChildFactory @Inject constructor(
|
||||||
initialCurrency = route.initialCurrency,
|
initialCurrency = route.initialCurrency,
|
||||||
selectedCurrency = route.selectedCurrency,
|
selectedCurrency = route.selectedCurrency,
|
||||||
source = ChooseManagedTokensComponent.Source.valueOf(route.source.name),
|
source = ChooseManagedTokensComponent.Source.valueOf(route.source.name),
|
||||||
showSendViaSwapNotification = route.showSendViaSwapNotification,
|
shouldShowSendViaSwapNotification = route.shouldShowSendViaSwapNotification,
|
||||||
analyticsCategoryName = route.analyticsCategoryName,
|
analyticsCategoryName = route.analyticsCategoryName,
|
||||||
),
|
),
|
||||||
componentFactory = chooseManagedTokensComponentFactory,
|
componentFactory = chooseManagedTokensComponentFactory,
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,8 @@ object GoogleServicesHelper {
|
||||||
return suspendCoroutine<Result<Boolean>> { continuation ->
|
return suspendCoroutine<Result<Boolean>> { continuation ->
|
||||||
task.addOnCompleteListener { completedTask ->
|
task.addOnCompleteListener { completedTask ->
|
||||||
try {
|
try {
|
||||||
val result = completedTask.getResult(ApiException::class.java)
|
val isGooglePayAvailable = completedTask.getResult(ApiException::class.java)
|
||||||
continuation.resume(Result.success(result))
|
continuation.resume(Result.success(isGooglePayAvailable))
|
||||||
} catch (exception: ApiException) {
|
} catch (exception: ApiException) {
|
||||||
continuation.resume(Result.failure(exception))
|
continuation.resume(Result.failure(exception))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@file:Suppress("NullableToStringCall")
|
||||||
|
|
||||||
package com.tangem.common.routing
|
package com.tangem.common.routing
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
|
@ -160,7 +162,7 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val initialCurrency: CryptoCurrency,
|
val initialCurrency: CryptoCurrency,
|
||||||
val selectedCurrency: CryptoCurrency?,
|
val selectedCurrency: CryptoCurrency?,
|
||||||
val source: Source,
|
val source: Source,
|
||||||
val showSendViaSwapNotification: Boolean,
|
val shouldShowSendViaSwapNotification: Boolean,
|
||||||
val analyticsCategoryName: String,
|
val analyticsCategoryName: String,
|
||||||
) : AppRoute(path = "/$source/choose_managed_tokens/$userWalletId/${initialCurrency.id.value}") {
|
) : AppRoute(path = "/$source/choose_managed_tokens/$userWalletId/${initialCurrency.id.value}") {
|
||||||
enum class Source {
|
enum class Source {
|
||||||
|
|
@ -271,9 +273,9 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
data class MarketsTokenDetails(
|
data class MarketsTokenDetails(
|
||||||
val token: TokenMarketParams,
|
val token: TokenMarketParams,
|
||||||
val appCurrency: AppCurrency,
|
val appCurrency: AppCurrency,
|
||||||
val showPortfolio: Boolean,
|
val shouldShowPortfolio: Boolean,
|
||||||
val analyticsParams: AnalyticsParams? = null,
|
val analyticsParams: AnalyticsParams? = null,
|
||||||
) : AppRoute(path = "/markets_token_details/${token.id}/$showPortfolio") {
|
) : AppRoute(path = "/markets_token_details/${token.id}/$shouldShowPortfolio") {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AnalyticsParams(
|
data class AnalyticsParams(
|
||||||
|
|
@ -287,7 +289,7 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val source: OnrampSource,
|
val source: OnrampSource,
|
||||||
val portfolioId: PortfolioId,
|
val portfolioId: PortfolioId,
|
||||||
val currency: CryptoCurrency,
|
val currency: CryptoCurrency,
|
||||||
val launchSepa: Boolean = false,
|
val shouldLaunchSepa: Boolean = false,
|
||||||
) : AppRoute(path = "/onramp/${portfolioId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
) : AppRoute(path = "/onramp/${portfolioId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
||||||
override fun getBundle(): Bundle = bundle(serializer())
|
override fun getBundle(): Bundle = bundle(serializer())
|
||||||
|
|
||||||
|
|
@ -301,7 +303,7 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
source = source,
|
source = source,
|
||||||
portfolioId = PortfolioId(userWalletId),
|
portfolioId = PortfolioId(userWalletId),
|
||||||
currency = currency,
|
currency = currency,
|
||||||
launchSepa = launchSepa,
|
shouldLaunchSepa = launchSepa,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import kotlinx.serialization.encoding.CompositeDecoder
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
|
||||||
@ExperimentalSerializationApi
|
@ExperimentalSerializationApi
|
||||||
|
@Suppress("NullableToStringCall")
|
||||||
internal class BundleDecoder(
|
internal class BundleDecoder(
|
||||||
private val bundle: Bundle,
|
private val bundle: Bundle,
|
||||||
private val elementsCount: Int = -1,
|
private val elementsCount: Int = -1,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ object MockYieldBalanceWrapperDTOFactory {
|
||||||
amount = BigDecimal.ONE,
|
amount = BigDecimal.ONE,
|
||||||
date = null,
|
date = null,
|
||||||
pricePerShare = BigDecimal.ZERO,
|
pricePerShare = BigDecimal.ZERO,
|
||||||
pendingActions = listOf(),
|
pendingActions = emptyList(),
|
||||||
pendingActionConstraints = null,
|
pendingActionConstraints = null,
|
||||||
tokenDTO = TokenDTO(
|
tokenDTO = TokenDTO(
|
||||||
name = "The-Open-Network",
|
name = "The-Open-Network",
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ object MockYieldDTOFactory {
|
||||||
logoURI = null,
|
logoURI = null,
|
||||||
isPoints = null,
|
isPoints = null,
|
||||||
),
|
),
|
||||||
tokens = listOf(),
|
tokens = emptyList(),
|
||||||
args = YieldDTO.ArgsDTO(
|
args = YieldDTO.ArgsDTO(
|
||||||
enter = YieldDTO.ArgsDTO.Enter(
|
enter = YieldDTO.ArgsDTO.Enter(
|
||||||
addresses = YieldDTO.ArgsDTO.Enter.Addresses(
|
addresses = YieldDTO.ArgsDTO.Enter.Addresses(
|
||||||
address = AddressArgumentDTO(required = false),
|
address = AddressArgumentDTO(required = false),
|
||||||
),
|
),
|
||||||
args = mapOf(),
|
args = emptyMap(),
|
||||||
),
|
),
|
||||||
exit = null,
|
exit = null,
|
||||||
),
|
),
|
||||||
|
|
@ -69,7 +69,7 @@ object MockYieldDTOFactory {
|
||||||
logoURI = null,
|
logoURI = null,
|
||||||
isPoints = null,
|
isPoints = null,
|
||||||
),
|
),
|
||||||
tokensDTO = listOf(),
|
tokensDTO = emptyList(),
|
||||||
type = "type",
|
type = "type",
|
||||||
rewardSchedule = YieldDTO.MetadataDTO.RewardScheduleDTO.DAY,
|
rewardSchedule = YieldDTO.MetadataDTO.RewardScheduleDTO.DAY,
|
||||||
cooldownPeriod = null,
|
cooldownPeriod = null,
|
||||||
|
|
@ -81,7 +81,7 @@ object MockYieldDTOFactory {
|
||||||
revshare = YieldDTO.MetadataDTO.EnabledDTO(enabled = true),
|
revshare = YieldDTO.MetadataDTO.EnabledDTO(enabled = true),
|
||||||
fee = YieldDTO.MetadataDTO.EnabledDTO(enabled = true),
|
fee = YieldDTO.MetadataDTO.EnabledDTO(enabled = true),
|
||||||
),
|
),
|
||||||
validators = listOf(),
|
validators = emptyList(),
|
||||||
isAvailable = true,
|
isAvailable = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ object MockScanResponseFactory {
|
||||||
isResettingUserCodesAllowed = false,
|
isResettingUserCodesAllowed = false,
|
||||||
isLinkedTerminalEnabled = false,
|
isLinkedTerminalEnabled = false,
|
||||||
isBackupAllowed = cardConfig is MultiWalletCardConfig,
|
isBackupAllowed = cardConfig is MultiWalletCardConfig,
|
||||||
supportedEncryptionModes = listOf(),
|
supportedEncryptionModes = emptyList(),
|
||||||
isFilesAllowed = true,
|
isFilesAllowed = true,
|
||||||
isHDWalletAllowed = cardConfig is MultiWalletCardConfig,
|
isHDWalletAllowed = cardConfig is MultiWalletCardConfig,
|
||||||
isKeysImportAllowed = true,
|
isKeysImportAllowed = true,
|
||||||
|
|
@ -72,7 +72,7 @@ object MockScanResponseFactory {
|
||||||
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.Current,
|
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.Current,
|
||||||
isAccessCodeSet = false,
|
isAccessCodeSet = false,
|
||||||
isPasscodeSet = null,
|
isPasscodeSet = null,
|
||||||
supportedCurves = listOf(),
|
supportedCurves = emptyList(),
|
||||||
wallets = cardConfig.mandatoryCurves.map {
|
wallets = cardConfig.mandatoryCurves.map {
|
||||||
CardDTO.Wallet(
|
CardDTO.Wallet(
|
||||||
CardWallet(
|
CardWallet(
|
||||||
|
|
@ -85,7 +85,7 @@ object MockScanResponseFactory {
|
||||||
index = 0,
|
index = 0,
|
||||||
isImported = true,
|
isImported = true,
|
||||||
hasBackup = true,
|
hasBackup = true,
|
||||||
derivedKeys = mapOf(),
|
derivedKeys = emptyMap(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,20 @@ object MockNetworkStatusFactory {
|
||||||
): NetworkStatus {
|
): NetworkStatus {
|
||||||
return NetworkStatus(
|
return NetworkStatus(
|
||||||
network = network,
|
network = network,
|
||||||
value = NetworkStatus.Verified(
|
value = transform(
|
||||||
address = NetworkAddress.Single(
|
NetworkStatus.Verified(
|
||||||
defaultAddress = NetworkAddress.Address(
|
address = NetworkAddress.Single(
|
||||||
value = "0x1",
|
defaultAddress = NetworkAddress.Address(
|
||||||
type = NetworkAddress.Address.Type.Primary,
|
value = "0x1",
|
||||||
|
type = NetworkAddress.Address.Type.Primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
amounts = emptyMap(),
|
||||||
|
pendingTransactions = emptyMap(),
|
||||||
|
yieldSupplyStatuses = emptyMap(),
|
||||||
|
source = source,
|
||||||
),
|
),
|
||||||
amounts = mapOf(),
|
),
|
||||||
pendingTransactions = mapOf(),
|
|
||||||
yieldSupplyStatuses = mapOf(),
|
|
||||||
source = source,
|
|
||||||
)
|
|
||||||
.let(transform),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ object MockUserWalletFactory {
|
||||||
return UserWallet.Cold(
|
return UserWallet.Cold(
|
||||||
name = "NODL",
|
name = "NODL",
|
||||||
walletId = UserWalletId("011"),
|
walletId = UserWalletId("011"),
|
||||||
cardsInWallet = setOf(),
|
cardsInWallet = emptySet(),
|
||||||
isMultiCurrency = false,
|
isMultiCurrency = false,
|
||||||
scanResponse = MockScanResponseFactory.create(
|
scanResponse = MockScanResponseFactory.create(
|
||||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import com.google.common.truth.Truth
|
||||||
fun <B> assertEither(actual: Either<Throwable, B>, expected: Either<Throwable, B>) {
|
fun <B> assertEither(actual: Either<Throwable, B>, expected: Either<Throwable, B>) {
|
||||||
actual
|
actual
|
||||||
.onRight { Truth.assertThat(actual).isEqualTo(expected) }
|
.onRight { Truth.assertThat(actual).isEqualTo(expected) }
|
||||||
.onLeft {
|
.onLeft { throwable ->
|
||||||
val expectedError = expected.leftOrNull() ?: error("Actual is Either.Left: $it")
|
val expectedError = expected.leftOrNull() ?: error("Actual is Either.Left: $throwable")
|
||||||
|
|
||||||
Truth.assertThat(it).isInstanceOf(expectedError::class.java)
|
Truth.assertThat(throwable).isInstanceOf(expectedError::class.java)
|
||||||
Truth.assertThat(it).hasMessageThat().isEqualTo(expectedError.message)
|
Truth.assertThat(throwable).hasMessageThat().isEqualTo(expectedError.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,6 +24,7 @@ fun assertEitherRight(actual: Either<Throwable, Unit>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("NullableToStringCall")
|
||||||
fun <B> assertEitherLeft(actual: Either<Throwable, B>, expected: Throwable) {
|
fun <B> assertEitherLeft(actual: Either<Throwable, B>, expected: Throwable) {
|
||||||
actual
|
actual
|
||||||
.onRight { error("Actual is Either.Right: $it") }
|
.onRight { error("Actual is Either.Right: $it") }
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ fun MarketChart(
|
||||||
backgroundLineColor = state.chartColor.copy(alpha = backgroundColorAlpha),
|
backgroundLineColor = state.chartColor.copy(alpha = backgroundColorAlpha),
|
||||||
secondLineColor = splitChartSegmentColor,
|
secondLineColor = splitChartSegmentColor,
|
||||||
backgroundSecondLineColor = splitChartSegmentColor.copy(alpha = backgroundSplitChartSegmentColorAlpha),
|
backgroundSecondLineColor = splitChartSegmentColor.copy(alpha = backgroundSplitChartSegmentColorAlpha),
|
||||||
secondColorOnTheRightSide = state.markerHighlightRightSide.not(),
|
secondColorOnTheRightSide = state.shouldMarkerHighlightRightSide.not(),
|
||||||
markerFraction = state.markerFraction,
|
markerFraction = state.markerFraction,
|
||||||
axisValueOverrider = AxisValueOverrider.fixed(),
|
axisValueOverrider = AxisValueOverrider.fixed(),
|
||||||
canvasHeight = chartHeight,
|
canvasHeight = chartHeight,
|
||||||
|
|
@ -114,10 +114,10 @@ fun MarketChart(
|
||||||
|
|
||||||
CartesianChartHost(
|
CartesianChartHost(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned { layoutCoordinates ->
|
||||||
canvasWidth = it.size.width
|
canvasWidth = layoutCoordinates.size.width
|
||||||
chartHeight = if (it.size.height != 0) {
|
chartHeight = if (layoutCoordinates.size.height != 0) {
|
||||||
it.size.height - bottomAxisHeight
|
layoutCoordinates.size.height - bottomAxisHeight
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +126,7 @@ fun MarketChart(
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
state.markerFraction
|
state.markerFraction
|
||||||
state.chartColor
|
state.chartColor
|
||||||
state.markerHighlightRightSide
|
state.shouldMarkerHighlightRightSide
|
||||||
},
|
},
|
||||||
chart = chart,
|
chart = chart,
|
||||||
modelProducer = state.modelProducer,
|
modelProducer = state.modelProducer,
|
||||||
|
|
@ -269,7 +269,7 @@ private fun rememberChartAxisGuidelineComponent(color: Color): LineComponent {
|
||||||
|
|
||||||
// region Preview
|
// region Preview
|
||||||
|
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod", "NullableToStringCall")
|
||||||
@Preview(showBackground = true, widthDp = 360)
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -283,7 +283,7 @@ private fun MarketChartPreview(
|
||||||
MarketChartDataProducer.build {
|
MarketChartDataProducer.build {
|
||||||
chartLook = MarketChartLook(
|
chartLook = MarketChartLook(
|
||||||
type = MarketChartLook.Type.Growing,
|
type = MarketChartLook.Type.Growing,
|
||||||
markerHighlightRightSide = true,
|
shouldMarkerHighlightRightSide = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -294,8 +294,8 @@ private fun MarketChartPreview(
|
||||||
x = x.toImmutableList(),
|
x = x.toImmutableList(),
|
||||||
y = y.toImmutableList(),
|
y = y.toImmutableList(),
|
||||||
)
|
)
|
||||||
updateLook {
|
updateLook { marketChartLook ->
|
||||||
it.copy(
|
marketChartLook.copy(
|
||||||
xAxisFormatter = { value ->
|
xAxisFormatter = { value ->
|
||||||
value.toLong().toTimeFormat(DateTimeFormatters.dateMMMdd)
|
value.toLong().toTimeFormat(DateTimeFormatters.dateMMMdd)
|
||||||
},
|
},
|
||||||
|
|
@ -322,8 +322,8 @@ private fun MarketChartPreview(
|
||||||
onMarkerShown = { x, y ->
|
onMarkerShown = { x, y ->
|
||||||
markerPoint = Pair(x, y)
|
markerPoint = Pair(x, y)
|
||||||
},
|
},
|
||||||
colorMapper = {
|
colorMapper = { type ->
|
||||||
when (it) {
|
when (type) {
|
||||||
MarketChartLook.Type.Growing -> growingColor
|
MarketChartLook.Type.Growing -> growingColor
|
||||||
MarketChartLook.Type.Falling -> fallingColor
|
MarketChartLook.Type.Falling -> fallingColor
|
||||||
MarketChartLook.Type.Neutral -> neutralColor
|
MarketChartLook.Type.Neutral -> neutralColor
|
||||||
|
|
@ -354,7 +354,7 @@ private fun MarketChartPreview(
|
||||||
onClick = {
|
onClick = {
|
||||||
dataProducer.runTransaction {
|
dataProducer.runTransaction {
|
||||||
updateLook {
|
updateLook {
|
||||||
it.copy(markerHighlightRightSide = !it.markerHighlightRightSide)
|
it.copy(shouldMarkerHighlightRightSide = !it.shouldMarkerHighlightRightSide)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -367,10 +367,10 @@ private fun MarketChartPreview(
|
||||||
onClick = {
|
onClick = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
dataProducer.runTransactionSuspend {
|
dataProducer.runTransactionSuspend {
|
||||||
updateData {
|
updateData { data ->
|
||||||
MarketChartData.Data(
|
MarketChartData.Data(
|
||||||
x = it.x,
|
x = data.x,
|
||||||
y = it.y.reversed().toImmutableList(),
|
y = data.y.reversed().toImmutableList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -383,9 +383,9 @@ private fun MarketChartPreview(
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
dataProducer.runTransaction {
|
dataProducer.runTransaction {
|
||||||
updateLook {
|
updateLook { look ->
|
||||||
it.copy(
|
look.copy(
|
||||||
type = when (it.type) {
|
type = when (look.type) {
|
||||||
MarketChartLook.Type.Growing -> MarketChartLook.Type.Falling
|
MarketChartLook.Type.Growing -> MarketChartLook.Type.Falling
|
||||||
MarketChartLook.Type.Falling -> MarketChartLook.Type.Neutral
|
MarketChartLook.Type.Falling -> MarketChartLook.Type.Neutral
|
||||||
MarketChartLook.Type.Neutral -> MarketChartLook.Type.Growing
|
MarketChartLook.Type.Neutral -> MarketChartLook.Type.Growing
|
||||||
|
|
|
||||||
|
|
@ -104,10 +104,10 @@ private fun PreviewColumn() {
|
||||||
|
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
items(100) {
|
items(100) { i ->
|
||||||
MarketChartMini(
|
MarketChartMini(
|
||||||
rawData = data,
|
rawData = data,
|
||||||
type = if (it % 3 == 0) MarketChartLook.Type.Growing else MarketChartLook.Type.Falling,
|
type = if (i % 3 == 0) MarketChartLook.Type.Growing else MarketChartLook.Type.Falling,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,10 @@ object LTThreeBuckets {
|
||||||
val yRes = ArrayList<Double>(points.size)
|
val yRes = ArrayList<Double>(points.size)
|
||||||
val indexesRes = ArrayList<Int>(points.size)
|
val indexesRes = ArrayList<Int>(points.size)
|
||||||
|
|
||||||
results.fastForEach {
|
results.fastForEach { result ->
|
||||||
xRes.add(it.x)
|
xRes.add(result.x)
|
||||||
yRes.add(it.y)
|
yRes.add(result.y)
|
||||||
indexesRes.add(it.originalIndex!!)
|
indexesRes.add(requireNotNull(result.originalIndex))
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result(
|
return Result(
|
||||||
|
|
|
||||||
|
|
@ -62,12 +62,7 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
||||||
},
|
},
|
||||||
indicatorSizeDp = INDICATOR_SIZE_DP,
|
indicatorSizeDp = INDICATOR_SIZE_DP,
|
||||||
guideline = guideline,
|
guideline = guideline,
|
||||||
valueFormatter = object : CartesianMarkerValueFormatter {
|
valueFormatter = CartesianMarkerValueFormatter { _, _ -> "" },
|
||||||
override fun format(
|
|
||||||
context: CartesianDrawContext,
|
|
||||||
targets: List<CartesianMarker.Target>,
|
|
||||||
): CharSequence = ""
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
override fun updateInsets(
|
override fun updateInsets(
|
||||||
context: CartesianMeasureContext,
|
context: CartesianMeasureContext,
|
||||||
|
|
@ -76,7 +71,12 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
||||||
insets: Insets,
|
insets: Insets,
|
||||||
) {
|
) {
|
||||||
with(context) {
|
with(context) {
|
||||||
super.updateInsets(context, horizontalDimensions, model, insets)
|
super.updateInsets(
|
||||||
|
context = context,
|
||||||
|
horizontalDimensions = horizontalDimensions,
|
||||||
|
model = model,
|
||||||
|
insets = insets,
|
||||||
|
)
|
||||||
val baseShadowInsetDp =
|
val baseShadowInsetDp =
|
||||||
CLIPPING_FREE_SHADOW_RADIUS_MULTIPLIER * LABEL_BACKGROUND_SHADOW_RADIUS_DP
|
CLIPPING_FREE_SHADOW_RADIUS_MULTIPLIER * LABEL_BACKGROUND_SHADOW_RADIUS_DP
|
||||||
val topInset = (baseShadowInsetDp - LABEL_BACKGROUND_SHADOW_DY_DP).pixels
|
val topInset = (baseShadowInsetDp - LABEL_BACKGROUND_SHADOW_DY_DP).pixels
|
||||||
|
|
@ -90,11 +90,11 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
|
||||||
cacheStore
|
cacheStore
|
||||||
.getOrSet(keyNamespace, indicator, outColor) { indicator.invoke(outColor) }
|
.getOrSet(keyNamespace, indicator, outColor) { indicator.invoke(outColor) }
|
||||||
.draw(
|
.draw(
|
||||||
this,
|
context = this,
|
||||||
x - halfIndicatorSize,
|
left = x - halfIndicatorSize,
|
||||||
y - halfIndicatorSize,
|
top = y - halfIndicatorSize,
|
||||||
x + halfIndicatorSize,
|
right = x + halfIndicatorSize,
|
||||||
y + halfIndicatorSize,
|
bottom = y + halfIndicatorSize,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ internal fun rememberMarketChartLayer(
|
||||||
val backgroundColorLineGradient = persistentListOf(backgroundLineColor, Color.Transparent)
|
val backgroundColorLineGradient = persistentListOf(backgroundLineColor, Color.Transparent)
|
||||||
val backgroundSecondLineColorGradient = persistentListOf(backgroundSecondLineColor, Color.Transparent)
|
val backgroundSecondLineColorGradient = persistentListOf(backgroundSecondLineColor, Color.Transparent)
|
||||||
|
|
||||||
val markerSet = markerFraction != null
|
val isMarkerSet = markerFraction != null
|
||||||
|
|
||||||
return rememberLayer(
|
return rememberLayer(
|
||||||
fractionValue = markerFraction ?: 0f,
|
fractionValue = markerFraction ?: 0f,
|
||||||
|
|
@ -75,17 +75,17 @@ internal fun rememberMarketChartLayer(
|
||||||
} else {
|
} else {
|
||||||
lineColor
|
lineColor
|
||||||
},
|
},
|
||||||
backLineColor = if (markerSet && !secondColorOnTheRightSide) {
|
backLineColor = if (isMarkerSet && !secondColorOnTheRightSide) {
|
||||||
backgroundSecondLineColorGradient
|
backgroundSecondLineColorGradient
|
||||||
} else {
|
} else {
|
||||||
backgroundColorLineGradient
|
backgroundColorLineGradient
|
||||||
},
|
},
|
||||||
lineColorRight = when {
|
lineColorRight = when {
|
||||||
markerSet && secondColorOnTheRightSide -> secondLineColor
|
isMarkerSet && secondColorOnTheRightSide -> secondLineColor
|
||||||
else -> lineColor
|
else -> lineColor
|
||||||
},
|
},
|
||||||
backLineColorRight = when {
|
backLineColorRight = when {
|
||||||
markerSet && secondColorOnTheRightSide -> backgroundSecondLineColorGradient
|
isMarkerSet && secondColorOnTheRightSide -> backgroundSecondLineColorGradient
|
||||||
else -> backgroundColorLineGradient
|
else -> backgroundColorLineGradient
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -148,7 +148,7 @@ private fun LayerChartPreview(
|
||||||
val y = previewData.second.map { it.toFloat() }
|
val y = previewData.second.map { it.toFloat() }
|
||||||
val x = List(y.size) { it.toFloat() }
|
val x = List(y.size) { it.toFloat() }
|
||||||
val model = CartesianChartModel(LineCartesianLayerModel.build { series(x, y) })
|
val model = CartesianChartModel(LineCartesianLayerModel.build { series(x, y) })
|
||||||
var lineColor by remember {
|
val lineColor by remember {
|
||||||
mutableStateOf(Color.Blue)
|
mutableStateOf(Color.Blue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ class TransactionSuspend(
|
||||||
class MarketChartDataProducer private constructor(
|
class MarketChartDataProducer private constructor(
|
||||||
initialData: MarketChartData,
|
initialData: MarketChartData,
|
||||||
initialLook: MarketChartLook,
|
initialLook: MarketChartLook,
|
||||||
val pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
|
val pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
|
||||||
private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||||
) {
|
) {
|
||||||
internal val dataState = MutableStateFlow(initialData)
|
internal val dataState = MutableStateFlow(initialData)
|
||||||
|
|
@ -166,7 +166,7 @@ class MarketChartDataProducer private constructor(
|
||||||
* @return A MarketChartDataProducer.
|
* @return A MarketChartDataProducer.
|
||||||
*/
|
*/
|
||||||
suspend fun buildSuspend(
|
suspend fun buildSuspend(
|
||||||
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
|
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
|
||||||
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||||
block: TransactionSuspend.() -> Unit,
|
block: TransactionSuspend.() -> Unit,
|
||||||
): MarketChartDataProducer {
|
): MarketChartDataProducer {
|
||||||
|
|
@ -191,7 +191,7 @@ class MarketChartDataProducer private constructor(
|
||||||
* @return A MarketChartDataProducer.
|
* @return A MarketChartDataProducer.
|
||||||
*/
|
*/
|
||||||
fun build(
|
fun build(
|
||||||
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
|
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
|
||||||
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
dispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||||
block: Transaction.() -> Unit,
|
block: Transaction.() -> Unit,
|
||||||
): MarketChartDataProducer {
|
): MarketChartDataProducer {
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@ import com.tangem.common.ui.charts.state.formatter.AxisLabelFormatter
|
||||||
* and formatters for x and y axis.
|
* and formatters for x and y axis.
|
||||||
*
|
*
|
||||||
* @property type The type of the chart, can be either Growing or Falling.
|
* @property type The type of the chart, can be either Growing or Falling.
|
||||||
* @property markerHighlightRightSide A boolean indicating whether the marker highlights the right side of the chart.
|
* @property shouldMarkerHighlightRightSide A boolean indicating whether the marker highlights the right side of the chart.
|
||||||
* @property xAxisFormatter A formatter for the x-axis labels.
|
* @property xAxisFormatter A formatter for the x-axis labels.
|
||||||
* @property yAxisFormatter A formatter for the y-axis labels.
|
* @property yAxisFormatter A formatter for the y-axis labels.
|
||||||
*/
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
data class MarketChartLook(
|
data class MarketChartLook(
|
||||||
val type: Type = Type.Growing,
|
val type: Type = Type.Growing,
|
||||||
val markerHighlightRightSide: Boolean = true,
|
val shouldMarkerHighlightRightSide: Boolean = true,
|
||||||
val xAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
val xAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
||||||
val yAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
val yAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import java.math.BigDecimal
|
||||||
fun rememberMarketChartState(
|
fun rememberMarketChartState(
|
||||||
dataProducer: MarketChartDataProducer = remember { MarketChartDataProducer.build {} },
|
dataProducer: MarketChartDataProducer = remember { MarketChartDataProducer.build {} },
|
||||||
colorMapper: (MarketChartLook.Type) -> Color = remember {
|
colorMapper: (MarketChartLook.Type) -> Color = remember {
|
||||||
{
|
{ type ->
|
||||||
when (it) {
|
when (type) {
|
||||||
MarketChartLook.Type.Growing -> Color.Green
|
MarketChartLook.Type.Growing -> Color.Green
|
||||||
MarketChartLook.Type.Falling -> Color.Red
|
MarketChartLook.Type.Falling -> Color.Red
|
||||||
MarketChartLook.Type.Neutral -> Color.Gray
|
MarketChartLook.Type.Neutral -> Color.Gray
|
||||||
|
|
@ -33,7 +33,12 @@ fun rememberMarketChartState(
|
||||||
val lookState = dataProducer.lookState.collectAsState()
|
val lookState = dataProducer.lookState.collectAsState()
|
||||||
|
|
||||||
val state = remember(dataProducer, lookState, colorMapper, onMarkerShown) {
|
val state = remember(dataProducer, lookState, colorMapper, onMarkerShown) {
|
||||||
MarketChartState(dataProducer, lookState, colorMapper, onMarkerShown)
|
MarketChartState(
|
||||||
|
dataProducer = dataProducer,
|
||||||
|
lookState = lookState,
|
||||||
|
colorMapper = colorMapper,
|
||||||
|
markerCallback = onMarkerShown,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
@ -61,8 +66,8 @@ class MarketChartState internal constructor(
|
||||||
colorMapper(lookState.value.type)
|
colorMapper(lookState.value.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val markerHighlightRightSide by derivedStateOf {
|
internal val shouldMarkerHighlightRightSide by derivedStateOf {
|
||||||
lookState.value.markerHighlightRightSide
|
lookState.value.shouldMarkerHighlightRightSide
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val xValueFormatter = CartesianValueFormatter { value, _, _ ->
|
internal val xValueFormatter = CartesianValueFormatter { value, _, _ ->
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import java.math.BigDecimal
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
class PriceAndTimePointValuesConverter(
|
class PriceAndTimePointValuesConverter(
|
||||||
private val needToFormatAxis: Boolean,
|
private val shouldFormatAxis: Boolean,
|
||||||
) : PointValuesConverter {
|
) : PointValuesConverter {
|
||||||
|
|
||||||
private data class MinMaxCache(
|
private data class MinMaxCache(
|
||||||
|
|
@ -18,7 +18,12 @@ class PriceAndTimePointValuesConverter(
|
||||||
val maxY: BigDecimal,
|
val maxY: BigDecimal,
|
||||||
)
|
)
|
||||||
|
|
||||||
private var minMaxCache = MinMaxCache(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)
|
private var minMaxCache = MinMaxCache(
|
||||||
|
minX = BigDecimal.ZERO,
|
||||||
|
maxX = BigDecimal.ZERO,
|
||||||
|
minY = BigDecimal.ZERO,
|
||||||
|
maxY = BigDecimal.ZERO,
|
||||||
|
)
|
||||||
private val formatYValuesCache = mutableMapOf<Double, BigDecimal>()
|
private val formatYValuesCache = mutableMapOf<Double, BigDecimal>()
|
||||||
private val formatXValuesCache = mutableMapOf<Double, BigDecimal>()
|
private val formatXValuesCache = mutableMapOf<Double, BigDecimal>()
|
||||||
|
|
||||||
|
|
@ -37,15 +42,14 @@ class PriceAndTimePointValuesConverter(
|
||||||
val normX = data.x.normalizeTime(min = cache.minX, max = cache.maxX)
|
val normX = data.x.normalizeTime(min = cache.minX, max = cache.maxX)
|
||||||
|
|
||||||
return if (normX.size > MAX_POINTS) {
|
return if (normX.size > MAX_POINTS) {
|
||||||
LTThreeBuckets
|
val downsampled = LTThreeBuckets
|
||||||
.downsample(normX, normY, MAX_POINTS - 2)
|
.downsample(normX, normY, MAX_POINTS - 2)
|
||||||
.let {
|
|
||||||
MarketChartRawData(
|
MarketChartRawData(
|
||||||
originalIndexes = it.originalIndexes.toImmutableList(),
|
originalIndexes = downsampled.originalIndexes.toImmutableList(),
|
||||||
x = it.x.toImmutableList(),
|
x = downsampled.x.toImmutableList(),
|
||||||
y = it.y.toImmutableList(),
|
y = downsampled.y.toImmutableList(),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
MarketChartRawData(
|
MarketChartRawData(
|
||||||
x = normX.toImmutableList(),
|
x = normX.toImmutableList(),
|
||||||
|
|
@ -55,8 +59,8 @@ class PriceAndTimePointValuesConverter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun prepareRawXForFormat(rawX: Double, data: MarketChartData.Data): BigDecimal {
|
override fun prepareRawXForFormat(rawX: Double, data: MarketChartData.Data): BigDecimal {
|
||||||
if (!needToFormatAxis) return BigDecimal.ZERO
|
if (!shouldFormatAxis) return BigDecimal.ZERO
|
||||||
if (formatXValuesCache.containsKey(rawX)) return formatXValuesCache[rawX]!!
|
if (formatXValuesCache.containsKey(rawX)) return requireNotNull(formatXValuesCache[rawX])
|
||||||
|
|
||||||
val result = (rawX * MINUTE).toBigDecimal()
|
val result = (rawX * MINUTE).toBigDecimal()
|
||||||
|
|
||||||
|
|
@ -65,8 +69,8 @@ class PriceAndTimePointValuesConverter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun prepareRawYForFormat(rawY: Double, data: MarketChartData.Data): BigDecimal {
|
override fun prepareRawYForFormat(rawY: Double, data: MarketChartData.Data): BigDecimal {
|
||||||
if (!needToFormatAxis) return BigDecimal.ZERO
|
if (!shouldFormatAxis) return BigDecimal.ZERO
|
||||||
if (formatYValuesCache.containsKey(rawY)) return formatYValuesCache[rawY]!!
|
if (formatYValuesCache.containsKey(rawY)) return requireNotNull(formatYValuesCache[rawY])
|
||||||
|
|
||||||
val min = minMaxCache.minY
|
val min = minMaxCache.minY
|
||||||
val max = minMaxCache.maxY
|
val max = minMaxCache.maxY
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ class AmountReduceByTransformer(
|
||||||
|
|
||||||
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
|
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
|
||||||
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
|
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
|
||||||
val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } ?: false
|
val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } == true
|
||||||
val isZero = if (amountTextField.isFiatValue) {
|
val isZero = if (amountTextField.isFiatValue) {
|
||||||
decimalFiatValue.isNullOrZero()
|
decimalFiatValue.isNullOrZero()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class AmountReduceToTransformer(
|
||||||
|
|
||||||
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
|
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
|
||||||
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
|
val isExceedBalance = checkValue.checkExceedBalance(maxEnterAmount, amountTextField)
|
||||||
val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } ?: false
|
val isLessThanMinimumIfProvided = minimumTransactionAmount?.amount?.let { decimalCryptoValue < it } == true
|
||||||
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isNullOrZero()
|
val isZero = if (amountTextField.isFiatValue) decimalFiatValue.isNullOrZero() else value.isNullOrZero()
|
||||||
val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided
|
val isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided
|
||||||
return prevState.copy(
|
return prevState.copy(
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class AmountStateConverter(
|
||||||
val status = cryptoCurrencyStatusProvider()
|
val status = cryptoCurrencyStatusProvider()
|
||||||
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||||
val crypto = maxEnterAmount.amount.format { crypto(status.currency) }
|
val crypto = maxEnterAmount.amount.format { crypto(status.currency) }
|
||||||
val noFeeRate = status.value.fiatRate.isNullOrZero()
|
val hasNoFeeRate = status.value.fiatRate.isNullOrZero()
|
||||||
|
|
||||||
return AmountState.Data(
|
return AmountState.Data(
|
||||||
title = value.title,
|
title = value.title,
|
||||||
|
|
@ -69,7 +69,7 @@ class AmountStateConverter(
|
||||||
title = stringReference(status.currency.symbol),
|
title = stringReference(status.currency.symbol),
|
||||||
iconState = iconStateConverter.convertCustom(
|
iconState = iconStateConverter.convertCustom(
|
||||||
value = status,
|
value = status,
|
||||||
forceGrayscale = noFeeRate,
|
forceGrayscale = hasNoFeeRate,
|
||||||
showCustomTokenBadge = false,
|
showCustomTokenBadge = false,
|
||||||
),
|
),
|
||||||
isFiat = false,
|
isFiat = false,
|
||||||
|
|
@ -80,7 +80,7 @@ class AmountStateConverter(
|
||||||
isFiat = true,
|
isFiat = true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
isSegmentedButtonsEnabled = !noFeeRate,
|
isSegmentedButtonsEnabled = !hasNoFeeRate,
|
||||||
selectedButton = 0,
|
selectedButton = 0,
|
||||||
isRedesignEnabled = false,
|
isRedesignEnabled = false,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -88,12 +88,12 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
|
||||||
PrimaryButtonIconEnd(
|
PrimaryButtonIconEnd(
|
||||||
text = stringResourceSafe(id = R.string.common_approve),
|
text = stringResourceSafe(id = R.string.common_approve),
|
||||||
iconResId = content.walletInteractionIcon,
|
iconResId = content.walletInteractionIcon,
|
||||||
showProgress = data.approveButton.loading,
|
showProgress = data.approveButton.isLoading,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||||
onClick = data.approveButton.onClick,
|
onClick = data.approveButton.onClick,
|
||||||
enabled = data.approveButton.enabled,
|
enabled = data.approveButton.isEnabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
SpacerH12()
|
SpacerH12()
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ enum class ApproveType(val text: TextReference) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ApprovePermissionButton(
|
data class ApprovePermissionButton(
|
||||||
val enabled: Boolean,
|
val isEnabled: Boolean,
|
||||||
val loading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
val onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,9 +105,9 @@ private fun Info(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
notifications.fastForEach {
|
notifications.fastForEach { notificationConfig ->
|
||||||
key(it.hashCode()) {
|
key(notificationConfig.hashCode()) {
|
||||||
Notification(config = it)
|
Notification(config = notificationConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ fun NavigationPrimaryButton(primaryButton: NavigationButton?, modifier: Modifier
|
||||||
}
|
}
|
||||||
button.onClick()
|
button.onClick()
|
||||||
},
|
},
|
||||||
showProgress = button.showProgress,
|
showProgress = button.shouldShowProgress,
|
||||||
colors = color,
|
colors = color,
|
||||||
textStyle = TangemTheme.typography.subtitle1,
|
textStyle = TangemTheme.typography.subtitle1,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
|
|
@ -232,7 +232,7 @@ private fun rememberNavigationButton(button: NavigationButton?): MutableState<Na
|
||||||
button?.iconRes,
|
button?.iconRes,
|
||||||
button?.isIconVisible,
|
button?.isIconVisible,
|
||||||
button?.isEnabled,
|
button?.isEnabled,
|
||||||
button?.showProgress,
|
button?.shouldShowProgress,
|
||||||
button?.textReference,
|
button?.textReference,
|
||||||
) { mutableStateOf(button) }
|
) { mutableStateOf(button) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,22 @@ sealed class NavigationButtonsState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property textReference text
|
* @property textReference text
|
||||||
* @property iconRes icon resource id
|
* @property iconRes icon resource id
|
||||||
* @property isSecondary should set secondary color scheme
|
* @property isSecondary should set secondary color scheme
|
||||||
* @property isIconVisible determines whether icon is visible
|
* @property isIconVisible determines whether icon is visible
|
||||||
* @property showProgress indicates progress state of button
|
* @property shouldShowProgress indicates progress state of button
|
||||||
* @property isEnabled enabled
|
* @property isEnabled enabled
|
||||||
* @property isDimmed determines whether the button content will be dimmed.
|
* @property isDimmed determines whether the button content will be dimmed.
|
||||||
* This property will be ignored if [isEnabled] is `false`.
|
* This property will be ignored if [isEnabled] is `false`.
|
||||||
* @property onClick lambda be invoked when action component is clicked
|
* @property onClick lambda be invoked when action component is clicked
|
||||||
*/
|
*/
|
||||||
data class NavigationButton(
|
data class NavigationButton(
|
||||||
val textReference: TextReference,
|
val textReference: TextReference,
|
||||||
@DrawableRes val iconRes: Int? = null,
|
@DrawableRes val iconRes: Int? = null,
|
||||||
val isSecondary: Boolean = false,
|
val isSecondary: Boolean = false,
|
||||||
val isIconVisible: Boolean = false,
|
val isIconVisible: Boolean = false,
|
||||||
val showProgress: Boolean = false,
|
val shouldShowProgress: Boolean = false,
|
||||||
val isEnabled: Boolean = true,
|
val isEnabled: Boolean = true,
|
||||||
val isDimmed: Boolean = false,
|
val isDimmed: Boolean = false,
|
||||||
val isHapticClick: Boolean = false,
|
val isHapticClick: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ internal object NavigationButtonsPreview {
|
||||||
iconRes = R.drawable.ic_tangem_24,
|
iconRes = R.drawable.ic_tangem_24,
|
||||||
isSecondary = true,
|
isSecondary = true,
|
||||||
isIconVisible = true,
|
isIconVisible = true,
|
||||||
showProgress = false,
|
shouldShowProgress = false,
|
||||||
isEnabled = true,
|
isEnabled = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
) to NavigationButton(
|
) to NavigationButton(
|
||||||
|
|
@ -21,7 +21,7 @@ internal object NavigationButtonsPreview {
|
||||||
iconRes = R.drawable.ic_tangem_24,
|
iconRes = R.drawable.ic_tangem_24,
|
||||||
isSecondary = true,
|
isSecondary = true,
|
||||||
isIconVisible = true,
|
isIconVisible = true,
|
||||||
showProgress = false,
|
shouldShowProgress = false,
|
||||||
isEnabled = true,
|
isEnabled = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
)
|
)
|
||||||
|
|
@ -31,7 +31,7 @@ internal object NavigationButtonsPreview {
|
||||||
iconRes = R.drawable.ic_back_24,
|
iconRes = R.drawable.ic_back_24,
|
||||||
isSecondary = true,
|
isSecondary = true,
|
||||||
isIconVisible = true,
|
isIconVisible = true,
|
||||||
showProgress = false,
|
shouldShowProgress = false,
|
||||||
isEnabled = true,
|
isEnabled = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
)
|
)
|
||||||
|
|
@ -40,7 +40,7 @@ internal object NavigationButtonsPreview {
|
||||||
textReference = resourceReference(R.string.common_close),
|
textReference = resourceReference(R.string.common_close),
|
||||||
isSecondary = false,
|
isSecondary = false,
|
||||||
isIconVisible = false,
|
isIconVisible = false,
|
||||||
showProgress = false,
|
shouldShowProgress = false,
|
||||||
isEnabled = true,
|
isEnabled = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
||||||
val feeName: String,
|
val feeName: String,
|
||||||
val feeSymbol: String,
|
val feeSymbol: String,
|
||||||
val networkName: String,
|
val networkName: String,
|
||||||
val mergeFeeNetworkName: Boolean = false,
|
val shouldMergeFeeNetworkName: Boolean = false,
|
||||||
val onClick: (() -> Unit)? = null,
|
val onClick: (() -> Unit)? = null,
|
||||||
) : Error(
|
) : Error(
|
||||||
title = resourceReference(
|
title = resourceReference(
|
||||||
|
|
@ -101,7 +101,7 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
||||||
text = resourceReference(
|
text = resourceReference(
|
||||||
R.string.common_buy_currency,
|
R.string.common_buy_currency,
|
||||||
wrappedList(
|
wrappedList(
|
||||||
if (mergeFeeNetworkName) {
|
if (shouldMergeFeeNetworkName) {
|
||||||
"$currencyName ($feeSymbol)"
|
"$currencyName ($feeSymbol)"
|
||||||
} else {
|
} else {
|
||||||
feeName
|
feeName
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ object NotificationsFactory {
|
||||||
currencyName = tokenStatus.currency.name,
|
currencyName = tokenStatus.currency.name,
|
||||||
feeName = coinStatus.currency.name,
|
feeName = coinStatus.currency.name,
|
||||||
feeSymbol = coinStatus.currency.symbol,
|
feeSymbol = coinStatus.currency.symbol,
|
||||||
mergeFeeNetworkName = false,
|
shouldMergeFeeNetworkName = false,
|
||||||
onClick = {
|
onClick = {
|
||||||
onClick(coinStatus.currency)
|
onClick(coinStatus.currency)
|
||||||
},
|
},
|
||||||
|
|
@ -303,7 +303,7 @@ object NotificationsFactory {
|
||||||
currencyName = cryptoCurrencyStatus.currency.name,
|
currencyName = cryptoCurrencyStatus.currency.name,
|
||||||
feeName = cryptoCurrencyWarning.coinCurrency.name,
|
feeName = cryptoCurrencyWarning.coinCurrency.name,
|
||||||
feeSymbol = cryptoCurrencyWarning.coinCurrency.symbol,
|
feeSymbol = cryptoCurrencyWarning.coinCurrency.symbol,
|
||||||
mergeFeeNetworkName = shouldMergeFeeNetworkName,
|
shouldMergeFeeNetworkName = shouldMergeFeeNetworkName,
|
||||||
onClick = {
|
onClick = {
|
||||||
onClick(cryptoCurrencyWarning.coinCurrency)
|
onClick(cryptoCurrencyWarning.coinCurrency)
|
||||||
},
|
},
|
||||||
|
|
@ -320,9 +320,9 @@ object NotificationsFactory {
|
||||||
feeName = cryptoCurrencyWarning.feeCurrencyName,
|
feeName = cryptoCurrencyWarning.feeCurrencyName,
|
||||||
feeSymbol = cryptoCurrencyWarning.feeCurrencySymbol,
|
feeSymbol = cryptoCurrencyWarning.feeCurrencySymbol,
|
||||||
networkName = cryptoCurrencyWarning.networkName,
|
networkName = cryptoCurrencyWarning.networkName,
|
||||||
mergeFeeNetworkName = shouldMergeFeeNetworkName,
|
shouldMergeFeeNetworkName = shouldMergeFeeNetworkName,
|
||||||
onClick = {
|
onClick = {
|
||||||
currency?.let {
|
if (currency != null) {
|
||||||
onClick(currency)
|
onClick(currency)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -381,20 +381,22 @@ object NotificationsFactory {
|
||||||
add(NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
|
add(NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
|
||||||
}
|
}
|
||||||
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||||
when (sendingCurrency) {
|
add(
|
||||||
is CryptoCurrency.Coin -> NotificationUM.Cardano.InsufficientBalanceToTransferCoin
|
when (sendingCurrency) {
|
||||||
is CryptoCurrency.Token -> {
|
is CryptoCurrency.Coin -> NotificationUM.Cardano.InsufficientBalanceToTransferCoin
|
||||||
NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
is CryptoCurrency.Token -> {
|
||||||
}
|
NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
||||||
}.let(::add)
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||||
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||||
-> {
|
-> {
|
||||||
dustValue?.let {
|
dustValue?.let { value ->
|
||||||
add(
|
add(
|
||||||
NotificationUM.Error.MinimumAmountError(
|
NotificationUM.Error.MinimumAmountError(
|
||||||
amount = it.format { crypto(sendingCurrency) },
|
amount = value.format { crypto(sendingCurrency) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -219,15 +219,19 @@ class TokenItemStateConverter(
|
||||||
isFlickering = status.value.isFlickering(),
|
isFlickering = status.value.isFlickering(),
|
||||||
icons = buildList {
|
icons = buildList {
|
||||||
if (!status.getStakedBalance().isZero()) {
|
if (!status.getStakedBalance().isZero()) {
|
||||||
TokenItemState.FiatAmountState.Content.IconUM(
|
add(
|
||||||
iconRes = R.drawable.ic_staking_24,
|
TokenItemState.FiatAmountState.Content.IconUM(
|
||||||
tint = IconTint.Accent,
|
iconRes = R.drawable.ic_staking_24,
|
||||||
).let(::add)
|
tint = IconTint.Accent,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (status.value.sources.total == StatusSource.ONLY_CACHE) {
|
if (status.value.sources.total == StatusSource.ONLY_CACHE) {
|
||||||
TokenItemState.FiatAmountState.Content.IconUM(
|
add(
|
||||||
iconRes = R.drawable.ic_error_sync_24,
|
TokenItemState.FiatAmountState.Content.IconUM(
|
||||||
).let(::add)
|
iconRes = R.drawable.ic_error_sync_24,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ fun TokenPriceText(price: String, modifier: Modifier = Modifier, priceChangeType
|
||||||
val generalColor = TangemTheme.colors.text.primary1
|
val generalColor = TangemTheme.colors.text.primary1
|
||||||
|
|
||||||
val color = remember(generalColor) { Animatable(generalColor) }
|
val color = remember(generalColor) { Animatable(generalColor) }
|
||||||
var animationSkipped by remember { mutableStateOf(false) }
|
var isAnimationSkipped by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(price) {
|
LaunchedEffect(price) {
|
||||||
if (animationSkipped.not()) {
|
if (isAnimationSkipped.not()) {
|
||||||
animationSkipped = true
|
isAnimationSkipped = true
|
||||||
return@LaunchedEffect
|
return@LaunchedEffect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ internal class DevFeatureTogglesManager(
|
||||||
private val featureTogglesLocalStorage: LocalTogglesStorage,
|
private val featureTogglesLocalStorage: LocalTogglesStorage,
|
||||||
) : MutableFeatureTogglesManager {
|
) : MutableFeatureTogglesManager {
|
||||||
|
|
||||||
private var fileFeatureTogglesMap: Map<String, Boolean> = getFileFeatureToggles()
|
private val fileFeatureTogglesMap: Map<String, Boolean> = getFileFeatureToggles()
|
||||||
private var featureTogglesMap: MutableMap<String, Boolean> by Delegates.notNull()
|
private var featureTogglesMap: MutableMap<String, Boolean> by Delegates.notNull()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ internal class DefaultVersionProvider @Inject constructor(
|
||||||
|
|
||||||
private fun getVersionName(): String {
|
private fun getVersionName(): String {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
context.packageManager
|
requireNotNull(
|
||||||
.getPackageInfo(
|
context
|
||||||
context.packageName,
|
.packageManager
|
||||||
PackageManager.PackageInfoFlags.of(0),
|
.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(0))
|
||||||
)
|
.versionName,
|
||||||
.versionName!!
|
)
|
||||||
} else {
|
} else {
|
||||||
context.packageManager.getPackageInfo(context.packageName, 0).versionName!!
|
requireNotNull(context.packageManager.getPackageInfo(context.packageName, 0).versionName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
interface ApiConfigsManager {
|
interface ApiConfigsManager {
|
||||||
|
|
||||||
/** Flag that determines whether the manager is initialized */
|
/** Flag that determines whether the manager is initialized */
|
||||||
val isInitialized: StateFlow<Boolean>
|
val initializedState: StateFlow<Boolean>
|
||||||
|
|
||||||
/** Initialize resources */
|
/** Initialize resources */
|
||||||
fun initialize()
|
fun initialize()
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@ internal class DevApiConfigsManager(
|
||||||
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
|
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
|
||||||
field = MutableStateFlow(value = getInitialConfigs())
|
field = MutableStateFlow(value = getInitialConfigs())
|
||||||
|
|
||||||
override val isInitialized: StateFlow<Boolean>
|
override val initializedState: StateFlow<Boolean>
|
||||||
field = MutableStateFlow(value = false)
|
field = MutableStateFlow(value = false)
|
||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
isInitialized.value = false
|
initializedState.value = false
|
||||||
|
|
||||||
appPreferencesStore.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
|
appPreferencesStore.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
@ -45,8 +45,8 @@ internal class DevApiConfigsManager(
|
||||||
savedEnvironments[config.id.name] ?: currentEnvironment
|
savedEnvironments[config.id.name] ?: currentEnvironment
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isInitialized.value) {
|
if (!initializedState.value) {
|
||||||
isInitialized.value = true
|
initializedState.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners(apiConfigs = apiConfigs, savedEnvironments = savedEnvironments)
|
notifyListeners(apiConfigs = apiConfigs, savedEnvironments = savedEnvironments)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ internal class MockApiConfigsManager(
|
||||||
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
|
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
|
||||||
field = MutableStateFlow(value = getInitialConfigs())
|
field = MutableStateFlow(value = getInitialConfigs())
|
||||||
|
|
||||||
override val isInitialized: StateFlow<Boolean> = MutableStateFlow(value = true)
|
override val initializedState: StateFlow<Boolean> = MutableStateFlow(value = true)
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob() + dispatchers.default)
|
private val coroutineScope = CoroutineScope(SupervisorJob() + dispatchers.default)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ internal class ProdApiConfigsManager(
|
||||||
private val apiConfigs: ApiConfigs,
|
private val apiConfigs: ApiConfigs,
|
||||||
) : ApiConfigsManager {
|
) : ApiConfigsManager {
|
||||||
|
|
||||||
override val isInitialized: StateFlow<Boolean> = MutableStateFlow(value = true)
|
override val initializedState: StateFlow<Boolean> = MutableStateFlow(value = true)
|
||||||
|
|
||||||
override fun initialize() = Unit
|
override fun initialize() = Unit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,14 +72,14 @@ sealed class ApiResponseError : Exception() {
|
||||||
|
|
||||||
/** Represents a network error, typically when there's no connectivity. */
|
/** Represents a network error, typically when there's no connectivity. */
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
data object NetworkException : ApiResponseError() {
|
class NetworkException : ApiResponseError() {
|
||||||
private fun readResolve(): Any = NetworkException
|
private fun readResolve(): Any = NetworkException()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Represents a timeout error, typically when the server takes too long to respond. */
|
/** Represents a timeout error, typically when the server takes too long to respond. */
|
||||||
@Suppress("UnusedPrivateMember")
|
@Suppress("UnusedPrivateMember")
|
||||||
data object TimeoutException : ApiResponseError() {
|
class TimeoutException : ApiResponseError() {
|
||||||
private fun readResolve(): Any = TimeoutException
|
private fun readResolve(): Any = TimeoutException()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -87,5 +87,5 @@ sealed class ApiResponseError : Exception() {
|
||||||
*
|
*
|
||||||
* @property cause The exception that caused this error.
|
* @property cause The exception that caused this error.
|
||||||
*/
|
*/
|
||||||
data class UnknownException(override val cause: Throwable) : ApiResponseError()
|
class UnknownException(override val cause: Throwable) : ApiResponseError()
|
||||||
}
|
}
|
||||||
|
|
@ -58,10 +58,10 @@ internal fun Throwable.toApiError(): ApiResponseError = when (this) {
|
||||||
is ConnectException,
|
is ConnectException,
|
||||||
is UnknownHostException,
|
is UnknownHostException,
|
||||||
is SSLHandshakeException,
|
is SSLHandshakeException,
|
||||||
-> ApiResponseError.NetworkException
|
-> ApiResponseError.NetworkException()
|
||||||
is TimeoutException,
|
is TimeoutException,
|
||||||
is TimeoutCancellationException,
|
is TimeoutCancellationException,
|
||||||
is SocketTimeoutException,
|
is SocketTimeoutException,
|
||||||
-> ApiResponseError.TimeoutException
|
-> ApiResponseError.TimeoutException()
|
||||||
else -> ApiResponseError.UnknownException(cause = this)
|
else -> ApiResponseError.UnknownException(cause = this)
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class Asset(
|
data class Asset(
|
||||||
@Json(name = "contractAddress")
|
@Json(name = "contractAddress")
|
||||||
val contractAddress: String,
|
val contractAddress: String,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ data class TokenMarketInfoResponse(
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class Network(
|
data class Network(
|
||||||
@Json(name = "network_id")
|
@Json(name = "network_id")
|
||||||
val networkId: String,
|
val networkId: String,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class OnrampCountryDTO(
|
data class OnrampCountryDTO(
|
||||||
@Json(name = "name")
|
@Json(name = "name")
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ data class TangemPayTxHistoryResponse(
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class Spend(
|
data class Spend(
|
||||||
@Json(name = "amount") val amount: BigDecimal,
|
@Json(name = "amount") val amount: BigDecimal,
|
||||||
@Json(name = "currency") val currency: String,
|
@Json(name = "currency") val currency: String,
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ data class ActionRequestBody(
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class ActionRequestBodyArgs(
|
data class ActionRequestBodyArgs(
|
||||||
@Json(name = "amount")
|
@Json(name = "amount")
|
||||||
val amount: String,
|
val amount: String,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class AddressArgumentDTO(
|
data class AddressArgumentDTO(
|
||||||
@Json(name = "required")
|
@Json(name = "required")
|
||||||
val required: Boolean,
|
val required: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@file:Suppress("BooleanPropertyNaming")
|
||||||
|
|
||||||
package com.tangem.datasource.api.stakekit.models.response.model
|
package com.tangem.datasource.api.stakekit.models.response.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@file:Suppress("BooleanPropertyNaming")
|
||||||
|
|
||||||
package com.tangem.datasource.api.stakekit.models.response.model
|
package com.tangem.datasource.api.stakekit.models.response.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@file:Suppress("BooleanPropertyNaming")
|
||||||
|
|
||||||
package com.tangem.datasource.api.tangemTech.models
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
|
@ -12,12 +14,13 @@ data class CoinsResponse(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class Coin(
|
data class Coin(
|
||||||
@Json(name = "id") val id: String,
|
@Json(name = "id") val id: String,
|
||||||
@Json(name = "name") val name: String,
|
@Json(name = "name") val name: String,
|
||||||
@Json(name = "symbol") val symbol: String,
|
@Json(name = "symbol") val symbol: String,
|
||||||
@Json(name = "active") val active: Boolean,
|
@Json(name = "active") val active: Boolean,
|
||||||
@Json(name = "networks") val networks: List<Network> = listOf(),
|
@Json(name = "networks") val networks: List<Network> = emptyList(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class CreateUserNetworkAccountResponse(
|
data class CreateUserNetworkAccountResponse(
|
||||||
@Json(name = "status") val status: Boolean,
|
@Json(name = "status") val status: Boolean,
|
||||||
@Json(name = "data") val data: AccountCreated,
|
@Json(name = "data") val data: AccountCreated,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass
|
||||||
import com.tangem.common.extensions.calculateHashCode
|
import com.tangem.common.extensions.calculateHashCode
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class UserTokensResponse(
|
data class UserTokensResponse(
|
||||||
@Json(name = "version") val version: Int = 0,
|
@Json(name = "version") val version: Int = 0,
|
||||||
@Json(name = "group") val group: GroupType,
|
@Json(name = "group") val group: GroupType,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class WalletBody(
|
data class WalletBody(
|
||||||
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
|
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
|
||||||
@Json(name = "name") val name: String? = null,
|
@Json(name = "name") val name: String? = null,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Suppress("BooleanPropertyNaming")
|
||||||
data class WalletResponse(
|
data class WalletResponse(
|
||||||
@Json(name = "notifyStatus") val notifyStatus: Boolean,
|
@Json(name = "notifyStatus") val notifyStatus: Boolean,
|
||||||
@Json(name = "id") val id: String,
|
@Json(name = "id") val id: String,
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,8 @@ class AssetLoader @Inject constructor(
|
||||||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||||
parsedConfig.orEmpty()
|
parsedConfig.orEmpty()
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = { throwable ->
|
||||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
Timber.e(throwable, "Failed to load config [$fileName] from assets")
|
||||||
emptyList()
|
emptyList()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,7 @@ internal class RetrofitApiBuilder @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UseEmptyCounterpart")
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
||||||
val excludedApiForLogging: Set<ApiConfig.ID> = setOf(
|
val excludedApiForLogging: Set<ApiConfig.ID> = setOf(
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ internal class DefaultExpressServiceLoader @Inject constructor(
|
||||||
return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } }
|
return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("SuspendFunWithFlowReturnType")
|
||||||
private suspend fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow {
|
private suspend fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow {
|
||||||
val initializationStatus = initializationStatuses.value.get(key = userWalletId)
|
val initializationStatus = initializationStatuses.value.get(key = userWalletId)
|
||||||
if (initializationStatus != null) return initializationStatus
|
if (initializationStatus != null) return initializationStatus
|
||||||
|
|
@ -79,8 +80,8 @@ internal class DefaultExpressServiceLoader @Inject constructor(
|
||||||
val cached = expressAssetsStore.getSyncOrNull(userWalletId)
|
val cached = expressAssetsStore.getSyncOrNull(userWalletId)
|
||||||
val default: InitializationStatusFlow = MutableStateFlow(value = cached?.lceContent() ?: lceLoading())
|
val default: InitializationStatusFlow = MutableStateFlow(value = cached?.lceContent() ?: lceLoading())
|
||||||
|
|
||||||
initializationStatuses.update {
|
initializationStatuses.update { statuses ->
|
||||||
it.toMutableMap().apply {
|
statuses.toMutableMap().apply {
|
||||||
put(key = userWalletId, value = default)
|
put(key = userWalletId, value = default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ object NFTSdkAssetConverter : TwoWayConverter<Pair<Network, SdkNFTAsset>, NFTAss
|
||||||
amount = asset.amount,
|
amount = asset.amount,
|
||||||
decimals = asset.decimals,
|
decimals = asset.decimals,
|
||||||
salePrice = salePrice,
|
salePrice = salePrice,
|
||||||
rarity = asset.rarity?.let {
|
rarity = asset.rarity?.let { rarity ->
|
||||||
NFTAsset.Rarity(
|
NFTAsset.Rarity(
|
||||||
rank = it.rank,
|
rank = rarity.rank,
|
||||||
label = it.label,
|
label = rarity.label,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
media = asset.media?.let {
|
media = asset.media?.let {
|
||||||
|
|
@ -65,22 +65,22 @@ object NFTSdkAssetConverter : TwoWayConverter<Pair<Network, SdkNFTAsset>, NFTAss
|
||||||
amount = value.amount,
|
amount = value.amount,
|
||||||
decimals = value.decimals,
|
decimals = value.decimals,
|
||||||
salePrice = salePrice,
|
salePrice = salePrice,
|
||||||
rarity = value.rarity?.let {
|
rarity = value.rarity?.let { rarity ->
|
||||||
SdkNFTAsset.Rarity(
|
SdkNFTAsset.Rarity(
|
||||||
rank = it.rank,
|
rank = rarity.rank,
|
||||||
label = it.label,
|
label = rarity.label,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
media = value.media?.let {
|
media = value.media?.let { media ->
|
||||||
SdkNFTAsset.Media(
|
SdkNFTAsset.Media(
|
||||||
animationUrl = it.animationUrl,
|
animationUrl = media.animationUrl,
|
||||||
imageUrl = it.imageUrl,
|
imageUrl = media.imageUrl,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
traits = value.traits.map {
|
traits = value.traits.map { trait ->
|
||||||
SdkNFTAsset.Trait(
|
SdkNFTAsset.Trait(
|
||||||
name = it.name,
|
name = trait.name,
|
||||||
value = it.value,
|
value = trait.value,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ inline fun <reified T> AppPreferencesStore.getObject(key: Preferences.Key<String
|
||||||
return flow {
|
return flow {
|
||||||
val adapter = moshi.adapter(T::class.java)
|
val adapter = moshi.adapter(T::class.java)
|
||||||
emitAll(
|
emitAll(
|
||||||
data.map {
|
data.map { prefs ->
|
||||||
try {
|
try {
|
||||||
it[key]?.let(adapter::fromJson) ?: default
|
prefs[key]?.let(adapter::fromJson) ?: default
|
||||||
} catch (e: JsonDataException) {
|
} catch (e: JsonDataException) {
|
||||||
default
|
default
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ internal class DefaultSwapBestRateAnimationStore(
|
||||||
* If true, reset flag to false
|
* If true, reset flag to false
|
||||||
*/
|
*/
|
||||||
override suspend fun getSyncOrNull(): Boolean {
|
override suspend fun getSyncOrNull(): Boolean {
|
||||||
val value = dataStore.getSyncOrNull() ?: true
|
val shouldShowBestRateAnimation = dataStore.getSyncOrNull() ?: true
|
||||||
if (value) {
|
if (shouldShowBestRateAnimation) {
|
||||||
dataStore.store(false)
|
dataStore.store(false)
|
||||||
}
|
}
|
||||||
return value
|
return shouldShowBestRateAnimation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ internal class DefaultTokenReceiveWarningActionStore(
|
||||||
) : TokenReceiveWarningActionStore {
|
) : TokenReceiveWarningActionStore {
|
||||||
|
|
||||||
override suspend fun getSync(): Set<String> {
|
override suspend fun getSync(): Set<String> {
|
||||||
return persistenceStore.data.firstOrNull() ?: emptySet()
|
return persistenceStore.data.firstOrNull().orEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun store(symbol: String) {
|
override suspend fun store(symbol: String) {
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ internal object PendingActionConverter : Converter<BalanceDTO.PendingAction, Pen
|
||||||
maximum = it.maximum,
|
maximum = it.maximum,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
duration = this?.duration?.let {
|
duration = this?.duration?.let { duration ->
|
||||||
PendingAction.PendingActionArgs.Duration(
|
PendingAction.PendingActionArgs.Duration(
|
||||||
required = it.required,
|
required = duration.required,
|
||||||
minimum = it.minimum,
|
minimum = duration.minimum,
|
||||||
maximum = it.maximum,
|
maximum = duration.maximum,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
validatorAddress = this?.validatorAddress?.required,
|
validatorAddress = this?.validatorAddress?.required,
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ internal fun OkHttpClient.Builder.addHeaders(
|
||||||
Interceptor { chain ->
|
Interceptor { chain ->
|
||||||
val request = chain.request().newBuilder().apply {
|
val request = chain.request().newBuilder().apply {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
requestHeaders.forEach {
|
requestHeaders.forEach { header ->
|
||||||
val value = it.value.invoke()
|
val value = header.value.invoke()
|
||||||
if (value.isNotBlank()) addHeader(name = it.key, value = value)
|
if (value.isNotBlank()) addHeader(name = header.key, value = value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.build()
|
}.build()
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class NetworkLogsSaveInterceptor(
|
||||||
|
|
||||||
private fun logResponseMessage(response: Response, startNs: Long) {
|
private fun logResponseMessage(response: Response, startNs: Long) {
|
||||||
val responseHeaders = response.headers
|
val responseHeaders = response.headers
|
||||||
val responseBody = response.body!!
|
val responseBody = requireNotNull(response.body)
|
||||||
val contentLength = responseBody.contentLength()
|
val contentLength = responseBody.contentLength()
|
||||||
|
|
||||||
val message = if (!response.promisesBody()) {
|
val message = if (!response.promisesBody()) {
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ sealed class RequestHeader(vararg pairs: Pair<String, ProviderSuspend<String>>)
|
||||||
fun String.checkHeaderValueOrEmpty(): String {
|
fun String.checkHeaderValueOrEmpty(): String {
|
||||||
for (i in this.indices) {
|
for (i in this.indices) {
|
||||||
val c = this[i]
|
val c = this[i]
|
||||||
val charCondition = c == '\t' || c in '\u0020'..'\u007e'
|
val isChar = c == '\t' || c in '\u0020'..'\u007e'
|
||||||
if (!charCondition) {
|
if (!isChar) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ inline fun <reified M : Model> AppComponentContext.getOrCreateModel(): M = getOr
|
||||||
* @param params The parameters to store in the [ParamsContainer],
|
* @param params The parameters to store in the [ParamsContainer],
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
@Suppress("NullableToStringCall")
|
||||||
inline fun <reified M : Model, reified P : Any> AppComponentContext.getOrCreateModel(
|
inline fun <reified M : Model, reified P : Any> AppComponentContext.getOrCreateModel(
|
||||||
params: P?,
|
params: P?,
|
||||||
messageSender: UiMessageSender? = null,
|
messageSender: UiMessageSender? = null,
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,16 @@ fun Resources.getStringSafe(@StringRes id: Int): String {
|
||||||
*/
|
*/
|
||||||
fun Resources.getStringSafe(@StringRes id: Int, vararg formatArgs: Any): String {
|
fun Resources.getStringSafe(@StringRes id: Int, vararg formatArgs: Any): String {
|
||||||
return runCatching { getString(id, *formatArgs) }
|
return runCatching { getString(id, *formatArgs) }
|
||||||
.recoverCatching {
|
.recoverCatching { throwable ->
|
||||||
// If something goes wrong, returns the resource without arguments
|
// If something goes wrong, returns the resource without arguments
|
||||||
val string = getString(id)
|
val string = getString(id)
|
||||||
|
|
||||||
reportIssue(it, resources = this, id, *formatArgs)
|
reportIssue(
|
||||||
|
throwable = throwable,
|
||||||
|
resources = this,
|
||||||
|
id = id,
|
||||||
|
formatArgs = formatArgs,
|
||||||
|
)
|
||||||
|
|
||||||
string
|
string
|
||||||
}
|
}
|
||||||
|
|
@ -60,8 +65,13 @@ fun Resources.getPluralStringSafe(@PluralsRes id: Int, count: Int, vararg format
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Result<String>.getOrResourceName(resources: Resources, id: Int, vararg formatArgs: Any): String {
|
private fun Result<String>.getOrResourceName(resources: Resources, id: Int, vararg formatArgs: Any): String {
|
||||||
return getOrElse {
|
return getOrElse { throwable ->
|
||||||
reportIssue(it, resources, id, formatArgs)
|
reportIssue(
|
||||||
|
throwable = throwable,
|
||||||
|
resources = resources,
|
||||||
|
id = id,
|
||||||
|
formatArgs = formatArgs,
|
||||||
|
)
|
||||||
|
|
||||||
// If something still goes wrong, returns the resource name
|
// If something still goes wrong, returns the resource name
|
||||||
resources.getResourceEntryName(id)
|
resources.getResourceEntryName(id)
|
||||||
|
|
|
||||||
|
|
@ -178,14 +178,14 @@ fun SelectorDialog(
|
||||||
* Dialog button params
|
* Dialog button params
|
||||||
*
|
*
|
||||||
* @param title Button text. If not provided default values will be used
|
* @param title Button text. If not provided default values will be used
|
||||||
* @param warning If true then button text will be in theme warning color
|
* @param isWarning If true then button text will be in theme warning color
|
||||||
* @param enabled If false button will be disabled
|
* @param isEnabled If false button will be disabled
|
||||||
* @param onClick Button click callback
|
* @param onClick Button click callback
|
||||||
*/
|
*/
|
||||||
data class DialogButtonUM(
|
data class DialogButtonUM(
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val warning: Boolean = false,
|
val isWarning: Boolean = false,
|
||||||
val enabled: Boolean = true,
|
val isEnabled: Boolean = true,
|
||||||
val onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -196,7 +196,7 @@ data class AdditionalTextInputDialogUM(
|
||||||
val label: String? = null,
|
val label: String? = null,
|
||||||
val placeholder: String? = null,
|
val placeholder: String? = null,
|
||||||
val caption: String? = null,
|
val caption: String? = null,
|
||||||
val enabled: Boolean = true,
|
val isEnabled: Boolean = true,
|
||||||
val isError: Boolean = false,
|
val isError: Boolean = false,
|
||||||
val errorText: String? = null,
|
val errorText: String? = null,
|
||||||
)
|
)
|
||||||
|
|
@ -276,7 +276,7 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
||||||
onValueChange = type.onValueChange,
|
onValueChange = type.onValueChange,
|
||||||
isError = type.params.isError,
|
isError = type.params.isError,
|
||||||
errorText = type.params.errorText,
|
errorText = type.params.errorText,
|
||||||
isEnabled = type.params.enabled,
|
isEnabled = type.params.isEnabled,
|
||||||
placeholder = type.params.placeholder,
|
placeholder = type.params.placeholder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -289,7 +289,7 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
||||||
label = type.params.label,
|
label = type.params.label,
|
||||||
placeholder = type.params.placeholder,
|
placeholder = type.params.placeholder,
|
||||||
caption = type.params.caption,
|
caption = type.params.caption,
|
||||||
enabled = type.params.enabled,
|
enabled = type.params.isEnabled,
|
||||||
isError = type.params.isError,
|
isError = type.params.isError,
|
||||||
onValueChange = { newValue ->
|
onValueChange = { newValue ->
|
||||||
type.onValueChange(newValue)
|
type.onValueChange(newValue)
|
||||||
|
|
@ -324,15 +324,15 @@ private fun DialogButtons(
|
||||||
if (dismissButton != null) {
|
if (dismissButton != null) {
|
||||||
DialogButton(
|
DialogButton(
|
||||||
text = dismissButton.title ?: stringResourceSafe(id = R.string.common_cancel),
|
text = dismissButton.title ?: stringResourceSafe(id = R.string.common_cancel),
|
||||||
warning = dismissButton.warning,
|
warning = dismissButton.isWarning,
|
||||||
enabled = dismissButton.enabled,
|
enabled = dismissButton.isEnabled,
|
||||||
onClick = dismissButton.onClick,
|
onClick = dismissButton.onClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DialogButton(
|
DialogButton(
|
||||||
text = confirmButton.title ?: stringResourceSafe(id = R.string.common_ok),
|
text = confirmButton.title ?: stringResourceSafe(id = R.string.common_ok),
|
||||||
warning = confirmButton.warning,
|
warning = confirmButton.isWarning,
|
||||||
enabled = confirmButton.enabled,
|
enabled = confirmButton.isEnabled,
|
||||||
onClick = confirmButton.onClick,
|
onClick = confirmButton.onClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -487,7 +487,7 @@ private fun WarningBasicDialogSample(modifier: Modifier = Modifier) {
|
||||||
message = "All protected passwords will be deleted from the secure storage, you must enter the wallet " +
|
message = "All protected passwords will be deleted from the secure storage, you must enter the wallet " +
|
||||||
"password to work with the app",
|
"password to work with the app",
|
||||||
title = "Attention",
|
title = "Attention",
|
||||||
confirmButton = DialogButtonUM(warning = true) {},
|
confirmButton = DialogButtonUM(isWarning = true) {},
|
||||||
dismissButton = DialogButtonUM {},
|
dismissButton = DialogButtonUM {},
|
||||||
onDismissDialog = {},
|
onDismissDialog = {},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ fun Modifier.edgeFade(
|
||||||
require(value = size > 0.dp) {
|
require(value = size > 0.dp) {
|
||||||
"Size must be greater than '0'"
|
"Size must be greater than '0'"
|
||||||
}
|
}
|
||||||
val animatedSize = animationSpec?.let {
|
val animatedSize = animationSpec?.let { spec ->
|
||||||
animateDpAsState(
|
animateDpAsState(
|
||||||
targetValue = if (isVisible) size else 0.dp,
|
targetValue = if (isVisible) size else 0.dp,
|
||||||
animationSpec = it,
|
animationSpec = spec,
|
||||||
label = "Edge fade width",
|
label = "Edge fade width",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,22 +29,22 @@ fun Modifier.flicker(
|
||||||
targetTextAlpha: Float = 0.4f,
|
targetTextAlpha: Float = 0.4f,
|
||||||
animationDurationMillis: Int = 1500,
|
animationDurationMillis: Int = 1500,
|
||||||
): Modifier = composed {
|
): Modifier = composed {
|
||||||
var alphaChange by remember { mutableStateOf(false) }
|
var shouldAlphaChange by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val alpha: Float by animateFloatAsState(
|
val alpha: Float by animateFloatAsState(
|
||||||
targetValue = if (alphaChange) targetTextAlpha else 1f,
|
targetValue = if (shouldAlphaChange) targetTextAlpha else 1f,
|
||||||
animationSpec = tween(
|
animationSpec = tween(
|
||||||
durationMillis = animationDurationMillis,
|
durationMillis = animationDurationMillis,
|
||||||
easing = CubicBezierEasing(a = 0.45f, b = 0.0f, c = 0.55f, d = 1.0f),
|
easing = CubicBezierEasing(a = 0.45f, b = 0.0f, c = 0.55f, d = 1.0f),
|
||||||
),
|
),
|
||||||
finishedListener = {
|
finishedListener = {
|
||||||
alphaChange = it == 1f && isFlickering
|
shouldAlphaChange = it == 1f && isFlickering
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(isFlickering) {
|
LaunchedEffect(isFlickering) {
|
||||||
if (isFlickering) {
|
if (isFlickering) {
|
||||||
alphaChange = true
|
shouldAlphaChange = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ fun FullScreen(
|
||||||
|
|
||||||
val fullScreenLayout = remember {
|
val fullScreenLayout = remember {
|
||||||
FullScreenLayout(
|
FullScreenLayout(
|
||||||
notTouchable = notTouchable,
|
isNotTouchable = notTouchable,
|
||||||
focusable = focusable,
|
focusable = focusable,
|
||||||
composeView = view,
|
composeView = view,
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
|
|
@ -53,7 +53,7 @@ fun FullScreen(
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor", "ClickableViewAccessibility")
|
@SuppressLint("ViewConstructor", "ClickableViewAccessibility")
|
||||||
private class FullScreenLayout(
|
private class FullScreenLayout(
|
||||||
private val notTouchable: Boolean,
|
private val isNotTouchable: Boolean,
|
||||||
private val focusable: Boolean,
|
private val focusable: Boolean,
|
||||||
private val composeView: View,
|
private val composeView: View,
|
||||||
private val onBackClick: () -> Unit,
|
private val onBackClick: () -> Unit,
|
||||||
|
|
@ -68,10 +68,10 @@ private class FullScreenLayout(
|
||||||
override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
|
override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private var viewShowing = false
|
private var isViewShowing = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (notTouchable) {
|
if (isNotTouchable) {
|
||||||
setOnTouchListener { _, _ -> false }
|
setOnTouchListener { _, _ -> false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,19 +110,19 @@ private class FullScreenLayout(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun show() {
|
fun show() {
|
||||||
if (viewShowing) dismiss()
|
if (isViewShowing) dismiss()
|
||||||
windowManager.addView(this, params)
|
windowManager.addView(this, params)
|
||||||
|
|
||||||
if (focusable.not()) {
|
if (focusable.not()) {
|
||||||
params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notTouchable) {
|
if (isNotTouchable) {
|
||||||
params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
windowManager.updateViewLayout(this, params)
|
windowManager.updateViewLayout(this, params)
|
||||||
viewShowing = true
|
isViewShowing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
|
@ -139,10 +139,10 @@ private class FullScreenLayout(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dismiss() {
|
fun dismiss() {
|
||||||
if (!viewShowing) return
|
if (!isViewShowing) return
|
||||||
disposeComposition()
|
disposeComposition()
|
||||||
windowManager.removeViewImmediate(this)
|
windowManager.removeViewImmediate(this)
|
||||||
viewShowing = false
|
isViewShowing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispose() {
|
fun dispose() {
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ fun keyboardAsState(): State<Keyboard> {
|
||||||
val keyboardState = remember { mutableStateOf(keyboardStateInternal) }
|
val keyboardState = remember { mutableStateOf(keyboardStateInternal) }
|
||||||
|
|
||||||
LaunchedEffect(keyboardStateInternal) {
|
LaunchedEffect(keyboardStateInternal) {
|
||||||
val falsePositive = Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q &&
|
val isFalsePositive = Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q &&
|
||||||
keyboardStateInternal is Keyboard.Opened &&
|
keyboardStateInternal is Keyboard.Opened &&
|
||||||
keyboardStateInternal.height < 50.dp
|
keyboardStateInternal.height < 50.dp
|
||||||
// FIX android <=10 devices can randomly send ime paddings,
|
// FIX android <=10 devices can randomly send ime paddings,
|
||||||
// which leads to a false positive keyboard opening ([REDACTED_TASK_KEY])
|
// which leads to a false positive keyboard opening ([REDACTED_TASK_KEY])
|
||||||
if (falsePositive) return@LaunchedEffect
|
if (isFalsePositive) return@LaunchedEffect
|
||||||
|
|
||||||
keyboardState.value = keyboardStateInternal
|
keyboardState.value = keyboardStateInternal
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ fun ResizableText(
|
||||||
overflow = overflow,
|
overflow = overflow,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
maxLines = maxLines,
|
maxLines = maxLines,
|
||||||
onTextLayout = {
|
onTextLayout = { textLayoutResult ->
|
||||||
if (it.hasVisualOverflow) {
|
if (textLayoutResult.hasVisualOverflow) {
|
||||||
val nextFontSizeValue = fontSizeValue.floatValue - fontSizeRange.step.value
|
val nextFontSizeValue = fontSizeValue.floatValue - fontSizeRange.step.value
|
||||||
if (nextFontSizeValue <= fontSizeRange.min.value) {
|
if (nextFontSizeValue <= fontSizeRange.min.value) {
|
||||||
fontSizeValue.floatValue = fontSizeRange.min.value
|
fontSizeValue.floatValue = fontSizeRange.min.value
|
||||||
|
|
@ -150,8 +150,8 @@ fun ResizableText(
|
||||||
overflow = overflow,
|
overflow = overflow,
|
||||||
softWrap = false,
|
softWrap = false,
|
||||||
maxLines = maxLines,
|
maxLines = maxLines,
|
||||||
onTextLayout = {
|
onTextLayout = { result ->
|
||||||
if (it.hasVisualOverflow) {
|
if (result.hasVisualOverflow) {
|
||||||
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
|
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
|
||||||
if (nextFontSizeValue <= fontSizeRange.min.value) {
|
if (nextFontSizeValue <= fontSizeRange.min.value) {
|
||||||
onFontSizeChange(fontSizeRange.min.value)
|
onFontSizeChange(fontSizeRange.min.value)
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ fun SimpleSettingsRow(
|
||||||
visible = !subtitle.isNullOrEmpty(),
|
visible = !subtitle.isNullOrEmpty(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = subtitle ?: "",
|
text = subtitle.orEmpty(),
|
||||||
style = TangemTheme.typography.body2,
|
style = TangemTheme.typography.body2,
|
||||||
color = rowColors.subtitleColor(enabled = enabled).value,
|
color = rowColors.subtitleColor(enabled = enabled).value,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -169,17 +169,17 @@ private val TangemShimmerColors: List<Color>
|
||||||
|
|
||||||
return buildList {
|
return buildList {
|
||||||
if (isInDarkTheme) {
|
if (isInDarkTheme) {
|
||||||
TangemColorPalette.Dark3.let(::add)
|
add(TangemColorPalette.Dark3)
|
||||||
TangemColorPalette.Dark4.let(::add)
|
add(TangemColorPalette.Dark4)
|
||||||
TangemColorPalette.Dark6.let(::add)
|
add(TangemColorPalette.Dark6)
|
||||||
TangemColorPalette.Dark4.let(::add)
|
add(TangemColorPalette.Dark4)
|
||||||
TangemColorPalette.Dark3.let(::add)
|
add(TangemColorPalette.Dark3)
|
||||||
} else {
|
} else {
|
||||||
TangemColorPalette.Light2.let(::add)
|
add(TangemColorPalette.Light2)
|
||||||
TangemColorPalette.Light1.let(::add)
|
add(TangemColorPalette.Light1)
|
||||||
TangemColorPalette.White.let(::add)
|
add(TangemColorPalette.White)
|
||||||
TangemColorPalette.Light1.let(::add)
|
add(TangemColorPalette.Light1)
|
||||||
TangemColorPalette.Light2.let(::add)
|
add(TangemColorPalette.Light2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,8 @@ private fun TangemTextField(
|
||||||
text = label,
|
text = label,
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = colors.labelColor(
|
color = colors.labelColor(
|
||||||
enabled = enabled,
|
isEnabled = enabled,
|
||||||
error = isError,
|
isError = isError,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
).value,
|
).value,
|
||||||
)
|
)
|
||||||
|
|
@ -174,7 +174,7 @@ private fun TangemTextField(
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (iconRes != null) {
|
iconRes?.let { iconRes ->
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(32.dp),
|
modifier = Modifier.size(32.dp),
|
||||||
onClick = onClear,
|
onClick = onClear,
|
||||||
|
|
@ -182,7 +182,7 @@ private fun TangemTextField(
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
painter = painterResource(id = iconRes!!),
|
painter = painterResource(id = iconRes),
|
||||||
tint = colors.trailingIconColor(enabled = enabled, isError = isError).value,
|
tint = colors.trailingIconColor(enabled = enabled, isError = isError).value,
|
||||||
contentDescription = "Clear input",
|
contentDescription = "Clear input",
|
||||||
)
|
)
|
||||||
|
|
@ -259,8 +259,8 @@ private fun TangemTextFieldWithIcon(
|
||||||
text = label,
|
text = label,
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = colors.labelColor(
|
color = colors.labelColor(
|
||||||
enabled = enabled,
|
isEnabled = enabled,
|
||||||
error = isError,
|
isError = isError,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
).value,
|
).value,
|
||||||
)
|
)
|
||||||
|
|
@ -399,19 +399,19 @@ fun TextFieldColors.trailingIconColor(enabled: Boolean, isError: Boolean): State
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextFieldColors.indicatorColor(
|
fun TextFieldColors.indicatorColor(
|
||||||
enabled: Boolean,
|
isEnabled: Boolean,
|
||||||
isError: Boolean,
|
isError: Boolean,
|
||||||
interactionSource: InteractionSource,
|
interactionSource: InteractionSource,
|
||||||
): State<Color> {
|
): State<Color> {
|
||||||
val focused by interactionSource.collectIsFocusedAsState()
|
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||||
|
|
||||||
val targetValue = when {
|
val targetValue = when {
|
||||||
!enabled -> disabledIndicatorColor
|
!isEnabled -> disabledIndicatorColor
|
||||||
isError -> errorIndicatorColor
|
isError -> errorIndicatorColor
|
||||||
focused -> focusedIndicatorColor
|
isFocused -> focusedIndicatorColor
|
||||||
else -> unfocusedIndicatorColor
|
else -> unfocusedIndicatorColor
|
||||||
}
|
}
|
||||||
return if (enabled) {
|
return if (isEnabled) {
|
||||||
animateColorAsState(
|
animateColorAsState(
|
||||||
targetValue = targetValue,
|
targetValue = targetValue,
|
||||||
animationSpec = tween(durationMillis = 120),
|
animationSpec = tween(durationMillis = 120),
|
||||||
|
|
@ -428,13 +428,17 @@ fun TextFieldColors.placeholderColor(enabled: Boolean): State<Color> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextFieldColors.labelColor(enabled: Boolean, error: Boolean, interactionSource: InteractionSource): State<Color> {
|
fun TextFieldColors.labelColor(
|
||||||
val focused by interactionSource.collectIsFocusedAsState()
|
isEnabled: Boolean,
|
||||||
|
isError: Boolean,
|
||||||
|
interactionSource: InteractionSource,
|
||||||
|
): State<Color> {
|
||||||
|
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||||
|
|
||||||
val targetValue = when {
|
val targetValue = when {
|
||||||
!enabled -> disabledLabelColor
|
!isEnabled -> disabledLabelColor
|
||||||
error -> errorLabelColor
|
isError -> errorLabelColor
|
||||||
focused -> focusedLabelColor
|
isFocused -> focusedLabelColor
|
||||||
else -> unfocusedLabelColor
|
else -> unfocusedLabelColor
|
||||||
}
|
}
|
||||||
return rememberUpdatedState(targetValue)
|
return rememberUpdatedState(targetValue)
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ private fun SubtitleView(subtitle: String, icon: Painter?) {
|
||||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
icon?.let {
|
icon?.let { icon ->
|
||||||
Image(
|
Image(
|
||||||
painter = icon,
|
painter = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
|
|
@ -202,9 +202,9 @@ private fun ExpandedSearchView(
|
||||||
}
|
}
|
||||||
TextField(
|
TextField(
|
||||||
value = textFieldValue,
|
value = textFieldValue,
|
||||||
onValueChange = {
|
onValueChange = { value ->
|
||||||
textFieldValue = it
|
textFieldValue = value
|
||||||
onSearchChange(it.text)
|
onSearchChange(value.text)
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ fun TopAppBarButton(button: TopAppBarButtonUM, tint: Color, modifier: Modifier =
|
||||||
when (button) {
|
when (button) {
|
||||||
is TopAppBarButtonUM.Icon -> {
|
is TopAppBarButtonUM.Icon -> {
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = button.enabled,
|
enabled = button.isEnabled,
|
||||||
modifier = modifier.size(TangemTheme.dimens.size32),
|
modifier = modifier.size(TangemTheme.dimens.size32),
|
||||||
onClick = button.onClicked,
|
onClick = button.onClicked,
|
||||||
) {
|
) {
|
||||||
|
|
@ -36,7 +36,7 @@ fun TopAppBarButton(button: TopAppBarButtonUM, tint: Color, modifier: Modifier =
|
||||||
is TopAppBarButtonUM.Text -> {
|
is TopAppBarButtonUM.Text -> {
|
||||||
Text(
|
Text(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.conditional(button.enabled) {
|
.conditional(button.isEnabled) {
|
||||||
clickable { button.onClicked() }
|
clickable { button.onClicked() }
|
||||||
}
|
}
|
||||||
.padding(4.dp),
|
.padding(4.dp),
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,20 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
|
|
||||||
sealed class TopAppBarButtonUM(
|
sealed class TopAppBarButtonUM(
|
||||||
open val onClicked: () -> Unit,
|
open val onClicked: () -> Unit,
|
||||||
open val enabled: Boolean = true,
|
open val isEnabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class Icon(
|
data class Icon(
|
||||||
@DrawableRes val iconRes: Int,
|
@DrawableRes val iconRes: Int,
|
||||||
override val onClicked: () -> Unit,
|
override val onClicked: () -> Unit,
|
||||||
override val enabled: Boolean = true,
|
override val isEnabled: Boolean = true,
|
||||||
) : TopAppBarButtonUM(onClicked, enabled)
|
) : TopAppBarButtonUM(onClicked, isEnabled)
|
||||||
|
|
||||||
data class Text(
|
data class Text(
|
||||||
val text: TextReference,
|
val text: TextReference,
|
||||||
override val onClicked: () -> Unit,
|
override val onClicked: () -> Unit,
|
||||||
override val enabled: Boolean = true,
|
override val isEnabled: Boolean = true,
|
||||||
) : TopAppBarButtonUM(onClicked, enabled)
|
) : TopAppBarButtonUM(onClicked, isEnabled)
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -29,19 +29,19 @@ sealed class TopAppBarButtonUM(
|
||||||
fun Back(enabled: Boolean = true, onBackClicked: () -> Unit) = Icon(
|
fun Back(enabled: Boolean = true, onBackClicked: () -> Unit) = Icon(
|
||||||
iconRes = R.drawable.ic_back_24,
|
iconRes = R.drawable.ic_back_24,
|
||||||
onClicked = onBackClicked,
|
onClicked = onBackClicked,
|
||||||
enabled = enabled,
|
isEnabled = enabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Close(enabled: Boolean = true, onCloseClick: () -> Unit) = Icon(
|
fun Close(enabled: Boolean = true, onCloseClick: () -> Unit) = Icon(
|
||||||
iconRes = R.drawable.ic_close_24,
|
iconRes = R.drawable.ic_close_24,
|
||||||
onClicked = onCloseClick,
|
onClicked = onCloseClick,
|
||||||
enabled = enabled,
|
isEnabled = enabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Text(text: TextReference, onTextClicked: () -> Unit, enabled: Boolean = true) = Text(
|
fun Text(text: TextReference, onTextClicked: () -> Unit, enabled: Boolean = true) = Text(
|
||||||
text = text,
|
text = text,
|
||||||
onClicked = onTextClicked,
|
onClicked = onTextClicked,
|
||||||
enabled = enabled,
|
isEnabled = enabled,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,8 +50,11 @@ fun ModalBottomSheetWithBackHandling(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.focusRequester(requester)
|
.focusRequester(requester)
|
||||||
.focusable()
|
.focusable()
|
||||||
.onPreviewKeyEvent {
|
.onPreviewKeyEvent { keyEvent ->
|
||||||
if (it.key == Key.Back && it.type == KeyEventType.KeyUp && !it.nativeKeyEvent.isCanceled) {
|
if (keyEvent.key == Key.Back &&
|
||||||
|
keyEvent.type == KeyEventType.KeyUp &&
|
||||||
|
!keyEvent.nativeKeyEvent.isCanceled
|
||||||
|
) {
|
||||||
backPressedDispatcherOwner?.onBackPressedDispatcher?.onBackPressed()
|
backPressedDispatcherOwner?.onBackPressedDispatcher?.onBackPressed()
|
||||||
return@onPreviewKeyEvent true
|
return@onPreviewKeyEvent true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ internal fun Content(model: MessageBottomSheetUM, modifier: Modifier = Modifier)
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = model.primaryAction.text.resolveReference(),
|
text = model.primaryAction.text.resolveReference(),
|
||||||
enabled = model.primaryAction.enabled,
|
enabled = model.primaryAction.isEnabled,
|
||||||
onClick = model.primaryAction.onClick,
|
onClick = model.primaryAction.onClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +93,7 @@ internal fun Content(model: MessageBottomSheetUM, modifier: Modifier = Modifier)
|
||||||
SecondaryButton(
|
SecondaryButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = model.secondaryAction.text.resolveReference(),
|
text = model.secondaryAction.text.resolveReference(),
|
||||||
enabled = model.secondaryAction.enabled,
|
enabled = model.secondaryAction.isEnabled,
|
||||||
onClick = model.secondaryAction.onClick,
|
onClick = model.secondaryAction.onClick,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ data class MessageBottomSheetUM(
|
||||||
|
|
||||||
data class ActionUM(
|
data class ActionUM(
|
||||||
val text: TextReference,
|
val text: TextReference,
|
||||||
val enabled: Boolean = true,
|
val isEnabled: Boolean = true,
|
||||||
val onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -64,8 +64,8 @@ fun MessageBottomSheetV2(state: MessageBottomSheetUMV2, onDismissRequest: () ->
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifier = Modifier) {
|
fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifier = Modifier) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
state.elements.fastForEach {
|
state.elements.fastForEach { element ->
|
||||||
when (it) {
|
when (element) {
|
||||||
is MessageBottomSheetUMV2.InfoBlock -> {
|
is MessageBottomSheetUMV2.InfoBlock -> {
|
||||||
ContentContainer(
|
ContentContainer(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -73,7 +73,7 @@ fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifie
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||||
.padding(bottom = 32.dp),
|
.padding(bottom = 32.dp),
|
||||||
state = it,
|
state = element,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
|
|
@ -94,32 +94,32 @@ private fun ContentContainer(state: MessageBottomSheetUMV2.InfoBlock, modifier:
|
||||||
state.icon?.let {
|
state.icon?.let {
|
||||||
BottomSheetIcon(it)
|
BottomSheetIcon(it)
|
||||||
}
|
}
|
||||||
state.title?.let {
|
state.title?.let { title ->
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = TangemTheme.dimens.spacing24),
|
.padding(top = TangemTheme.dimens.spacing24),
|
||||||
text = it.resolveReference(),
|
text = title.resolveReference(),
|
||||||
style = TangemTheme.typography.h3,
|
style = TangemTheme.typography.h3,
|
||||||
color = TangemTheme.colors.text.primary1,
|
color = TangemTheme.colors.text.primary1,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
state.body?.let {
|
state.body?.let { body ->
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = TangemTheme.dimens.spacing8),
|
.padding(top = TangemTheme.dimens.spacing8),
|
||||||
text = it.resolveReference(),
|
text = body.resolveReference(),
|
||||||
style = TangemTheme.typography.body2,
|
style = TangemTheme.typography.body2,
|
||||||
color = TangemTheme.colors.text.secondary,
|
color = TangemTheme.colors.text.secondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
state.chip?.let {
|
state.chip?.let { chip ->
|
||||||
BottomSheetChip(
|
BottomSheetChip(
|
||||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing16),
|
modifier = Modifier.padding(top = TangemTheme.dimens.spacing16),
|
||||||
chip = it,
|
chip = chip,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,16 +193,16 @@ private fun ButtonsContainer(
|
||||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||||
) {
|
) {
|
||||||
buttons.fastForEach { button ->
|
buttons.fastForEach { button ->
|
||||||
val icon = button.icon?.let {
|
val icon = button.icon?.let { iconResId ->
|
||||||
when (button.iconOrder) {
|
when (button.iconOrder) {
|
||||||
IconOrder.Start -> TangemButtonIconPosition.Start(it)
|
IconOrder.Start -> TangemButtonIconPosition.Start(iconResId)
|
||||||
IconOrder.End -> TangemButtonIconPosition.End(it)
|
IconOrder.End -> TangemButtonIconPosition.End(iconResId)
|
||||||
}
|
}
|
||||||
} ?: TangemButtonIconPosition.None
|
} ?: TangemButtonIconPosition.None
|
||||||
|
|
||||||
TangemButton(
|
TangemButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = button.text?.resolveReference() ?: "",
|
text = button.text?.resolveReference().orEmpty(),
|
||||||
icon = icon,
|
icon = icon,
|
||||||
onClick = { button.onClick?.invoke(closeScope) },
|
onClick = { button.onClick?.invoke(closeScope) },
|
||||||
colors = if (button.isPrimary) {
|
colors = if (button.isPrimary) {
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,10 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheet(
|
||||||
var isVisible by remember { mutableStateOf(value = config.isShown) }
|
var isVisible by remember { mutableStateOf(value = config.isShown) }
|
||||||
val sheetState = rememberModalBottomSheetState(
|
val sheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||||
confirmValueChange = {
|
confirmValueChange = { sheetValue ->
|
||||||
if (!dismissOnClickOutside) {
|
if (!dismissOnClickOutside) {
|
||||||
it != SheetValue.Hidden // Ignore transitions to hidden (prevents dismiss on outside click/back press)
|
// Ignore transitions to hidden (prevents dismiss on outside click/back press)
|
||||||
|
sheetValue != SheetValue.Hidden
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -163,11 +163,11 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
||||||
|
|
||||||
val bsContent: @Composable ColumnScope.() -> Unit = {
|
val bsContent: @Composable ColumnScope.() -> Unit = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.let {
|
modifier = Modifier.let { modifier ->
|
||||||
if (addBottomInsets) {
|
if (addBottomInsets) {
|
||||||
it.padding(bottom = bottomBarHeight)
|
modifier.padding(bottom = bottomBarHeight)
|
||||||
} else {
|
} else {
|
||||||
it
|
modifier
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ private class ActionButtonConfigProvider : CollectionPreviewParameterProvider<Ho
|
||||||
text = TextReference.Str(value = "Exchange"),
|
text = TextReference.Str(value = "Exchange"),
|
||||||
iconResId = R.drawable.ic_exchange_vertical_24,
|
iconResId = R.drawable.ic_exchange_vertical_24,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
showBadge = true,
|
shouldShowBadge = true,
|
||||||
),
|
),
|
||||||
ActionButtonConfig(
|
ActionButtonConfig(
|
||||||
text = TextReference.Str(value = "Send"),
|
text = TextReference.Str(value = "Send"),
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ data class SmallButtonConfig(
|
||||||
val text: TextReference,
|
val text: TextReference,
|
||||||
val onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
val icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
|
val icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
|
||||||
val enabled: Boolean = true,
|
val isEnabled: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,7 +79,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
||||||
color = backgroundColor,
|
color = backgroundColor,
|
||||||
shape = shape,
|
shape = shape,
|
||||||
)
|
)
|
||||||
.clickable(enabled = config.enabled, onClick = config.onClick)
|
.clickable(enabled = config.isEnabled, onClick = config.onClick)
|
||||||
.padding(
|
.padding(
|
||||||
paddingValues = when (config.icon) {
|
paddingValues = when (config.icon) {
|
||||||
is TangemButtonIconPosition.None -> PaddingValues(
|
is TangemButtonIconPosition.None -> PaddingValues(
|
||||||
|
|
@ -103,7 +103,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
||||||
text = {
|
text = {
|
||||||
val textColor by animateColorAsState(
|
val textColor by animateColorAsState(
|
||||||
targetValue = when {
|
targetValue = when {
|
||||||
!config.enabled -> TangemTheme.colors.text.disabled
|
!config.isEnabled -> TangemTheme.colors.text.disabled
|
||||||
isPrimary -> TangemTheme.colors.text.primary2
|
isPrimary -> TangemTheme.colors.text.primary2
|
||||||
else -> TangemTheme.colors.text.primary1
|
else -> TangemTheme.colors.text.primary1
|
||||||
},
|
},
|
||||||
|
|
@ -122,7 +122,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||||
painter = painterResource(id = iconResId),
|
painter = painterResource(id = iconResId),
|
||||||
tint = if (config.enabled) {
|
tint = if (config.isEnabled) {
|
||||||
TangemTheme.colors.icon.secondary
|
TangemTheme.colors.icon.secondary
|
||||||
} else {
|
} else {
|
||||||
TangemTheme.colors.icon.inactive
|
TangemTheme.colors.icon.inactive
|
||||||
|
|
@ -188,7 +188,7 @@ private fun ButtonsSample() {
|
||||||
config = config.copy(
|
config = config.copy(
|
||||||
text = TextReference.Str(value = "Add token"),
|
text = TextReference.Str(value = "Add token"),
|
||||||
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
|
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
|
||||||
enabled = false,
|
isEnabled = false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,14 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
/**
|
/**
|
||||||
* Action button config
|
* Action button config
|
||||||
*
|
*
|
||||||
* @property text text
|
* @property text text
|
||||||
* @property iconResId icon resource id
|
* @property iconResId icon resource id
|
||||||
* @property onClick lambda be invoked when action component is clicked
|
* @property onClick lambda be invoked when action component is clicked
|
||||||
* @property onLongClick lambda be invoked when action component is long clicked
|
* @property onLongClick lambda be invoked when action component is long clicked
|
||||||
* @property enabled enabled
|
* @property isEnabled enabled
|
||||||
* @property dimContent determines whether the button content will be dimmed. This property will be ignored if [enabled]
|
* @property shouldDimContent determines whether the button content will be dimmed. This property will be ignored if [isEnabled] is `false`.
|
||||||
* is `false`.
|
* @property isInProgress indicates progress state of button
|
||||||
* @property isInProgress indicates progress state of button
|
* @property shouldShowBadge display dot in upper right corner
|
||||||
* @property showBadge display dot in upper right corner
|
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
|
@ -23,8 +22,8 @@ data class ActionButtonConfig(
|
||||||
@DrawableRes val iconResId: Int,
|
@DrawableRes val iconResId: Int,
|
||||||
val onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
val onLongClick: (() -> TextReference?)? = null,
|
val onLongClick: (() -> TextReference?)? = null,
|
||||||
val enabled: Boolean = true,
|
val isEnabled: Boolean = true,
|
||||||
val dimContent: Boolean = false,
|
val shouldDimContent: Boolean = false,
|
||||||
val isInProgress: Boolean = false,
|
val isInProgress: Boolean = false,
|
||||||
val showBadge: Boolean = false,
|
val shouldShowBadge: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
@ -55,11 +55,11 @@ fun RoundedActionButton(
|
||||||
ActionBaseButton(
|
ActionBaseButton(
|
||||||
config = config,
|
config = config,
|
||||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius24),
|
shape = RoundedCornerShape(size = TangemTheme.dimens.radius24),
|
||||||
content = {
|
content = { modifier ->
|
||||||
ActionButtonContent(
|
ActionButtonContent(
|
||||||
config = config,
|
config = config,
|
||||||
text = { Text(text = config.text, textColor = it) },
|
text = { color -> Text(text = config.text, textColor = color) },
|
||||||
modifier = it.padding(
|
modifier = modifier.padding(
|
||||||
start = TangemTheme.dimens.spacing16,
|
start = TangemTheme.dimens.spacing16,
|
||||||
end = TangemTheme.dimens.spacing24,
|
end = TangemTheme.dimens.spacing24,
|
||||||
),
|
),
|
||||||
|
|
@ -118,7 +118,7 @@ fun ActionBaseButton(
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val backgroundColor by animateColorAsState(
|
val backgroundColor by animateColorAsState(
|
||||||
targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled,
|
targetValue = if (config.isEnabled) color else TangemTheme.colors.button.disabled,
|
||||||
label = "Update background color",
|
label = "Update background color",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -128,17 +128,17 @@ fun ActionBaseButton(
|
||||||
.widthIn(min = 100.dp)
|
.widthIn(min = 100.dp)
|
||||||
.drawWithContent {
|
.drawWithContent {
|
||||||
drawContent()
|
drawContent()
|
||||||
if (config.showBadge) {
|
if (config.shouldShowBadge) {
|
||||||
drawBadge(containerColor = containerColor)
|
drawBadge(containerColor = containerColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.clip(shape)
|
.clip(shape)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
enabled = config.enabled,
|
enabled = config.isEnabled,
|
||||||
onClick = config.onClick,
|
onClick = config.onClick,
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
val toastReference = config.onLongClick?.invoke()
|
val toastReference = config.onLongClick?.invoke()
|
||||||
toastReference?.let {
|
toastReference?.let { toastReference ->
|
||||||
Toast
|
Toast
|
||||||
.makeText(context, toastReference.resolveReference(context.resources), Toast.LENGTH_SHORT)
|
.makeText(context, toastReference.resolveReference(context.resources), Toast.LENGTH_SHORT)
|
||||||
.show()
|
.show()
|
||||||
|
|
@ -175,8 +175,8 @@ fun ActionButtonContent(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
val iconTint = when {
|
val iconTint = when {
|
||||||
!config.enabled -> TangemTheme.colors.icon.informative
|
!config.isEnabled -> TangemTheme.colors.icon.informative
|
||||||
config.dimContent -> TangemTheme.colors.icon.informative
|
config.shouldDimContent -> TangemTheme.colors.icon.informative
|
||||||
else -> TangemTheme.colors.icon.primary1
|
else -> TangemTheme.colors.icon.primary1
|
||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
|
|
@ -220,8 +220,8 @@ private fun Loading(backgroundColor: Color, modifier: Modifier = Modifier) {
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
fun getTextColor(config: ActionButtonConfig): Color {
|
fun getTextColor(config: ActionButtonConfig): Color {
|
||||||
return when {
|
return when {
|
||||||
!config.enabled -> TangemTheme.colors.text.disabled
|
!config.isEnabled -> TangemTheme.colors.text.disabled
|
||||||
config.dimContent -> TangemTheme.colors.text.tertiary
|
config.shouldDimContent -> TangemTheme.colors.text.tertiary
|
||||||
else -> TangemTheme.colors.text.primary1
|
else -> TangemTheme.colors.text.primary1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -249,27 +249,27 @@ private class ActionStateProvider : CollectionPreviewParameterProvider<ActionBut
|
||||||
ActionButtonConfig(
|
ActionButtonConfig(
|
||||||
text = TextReference.Str(value = "Enabled"),
|
text = TextReference.Str(value = "Enabled"),
|
||||||
iconResId = R.drawable.ic_arrow_up_24,
|
iconResId = R.drawable.ic_arrow_up_24,
|
||||||
enabled = true,
|
isEnabled = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
showBadge = true,
|
shouldShowBadge = true,
|
||||||
),
|
),
|
||||||
ActionButtonConfig(
|
ActionButtonConfig(
|
||||||
text = TextReference.Str(value = "Dimmed"),
|
text = TextReference.Str(value = "Dimmed"),
|
||||||
iconResId = R.drawable.ic_arrow_up_24,
|
iconResId = R.drawable.ic_arrow_up_24,
|
||||||
enabled = true,
|
isEnabled = true,
|
||||||
dimContent = true,
|
shouldDimContent = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
ActionButtonConfig(
|
ActionButtonConfig(
|
||||||
text = TextReference.Str(value = "Disabled"),
|
text = TextReference.Str(value = "Disabled"),
|
||||||
iconResId = R.drawable.ic_arrow_down_24,
|
iconResId = R.drawable.ic_arrow_down_24,
|
||||||
enabled = false,
|
isEnabled = false,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
ActionButtonConfig(
|
ActionButtonConfig(
|
||||||
text = TextReference.Str(value = "Loading"),
|
text = TextReference.Str(value = "Loading"),
|
||||||
iconResId = R.drawable.ic_arrow_down_24,
|
iconResId = R.drawable.ic_arrow_down_24,
|
||||||
enabled = false,
|
isEnabled = false,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
isInProgress = true,
|
isInProgress = true,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -112,9 +112,9 @@ private fun SegmentedButtonsPreview(
|
||||||
SegmentedButtons(
|
SegmentedButtons(
|
||||||
config = config,
|
config = config,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
) {
|
) { configPreview ->
|
||||||
Text(
|
Text(
|
||||||
text = it.text,
|
text = configPreview.text,
|
||||||
modifier = Modifier.padding(TangemTheme.dimens.spacing16),
|
modifier = Modifier.padding(TangemTheme.dimens.spacing16),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ private fun BoxScope.ContentIconContainer(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icon.showCustomBadge) {
|
if (icon.shouldShowCustomBadge) {
|
||||||
CurrencyIconBottomBadge(
|
CurrencyIconBottomBadge(
|
||||||
modifier = Modifier.align(Alignment.BottomEnd),
|
modifier = Modifier.align(Alignment.BottomEnd),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
sealed class CurrencyIconState {
|
sealed class CurrencyIconState {
|
||||||
|
|
||||||
abstract val isGrayscale: Boolean
|
abstract val isGrayscale: Boolean
|
||||||
abstract val showCustomBadge: Boolean
|
abstract val shouldShowCustomBadge: Boolean
|
||||||
abstract val topBadgeIconResId: Int?
|
abstract val topBadgeIconResId: Int?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,13 +24,13 @@ sealed class CurrencyIconState {
|
||||||
* @property url The URL where the coin icon can be fetched from. May be `null` if not found.
|
* @property url The URL where the coin icon can be fetched from. May be `null` if not found.
|
||||||
* @property fallbackResId The drawable resource ID to be used as a fallback if the URL is not available.
|
* @property fallbackResId The drawable resource ID to be used as a fallback if the URL is not available.
|
||||||
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
||||||
* @property showCustomBadge Specifies whether to show the custom token badge.
|
* @property shouldShowCustomBadge Specifies whether to show the custom token badge.
|
||||||
*/
|
*/
|
||||||
data class CoinIcon(
|
data class CoinIcon(
|
||||||
val url: String?,
|
val url: String?,
|
||||||
@DrawableRes val fallbackResId: Int,
|
@DrawableRes val fallbackResId: Int,
|
||||||
override val isGrayscale: Boolean,
|
override val isGrayscale: Boolean,
|
||||||
override val showCustomBadge: Boolean,
|
override val shouldShowCustomBadge: Boolean,
|
||||||
) : CurrencyIconState() {
|
) : CurrencyIconState() {
|
||||||
|
|
||||||
override val topBadgeIconResId: Int? = null
|
override val topBadgeIconResId: Int? = null
|
||||||
|
|
@ -42,7 +42,7 @@ sealed class CurrencyIconState {
|
||||||
* @property url The URL where the token icon can be fetched from. May be `null` if not found.
|
* @property url The URL where the token icon can be fetched from. May be `null` if not found.
|
||||||
* @property topBadgeIconResId The drawable resource ID for the network badge. May be `null`.
|
* @property topBadgeIconResId The drawable resource ID for the network badge. May be `null`.
|
||||||
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
||||||
* @property showCustomBadge Specifies whether to show the custom token badge.
|
* @property shouldShowCustomBadge Specifies whether to show the custom token badge.
|
||||||
* @property fallbackTint The color to be used for tinting the fallback icon.
|
* @property fallbackTint The color to be used for tinting the fallback icon.
|
||||||
* @property fallbackBackground The background color to be used for the fallback icon.
|
* @property fallbackBackground The background color to be used for the fallback icon.
|
||||||
*/
|
*/
|
||||||
|
|
@ -50,7 +50,7 @@ sealed class CurrencyIconState {
|
||||||
val url: String?,
|
val url: String?,
|
||||||
@DrawableRes override val topBadgeIconResId: Int?,
|
@DrawableRes override val topBadgeIconResId: Int?,
|
||||||
override val isGrayscale: Boolean,
|
override val isGrayscale: Boolean,
|
||||||
override val showCustomBadge: Boolean,
|
override val shouldShowCustomBadge: Boolean,
|
||||||
val fallbackTint: Color,
|
val fallbackTint: Color,
|
||||||
val fallbackBackground: Color,
|
val fallbackBackground: Color,
|
||||||
) : CurrencyIconState()
|
) : CurrencyIconState()
|
||||||
|
|
@ -62,14 +62,14 @@ sealed class CurrencyIconState {
|
||||||
* @property background The background color to be used for the icon.
|
* @property background The background color to be used for the icon.
|
||||||
* @property topBadgeIconResId The drawable resource ID for the network badge.
|
* @property topBadgeIconResId The drawable resource ID for the network badge.
|
||||||
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
||||||
* @property showCustomBadge Specifies whether to show the custom token badge.
|
* @property shouldShowCustomBadge Specifies whether to show the custom token badge.
|
||||||
*/
|
*/
|
||||||
data class CustomTokenIcon(
|
data class CustomTokenIcon(
|
||||||
val tint: Color,
|
val tint: Color,
|
||||||
val background: Color,
|
val background: Color,
|
||||||
@DrawableRes override val topBadgeIconResId: Int,
|
@DrawableRes override val topBadgeIconResId: Int,
|
||||||
override val isGrayscale: Boolean,
|
override val isGrayscale: Boolean,
|
||||||
override val showCustomBadge: Boolean = true,
|
override val shouldShowCustomBadge: Boolean = true,
|
||||||
) : CurrencyIconState()
|
) : CurrencyIconState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -83,13 +83,13 @@ sealed class CurrencyIconState {
|
||||||
@DrawableRes val fallbackResId: Int,
|
@DrawableRes val fallbackResId: Int,
|
||||||
) : CurrencyIconState() {
|
) : CurrencyIconState() {
|
||||||
override val isGrayscale: Boolean = false
|
override val isGrayscale: Boolean = false
|
||||||
override val showCustomBadge: Boolean = false
|
override val shouldShowCustomBadge: Boolean = false
|
||||||
override val topBadgeIconResId: Int? = null
|
override val topBadgeIconResId: Int? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class CryptoPortfolio : CurrencyIconState() {
|
sealed class CryptoPortfolio : CurrencyIconState() {
|
||||||
override val showCustomBadge: Boolean = false
|
override val shouldShowCustomBadge: Boolean = false
|
||||||
override val topBadgeIconResId: Int? = null
|
override val topBadgeIconResId: Int? = null
|
||||||
abstract val color: Color
|
abstract val color: Color
|
||||||
|
|
||||||
|
|
@ -108,13 +108,13 @@ sealed class CurrencyIconState {
|
||||||
|
|
||||||
data object Loading : CurrencyIconState() {
|
data object Loading : CurrencyIconState() {
|
||||||
override val isGrayscale: Boolean = false
|
override val isGrayscale: Boolean = false
|
||||||
override val showCustomBadge: Boolean = false
|
override val shouldShowCustomBadge: Boolean = false
|
||||||
override val topBadgeIconResId: Int? = null
|
override val topBadgeIconResId: Int? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
data object Locked : CurrencyIconState() {
|
data object Locked : CurrencyIconState() {
|
||||||
override val isGrayscale: Boolean = false
|
override val isGrayscale: Boolean = false
|
||||||
override val showCustomBadge: Boolean = false
|
override val shouldShowCustomBadge: Boolean = false
|
||||||
override val topBadgeIconResId: Int? = null
|
override val topBadgeIconResId: Int? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,27 +122,27 @@ sealed class CurrencyIconState {
|
||||||
@DrawableRes val resId: Int = R.drawable.ic_empty_64,
|
@DrawableRes val resId: Int = R.drawable.ic_empty_64,
|
||||||
) : CurrencyIconState() {
|
) : CurrencyIconState() {
|
||||||
override val isGrayscale: Boolean = true
|
override val isGrayscale: Boolean = true
|
||||||
override val showCustomBadge: Boolean = false
|
override val shouldShowCustomBadge: Boolean = false
|
||||||
override val topBadgeIconResId: Int? = null
|
override val topBadgeIconResId: Int? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copySealed(
|
fun copySealed(
|
||||||
isGrayscale: Boolean = this.isGrayscale,
|
isGrayscale: Boolean = this.isGrayscale,
|
||||||
showCustomBadge: Boolean = this.showCustomBadge,
|
showCustomBadge: Boolean = this.shouldShowCustomBadge,
|
||||||
topBadgeIconResId: Int? = this.topBadgeIconResId,
|
topBadgeIconResId: Int? = this.topBadgeIconResId,
|
||||||
): CurrencyIconState = when (this) {
|
): CurrencyIconState = when (this) {
|
||||||
is CoinIcon -> copy(
|
is CoinIcon -> copy(
|
||||||
isGrayscale = isGrayscale,
|
isGrayscale = isGrayscale,
|
||||||
showCustomBadge = showCustomBadge,
|
shouldShowCustomBadge = showCustomBadge,
|
||||||
)
|
)
|
||||||
is CustomTokenIcon -> copy(
|
is CustomTokenIcon -> copy(
|
||||||
isGrayscale = isGrayscale,
|
isGrayscale = isGrayscale,
|
||||||
showCustomBadge = showCustomBadge,
|
shouldShowCustomBadge = showCustomBadge,
|
||||||
topBadgeIconResId = topBadgeIconResId ?: this.topBadgeIconResId,
|
topBadgeIconResId = topBadgeIconResId ?: this.topBadgeIconResId,
|
||||||
)
|
)
|
||||||
is TokenIcon -> copy(
|
is TokenIcon -> copy(
|
||||||
isGrayscale = isGrayscale,
|
isGrayscale = isGrayscale,
|
||||||
showCustomBadge = showCustomBadge,
|
shouldShowCustomBadge = showCustomBadge,
|
||||||
topBadgeIconResId = topBadgeIconResId,
|
topBadgeIconResId = topBadgeIconResId,
|
||||||
)
|
)
|
||||||
is CryptoPortfolio.Icon -> copy(
|
is CryptoPortfolio.Icon -> copy(
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ object CurrencyIconStateBuilder {
|
||||||
url = url,
|
url = url,
|
||||||
fallbackResId = fallbackResId,
|
fallbackResId = fallbackResId,
|
||||||
isGrayscale = isGrayscale,
|
isGrayscale = isGrayscale,
|
||||||
showCustomBadge = showCustomBadge,
|
shouldShowCustomBadge = showCustomBadge,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createTokenIcon(
|
private fun createTokenIcon(
|
||||||
|
|
@ -44,7 +44,7 @@ object CurrencyIconStateBuilder {
|
||||||
isGrayscale = isGrayscale,
|
isGrayscale = isGrayscale,
|
||||||
fallbackTint = fallbackTint,
|
fallbackTint = fallbackTint,
|
||||||
fallbackBackground = fallbackBackground,
|
fallbackBackground = fallbackBackground,
|
||||||
showCustomBadge = showCustomBadge,
|
shouldShowCustomBadge = showCustomBadge,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createCustomTokenIcon(
|
private fun createCustomTokenIcon(
|
||||||
|
|
@ -58,7 +58,7 @@ object CurrencyIconStateBuilder {
|
||||||
background = background,
|
background = background,
|
||||||
topBadgeIconResId = topBadgeIconResId,
|
topBadgeIconResId = topBadgeIconResId,
|
||||||
isGrayscale = isGrayscale,
|
isGrayscale = isGrayscale,
|
||||||
showCustomBadge = showCustomBadge,
|
shouldShowCustomBadge = showCustomBadge,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun fromCoin(
|
private fun fromCoin(
|
||||||
|
|
@ -77,8 +77,8 @@ object CurrencyIconStateBuilder {
|
||||||
isGrayscale: Boolean = false,
|
isGrayscale: Boolean = false,
|
||||||
showCustomBadge: Boolean = true,
|
showCustomBadge: Boolean = true,
|
||||||
): CurrencyIconState {
|
): CurrencyIconState {
|
||||||
val grayScale = isGrayscale || token.network.isTestnet
|
val isGrayscaleOrTestnet = isGrayscale || token.network.isTestnet
|
||||||
val background = token.tryGetBackgroundForTokenIcon(grayScale)
|
val background = token.tryGetBackgroundForTokenIcon(isGrayscaleOrTestnet)
|
||||||
val tint = getTintForTokenIcon(background)
|
val tint = getTintForTokenIcon(background)
|
||||||
|
|
||||||
return if (token.isCustom && token.iconUrl == null) {
|
return if (token.isCustom && token.iconUrl == null) {
|
||||||
|
|
@ -86,14 +86,14 @@ object CurrencyIconStateBuilder {
|
||||||
tint = tint,
|
tint = tint,
|
||||||
background = background,
|
background = background,
|
||||||
topBadgeIconResId = token.networkIconResId,
|
topBadgeIconResId = token.networkIconResId,
|
||||||
isGrayscale = grayScale,
|
isGrayscale = isGrayscaleOrTestnet,
|
||||||
showCustomBadge = showCustomBadge,
|
showCustomBadge = showCustomBadge,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
createTokenIcon(
|
createTokenIcon(
|
||||||
url = token.iconUrl,
|
url = token.iconUrl,
|
||||||
topBadgeIconResId = token.networkIconResId,
|
topBadgeIconResId = token.networkIconResId,
|
||||||
isGrayscale = grayScale,
|
isGrayscale = isGrayscaleOrTestnet,
|
||||||
fallbackTint = tint,
|
fallbackTint = tint,
|
||||||
fallbackBackground = background,
|
fallbackBackground = background,
|
||||||
showCustomBadge = token.isCustom && showCustomBadge,
|
showCustomBadge = token.isCustom && showCustomBadge,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class CryptoCurrencyToIconStateConverter(
|
||||||
url = coin.iconUrl,
|
url = coin.iconUrl,
|
||||||
fallbackResId = coin.networkIconResId,
|
fallbackResId = coin.networkIconResId,
|
||||||
isGrayscale = forceGrayscale || coin.network.isTestnet || isUnreachable || !isAvailable,
|
isGrayscale = forceGrayscale || coin.network.isTestnet || isUnreachable || !isAvailable,
|
||||||
showCustomBadge = coin.isCustom && showCustomBadge,
|
shouldShowCustomBadge = coin.isCustom && showCustomBadge,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,8 +72,8 @@ class CryptoCurrencyToIconStateConverter(
|
||||||
showCustomBadge: Boolean = true,
|
showCustomBadge: Boolean = true,
|
||||||
forceGrayscale: Boolean = false,
|
forceGrayscale: Boolean = false,
|
||||||
): CurrencyIconState {
|
): CurrencyIconState {
|
||||||
val grayScale = forceGrayscale || token.network.isTestnet || isErrorStatus || !isAvailable
|
val isGrayscale = forceGrayscale || token.network.isTestnet || isErrorStatus || !isAvailable
|
||||||
val background = token.tryGetBackgroundForTokenIcon(grayScale)
|
val background = token.tryGetBackgroundForTokenIcon(isGrayscale)
|
||||||
val tint = getTintForTokenIcon(background)
|
val tint = getTintForTokenIcon(background)
|
||||||
|
|
||||||
return if (token.isCustom && token.iconUrl == null) {
|
return if (token.isCustom && token.iconUrl == null) {
|
||||||
|
|
@ -81,17 +81,17 @@ class CryptoCurrencyToIconStateConverter(
|
||||||
tint = tint,
|
tint = tint,
|
||||||
background = background,
|
background = background,
|
||||||
topBadgeIconResId = token.networkIconResId,
|
topBadgeIconResId = token.networkIconResId,
|
||||||
isGrayscale = grayScale,
|
isGrayscale = isGrayscale,
|
||||||
showCustomBadge = showCustomBadge,
|
shouldShowCustomBadge = showCustomBadge,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
CurrencyIconState.TokenIcon(
|
CurrencyIconState.TokenIcon(
|
||||||
url = token.iconUrl,
|
url = token.iconUrl,
|
||||||
topBadgeIconResId = token.networkIconResId,
|
topBadgeIconResId = token.networkIconResId,
|
||||||
isGrayscale = grayScale,
|
isGrayscale = isGrayscale,
|
||||||
fallbackTint = tint,
|
fallbackTint = tint,
|
||||||
fallbackBackground = background,
|
fallbackBackground = background,
|
||||||
showCustomBadge = token.isCustom && showCustomBadge, // `true` for tokens with custom derivation
|
shouldShowCustomBadge = token.isCustom && showCustomBadge, // `true` for tokens with custom derivation
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,9 +221,9 @@ internal data class DropdownMenuPositionProvider(
|
||||||
val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height
|
val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height
|
||||||
val toCenter = anchorBounds.top - popupContentSize.height / 2
|
val toCenter = anchorBounds.top - popupContentSize.height / 2
|
||||||
val toDisplayBottom = windowSize.height - popupContentSize.height - verticalMargin
|
val toDisplayBottom = windowSize.height - popupContentSize.height - verticalMargin
|
||||||
val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull {
|
val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull { element ->
|
||||||
it >= verticalMargin &&
|
element >= verticalMargin &&
|
||||||
it + popupContentSize.height <= windowSize.height - verticalMargin
|
element + popupContentSize.height <= windowSize.height - verticalMargin
|
||||||
} ?: toTop
|
} ?: toTop
|
||||||
|
|
||||||
onPositionCalculated(
|
onPositionCalculated(
|
||||||
|
|
|
||||||
|
|
@ -189,24 +189,24 @@ private class AmountTextFieldPreviewProvider : PreviewParameterProvider<AmountTe
|
||||||
value = "1000000,123123",
|
value = "1000000,123123",
|
||||||
decimals = 3,
|
decimals = 3,
|
||||||
placeholderAlignment = TopStart,
|
placeholderAlignment = TopStart,
|
||||||
showPlaceholder = true,
|
shouldShowPlaceholder = true,
|
||||||
),
|
),
|
||||||
AmountTextFieldPreviewData(
|
AmountTextFieldPreviewData(
|
||||||
value = "1000000.123123",
|
value = "1000000.123123",
|
||||||
decimals = 6,
|
decimals = 6,
|
||||||
placeholderAlignment = TopStart,
|
placeholderAlignment = TopStart,
|
||||||
showPlaceholder = false,
|
shouldShowPlaceholder = false,
|
||||||
),
|
),
|
||||||
AmountTextFieldPreviewData(
|
AmountTextFieldPreviewData(
|
||||||
value = null,
|
value = null,
|
||||||
decimals = 2,
|
decimals = 2,
|
||||||
showPlaceholder = true,
|
shouldShowPlaceholder = true,
|
||||||
placeholderAlignment = TopCenter,
|
placeholderAlignment = TopCenter,
|
||||||
),
|
),
|
||||||
AmountTextFieldPreviewData(
|
AmountTextFieldPreviewData(
|
||||||
value = null,
|
value = null,
|
||||||
decimals = 2,
|
decimals = 2,
|
||||||
showPlaceholder = true,
|
shouldShowPlaceholder = true,
|
||||||
placeholderAlignment = TopStart,
|
placeholderAlignment = TopStart,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -215,7 +215,7 @@ private class AmountTextFieldPreviewProvider : PreviewParameterProvider<AmountTe
|
||||||
private data class AmountTextFieldPreviewData(
|
private data class AmountTextFieldPreviewData(
|
||||||
val value: String? = null,
|
val value: String? = null,
|
||||||
val decimals: Int = 2,
|
val decimals: Int = 2,
|
||||||
val showPlaceholder: Boolean,
|
val shouldShowPlaceholder: Boolean,
|
||||||
val placeholderAlignment: Alignment,
|
val placeholderAlignment: Alignment,
|
||||||
)
|
)
|
||||||
// endregion
|
// endregion
|
||||||
|
|
@ -72,7 +72,12 @@ fun AutoSizeTextField(
|
||||||
) {
|
) {
|
||||||
BoxWithConstraints(modifier = boxModifier) {
|
BoxWithConstraints(modifier = boxModifier) {
|
||||||
val fontSize = if (isAutoResize) {
|
val fontSize = if (isAutoResize) {
|
||||||
resizeFont(visualTransformation, value, textStyle, reduceFactor)
|
resizeFont(
|
||||||
|
visualTransformation = visualTransformation,
|
||||||
|
value = value,
|
||||||
|
textStyle = textStyle,
|
||||||
|
reduceFactor = reduceFactor,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
textStyle.fontSize
|
textStyle.fontSize
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +146,7 @@ private fun AmountTextFieldPreview(
|
||||||
textFieldModifier = Modifier.fillMaxWidth(),
|
textFieldModifier = Modifier.fillMaxWidth(),
|
||||||
value = text,
|
value = text,
|
||||||
onValueChange = { text = it },
|
onValueChange = { text = it },
|
||||||
centered = data.centered,
|
centered = data.isCentered,
|
||||||
isAutoResize = data.isAutoResize,
|
isAutoResize = data.isAutoResize,
|
||||||
placeholder = data.placeholder,
|
placeholder = data.placeholder,
|
||||||
)
|
)
|
||||||
|
|
@ -154,43 +159,43 @@ private class AutoSizeTextFieldPreviewProvider : PreviewParameterProvider<AutoSi
|
||||||
value = "AutoSizeTextField",
|
value = "AutoSizeTextField",
|
||||||
placeholder = stringReference("placeholder"),
|
placeholder = stringReference("placeholder"),
|
||||||
isAutoResize = true,
|
isAutoResize = true,
|
||||||
centered = false,
|
isCentered = false,
|
||||||
),
|
),
|
||||||
AutoSizeTextFieldPreviewData(
|
AutoSizeTextFieldPreviewData(
|
||||||
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
|
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
|
||||||
placeholder = stringReference("placeholder"),
|
placeholder = stringReference("placeholder"),
|
||||||
isAutoResize = true,
|
isAutoResize = true,
|
||||||
centered = false,
|
isCentered = false,
|
||||||
),
|
),
|
||||||
AutoSizeTextFieldPreviewData(
|
AutoSizeTextFieldPreviewData(
|
||||||
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
|
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
|
||||||
placeholder = stringReference("Placeholder"),
|
placeholder = stringReference("Placeholder"),
|
||||||
isAutoResize = true,
|
isAutoResize = true,
|
||||||
centered = false,
|
isCentered = false,
|
||||||
),
|
),
|
||||||
AutoSizeTextFieldPreviewData(
|
AutoSizeTextFieldPreviewData(
|
||||||
value = "",
|
value = "",
|
||||||
placeholder = stringReference("Placeholder"),
|
placeholder = stringReference("Placeholder"),
|
||||||
isAutoResize = true,
|
isAutoResize = true,
|
||||||
centered = false,
|
isCentered = false,
|
||||||
),
|
),
|
||||||
AutoSizeTextFieldPreviewData(
|
AutoSizeTextFieldPreviewData(
|
||||||
value = "AutoSizeTextField",
|
value = "AutoSizeTextField",
|
||||||
placeholder = stringReference("Placeholder"),
|
placeholder = stringReference("Placeholder"),
|
||||||
isAutoResize = false,
|
isAutoResize = false,
|
||||||
centered = true,
|
isCentered = true,
|
||||||
),
|
),
|
||||||
AutoSizeTextFieldPreviewData(
|
AutoSizeTextFieldPreviewData(
|
||||||
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
|
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
|
||||||
placeholder = stringReference("Placeholder"),
|
placeholder = stringReference("Placeholder"),
|
||||||
isAutoResize = false,
|
isAutoResize = false,
|
||||||
centered = true,
|
isCentered = true,
|
||||||
),
|
),
|
||||||
AutoSizeTextFieldPreviewData(
|
AutoSizeTextFieldPreviewData(
|
||||||
value = "",
|
value = "",
|
||||||
placeholder = stringReference("Placeholder"),
|
placeholder = stringReference("Placeholder"),
|
||||||
isAutoResize = false,
|
isAutoResize = false,
|
||||||
centered = true,
|
isCentered = true,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -199,6 +204,6 @@ private data class AutoSizeTextFieldPreviewData(
|
||||||
val value: String,
|
val value: String,
|
||||||
val placeholder: TextReference,
|
val placeholder: TextReference,
|
||||||
val isAutoResize: Boolean,
|
val isAutoResize: Boolean,
|
||||||
val centered: Boolean,
|
val isCentered: Boolean,
|
||||||
)
|
)
|
||||||
// endregion
|
// endregion
|
||||||
|
|
@ -47,9 +47,9 @@ fun PinTextField(
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = textFieldValue,
|
value = textFieldValue,
|
||||||
onValueChange = {
|
onValueChange = { value ->
|
||||||
if (it.text.length <= length) {
|
if (value.text.length <= length) {
|
||||||
onValueChange(it.text)
|
onValueChange(value.text)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
|
||||||
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