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,
confirmButton = DialogButtonUM(
title = dialog.confirmText.resolveReference(),
warning = true,
isWarning = true,
onClick = dialog.onConfirm,
),
dismissButton = DialogButtonUM(

View file

@ -208,7 +208,7 @@ private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
),
confirmButton = DialogButtonUM(
title = stringResourceSafe(id = R.string.card_settings_action_sheet_reset),
warning = true,
isWarning = true,
onClick = dialog.onConfirmClick,
),
onDismissDialog = dialog.onDismiss,

View file

@ -165,7 +165,7 @@ internal class MainViewModel @Inject constructor(
private fun launchAPIRequests(function: suspend CoroutineScope.() -> Unit) {
viewModelScope.launch {
if (BuildConfig.TESTER_MENU_ENABLED) {
apiConfigsManager.isInitialized
apiConfigsManager.initializedState
.filter { it }
.first() // wait until isInitialized becomes true

View file

@ -185,7 +185,7 @@ internal class ChildFactory @Inject constructor(
params = MarketsTokenDetailsComponent.Params(
token = route.token,
appCurrency = route.appCurrency,
showPortfolio = route.showPortfolio,
shouldShowPortfolio = route.shouldShowPortfolio,
analyticsParams = route.analyticsParams?.let { params ->
MarketsTokenDetailsComponent.AnalyticsParams(
blockchain = params.blockchain,
@ -203,7 +203,7 @@ internal class ChildFactory @Inject constructor(
userWalletId = route.portfolioId.userWalletId, // todo account portfolioId param,
cryptoCurrency = route.currency,
source = route.source,
launchSepa = route.launchSepa,
shouldLaunchSepa = route.shouldLaunchSepa,
),
componentFactory = onrampComponentFactory,
)
@ -462,7 +462,7 @@ internal class ChildFactory @Inject constructor(
initialCurrency = route.initialCurrency,
selectedCurrency = route.selectedCurrency,
source = ChooseManagedTokensComponent.Source.valueOf(route.source.name),
showSendViaSwapNotification = route.showSendViaSwapNotification,
shouldShowSendViaSwapNotification = route.shouldShowSendViaSwapNotification,
analyticsCategoryName = route.analyticsCategoryName,
),
componentFactory = chooseManagedTokensComponentFactory,

View file

@ -78,8 +78,8 @@ object GoogleServicesHelper {
return suspendCoroutine<Result<Boolean>> { continuation ->
task.addOnCompleteListener { completedTask ->
try {
val result = completedTask.getResult(ApiException::class.java)
continuation.resume(Result.success(result))
val isGooglePayAvailable = completedTask.getResult(ApiException::class.java)
continuation.resume(Result.success(isGooglePayAvailable))
} catch (exception: ApiException) {
continuation.resume(Result.failure(exception))
}

View file

@ -1,3 +1,5 @@
@file:Suppress("NullableToStringCall")
package com.tangem.common.routing
import android.annotation.SuppressLint
@ -160,7 +162,7 @@ sealed class AppRoute(val path: String) : Route {
val initialCurrency: CryptoCurrency,
val selectedCurrency: CryptoCurrency?,
val source: Source,
val showSendViaSwapNotification: Boolean,
val shouldShowSendViaSwapNotification: Boolean,
val analyticsCategoryName: String,
) : AppRoute(path = "/$source/choose_managed_tokens/$userWalletId/${initialCurrency.id.value}") {
enum class Source {
@ -271,9 +273,9 @@ sealed class AppRoute(val path: String) : Route {
data class MarketsTokenDetails(
val token: TokenMarketParams,
val appCurrency: AppCurrency,
val showPortfolio: Boolean,
val shouldShowPortfolio: Boolean,
val analyticsParams: AnalyticsParams? = null,
) : AppRoute(path = "/markets_token_details/${token.id}/$showPortfolio") {
) : AppRoute(path = "/markets_token_details/${token.id}/$shouldShowPortfolio") {
@Serializable
data class AnalyticsParams(
@ -287,7 +289,7 @@ sealed class AppRoute(val path: String) : Route {
val source: OnrampSource,
val portfolioId: PortfolioId,
val currency: CryptoCurrency,
val launchSepa: Boolean = false,
val shouldLaunchSepa: Boolean = false,
) : AppRoute(path = "/onramp/${portfolioId.stringValue}/${currency.symbol}"), RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
@ -301,7 +303,7 @@ sealed class AppRoute(val path: String) : Route {
source = source,
portfolioId = PortfolioId(userWalletId),
currency = currency,
launchSepa = launchSepa,
shouldLaunchSepa = launchSepa,
)
}
}

View file

@ -9,6 +9,7 @@ import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.modules.SerializersModule
@ExperimentalSerializationApi
@Suppress("NullableToStringCall")
internal class BundleDecoder(
private val bundle: Bundle,
private val elementsCount: Int = -1,

View file

@ -28,7 +28,7 @@ object MockYieldBalanceWrapperDTOFactory {
amount = BigDecimal.ONE,
date = null,
pricePerShare = BigDecimal.ZERO,
pendingActions = listOf(),
pendingActions = emptyList(),
pendingActionConstraints = null,
tokenDTO = TokenDTO(
name = "The-Open-Network",

View file

@ -30,13 +30,13 @@ object MockYieldDTOFactory {
logoURI = null,
isPoints = null,
),
tokens = listOf(),
tokens = emptyList(),
args = YieldDTO.ArgsDTO(
enter = YieldDTO.ArgsDTO.Enter(
addresses = YieldDTO.ArgsDTO.Enter.Addresses(
address = AddressArgumentDTO(required = false),
),
args = mapOf(),
args = emptyMap(),
),
exit = null,
),
@ -69,7 +69,7 @@ object MockYieldDTOFactory {
logoURI = null,
isPoints = null,
),
tokensDTO = listOf(),
tokensDTO = emptyList(),
type = "type",
rewardSchedule = YieldDTO.MetadataDTO.RewardScheduleDTO.DAY,
cooldownPeriod = null,
@ -81,7 +81,7 @@ object MockYieldDTOFactory {
revshare = YieldDTO.MetadataDTO.EnabledDTO(enabled = true),
fee = YieldDTO.MetadataDTO.EnabledDTO(enabled = true),
),
validators = listOf(),
validators = emptyList(),
isAvailable = true,
)
}

View file

@ -63,7 +63,7 @@ object MockScanResponseFactory {
isResettingUserCodesAllowed = false,
isLinkedTerminalEnabled = false,
isBackupAllowed = cardConfig is MultiWalletCardConfig,
supportedEncryptionModes = listOf(),
supportedEncryptionModes = emptyList(),
isFilesAllowed = true,
isHDWalletAllowed = cardConfig is MultiWalletCardConfig,
isKeysImportAllowed = true,
@ -72,7 +72,7 @@ object MockScanResponseFactory {
linkedTerminalStatus = CardDTO.LinkedTerminalStatus.Current,
isAccessCodeSet = false,
isPasscodeSet = null,
supportedCurves = listOf(),
supportedCurves = emptyList(),
wallets = cardConfig.mandatoryCurves.map {
CardDTO.Wallet(
CardWallet(
@ -85,7 +85,7 @@ object MockScanResponseFactory {
index = 0,
isImported = true,
hasBackup = true,
derivedKeys = mapOf(),
derivedKeys = emptyMap(),
),
)
},

View file

@ -21,19 +21,20 @@ object MockNetworkStatusFactory {
): NetworkStatus {
return NetworkStatus(
network = network,
value = NetworkStatus.Verified(
address = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
value = "0x1",
type = NetworkAddress.Address.Type.Primary,
value = transform(
NetworkStatus.Verified(
address = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
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(
name = "NODL",
walletId = UserWalletId("011"),
cardsInWallet = setOf(),
cardsInWallet = emptySet(),
isMultiCurrency = false,
scanResponse = MockScanResponseFactory.create(
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>) {
actual
.onRight { Truth.assertThat(actual).isEqualTo(expected) }
.onLeft {
val expectedError = expected.leftOrNull() ?: error("Actual is Either.Left: $it")
.onLeft { throwable ->
val expectedError = expected.leftOrNull() ?: error("Actual is Either.Left: $throwable")
Truth.assertThat(it).isInstanceOf(expectedError::class.java)
Truth.assertThat(it).hasMessageThat().isEqualTo(expectedError.message)
Truth.assertThat(throwable).isInstanceOf(expectedError::class.java)
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) {
actual
.onRight { error("Actual is Either.Right: $it") }

View file

@ -90,7 +90,7 @@ fun MarketChart(
backgroundLineColor = state.chartColor.copy(alpha = backgroundColorAlpha),
secondLineColor = splitChartSegmentColor,
backgroundSecondLineColor = splitChartSegmentColor.copy(alpha = backgroundSplitChartSegmentColorAlpha),
secondColorOnTheRightSide = state.markerHighlightRightSide.not(),
secondColorOnTheRightSide = state.shouldMarkerHighlightRightSide.not(),
markerFraction = state.markerFraction,
axisValueOverrider = AxisValueOverrider.fixed(),
canvasHeight = chartHeight,
@ -114,10 +114,10 @@ fun MarketChart(
CartesianChartHost(
modifier = modifier
.onGloballyPositioned {
canvasWidth = it.size.width
chartHeight = if (it.size.height != 0) {
it.size.height - bottomAxisHeight
.onGloballyPositioned { layoutCoordinates ->
canvasWidth = layoutCoordinates.size.width
chartHeight = if (layoutCoordinates.size.height != 0) {
layoutCoordinates.size.height - bottomAxisHeight
} else {
0
}
@ -126,7 +126,7 @@ fun MarketChart(
.drawBehind {
state.markerFraction
state.chartColor
state.markerHighlightRightSide
state.shouldMarkerHighlightRightSide
},
chart = chart,
modelProducer = state.modelProducer,
@ -269,7 +269,7 @@ private fun rememberChartAxisGuidelineComponent(color: Color): LineComponent {
// region Preview
@Suppress("LongMethod")
@Suppress("LongMethod", "NullableToStringCall")
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
@ -283,7 +283,7 @@ private fun MarketChartPreview(
MarketChartDataProducer.build {
chartLook = MarketChartLook(
type = MarketChartLook.Type.Growing,
markerHighlightRightSide = true,
shouldMarkerHighlightRightSide = true,
)
}
}
@ -294,8 +294,8 @@ private fun MarketChartPreview(
x = x.toImmutableList(),
y = y.toImmutableList(),
)
updateLook {
it.copy(
updateLook { marketChartLook ->
marketChartLook.copy(
xAxisFormatter = { value ->
value.toLong().toTimeFormat(DateTimeFormatters.dateMMMdd)
},
@ -322,8 +322,8 @@ private fun MarketChartPreview(
onMarkerShown = { x, y ->
markerPoint = Pair(x, y)
},
colorMapper = {
when (it) {
colorMapper = { type ->
when (type) {
MarketChartLook.Type.Growing -> growingColor
MarketChartLook.Type.Falling -> fallingColor
MarketChartLook.Type.Neutral -> neutralColor
@ -354,7 +354,7 @@ private fun MarketChartPreview(
onClick = {
dataProducer.runTransaction {
updateLook {
it.copy(markerHighlightRightSide = !it.markerHighlightRightSide)
it.copy(shouldMarkerHighlightRightSide = !it.shouldMarkerHighlightRightSide)
}
}
},
@ -367,10 +367,10 @@ private fun MarketChartPreview(
onClick = {
coroutineScope.launch {
dataProducer.runTransactionSuspend {
updateData {
updateData { data ->
MarketChartData.Data(
x = it.x,
y = it.y.reversed().toImmutableList(),
x = data.x,
y = data.y.reversed().toImmutableList(),
)
}
}
@ -383,9 +383,9 @@ private fun MarketChartPreview(
Button(
onClick = {
dataProducer.runTransaction {
updateLook {
it.copy(
type = when (it.type) {
updateLook { look ->
look.copy(
type = when (look.type) {
MarketChartLook.Type.Growing -> MarketChartLook.Type.Falling
MarketChartLook.Type.Falling -> MarketChartLook.Type.Neutral
MarketChartLook.Type.Neutral -> MarketChartLook.Type.Growing

View file

@ -104,10 +104,10 @@ private fun PreviewColumn() {
TangemThemePreview {
LazyColumn {
items(100) {
items(100) { i ->
MarketChartMini(
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 indexesRes = ArrayList<Int>(points.size)
results.fastForEach {
xRes.add(it.x)
yRes.add(it.y)
indexesRes.add(it.originalIndex!!)
results.fastForEach { result ->
xRes.add(result.x)
yRes.add(result.y)
indexesRes.add(requireNotNull(result.originalIndex))
}
return Result(

View file

@ -62,12 +62,7 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
},
indicatorSizeDp = INDICATOR_SIZE_DP,
guideline = guideline,
valueFormatter = object : CartesianMarkerValueFormatter {
override fun format(
context: CartesianDrawContext,
targets: List<CartesianMarker.Target>,
): CharSequence = ""
},
valueFormatter = CartesianMarkerValueFormatter { _, _ -> "" },
) {
override fun updateInsets(
context: CartesianMeasureContext,
@ -76,7 +71,12 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
insets: Insets,
) {
with(context) {
super.updateInsets(context, horizontalDimensions, model, insets)
super.updateInsets(
context = context,
horizontalDimensions = horizontalDimensions,
model = model,
insets = insets,
)
val baseShadowInsetDp =
CLIPPING_FREE_SHADOW_RADIUS_MULTIPLIER * LABEL_BACKGROUND_SHADOW_RADIUS_DP
val topInset = (baseShadowInsetDp - LABEL_BACKGROUND_SHADOW_DY_DP).pixels
@ -90,11 +90,11 @@ internal fun rememberTangemChartMarker(color: Color): CartesianMarker {
cacheStore
.getOrSet(keyNamespace, indicator, outColor) { indicator.invoke(outColor) }
.draw(
this,
x - halfIndicatorSize,
y - halfIndicatorSize,
x + halfIndicatorSize,
y + halfIndicatorSize,
context = this,
left = x - halfIndicatorSize,
top = y - halfIndicatorSize,
right = x + halfIndicatorSize,
bottom = y + halfIndicatorSize,
)
}
}

View file

@ -64,7 +64,7 @@ internal fun rememberMarketChartLayer(
val backgroundColorLineGradient = persistentListOf(backgroundLineColor, Color.Transparent)
val backgroundSecondLineColorGradient = persistentListOf(backgroundSecondLineColor, Color.Transparent)
val markerSet = markerFraction != null
val isMarkerSet = markerFraction != null
return rememberLayer(
fractionValue = markerFraction ?: 0f,
@ -75,17 +75,17 @@ internal fun rememberMarketChartLayer(
} else {
lineColor
},
backLineColor = if (markerSet && !secondColorOnTheRightSide) {
backLineColor = if (isMarkerSet && !secondColorOnTheRightSide) {
backgroundSecondLineColorGradient
} else {
backgroundColorLineGradient
},
lineColorRight = when {
markerSet && secondColorOnTheRightSide -> secondLineColor
isMarkerSet && secondColorOnTheRightSide -> secondLineColor
else -> lineColor
},
backLineColorRight = when {
markerSet && secondColorOnTheRightSide -> backgroundSecondLineColorGradient
isMarkerSet && secondColorOnTheRightSide -> backgroundSecondLineColorGradient
else -> backgroundColorLineGradient
},
)
@ -148,7 +148,7 @@ private fun LayerChartPreview(
val y = previewData.second.map { it.toFloat() }
val x = List(y.size) { it.toFloat() }
val model = CartesianChartModel(LineCartesianLayerModel.build { series(x, y) })
var lineColor by remember {
val lineColor by remember {
mutableStateOf(Color.Blue)
}

View file

@ -85,7 +85,7 @@ class TransactionSuspend(
class MarketChartDataProducer private constructor(
initialData: MarketChartData,
initialLook: MarketChartLook,
val pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
val pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
private val dispatcher: CoroutineDispatcher = Dispatchers.Default,
) {
internal val dataState = MutableStateFlow(initialData)
@ -166,7 +166,7 @@ class MarketChartDataProducer private constructor(
* @return A MarketChartDataProducer.
*/
suspend fun buildSuspend(
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
dispatcher: CoroutineDispatcher = Dispatchers.Default,
block: TransactionSuspend.() -> Unit,
): MarketChartDataProducer {
@ -191,7 +191,7 @@ class MarketChartDataProducer private constructor(
* @return A MarketChartDataProducer.
*/
fun build(
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = true),
pointsValuesConverter: PointValuesConverter = PriceAndTimePointValuesConverter(shouldFormatAxis = true),
dispatcher: CoroutineDispatcher = Dispatchers.Default,
block: Transaction.() -> Unit,
): MarketChartDataProducer {

View file

@ -9,14 +9,14 @@ import com.tangem.common.ui.charts.state.formatter.AxisLabelFormatter
* and formatters for x and y axis.
*
* @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 yAxisFormatter A formatter for the y-axis labels.
*/
@Immutable
data class MarketChartLook(
val type: Type = Type.Growing,
val markerHighlightRightSide: Boolean = true,
val shouldMarkerHighlightRightSide: Boolean = true,
val xAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
val yAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
) {

View file

@ -20,8 +20,8 @@ import java.math.BigDecimal
fun rememberMarketChartState(
dataProducer: MarketChartDataProducer = remember { MarketChartDataProducer.build {} },
colorMapper: (MarketChartLook.Type) -> Color = remember {
{
when (it) {
{ type ->
when (type) {
MarketChartLook.Type.Growing -> Color.Green
MarketChartLook.Type.Falling -> Color.Red
MarketChartLook.Type.Neutral -> Color.Gray
@ -33,7 +33,12 @@ fun rememberMarketChartState(
val lookState = dataProducer.lookState.collectAsState()
val state = remember(dataProducer, lookState, colorMapper, onMarkerShown) {
MarketChartState(dataProducer, lookState, colorMapper, onMarkerShown)
MarketChartState(
dataProducer = dataProducer,
lookState = lookState,
colorMapper = colorMapper,
markerCallback = onMarkerShown,
)
}
return state
@ -61,8 +66,8 @@ class MarketChartState internal constructor(
colorMapper(lookState.value.type)
}
internal val markerHighlightRightSide by derivedStateOf {
lookState.value.markerHighlightRightSide
internal val shouldMarkerHighlightRightSide by derivedStateOf {
lookState.value.shouldMarkerHighlightRightSide
}
internal val xValueFormatter = CartesianValueFormatter { value, _, _ ->

View file

@ -8,7 +8,7 @@ import java.math.BigDecimal
@Suppress("MagicNumber")
class PriceAndTimePointValuesConverter(
private val needToFormatAxis: Boolean,
private val shouldFormatAxis: Boolean,
) : PointValuesConverter {
private data class MinMaxCache(
@ -18,7 +18,12 @@ class PriceAndTimePointValuesConverter(
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 formatXValuesCache = mutableMapOf<Double, BigDecimal>()
@ -37,15 +42,14 @@ class PriceAndTimePointValuesConverter(
val normX = data.x.normalizeTime(min = cache.minX, max = cache.maxX)
return if (normX.size > MAX_POINTS) {
LTThreeBuckets
val downsampled = LTThreeBuckets
.downsample(normX, normY, MAX_POINTS - 2)
.let {
MarketChartRawData(
originalIndexes = it.originalIndexes.toImmutableList(),
x = it.x.toImmutableList(),
y = it.y.toImmutableList(),
)
}
MarketChartRawData(
originalIndexes = downsampled.originalIndexes.toImmutableList(),
x = downsampled.x.toImmutableList(),
y = downsampled.y.toImmutableList(),
)
} else {
MarketChartRawData(
x = normX.toImmutableList(),
@ -55,8 +59,8 @@ class PriceAndTimePointValuesConverter(
}
override fun prepareRawXForFormat(rawX: Double, data: MarketChartData.Data): BigDecimal {
if (!needToFormatAxis) return BigDecimal.ZERO
if (formatXValuesCache.containsKey(rawX)) return formatXValuesCache[rawX]!!
if (!shouldFormatAxis) return BigDecimal.ZERO
if (formatXValuesCache.containsKey(rawX)) return requireNotNull(formatXValuesCache[rawX])
val result = (rawX * MINUTE).toBigDecimal()
@ -65,8 +69,8 @@ class PriceAndTimePointValuesConverter(
}
override fun prepareRawYForFormat(rawY: Double, data: MarketChartData.Data): BigDecimal {
if (!needToFormatAxis) return BigDecimal.ZERO
if (formatYValuesCache.containsKey(rawY)) return formatYValuesCache[rawY]!!
if (!shouldFormatAxis) return BigDecimal.ZERO
if (formatYValuesCache.containsKey(rawY)) return requireNotNull(formatYValuesCache[rawY])
val min = minMaxCache.minY
val max = minMaxCache.maxY

View file

@ -53,7 +53,7 @@ class AmountReduceByTransformer(
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
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 {

View file

@ -52,7 +52,7 @@ class AmountReduceToTransformer(
val checkValue = if (amountTextField.isFiatValue) fiatValue else cryptoValue
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 isCheckFailed = isExceedBalance || isLessThanMinimumIfProvided
return prevState.copy(

View file

@ -52,7 +52,7 @@ class AmountStateConverter(
val status = cryptoCurrencyStatusProvider()
val fiat = maxEnterAmount.fiatAmount.format { fiat(appCurrency.code, appCurrency.symbol) }
val crypto = maxEnterAmount.amount.format { crypto(status.currency) }
val noFeeRate = status.value.fiatRate.isNullOrZero()
val hasNoFeeRate = status.value.fiatRate.isNullOrZero()
return AmountState.Data(
title = value.title,
@ -69,7 +69,7 @@ class AmountStateConverter(
title = stringReference(status.currency.symbol),
iconState = iconStateConverter.convertCustom(
value = status,
forceGrayscale = noFeeRate,
forceGrayscale = hasNoFeeRate,
showCustomTokenBadge = false,
),
isFiat = false,
@ -80,7 +80,7 @@ class AmountStateConverter(
isFiat = true,
),
),
isSegmentedButtonsEnabled = !noFeeRate,
isSegmentedButtonsEnabled = !hasNoFeeRate,
selectedButton = 0,
isRedesignEnabled = false,
)

View file

@ -88,12 +88,12 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
PrimaryButtonIconEnd(
text = stringResourceSafe(id = R.string.common_approve),
iconResId = content.walletInteractionIcon,
showProgress = data.approveButton.loading,
showProgress = data.approveButton.isLoading,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing16),
onClick = data.approveButton.onClick,
enabled = data.approveButton.enabled,
enabled = data.approveButton.isEnabled,
)
SpacerH12()

View file

@ -39,8 +39,8 @@ enum class ApproveType(val text: TextReference) {
}
data class ApprovePermissionButton(
val enabled: Boolean,
val loading: Boolean = false,
val isEnabled: Boolean,
val isLoading: Boolean = false,
val onClick: () -> Unit,
)

View file

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

View file

@ -116,7 +116,7 @@ fun NavigationPrimaryButton(primaryButton: NavigationButton?, modifier: Modifier
}
button.onClick()
},
showProgress = button.showProgress,
showProgress = button.shouldShowProgress,
colors = color,
textStyle = TangemTheme.typography.subtitle1,
icon = icon,
@ -232,7 +232,7 @@ private fun rememberNavigationButton(button: NavigationButton?): MutableState<Na
button?.iconRes,
button?.isIconVisible,
button?.isEnabled,
button?.showProgress,
button?.shouldShowProgress,
button?.textReference,
) { mutableStateOf(button) }
}

View file

@ -16,22 +16,22 @@ sealed class NavigationButtonsState {
}
/**
* @property textReference text
* @property iconRes icon resource id
* @property isSecondary should set secondary color scheme
* @property isIconVisible determines whether icon is visible
* @property showProgress indicates progress state of button
* @property isEnabled enabled
* @property isDimmed determines whether the button content will be dimmed.
* @property textReference text
* @property iconRes icon resource id
* @property isSecondary should set secondary color scheme
* @property isIconVisible determines whether icon is visible
* @property shouldShowProgress indicates progress state of button
* @property isEnabled enabled
* @property isDimmed determines whether the button content will be dimmed.
* 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(
val textReference: TextReference,
@DrawableRes val iconRes: Int? = null,
val isSecondary: Boolean = false,
val isIconVisible: Boolean = false,
val showProgress: Boolean = false,
val shouldShowProgress: Boolean = false,
val isEnabled: Boolean = true,
val isDimmed: Boolean = false,
val isHapticClick: Boolean = false,

View file

@ -13,7 +13,7 @@ internal object NavigationButtonsPreview {
iconRes = R.drawable.ic_tangem_24,
isSecondary = true,
isIconVisible = true,
showProgress = false,
shouldShowProgress = false,
isEnabled = true,
onClick = {},
) to NavigationButton(
@ -21,7 +21,7 @@ internal object NavigationButtonsPreview {
iconRes = R.drawable.ic_tangem_24,
isSecondary = true,
isIconVisible = true,
showProgress = false,
shouldShowProgress = false,
isEnabled = true,
onClick = {},
)
@ -31,7 +31,7 @@ internal object NavigationButtonsPreview {
iconRes = R.drawable.ic_back_24,
isSecondary = true,
isIconVisible = true,
showProgress = false,
shouldShowProgress = false,
isEnabled = true,
onClick = {},
)
@ -40,7 +40,7 @@ internal object NavigationButtonsPreview {
textReference = resourceReference(R.string.common_close),
isSecondary = false,
isIconVisible = false,
showProgress = false,
shouldShowProgress = false,
isEnabled = true,
onClick = {},
)

View file

@ -84,7 +84,7 @@ sealed class NotificationUM(val config: NotificationConfig) {
val feeName: String,
val feeSymbol: String,
val networkName: String,
val mergeFeeNetworkName: Boolean = false,
val shouldMergeFeeNetworkName: Boolean = false,
val onClick: (() -> Unit)? = null,
) : Error(
title = resourceReference(
@ -101,7 +101,7 @@ sealed class NotificationUM(val config: NotificationConfig) {
text = resourceReference(
R.string.common_buy_currency,
wrappedList(
if (mergeFeeNetworkName) {
if (shouldMergeFeeNetworkName) {
"$currencyName ($feeSymbol)"
} else {
feeName

View file

@ -62,7 +62,7 @@ object NotificationsFactory {
currencyName = tokenStatus.currency.name,
feeName = coinStatus.currency.name,
feeSymbol = coinStatus.currency.symbol,
mergeFeeNetworkName = false,
shouldMergeFeeNetworkName = false,
onClick = {
onClick(coinStatus.currency)
},
@ -303,7 +303,7 @@ object NotificationsFactory {
currencyName = cryptoCurrencyStatus.currency.name,
feeName = cryptoCurrencyWarning.coinCurrency.name,
feeSymbol = cryptoCurrencyWarning.coinCurrency.symbol,
mergeFeeNetworkName = shouldMergeFeeNetworkName,
shouldMergeFeeNetworkName = shouldMergeFeeNetworkName,
onClick = {
onClick(cryptoCurrencyWarning.coinCurrency)
},
@ -320,9 +320,9 @@ object NotificationsFactory {
feeName = cryptoCurrencyWarning.feeCurrencyName,
feeSymbol = cryptoCurrencyWarning.feeCurrencySymbol,
networkName = cryptoCurrencyWarning.networkName,
mergeFeeNetworkName = shouldMergeFeeNetworkName,
shouldMergeFeeNetworkName = shouldMergeFeeNetworkName,
onClick = {
currency?.let {
if (currency != null) {
onClick(currency)
}
},
@ -381,20 +381,22 @@ object NotificationsFactory {
add(NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
}
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
when (sendingCurrency) {
is CryptoCurrency.Coin -> NotificationUM.Cardano.InsufficientBalanceToTransferCoin
is CryptoCurrency.Token -> {
NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
}
}.let(::add)
add(
when (sendingCurrency) {
is CryptoCurrency.Coin -> NotificationUM.Cardano.InsufficientBalanceToTransferCoin
is CryptoCurrency.Token -> {
NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
}
},
)
}
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
-> {
dustValue?.let {
dustValue?.let { value ->
add(
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(),
icons = buildList {
if (!status.getStakedBalance().isZero()) {
TokenItemState.FiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_staking_24,
tint = IconTint.Accent,
).let(::add)
add(
TokenItemState.FiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_staking_24,
tint = IconTint.Accent,
),
)
}
if (status.value.sources.total == StatusSource.ONLY_CACHE) {
TokenItemState.FiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_error_sync_24,
).let(::add)
add(
TokenItemState.FiatAmountState.Content.IconUM(
iconRes = R.drawable.ic_error_sync_24,
),
)
}
}.toImmutableList(),
)

View file

@ -23,11 +23,11 @@ fun TokenPriceText(price: String, modifier: Modifier = Modifier, priceChangeType
val generalColor = TangemTheme.colors.text.primary1
val color = remember(generalColor) { Animatable(generalColor) }
var animationSkipped by remember { mutableStateOf(false) }
var isAnimationSkipped by remember { mutableStateOf(false) }
LaunchedEffect(price) {
if (animationSkipped.not()) {
animationSkipped = true
if (isAnimationSkipped.not()) {
isAnimationSkipped = true
return@LaunchedEffect
}

View file

@ -21,7 +21,7 @@ internal class DevFeatureTogglesManager(
private val featureTogglesLocalStorage: LocalTogglesStorage,
) : MutableFeatureTogglesManager {
private var fileFeatureTogglesMap: Map<String, Boolean> = getFileFeatureToggles()
private val fileFeatureTogglesMap: Map<String, Boolean> = getFileFeatureToggles()
private var featureTogglesMap: MutableMap<String, Boolean> by Delegates.notNull()
init {

View file

@ -23,14 +23,14 @@ internal class DefaultVersionProvider @Inject constructor(
private fun getVersionName(): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager
.getPackageInfo(
context.packageName,
PackageManager.PackageInfoFlags.of(0),
)
.versionName!!
requireNotNull(
context
.packageManager
.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(0))
.versionName,
)
} 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 {
/** Flag that determines whether the manager is initialized */
val isInitialized: StateFlow<Boolean>
val initializedState: StateFlow<Boolean>
/** Initialize resources */
fun initialize()

View file

@ -28,11 +28,11 @@ internal class DevApiConfigsManager(
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
field = MutableStateFlow(value = getInitialConfigs())
override val isInitialized: StateFlow<Boolean>
override val initializedState: StateFlow<Boolean>
field = MutableStateFlow(value = false)
override fun initialize() {
isInitialized.value = false
initializedState.value = false
appPreferencesStore.getObjectMap<ApiEnvironment>(PreferencesKeys.apiConfigsEnvironmentKey)
.distinctUntilChanged()
@ -45,8 +45,8 @@ internal class DevApiConfigsManager(
savedEnvironments[config.id.name] ?: currentEnvironment
}
if (!isInitialized.value) {
isInitialized.value = true
if (!initializedState.value) {
initializedState.value = true
}
notifyListeners(apiConfigs = apiConfigs, savedEnvironments = savedEnvironments)

View file

@ -24,7 +24,7 @@ internal class MockApiConfigsManager(
override val configs: StateFlow<Map<ApiConfig, ApiEnvironment>>
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)

View file

@ -15,7 +15,7 @@ internal class ProdApiConfigsManager(
private val apiConfigs: ApiConfigs,
) : ApiConfigsManager {
override val isInitialized: StateFlow<Boolean> = MutableStateFlow(value = true)
override val initializedState: StateFlow<Boolean> = MutableStateFlow(value = true)
override fun initialize() = Unit

View file

@ -72,14 +72,14 @@ sealed class ApiResponseError : Exception() {
/** Represents a network error, typically when there's no connectivity. */
@Suppress("UnusedPrivateMember")
data object NetworkException : ApiResponseError() {
private fun readResolve(): Any = NetworkException
class NetworkException : ApiResponseError() {
private fun readResolve(): Any = NetworkException()
}
/** Represents a timeout error, typically when the server takes too long to respond. */
@Suppress("UnusedPrivateMember")
data object TimeoutException : ApiResponseError() {
private fun readResolve(): Any = TimeoutException
class TimeoutException : ApiResponseError() {
private fun readResolve(): Any = TimeoutException()
}
/**
@ -87,5 +87,5 @@ sealed class ApiResponseError : Exception() {
*
* @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 UnknownHostException,
is SSLHandshakeException,
-> ApiResponseError.NetworkException
-> ApiResponseError.NetworkException()
is TimeoutException,
is TimeoutCancellationException,
is SocketTimeoutException,
-> ApiResponseError.TimeoutException
-> ApiResponseError.TimeoutException()
else -> ApiResponseError.UnknownException(cause = this)
}

View file

@ -4,6 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class Asset(
@Json(name = "contractAddress")
val contractAddress: String,

View file

@ -56,6 +56,7 @@ data class TokenMarketInfoResponse(
)
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class Network(
@Json(name = "network_id")
val networkId: String,

View file

@ -4,6 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class OnrampCountryDTO(
@Json(name = "name")
val name: String,

View file

@ -26,6 +26,7 @@ data class TangemPayTxHistoryResponse(
)
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class Spend(
@Json(name = "amount") val amount: BigDecimal,
@Json(name = "currency") val currency: String,

View file

@ -36,6 +36,7 @@ data class ActionRequestBody(
)
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class ActionRequestBodyArgs(
@Json(name = "amount")
val amount: String,

View file

@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class AddressArgumentDTO(
@Json(name = "required")
val required: Boolean,

View file

@ -1,3 +1,5 @@
@file:Suppress("BooleanPropertyNaming")
package com.tangem.datasource.api.stakekit.models.response.model
import com.squareup.moshi.Json

View file

@ -1,3 +1,5 @@
@file:Suppress("BooleanPropertyNaming")
package com.tangem.datasource.api.stakekit.models.response.model
import com.squareup.moshi.Json

View file

@ -1,3 +1,5 @@
@file:Suppress("BooleanPropertyNaming")
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
@ -12,12 +14,13 @@ data class CoinsResponse(
) {
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class Coin(
@Json(name = "id") val id: String,
@Json(name = "name") val name: String,
@Json(name = "symbol") val symbol: String,
@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)

View file

@ -4,6 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class CreateUserNetworkAccountResponse(
@Json(name = "status") val status: Boolean,
@Json(name = "data") val data: AccountCreated,

View file

@ -5,6 +5,7 @@ import com.squareup.moshi.JsonClass
import com.tangem.common.extensions.calculateHashCode
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class UserTokensResponse(
@Json(name = "version") val version: Int = 0,
@Json(name = "group") val group: GroupType,

View file

@ -4,6 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class WalletBody(
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
@Json(name = "name") val name: String? = null,

View file

@ -4,6 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class WalletResponse(
@Json(name = "notifyStatus") val notifyStatus: Boolean,
@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"))
parsedConfig.orEmpty()
},
onFailure = {
Timber.e(it, "Failed to load config [$fileName] from assets")
onFailure = { throwable ->
Timber.e(throwable, "Failed to load config [$fileName] from assets")
emptyList()
},
)

View file

@ -192,6 +192,7 @@ internal class RetrofitApiBuilder @Inject constructor(
}
}
@Suppress("UseEmptyCounterpart")
private companion object {
val excludedApiForLogging: Set<ApiConfig.ID> = setOf(

View file

@ -72,6 +72,7 @@ internal class DefaultExpressServiceLoader @Inject constructor(
return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } }
}
@Suppress("SuspendFunWithFlowReturnType")
private suspend fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow {
val initializationStatus = initializationStatuses.value.get(key = userWalletId)
if (initializationStatus != null) return initializationStatus
@ -79,8 +80,8 @@ internal class DefaultExpressServiceLoader @Inject constructor(
val cached = expressAssetsStore.getSyncOrNull(userWalletId)
val default: InitializationStatusFlow = MutableStateFlow(value = cached?.lceContent() ?: lceLoading())
initializationStatuses.update {
it.toMutableMap().apply {
initializationStatuses.update { statuses ->
statuses.toMutableMap().apply {
put(key = userWalletId, value = default)
}
}

View file

@ -26,10 +26,10 @@ object NFTSdkAssetConverter : TwoWayConverter<Pair<Network, SdkNFTAsset>, NFTAss
amount = asset.amount,
decimals = asset.decimals,
salePrice = salePrice,
rarity = asset.rarity?.let {
rarity = asset.rarity?.let { rarity ->
NFTAsset.Rarity(
rank = it.rank,
label = it.label,
rank = rarity.rank,
label = rarity.label,
)
},
media = asset.media?.let {
@ -65,22 +65,22 @@ object NFTSdkAssetConverter : TwoWayConverter<Pair<Network, SdkNFTAsset>, NFTAss
amount = value.amount,
decimals = value.decimals,
salePrice = salePrice,
rarity = value.rarity?.let {
rarity = value.rarity?.let { rarity ->
SdkNFTAsset.Rarity(
rank = it.rank,
label = it.label,
rank = rarity.rank,
label = rarity.label,
)
},
media = value.media?.let {
media = value.media?.let { media ->
SdkNFTAsset.Media(
animationUrl = it.animationUrl,
imageUrl = it.imageUrl,
animationUrl = media.animationUrl,
imageUrl = media.imageUrl,
)
},
traits = value.traits.map {
traits = value.traits.map { trait ->
SdkNFTAsset.Trait(
name = it.name,
value = it.value,
name = trait.name,
value = trait.value,
)
},
)

View file

@ -37,9 +37,9 @@ inline fun <reified T> AppPreferencesStore.getObject(key: Preferences.Key<String
return flow {
val adapter = moshi.adapter(T::class.java)
emitAll(
data.map {
data.map { prefs ->
try {
it[key]?.let(adapter::fromJson) ?: default
prefs[key]?.let(adapter::fromJson) ?: default
} catch (e: JsonDataException) {
default
}

View file

@ -12,10 +12,10 @@ internal class DefaultSwapBestRateAnimationStore(
* If true, reset flag to false
*/
override suspend fun getSyncOrNull(): Boolean {
val value = dataStore.getSyncOrNull() ?: true
if (value) {
val shouldShowBestRateAnimation = dataStore.getSyncOrNull() ?: true
if (shouldShowBestRateAnimation) {
dataStore.store(false)
}
return value
return shouldShowBestRateAnimation
}
}

View file

@ -8,7 +8,7 @@ internal class DefaultTokenReceiveWarningActionStore(
) : TokenReceiveWarningActionStore {
override suspend fun getSync(): Set<String> {
return persistenceStore.data.firstOrNull() ?: emptySet()
return persistenceStore.data.firstOrNull().orEmpty()
}
override suspend fun store(symbol: String) {

View file

@ -19,11 +19,11 @@ internal object PendingActionConverter : Converter<BalanceDTO.PendingAction, Pen
maximum = it.maximum,
)
},
duration = this?.duration?.let {
duration = this?.duration?.let { duration ->
PendingAction.PendingActionArgs.Duration(
required = it.required,
minimum = it.minimum,
maximum = it.maximum,
required = duration.required,
minimum = duration.minimum,
maximum = duration.maximum,
)
},
validatorAddress = this?.validatorAddress?.required,

View file

@ -20,9 +20,9 @@ internal fun OkHttpClient.Builder.addHeaders(
Interceptor { chain ->
val request = chain.request().newBuilder().apply {
runBlocking {
requestHeaders.forEach {
val value = it.value.invoke()
if (value.isNotBlank()) addHeader(name = it.key, value = value)
requestHeaders.forEach { header ->
val value = header.value.invoke()
if (value.isNotBlank()) addHeader(name = header.key, value = value)
}
}
}.build()

View file

@ -101,7 +101,7 @@ class NetworkLogsSaveInterceptor(
private fun logResponseMessage(response: Response, startNs: Long) {
val responseHeaders = response.headers
val responseBody = response.body!!
val responseBody = requireNotNull(response.body)
val contentLength = responseBody.contentLength()
val message = if (!response.promisesBody()) {

View file

@ -46,8 +46,8 @@ sealed class RequestHeader(vararg pairs: Pair<String, ProviderSuspend<String>>)
fun String.checkHeaderValueOrEmpty(): String {
for (i in this.indices) {
val c = this[i]
val charCondition = c == '\t' || c in '\u0020'..'\u007e'
if (!charCondition) {
val isChar = c == '\t' || c in '\u0020'..'\u007e'
if (!isChar) {
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],
*/
@Suppress("NullableToStringCall")
inline fun <reified M : Model, reified P : Any> AppComponentContext.getOrCreateModel(
params: P?,
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 {
return runCatching { getString(id, *formatArgs) }
.recoverCatching {
.recoverCatching { throwable ->
// If something goes wrong, returns the resource without arguments
val string = getString(id)
reportIssue(it, resources = this, id, *formatArgs)
reportIssue(
throwable = throwable,
resources = this,
id = id,
formatArgs = formatArgs,
)
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 {
return getOrElse {
reportIssue(it, resources, id, formatArgs)
return getOrElse { throwable ->
reportIssue(
throwable = throwable,
resources = resources,
id = id,
formatArgs = formatArgs,
)
// If something still goes wrong, returns the resource name
resources.getResourceEntryName(id)

View file

@ -178,14 +178,14 @@ fun SelectorDialog(
* Dialog button params
*
* @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 enabled If false button will be disabled
* @param isWarning If true then button text will be in theme warning color
* @param isEnabled If false button will be disabled
* @param onClick Button click callback
*/
data class DialogButtonUM(
val title: String? = null,
val warning: Boolean = false,
val enabled: Boolean = true,
val isWarning: Boolean = false,
val isEnabled: Boolean = true,
val onClick: () -> Unit,
)
@ -196,7 +196,7 @@ data class AdditionalTextInputDialogUM(
val label: String? = null,
val placeholder: String? = null,
val caption: String? = null,
val enabled: Boolean = true,
val isEnabled: Boolean = true,
val isError: Boolean = false,
val errorText: String? = null,
)
@ -276,7 +276,7 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
onValueChange = type.onValueChange,
isError = type.params.isError,
errorText = type.params.errorText,
isEnabled = type.params.enabled,
isEnabled = type.params.isEnabled,
placeholder = type.params.placeholder,
)
}
@ -289,7 +289,7 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
label = type.params.label,
placeholder = type.params.placeholder,
caption = type.params.caption,
enabled = type.params.enabled,
enabled = type.params.isEnabled,
isError = type.params.isError,
onValueChange = { newValue ->
type.onValueChange(newValue)
@ -324,15 +324,15 @@ private fun DialogButtons(
if (dismissButton != null) {
DialogButton(
text = dismissButton.title ?: stringResourceSafe(id = R.string.common_cancel),
warning = dismissButton.warning,
enabled = dismissButton.enabled,
warning = dismissButton.isWarning,
enabled = dismissButton.isEnabled,
onClick = dismissButton.onClick,
)
}
DialogButton(
text = confirmButton.title ?: stringResourceSafe(id = R.string.common_ok),
warning = confirmButton.warning,
enabled = confirmButton.enabled,
warning = confirmButton.isWarning,
enabled = confirmButton.isEnabled,
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 " +
"password to work with the app",
title = "Attention",
confirmButton = DialogButtonUM(warning = true) {},
confirmButton = DialogButtonUM(isWarning = true) {},
dismissButton = DialogButtonUM {},
onDismissDialog = {},
)

View file

@ -29,10 +29,10 @@ fun Modifier.edgeFade(
require(value = size > 0.dp) {
"Size must be greater than '0'"
}
val animatedSize = animationSpec?.let {
val animatedSize = animationSpec?.let { spec ->
animateDpAsState(
targetValue = if (isVisible) size else 0.dp,
animationSpec = it,
animationSpec = spec,
label = "Edge fade width",
)
}

View file

@ -29,22 +29,22 @@ fun Modifier.flicker(
targetTextAlpha: Float = 0.4f,
animationDurationMillis: Int = 1500,
): Modifier = composed {
var alphaChange by remember { mutableStateOf(false) }
var shouldAlphaChange by remember { mutableStateOf(false) }
val alpha: Float by animateFloatAsState(
targetValue = if (alphaChange) targetTextAlpha else 1f,
targetValue = if (shouldAlphaChange) targetTextAlpha else 1f,
animationSpec = tween(
durationMillis = animationDurationMillis,
easing = CubicBezierEasing(a = 0.45f, b = 0.0f, c = 0.55f, d = 1.0f),
),
finishedListener = {
alphaChange = it == 1f && isFlickering
shouldAlphaChange = it == 1f && isFlickering
},
)
LaunchedEffect(isFlickering) {
if (isFlickering) {
alphaChange = true
shouldAlphaChange = true
}
}

View file

@ -33,7 +33,7 @@ fun FullScreen(
val fullScreenLayout = remember {
FullScreenLayout(
notTouchable = notTouchable,
isNotTouchable = notTouchable,
focusable = focusable,
composeView = view,
onBackClick = onBackClick,
@ -53,7 +53,7 @@ fun FullScreen(
@SuppressLint("ViewConstructor", "ClickableViewAccessibility")
private class FullScreenLayout(
private val notTouchable: Boolean,
private val isNotTouchable: Boolean,
private val focusable: Boolean,
private val composeView: View,
private val onBackClick: () -> Unit,
@ -68,10 +68,10 @@ private class FullScreenLayout(
override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
private set
private var viewShowing = false
private var isViewShowing = false
init {
if (notTouchable) {
if (isNotTouchable) {
setOnTouchListener { _, _ -> false }
}
@ -110,19 +110,19 @@ private class FullScreenLayout(
}
fun show() {
if (viewShowing) dismiss()
if (isViewShowing) dismiss()
windowManager.addView(this, params)
if (focusable.not()) {
params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
}
if (notTouchable) {
if (isNotTouchable) {
params.flags = params.flags or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
}
windowManager.updateViewLayout(this, params)
viewShowing = true
isViewShowing = true
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
@ -139,10 +139,10 @@ private class FullScreenLayout(
}
fun dismiss() {
if (!viewShowing) return
if (!isViewShowing) return
disposeComposition()
windowManager.removeViewImmediate(this)
viewShowing = false
isViewShowing = false
}
fun dispose() {

View file

@ -38,12 +38,12 @@ fun keyboardAsState(): State<Keyboard> {
val keyboardState = remember { mutableStateOf(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.height < 50.dp
// FIX android <=10 devices can randomly send ime paddings,
// which leads to a false positive keyboard opening ([REDACTED_TASK_KEY])
if (falsePositive) return@LaunchedEffect
if (isFalsePositive) return@LaunchedEffect
keyboardState.value = keyboardStateInternal
}

View file

@ -45,8 +45,8 @@ fun ResizableText(
overflow = overflow,
softWrap = false,
maxLines = maxLines,
onTextLayout = {
if (it.hasVisualOverflow) {
onTextLayout = { textLayoutResult ->
if (textLayoutResult.hasVisualOverflow) {
val nextFontSizeValue = fontSizeValue.floatValue - fontSizeRange.step.value
if (nextFontSizeValue <= fontSizeRange.min.value) {
fontSizeValue.floatValue = fontSizeRange.min.value
@ -150,8 +150,8 @@ fun ResizableText(
overflow = overflow,
softWrap = false,
maxLines = maxLines,
onTextLayout = {
if (it.hasVisualOverflow) {
onTextLayout = { result ->
if (result.hasVisualOverflow) {
val nextFontSizeValue = fontSizeValue.value - fontSizeRange.step.value
if (nextFontSizeValue <= fontSizeRange.min.value) {
onFontSizeChange(fontSizeRange.min.value)

View file

@ -66,7 +66,7 @@ fun SimpleSettingsRow(
visible = !subtitle.isNullOrEmpty(),
) {
Text(
text = subtitle ?: "",
text = subtitle.orEmpty(),
style = TangemTheme.typography.body2,
color = rowColors.subtitleColor(enabled = enabled).value,
)

View file

@ -169,17 +169,17 @@ private val TangemShimmerColors: List<Color>
return buildList {
if (isInDarkTheme) {
TangemColorPalette.Dark3.let(::add)
TangemColorPalette.Dark4.let(::add)
TangemColorPalette.Dark6.let(::add)
TangemColorPalette.Dark4.let(::add)
TangemColorPalette.Dark3.let(::add)
add(TangemColorPalette.Dark3)
add(TangemColorPalette.Dark4)
add(TangemColorPalette.Dark6)
add(TangemColorPalette.Dark4)
add(TangemColorPalette.Dark3)
} else {
TangemColorPalette.Light2.let(::add)
TangemColorPalette.Light1.let(::add)
TangemColorPalette.White.let(::add)
TangemColorPalette.Light1.let(::add)
TangemColorPalette.Light2.let(::add)
add(TangemColorPalette.Light2)
add(TangemColorPalette.Light1)
add(TangemColorPalette.White)
add(TangemColorPalette.Light1)
add(TangemColorPalette.Light2)
}
}
}

View file

@ -148,8 +148,8 @@ private fun TangemTextField(
text = label,
style = TangemTheme.typography.caption2,
color = colors.labelColor(
enabled = enabled,
error = isError,
isEnabled = enabled,
isError = isError,
interactionSource = interactionSource,
).value,
)
@ -174,7 +174,7 @@ private fun TangemTextField(
null
},
)
if (iconRes != null) {
iconRes?.let { iconRes ->
IconButton(
modifier = Modifier.size(32.dp),
onClick = onClear,
@ -182,7 +182,7 @@ private fun TangemTextField(
) {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(id = iconRes!!),
painter = painterResource(id = iconRes),
tint = colors.trailingIconColor(enabled = enabled, isError = isError).value,
contentDescription = "Clear input",
)
@ -259,8 +259,8 @@ private fun TangemTextFieldWithIcon(
text = label,
style = TangemTheme.typography.caption2,
color = colors.labelColor(
enabled = enabled,
error = isError,
isEnabled = enabled,
isError = isError,
interactionSource = interactionSource,
).value,
)
@ -399,19 +399,19 @@ fun TextFieldColors.trailingIconColor(enabled: Boolean, isError: Boolean): State
@Composable
fun TextFieldColors.indicatorColor(
enabled: Boolean,
isEnabled: Boolean,
isError: Boolean,
interactionSource: InteractionSource,
): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val isFocused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
!enabled -> disabledIndicatorColor
!isEnabled -> disabledIndicatorColor
isError -> errorIndicatorColor
focused -> focusedIndicatorColor
isFocused -> focusedIndicatorColor
else -> unfocusedIndicatorColor
}
return if (enabled) {
return if (isEnabled) {
animateColorAsState(
targetValue = targetValue,
animationSpec = tween(durationMillis = 120),
@ -428,13 +428,17 @@ fun TextFieldColors.placeholderColor(enabled: Boolean): State<Color> {
}
@Composable
fun TextFieldColors.labelColor(enabled: Boolean, error: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
fun TextFieldColors.labelColor(
isEnabled: Boolean,
isError: Boolean,
interactionSource: InteractionSource,
): State<Color> {
val isFocused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
!enabled -> disabledLabelColor
error -> errorLabelColor
focused -> focusedLabelColor
!isEnabled -> disabledLabelColor
isError -> errorLabelColor
isFocused -> focusedLabelColor
else -> unfocusedLabelColor
}
return rememberUpdatedState(targetValue)

View file

@ -146,7 +146,7 @@ private fun SubtitleView(subtitle: String, icon: Painter?) {
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
verticalAlignment = Alignment.CenterVertically,
) {
icon?.let {
icon?.let { icon ->
Image(
painter = icon,
contentDescription = null,
@ -202,9 +202,9 @@ private fun ExpandedSearchView(
}
TextField(
value = textFieldValue,
onValueChange = {
textFieldValue = it
onSearchChange(it.text)
onValueChange = { value ->
textFieldValue = value
onSearchChange(value.text)
},
singleLine = true,
modifier = Modifier

View file

@ -21,7 +21,7 @@ fun TopAppBarButton(button: TopAppBarButtonUM, tint: Color, modifier: Modifier =
when (button) {
is TopAppBarButtonUM.Icon -> {
IconButton(
enabled = button.enabled,
enabled = button.isEnabled,
modifier = modifier.size(TangemTheme.dimens.size32),
onClick = button.onClicked,
) {
@ -36,7 +36,7 @@ fun TopAppBarButton(button: TopAppBarButtonUM, tint: Color, modifier: Modifier =
is TopAppBarButtonUM.Text -> {
Text(
modifier = modifier
.conditional(button.enabled) {
.conditional(button.isEnabled) {
clickable { button.onClicked() }
}
.padding(4.dp),

View file

@ -6,20 +6,20 @@ import com.tangem.core.ui.extensions.TextReference
sealed class TopAppBarButtonUM(
open val onClicked: () -> Unit,
open val enabled: Boolean = true,
open val isEnabled: Boolean = true,
) {
data class Icon(
@DrawableRes val iconRes: Int,
override val onClicked: () -> Unit,
override val enabled: Boolean = true,
) : TopAppBarButtonUM(onClicked, enabled)
override val isEnabled: Boolean = true,
) : TopAppBarButtonUM(onClicked, isEnabled)
data class Text(
val text: TextReference,
override val onClicked: () -> Unit,
override val enabled: Boolean = true,
) : TopAppBarButtonUM(onClicked, enabled)
override val isEnabled: Boolean = true,
) : TopAppBarButtonUM(onClicked, isEnabled)
@Suppress("FunctionName")
companion object {
@ -29,19 +29,19 @@ sealed class TopAppBarButtonUM(
fun Back(enabled: Boolean = true, onBackClicked: () -> Unit) = Icon(
iconRes = R.drawable.ic_back_24,
onClicked = onBackClicked,
enabled = enabled,
isEnabled = enabled,
)
fun Close(enabled: Boolean = true, onCloseClick: () -> Unit) = Icon(
iconRes = R.drawable.ic_close_24,
onClicked = onCloseClick,
enabled = enabled,
isEnabled = enabled,
)
fun Text(text: TextReference, onTextClicked: () -> Unit, enabled: Boolean = true) = Text(
text = text,
onClicked = onTextClicked,
enabled = enabled,
isEnabled = enabled,
)
}
}

View file

@ -50,8 +50,11 @@ fun ModalBottomSheetWithBackHandling(
modifier = modifier
.focusRequester(requester)
.focusable()
.onPreviewKeyEvent {
if (it.key == Key.Back && it.type == KeyEventType.KeyUp && !it.nativeKeyEvent.isCanceled) {
.onPreviewKeyEvent { keyEvent ->
if (keyEvent.key == Key.Back &&
keyEvent.type == KeyEventType.KeyUp &&
!keyEvent.nativeKeyEvent.isCanceled
) {
backPressedDispatcherOwner?.onBackPressedDispatcher?.onBackPressed()
return@onPreviewKeyEvent true
}

View file

@ -84,7 +84,7 @@ internal fun Content(model: MessageBottomSheetUM, modifier: Modifier = Modifier)
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = model.primaryAction.text.resolveReference(),
enabled = model.primaryAction.enabled,
enabled = model.primaryAction.isEnabled,
onClick = model.primaryAction.onClick,
)
}
@ -93,7 +93,7 @@ internal fun Content(model: MessageBottomSheetUM, modifier: Modifier = Modifier)
SecondaryButton(
modifier = Modifier.fillMaxWidth(),
text = model.secondaryAction.text.resolveReference(),
enabled = model.secondaryAction.enabled,
enabled = model.secondaryAction.isEnabled,
onClick = model.secondaryAction.onClick,
)
}

View file

@ -14,7 +14,7 @@ data class MessageBottomSheetUM(
data class ActionUM(
val text: TextReference,
val enabled: Boolean = true,
val isEnabled: Boolean = true,
val onClick: () -> Unit,
)
}

View file

@ -64,8 +64,8 @@ fun MessageBottomSheetV2(state: MessageBottomSheetUMV2, onDismissRequest: () ->
@Composable
fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
state.elements.fastForEach {
when (it) {
state.elements.fastForEach { element ->
when (element) {
is MessageBottomSheetUMV2.InfoBlock -> {
ContentContainer(
modifier = Modifier
@ -73,7 +73,7 @@ fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifie
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(bottom = 32.dp),
state = it,
state = element,
)
}
else -> Unit
@ -94,32 +94,32 @@ private fun ContentContainer(state: MessageBottomSheetUMV2.InfoBlock, modifier:
state.icon?.let {
BottomSheetIcon(it)
}
state.title?.let {
state.title?.let { title ->
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing24),
text = it.resolveReference(),
text = title.resolveReference(),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
}
state.body?.let {
state.body?.let { body ->
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing8),
text = it.resolveReference(),
text = body.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
}
state.chip?.let {
state.chip?.let { chip ->
BottomSheetChip(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing16),
chip = it,
chip = chip,
)
}
}
@ -193,16 +193,16 @@ private fun ButtonsContainer(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
buttons.fastForEach { button ->
val icon = button.icon?.let {
val icon = button.icon?.let { iconResId ->
when (button.iconOrder) {
IconOrder.Start -> TangemButtonIconPosition.Start(it)
IconOrder.End -> TangemButtonIconPosition.End(it)
IconOrder.Start -> TangemButtonIconPosition.Start(iconResId)
IconOrder.End -> TangemButtonIconPosition.End(iconResId)
}
} ?: TangemButtonIconPosition.None
TangemButton(
modifier = Modifier.fillMaxWidth(),
text = button.text?.resolveReference() ?: "",
text = button.text?.resolveReference().orEmpty(),
icon = icon,
onClick = { button.onClick?.invoke(closeScope) },
colors = if (button.isPrimary) {

View file

@ -95,9 +95,10 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheet(
var isVisible by remember { mutableStateOf(value = config.isShown) }
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = skipPartiallyExpanded,
confirmValueChange = {
confirmValueChange = { sheetValue ->
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 {
true
}

View file

@ -163,11 +163,11 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
val bsContent: @Composable ColumnScope.() -> Unit = {
Column(
modifier = Modifier.let {
modifier = Modifier.let { modifier ->
if (addBottomInsets) {
it.padding(bottom = bottomBarHeight)
modifier.padding(bottom = bottomBarHeight)
} else {
it
modifier
}
},
) {

View file

@ -77,7 +77,7 @@ private class ActionButtonConfigProvider : CollectionPreviewParameterProvider<Ho
text = TextReference.Str(value = "Exchange"),
iconResId = R.drawable.ic_exchange_vertical_24,
onClick = {},
showBadge = true,
shouldShowBadge = true,
),
ActionButtonConfig(
text = TextReference.Str(value = "Send"),

View file

@ -33,7 +33,7 @@ data class SmallButtonConfig(
val text: TextReference,
val onClick: () -> Unit,
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,
shape = shape,
)
.clickable(enabled = config.enabled, onClick = config.onClick)
.clickable(enabled = config.isEnabled, onClick = config.onClick)
.padding(
paddingValues = when (config.icon) {
is TangemButtonIconPosition.None -> PaddingValues(
@ -103,7 +103,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
text = {
val textColor by animateColorAsState(
targetValue = when {
!config.enabled -> TangemTheme.colors.text.disabled
!config.isEnabled -> TangemTheme.colors.text.disabled
isPrimary -> TangemTheme.colors.text.primary2
else -> TangemTheme.colors.text.primary1
},
@ -122,7 +122,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = iconResId),
tint = if (config.enabled) {
tint = if (config.isEnabled) {
TangemTheme.colors.icon.secondary
} else {
TangemTheme.colors.icon.inactive
@ -188,7 +188,7 @@ private fun ButtonsSample() {
config = config.copy(
text = TextReference.Str(value = "Add token"),
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
*
* @property text text
* @property iconResId icon resource id
* @property onClick lambda be invoked when action component is clicked
* @property onLongClick lambda be invoked when action component is long clicked
* @property enabled enabled
* @property dimContent determines whether the button content will be dimmed. This property will be ignored if [enabled]
* is `false`.
* @property isInProgress indicates progress state of button
* @property showBadge display dot in upper right corner
* @property text text
* @property iconResId icon resource id
* @property onClick lambda be invoked when action component is clicked
* @property onLongClick lambda be invoked when action component is long clicked
* @property isEnabled enabled
* @property shouldDimContent determines whether the button content will be dimmed. This property will be ignored if [isEnabled] is `false`.
* @property isInProgress indicates progress state of button
* @property shouldShowBadge display dot in upper right corner
*
[REDACTED_AUTHOR]
*/
@ -23,8 +22,8 @@ data class ActionButtonConfig(
@DrawableRes val iconResId: Int,
val onClick: () -> Unit,
val onLongClick: (() -> TextReference?)? = null,
val enabled: Boolean = true,
val dimContent: Boolean = false,
val isEnabled: Boolean = true,
val shouldDimContent: Boolean = false,
val isInProgress: Boolean = false,
val showBadge: Boolean = false,
val shouldShowBadge: Boolean = false,
)

View file

@ -55,11 +55,11 @@ fun RoundedActionButton(
ActionBaseButton(
config = config,
shape = RoundedCornerShape(size = TangemTheme.dimens.radius24),
content = {
content = { modifier ->
ActionButtonContent(
config = config,
text = { Text(text = config.text, textColor = it) },
modifier = it.padding(
text = { color -> Text(text = config.text, textColor = color) },
modifier = modifier.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing24,
),
@ -118,7 +118,7 @@ fun ActionBaseButton(
) {
val context = LocalContext.current
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",
)
@ -128,17 +128,17 @@ fun ActionBaseButton(
.widthIn(min = 100.dp)
.drawWithContent {
drawContent()
if (config.showBadge) {
if (config.shouldShowBadge) {
drawBadge(containerColor = containerColor)
}
}
.clip(shape)
.combinedClickable(
enabled = config.enabled,
enabled = config.isEnabled,
onClick = config.onClick,
onLongClick = {
val toastReference = config.onLongClick?.invoke()
toastReference?.let {
toastReference?.let { toastReference ->
Toast
.makeText(context, toastReference.resolveReference(context.resources), Toast.LENGTH_SHORT)
.show()
@ -175,8 +175,8 @@ fun ActionButtonContent(
verticalAlignment = Alignment.CenterVertically,
) {
val iconTint = when {
!config.enabled -> TangemTheme.colors.icon.informative
config.dimContent -> TangemTheme.colors.icon.informative
!config.isEnabled -> TangemTheme.colors.icon.informative
config.shouldDimContent -> TangemTheme.colors.icon.informative
else -> TangemTheme.colors.icon.primary1
}
Icon(
@ -220,8 +220,8 @@ private fun Loading(backgroundColor: Color, modifier: Modifier = Modifier) {
@ReadOnlyComposable
fun getTextColor(config: ActionButtonConfig): Color {
return when {
!config.enabled -> TangemTheme.colors.text.disabled
config.dimContent -> TangemTheme.colors.text.tertiary
!config.isEnabled -> TangemTheme.colors.text.disabled
config.shouldDimContent -> TangemTheme.colors.text.tertiary
else -> TangemTheme.colors.text.primary1
}
}
@ -249,27 +249,27 @@ private class ActionStateProvider : CollectionPreviewParameterProvider<ActionBut
ActionButtonConfig(
text = TextReference.Str(value = "Enabled"),
iconResId = R.drawable.ic_arrow_up_24,
enabled = true,
isEnabled = true,
onClick = {},
showBadge = true,
shouldShowBadge = true,
),
ActionButtonConfig(
text = TextReference.Str(value = "Dimmed"),
iconResId = R.drawable.ic_arrow_up_24,
enabled = true,
dimContent = true,
isEnabled = true,
shouldDimContent = true,
onClick = {},
),
ActionButtonConfig(
text = TextReference.Str(value = "Disabled"),
iconResId = R.drawable.ic_arrow_down_24,
enabled = false,
isEnabled = false,
onClick = {},
),
ActionButtonConfig(
text = TextReference.Str(value = "Loading"),
iconResId = R.drawable.ic_arrow_down_24,
enabled = false,
isEnabled = false,
onClick = {},
isInProgress = true,
),

View file

@ -112,9 +112,9 @@ private fun SegmentedButtonsPreview(
SegmentedButtons(
config = config,
onClick = {},
) {
) { configPreview ->
Text(
text = it.text,
text = configPreview.text,
modifier = Modifier.padding(TangemTheme.dimens.spacing16),
)
}

View file

@ -127,7 +127,7 @@ private fun BoxScope.ContentIconContainer(
)
}
if (icon.showCustomBadge) {
if (icon.shouldShowCustomBadge) {
CurrencyIconBottomBadge(
modifier = Modifier.align(Alignment.BottomEnd),
)

View file

@ -15,7 +15,7 @@ import com.tangem.core.ui.extensions.TextReference
sealed class CurrencyIconState {
abstract val isGrayscale: Boolean
abstract val showCustomBadge: Boolean
abstract val shouldShowCustomBadge: Boolean
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 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 showCustomBadge Specifies whether to show the custom token badge.
* @property shouldShowCustomBadge Specifies whether to show the custom token badge.
*/
data class CoinIcon(
val url: String?,
@DrawableRes val fallbackResId: Int,
override val isGrayscale: Boolean,
override val showCustomBadge: Boolean,
override val shouldShowCustomBadge: Boolean,
) : CurrencyIconState() {
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 topBadgeIconResId The drawable resource ID for the network badge. May be `null`.
* @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 fallbackBackground The background color to be used for the fallback icon.
*/
@ -50,7 +50,7 @@ sealed class CurrencyIconState {
val url: String?,
@DrawableRes override val topBadgeIconResId: Int?,
override val isGrayscale: Boolean,
override val showCustomBadge: Boolean,
override val shouldShowCustomBadge: Boolean,
val fallbackTint: Color,
val fallbackBackground: Color,
) : CurrencyIconState()
@ -62,14 +62,14 @@ sealed class CurrencyIconState {
* @property background The background color to be used for the icon.
* @property topBadgeIconResId The drawable resource ID for the network badge.
* @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(
val tint: Color,
val background: Color,
@DrawableRes override val topBadgeIconResId: Int,
override val isGrayscale: Boolean,
override val showCustomBadge: Boolean = true,
override val shouldShowCustomBadge: Boolean = true,
) : CurrencyIconState()
/**
@ -83,13 +83,13 @@ sealed class CurrencyIconState {
@DrawableRes val fallbackResId: Int,
) : CurrencyIconState() {
override val isGrayscale: Boolean = false
override val showCustomBadge: Boolean = false
override val shouldShowCustomBadge: Boolean = false
override val topBadgeIconResId: Int? = null
}
@Immutable
sealed class CryptoPortfolio : CurrencyIconState() {
override val showCustomBadge: Boolean = false
override val shouldShowCustomBadge: Boolean = false
override val topBadgeIconResId: Int? = null
abstract val color: Color
@ -108,13 +108,13 @@ sealed class CurrencyIconState {
data object Loading : CurrencyIconState() {
override val isGrayscale: Boolean = false
override val showCustomBadge: Boolean = false
override val shouldShowCustomBadge: Boolean = false
override val topBadgeIconResId: Int? = null
}
data object Locked : CurrencyIconState() {
override val isGrayscale: Boolean = false
override val showCustomBadge: Boolean = false
override val shouldShowCustomBadge: Boolean = false
override val topBadgeIconResId: Int? = null
}
@ -122,27 +122,27 @@ sealed class CurrencyIconState {
@DrawableRes val resId: Int = R.drawable.ic_empty_64,
) : CurrencyIconState() {
override val isGrayscale: Boolean = true
override val showCustomBadge: Boolean = false
override val shouldShowCustomBadge: Boolean = false
override val topBadgeIconResId: Int? = null
}
fun copySealed(
isGrayscale: Boolean = this.isGrayscale,
showCustomBadge: Boolean = this.showCustomBadge,
showCustomBadge: Boolean = this.shouldShowCustomBadge,
topBadgeIconResId: Int? = this.topBadgeIconResId,
): CurrencyIconState = when (this) {
is CoinIcon -> copy(
isGrayscale = isGrayscale,
showCustomBadge = showCustomBadge,
shouldShowCustomBadge = showCustomBadge,
)
is CustomTokenIcon -> copy(
isGrayscale = isGrayscale,
showCustomBadge = showCustomBadge,
shouldShowCustomBadge = showCustomBadge,
topBadgeIconResId = topBadgeIconResId ?: this.topBadgeIconResId,
)
is TokenIcon -> copy(
isGrayscale = isGrayscale,
showCustomBadge = showCustomBadge,
shouldShowCustomBadge = showCustomBadge,
topBadgeIconResId = topBadgeIconResId,
)
is CryptoPortfolio.Icon -> copy(

View file

@ -28,7 +28,7 @@ object CurrencyIconStateBuilder {
url = url,
fallbackResId = fallbackResId,
isGrayscale = isGrayscale,
showCustomBadge = showCustomBadge,
shouldShowCustomBadge = showCustomBadge,
)
private fun createTokenIcon(
@ -44,7 +44,7 @@ object CurrencyIconStateBuilder {
isGrayscale = isGrayscale,
fallbackTint = fallbackTint,
fallbackBackground = fallbackBackground,
showCustomBadge = showCustomBadge,
shouldShowCustomBadge = showCustomBadge,
)
private fun createCustomTokenIcon(
@ -58,7 +58,7 @@ object CurrencyIconStateBuilder {
background = background,
topBadgeIconResId = topBadgeIconResId,
isGrayscale = isGrayscale,
showCustomBadge = showCustomBadge,
shouldShowCustomBadge = showCustomBadge,
)
private fun fromCoin(
@ -77,8 +77,8 @@ object CurrencyIconStateBuilder {
isGrayscale: Boolean = false,
showCustomBadge: Boolean = true,
): CurrencyIconState {
val grayScale = isGrayscale || token.network.isTestnet
val background = token.tryGetBackgroundForTokenIcon(grayScale)
val isGrayscaleOrTestnet = isGrayscale || token.network.isTestnet
val background = token.tryGetBackgroundForTokenIcon(isGrayscaleOrTestnet)
val tint = getTintForTokenIcon(background)
return if (token.isCustom && token.iconUrl == null) {
@ -86,14 +86,14 @@ object CurrencyIconStateBuilder {
tint = tint,
background = background,
topBadgeIconResId = token.networkIconResId,
isGrayscale = grayScale,
isGrayscale = isGrayscaleOrTestnet,
showCustomBadge = showCustomBadge,
)
} else {
createTokenIcon(
url = token.iconUrl,
topBadgeIconResId = token.networkIconResId,
isGrayscale = grayScale,
isGrayscale = isGrayscaleOrTestnet,
fallbackTint = tint,
fallbackBackground = background,
showCustomBadge = token.isCustom && showCustomBadge,

View file

@ -62,7 +62,7 @@ class CryptoCurrencyToIconStateConverter(
url = coin.iconUrl,
fallbackResId = coin.networkIconResId,
isGrayscale = forceGrayscale || coin.network.isTestnet || isUnreachable || !isAvailable,
showCustomBadge = coin.isCustom && showCustomBadge,
shouldShowCustomBadge = coin.isCustom && showCustomBadge,
)
}
@ -72,8 +72,8 @@ class CryptoCurrencyToIconStateConverter(
showCustomBadge: Boolean = true,
forceGrayscale: Boolean = false,
): CurrencyIconState {
val grayScale = forceGrayscale || token.network.isTestnet || isErrorStatus || !isAvailable
val background = token.tryGetBackgroundForTokenIcon(grayScale)
val isGrayscale = forceGrayscale || token.network.isTestnet || isErrorStatus || !isAvailable
val background = token.tryGetBackgroundForTokenIcon(isGrayscale)
val tint = getTintForTokenIcon(background)
return if (token.isCustom && token.iconUrl == null) {
@ -81,17 +81,17 @@ class CryptoCurrencyToIconStateConverter(
tint = tint,
background = background,
topBadgeIconResId = token.networkIconResId,
isGrayscale = grayScale,
showCustomBadge = showCustomBadge,
isGrayscale = isGrayscale,
shouldShowCustomBadge = showCustomBadge,
)
} else {
CurrencyIconState.TokenIcon(
url = token.iconUrl,
topBadgeIconResId = token.networkIconResId,
isGrayscale = grayScale,
isGrayscale = isGrayscale,
fallbackTint = tint,
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 toCenter = anchorBounds.top - popupContentSize.height / 2
val toDisplayBottom = windowSize.height - popupContentSize.height - verticalMargin
val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull {
it >= verticalMargin &&
it + popupContentSize.height <= windowSize.height - verticalMargin
val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull { element ->
element >= verticalMargin &&
element + popupContentSize.height <= windowSize.height - verticalMargin
} ?: toTop
onPositionCalculated(

View file

@ -189,24 +189,24 @@ private class AmountTextFieldPreviewProvider : PreviewParameterProvider<AmountTe
value = "1000000,123123",
decimals = 3,
placeholderAlignment = TopStart,
showPlaceholder = true,
shouldShowPlaceholder = true,
),
AmountTextFieldPreviewData(
value = "1000000.123123",
decimals = 6,
placeholderAlignment = TopStart,
showPlaceholder = false,
shouldShowPlaceholder = false,
),
AmountTextFieldPreviewData(
value = null,
decimals = 2,
showPlaceholder = true,
shouldShowPlaceholder = true,
placeholderAlignment = TopCenter,
),
AmountTextFieldPreviewData(
value = null,
decimals = 2,
showPlaceholder = true,
shouldShowPlaceholder = true,
placeholderAlignment = TopStart,
),
)
@ -215,7 +215,7 @@ private class AmountTextFieldPreviewProvider : PreviewParameterProvider<AmountTe
private data class AmountTextFieldPreviewData(
val value: String? = null,
val decimals: Int = 2,
val showPlaceholder: Boolean,
val shouldShowPlaceholder: Boolean,
val placeholderAlignment: Alignment,
)
// endregion

View file

@ -72,7 +72,12 @@ fun AutoSizeTextField(
) {
BoxWithConstraints(modifier = boxModifier) {
val fontSize = if (isAutoResize) {
resizeFont(visualTransformation, value, textStyle, reduceFactor)
resizeFont(
visualTransformation = visualTransformation,
value = value,
textStyle = textStyle,
reduceFactor = reduceFactor,
)
} else {
textStyle.fontSize
}
@ -141,7 +146,7 @@ private fun AmountTextFieldPreview(
textFieldModifier = Modifier.fillMaxWidth(),
value = text,
onValueChange = { text = it },
centered = data.centered,
centered = data.isCentered,
isAutoResize = data.isAutoResize,
placeholder = data.placeholder,
)
@ -154,43 +159,43 @@ private class AutoSizeTextFieldPreviewProvider : PreviewParameterProvider<AutoSi
value = "AutoSizeTextField",
placeholder = stringReference("placeholder"),
isAutoResize = true,
centered = false,
isCentered = false,
),
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
placeholder = stringReference("placeholder"),
isAutoResize = true,
centered = false,
isCentered = false,
),
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
placeholder = stringReference("Placeholder"),
isAutoResize = true,
centered = false,
isCentered = false,
),
AutoSizeTextFieldPreviewData(
value = "",
placeholder = stringReference("Placeholder"),
isAutoResize = true,
centered = false,
isCentered = false,
),
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextField",
placeholder = stringReference("Placeholder"),
isAutoResize = false,
centered = true,
isCentered = true,
),
AutoSizeTextFieldPreviewData(
value = "AutoSizeTextFieldAutoSizeTextFieldAutoSizeTextFieldAutoSizeTextField",
placeholder = stringReference("Placeholder"),
isAutoResize = false,
centered = true,
isCentered = true,
),
AutoSizeTextFieldPreviewData(
value = "",
placeholder = stringReference("Placeholder"),
isAutoResize = false,
centered = true,
isCentered = true,
),
)
}
@ -199,6 +204,6 @@ private data class AutoSizeTextFieldPreviewData(
val value: String,
val placeholder: TextReference,
val isAutoResize: Boolean,
val centered: Boolean,
val isCentered: Boolean,
)
// endregion

View file

@ -47,9 +47,9 @@ fun PinTextField(
BasicTextField(
value = textFieldValue,
onValueChange = {
if (it.text.length <= length) {
onValueChange(it.text)
onValueChange = { value ->
if (value.text.length <= length) {
onValueChange(value.text)
}
},
modifier = modifier

Some files were not shown because too many files have changed in this diff Show more