Updated on 2026-08-14
This commit is contained in:
parent
ae3d68a8d5
commit
99745c2ee4
14 changed files with 442 additions and 130 deletions
|
|
@ -46,6 +46,7 @@
|
|||
<string name="chat_button_title">Чат</string>
|
||||
<string name="common_accept">Принять</string>
|
||||
<string name="common_add">Добавить</string>
|
||||
<string name="common_apply">Применить</string>
|
||||
<string name="common_attention">Внимание</string>
|
||||
<string name="common_balance">Баланс: %s</string>
|
||||
<string name="common_biometric_authentication">биометрическую аутентификацию</string>
|
||||
|
|
@ -255,6 +256,9 @@
|
|||
<string name="onboarding_wallet_info_title_fourth">Восстановление кода доступа</string>
|
||||
<string name="onboarding_wallet_info_title_second">Идентичные карты</string>
|
||||
<string name="onboarding_wallet_info_title_third">Код доступа</string>
|
||||
<string name="organize_tokens_group">Группировка</string>
|
||||
<string name="organize_tokens_sort_by_balance">По балансу</string>
|
||||
<string name="organize_tokens_title">Сортировка токенов</string>
|
||||
<string name="referral_button_participate">Участвовать</string>
|
||||
<string name="referral_error_failed_to_load_info">Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже.</string>
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
<string name="chat_button_title">Support</string>
|
||||
<string name="common_accept">Accept</string>
|
||||
<string name="common_add">Add</string>
|
||||
<string name="common_apply">Apply</string>
|
||||
<string name="common_attention">Attention</string>
|
||||
<string name="common_balance">Balance: %s</string>
|
||||
<string name="common_biometric_authentication">biometric authentication</string>
|
||||
|
|
@ -253,6 +254,9 @@
|
|||
<string name="onboarding_wallet_info_title_fourth">Access code restore</string>
|
||||
<string name="onboarding_wallet_info_title_second">Identical cards</string>
|
||||
<string name="onboarding_wallet_info_title_third">Access code</string>
|
||||
<string name="organize_tokens_group">Group</string>
|
||||
<string name="organize_tokens_sort_by_balance">By balance</string>
|
||||
<string name="organize_tokens_title">Organize tokens</string>
|
||||
<string name="referral_button_participate">Participate</string>
|
||||
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string>
|
||||
|
|
|
|||
9
core/ui/src/main/res/drawable/ic_group_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_group_24.xml
Normal 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="M5,4V20H7V22H3V2H7V4H5ZM20,6H7V8H20V6ZM20,11H7V13H20V11ZM20,16H7V18H20V16Z" />
|
||||
</vector>
|
||||
9
core/ui/src/main/res/drawable/ic_sort_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_sort_24.xml
Normal 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="M19,17H22L18,21L14,17H17V3H19M3,6H13V8H3M7,16V18H3V16M3,11H10V13H3V11Z" />
|
||||
</vector>
|
||||
|
|
@ -8,6 +8,8 @@ import android.view.ViewGroup
|
|||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import com.tangem.features.wallet.navigation.WalletRouter
|
||||
|
|
@ -41,8 +43,15 @@ internal class WalletFragment : Fragment() {
|
|||
|
||||
return ComposeView(inflater.context).apply {
|
||||
setContent {
|
||||
isTransitionGroup = true
|
||||
_walletRouter.Initialize()
|
||||
TangemTheme {
|
||||
val systemBarsColor = TangemTheme.colors.background.secondary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(systemBarsColor)
|
||||
}
|
||||
|
||||
isTransitionGroup = true
|
||||
_walletRouter.Initialize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import com.tangem.core.ui.R
|
|||
import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenListState
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensListState
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.util.UUID
|
||||
|
||||
internal object WalletPreviewData {
|
||||
|
|
@ -107,42 +109,6 @@ internal object WalletPreviewData {
|
|||
|
||||
val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString())
|
||||
|
||||
val organizeTokensState = OrganizeTokensStateHolder(
|
||||
tokens = TokenListState.GroupedByNetwork(
|
||||
groups = persistentListOf(),
|
||||
),
|
||||
header = OrganizeTokensStateHolder.HeaderConfig(
|
||||
onSortByBalanceClick = {},
|
||||
onGroupByNetworkClick = {},
|
||||
),
|
||||
actions = OrganizeTokensStateHolder.ActionsConfig(
|
||||
onApplyClick = {},
|
||||
onCancelClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
private val draggableTokenList = persistentListOf(
|
||||
tokenItemDragState.copy(
|
||||
id = "token_1",
|
||||
name = "Ethereum",
|
||||
tokenIconResId = R.drawable.img_eth_22,
|
||||
networkIconResId = null,
|
||||
fiatAmount = "3 172,14 $",
|
||||
),
|
||||
tokenItemDragState.copy(
|
||||
id = "token_2",
|
||||
networkIconResId = R.drawable.img_eth_22,
|
||||
fiatAmount = "803,65 $",
|
||||
),
|
||||
tokenItemDragState.copy(
|
||||
id = "token_3",
|
||||
name = "USDT",
|
||||
tokenIconResId = R.drawable.img_arbitrum_22,
|
||||
networkIconResId = R.drawable.img_eth_22,
|
||||
fiatAmount = "88,01 $",
|
||||
),
|
||||
)
|
||||
|
||||
val networkGroup = NetworkGroupState.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
networkName = "Ethereum",
|
||||
|
|
@ -170,8 +136,66 @@ internal object WalletPreviewData {
|
|||
)
|
||||
|
||||
val draggableNetworkGroup = NetworkGroupState.Draggable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
id = "group_1",
|
||||
networkName = "Ethereum",
|
||||
tokens = draggableTokenList,
|
||||
)
|
||||
|
||||
private const val networksSize = 10
|
||||
private const val tokensSize = 3
|
||||
val draggableItems = List(networksSize) { it }
|
||||
.flatMap { index ->
|
||||
val n = index + 1
|
||||
|
||||
val group = DraggableItem.GroupHeader(
|
||||
groupState = NetworkGroupState.Draggable(
|
||||
id = "group_$n",
|
||||
networkName = "$n",
|
||||
),
|
||||
)
|
||||
|
||||
val tokens: MutableList<DraggableItem.Token> = mutableListOf()
|
||||
repeat(times = tokensSize) { i ->
|
||||
val nt = i + 1
|
||||
tokens.add(
|
||||
DraggableItem.Token(
|
||||
tokenItemState = tokenItemDragState.copy(
|
||||
id = "${group.id}_token_$nt",
|
||||
name = "Token $nt",
|
||||
networkIconResId = R.drawable.img_eth_22.takeIf { i != 0 },
|
||||
),
|
||||
groupId = group.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
buildList {
|
||||
add(group)
|
||||
addAll(tokens)
|
||||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
|
||||
val draggableTokens = draggableItems
|
||||
.filterIsInstance<DraggableItem.Token>()
|
||||
.toPersistentList()
|
||||
|
||||
val groupedOrganizeTokensState = OrganizeTokensStateHolder(
|
||||
itemsState = OrganizeTokensListState.GroupedByNetwork(
|
||||
items = draggableItems,
|
||||
),
|
||||
header = OrganizeTokensStateHolder.HeaderConfig(
|
||||
onSortByBalanceClick = {},
|
||||
onGroupByNetworkClick = {},
|
||||
),
|
||||
actions = OrganizeTokensStateHolder.ActionsConfig(
|
||||
onApplyClick = {},
|
||||
onCancelClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
val organizeTokensState = groupedOrganizeTokensState.copy(
|
||||
itemsState = OrganizeTokensListState.Ungrouped(
|
||||
items = draggableTokens,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -19,7 +19,42 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
|||
import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState
|
||||
|
||||
@Composable
|
||||
internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Modifier) {
|
||||
internal fun NetworkGroupItem(state: NetworkGroupState.Content, modifier: Modifier = Modifier) {
|
||||
InternalNetworkGroupItem(
|
||||
modifier = modifier,
|
||||
networkName = state.networkName,
|
||||
tokens = {
|
||||
state.tokens.forEach { token ->
|
||||
key(token.id) {
|
||||
TokenItem(state = token)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DraggableNetworkGroupItem(state: NetworkGroupState.Draggable, modifier: Modifier = Modifier) {
|
||||
InternalNetworkGroupItem(
|
||||
modifier = modifier,
|
||||
networkName = state.networkName,
|
||||
endIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_group_drop_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InternalNetworkGroupItem(
|
||||
networkName: String,
|
||||
modifier: Modifier = Modifier,
|
||||
tokens: @Composable ColumnScope.() -> Unit = {},
|
||||
endIcon: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -31,34 +66,30 @@ internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Mod
|
|||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_network_group_title, state.networkName),
|
||||
text = stringResource(id = R.string.wallet_network_group_title, networkName),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
if (state is NetworkGroupState.Draggable) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_group_drop_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
state.tokens.forEach { token ->
|
||||
key(token.id) {
|
||||
TokenItem(state = token)
|
||||
}
|
||||
}
|
||||
endIcon()
|
||||
}
|
||||
tokens()
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun NetworkGroupItemSample(group: NetworkGroupState) {
|
||||
when (group) {
|
||||
is NetworkGroupState.Content -> NetworkGroupItem(group)
|
||||
is NetworkGroupState.Draggable -> DraggableNetworkGroupItem(group)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) {
|
||||
TangemTheme {
|
||||
NetworkGroupItem(group)
|
||||
NetworkGroupItemSample(group)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +97,7 @@ private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider
|
|||
@Composable
|
||||
private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) {
|
||||
TangemTheme(isDark = true) {
|
||||
NetworkGroupItem(group)
|
||||
NetworkGroupItemSample(group)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.material3.Icon
|
|||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -18,6 +19,7 @@ import androidx.compose.ui.res.painterResource
|
|||
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.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstrainedLayoutReference
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
|
|
@ -34,6 +36,10 @@ import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
|||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
|
||||
|
||||
private const val DOTS = "•••"
|
||||
val TOKEN_ITEM_HEIGHT: Dp
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = TangemTheme.dimens.size68
|
||||
|
||||
@Composable
|
||||
internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
|
||||
|
|
@ -76,7 +82,8 @@ internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modif
|
|||
hasPending = false,
|
||||
options = { ref ->
|
||||
Icon(
|
||||
modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
|
||||
modifier = Modifier
|
||||
.constrainAsOptionsItem(scope = this, ref),
|
||||
painter = painterResource(id = R.drawable.ic_drag_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
|
|
@ -251,7 +258,7 @@ private fun BaseSurface(
|
|||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68),
|
||||
modifier = modifier.defaultMinSize(minHeight = TOKEN_ITEM_HEIGHT),
|
||||
color = TangemTheme.colors.background.primary,
|
||||
onClick = onClick ?: {},
|
||||
enabled = onClick != null,
|
||||
|
|
|
|||
|
|
@ -7,17 +7,15 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
internal sealed interface NetworkGroupState {
|
||||
val id: String
|
||||
val networkName: String
|
||||
val tokens: ImmutableList<TokenItemState>
|
||||
|
||||
data class Draggable(
|
||||
override val id: String,
|
||||
override val networkName: String,
|
||||
override val tokens: ImmutableList<TokenItemState.Draggable>,
|
||||
) : NetworkGroupState
|
||||
|
||||
data class Content(
|
||||
override val id: String,
|
||||
override val networkName: String,
|
||||
override val tokens: ImmutableList<TokenItemState>,
|
||||
val tokens: ImmutableList<TokenItemState>,
|
||||
) : NetworkGroupState
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.common.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal sealed interface TokenListState {
|
||||
|
||||
data class GroupedByNetwork(
|
||||
val groups: ImmutableList<NetworkGroupState>,
|
||||
) : TokenListState
|
||||
|
||||
data class Ungrouped(
|
||||
val tokens: ImmutableList<TokenItemState>,
|
||||
) : TokenListState
|
||||
}
|
||||
|
|
@ -1,88 +1,255 @@
|
|||
// TODO: Remove after components implementation
|
||||
@file:Suppress("UNUSED_PARAMETER")
|
||||
|
||||
package com.tangem.feature.wallet.presentation.organizetokens
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.foundation.lazy.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.AppBarDefaults
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.BackgroundActionButton
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenListState
|
||||
import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem
|
||||
import com.tangem.feature.wallet.presentation.common.component.DraggableTokenItem
|
||||
|
||||
@Composable
|
||||
internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder) {
|
||||
val tokensListState = rememberLazyListState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopBar(state.header)
|
||||
TopBar(state.header, tokensListState)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
TokenList(
|
||||
state = state.tokens,
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
listState = tokensListState,
|
||||
state = state.itemsState,
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
floatingActionButton = {
|
||||
Actions(state.actions)
|
||||
},
|
||||
contentColor = TangemTheme.colors.background.primary,
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenList(state: TokenListState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TokenListState.GroupedByNetwork -> {
|
||||
/* [REDACTED_TODO_COMMENT] */
|
||||
}
|
||||
is TokenListState.Ungrouped -> {
|
||||
/* [REDACTED_TODO_COMMENT] */
|
||||
private fun TokenList(listState: LazyListState, state: OrganizeTokensListState, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing92,
|
||||
),
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = state.items,
|
||||
key = { _, item -> item.id },
|
||||
) { index, item ->
|
||||
DraggableItem(
|
||||
item = item,
|
||||
index = index,
|
||||
lastItemIndex = state.items.lastIndex,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
BottomGradient(modifier = Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun TopBar(config: OrganizeTokensStateHolder.HeaderConfig, modifier: Modifier = Modifier) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
private fun DraggableItem(index: Int, item: DraggableItem, lastItemIndex: Int) {
|
||||
val itemModifier = Modifier
|
||||
.clipFirstAndLastItems(index, lastItemIndex)
|
||||
|
||||
when (item) {
|
||||
is DraggableItem.GroupHeader -> DraggableNetworkGroupItem(
|
||||
modifier = itemModifier,
|
||||
state = item.groupState,
|
||||
)
|
||||
is DraggableItem.Token -> DraggableTokenItem(
|
||||
modifier = itemModifier,
|
||||
state = item.tokenItemState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomGradient(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size116)
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
TangemTheme.colors.background.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopBar(
|
||||
config: OrganizeTokensStateHolder.HeaderConfig,
|
||||
tokensListState: LazyListState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isElevationEnabled by remember {
|
||||
derivedStateOf {
|
||||
tokensListState.firstVisibleItemScrollOffset > 0
|
||||
}
|
||||
}
|
||||
val elevation by animateDpAsState(
|
||||
targetValue = if (isElevationEnabled) AppBarDefaults.TopAppBarElevation else TangemTheme.dimens.elevation0,
|
||||
label = "top_bar_shadow_elevation",
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.shadow(elevation)
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size56),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
Text(
|
||||
text = "Organize tokens", // TODO: Move to resources
|
||||
text = stringResource(id = R.string.organize_tokens_title),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
)
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
titleContentColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size56),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
BackgroundActionButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.organize_tokens_sort_by_balance),
|
||||
iconResId = R.drawable.ic_sort_24,
|
||||
onClick = config.onSortByBalanceClick,
|
||||
)
|
||||
BackgroundActionButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.organize_tokens_group),
|
||||
iconResId = R.drawable.ic_group_24,
|
||||
onClick = config.onGroupByNetworkClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Actions(config: OrganizeTokensStateHolder.ActionsConfig, modifier: Modifier = Modifier) {
|
||||
// TODO: Implement list apply and cancel actions
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
SecondaryButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.common_cancel),
|
||||
onClick = config.onCancelClick,
|
||||
)
|
||||
PrimaryButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = stringResource(id = R.string.common_apply),
|
||||
onClick = config.onApplyClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed {
|
||||
when (index) {
|
||||
0 -> {
|
||||
this
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens.radius16,
|
||||
topEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
lastItemIndex -> {
|
||||
this
|
||||
.clip(
|
||||
RoundedCornerShape(
|
||||
bottomStart = TangemTheme.dimens.radius16,
|
||||
bottomEnd = TangemTheme.dimens.radius16,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun OrganizeTokensScreenPreview_Light() {
|
||||
private fun OrganizeTokensScreenPreview_Light(
|
||||
@PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensStateHolder,
|
||||
) {
|
||||
TangemTheme {
|
||||
OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState)
|
||||
OrganizeTokensScreen(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun OrganizeTokensScreenPreview_Dark() {
|
||||
private fun OrganizeTokensScreenPreview_Dark(
|
||||
@PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensStateHolder,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState)
|
||||
OrganizeTokensScreen(state)
|
||||
}
|
||||
}
|
||||
|
||||
private class OrganizeTokensStateProvider : CollectionPreviewParameterProvider<OrganizeTokensStateHolder>(
|
||||
collection = listOf(
|
||||
WalletPreviewData.organizeTokensState,
|
||||
WalletPreviewData.groupedOrganizeTokensState,
|
||||
),
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
package com.tangem.feature.wallet.presentation.organizetokens
|
||||
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenListState
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal data class OrganizeTokensStateHolder(
|
||||
val tokens: TokenListState,
|
||||
val header: HeaderConfig,
|
||||
val itemsState: OrganizeTokensListState,
|
||||
val actions: ActionsConfig,
|
||||
) {
|
||||
|
||||
|
|
@ -17,4 +21,45 @@ internal data class OrganizeTokensStateHolder(
|
|||
val onApplyClick: () -> Unit,
|
||||
val onCancelClick: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed class OrganizeTokensListState {
|
||||
abstract val items: ImmutableList<DraggableItem>
|
||||
|
||||
data class GroupedByNetwork(
|
||||
override val items: ImmutableList<DraggableItem>,
|
||||
) : OrganizeTokensListState()
|
||||
|
||||
data class Ungrouped(
|
||||
override val items: ImmutableList<DraggableItem.Token>,
|
||||
) : OrganizeTokensListState()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun updateItems(update: (List<DraggableItem>) -> List<DraggableItem>): OrganizeTokensListState {
|
||||
val updatedItems = update(this.items).toPersistentList()
|
||||
|
||||
return when (this) {
|
||||
is GroupedByNetwork -> this.copy(items = updatedItems)
|
||||
is Ungrouped -> this.copy(items = updatedItems as ImmutableList<DraggableItem.Token>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed interface DraggableItem {
|
||||
val id: String
|
||||
|
||||
data class GroupHeader(
|
||||
val groupState: NetworkGroupState.Draggable,
|
||||
) : DraggableItem {
|
||||
override val id: String = groupState.id
|
||||
}
|
||||
|
||||
data class Token(
|
||||
val tokenItemState: TokenItemState.Draggable,
|
||||
val groupId: String,
|
||||
) : DraggableItem {
|
||||
override val id: String = tokenItemState.id
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,14 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder.HeaderConfig
|
||||
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import javax.inject.Inject
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
// FIXME: Implemented with preview data
|
||||
@HiltViewModel
|
||||
internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
|
|
@ -18,5 +21,26 @@ internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
|
|||
var uiState: OrganizeTokensStateHolder by mutableStateOf(getInitialState())
|
||||
private set
|
||||
|
||||
private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState
|
||||
private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState.copy(
|
||||
itemsState = OrganizeTokensListState.Ungrouped(
|
||||
items = WalletPreviewData.draggableTokens,
|
||||
),
|
||||
header = HeaderConfig(
|
||||
onSortByBalanceClick = { /* no-op */ },
|
||||
onGroupByNetworkClick = this::toggleTokensByNetworkGrouping,
|
||||
),
|
||||
)
|
||||
|
||||
private fun toggleTokensByNetworkGrouping() {
|
||||
val newListState = when (val itemsState = uiState.itemsState) {
|
||||
is OrganizeTokensListState.GroupedByNetwork -> OrganizeTokensListState.Ungrouped(
|
||||
items = itemsState.items.filterIsInstance<DraggableItem.Token>().toPersistentList(),
|
||||
)
|
||||
is OrganizeTokensListState.Ungrouped -> OrganizeTokensListState.GroupedByNetwork(
|
||||
items = WalletPreviewData.draggableItems,
|
||||
)
|
||||
}
|
||||
|
||||
uiState = uiState.copy(itemsState = newListState)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.presentation.WalletFragment
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen
|
||||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
|
||||
|
|
@ -25,24 +24,22 @@ internal class DefaultWalletRouter : InnerWalletRouter {
|
|||
|
||||
@Composable
|
||||
override fun Initialize() {
|
||||
TangemTheme {
|
||||
NavHost(
|
||||
navController = rememberNavController().apply { navController = this },
|
||||
startDestination = WalletScreens.WALLET.name,
|
||||
) {
|
||||
composable(WalletScreens.WALLET.name) {
|
||||
val viewModel = hiltViewModel<WalletViewModel>().apply { router = this@DefaultWalletRouter }
|
||||
WalletScreen(state = viewModel.uiState)
|
||||
}
|
||||
NavHost(
|
||||
navController = rememberNavController().apply { navController = this },
|
||||
startDestination = WalletScreens.WALLET.name,
|
||||
) {
|
||||
composable(WalletScreens.WALLET.name) {
|
||||
val viewModel = hiltViewModel<WalletViewModel>().apply { router = this@DefaultWalletRouter }
|
||||
WalletScreen(state = viewModel.uiState)
|
||||
}
|
||||
|
||||
composable(WalletScreens.ORGANIZE_TOKENS.name) {
|
||||
BackHandler(onBack = ::popBackStack)
|
||||
composable(WalletScreens.ORGANIZE_TOKENS.name) {
|
||||
BackHandler(onBack = ::popBackStack)
|
||||
|
||||
val viewModel: OrganizeTokensViewModel = hiltViewModel<OrganizeTokensViewModel>()
|
||||
.apply { router = this@DefaultWalletRouter }
|
||||
val viewModel: OrganizeTokensViewModel = hiltViewModel<OrganizeTokensViewModel>()
|
||||
.apply { router = this@DefaultWalletRouter }
|
||||
|
||||
OrganizeTokensScreen(state = viewModel.uiState)
|
||||
}
|
||||
OrganizeTokensScreen(state = viewModel.uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue