Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-24 00:07:05 +03:00
commit edec3fcdff
391 changed files with 10988 additions and 3221 deletions

View file

@ -365,7 +365,12 @@ internal class WalletModel @Inject constructor(
value = info,
onClickIssue = ::issueOrder,
onClickKyc = innerWalletRouter::openTangemPayOnboarding,
openDetails = innerWalletRouter::openTangemPayDetails,
openDetails = { config ->
innerWalletRouter.openTangemPayDetails(
userWalletId = stateHolder.getSelectedWalletId(),
config = config,
)
},
),
)
}

View file

@ -24,7 +24,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.core.lce.Lce
@ -157,7 +157,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
override fun onSendClick(
@ -360,7 +360,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
return@launch
}
saveCryptoCurrenciesUseCase(accountId = accountId, remove = cryptoCurrencyStatus.currency)
manageCryptoCurrenciesUseCase(accountId = accountId, remove = cryptoCurrencyStatus.currency)
} else {
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
}
@ -507,7 +507,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
appRouter.push(
AppRoute.Staking(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
cryptoCurrency = cryptoCurrency,
yieldId = yield?.id ?: return@launch,
),
)

View file

@ -106,6 +106,28 @@ internal object WalletScreenPreviewData {
),
)
private val emptyPortfolioContentState = WalletTokensListState.ContentState.PortfolioContent(
items = persistentListOf(
TokensListItemUM.Portfolio(
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
isExpanded = false,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem
.copy(iconState = AccountItemPreviewData.accountLetterIcon),
),
TokensListItemUM.Portfolio(
tokens = persistentListOf(),
isExpanded = true,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem,
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
isEnabled = true,
onClick = {},
),
)
private val noteLockedCard by lazy {
WalletCardState.LockedContent(
id = UserWalletId(stringValue = "1"),
@ -208,4 +230,12 @@ internal object WalletScreenPreviewData {
multiWalletState.copy(tokensListState = portfolioContentState),
),
)
internal val accountScreenWithEmptyTokensState =
walletScreenState.copy(
wallets = persistentListOf(
singleWalletLockedState,
multiWalletState.copy(tokensListState = emptyPortfolioContentState),
),
)
}

View file

@ -109,8 +109,8 @@ internal class DefaultWalletRouter @Inject constructor(
router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding))
}
override fun openTangemPayDetails(config: TangemPayDetailsConfig) {
router.push(AppRoute.TangemPayDetails(config))
override fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig) {
router.push(AppRoute.TangemPayDetails(userWalletId = userWalletId, config = config))
}
override fun openYieldSupplyBottomSheet(

View file

@ -60,7 +60,7 @@ internal interface InnerWalletRouter {
fun openTangemPayOnboarding()
fun openTangemPayDetails(config: TangemPayDetailsConfig)
fun openTangemPayDetails(userWalletId: UserWalletId, config: TangemPayDetailsConfig)
/** Open BS abput yield supply active and all money deposited in AAVE */
fun openYieldSupplyBottomSheet(

View file

@ -100,6 +100,7 @@ internal class TokenListStateConverter(
appCurrency = appCurrency,
account = account,
onItemClick = onItemClick,
priceChangeLce = this.priceChangeLce,
)
val accountItem = converter.convert(tokenList.totalFiatBalance)
val tokenConverter = tokenStatusConverter(account.accountId)

View file

@ -69,6 +69,7 @@ import com.tangem.core.ui.utils.moveTo
import com.tangem.core.ui.utils.toPx
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenState
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenWithEmptyTokensState
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder
@ -142,6 +143,7 @@ private fun WalletContent(
*/
val selectedWalletIndex by remember(state.selectedWalletIndex) { mutableIntStateOf(state.selectedWalletIndex) }
val selectedWallet = state.wallets.getOrElse(selectedWalletIndex) { state.wallets[state.selectedWalletIndex] }
val selectedWalletChanged = rememberChangedOnce(selectedWalletIndex)
val listState = rememberLazyListState()
@ -239,6 +241,7 @@ private fun WalletContent(
contentItems(
state = selectedWallet,
selectedWalletChanged = selectedWalletChanged,
txHistoryItems = txHistoryItems,
isBalanceHidden = state.isHidingMode,
modifier = movableItemModifier,
@ -289,6 +292,14 @@ private fun WalletContent(
)
}
@Composable
private fun rememberChangedOnce(selectedWalletIndex: Int): Boolean {
var prev by remember { mutableIntStateOf(selectedWalletIndex) }
val changed = prev != selectedWalletIndex
SideEffect { prev = selectedWalletIndex }
return changed
}
@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -743,6 +754,7 @@ private class WalletScreenPreviewProvider : PreviewParameterProvider<WalletScree
walletScreenState,
walletScreenState.copy(selectedWalletIndex = 1),
accountScreenState.copy(selectedWalletIndex = 1),
accountScreenWithEmptyTokensState.copy(selectedWalletIndex = 1),
)
}
// endregion

View file

@ -22,10 +22,11 @@ internal fun LazyListScope.contentItems(
txHistoryItems: LazyPagingItems<TxHistoryState.TxHistoryItemState>?,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
selectedWalletChanged: Boolean,
) {
when (state) {
is WalletState.MultiCurrency -> {
tokensListItems(state.tokensListState, modifier, isBalanceHidden)
tokensListItems(state.tokensListState, modifier, isBalanceHidden, selectedWalletChanged)
}
is WalletState.SingleCurrency -> {
txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier)

View file

@ -27,6 +27,7 @@ internal fun LazyListScope.portfolioContentItems(
items: ImmutableList<TokensListItemUM.Portfolio>,
modifier: Modifier = Modifier,
isBalanceHidden: Boolean,
selectedWalletChanged: Boolean,
) {
items.forEachIndexed { index, item ->
portfolioTokensList(
@ -34,6 +35,7 @@ internal fun LazyListScope.portfolioContentItems(
modifier = modifier,
portfolioIndex = index,
isBalanceHidden = isBalanceHidden,
selectedWalletChanged = selectedWalletChanged,
)
}
}
@ -43,6 +45,7 @@ internal fun LazyListScope.portfolioTokensList(
modifier: Modifier,
portfolioIndex: Int,
isBalanceHidden: Boolean,
selectedWalletChanged: Boolean,
) {
val tokens = portfolio.tokens
val isExpanded = portfolio.isExpanded
@ -52,8 +55,28 @@ internal fun LazyListScope.portfolioTokensList(
modifier = modifier,
portfolioIndex = portfolioIndex,
isBalanceHidden = isBalanceHidden,
selectedWalletChanged = selectedWalletChanged,
)
if (!isExpanded) return
if (tokens.isEmpty()) {
item(
key = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}",
contentType = "$NON_CONTENT_TOKENS_LIST_KEY account-${portfolio.id}",
) {
NonContentItemContent(
modifier = Modifier
.animateItem()
.roundedShapeItemDecoration(
radius = TangemTheme.dimens.radius14,
currentIndex = 1,
lastIndex = 1,
backgroundColor = TangemTheme.colors.background.primary,
)
.padding(vertical = TangemTheme.dimens.spacing28),
)
}
return
}
itemsIndexed(
items = tokens,
key = { _, item -> item.id },
@ -63,13 +86,14 @@ internal fun LazyListScope.portfolioTokensList(
val lastIndex = tokens.lastIndex.inc()
val isPreview = LocalInspectionMode.current
val appear = remember {
MutableTransitionState(isPreview).apply { targetState = true }
MutableTransitionState(selectedWalletChanged || isPreview).apply { targetState = true }
}
SlideInItemVisibility(
modifier = modifier
.testModifier(indexWithHeader)
.animateItem()
.roundedShapeItemDecoration(
radius = TangemTheme.dimens.radius14,
currentIndex = indexWithHeader,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
@ -92,10 +116,17 @@ private fun LazyListScope.portfolioItem(
modifier: Modifier,
portfolioIndex: Int,
isBalanceHidden: Boolean,
selectedWalletChanged: Boolean,
) {
val tokens = portfolio.tokens
val isExpanded = portfolio.isExpanded
val lastIndex = when {
isExpanded && tokens.isEmpty() -> 1
isExpanded -> tokens.lastIndex.inc()
else -> 0
}
item(
key = "account-${portfolio.id}-isExpanded$isExpanded",
contentType = "account-isExpanded$isExpanded",
@ -105,27 +136,24 @@ private fun LazyListScope.portfolioItem(
.animateItem()
.roundedShapeItemDecoration(
currentIndex = 0,
lastIndex = if (isExpanded) tokens.lastIndex.inc() else 0,
radius = TangemTheme.dimens.radius14,
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
)
val isPreview = LocalInspectionMode.current
val appear = remember {
MutableTransitionState(isPreview).apply { targetState = true }
MutableTransitionState(selectedWalletChanged || isPreview || tokens.isEmpty())
.apply { targetState = true }
}
if (isExpanded) {
SlideInItemVisibility(
modifier = anchorModifier,
visibleState = appear,
) {
val modifier = if (portfolio.tokens.isEmpty()) {
Modifier.padding(vertical = 8.dp)
} else {
Modifier.padding(top = 8.dp)
}
PortfolioListItem(
state = portfolio,
isBalanceHidden = isBalanceHidden,
modifier = modifier,
modifier = Modifier.padding(vertical = 8.dp),
)
}
} else {

View file

@ -8,6 +8,7 @@ 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.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
@ -25,7 +26,7 @@ import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import kotlinx.collections.immutable.ImmutableList
private const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST"
internal const val NON_CONTENT_TOKENS_LIST_KEY = "NON_CONTENT_TOKENS_LIST"
/**
* LazyList extension for [WalletTokensListState]
@ -39,12 +40,14 @@ internal fun LazyListScope.tokensListItems(
state: WalletTokensListState,
modifier: Modifier = Modifier,
isBalanceHidden: Boolean,
selectedWalletChanged: Boolean,
) {
when (state) {
is WalletTokensListState.ContentState.PortfolioContent -> portfolioContentItems(
items = state.items,
isBalanceHidden = isBalanceHidden,
modifier = modifier,
selectedWalletChanged = selectedWalletChanged,
)
is WalletTokensListState.ContentState.Content,
is WalletTokensListState.ContentState.Loading,
@ -89,27 +92,34 @@ private fun LazyListScope.nonContentItem(modifier: Modifier = Modifier) {
key = NON_CONTENT_TOKENS_LIST_KEY,
contentType = NON_CONTENT_TOKENS_LIST_KEY,
) {
Column(
NonContentItemContent(
modifier = modifier
.animateItem(fadeInSpec = null, fadeOutSpec = null)
.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 = stringResourceSafe(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.caption2,
)
}
@Composable
internal fun NonContentItemContent(modifier: Modifier = Modifier) {
Column(
modifier = modifier,
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 = stringResourceSafe(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.caption2,
)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.feature.wallet.utils
import arrow.core.Either
import com.tangem.common.ui.userwallet.converter.ArtworkUMConverter
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.components.artwork.ArtworkUM
@ -47,12 +46,11 @@ class DefaultUserWalletImageFetcher @Inject constructor(
override fun walletImage(walletId: UserWalletId, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> =
getUserWalletUseCase.invokeFlow(walletId)
.transform { either ->
if (either.isLeft()) {
emit(UserWalletItemUM.ImageState.Loading)
} else if (either is Either.Right) {
emitAll(walletImage(either.value, size))
}
.transform {
it.fold(
ifLeft = { emit(UserWalletItemUM.ImageState.Loading) },
ifRight = { wallet -> emitAll(walletImage(wallet, size)) },
)
}
.distinctUntilChanged()