Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-02 14:34:29 +04:00
parent 1101c09e91
commit a3bee722b7
6 changed files with 298 additions and 9 deletions

View file

@ -375,7 +375,7 @@
<string name="markets_sort_by_trending_title">トレンド</string>
<string name="markets_token_details_about_token_title">%sについて</string>
<plurals name="markets_token_details_amount_exchanges">
<item quantity="other">%1$s取引所</item>
<item quantity="other">%d取引所</item>
</plurals>
<plurals name="markets_token_details_based_on_ratings">
<item quantity="other">%d のレーティングに基づいて</item>
@ -405,6 +405,7 @@
<string name="markets_token_details_liquidity_description">指定された期間中のトークンに利用可能な流動性の変化</string>
<string name="markets_token_details_liquidity_full">流動性</string>
<string name="markets_token_details_liquidity_index">流動性指数</string>
<string name="markets_token_details_listed_on">上場先</string>
<string name="markets_token_details_low">低い</string>
<string name="markets_token_details_market_capitalization">時価総額</string>
<string name="markets_token_details_market_capitalization_description">暗号資産の市場価値の合計。コインの現在の価格と、流通しているコインの総数を掛けて計算されます。</string>

View file

@ -383,10 +383,10 @@
<string name="markets_sort_by_trending_title">В тренде</string>
<string name="markets_token_details_about_token_title">О %s</string>
<plurals name="markets_token_details_amount_exchanges">
<item quantity="one">%1$s биржа</item>
<item quantity="few">%1$s биржи</item>
<item quantity="many">%1$s бирж</item>
<item quantity="other">%1$s бирж</item>
<item quantity="one">%d биржа</item>
<item quantity="few">%d биржи</item>
<item quantity="many">%d бирж</item>
<item quantity="other">%d бирж</item>
</plurals>
<plurals name="markets_token_details_based_on_ratings">
<item quantity="one">На основе %d оценки</item>
@ -402,6 +402,8 @@
<string name="markets_token_details_circulating_supply_description">Общее количество монет, доступных для торговли и находящихся в обращении на рынке</string>
<string name="markets_token_details_circulating_supply_full">Циркулирующее предложение</string>
<string name="markets_token_details_empty_exchanges">Биржи не найдены</string>
<string name="markets_token_details_exchange">Биржа</string>
<string name="markets_token_details_exchanges_title">Биржи</string>
<string name="markets_token_details_experienced_buyers">Опытные трейдеры</string>
<string name="markets_token_details_experienced_buyers_description">Покупатели, у которых было как минимум 100 исходящих транзакций.</string>
<string name="markets_token_details_experienced_buyers_full">Опытные трейдеры</string>
@ -442,6 +444,7 @@
<string name="markets_token_details_trading_volume">Объем торгов (24ч)</string>
<string name="markets_token_details_trading_volume_24h_description">Общая сумма криптовалюты, которая была продана за последние 24 часа, показывающая уровень активности и ликвидности на рынке.</string>
<string name="markets_token_details_trading_volume_full">Объем торгов (24ч)</string>
<string name="markets_token_details_volume">Объем</string>
<string name="markets_tooltip_message">Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из маркета</string>
<string name="markets_tooltip_title">Добавить токены</string>
<string name="nfc_error_unavailable">Функция NFC недоступна на вашем устройстве</string>

View file

@ -379,8 +379,8 @@
<string name="markets_sort_by_trending_title">Trending</string>
<string name="markets_token_details_about_token_title">About %s</string>
<plurals name="markets_token_details_amount_exchanges">
<item quantity="one">%1$s exchange</item>
<item quantity="other">%1$s exchanges</item>
<item quantity="one">%d exchange</item>
<item quantity="other">%d exchanges</item>
</plurals>
<plurals name="markets_token_details_based_on_ratings">
<item quantity="one">Based on %d rating</item>
@ -394,6 +394,11 @@
<string name="markets_token_details_circulating_supply_description">The total number of coins that are available for trading and are circulating in the market</string>
<string name="markets_token_details_circulating_supply_full">Circulating supply</string>
<string name="markets_token_details_empty_exchanges">No exchanges found</string>
<string name="markets_token_details_exchange">Exchange</string>
<string name="markets_token_details_exchange_trust_score_caution">Caution</string>
<string name="markets_token_details_exchange_trust_score_risky">Risky</string>
<string name="markets_token_details_exchange_trust_score_trusted">Trusted</string>
<string name="markets_token_details_exchanges_title">Exchanges</string>
<string name="markets_token_details_experienced_buyers">Experienced buyers</string>
<string name="markets_token_details_experienced_buyers_description">Net buyers with the additional requirement of having at least 100 outgoing transactions</string>
<string name="markets_token_details_experienced_buyers_full">Experienced buyers</string>
@ -434,6 +439,7 @@
<string name="markets_token_details_trading_volume">Trading volume (24h)</string>
<string name="markets_token_details_trading_volume_24h_description">The total amount of a cryptocurrency that has been traded within the last 24 hours, indicating the level of activity and liquidity in the market</string>
<string name="markets_token_details_trading_volume_full">Trading volume (24h)</string>
<string name="markets_token_details_volume">Volume</string>
<string name="markets_tooltip_message">Pull this up or tap the search bar to add tokens directly from the market</string>
<string name="markets_tooltip_title">Add tokens</string>
<string name="nfc_error_unavailable">NFC is not available on your device</string>

View file

@ -46,8 +46,8 @@ sealed class TokenItemState {
*/
data class Loading(
override val id: String,
override val iconState: CurrencyIconState,
override val titleState: TitleState.Content,
override val iconState: CurrencyIconState = CurrencyIconState.Loading,
override val titleState: TitleState = TitleState.Loading,
override val subtitleState: SubtitleState = SubtitleState.Loading,
) : TokenItemState() {
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading

View file

@ -0,0 +1,206 @@
package com.tangem.features.markets.details.impl.ui.components
import android.content.res.Configuration
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.audits.AuditLabelUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.TokenItem
import com.tangem.core.ui.components.token.state.TokenItemState
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.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.markets.details.impl.ui.state.ExchangesBottomSheetContent
import com.tangem.features.markets.impl.R
import kotlinx.collections.immutable.toImmutableList
/**
* Exchanges bottom sheet
*
* @param config bottom sheet config
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun ExchangesBottomSheet(config: TangemBottomSheetConfig) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
TangemBottomSheet<ExchangesBottomSheetContent>(
config = config,
addBottomInsets = false,
title = { Title(textResId = it.titleResId, onBackClick = config.onDismissRequest) },
content = { content ->
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = rememberScrollState()),
) {
Subtitle(
subtitleRes = content.subtitleResId,
volumeReference = content.volumeReference,
modifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing8,
),
)
when (content) {
is ExchangesBottomSheetContent.Content,
is ExchangesBottomSheetContent.Loading,
-> {
content.exchangeItems.fastForEach { item ->
key(item.id) {
TokenItem(state = item, isBalanceHidden = false)
}
}
}
is ExchangesBottomSheetContent.Error -> {
Error(
content = content,
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally)
.padding(horizontal = 16.dp)
.weight(1f),
)
}
}
SpacerH(bottomBarHeight)
}
},
)
}
@Composable
private fun Title(@StringRes textResId: Int, onBackClick: () -> Unit) {
TangemTopAppBar(
title = stringResource(id = textResId),
startButton = TopAppBarButtonUM.Back(onBackClicked = onBackClick),
)
}
@Composable
private fun Subtitle(@StringRes subtitleRes: Int, volumeReference: TextReference, modifier: Modifier = Modifier) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
SubtitleText(textReference = resourceReference(id = subtitleRes))
SubtitleText(textReference = volumeReference)
}
}
@Composable
private fun SubtitleText(textReference: TextReference) {
Text(
text = textReference.resolveReference(),
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
}
@Composable
private fun Error(content: ExchangesBottomSheetContent.Error, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = content.message),
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.caption1,
)
SpacerH12()
SecondarySmallButton(
config = SmallButtonConfig(
text = resourceReference(id = R.string.alert_button_try_again),
onClick = content.onRetryClick,
),
)
}
}
@Preview
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_ExchangesBottomSheet(
@PreviewParameter(ExchangesBottomSheetContentProvider::class) content: ExchangesBottomSheetContent,
) {
TangemThemePreview {
ExchangesBottomSheet(
config = TangemBottomSheetConfig(
onDismissRequest = {},
content = content,
isShow = true,
),
)
}
}
private class ExchangesBottomSheetContentProvider : CollectionPreviewParameterProvider<ExchangesBottomSheetContent>(
listOf(
ExchangesBottomSheetContent.Loading(exchangesCount = 13),
ExchangesBottomSheetContent.Error(onRetryClick = {}),
ExchangesBottomSheetContent.Content(
exchangeItems = List(size = 13) { index ->
TokenItemState.Content(
id = index.toString(),
iconState = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.ic_facebook_24,
isGrayscale = false,
showCustomBadge = false,
),
titleState = TokenItemState.TitleState.Content(text = "OKX"),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "$67.52M"),
subtitleState = TokenItemState.SubtitleState.TextContent(value = "CEX"),
subtitle2State = TokenItemState.Subtitle2State.LabelContent(
auditLabelUM = AuditLabelUM(
text = stringReference("Caution"),
type = AuditLabelUM.Type.Warning,
),
),
onItemClick = {},
onItemLongClick = {},
)
}
.toImmutableList(),
),
),
)

View file

@ -0,0 +1,73 @@
package com.tangem.features.markets.details.impl.ui.state
import androidx.annotation.StringRes
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.plus
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.features.markets.impl.R
import com.tangem.utils.StringsSigns.DOT
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
/**
* Exchanges bottom sheet content
*
[REDACTED_AUTHOR]
*/
internal sealed interface ExchangesBottomSheetContent : TangemBottomSheetConfigContent {
/** Title of bottom sheet. Like, app bar. */
@get:StringRes
val titleResId: Int
get() = R.string.markets_token_details_exchanges_title
/** Subtitle */
@get:StringRes
val subtitleResId: Int
get() = R.string.markets_token_details_exchange
/** Volume info */
@get:StringRes
val volumeReference: TextReference
get() = resourceReference(id = R.string.markets_token_details_volume) +
stringReference(value = " $DOT ") +
resourceReference(id = R.string.markets_selector_interval_24h_title)
/** Exchange items */
val exchangeItems: ImmutableList<TokenItemState>
/**
* Loading state
*
* @property exchangesCount count of exchanges
*/
data class Loading(val exchangesCount: Int) : ExchangesBottomSheetContent {
override val exchangeItems: ImmutableList<TokenItemState>
get() = List(size = exchangesCount) { index -> TokenItemState.Loading(id = "loading#$index") }
.toImmutableList()
}
/**
* Content state
*
* @property exchangeItems exchanges
*/
data class Content(
override val exchangeItems: ImmutableList<TokenItemState>,
) : ExchangesBottomSheetContent
/** Error state */
data class Error(
val onRetryClick: () -> Unit,
) : ExchangesBottomSheetContent {
override val exchangeItems: ImmutableList<TokenItemState> = persistentListOf()
@StringRes
val message: Int = R.string.markets_loading_error_title
}
}