Updated on 2026-08-14
This commit is contained in:
parent
acf110dbcc
commit
6910f74fd9
242 changed files with 866 additions and 732 deletions
|
|
@ -22,7 +22,7 @@ internal fun SettingsAlertDialog(dialog: Dialog.Alert) {
|
|||
isDismissable = false,
|
||||
confirmButton = DialogButtonUM(
|
||||
title = dialog.confirmText.resolveReference(),
|
||||
warning = true,
|
||||
isWarning = true,
|
||||
onClick = dialog.onConfirm,
|
||||
),
|
||||
dismissButton = DialogButtonUM(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,19 +21,20 @@ object MockNetworkStatusFactory {
|
|||
): NetworkStatus {
|
||||
return NetworkStatus(
|
||||
network = network,
|
||||
value = NetworkStatus.Verified(
|
||||
value = transform(
|
||||
NetworkStatus.Verified(
|
||||
address = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "0x1",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
),
|
||||
amounts = mapOf(),
|
||||
pendingTransactions = mapOf(),
|
||||
yieldSupplyStatuses = mapOf(),
|
||||
amounts = emptyMap(),
|
||||
pendingTransactions = emptyMap(),
|
||||
yieldSupplyStatuses = emptyMap(),
|
||||
source = source,
|
||||
)
|
||||
.let(transform),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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") }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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, _, _ ->
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -105,9 +105,9 @@ private fun Info(
|
|||
)
|
||||
}
|
||||
|
||||
notifications.fastForEach {
|
||||
key(it.hashCode()) {
|
||||
Notification(config = it)
|
||||
notifications.fastForEach { notificationConfig ->
|
||||
key(notificationConfig.hashCode()) {
|
||||
Notification(config = notificationConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ sealed class NavigationButtonsState {
|
|||
* @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 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`.
|
||||
|
|
@ -31,7 +31,7 @@ data class NavigationButton(
|
|||
@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,
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
add(
|
||||
when (sendingCurrency) {
|
||||
is CryptoCurrency.Coin -> NotificationUM.Cardano.InsufficientBalanceToTransferCoin
|
||||
is CryptoCurrency.Token -> {
|
||||
NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
||||
}
|
||||
}.let(::add)
|
||||
},
|
||||
)
|
||||
}
|
||||
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) },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,15 +219,19 @@ class TokenItemStateConverter(
|
|||
isFlickering = status.value.isFlickering(),
|
||||
icons = buildList {
|
||||
if (!status.getStakedBalance().isZero()) {
|
||||
add(
|
||||
TokenItemState.FiatAmountState.Content.IconUM(
|
||||
iconRes = R.drawable.ic_staking_24,
|
||||
tint = IconTint.Accent,
|
||||
).let(::add)
|
||||
),
|
||||
)
|
||||
}
|
||||
if (status.value.sources.total == StatusSource.ONLY_CACHE) {
|
||||
add(
|
||||
TokenItemState.FiatAmountState.Content.IconUM(
|
||||
iconRes = R.drawable.ic_error_sync_24,
|
||||
).let(::add)
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toImmutableList(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
requireNotNull(
|
||||
context
|
||||
.packageManager
|
||||
.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(0))
|
||||
.versionName,
|
||||
)
|
||||
.versionName!!
|
||||
} else {
|
||||
context.packageManager.getPackageInfo(context.packageName, 0).versionName!!
|
||||
requireNotNull(context.packageManager.getPackageInfo(context.packageName, 0).versionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
interface ApiConfigsManager {
|
||||
|
||||
/** Flag that determines whether the manager is initialized */
|
||||
val isInitialized: StateFlow<Boolean>
|
||||
val initializedState: StateFlow<Boolean>
|
||||
|
||||
/** Initialize resources */
|
||||
fun initialize()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ data class TokenMarketInfoResponse(
|
|||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
data class Network(
|
||||
@Json(name = "network_id")
|
||||
val networkId: String,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ data class ActionRequestBody(
|
|||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
data class ActionRequestBodyArgs(
|
||||
@Json(name = "amount")
|
||||
val amount: String,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@file:Suppress("BooleanPropertyNaming")
|
||||
|
||||
package com.tangem.datasource.api.stakekit.models.response.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@file:Suppress("BooleanPropertyNaming")
|
||||
|
||||
package com.tangem.datasource.api.stakekit.models.response.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ internal class RetrofitApiBuilder @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("UseEmptyCounterpart")
|
||||
private companion object {
|
||||
|
||||
val excludedApiForLogging: Set<ApiConfig.ID> = setOf(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ data class MessageBottomSheetUM(
|
|||
|
||||
data class ActionUM(
|
||||
val text: TextReference,
|
||||
val enabled: Boolean = true,
|
||||
val isEnabled: Boolean = true,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
},
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
* @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 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 showBadge display dot in upper right corner
|
||||
* @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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ private fun BoxScope.ContentIconContainer(
|
|||
)
|
||||
}
|
||||
|
||||
if (icon.showCustomBadge) {
|
||||
if (icon.shouldShowCustomBadge) {
|
||||
CurrencyIconBottomBadge(
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue