Updated on 2026-08-14
This commit is contained in:
parent
ddab2eb560
commit
3aa098dcd4
4 changed files with 141 additions and 164 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
|
||||
import com.tangem.feature.swap.domain.models.ui.FoundTokensState
|
||||
|
|
@ -8,7 +9,8 @@ import com.tangem.feature.swap.domain.models.ui.TokenWithBalance
|
|||
import com.tangem.feature.swap.models.Network
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
import com.tangem.feature.swap.models.TokenBalanceData
|
||||
import com.tangem.feature.swap.models.TokenToSelect
|
||||
import com.tangem.feature.swap.models.TokenToSelectState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class TokensDataConverter(
|
||||
private val onSearchEntered: (String) -> Unit,
|
||||
|
|
@ -18,21 +20,27 @@ class TokensDataConverter(
|
|||
|
||||
fun convertWithNetwork(value: FoundTokensState, network: NetworkInfo): SwapSelectTokenStateHolder {
|
||||
return SwapSelectTokenStateHolder(
|
||||
addedTokens = value.tokensInWallet.map { tokenWithBalanceToTokenToSelect(it) },
|
||||
otherTokens = value.loadedTokens.map { tokenWithBalanceToTokenToSelect(it) },
|
||||
availableTokens = value.tokensInWallet.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(),
|
||||
unavailableTokens = value.loadedTokens.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(),
|
||||
onSearchEntered = onSearchEntered,
|
||||
onTokenSelected = onTokenSelected,
|
||||
network = Network(network.name, network.blockchainId),
|
||||
)
|
||||
}
|
||||
|
||||
private fun tokenWithBalanceToTokenToSelect(tokenWithBalance: TokenWithBalance): TokenToSelect {
|
||||
return TokenToSelect(
|
||||
private fun tokenWithBalanceToTokenToSelect(tokenWithBalance: TokenWithBalance): TokenToSelectState.TokenToSelect {
|
||||
return TokenToSelectState.TokenToSelect(
|
||||
id = tokenWithBalance.token.id,
|
||||
name = tokenWithBalance.token.name,
|
||||
symbol = tokenWithBalance.token.symbol,
|
||||
iconUrl = tokenWithBalance.token.logoUrl,
|
||||
isNative = tokenWithBalance.token is Currency.NativeToken,
|
||||
// todo replace converting
|
||||
tokenIcon = TokenIconState.CoinIcon(
|
||||
url = "",
|
||||
fallbackResId = 0,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
addedTokenBalanceData = TokenBalanceData(
|
||||
amount = tokenWithBalance.tokenBalanceData?.amount,
|
||||
amountEquivalent = tokenWithBalance.tokenBalanceData?.amountEquivalent,
|
||||
|
|
|
|||
|
|
@ -1,28 +1,36 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class SwapSelectTokenStateHolder(
|
||||
val addedTokens: List<TokenToSelect>,
|
||||
val otherTokens: List<TokenToSelect>,
|
||||
val availableTokens: ImmutableList<TokenToSelectState>,
|
||||
val unavailableTokens: ImmutableList<TokenToSelectState>,
|
||||
val network: Network,
|
||||
val onSearchEntered: (String) -> Unit,
|
||||
val onTokenSelected: (String) -> Unit,
|
||||
)
|
||||
|
||||
data class TokenToSelect(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val iconUrl: String,
|
||||
val isNative: Boolean,
|
||||
val available: Boolean = true,
|
||||
val addedTokenBalanceData: TokenBalanceData? = null,
|
||||
)
|
||||
|
||||
data class Network(
|
||||
val name: String,
|
||||
val blockchainId: String,
|
||||
)
|
||||
|
||||
sealed class TokenToSelectState {
|
||||
|
||||
data class Title(val title: String) : TokenToSelectState()
|
||||
|
||||
data class TokenToSelect(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val isNative: Boolean,
|
||||
val tokenIcon: TokenIconState,
|
||||
val available: Boolean = true,
|
||||
val addedTokenBalanceData: TokenBalanceData? = null,
|
||||
) : TokenToSelectState()
|
||||
}
|
||||
|
||||
data class TokenBalanceData(
|
||||
val amount: String?,
|
||||
val amountEquivalent: String?,
|
||||
|
|
|
|||
|
|
@ -274,11 +274,18 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
|
|||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
val selectTokenState = uiState.selectTokenState?.copy(
|
||||
addedTokens = uiState.selectTokenState.addedTokens.map {
|
||||
it.copy(
|
||||
addedTokenBalanceData = it.addedTokenBalanceData?.copy(isBalanceHidden = isBalanceHidden),
|
||||
)
|
||||
},
|
||||
availableTokens = uiState.selectTokenState.availableTokens.map {
|
||||
when (it) {
|
||||
is TokenToSelectState.TokenToSelect -> {
|
||||
it.copy(
|
||||
addedTokenBalanceData = it.addedTokenBalanceData?.copy(isBalanceHidden = isBalanceHidden),
|
||||
)
|
||||
}
|
||||
is TokenToSelectState.Title -> {
|
||||
it
|
||||
}
|
||||
}
|
||||
}.toImmutableList(),
|
||||
)
|
||||
|
||||
return uiState.copy(
|
||||
|
|
|
|||
|
|
@ -1,45 +1,31 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.common.Strings
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.CurrencyPlaceholderIcon
|
||||
import com.tangem.core.ui.components.SpacerW2
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.appbar.ExpandableSearchView
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
|
||||
import com.tangem.feature.swap.models.Network
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
import com.tangem.feature.swap.models.TokenBalanceData
|
||||
import com.tangem.feature.swap.models.TokenToSelect
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit) {
|
||||
|
|
@ -64,7 +50,6 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit)
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier = Modifier) {
|
||||
val screenBackgroundColor = TangemTheme.colors.background.secondary
|
||||
|
|
@ -74,44 +59,45 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier =
|
|||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
if (state.addedTokens.isNotEmpty()) {
|
||||
stickyHeader { Header(title = R.string.swapping_token_list_your_tokens) }
|
||||
}
|
||||
item { SpacerH8() }
|
||||
|
||||
itemsIndexed(items = state.addedTokens) { index, item ->
|
||||
TokenItem(
|
||||
token = item,
|
||||
network = state.network,
|
||||
screenBackgroundColor = screenBackgroundColor,
|
||||
onTokenClick = {
|
||||
state
|
||||
.onTokenSelected(item.id)
|
||||
},
|
||||
)
|
||||
tokensToSelectItems(state.availableTokens, state.onTokenSelected)
|
||||
|
||||
if (index != state.addedTokens.lastIndex) {
|
||||
Divider(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
startIndent = TangemTheme.dimens.spacing54,
|
||||
item { SpacerH12() }
|
||||
|
||||
tokensToSelectItems(state.unavailableTokens, state.onTokenSelected)
|
||||
|
||||
item { SpacerH12() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.tokensToSelectItems(
|
||||
items: ImmutableList<TokenToSelectState>,
|
||||
onTokenClick: (String) -> Unit,
|
||||
) {
|
||||
itemsIndexed(items = items) { index, item ->
|
||||
when (item) {
|
||||
is TokenToSelectState.Title -> {
|
||||
TitleHeader(
|
||||
item = item,
|
||||
modifier = Modifier.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = items.lastIndex,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.otherTokens.isNotEmpty()) {
|
||||
stickyHeader { Header(title = R.string.swapping_token_list_other_tokens) }
|
||||
}
|
||||
|
||||
itemsIndexed(items = state.otherTokens) { index, item ->
|
||||
TokenItem(
|
||||
token = item,
|
||||
network = state.network,
|
||||
screenBackgroundColor = screenBackgroundColor,
|
||||
onTokenClick = { state.onTokenSelected(item.id) },
|
||||
)
|
||||
if (index != state.otherTokens.lastIndex) {
|
||||
Divider(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
startIndent = TangemTheme.dimens.spacing54,
|
||||
is TokenToSelectState.TokenToSelect -> {
|
||||
TokenItem(
|
||||
token = item,
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
lastIndex = items.lastIndex,
|
||||
)
|
||||
.background(TangemTheme.colors.background.action),
|
||||
onTokenClick = {
|
||||
onTokenClick(item.id)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -119,27 +105,35 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier =
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Header(@StringRes title: Int) {
|
||||
Text(
|
||||
text = stringResource(id = title).uppercase(),
|
||||
style = TangemTheme.typography.overline,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
modifier = Modifier
|
||||
private fun TitleHeader(item: TokenToSelectState.Title, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing6,
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
.background(TangemTheme.colors.background.action),
|
||||
) {
|
||||
Text(
|
||||
text = item.title,
|
||||
style = TangemTheme.typography.overline,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun TokenItem(token: TokenToSelect, network: Network, screenBackgroundColor: Color, onTokenClick: () -> Unit) {
|
||||
private fun TokenItem(
|
||||
token: TokenToSelectState.TokenToSelect,
|
||||
onTokenClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size72)
|
||||
.clickable(onClick = onTokenClick)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
|
|
@ -148,12 +142,15 @@ private fun TokenItem(token: TokenToSelect, network: Network, screenBackgroundCo
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TokenIcon(
|
||||
token = token,
|
||||
screenBackgroundColor = screenBackgroundColor,
|
||||
iconPlaceholder = if (token.isNative) getActiveIconRes(network.blockchainId) else null,
|
||||
state = token.tokenIcon,
|
||||
shouldDisplayNetwork = true,
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.align(Alignment.CenterVertically)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(start = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = token.name,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
|
|
@ -213,65 +210,16 @@ private fun TokenItem(token: TokenToSelect, network: Network, screenBackgroundCo
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun TokenIcon(token: TokenToSelect, screenBackgroundColor: Color, @DrawableRes iconPlaceholder: Int?) {
|
||||
var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) }
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val data = token.iconUrl.ifEmpty {
|
||||
iconPlaceholder
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(end = TangemTheme.dimens.spacing12)
|
||||
.background(
|
||||
color = iconBackgroundColor,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
),
|
||||
) {
|
||||
val iconModifier = Modifier
|
||||
.size(TangemTheme.dimens.size40)
|
||||
val colorFilter = if (!token.available) {
|
||||
val matrix = ColorMatrix().apply { setToSaturation(0f) }
|
||||
ColorFilter.colorMatrix(matrix)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
SubcomposeAsyncImage(
|
||||
modifier = iconModifier,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(data)
|
||||
.crossfade(true)
|
||||
.allowHardware(false)
|
||||
.listener(
|
||||
onSuccess = { _, result ->
|
||||
if (isDarkTheme) {
|
||||
coroutineScope.launch {
|
||||
val color = ImageBackgroundContrastChecker(
|
||||
drawable = result.drawable,
|
||||
backgroundColor = screenBackgroundColor.toArgb(),
|
||||
).getContrastColorIfNeeded(isDarkTheme)
|
||||
iconBackgroundColor = color
|
||||
}
|
||||
}
|
||||
},
|
||||
).build(),
|
||||
contentDescription = token.id,
|
||||
loading = { CircleShimmer(modifier = iconModifier) },
|
||||
error = { CurrencyPlaceholderIcon(modifier = iconModifier, id = token.id) },
|
||||
alpha = if (!token.available) 0.7f else 1f,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val token = TokenToSelect(
|
||||
private val token = TokenToSelectState.TokenToSelect(
|
||||
tokenIcon = TokenIconState.CoinIcon(
|
||||
url = "",
|
||||
fallbackResId = 0,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
id = "",
|
||||
name = "USDC",
|
||||
symbol = "USDC",
|
||||
iconUrl = "",
|
||||
isNative = false,
|
||||
addedTokenBalanceData = TokenBalanceData(
|
||||
amount = "15 000 $",
|
||||
|
|
@ -281,17 +229,23 @@ private val token = TokenToSelect(
|
|||
),
|
||||
)
|
||||
|
||||
private val title = TokenToSelectState.Title(
|
||||
title = "MY TOKENS",
|
||||
)
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TokenScreenPreview() {
|
||||
SwapSelectTokenScreen(
|
||||
state = SwapSelectTokenStateHolder(
|
||||
addedTokens = listOf(token, token, token),
|
||||
otherTokens = listOf(token, token, token),
|
||||
onSearchEntered = {},
|
||||
onTokenSelected = {},
|
||||
network = Network("Ethereum", "ETH"),
|
||||
),
|
||||
onBack = {},
|
||||
)
|
||||
TangemTheme(isDark = false) {
|
||||
SwapSelectTokenScreen(
|
||||
state = SwapSelectTokenStateHolder(
|
||||
availableTokens = listOf(title, token, token, token).toImmutableList(),
|
||||
unavailableTokens = listOf(title, token, token, token).toImmutableList(),
|
||||
onSearchEntered = {},
|
||||
onTokenSelected = {},
|
||||
network = Network("Ethereum", "ETH"),
|
||||
),
|
||||
onBack = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue