Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-14 17:19:17 +08:00
parent c203e8cc52
commit a62354d5fb
13 changed files with 130 additions and 33 deletions

View file

@ -160,6 +160,7 @@
<string name="initial_message_tap_header">Tap the card</string>
<string name="internal_error_wallet_manager_not_found">Internal error: wallet manager not found</string>
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card to enter</string>
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens.</string>
<string name="main_get_bonus_subtitle">You have completed all of the lessons, and are now eligible to receive your 1INCH tokens</string>
<plurals name="main_learn_subtitle">
<item quantity="one">Complete three lessons and receive %d 1INCH token to your wallet</item>

View file

@ -105,11 +105,13 @@ data class TangemDimens internal constructor(
val spacing38: Dp = 38.dp,
val spacing40: Dp = 40.dp,
val spacing44: Dp = 44.dp,
val spacing48: Dp = 48.dp,
val spacing50: Dp = 50.dp,
val spacing52: Dp = 52.dp,
val spacing54: Dp = 54.dp,
val spacing56: Dp = 56.dp,
val spacing92: Dp = 92.dp,
val spacing96: Dp = 96.dp,
val spacing154: Dp = 154.dp,
// endregion Spacing
)

View file

@ -11,7 +11,6 @@ internal class TokenListErrorConverter(
private val inProgressStateConverter: InProgressStateConverter,
) : Converter<TokenListError, OrganizeTokensState> {
// TODO: [REDACTED_JIRA]
override fun convert(value: TokenListError): OrganizeTokensState {
return inProgressStateConverter.convertBack(currentState())
}

View file

@ -11,7 +11,6 @@ internal class TokenListSortingErrorConverter(
private val inProgressStateConverter: InProgressStateConverter,
) : Converter<TokenListSortingError, OrganizeTokensState> {
// TODO: [REDACTED_JIRA]
override fun convert(value: TokenListSortingError): OrganizeTokensState {
return inProgressStateConverter.convertBack(currentState())
}

View file

@ -10,18 +10,26 @@ import kotlinx.collections.immutable.persistentListOf
/**
* Wallet tokens list state
*
* @property items content items
* @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked
*
[REDACTED_AUTHOR]
*/
internal sealed class WalletTokensListState(
open val items: ImmutableList<TokensListItemState>,
open val onOrganizeTokensClick: (() -> Unit)?,
) {
internal sealed class WalletTokensListState {
/** Empty token list state */
object Empty : WalletTokensListState()
/**
* Wallet content token list state
*
* @property items content items
* @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked
*/
sealed class ContentState(
open val items: ImmutableList<TokensListItemState>,
open val onOrganizeTokensClick: (() -> Unit)?,
) : WalletTokensListState()
/** Loading content state */
object Loading : WalletTokensListState(
object Loading : ContentState(
items = persistentListOf(
TokensListItemState.Token(state = TokenItemState.Loading(id = FIRST_LOADING_TOKEN_ID)),
TokensListItemState.Token(state = TokenItemState.Loading(id = SECOND_LOADING_TOKEN_ID)),
@ -38,11 +46,11 @@ internal sealed class WalletTokensListState(
data class Content(
override val items: ImmutableList<TokensListItemState>,
override val onOrganizeTokensClick: (() -> Unit)?,
) : WalletTokensListState(items, onOrganizeTokensClick)
) : ContentState(items, onOrganizeTokensClick)
/** Locked content state */
object Locked :
WalletTokensListState(
ContentState(
items = persistentListOf(
TokensListItemState.NetworkGroupTitle(value = TextReference.Res(id = R.string.main_tokens)),
TokensListItemState.Token(state = TokenItemState.Loading(id = LOCKED_TOKEN_ID)),

View file

@ -22,6 +22,7 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeButton
@ -106,7 +107,10 @@ private fun WalletContent(state: WalletState.ContentState) {
contentItems(state = state, txHistoryItems = txHistoryItems, modifier = movableItemModifier)
if (state is WalletMultiCurrencyState) {
organizeButton(onClick = state.tokensListState.onOrganizeTokensClick, modifier = itemModifier)
val tokensListState = state.tokensListState
if (tokensListState is WalletTokensListState.ContentState) {
organizeButton(onClick = tokensListState.onOrganizeTokensClick, modifier = itemModifier)
}
}
}

View file

@ -1,11 +1,26 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import kotlinx.collections.immutable.ImmutableList
private const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST"
/**
* LazyList extension for [WalletTokensListState]
@ -15,10 +30,20 @@ import com.tangem.feature.wallet.presentation.wallet.state.components.WalletToke
*
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalFoundationApi::class)
internal fun LazyListScope.tokensListItems(state: WalletTokensListState, modifier: Modifier = Modifier) {
when (state) {
is WalletTokensListState.ContentState -> contentItems(items = state.items, modifier = modifier)
WalletTokensListState.Empty -> nonContentItem(modifier = modifier)
}
}
@OptIn(ExperimentalFoundationApi::class)
private fun LazyListScope.contentItems(
items: ImmutableList<WalletTokensListState.TokensListItemState>,
modifier: Modifier = Modifier,
) {
itemsIndexed(
items = state.items,
items = items,
key = { _, item ->
when (item) {
is WalletTokensListState.TokensListItemState.NetworkGroupTitle -> item.value.hashCode()
@ -33,9 +58,40 @@ internal fun LazyListScope.tokensListItems(state: WalletTokensListState, modifie
.animateItemPlacement()
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = state.items.lastIndex,
lastIndex = items.lastIndex,
),
)
},
)
}
@OptIn(ExperimentalFoundationApi::class)
private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) {
item(
key = NON_CONTENT_TOKENS_LIST_KEY,
contentType = NON_CONTENT_TOKENS_LIST_KEY,
) {
Column(
modifier = modifier
.animateItemPlacement()
.padding(top = TangemTheme.dimens.spacing96),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing16),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
painter = painterResource(id = R.drawable.ic_empty_64),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size64),
tint = TangemTheme.colors.icon.inactive,
)
Text(
text = stringResource(id = R.string.main_empty_tokens_list_message),
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing48),
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.caption,
)
}
}
}

View file

@ -12,7 +12,6 @@ internal class TokenListErrorConverter(
private val currentStateProvider: Provider<WalletState>,
) : Converter<TokenListError, WalletMultiCurrencyState.Content> {
// TODO: [REDACTED_JIRA]
override fun convert(value: TokenListError): WalletMultiCurrencyState.Content {
return requireNotNull(currentStateProvider() as? WalletMultiCurrencyState.Content).copy(
tokensListState = WalletTokensListState.Content(items = persistentListOf(), onOrganizeTokensClick = null),

View file

@ -27,18 +27,28 @@ internal class TokenListToContentItemsConverter(
)
override fun convert(value: TokenList): WalletTokensListState {
return WalletTokensListState.Content(
items = when (value) {
is TokenList.GroupedByNetwork -> value.mapToMultiCurrencyItems()
is TokenList.Ungrouped -> value.mapToMultiCurrencyItems()
is TokenList.NotInitialized -> getLoadingMultiCurrencyTokens()
},
onOrganizeTokensClick = if (value.totalFiatBalance is TokenList.FiatBalance.Loaded) {
clickIntents::onOrganizeTokensClick
} else {
null
},
)
val isEmptyList = when (value) {
is TokenList.GroupedByNetwork -> value.groups.isEmpty()
is TokenList.NotInitialized -> true
is TokenList.Ungrouped -> value.currencies.isEmpty()
}
return if (isEmptyList) {
WalletTokensListState.Empty
} else {
WalletTokensListState.Content(
items = when (value) {
is TokenList.GroupedByNetwork -> value.mapToMultiCurrencyItems()
is TokenList.Ungrouped -> value.mapToMultiCurrencyItems()
is TokenList.NotInitialized -> getLoadingMultiCurrencyTokens()
},
onOrganizeTokensClick = if (value.totalFiatBalance is TokenList.FiatBalance.Loaded) {
clickIntents::onOrganizeTokensClick
} else {
null
},
)
}
}
private fun TokenList.GroupedByNetwork.mapToMultiCurrencyItems(): PersistentList<TokensListItemState> {

View file

@ -63,7 +63,9 @@ internal class TokenListToWalletStateConverter(
}
private fun WalletState.getRefreshingStatus(): Boolean {
return if (this is WalletMultiCurrencyState.Content) {
return if (this is WalletMultiCurrencyState.Content &&
this.tokensListState is WalletTokensListState.ContentState
) {
tokensListState.items.any { tokensListItemState ->
tokensListItemState is WalletTokensListState.TokensListItemState.Token &&
tokensListItemState.state is TokenItemState.Loading

View file

@ -118,7 +118,9 @@ internal class WalletNotificationsListFactory(
}
return currentStateProvider().let { state ->
state is WalletMultiCurrencyState.Content && state.tokensListState.items.any(isUnreachableState)
state is WalletMultiCurrencyState.Content &&
state.tokensListState is WalletTokensListState.ContentState &&
state.tokensListState.items.any(isUnreachableState)
}
}

View file

@ -352,10 +352,12 @@ internal class WalletViewModel @Inject constructor(
// Check the special components
return when (this) {
is WalletMultiCurrencyState -> {
tokensListState is WalletTokensListState.Loading ||
tokensListState.items
val hasLoadingTokens = tokensListState is WalletTokensListState.ContentState &&
(tokensListState as WalletTokensListState.ContentState).items
.filterIsInstance<WalletTokensListState.TokensListItemState.Token>()
.any { it.state is TokenItemState.Loading }
tokensListState is WalletTokensListState.Loading || hasLoadingTokens
}
is WalletSingleCurrencyState -> {
txHistoryState is TxHistoryState.Loading || marketPriceBlockState is MarketPriceBlockState.Loading

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<group>
<clip-path android:pathData="M0,0h64v64h-64z" />
<path
android:pathData="M55.941,38.668C55.64,37.404 55.239,36.109 54.738,34.798C52.628,38.647 49.782,42.465 46.265,45.981C42.749,49.497 38.931,52.343 35.082,54.454C36.394,54.954 37.688,55.356 38.952,55.657C45.197,57.144 50.274,56.114 53.336,53.052C56.399,49.99 57.428,44.913 55.941,38.668ZM44.498,44.213C48.408,40.303 51.415,36.036 53.455,31.839C51.415,27.641 48.408,23.375 44.497,19.465C40.587,15.554 36.321,12.547 32.123,10.507C27.925,12.547 23.659,15.554 19.749,19.465C15.838,23.375 12.831,27.641 10.791,31.839C12.831,36.036 15.838,40.303 19.749,44.213C23.659,48.124 27.925,51.131 32.123,53.171C36.321,51.131 40.587,48.124 44.498,44.213ZM55.104,54.82C60.196,49.728 60.286,40.89 56.215,31.839C60.286,22.787 60.196,13.95 55.104,8.858C50.012,3.766 41.175,3.676 32.123,7.747C23.071,3.676 14.234,3.766 9.142,8.858C4.05,13.95 3.96,22.787 8.031,31.839C3.96,40.89 4.05,49.728 9.142,54.82C14.234,59.912 23.071,60.002 32.123,55.931C41.175,60.002 50.012,59.912 55.104,54.82ZM38.952,8.021C37.688,8.322 36.394,8.723 35.082,9.223C38.931,11.334 42.749,14.18 46.265,17.697C49.782,21.213 52.628,25.031 54.738,28.88C55.239,27.568 55.64,26.274 55.941,25.01C57.428,18.764 56.399,13.688 53.336,10.626C50.274,7.563 45.197,6.534 38.952,8.021ZM25.294,8.021C26.558,8.322 27.853,8.723 29.164,9.223C25.315,11.334 21.497,14.18 17.981,17.697C14.465,21.213 11.619,25.031 9.508,28.88C9.008,27.568 8.606,26.274 8.305,25.01C6.818,18.764 7.847,13.688 10.91,10.626C13.972,7.563 19.049,6.534 25.294,8.021ZM8.305,38.668C8.606,37.404 9.008,36.109 9.508,34.798C11.619,38.647 14.465,42.465 17.981,45.981C21.497,49.497 25.315,52.343 29.164,54.454C27.853,54.954 26.558,55.356 25.294,55.657C19.049,57.144 13.972,56.114 10.91,53.052C7.847,49.99 6.818,44.913 8.305,38.668ZM32.853,27.774L32,22L31.147,27.774C30.889,29.519 29.519,30.889 27.774,31.147L22,32L27.774,32.853C29.519,33.111 30.889,34.481 31.147,36.225L32,42L32.853,36.225C33.111,34.481 34.481,33.111 36.225,32.853L42,32L36.225,31.147C34.481,30.889 33.111,29.519 32.853,27.774Z"
android:fillColor="#C9C9C9"
android:fillType="evenOdd" />
</group>
</vector>