Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-05 15:06:54 +08:00
parent 2ca7759e7e
commit 552e8cc752
12 changed files with 1051 additions and 93 deletions

View file

@ -45,6 +45,7 @@ dependencies {
implementation(deps.lifecycle.viewModel.ktx)
/** Compose libraries */
implementation(deps.compose.constraintLayout)
implementation(deps.compose.material)
implementation(deps.compose.animation)
implementation(deps.compose.foundation)

View file

@ -0,0 +1,223 @@
package com.tangem.tap.features.tokens.presentation.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.compose.extensions.OnBottomReached
import com.tangem.tap.features.tokens.presentation.states.AddTokensNetworkItemState
import com.tangem.tap.features.tokens.presentation.states.AddTokensStateHolder
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState
import com.tangem.tap.features.tokens.presentation.states.TokenItemModel
import com.tangem.tap.features.tokens.presentation.states.TokensListVisibility
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.store
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun AddTokensScreen(stateHolder: AddTokensStateHolder) {
BackHandler(onBack = stateHolder.toolbarState.onBackButtonClick)
Scaffold(
topBar = { AddTokensToolbar(state = stateHolder.toolbarState) },
floatingActionButton = {
if (stateHolder is AddTokensStateHolder.ManageAccess) {
SaveChangesButton(onClick = stateHolder.onSaveButtonClick)
}
},
) {
when (stateHolder) {
is AddTokensStateHolder.Loading -> LoadingContent()
is TokensListVisibility -> TokensListContent(stateHolder = stateHolder, scaffoldPadding = it)
}
}
}
@Composable
private fun LoadingContent() {
Box(
modifier = Modifier
.fillMaxSize()
.background(color = TangemColorPalette.White),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(color = TangemColorPalette.Meadow)
}
}
@Composable
private fun TokensListContent(stateHolder: TokensListVisibility, scaffoldPadding: PaddingValues) {
val state = rememberLazyListState()
state.OnBottomReached(loadMoreThreshold = 40) {
store.dispatch(TokensAction.LoadMore(scanResponse = store.state.globalState.scanResponse))
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(scaffoldPadding),
state,
) {
items(
items = stateHolder.tokens,
key = TokenItemModel::name,
itemContent = { TokenItem(model = it) },
)
}
}
@Composable
private fun SaveChangesButton(onClick: () -> Unit) {
PrimaryButton(
modifier = Modifier
.imePadding()
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
text = stringResource(id = R.string.common_save_changes),
onClick = onClick,
)
}
@Preview
@Composable
private fun Preview_AddTokensScreen_Loading() {
TangemTheme {
AddTokensScreen(
stateHolder = AddTokensStateHolder.Loading(
toolbarState = AddTokensToolbarState.Title.EditAccess(
titleResId = R.string.main_manage_tokens,
onBackButtonClick = {},
onSearchButtonClick = {},
onAddCustomTokenClick = {},
),
),
)
}
}
@Preview
@Composable
private fun Preview_AddTokensScreen_ManageAccess() {
TangemTheme {
AddTokensScreen(
stateHolder = AddTokensStateHolder.ManageAccess(
toolbarState = AddTokensToolbarState.Title.EditAccess(
titleResId = R.string.main_manage_tokens,
onBackButtonClick = {},
onSearchButtonClick = {},
onAddCustomTokenClick = {},
),
tokens = listOf(
TokenItemModel(
name = "ETHEREUM (ETH)",
iconUrl = "",
networks = listOf(
AddTokensNetworkItemState.EditAccess(
name = "ETHEREUM",
protocolName = "MAIN",
iconResId = R.drawable.img_btc_22,
isMainNetwork = true,
isAdded = true,
networkId = "",
onToggleClick = {},
),
),
),
TokenItemModel(
name = "Tether (USDT)",
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
networks = listOf(
AddTokensNetworkItemState.EditAccess(
name = "ETHEREUM",
protocolName = "MAIN",
iconResId = R.drawable.img_eth_22,
isMainNetwork = false,
isAdded = true,
networkId = "",
onToggleClick = {},
),
AddTokensNetworkItemState.EditAccess(
name = "BNB SMART CHAIN",
protocolName = "BEP20",
iconResId = R.drawable.ic_bsc_no_color,
isMainNetwork = false,
isAdded = false,
networkId = "",
onToggleClick = {},
),
),
),
),
onSaveButtonClick = {},
),
)
}
}
@Preview
@Composable
private fun Preview_AddTokensScreen_ReadAccess() {
TangemTheme {
AddTokensScreen(
stateHolder = AddTokensStateHolder.ReadAccess(
toolbarState = AddTokensToolbarState.Title.EditAccess(
titleResId = R.string.search_tokens_title,
onBackButtonClick = {},
onSearchButtonClick = {},
onAddCustomTokenClick = {},
),
tokens = listOf(
TokenItemModel(
name = "ETHEREUM (ETH)",
iconUrl = "",
networks = listOf(
AddTokensNetworkItemState.ReadAccess(
name = "ETHEREUM",
protocolName = "MAIN",
iconResId = R.drawable.img_btc_22,
isMainNetwork = true,
),
),
),
TokenItemModel(
name = "Tether (USDT)",
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
networks = listOf(
AddTokensNetworkItemState.ReadAccess(
name = "ETHEREUM",
protocolName = "MAIN",
iconResId = R.drawable.img_eth_22,
isMainNetwork = false,
),
AddTokensNetworkItemState.ReadAccess(
name = "BNB SMART CHAIN",
protocolName = "BEP20",
iconResId = R.drawable.ic_bsc_no_color,
isMainNetwork = false,
),
),
),
),
),
)
}
}

View file

@ -0,0 +1,207 @@
package com.tangem.tap.features.tokens.presentation.ui
import androidx.compose.foundation.background
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState.SearchInputField
import com.tangem.tap.features.tokens.presentation.states.AddTokensToolbarState.Title
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun AddTokensToolbar(state: AddTokensToolbarState) {
TopAppBar(backgroundColor = TangemTheme.colors.background.secondary) {
IconButton(onClick = state.onBackButtonClick) {
Icon(
painter = painterResource(id = R.drawable.ic_back_24),
contentDescription = "Go back",
tint = TangemTheme.colors.icon.secondary,
)
}
val contentModifier = Modifier
.weight(1f)
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(bottom = TangemTheme.dimens.spacing4)
when (state) {
is Title -> TitleContent(state = state, modifier = contentModifier)
is SearchInputField -> InputContent(state = state, modifier = contentModifier)
}
}
}
@Composable
private fun TitleContent(state: Title, modifier: Modifier = Modifier) {
Text(
text = stringResource(id = state.titleResId),
modifier = modifier,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
style = TangemTheme.typography.h3,
)
IconButton(onClick = state.onSearchButtonClick) {
Icon(
painter = painterResource(id = R.drawable.ic_search_24),
contentDescription = "Search",
tint = TangemTheme.colors.icon.secondary,
)
}
if (state is Title.EditAccess) {
IconButton(onClick = state.onAddCustomTokenClick) {
Icon(
painter = painterResource(id = R.drawable.ic_plus_24),
contentDescription = "Add custom token",
tint = TangemTheme.colors.icon.secondary,
)
}
}
}
@Composable
fun InputContent(state: SearchInputField, modifier: Modifier = Modifier) {
BasicTextField(
value = state.value,
onValueChange = state.onValueChange,
modifier = modifier,
textStyle = TangemTheme.typography.subtitle1.copy(fontWeight = FontWeight.Normal),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text, imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = { state.onSearchButtonClick() }),
singleLine = true,
maxLines = 1,
cursorBrush = SolidColor(value = TangemTheme.colors.stroke.secondary),
) { innerTextField ->
Column(modifier = Modifier.padding(top = TangemTheme.dimens.spacing6)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.weight(1f)
.padding(start = TangemTheme.dimens.spacing6),
) {
Hint(value = state.value)
innerTextField()
}
IconButton(onClick = state.onCleanButtonClick) {
Icon(
painter = painterResource(id = R.drawable.ic_cross_rounded_24),
contentDescription = "Clean entered value",
tint = TangemTheme.colors.icon.secondary,
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(height = TangemTheme.dimens.size1)
.background(color = TangemTheme.colors.stroke.primary),
)
}
}
}
@Composable
private fun Hint(value: String) {
if (value.isBlank()) {
Row(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing4),
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(id = R.drawable.ic_search_24),
contentDescription = null,
tint = TangemTheme.colors.icon.secondary,
)
Text(
text = stringResource(id = R.string.common_search),
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing2),
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
// FIXME("Incorrect typography. Replace with typography from design system")
style = TangemTheme.typography.subtitle1.copy(fontSize = 18.sp, fontWeight = FontWeight.Normal),
)
}
}
}
@Preview
@Composable
private fun Preview_AddTokensToolbar_EditAccess() {
TangemTheme {
AddTokensToolbar(
state = Title.EditAccess(
titleResId = R.string.main_manage_tokens,
onBackButtonClick = {},
onSearchButtonClick = {},
onAddCustomTokenClick = {},
),
)
}
}
@Preview
@Composable
private fun Preview_AddTokensToolbar_ReadAccess() {
TangemTheme {
AddTokensToolbar(
state = Title.ReadAccess(
titleResId = R.string.search_tokens_title,
onBackButtonClick = {},
onSearchButtonClick = {},
),
)
}
}
@Preview
@Composable
private fun Preview_AddTokensToolbar_SearchInputField() {
var value by remember { mutableStateOf("") }
TangemTheme {
AddTokensToolbar(
state = SearchInputField(
onBackButtonClick = {},
onSearchButtonClick = {},
value = value,
onValueChange = { value = it },
onCleanButtonClick = { value = "" },
),
)
}
}

View file

@ -0,0 +1,82 @@
package com.tangem.tap.features.tokens.presentation.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.tokens.presentation.states.AddTokensNetworkItemState
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun BriefNetworksList(
isCollapsed: Boolean,
networks: List<AddTokensNetworkItemState>,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isCollapsed,
modifier = modifier,
enter = fadeIn() + expandVertically(),
exit = shrinkVertically() + fadeOut(),
) {
if (isCollapsed) {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
networks.forEach {
key(it.name) {
BriefNetworkItem(model = it)
}
}
}
}
}
}
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun BriefNetworkItem(model: AddTokensNetworkItemState) {
Box(modifier = Modifier.size(size = TangemTheme.dimens.size20)) {
Image(
painter = painterResource(id = model.iconResId),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size20),
)
if (model.isMainNetwork) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(TangemTheme.dimens.size7)
.clip(CircleShape)
.background(TangemColorPalette.White),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.size(TangemTheme.dimens.size5)
.clip(CircleShape)
.background(TangemColorPalette.Meadow),
)
}
}
}
}

View file

@ -0,0 +1,168 @@
package com.tangem.tap.features.tokens.presentation.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.Switch
import androidx.compose.material.SwitchDefaults
import androidx.compose.material.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.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.tokens.presentation.states.AddTokensNetworkItemState
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun DetailedNetworksList(
isExpanded: Boolean,
networks: List<AddTokensNetworkItemState>,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isExpanded,
modifier = modifier,
enter = fadeIn() + expandVertically(),
exit = shrinkVertically() + fadeOut(),
) {
if (isExpanded) {
Column {
networks.forEachIndexed { index, network ->
key(network.name) {
DetailedNetworkItem(model = network, isLastItem = networks.lastIndex == index)
}
}
}
}
}
}
@Composable
private fun DetailedNetworkItem(model: AddTokensNetworkItemState, isLastItem: Boolean) {
val itemHeight = TangemTheme.dimens.size50
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = itemHeight)
.padding(start = TangemTheme.dimens.spacing38),
verticalAlignment = Alignment.CenterVertically,
) {
NetworkItemArrow(itemHeight = itemHeight, isLastItem = isLastItem)
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing14))
BriefNetworkItem(model = model)
Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing6))
NetworkTitle(model = model)
if (model is AddTokensNetworkItemState.EditAccess) {
Switch(
checked = model.isAdded,
onCheckedChange = { model.onToggleClick(model.networkId) },
modifier = Modifier.padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing8),
colors = SwitchDefaults.colors(
checkedThumbColor = TangemColorPalette.Meadow,
),
)
}
}
}
@Composable
private fun RowScope.NetworkTitle(model: AddTokensNetworkItemState) {
Text(
modifier = Modifier.weight(1f),
text = buildAnnotatedString {
append(text = model.name)
append(text = " ")
withStyle(
style = SpanStyle(
fontWeight = FontWeight.Normal,
color = if (model.isMainNetwork) TangemColorPalette.Meadow else TangemColorPalette.Dark2,
),
) {
append(text = model.protocolName)
}
},
fontWeight = FontWeight.SemiBold,
fontSize = 13.sp,
color = if (model is AddTokensNetworkItemState.EditAccess && model.isAdded) {
TangemColorPalette.Black
} else {
TangemColorPalette.Dark2
},
)
}
@Preview
@Composable
private fun Preview_DetailedNetworksList_EditAccess() {
TangemTheme {
DetailedNetworksList(
networks = listOf(
AddTokensNetworkItemState.EditAccess(
name = "ETHEREUM",
protocolName = "MAIN",
iconResId = R.drawable.ic_eth_no_color,
isMainNetwork = true,
isAdded = true,
networkId = "",
onToggleClick = {},
),
AddTokensNetworkItemState.EditAccess(
name = "BNB SMART CHAIN",
protocolName = "BEP20",
iconResId = R.drawable.ic_bsc_no_color,
isMainNetwork = false,
isAdded = false,
networkId = "",
onToggleClick = {},
),
),
isExpanded = false,
)
}
}
@Preview
@Composable
private fun Preview_DetailedNetworksList_ReadAccess() {
TangemTheme {
DetailedNetworksList(
networks = listOf(
AddTokensNetworkItemState.ReadAccess(
name = "ETHEREUM",
protocolName = "MAIN",
iconResId = R.drawable.ic_eth_no_color,
isMainNetwork = true,
),
AddTokensNetworkItemState.ReadAccess(
name = "BNB SMART CHAIN",
protocolName = "BEP20",
iconResId = R.drawable.ic_bsc_no_color,
isMainNetwork = false,
),
),
isExpanded = false,
)
}
}

View file

@ -0,0 +1,60 @@
package com.tangem.tap.features.tokens.presentation.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.wallet.R
private const val SPECIAL_HEIGHT_3_5 = 3.5
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun NetworkItemArrow(itemHeight: Dp, isLastItem: Boolean) {
Box(modifier = Modifier.height(height = itemHeight)) {
if (!isLastItem) {
Box(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing0_5)
.background(color = TangemTheme.colors.stroke.primary)
.size(width = TangemTheme.dimens.size1, height = itemHeight),
)
}
Icon(
painter = painterResource(id = R.drawable.ic_link),
contentDescription = null,
modifier = Modifier.height(height = itemHeight / 2 + SPECIAL_HEIGHT_3_5.dp),
tint = TangemTheme.colors.stroke.primary,
)
}
}
@Preview
@Composable
private fun Preview_NetworkItemArrow_Column() {
TangemTheme {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = TangemTheme.colors.background.primary)
.padding(start = TangemTheme.dimens.size36),
) {
NetworkItemArrow(itemHeight = TangemTheme.dimens.size62, isLastItem = false)
NetworkItemArrow(itemHeight = TangemTheme.dimens.size62, isLastItem = true)
}
}
}

View file

@ -0,0 +1,292 @@
package com.tangem.tap.features.tokens.presentation.ui
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.with
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.compose.extensions.toPx
import com.tangem.tap.features.tokens.presentation.states.AddTokensNetworkItemState
import com.tangem.tap.features.tokens.presentation.states.TokenItemModel
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
internal fun TokenItem(model: TokenItemModel) {
var isExpanded by remember { mutableStateOf(false) }
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing16),
) {
val (icon, title, availableNetworksText) = createRefs()
val (briefNetworksList, detailedNetworksList, changeNetworksViewButton) = createRefs()
val spacing16 = TangemTheme.dimens.spacing16
val spacing6 = TangemTheme.dimens.spacing6
Icon(
name = model.name,
iconUrl = model.iconUrl,
modifier = Modifier.constrainAs(icon) {
top.linkTo(parent.top)
start.linkTo(anchor = parent.start, margin = spacing16)
},
)
Title(
title = model.name,
modifier = Modifier.constrainAs(title) {
top.linkTo(parent.top)
start.linkTo(icon.end, margin = spacing16)
end.linkTo(anchor = changeNetworksViewButton.start, margin = spacing16)
width = Dimension.fillToConstraints
},
)
Subtitle(
isExpanded = isExpanded,
modifier = Modifier.constrainAs(availableNetworksText) {
top.linkTo(title.bottom, margin = spacing6)
start.linkTo(icon.end, margin = spacing16)
end.linkTo(anchor = changeNetworksViewButton.start, margin = spacing16)
width = Dimension.fillToConstraints
},
)
BriefNetworksList(
isCollapsed = !isExpanded,
networks = model.networks,
modifier = Modifier.constrainAs(briefNetworksList) {
top.linkTo(title.bottom, margin = spacing6)
start.linkTo(icon.end, margin = spacing16)
end.linkTo(anchor = changeNetworksViewButton.start, margin = spacing16)
width = Dimension.fillToConstraints
},
)
DetailedNetworksList(
isExpanded = isExpanded,
networks = model.networks,
modifier = Modifier.constrainAs(detailedNetworksList) {
top.linkTo(anchor = availableNetworksText.bottom, margin = spacing16)
centerHorizontallyTo(parent)
width = Dimension.fillToConstraints
},
)
ChangeNetworksViewButton(
isExpanded = isExpanded,
onClick = { isExpanded = !isExpanded },
modifier = Modifier.constrainAs(changeNetworksViewButton) {
top.linkTo(parent.top)
end.linkTo(anchor = parent.end, margin = spacing16)
if (!isExpanded) bottom.linkTo(parent.bottom)
},
)
}
}
@Composable
private fun Icon(name: String, iconUrl: String, modifier: Modifier = Modifier) {
val iconModifier = modifier.size(size = TangemTheme.dimens.size46)
SubcomposeAsyncImage(
modifier = iconModifier,
model = ImageRequest.Builder(context = LocalContext.current)
.size(size = TangemTheme.dimens.size46.toPx().toInt())
.data(data = iconUrl)
.crossfade(enable = true)
.build(),
contentDescription = null,
loading = { PlaceholderIcon(name = name, modifier = iconModifier) },
error = { PlaceholderIcon(name = name, modifier = iconModifier) },
)
}
@Composable
private fun PlaceholderIcon(name: String, modifier: Modifier = Modifier) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier.background(TangemTheme.colors.icon.primary1, shape = CircleShape),
) {
Text(
text = name.firstOrNull()?.titlecase() ?: "",
color = TangemTheme.colors.icon.primary2,
maxLines = 1,
// FIXME("Incorrect typography. Replace with typography from design system")
style = TangemTheme.typography.h3.copy(fontWeight = FontWeight.Bold),
)
}
}
@Composable
private fun Title(title: String, modifier: Modifier = Modifier) {
Text(
text = title,
modifier = modifier,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Start,
maxLines = 1,
style = TangemTheme.typography.body1,
)
}
@Composable
private fun Subtitle(isExpanded: Boolean, modifier: Modifier = Modifier) {
AnimatedVisibility(
visible = isExpanded,
modifier = modifier,
enter = fadeIn() + expandVertically(),
exit = shrinkVertically() + fadeOut(),
) {
Text(
text = stringResource(id = R.string.currency_subtitle_expanded),
color = TangemTheme.colors.text.secondary,
maxLines = 1,
style = TangemTheme.typography.body2,
)
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun ChangeNetworksViewButton(
isExpanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
IconButton(onClick = onClick, modifier = modifier.size(size = TangemTheme.dimens.size46)) {
AnimatedContent(
targetState = isExpanded,
transitionSpec = { fadeIn() + scaleIn() with scaleOut() + fadeOut() },
) { isExpanded ->
Icon(
painter = painterResource(
id = if (isExpanded) R.drawable.ic_arrow_extended else R.drawable.ic_arrow_collapsed,
),
contentDescription = null,
tint = TangemTheme.colors.icon.secondary,
)
}
}
}
@Preview
@Composable
private fun Preview_TokenItem_EditAccess() {
TangemTheme {
TokenItem(
TokenItemModel(
name = "Tether (USDT)",
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
networks = listOf(
AddTokensNetworkItemState.EditAccess(
name = "Ethereum",
protocolName = "MAIN",
iconResId = R.drawable.ic_eth_no_color,
isMainNetwork = true,
isAdded = true,
networkId = "",
onToggleClick = {},
),
AddTokensNetworkItemState.EditAccess(
name = "BNB SMART CHAIN",
protocolName = "BEP20",
iconResId = R.drawable.ic_bsc_no_color,
isMainNetwork = false,
isAdded = false,
networkId = "",
onToggleClick = {},
),
),
),
)
}
}
@Preview
@Composable
private fun Preview_TokenItem_ReadAccess() {
TangemTheme {
TokenItem(
TokenItemModel(
name = "Tether (USDT)",
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
networks = listOf(
AddTokensNetworkItemState.ReadAccess(
name = "Ethereum",
protocolName = "MAIN",
iconResId = R.drawable.ic_eth_no_color,
isMainNetwork = true,
),
AddTokensNetworkItemState.ReadAccess(
name = "BNB SMART CHAIN",
protocolName = "BEP20",
iconResId = R.drawable.ic_bsc_no_color,
isMainNetwork = false,
),
),
),
)
}
}
@Preview
@Composable
private fun Preview_TokenItem_EditAccess_LargeNetworkList() {
TangemTheme {
TokenItem(
TokenItemModel(
name = "Tether (USDT)",
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
networks = List(
size = 15,
init = {
AddTokensNetworkItemState.ReadAccess(
name = if (it % 2 != 0) "Ethereum" else "BNB SMART CHAIN",
protocolName = if (it % 2 != 0) "MAIN" else "BEP20",
iconResId = if (it % 2 != 0) R.drawable.ic_eth_no_color else R.drawable.ic_bsc_no_color,
isMainNetwork = it % 2 != 0,
)
},
),
),
)
}
}

View file

@ -1,89 +0,0 @@
package com.tangem.tap.features.tokens.ui.compose.currencyItem
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.wallet.R
/**
[REDACTED_AUTHOR]
*/
@Composable
fun CurrencyItemArrow(
rowHeight: Dp,
isLastArrow: Boolean,
) {
Box(Modifier.height(rowHeight)) {
if (!isLastArrow) {
MiddleArrowView(rowHeight)
} else {
LastArrowView(rowHeight)
}
}
}
@Suppress("MagicNumber")
@Composable
private fun MiddleArrowView(rowHeight: Dp) {
Box {
Box(
modifier = Modifier
.padding(start = 0.5.dp),
) {
Box(
modifier = Modifier
.background(color = Color(0xFFDEDEDE))
.width(0.8.dp)
.fillMaxHeight(),
)
}
LastArrowView(rowHeight)
}
}
@Composable
private fun LastArrowView(rowHeight: Dp) {
Image(
modifier = Modifier
.height(rowHeight / 2 + 3.5.dp),
painter = painterResource(id = R.drawable.ic_link),
contentDescription = null,
)
}
@Preview
@Composable
private fun ArrowViewPreview() {
Box(Modifier.background(color = Color.White)) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 37.dp),
) {
CurrencyItemArrow(
rowHeight = 62.dp,
isLastArrow = false,
)
CurrencyItemArrow(
rowHeight = 62.dp,
isLastArrow = true,
)
}
}
}

