Updated on 2026-08-14
This commit is contained in:
parent
700731a408
commit
c500f45307
59 changed files with 1338 additions and 278 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue