Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-01 20:05:51 +03:00
commit d0b35bc331
680 changed files with 26556 additions and 2709 deletions

View file

@ -96,6 +96,7 @@ internal fun OrganizeTokensContent(
onClick = { isShowDropdownMenu = true },
ghostModeProgress = 0f,
),
modifier = Modifier.testTag(OrganizeTokensScreenTestTags.MENU_BUTTON),
type = TangemTopBarType.BottomSheet,
)
OrganizeDropDownMenu(
@ -161,8 +162,7 @@ private fun TokenList(
modifier = Modifier
.align(Alignment.TopCenter)
.reorderable(reorderableListState)
.testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST)
.hazeSourceTangem(zIndex = 1f),
.testTag(OrganizeTokensScreenTestTags.TOKENS_LAZY_LIST),
state = reorderableListState.listState,
contentPadding = listContentPadding,
) {
@ -221,7 +221,7 @@ private fun LazyItemScope.DraggableItem(
when (item) {
is OrganizeRowItemUM.Network -> TangemHeaderRow(
modifier = modifierWithBackground,
modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.GROUP_TITLE_ITEM),
reorderableState = reorderableState,
headerRowUM = item.headerRowUM,
)
@ -231,7 +231,7 @@ private fun LazyItemScope.DraggableItem(
isBalanceHidden = isBalanceHidden,
)
is OrganizeRowItemUM.Token -> OrganizeTokenRow(
modifier = modifierWithBackground,
modifier = modifierWithBackground.testTag(OrganizeTokensScreenTestTags.TOKEN_LIST_ITEM),
tokenRowUM = item.tokenRowUM,
reorderableState = reorderableState,
isBalanceHidden = isBalanceHidden,
@ -262,11 +262,15 @@ private fun BoxScope.BottomButtons(organizeTokensUM: OrganizeTokensUM) {
) {
TangemButton(
buttonUM = organizeTokensUM.cancelButton,
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.testTag(OrganizeTokensScreenTestTags.CANCEL_BUTTON),
)
TangemButton(
buttonUM = organizeTokensUM.applyButton,
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.testTag(OrganizeTokensScreenTestTags.APPLY_BUTTON),
)
}
}

View file

@ -14,6 +14,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import com.tangem.core.ui.test.BaseBottomSheetTestTags
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
@ -101,6 +103,7 @@ private fun TokenActionContextMenuContent(actions: ImmutableList<TokenActionButt
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
modifier = Modifier
.testTag(BaseBottomSheetTestTags.ACTION_BUTTON)
.clickable(
enabled = item.isEnabled,
onClick = {
@ -123,7 +126,9 @@ private fun TokenActionContextMenuContent(actions: ImmutableList<TokenActionButt
} else {
TangemTheme.colors2.graphic.neutral.primary
},
modifier = Modifier.size(TangemTheme.dimens2.x5),
modifier = Modifier
.size(TangemTheme.dimens2.x5)
.testTag(BaseBottomSheetTestTags.ACTION_ICON),
)
Text(
text = item.text.resolveReference(),

View file

@ -25,6 +25,7 @@ import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.details.TokenAction
import com.tangem.feature.wallet.child.managetokens.AddAndManageBottomSheetComponent
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent
import com.tangem.features.commonfeatures.api.addfunds.AddFundsComponent
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
import com.tangem.feature.wallet.child.tokenActions.DefaultTokenActionsComponent
import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent
@ -67,6 +68,7 @@ internal class WalletComponent @AssistedInject constructor(
private val networkSelectionComponentFactory: NetworkSelectionComponent.Factory,
private val tokenActionsComponentFactory: TokenActionsComponent.Factory,
private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory,
private val addFundsComponentFactory: AddFundsComponent.Factory,
private val designFeatureToggles: DesignFeatureToggles,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
@ -180,6 +182,15 @@ internal class WalletComponent @AssistedInject constructor(
),
)
}
is WalletDialogConfig.AddFunds -> {
addFundsComponentFactory.create(
context = childByContext(componentContext),
params = AddFundsComponent.Params(
launchMode = AddFundsComponent.LaunchMode.ChooseToken(dialogConfig.userWalletId),
onDismiss = model.innerWalletRouter.dialogNavigation::dismiss,
),
)
}
is WalletDialogConfig.AddAndManage -> {
AddAndManageBottomSheetComponent(
appComponentContext = childByContext(componentContext),

View file

@ -120,7 +120,9 @@ internal class DefaultWalletRouter @Inject constructor(
}
override fun openAddFunds(userWalletId: UserWalletId) {
router.push(AppRoute.AddFunds(userWalletId = userWalletId))
dialogNavigation.activate(
configuration = WalletDialogConfig.AddFunds(userWalletId = userWalletId),
)
}
override fun isWalletLastScreen(): Boolean {

View file

@ -62,6 +62,9 @@ internal sealed interface WalletDialogConfig {
val customerId: String,
) : WalletDialogConfig
@Serializable
data class AddFunds(val userWalletId: UserWalletId) : WalletDialogConfig
@Serializable
data class OrganizeTokens(val userWalletId: UserWalletId) : WalletDialogConfig

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
@ -34,11 +35,18 @@ internal class SetRefreshStateTransformer(
override fun transform(walletUM: WalletUM): WalletUM {
return when (walletUM) {
is WalletUM.Content -> walletUM.copy(
pullToRefreshConfig = walletUM.pullToRefreshConfig.toUpdatedState(isRefreshing),
tokensListUM = walletUM.tokensListUM.toUpdatedState(),
buttons = walletUM.enableButtons(),
)
is WalletUM.Content -> {
val tokensListUM = walletUM.tokensListUM.toUpdatedState()
walletUM.copy(
pullToRefreshConfig = walletUM.pullToRefreshConfig.toUpdatedState(isRefreshing),
tokensListUM = tokensListUM,
buttons = if (tokensListUM is WalletTokensListUM.Empty) {
walletUM.disableButtons()
} else {
walletUM.enableButtons()
},
)
}
is WalletUM.Locked -> walletUM
}
}

View file

@ -9,6 +9,7 @@ import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.*
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
import com.tangem.features.tangempay.entity.TangemPayMainUM
import com.tangem.utils.logging.TangemLogger
@ -60,11 +61,16 @@ internal class SetTokenListTransformer(
override fun transform(walletUM: WalletUM): WalletUM {
return when (walletUM) {
is WalletUM.Content -> {
val tokensListUM = toLoadedState()
walletUM.copy(
walletsBalanceUM = walletUM.walletsBalanceUM.toLoadedState2(),
tangemPayMainUM = walletUM.tangemPayMainUM.toLoadedState(),
tokensListUM = toLoadedState(),
buttons = walletUM.enableButtons(),
tokensListUM = tokensListUM,
buttons = if (tokensListUM is WalletTokensListUM.Empty) {
walletUM.disableButtons()
} else {
walletUM.enableButtons()
},
)
}
is WalletUM.Locked -> {

View file

@ -13,6 +13,7 @@ import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
import com.tangem.domain.models.kyc.KycStatus
import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.feature.wallet.child.wallet.model.intents.TangemPayIntents
import com.tangem.features.tangempay.entity.TangemPayMainUM
import com.tangem.utils.converter.Converter
@ -67,7 +68,7 @@ internal class TangemPayMainBlockConverter(
is PaymentAccountStatusValue.Loaded -> {
val card = statusValue.cards.firstOrNull() ?: return TangemPayMainUM.TemporaryUnavailable
TangemPayMainUM.Content(
subtitle = if (card.isReissuing) {
subtitle = if (card.state == TangemPayCardState.Reissuing) {
resourceReference(R.string.tangempay_status_replacing)
} else {
stringReference("*${card.lastDigits}")

View file

@ -193,20 +193,22 @@ internal class WalletTokenCurrencyItemConverter(
is CryptoCurrencyStatus.MissedDerivation -> TangemTokenRowUM.EndContentUM.Content(
text = stringReference(StringsSigns.DASH_SIGN),
)
is CryptoCurrencyStatus.Unreachable -> TangemTokenRowUM.EndContentUM.Content(
text = styledResourceReference(
id = R.string.common_unreachable,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) },
),
endIcons = persistentListOf(
TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.status.attention },
),
),
)
is CryptoCurrencyStatus.Unreachable,
is CryptoCurrencyStatus.NoAmount,
-> TangemTokenRowUM.EndContentUM.Empty
-> {
TangemTokenRowUM.EndContentUM.Content(
text = styledResourceReference(
id = R.string.common_unreachable,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) },
),
endIcons = persistentListOf(
TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.status.attention },
),
),
)
}
}
}

View file

@ -171,7 +171,7 @@ internal class WalletTokensListUMConverter(
} else {
R.drawable.ic_filter_24
}
return if (accountList.flattenCurrencies().size > 1 && !selectedWallet.isSingleWalletWithToken()) {
return if (accountList.flattenCurrencies().isNotEmpty() && !selectedWallet.isSingleWalletWithToken()) {
TangemButtonUM(
text = resourceReference(textRes),
isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading,

View file

@ -31,7 +31,6 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusState
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
@ -46,7 +45,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
import com.tangem.core.ui.components.background.northernlights.NorthernLightsBackground
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeader
@ -59,8 +57,8 @@ import com.tangem.core.ui.components.sheetscaffold.*
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingTopBar
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
import com.tangem.core.ui.extensions.softLayerShadow
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.softLayerShadow
import com.tangem.core.ui.res.*
import com.tangem.core.ui.utils.TangemSharedTransitionLayout
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData
@ -176,6 +174,7 @@ private fun WalletContent2(
state.wallets2.getOrNull(state.selectedWalletIndex)?.pullToRefreshConfig,
)
}
var subtitleBottom by remember { mutableStateOf(0.dp) }
BaseScaffoldWithMarkets(
modifier = modifier,
@ -240,6 +239,7 @@ private fun WalletContent2(
pullToRefreshState = pullToRefreshState,
pullToRefreshConfig = pullToRefreshConfig,
behavior = behavior,
topOffset = subtitleBottom + TangemTheme.dimens2.x2,
)
val overlay = TangemTheme.colors2.overlay.overlayPrimary
@ -306,6 +306,9 @@ private fun WalletContent2(
walletBalanceUM = currentWallet.walletsBalanceUM,
buttons = currentWallet.buttons,
isBalanceHidden = state.isHidingMode,
onSubtitleBottomChange = { newValue ->
if (newValue > subtitleBottom) subtitleBottom = maxOf(subtitleBottom, newValue)
},
)
},
body = {
@ -494,26 +497,14 @@ private fun BottomSheet(
.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
Box(modifier = Modifier.fillMaxWidth()) {
BottomFade(
gradientBrush = Brush.verticalGradient(
colors = listOf(
TangemTheme.colors2.shadow.min,
TangemTheme.colors2.shadow.max,
),
),
modifier = Modifier
.offset(y = TangemTheme.dimens2.x5.unaryMinus()),
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
TangemBottomSheetDraggableHeader()
Box(
modifier = Modifier
.fillMaxWidth()
.softLayerShadow(
radius = 16.dp,
radius = 8.dp,
spread = 0.dp,
color = Color.Black.copy(alpha = if (LocalIsInDarkTheme.current) .24f else .12f),
shape = shape,
offset = DpOffset(x = 0.dp, y = (-6).dp),

View file

@ -2,7 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import com.tangem.core.ui.ds.button.TangemButton
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
import com.tangem.features.tangempay.entity.TangemPayMainUM
@ -25,9 +29,14 @@ internal fun LazyListScope.organizeTokens2(state: WalletUM, itemModifier: Modifi
key = "OrganizeTokensButton",
contentType = "OrganizeTokensButton",
) {
val testTag = if (organizeButton.text == resourceReference(R.string.main_add_and_manage_tokens)) {
MainScreenTestTags.ADD_AND_MANAGE_BUTTON
} else {
MainScreenTestTags.ORGANIZE_TOKENS_BUTTON
}
TangemButton(
buttonUM = organizeButton,
modifier = itemModifier,
modifier = itemModifier.testTag(testTag),
)
}
}

View file

@ -16,11 +16,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.TextShimmer
@ -32,11 +36,7 @@ import com.tangem.core.ui.ds.placeholder.TextPlaceholder
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
import com.tangem.core.ui.ds.topbar.collapsing.snapToExitUntilCollapsed
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.test.MainScreenTestTags
@ -58,14 +58,17 @@ internal fun WalletBalance(
buttons: ImmutableList<TangemButtonUM>,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
onSubtitleBottomChange: (Dp) -> Unit = {},
) {
val collapsedFraction = behavior.state.collapsedFraction
val alpha = 1f - collapsedFraction
val scale = alpha.coerceIn(MIN_SCALE, MAX_SCALE)
val density = LocalDensity.current
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.testTag(MainScreenTestTags.WALLET_LIST_ITEM)
.alpha(alpha)
.scale(scale)
.snapToExitUntilCollapsed(behavior)
@ -85,7 +88,15 @@ internal fun WalletBalance(
isBalanceHidden = isBalanceHidden,
)
SpacerH(TangemTheme.dimens2.x3)
SubtitleRow(walletBalanceUM = walletBalanceUM)
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val rawBottomPx = coordinates.boundsInRoot().bottom
if (rawBottomPx <= 0f) return@onGloballyPositioned
onSubtitleBottomChange(with(density) { rawBottomPx.toDp() })
},
) {
SubtitleRow(walletBalanceUM = walletBalanceUM)
}
}
SpacerH(TangemTheme.dimens2.x2)
ActionButtons(buttons)
@ -140,8 +151,12 @@ private fun SubtitleRow(walletBalanceUM: WalletBalanceUM, modifier: Modifier = M
text = walletBalanceUM.name,
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
modifier = Modifier.testTag(MainScreenTestTags.CARD_TITLE),
)
TangemDeviceIcon(
state = walletBalanceUM.deviceIcon,
modifier = Modifier.testTag(MainScreenTestTags.CARD_IMAGE),
)
TangemDeviceIcon(state = walletBalanceUM.deviceIcon)
}
}
}

View file

@ -337,6 +337,7 @@ private fun AdditionalInfo(
id = R.string.initial_wallet_sync_restore_progress,
formatArgs = wrappedList(animatedContent.progressPercent),
),
testTag = MainScreenTestTags.SYNC_PROGRESS_TEXT,
)
CircularProgressIndicator(
modifier = Modifier.size(TangemTheme.dimens.size16),
@ -353,14 +354,14 @@ private fun AdditionalInfo(
}
@Composable
private fun AdditionalInfoText(text: TextReference) {
private fun AdditionalInfoText(text: TextReference, testTag: String = MainScreenTestTags.DEVICES_COUNT) {
Text(
text = text.resolveReference(),
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = TangemTheme.typography.caption2,
modifier = Modifier.testTag(MainScreenTestTags.DEVICES_COUNT),
modifier = Modifier.testTag(testTag),
)
}

View file

@ -9,12 +9,14 @@ import androidx.compose.foundation.rememberOverscrollEffect
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.paging.compose.LazyPagingItems
import com.tangem.common.ui.notifications.notifications
import com.tangem.common.ui.notifications.notificationsCarousel
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.txHistoryItems
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.tokensListItems
@ -40,7 +42,7 @@ internal fun WalletListContent(
val itemModifier = movableItemModifier.padding(top = TangemTheme.dimens2.x3)
LazyColumn(
modifier = modifier,
modifier = modifier.testTag(MainScreenTestTags.SCREEN_CONTAINER),
state = listState,
contentPadding = contentPadding,
horizontalAlignment = Alignment.CenterHorizontally,

View file

@ -1,9 +1,11 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
@ -16,6 +18,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.ForceDarkTheme
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.WalletNotificationTestTags
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import kotlinx.collections.immutable.ImmutableList
@ -73,26 +76,35 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
subtitle = resourceReference(R.string.tangempay_onboarding_banner_description),
)
}
else -> {
Notification(
config = item.config,
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
iconTint = when (item) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null
},
subtitleColor = TangemTheme.colors.text.secondary,
)
}
else -> DefaultWalletNotification(item = item, modifier = modifier)
}
},
)
}
@Composable
private fun LazyItemScope.DefaultWalletNotification(item: WalletNotification, modifier: Modifier = Modifier) {
val itemModifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
val taggedModifier = when (item) {
is WalletNotification.AssetsDiscoveryCompleted ->
itemModifier.testTag(WalletNotificationTestTags.ASSETS_DISCOVERY_BANNER)
else -> itemModifier
}
Notification(
config = item.config,
modifier = taggedModifier,
iconTint = when (item) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
is WalletNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null
},
subtitleColor = TangemTheme.colors.text.secondary,
)
}
@Composable
private fun yieldBoostPromoTitle(): AnnotatedString {
val accent = TangemTheme.colors.text.accent

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.containers.pullToRefresh.getPullToRefreshIndicatorOffset
import com.tangem.core.ui.ds.TangemPagerIndicator
@ -23,7 +24,6 @@ import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
private const val MIN_SCALE = 0.75f
private const val MAX_SCALE = 1f
private const val WALLET_INDICATOR_OFFSET = 0.6f
@Composable
internal fun WalletPagerIndicator(
@ -31,6 +31,7 @@ internal fun WalletPagerIndicator(
behavior: TangemCollapsingAppBarBehavior,
pullToRefreshConfig: PullToRefreshConfig?,
pullToRefreshState: PullToRefreshState,
topOffset: Dp,
) {
val collapsedFraction = behavior.state.collapsedFraction
val alpha = MAX_SCALE - collapsedFraction
@ -43,7 +44,6 @@ internal fun WalletPagerIndicator(
pullToRefreshConfig = pullToRefreshConfig,
pullToRefreshState = pullToRefreshState,
)
val padding = height * WALLET_INDICATOR_OFFSET
AnimatedVisibility(
visible = pagerState.pageCount > 1,
@ -64,7 +64,7 @@ internal fun WalletPagerIndicator(
TangemPagerIndicator(
pagerState = pagerState,
modifier = Modifier
.padding(top = padding)
.padding(top = topOffset)
.scale(scaleY = 1f, scaleX = scale),
)
}

View file

@ -93,7 +93,10 @@ internal fun WalletTopBar(
),
) {
topBarConfig.endActions.forEach { action ->
TangemTopBarActionContent(action)
TangemTopBarActionContent(
action,
modifier = Modifier.testTag(MainScreenTestTags.MORE_BUTTON),
)
}
}
},

View file

@ -0,0 +1,188 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.google.common.truth.Truth.assertThat
import com.tangem.common.ui.tokens.TokenConverterParams
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.Account.CryptoPortfolio.Companion.createMainAccount
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.features.tangempay.entity.TangemPayMainUM
import io.mockk.every
import io.mockk.mockk
import kotlinx.collections.immutable.persistentListOf
import org.junit.Test
import java.math.BigDecimal
class SetTokenListTransformerTest {
private val userWalletId = UserWalletId("00")
@Test
fun `GIVEN empty account list WHEN transform THEN tokens are Empty and buttons disabled`() {
val transformer = createTransformer(accountList = emptyAccountList())
val result = transformer.transform(walletUM(buttonEnabled = true)) as WalletUM.Content
assertThat(result.tokensListUM).isInstanceOf(WalletTokensListUM.Empty::class.java)
assertThat(result.buttons).hasSize(1)
assertThat(result.buttons.single().isEnabled).isFalse()
}
@Test
fun `GIVEN non-empty account list WHEN transform THEN tokens not Empty and buttons enabled`() {
val transformer = createTransformer(accountList = nonEmptyAccountList())
val result = transformer.transform(walletUM(buttonEnabled = false)) as WalletUM.Content
assertThat(result.tokensListUM).isNotInstanceOf(WalletTokensListUM.Empty::class.java)
assertThat(result.buttons).hasSize(1)
assertThat(result.buttons.single().isEnabled).isTrue()
}
private fun createTransformer(accountList: AccountStatusList): SetTokenListTransformer {
val userWallet = mockk<UserWallet.Hot>(relaxed = true) {
every { walletId } returns userWalletId
}
return SetTokenListTransformer(
params = TokenConverterParams.Account(
accountList = accountList,
expandedAccounts = emptySet(),
),
userWallet = userWallet,
appCurrency = AppCurrency.Default,
clickIntents = mockk<WalletClickIntents>(relaxed = true),
shouldShowMainPromo = false,
isAccountsModeEnabled = false,
isRedesignEnabled = true,
isAddAndManageTokensEnabled = false,
)
}
private fun emptyAccountList(): AccountStatusList = AccountStatusList(
userWalletId = userWalletId,
accountStatuses = listOf(mainCryptoPortfolioStatus(tokenList = TokenList.Empty)),
totalAccounts = 1,
totalArchivedAccounts = 0,
totalFiatBalance = TotalFiatBalance.Loading,
sortType = TokensSortType.NONE,
groupType = TokensGroupType.NONE,
)
private fun nonEmptyAccountList(): AccountStatusList {
val token = createToken()
val tokenList = TokenList.Ungrouped(
currencies = listOf(createLoadedStatus(token)),
totalFiatBalance = TotalFiatBalance.Loaded(BigDecimal.ZERO, StatusSource.ACTUAL),
sortedBy = TokensSortType.NONE,
)
return AccountStatusList(
userWalletId = userWalletId,
accountStatuses = listOf(mainCryptoPortfolioStatus(tokenList = tokenList)),
totalAccounts = 1,
totalArchivedAccounts = 0,
totalFiatBalance = TotalFiatBalance.Loading,
sortType = TokensSortType.NONE,
groupType = TokensGroupType.NONE,
)
}
private fun mainCryptoPortfolioStatus(tokenList: TokenList): AccountStatus.CryptoPortfolio =
AccountStatus.CryptoPortfolio(
account = createMainAccount(userWalletId),
tokenList = tokenList,
priceChangeLce = Unit.lceError(),
)
private fun walletUM(buttonEnabled: Boolean): WalletUM.Content = WalletUM.Content(
pullToRefreshConfig = PullToRefreshConfig(isRefreshing = false, onRefresh = {}),
walletsBalanceUM = WalletBalanceUM.Loading(
id = userWalletId,
name = "test",
deviceIcon = DeviceIconUM.Mobile,
),
buttons = persistentListOf(
TangemButtonUM(
text = stringReference(value = "Buy"),
type = TangemButtonType.Secondary,
onClick = {},
isEnabled = buttonEnabled,
),
),
notifications = persistentListOf(),
notificationsCarousel = persistentListOf(),
tokensListUM = WalletTokensListUM.Loading,
nftState = WalletNFTItemUM.Hidden,
type = WalletType.Hot,
tangemPayMainUM = TangemPayMainUM.Empty,
)
private fun createToken(): CryptoCurrency.Token {
val network = Network(
id = Network.ID(value = "ethereum", derivationPath = Network.DerivationPath.None),
name = "ethereum",
currencySymbol = "ETH",
derivationPath = Network.DerivationPath.None,
isTestnet = false,
standardType = Network.StandardType.ERC20,
hasFiatFeeRate = false,
canHandleTokens = true,
transactionExtrasType = Network.TransactionExtrasType.NONE,
nameResolvingType = Network.NameResolvingType.NONE,
)
return CryptoCurrency.Token(
id = CryptoCurrency.ID(
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
body = CryptoCurrency.ID.Body.NetworkId(rawId = "ethereum"),
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contractAddress = "0xABCDEF"),
),
network = network,
name = "Token",
symbol = "TKN",
decimals = 18,
iconUrl = null,
isCustom = false,
contractAddress = "0xABCDEF",
)
}
private fun createLoadedStatus(token: CryptoCurrency.Token): CryptoCurrencyStatus {
val networkAddress = NetworkAddress.Single(
defaultAddress = NetworkAddress.Address(
value = "addr",
type = NetworkAddress.Address.Type.Primary,
),
)
val value = CryptoCurrencyStatus.Loaded(
amount = BigDecimal.ONE,
fiatAmount = BigDecimal.ZERO,
fiatRate = BigDecimal.ONE,
priceChange = BigDecimal.ZERO,
stakingBalance = null,
yieldSupplyStatus = null,
hasCurrentNetworkTransactions = false,
pendingTransactions = emptySet(),
networkAddress = networkAddress,
sources = CryptoCurrencyStatus.Sources(),
)
return CryptoCurrencyStatus(currency = token, value = value)
}
}