Updated on 2026-08-14
This commit is contained in:
commit
af09ce9ca4
877 changed files with 16091 additions and 6613 deletions
|
|
@ -25,7 +25,6 @@ dependencies {
|
|||
implementation(deps.firebase.messaging)
|
||||
// end
|
||||
|
||||
implementation(deps.timber)
|
||||
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ dependencies {
|
|||
/* Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/* Domain */
|
||||
implementation(projects.domain.qrScanning.models)
|
||||
|
|
@ -30,7 +31,6 @@ dependencies {
|
|||
/* Libs - Other */
|
||||
api(deps.kotlin.serialization)
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.timber)
|
||||
|
||||
/* Tests */
|
||||
testImplementation(deps.test.junit)
|
||||
|
|
|
|||
|
|
@ -146,7 +146,6 @@ sealed class AppRoute(val path: String) : Route {
|
|||
STORIES,
|
||||
SETTINGS,
|
||||
ACCOUNT,
|
||||
TOKEN_SYNC_BANNER,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
package com.tangem.common.routing
|
||||
|
||||
import android.net.Uri
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Routes in-app content links (deep links and external URLs)
|
||||
* - `tangem://` scheme → parsed to AppRoute and pushed via AppRouter
|
||||
* - `https://` / `http://` → opened in external browser via UrlOpener
|
||||
* - Unknown scheme → logged and ignored
|
||||
*/
|
||||
class LinkHandler(
|
||||
private val appRouter: AppRouter,
|
||||
) {
|
||||
|
||||
fun navigate(link: String) {
|
||||
val uri = Uri.parse(link)
|
||||
handleTangemDeepLink(uri)
|
||||
}
|
||||
|
||||
private fun handleTangemDeepLink(uri: Uri) {
|
||||
val route = parseDeepLinkToRoute(uri)
|
||||
if (route != null) {
|
||||
appRouter.push(route)
|
||||
} else {
|
||||
Timber.w("ContentLinkHandler: unrecognized tangem deep link: %s", uri)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedParameter", "FunctionOnlyReturningConstant")
|
||||
private fun parseDeepLinkToRoute(uri: Uri): AppRoute? {
|
||||
// TODO [REDACTED_TASK_KEY] refactor deepling routing
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -35,5 +35,9 @@ object TangemBlogUrlBuilder {
|
|||
data object WhatIsTransactionFee : Post {
|
||||
override val path: String = "what-is-a-transaction-fee-and-why-do-we-need-it"
|
||||
}
|
||||
|
||||
data object HowToScan : Post {
|
||||
override val path: String = "scan-tangem-card"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.common.uri
|
||||
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import timber.log.Timber
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
|
|
@ -22,7 +22,7 @@ object ExternalUrlValidator {
|
|||
} catch (e: Exception) {
|
||||
val exception = IllegalStateException("Failed to validate URI: $externalUri", e)
|
||||
|
||||
Timber.e(exception)
|
||||
TangemLogger.e("Error", exception)
|
||||
FirebaseCrashlytics.getInstance().recordException(exception)
|
||||
|
||||
false
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ object MockQuoteResponseFactory {
|
|||
priceChange24h = value,
|
||||
priceChange1w = value,
|
||||
priceChange30d = value,
|
||||
priceUsd = value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource =
|
|||
source = source,
|
||||
fiatRate = price.orZero(),
|
||||
priceChange = priceChange24h.orZero().movePointLeft(2),
|
||||
fiatRateUSD = priceUsd.orZero(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,20 +24,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisGuidelineComponent
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLabelComponent
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottomAxis
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberCustomStartAxis
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.*
|
||||
import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart
|
||||
import com.patrykandpatrick.vico.compose.cartesian.rememberVicoScrollState
|
||||
import com.patrykandpatrick.vico.compose.cartesian.rememberVicoZoomState
|
||||
import com.patrykandpatrick.vico.compose.common.of
|
||||
import com.patrykandpatrick.vico.core.cartesian.HorizontalLayout
|
||||
import com.patrykandpatrick.vico.core.cartesian.Zoom
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.AxisPosition
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.BaseAxis
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.*
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.AxisValueOverrider
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
|
||||
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
|
||||
|
|
@ -54,6 +48,7 @@ import com.tangem.common.ui.charts.state.*
|
|||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.res.LocalHapticManager
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.DateTimeFormatters
|
||||
|
|
@ -98,14 +93,26 @@ fun MarketChart(
|
|||
|
||||
val marker = rememberTangemChartMarker(color = state.chartColor)
|
||||
|
||||
val chart = rememberCartesianChart(
|
||||
layer,
|
||||
startAxis = rememberMarketChartStartAxis(state.yValueFormatter),
|
||||
bottomAxis = rememberMarketChartBottomAxis(state.xValueFormatter),
|
||||
horizontalLayout = HorizontalLayout.FullWidth(),
|
||||
markerVisibilityListener = rememberMarketVisibilityListener(canvasWidth, state),
|
||||
marker = marker,
|
||||
)
|
||||
val chart = if (state.isMinMaxLook) {
|
||||
rememberCartesianChart(
|
||||
layer,
|
||||
startAxis = rememberMarketChartStartMinMaxAxis(state.yValueFormatter),
|
||||
endAxis = rememberMarketChartEndAxis(),
|
||||
bottomAxis = rememberMarketChartBottomAxis(state.xValueFormatter),
|
||||
horizontalLayout = HorizontalLayout.FullWidth(),
|
||||
markerVisibilityListener = rememberMarketVisibilityListener(canvasWidth, state),
|
||||
marker = marker,
|
||||
)
|
||||
} else {
|
||||
rememberCartesianChart(
|
||||
layer,
|
||||
startAxis = rememberMarketChartStartAxis(state.yValueFormatter),
|
||||
bottomAxis = rememberMarketChartBottomAxis(state.xValueFormatter),
|
||||
horizontalLayout = HorizontalLayout.FullWidth(),
|
||||
markerVisibilityListener = rememberMarketVisibilityListener(canvasWidth, state),
|
||||
marker = marker,
|
||||
)
|
||||
}
|
||||
|
||||
// we need to calculate what the overall height should be in order to get the correct height of the graph
|
||||
val bottomAxisHeight = with(LocalDensity.current) {
|
||||
|
|
@ -180,6 +187,62 @@ private fun rememberMarketVisibilityListener(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberMarketChartEndAxis(): VerticalAxis<AxisPosition.Vertical.End> {
|
||||
return rememberEndAxis(
|
||||
line = null,
|
||||
tick = null,
|
||||
guideline = rememberChartAxisGuidelineComponent(
|
||||
color = TangemTheme.colors.icon.inactive.copy(alpha = 0.12f),
|
||||
isWithPadding = false,
|
||||
),
|
||||
label = null,
|
||||
horizontalLabelPosition = VerticalAxis.HorizontalLabelPosition.Inside,
|
||||
verticalLabelPosition = VerticalAxis.VerticalLabelPosition.Center,
|
||||
itemPlacer = MidVerticalAxisItemPlacer(false),
|
||||
valueFormatter = CartesianValueFormatter.yPercent(),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberMarketChartStartMinMaxAxis(
|
||||
yValueFormatter: CartesianValueFormatter,
|
||||
): VerticalAxis<AxisPosition.Vertical.Start> {
|
||||
val textStyle = TangemTheme.typography.caption2
|
||||
val resolver = LocalFontFamilyResolver.current
|
||||
val typeface by remember(resolver, textStyle) {
|
||||
resolver.resolveAsTypeface(
|
||||
fontFamily = textStyle.fontFamily,
|
||||
fontWeight = textStyle.fontWeight ?: FontWeight.Normal,
|
||||
fontStyle = textStyle.fontStyle ?: FontStyle.Normal,
|
||||
fontSynthesis = textStyle.fontSynthesis ?: FontSynthesis.All,
|
||||
)
|
||||
}
|
||||
|
||||
return rememberMinMaxStartAxis(
|
||||
line = null,
|
||||
tick = null,
|
||||
guideline = null,
|
||||
labelGuideline = rememberChartAxisGuidelineComponent(
|
||||
color = TangemTheme.colors.icon.inactive.copy(alpha = 0.12f),
|
||||
),
|
||||
label = rememberAxisLabelComponent(
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
background = null,
|
||||
padding = Dimensions.of(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
textSize = TangemTheme.typography.caption2.fontSize,
|
||||
typeface = typeface,
|
||||
),
|
||||
horizontalLabelPosition = VerticalAxis.HorizontalLabelPosition.Inside,
|
||||
verticalLabelPosition = VerticalAxis.VerticalLabelPosition.Center,
|
||||
itemPlacer = VerticalAxis.ItemPlacer.count({ 2 }, false),
|
||||
valueFormatter = yValueFormatter,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberMarketChartStartAxis(
|
||||
yValueFormatter: CartesianValueFormatter,
|
||||
|
|
@ -253,13 +316,27 @@ private fun rememberMarketChartBottomAxis(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberChartAxisGuidelineComponent(color: Color): LineComponent {
|
||||
private fun rememberChartAxisGuidelineComponent(color: Color, isWithPadding: Boolean = true): LineComponent {
|
||||
val isRedesign = LocalRedesignEnabled.current
|
||||
|
||||
val startPadding = if (isRedesign) {
|
||||
TangemTheme.dimens2.x2_5
|
||||
} else {
|
||||
TangemTheme.dimens.spacing4
|
||||
}
|
||||
|
||||
val endPadding = if (isRedesign) {
|
||||
TangemTheme.dimens2.x2
|
||||
} else {
|
||||
TangemTheme.dimens.spacing4
|
||||
}
|
||||
|
||||
return rememberAxisGuidelineComponent(
|
||||
color = color,
|
||||
shape = Shape.Rectangle,
|
||||
margins = Dimensions(
|
||||
startDp = TangemTheme.dimens.spacing4.value,
|
||||
endDp = TangemTheme.dimens.spacing4.value,
|
||||
startDp = if (isWithPadding) startPadding.value else 0f,
|
||||
endDp = if (isWithPadding) endPadding.value else 0f,
|
||||
topDp = 0f,
|
||||
bottomDp = 0f,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import com.tangem.common.ui.charts.state.formatter.AxisLabelFormatter
|
|||
/**
|
||||
* This class represents the look and feel of a Market Chart.
|
||||
* It includes properties for type, marker highlight, animation on data change, animate data appearance,
|
||||
* and formatters for x and y axis.
|
||||
* and formatters for x and y-axis.
|
||||
*
|
||||
* @property type The type of the chart, can be either Growing or Falling.
|
||||
* @property isMinMaxLook A boolean indicating whether the chart should contain markers for minimum and maximum values.
|
||||
* @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.
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.common.ui.charts.state.formatter.AxisLabelFormatter
|
|||
@Immutable
|
||||
data class MarketChartLook(
|
||||
val type: Type = Type.Growing,
|
||||
val isMinMaxLook: Boolean = true,
|
||||
val shouldMarkerHighlightRightSide: Boolean = true,
|
||||
val xAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
||||
val yAxisFormatter: AxisLabelFormatter = AxisLabelFormatter { it.toString() },
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ fun rememberMarketChartState(
|
|||
* @property lookState The look state of the Market Chart.
|
||||
* @property colorMapper A function that maps a MarketChartLook.Type to a Color.
|
||||
* @property markerCallback A callback function that is called when the marker is shown, hidden, or updated.
|
||||
* @property isDrawingAnimationInProgress A boolean indicating whether the drawing animation is in progress.
|
||||
*/
|
||||
@Stable
|
||||
class MarketChartState internal constructor(
|
||||
|
|
@ -70,6 +69,10 @@ class MarketChartState internal constructor(
|
|||
lookState.value.shouldMarkerHighlightRightSide
|
||||
}
|
||||
|
||||
internal val isMinMaxLook by derivedStateOf {
|
||||
lookState.value.isMinMaxLook
|
||||
}
|
||||
|
||||
internal val xValueFormatter = CartesianValueFormatter { value, _, _ ->
|
||||
val formatter = dataProducer.lookState.value.xAxisFormatter
|
||||
|
||||
|
|
|
|||
|
|
@ -8,23 +8,20 @@ import com.tangem.utils.converter.Converter
|
|||
|
||||
class AccountIconItemStateConverter(
|
||||
val size: AccountIconSize = AccountIconSize.Default,
|
||||
) : Converter<Account, CurrencyIconState.CryptoPortfolio> {
|
||||
) : Converter<Account.CryptoPortfolio, CurrencyIconState.CryptoPortfolio> {
|
||||
|
||||
override fun convert(value: Account): CurrencyIconState.CryptoPortfolio = when (value) {
|
||||
is Account.CryptoPortfolio -> when {
|
||||
value.icon.value == CryptoPortfolioIcon.Icon.Letter -> CurrencyIconState.CryptoPortfolio.Letter(
|
||||
char = value.accountName.toUM().value,
|
||||
color = value.icon.color.getUiColor(),
|
||||
isGrayscale = false,
|
||||
size = size,
|
||||
)
|
||||
else -> CurrencyIconState.CryptoPortfolio.Icon(
|
||||
resId = value.icon.value.getResId(),
|
||||
color = value.icon.color.getUiColor(),
|
||||
isGrayscale = false,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
is Account.Payment -> TODO("[REDACTED_JIRA]")
|
||||
override fun convert(value: Account.CryptoPortfolio): CurrencyIconState.CryptoPortfolio = when {
|
||||
value.icon.value == CryptoPortfolioIcon.Icon.Letter -> CurrencyIconState.CryptoPortfolio.Letter(
|
||||
char = value.accountName.toUM().value,
|
||||
color = value.icon.color.getUiColor(),
|
||||
isGrayscale = false,
|
||||
size = size,
|
||||
)
|
||||
else -> CurrencyIconState.CryptoPortfolio.Icon(
|
||||
resId = value.icon.value.getResId(),
|
||||
color = value.icon.color.getUiColor(),
|
||||
isGrayscale = false,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,23 +19,20 @@ class AccountPortfolioItemUMConverter(
|
|||
private val isBalanceHidden: Boolean = false,
|
||||
private val isEnabled: Boolean = true,
|
||||
private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None,
|
||||
) : Converter<Account, UserWalletItemUM> {
|
||||
) : Converter<Account.CryptoPortfolio, UserWalletItemUM> {
|
||||
|
||||
override fun convert(value: Account): UserWalletItemUM {
|
||||
return when (value) {
|
||||
is Account.CryptoPortfolio -> with(value) {
|
||||
UserWalletItemUM(
|
||||
id = accountId.value,
|
||||
name = accountName.toUM().value,
|
||||
information = getInfo(account = this),
|
||||
balance = getBalanceInfo(),
|
||||
isEnabled = isEnabled,
|
||||
endIcon = endIcon,
|
||||
onClick = onClick,
|
||||
imageState = getImageState(account = this),
|
||||
)
|
||||
}
|
||||
is Account.Payment -> TODO("[REDACTED_JIRA]")
|
||||
override fun convert(value: Account.CryptoPortfolio): UserWalletItemUM {
|
||||
return with(value) {
|
||||
UserWalletItemUM(
|
||||
id = accountId.value,
|
||||
name = accountName.toUM().value,
|
||||
information = getInfo(account = this),
|
||||
balance = getBalanceInfo(),
|
||||
isEnabled = isEnabled,
|
||||
endIcon = endIcon,
|
||||
onClick = onClick,
|
||||
imageState = getImageState(account = this),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class AmountFieldChangeTransformer(
|
|||
fiatValue = fiatValue,
|
||||
isError = isCheckFailed,
|
||||
error = when {
|
||||
isExceedBalance -> resourceReference(R.string.send_validation_amount_exceeds_balance)
|
||||
isExceedBalance -> resourceReference(R.string.common_insufficient_balance)
|
||||
isLessThanMinimumIfProvided -> {
|
||||
val minimumAmount = minimumTransactionAmount.amount.format {
|
||||
crypto(cryptoCurrencyStatus.currency)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ fun AmountFieldV2(
|
|||
onValuePastedTriggerDismiss: () -> Unit,
|
||||
onCurrencyChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
reserveSpaceForError: Boolean = true,
|
||||
) {
|
||||
val decimalFormat = rememberDecimalFormat()
|
||||
|
||||
|
|
@ -120,12 +121,13 @@ fun AmountFieldV2(
|
|||
AmountSecondary(
|
||||
amountUM = amountUM,
|
||||
onCurrencyChange = onCurrencyChange,
|
||||
reserveSpaceForError = reserveSpaceForError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -> Unit) {
|
||||
private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -> Unit, reserveSpaceForError: Boolean) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -144,29 +146,33 @@ private fun AmountSecondary(amountUM: AmountState, onCurrencyChange: (Boolean) -
|
|||
AmountFieldCurrencyInfo(
|
||||
amountUM = amountUM,
|
||||
onCurrencyChange = onCurrencyChange,
|
||||
modifier = if (reserveSpaceForError) Modifier.padding(bottom = 16.dp) else Modifier,
|
||||
)
|
||||
AmountFieldError(
|
||||
isError = amountUM.amountTextField.isError,
|
||||
isWarning = amountUM.amountTextField.isWarning,
|
||||
error = amountUM.amountTextField.error,
|
||||
reserveSpaceForError = reserveSpaceForError,
|
||||
modifier = Modifier
|
||||
.align(BottomCenter)
|
||||
.padding(top = 24.dp),
|
||||
.align(BottomCenter),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.AmountFieldCurrencyInfo(amountUM: AmountState.Data, onCurrencyChange: (Boolean) -> Unit) {
|
||||
private fun BoxScope.AmountFieldCurrencyInfo(
|
||||
amountUM: AmountState.Data,
|
||||
onCurrencyChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isFiatAvailable = amountUM.amountTextField.fiatAmount.value != null
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.align(TopCenter)
|
||||
.padding(bottom = 16.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
|
|
@ -255,13 +261,14 @@ private fun AmountFieldError(
|
|||
isError: Boolean,
|
||||
isWarning: Boolean,
|
||||
error: TextReference,
|
||||
reserveSpaceForError: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = isError || isWarning,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
modifier = modifier,
|
||||
modifier = if (reserveSpaceForError) modifier.padding(top = 24.dp) else modifier,
|
||||
label = "Error field appearance animation",
|
||||
) {
|
||||
val errorText = remember(this, error) { error }
|
||||
|
|
@ -271,7 +278,13 @@ private fun AmountFieldError(
|
|||
style = TangemTheme.typography.caption2,
|
||||
color = color,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT),
|
||||
modifier = if (reserveSpaceForError) {
|
||||
Modifier.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT)
|
||||
} else {
|
||||
Modifier
|
||||
.padding(top = 24.dp)
|
||||
.testTag(SendScreenTestTags.AMOUNT_ERROR_TEXT)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -324,6 +337,7 @@ private fun AmountFieldV2_Preview(@PreviewParameter(AmountFieldV2PreviewProvider
|
|||
onValueChange = {},
|
||||
onValuePastedTriggerDismiss = { },
|
||||
onCurrencyChange = {},
|
||||
reserveSpaceForError = false,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,13 @@ internal fun String.checkExceedBalance(
|
|||
maxEnterAmount: EnterAmountBoundary,
|
||||
amountTextField: AmountFieldModel,
|
||||
): Boolean {
|
||||
val currencyCryptoAmount = maxEnterAmount.amount ?: BigDecimal.ZERO
|
||||
val currencyFiatAmount = maxEnterAmount.fiatAmount ?: BigDecimal.ZERO
|
||||
val fiatDecimal = parseToBigDecimal(amountTextField.fiatAmount.decimals)
|
||||
val cryptoDecimal = parseToBigDecimal(amountTextField.cryptoAmount.decimals)
|
||||
return if (amountTextField.isFiatValue) {
|
||||
val currencyFiatAmount = maxEnterAmount.fiatAmount ?: return false
|
||||
fiatDecimal > currencyFiatAmount
|
||||
} else {
|
||||
val currencyCryptoAmount = maxEnterAmount.amount ?: return false
|
||||
cryptoDecimal > currencyCryptoAmount
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ enum class ApproveType(val text: TextReference) {
|
|||
data class ApprovePermissionButton(
|
||||
val isEnabled: Boolean,
|
||||
val isLoading: Boolean = false,
|
||||
val isHoldToConfirm: Boolean = false,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
package com.tangem.common.ui.notifications
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.notifications.CloseableIconButton
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.ForceDarkTheme
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
private const val GRADIENT_START_COLOR = 0xFF252934
|
||||
private const val GRADIENT_END_COLOR = 0xFF12141E
|
||||
private const val GRADIENT_OFFSET_X = 164f
|
||||
private const val GRADIENT_OFFSET_Y = 39f
|
||||
private const val GRADIENT_RADIUS = 82f
|
||||
|
||||
@Composable
|
||||
fun CreatePaymentAccountNotification(
|
||||
onClick: () -> Unit,
|
||||
onCloseClick: () -> Unit,
|
||||
@DrawableRes image: Int,
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(
|
||||
brush = Brush.radialGradient(
|
||||
colors = listOf(Color(GRADIENT_START_COLOR), Color(GRADIENT_END_COLOR)),
|
||||
center = Offset(GRADIENT_OFFSET_X, GRADIENT_OFFSET_Y),
|
||||
radius = GRADIENT_RADIUS,
|
||||
),
|
||||
)
|
||||
.clickable(onClick = onClick),
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(78.dp),
|
||||
painter = painterResource(id = image),
|
||||
contentDescription = null,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 78.dp, top = 12.dp, end = 12.dp, bottom = 12.dp)
|
||||
.align(Alignment.CenterStart),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(end = TangemTheme.dimens.size32),
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
Text(
|
||||
text = subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
CloseableIconButton(
|
||||
onClick = onCloseClick,
|
||||
modifier = Modifier.align(alignment = Alignment.TopEnd),
|
||||
iconTint = TangemTheme.colors.icon.inactive,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360)
|
||||
@Composable
|
||||
private fun CreatePaymentAccountNotification_Preview() {
|
||||
ForceDarkTheme {
|
||||
CreatePaymentAccountNotification(
|
||||
onClick = {},
|
||||
onCloseClick = {},
|
||||
image = R.drawable.img_tangem_pay_visa_banner,
|
||||
title = resourceReference(R.string.tangempay_onboarding_banner_title),
|
||||
subtitle = resourceReference(R.string.tangempay_onboarding_banner_description),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -43,6 +44,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.WalletSettingsScreenTestTags
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import com.tangem.utils.StringsSigns.THREE_STARS
|
||||
|
|
@ -64,7 +66,8 @@ fun UserWalletItem(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size68)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
.padding(all = TangemTheme.dimens.spacing12)
|
||||
.testTag(WalletSettingsScreenTestTags.USER_ACCOUNT_ITEM),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessage
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.common.wallets.error.UnlockWalletError
|
||||
import com.tangem.domain.common.wallets.error.UnlockWalletError.UnableToUnlock.Reason
|
||||
|
|
@ -22,12 +23,7 @@ inline fun UnlockWalletError.handle(
|
|||
when (this) {
|
||||
UnlockWalletError.AlreadyUnlocked -> onAlreadyUnlocked()
|
||||
UnlockWalletError.ScannedCardWalletNotMatched -> {
|
||||
showMessage(
|
||||
DialogMessage(
|
||||
title = resourceReference(R.string.common_warning),
|
||||
message = resourceReference(R.string.error_wrong_wallet_tapped),
|
||||
),
|
||||
)
|
||||
showMessage(Dialogs.wrongWalletTapped())
|
||||
}
|
||||
UnlockWalletError.UserCancelled -> onUserCancelled()
|
||||
UnlockWalletError.UserWalletNotFound -> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue