Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-08 18:53:25 +03:00
parent 700731a408
commit c500f45307
59 changed files with 1338 additions and 278 deletions

View file

@ -24,6 +24,7 @@ interface TangemTechMarketsApi {
suspend fun getCoinMarketData(
@Path("coin_id") coinId: String,
@Query("currency") currency: String,
@Query("language") language: String,
): ApiResponse<TokenMarketInfoResponse>
@GET("coins/{coin_id}/history")

View file

@ -21,7 +21,7 @@ data class TokenMarketInfoResponse(
@Json(name = "full_description")
val fullDescription: String?,
@Json(name = "insights")
val insights: List<Insight>?,
val insights: Insights?,
@Json(name = "metrics")
val metrics: Metrics?,
@Json(name = "links")
@ -32,33 +32,33 @@ data class TokenMarketInfoResponse(
data class PriceChangePercentage(
@Json(name = "24h")
val day: Int?,
val day: BigDecimal?,
@Json(name = "1w")
val week: Int?,
val week: BigDecimal?,
@Json(name = "1m")
val month: Int?,
val month: BigDecimal?,
@Json(name = "3m")
val threeMonths: Int?,
val threeMonths: BigDecimal?,
@Json(name = "6m")
val sixMonths: Int?,
val sixMonths: BigDecimal?,
@Json(name = "1y")
val year: Int?,
val year: BigDecimal?,
@Json(name = "all_time")
val allTime: Int?,
val allTime: BigDecimal?,
)
data class Network(
@Json(name = "network_id")
val networkId: String,
@Json(name = "exchangeable")
val exchangeable: Boolean,
val exchangeable: Boolean = false,
@Json(name = "contract_address")
val contractAddress: String,
val contractAddress: String?,
@Json(name = "decimal_count")
val decimalCount: Int,
val decimalCount: Int?,
)
data class Insight(
data class Insights(
@Json(name = "holders_change")
val holdersChange: Change?,
@Json(name = "liquidity_change")
@ -71,11 +71,11 @@ data class TokenMarketInfoResponse(
data class Change(
@Json(name = "24h")
val day: Int?,
val day: BigDecimal?,
@Json(name = "1w")
val week: Int?,
val week: BigDecimal?,
@Json(name = "1m")
val month: Int?,
val month: BigDecimal?,
)
data class Metrics(
@ -123,9 +123,9 @@ data class TokenMarketInfoResponse(
)
data class Range(
@Json(name = "low")
val low: Int?,
@Json(name = "high")
val high: Int?,
@Json(name = "low_price")
val low: BigDecimal?,
@Json(name = "high_price")
val high: BigDecimal?,
)
}

View file

@ -30,6 +30,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
titleAction: TopAppBarButtonUM? = null,
containerColor: Color = TangemTheme.colors.background.primary,
addBottomInsets: Boolean = true,
skipPartiallyExpanded: Boolean = true,
crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
TangemBottomSheet(
@ -37,6 +38,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
containerColor = containerColor,
addBottomInsets = addBottomInsets,
title = { TangemBottomSheetTitle(title = titleText, endButton = titleAction) },
skipPartiallyExpanded = skipPartiallyExpanded,
content = content,
)
}
@ -49,6 +51,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
config: TangemBottomSheetConfig,
containerColor: Color = TangemTheme.colors.background.primary,
addBottomInsets: Boolean = true,
skipPartiallyExpanded: Boolean = true,
crossinline title: @Composable BoxScope.(T) -> Unit = {},
crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
@ -61,6 +64,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
addBottomInsets = addBottomInsets,
title = title,
content = content,
skipPartiallyExpanded = skipPartiallyExpanded,
)
} else {
DefaultBottomSheet<T>(
@ -69,6 +73,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
addBottomInsets = addBottomInsets,
title = title,
content = content,
skipPartiallyExpanded = skipPartiallyExpanded,
)
}
}
@ -79,11 +84,12 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultBottomSheet(
config: TangemBottomSheetConfig,
containerColor: Color,
addBottomInsets: Boolean,
skipPartiallyExpanded: Boolean = true,
crossinline title: @Composable (BoxScope.(T) -> Unit),
crossinline content: @Composable (ColumnScope.(T) -> Unit),
) {
var isVisible by remember { mutableStateOf(value = config.isShow) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
if (isVisible && config.content is T) {
BasicBottomSheet<T>(
@ -111,6 +117,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewBottomSheet(
config: TangemBottomSheetConfig,
containerColor: Color,
addBottomInsets: Boolean,
skipPartiallyExpanded: Boolean = true,
crossinline title: @Composable (BoxScope.(T) -> Unit),
crossinline content: @Composable (ColumnScope.(T) -> Unit),
) {
@ -118,7 +125,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewBottomSheet(
modifier = Modifier.width(360.dp),
config = config,
sheetState = SheetState(
skipPartiallyExpanded = true,
skipPartiallyExpanded = skipPartiallyExpanded,
initialValue = Expanded,
density = LocalDensity.current,
),

View file

@ -1,10 +1,7 @@
package com.tangem.core.ui.res
import androidx.compose.animation.*
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.*
import androidx.compose.runtime.*
@Immutable
@ -24,10 +21,6 @@ object TangemAnimations {
@Immutable
object TransitionSpecs {
@Stable
val textChange: ContentTransform =
fadeIn(animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)) togetherWith
fadeOut(animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy))
// TODO add more transition specs
}
}

View file

@ -117,6 +117,7 @@ object BigDecimalFormatter {
fiatAmount: BigDecimal?,
fiatCurrencyCode: String,
fiatCurrencySymbol: String,
decimals: Int = FIAT_MARKET_DEFAULT_DIGITS,
locale: Locale = Locale.getDefault(),
): String {
if (fiatAmount == null) return EMPTY_BALANCE_SIGN
@ -124,8 +125,8 @@ object BigDecimalFormatter {
val formatterCurrency = getCurrency(fiatCurrencyCode)
val formatter = NumberFormat.getCurrencyInstance(locale).apply {
currency = formatterCurrency
maximumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS
minimumFractionDigits = FIAT_MARKET_DEFAULT_DIGITS
maximumFractionDigits = decimals
minimumFractionDigits = decimals
roundingMode = RoundingMode.HALF_UP
}
@ -250,40 +251,27 @@ object BigDecimalFormatter {
/**
* "123456.6" -> "$123.457K"
* "12345.6" -> "$123.046K"
* Negative amount is not supported
* @param threeDigitsMethod if true, will format the amount always with 3 significant digits
* @param scale the number of digits to the right of the decimal point
*/
@Suppress("MagicNumber")
fun formatCompactAmount(
amount: BigDecimal,
fun formatCompactFiatAmount(
amount: BigDecimal?,
fiatCurrencyCode: String,
fiatCurrencySymbol: String,
threeDigitsMethod: Boolean = false,
scale: Int = 0,
locale: Locale = Locale.getDefault(),
): String {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return BigDecimalFormatterCompat.formatCompactAmountNoLocaleContext(
amount = amount,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
locale = locale,
)
}
if (amount == null) return EMPTY_BALANCE_SIGN
val scaledAmount = amount.setScale(0, RoundingMode.HALF_UP)
val digitsCount = scaledAmount.longValueExact().toString().count()
val digitsToFormat = 6 - when (digitsCount % 3) {
0 -> 0
1 -> 2
else -> 1
}
val formatter = CompactDecimalFormat.getInstance(
locale,
CompactDecimalFormat.CompactStyle.SHORT,
).apply {
minimumSignificantDigits = 4
maximumSignificantDigits = digitsToFormat
}
val rawAmount = formatter.format(amount.setScale(0, RoundingMode.HALF_UP))
val rawAmount = formatCompactAmount(
amount = amount,
locale = locale,
threeDigitsMethod = threeDigitsMethod,
scale = scale,
)
return addCurrencySymbolToStringAmount(
amount = rawAmount,
@ -293,5 +281,53 @@ object BigDecimalFormatter {
)
}
/**
* "123456.6" -> "123.457K"
* "12345.6" -> "123.046K"
* Negative amount is not supported
* @param threeDigitsMethod if true, will format the amount always with 3 significant digits
* @param scale the number of digits to the right of the decimal point
*/
@Suppress("MagicNumber")
fun formatCompactAmount(
amount: BigDecimal,
locale: Locale = Locale.getDefault(),
threeDigitsMethod: Boolean = false,
scale: Int = 0,
): String {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return BigDecimalFormatterCompat.formatCompactAmountNoLocaleContext(amount = amount)
}
if (threeDigitsMethod) {
val scaledAmount = amount.setScale(scale, RoundingMode.HALF_UP)
val digitsCount = scaledAmount.longValueExact().toString().count()
val digitsToFormat = 6 - when (digitsCount % 3) {
0 -> 0
1 -> 2
else -> 1
}
val formatter = CompactDecimalFormat.getInstance(
locale,
CompactDecimalFormat.CompactStyle.SHORT,
).apply {
minimumSignificantDigits = 4
maximumSignificantDigits = digitsToFormat
}
return formatter.format(amount.setScale(scale, RoundingMode.HALF_UP))
} else {
val value = amount.setScale(scale, RoundingMode.HALF_UP)
val formatter = CompactDecimalFormat.getInstance(
locale,
CompactDecimalFormat.CompactStyle.SHORT,
)
return formatter.format(value)
}
}
private fun BigDecimal.isLessThanThreshold() = this > BigDecimal.ZERO && this < FIAT_FORMAT_THRESHOLD
}

View file

@ -7,16 +7,32 @@ import java.util.Locale
internal object BigDecimalFormatterCompat {
/**
* Formats value as [BigDecimalFormatter.formatCompactAmount] does using only "T","B","M","K" suffixes
* Formats value as [BigDecimalFormatter.formatCompactFiatAmount] does using only "T","B","M","K" suffixes
* Used for < API24 compatibility
*/
@Suppress("MagicNumber", "UnnecessaryParentheses")
fun formatCompactAmountNoLocaleContext(
fun formatCompactFiatAmountNoLocaleContext(
amount: BigDecimal,
fiatCurrencyCode: String,
fiatCurrencySymbol: String,
locale: Locale = Locale.getDefault(),
): String {
val formatted = formatCompactAmountNoLocaleContext(amount)
return BigDecimalFormatter.addCurrencySymbolToStringAmount(
amount = formatted,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
locale = locale,
)
}
/**
* Formats value as [BigDecimalFormatter.formatCompactAmount] does using only "T","B","M","K" suffixes
* Used for < API24 compatibility
*/
@Suppress("MagicNumber", "UnnecessaryParentheses")
fun formatCompactAmountNoLocaleContext(amount: BigDecimal): String {
val value = amount.setScale(0, RoundingMode.HALF_UP).longValueExact()
val formatted = when {
@ -42,11 +58,6 @@ internal object BigDecimalFormatterCompat {
else -> return value.toString()
}
return BigDecimalFormatter.addCurrencySymbolToStringAmount(
amount = formatted,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
locale = locale,
)
return formatted
}
}

View file

@ -0,0 +1,15 @@
package com.tangem.core.ui.utils
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
fun Modifier.disableNestedScroll(): Modifier = nestedScroll(DisableParentConnection)
private object DisableParentConnection : NestedScrollConnection {
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
return available.copy(x = 0f)
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.utils
import java.util.Locale
object SupportedLanguages {
const val ENGLISH = "en"
const val RUSSIAN = "ru"
const val GERMAN = "de"
const val FRANCH = "fr"
const val ITALIAN = "it"
const val JAPANESE = "ja"
const val UKRAINIAN = "uk"
const val CHINESE = "uk"
val supportedLangugeCodes = listOf(
ENGLISH,
RUSSIAN,
GERMAN,
FRANCH,
ITALIAN,
JAPANESE,
UKRAINIAN,
CHINESE,
)
fun getCurrentSupportedLanguageCode(): String {
val locale = Locale.getDefault()
return if (supportedLangugeCodes.contains(locale.language)) {
locale.language
} else {
ENGLISH
}
}
}