Updated on 2026-08-14

This commit is contained in:
Tangem 2022-12-26 11:27:09 +04:00
parent d1798a2c49
commit cf4affa513
16 changed files with 572 additions and 22 deletions

View file

@ -1,7 +1,21 @@
package com.tangem.feature.swap.models
data class SwapSelectTokenStateHolder(
val tokens: List<TokenToSelect>,
val addedTokens: List<TokenToSelect>,
val otherTokens: List<TokenToSelect>,
val onSearchEntered: (String) -> Unit,
val onTokenSelected: (String) -> Unit,
)
data class TokenToSelect(
val id: String,
val name: String,
val symbol: String,
val iconUrl: String,
val addedTokenBalanceData: TokenBalanceData? = null,
)
data class TokenBalanceData(
val amount: String?,
val amountEquivalent: String?,
)

View file

@ -22,18 +22,6 @@ data class SwapStateHolder(
val onSuccess: (() -> Unit)? = null,
)
data class TokenToSelect(
val id: String,
val name: String,
val symbol: String,
val addedTokenBalanceData: TokenBalanceData? = null,
)
data class TokenBalanceData(
val amount: String?,
val amountEquivalent: String?,
)
data class SwapCardData(
val type: TransactionCardType,
val amount: String?,

View file

@ -12,6 +12,7 @@ import androidx.fragment.app.viewModels
import com.tangem.feature.swap.router.SwapRouter
import com.tangem.feature.swap.router.SwapScreen
import com.tangem.feature.swap.ui.SwapScreen
import com.tangem.feature.swap.ui.SwapSelectTokenScreen
import com.tangem.feature.swap.ui.SwapSuccessScreen
import com.tangem.feature.swap.viewmodels.SwapViewModel
import dagger.hilt.android.AndroidEntryPoint
@ -38,7 +39,18 @@ class SwapFragment : Fragment() {
SwapScreen(stateHolder = viewModel.uiState)
}
}
SwapScreen.SelectToken -> SwapScreen(stateHolder = viewModel.uiState)// TODO: use proper screen
SwapScreen.SelectToken -> {
val tokenState = viewModel.uiState.selectTokenState
if (tokenState != null) {
SwapSelectTokenScreen(
state = tokenState,
onBack = viewModel.uiState.onBackClicked,
)
} else {
SwapScreen(stateHolder = viewModel.uiState)
}
}
// screen
}
}

View file

@ -0,0 +1,237 @@
@file:OptIn(ExperimentalFoundationApi::class)
package com.tangem.feature.swap.ui
import androidx.annotation.StringRes
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Divider
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
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.core.ui.components.CurrencyPlaceholderIcon
import com.tangem.core.ui.components.SpacerW2
import com.tangem.core.ui.components.appbar.ExpandableSearchView
import com.tangem.core.ui.res.TangemTheme
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.presentation.R
import com.valentinilk.shimmer.shimmer
@Composable
fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit) {
TangemTheme {
Scaffold(
content = {
ListOfTokens(state = state)
},
topBar = {
ExpandableSearchView(
title = stringResource(R.string.swapping_token_list_your_title),
onBackClick = onBack,
placeholderSearchText = stringResource(id = R.string.search_tokens_title),
onSearchChanged = state.onSearchEntered,
onSearchDisplayClosed = { state.onSearchEntered("") },
)
},
)
}
}
@Composable
private fun ListOfTokens(state: SwapSelectTokenStateHolder) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.primary)
.padding(
start = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing32,
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
stickyHeader {
Header(title = R.string.swapping_token_list_your_tokens)
}
itemsIndexed(items = state.addedTokens) { index, item ->
TokenItem(token = item, onTokenClick = { state.onTokenSelected(item.id) })
if (index != state.addedTokens.lastIndex) Divider(
color = TangemTheme.colors.stroke.primary,
startIndent = TangemTheme.dimens.spacing54,
)
}
stickyHeader {
Header(title = R.string.swapping_token_list_other_tokens)
}
itemsIndexed(items = state.otherTokens) { index, item ->
TokenItem(token = item, onTokenClick = { state.onTokenSelected(item.id) })
if (index != state.otherTokens.lastIndex) Divider(
color = TangemTheme.colors.stroke.primary,
startIndent = TangemTheme.dimens.spacing54,
)
}
}
}
@Composable
private fun Header(@StringRes title: Int) {
Text(
text = stringResource(id = title).uppercase(),
style = TangemTheme.typography.overline,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.primary)
.padding(vertical = TangemTheme.dimens.spacing6),
textAlign = TextAlign.Start,
)
}
@Composable
private fun TokenItem(
token: TokenToSelect,
onTokenClick: (String) -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
top = TangemTheme.dimens.spacing14,
bottom = TangemTheme.dimens.spacing14,
end = TangemTheme.dimens.spacing16,
)
.clickable(
onClick = { onTokenClick(token.id) },
),
) {
Box(
modifier = Modifier
.padding(end = TangemTheme.dimens.spacing12)
.align(Alignment.CenterVertically),
) {
val iconModifier = Modifier
.size(TangemTheme.dimens.size40)
SubcomposeAsyncImage(
modifier = iconModifier,
model = ImageRequest.Builder(LocalContext.current)
.data(token.iconUrl)
.crossfade(true)
.build(),
contentDescription = token.id,
loading = { TokenImageShimmer(modifier = iconModifier) },
error = { CurrencyPlaceholderIcon(modifier = iconModifier, id = token.id) },
)
}
Column(
modifier = Modifier
.align(Alignment.CenterVertically),
) {
Text(
text = token.name,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
SpacerW2()
Text(
text = token.symbol,
style = TangemTheme.typography.caption,
color = TangemTheme.colors.text.tertiary,
)
}
Spacer(modifier = Modifier.weight(1f))
if (token.addedTokenBalanceData != null) {
Column(
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing8),
) {
Text(
text = token.addedTokenBalanceData.amount ?: "",
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
SpacerW2()
Text(
text = token.addedTokenBalanceData.amountEquivalent ?: "",
style = TangemTheme.typography.caption,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
}
@Composable
private fun TokenImageShimmer(
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.shimmer()) {
Box(
modifier = Modifier
.matchParentSize()
.background(
color = TangemTheme.colors.button.secondary,
shape = CircleShape,
),
)
}
}
private val token = TokenToSelect(
id = "",
name = "USDC",
symbol = "USDC",
iconUrl = "",
addedTokenBalanceData = TokenBalanceData(
amount = "15 000 $",
amountEquivalent = "15 000 " +
"USDT",
),
)
@Preview
@Composable
fun TokenScreenPreview() {
SwapSelectTokenScreen(
state = SwapSelectTokenStateHolder(
listOf(token, token, token), listOf(token, token, token), {}, {},
),
) {
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.swap.ui
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -15,10 +16,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
@ -46,9 +47,9 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.TransactionCardType
import com.valentinilk.shimmer.shimmer
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun TransactionCard(
modifier: Modifier = Modifier,
type: TransactionCardType,
amount: String?,
amountEquivalent: String?,
@ -56,15 +57,12 @@ fun TransactionCard(
tokenCurrency: String,
@DrawableRes networkIconRes: Int? = null,
onChangeTokenClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
Card(
modifier = modifier,
shape = RoundedCornerShape(TangemTheme.dimens.radius12),
backgroundColor = TangemTheme.colors.background.primary,
elevation = TangemTheme.dimens.elevation2,
onClick = onChangeTokenClick ?: {},
enabled = onChangeTokenClick != null,
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
@ -96,6 +94,13 @@ fun TransactionCard(
Box(modifier = Modifier.align(Alignment.CenterEnd)) {
ChangeTokenSelector()
}
Box(
Modifier
.align(Alignment.CenterEnd)
.height(TangemTheme.dimens.size116)
.width(TangemTheme.dimens.size102)
.clickable { onChangeTokenClick() },
)
}
}

View file

@ -14,9 +14,12 @@ import com.tangem.feature.swap.models.FeeState
import com.tangem.feature.swap.models.SwapButton
import com.tangem.feature.swap.models.SwapCardData
import com.tangem.feature.swap.models.SwapPermissionStateHolder
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.models.SwapSuccessStateHolder
import com.tangem.feature.swap.models.SwapWarning
import com.tangem.feature.swap.models.TokenBalanceData
import com.tangem.feature.swap.models.TokenToSelect
import com.tangem.feature.swap.models.TransactionCardType
import com.tangem.feature.swap.presentation.R
import com.tangem.feature.swap.router.SwapRouter
@ -45,7 +48,7 @@ internal class SwapViewModel @Inject constructor(
swapRouter = router
uiState = uiState.copy(
onBackClicked = router::back,
onSelectTokenClick = { router.openScreen(SwapScreen.Success) },
onSelectTokenClick = { router.openScreen(SwapScreen.SelectToken) },
onSuccess = { router.openScreen(SwapScreen.Success) },
)
}
@ -108,6 +111,19 @@ internal class SwapViewModel @Inject constructor(
cancelButton = CancelPermissionButton(true) {},
)
val token = TokenToSelect(
id = "USDC",
name = "USDC",
symbol = "USDC",
iconUrl = "",
addedTokenBalanceData = TokenBalanceData(amount = "15 000 $", amountEquivalent = "15 000 USDT"),
)
val selectTokenState = SwapSelectTokenStateHolder(
listOf(token, token, token, token, token, token, token, token, token),
listOf(token, token, token, token, token, token, token, token, token), {}, {},
)
return SwapStateHolder(
sendCardData = sendCard,
receiveCardData = receiveCard,
@ -118,6 +134,7 @@ internal class SwapViewModel @Inject constructor(
onRefresh = {}, onBackClicked = {}, onChangeCardsClicked = {},
permissionState = permissionState,
successState = SwapSuccessStateHolder("Success"),
selectTokenState = selectTokenState,
)
}
// endregion temp for test