View file

@ -37,6 +37,7 @@ import com.tangem.tap.common.extensions.getGreyedOutIconRes
import com.tangem.tap.common.extensions.getNetworkName
import com.tangem.tap.domain.tokens.Contract
import com.tangem.tap.domain.tokens.Currency
import com.tangem.tap.features.tokens.presentation.ui.NetworkItemArrow
import com.tangem.tap.features.tokens.redux.ContractAddress
import com.tangem.tap.features.tokens.redux.TokenWithBlockchain
import com.tangem.tap.features.tokens.ui.compose.CurrencyPlaceholderIcon
@ -76,9 +77,9 @@ fun NetworkItem(
.width(78.dp)
.padding(start = 38.dp),
) {
CurrencyItemArrow(
rowHeight = rowHeight,
isLastArrow = index == size - 1,
NetworkItemArrow(
itemHeight = rowHeight,
isLastItem = index == size - 1,
)
}
Box(

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" />
</vector>

View file

@ -32,8 +32,11 @@ data class TangemDimens internal constructor(
// region Size
val size0: Dp = 0.dp,
val size0_5: Dp = 0.5.dp,
val size1: Dp = 1.dp,
val size2: Dp = 2.dp,
val size4: Dp = 4.dp,
val size5: Dp = 5.dp,
val size7: Dp = 7.dp,
val size8: Dp = 8.dp,
val size11: Dp = 11.dp,
val size12: Dp = 12.dp,
@ -80,6 +83,7 @@ data class TangemDimens internal constructor(
val spacing28: Dp = 28.dp,
val spacing32: Dp = 32.dp,
val spacing34: Dp = 34.dp,
val spacing38: Dp = 38.dp,
val spacing44: Dp = 44.dp,
val spacing50: Dp = 50.dp,
val spacing52: Dp = 52.dp,

View file

@ -84,7 +84,7 @@ complexity:
threshold: 300
LongMethod:
active: true
threshold: 60
threshold: 70
LongParameterList:
active: true
functionThreshold: 6