Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-30 16:48:26 +03:00
parent acf110dbcc
commit 6910f74fd9
242 changed files with 866 additions and 732 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -105,9 +105,9 @@ private fun Info(
) )
} }
notifications.fastForEach { notifications.fastForEach { notificationConfig ->
key(it.hashCode()) { key(notificationConfig.hashCode()) {
Notification(config = it) Notification(config = notificationConfig)
} }
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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