Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-30 19:55:05 +05:00
parent 0b86264bbf
commit 6a082c1d58
19 changed files with 603 additions and 118 deletions

View file

@ -0,0 +1,110 @@
package com.tangem.feature.wallet.child.tokenActions
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpOffset
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.components.SimpleSettingsRow
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.getDefaultRowColors
import com.tangem.core.ui.components.getWarningRowColors
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent.Params
import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.*
internal class DefaultTokenActionsComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: Params,
val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : TokenActionsComponent, AppComponentContext by appComponentContext {
val isBalanceHiddenFlow: StateFlow<Boolean>
field = MutableStateFlow(false)
init {
getBalanceHidingSettingsUseCase()
.conflate()
.distinctUntilChanged()
.onEach {
isBalanceHiddenFlow.value = it.isBalanceHidden
}
.launchIn(componentScope)
}
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
if (!LocalRedesignEnabled.current) {
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
containerColor = TangemTheme.colors.background.primary,
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
),
) {
Column {
params.actions.fastForEach { action ->
if (action.isEnabled) {
val rowColors = if (action.isWarning) {
getWarningRowColors()
} else {
getDefaultRowColors()
}
SimpleSettingsRow(
title = action.text.resolveReference(),
icon = action.iconResId,
enabled = action.isEnabled,
rowColors = rowColors,
onItemsClick = action.onClick,
)
}
}
}
}
}
}
@Composable
override fun Content(modifier: Modifier) {
if (params.tokenRowUM == null) {
dismiss()
} else {
val isBalanceHidden by isBalanceHiddenFlow.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) {
val offset = with(LocalDensity.current) {
DpOffset(params.offsetX.toDp(), params.offsetY.toDp())
}
TokenActionContent(
tokenRowUM = params.tokenRowUM,
isBalanceHidden = isBalanceHidden,
offset = offset,
actions = params.actions,
onDismiss = params.onDismiss,
modifier = modifier,
)
}
}
}
@AssistedFactory
interface Factory : TokenActionsComponent.Factory {
override fun create(context: AppComponentContext, params: Params): DefaultTokenActionsComponent
}
}

View file

@ -0,0 +1,210 @@
package com.tangem.feature.wallet.child.tokenActions
import android.content.res.Configuration
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.*
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.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.ds.contextmenu.CenteredContextMenuPositionProvider
import com.tangem.core.ui.ds.contextmenu.TangemContextMenu
import com.tangem.core.ui.ds.row.token.TangemTokenRow
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.core.ui.ds.row.token.internal.TangemTokenRowPreviewData
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun TokenActionContent(
tokenRowUM: TangemTokenRowUM,
isBalanceHidden: Boolean,
offset: DpOffset,
actions: ImmutableList<TokenActionButtonUM>,
modifier: Modifier = Modifier,
onDismiss: () -> Unit,
) {
val density = LocalDensity.current
var anchorShiftPx by remember { mutableIntStateOf(0) }
val anchorShiftDp = with(density) { anchorShiftPx.toDp() }
val animatedShift by animateDpAsState(
targetValue = anchorShiftDp,
animationSpec = tween(),
label = "AnchorShift",
)
Box(modifier.fillMaxSize()) {
Box(modifier = Modifier.offset(y = offset.y - animatedShift)) {
TangemTokenRow(
tokenRowUM = tokenRowUM,
isBalanceHidden = isBalanceHidden,
reorderableState = null,
modifier = Modifier
.padding(horizontal = TangemTheme.dimens2.x3)
.clip(RoundedCornerShape(18.dp))
.background(TangemTheme.colors2.surface.level3),
)
TangemContextMenu(
expanded = true,
onDismissRequest = onDismiss,
positionProvider = remember(density) {
CenteredContextMenuPositionProvider(
contentOffset = DpOffset(x = 0.dp, y = 12.dp),
density = density,
onAnchorShiftRequired = { shift ->
if (anchorShiftPx == 0 && shift > 0) {
anchorShiftPx = shift
}
},
)
},
) {
TokenActionContextMenuContent(
actions = actions,
onDismiss = onDismiss,
)
}
}
}
}
@Composable
private fun TokenActionContextMenuContent(actions: ImmutableList<TokenActionButtonUM>, onDismiss: () -> Unit) {
Column(
modifier = Modifier
.widthIn(min = 206.dp)
.padding(
vertical = TangemTheme.dimens2.x2_5,
horizontal = TangemTheme.dimens2.x4,
),
) {
actions.fastForEach { item ->
Column {
Row(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
modifier = Modifier
.clickable(
enabled = item.isEnabled,
onClick = {
item.onClick()
onDismiss()
},
)
.padding(
start = TangemTheme.dimens2.x1_5,
end = TangemTheme.dimens2.x2,
top = TangemTheme.dimens2.x2_5,
bottom = TangemTheme.dimens2.x2_5,
),
) {
Icon(
imageVector = ImageVector.vectorResource(item.iconResId),
contentDescription = null,
tint = if (item.isWarning) {
TangemTheme.colors2.graphic.status.warning
} else {
TangemTheme.colors2.graphic.neutral.primary
},
modifier = Modifier.size(TangemTheme.dimens2.x5),
)
Text(
text = item.text.resolveReference(),
style = TangemTheme.typography2.headingRegular17,
color = if (item.isWarning) {
TangemTheme.colors2.text.status.warning
} else {
TangemTheme.colors2.text.neutral.primary
},
)
}
if (item.hasDivider) {
Spacer(
modifier = Modifier
.padding(
vertical = TangemTheme.dimens2.x2_5,
horizontal = TangemTheme.dimens2.x2,
)
.fillMaxWidth()
.height(1.dp)
.background(TangemTheme.colors2.border.neutral.primary),
)
}
}
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TokenActionContent_Preview() {
TangemThemePreviewRedesign {
TokenActionContent(
tokenRowUM = TangemTokenRowPreviewData.tokenState,
offset = DpOffset(
x = 100.dp,
y = 100.dp,
),
actions = persistentListOf(
TokenActionButtonUM(
id = "Send",
text = stringReference("Send"),
iconResId = R.drawable.ic_arrow_up_24,
isEnabled = true,
isWarning = false,
hasDivider = false,
onClick = {},
),
TokenActionButtonUM(
id = "Receive",
text = stringReference("Receive"),
iconResId = R.drawable.ic_arrow_down_24,
isEnabled = true,
isWarning = false,
hasDivider = false,
onClick = {},
),
TokenActionButtonUM(
id = "Swap",
text = stringReference("Swap"),
iconResId = R.drawable.ic_exchange_vertical_24,
isEnabled = true,
isWarning = false,
hasDivider = true,
onClick = {},
),
TokenActionButtonUM(
id = "Remove",
text = stringReference("Remove"),
iconResId = R.drawable.ic_trash_24,
isEnabled = true,
isWarning = true,
hasDivider = false,
onClick = {},
),
),
isBalanceHidden = false,
onDismiss = {},
)
}
}
// endregion

View file

@ -1,64 +1,20 @@
package com.tangem.feature.wallet.child.tokenActions
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.components.SimpleSettingsRow
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.getDefaultRowColors
import com.tangem.core.ui.components.getWarningRowColors
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.feature.wallet.presentation.wallet.state.model.TokenActionButtonUM
import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
internal class TokenActionsComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: Params,
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
override fun dismiss() {
params.onDismiss()
}
@Composable
override fun BottomSheet() {
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
containerColor = TangemTheme.colors.background.primary,
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
),
) {
Column {
params.actions.fastForEach { action ->
if (action.isEnabled) {
val rowColors = if (action.isWarning) {
getWarningRowColors()
} else {
getDefaultRowColors()
}
SimpleSettingsRow(
title = action.text.resolveReference(),
icon = action.iconResId,
enabled = action.isEnabled,
rowColors = rowColors,
onItemsClick = action.onClick,
)
}
}
}
}
}
import kotlinx.collections.immutable.ImmutableList
internal interface TokenActionsComponent : ComposableBottomSheetComponent, ComposableContentComponent {
data class Params(
val actions: List<TokenActionButtonUM>,
val actions: ImmutableList<TokenActionButtonUM>,
val tokenRowUM: TangemTokenRowUM?,
val offsetX: Float,
val offsetY: Float,
val onDismiss: () -> Unit,
)
interface Factory : ComponentFactory<Params, TokenActionsComponent>
}

View file

@ -0,0 +1,16 @@
package com.tangem.feature.wallet.child.tokenActions.di
import com.tangem.feature.wallet.child.tokenActions.DefaultTokenActionsComponent
import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal interface TokenActionsModule {
@Binds
fun bindTokenActionsComponentFactory(impl: DefaultTokenActionsComponent.Factory): TokenActionsComponent.Factory
}

View file

@ -17,12 +17,14 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.decompose.ComposableDialogComponent
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.domain.tokens.model.details.TokenAction
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent
import com.tangem.feature.wallet.child.tokenActions.DefaultTokenActionsComponent
import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent
import com.tangem.feature.wallet.child.wallet.model.WalletModel
import com.tangem.feature.wallet.navigation.WalletRoute
@ -61,6 +63,7 @@ internal class WalletComponent @AssistedInject constructor(
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
private val networkSelectionComponentFactory: NetworkSelectionComponent.Factory,
private val tokenActionsComponentFactory: TokenActionsComponent.Factory,
private val designFeatureToggles: DesignFeatureToggles,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
@ -164,11 +167,14 @@ internal class WalletComponent @AssistedInject constructor(
)
}
is WalletDialogConfig.TokenActionList -> {
TokenActionsComponent(
appComponentContext = childByContext(componentContext),
tokenActionsComponentFactory.create(
context = childByContext(componentContext),
params = TokenActionsComponent.Params(
actions = dialogConfig.actionList,
onDismiss = model.innerWalletRouter.dialogNavigation::dismiss,
tokenRowUM = dialogConfig.tokenRowUM,
offsetX = dialogConfig.offsetX,
offsetY = dialogConfig.offsetY,
),
)
}
@ -261,6 +267,13 @@ internal class WalletComponent @AssistedInject constructor(
when (val dialog = dialog.child?.instance) {
is ComposableDialogComponent -> dialog.Dialog()
is DefaultTokenActionsComponent -> {
if (designFeatureToggles.isRedesignEnabled) {
dialog.Content(Modifier.hazeEffectTangem())
} else {
dialog.BottomSheet()
}
}
is ComposableBottomSheetComponent -> dialog.BottomSheet()
else -> {}
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import androidx.compose.ui.geometry.Offset
import arrow.core.getOrElse
import com.tangem.utils.logging.TangemLogger
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
@ -9,6 +10,7 @@ import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
@ -38,6 +40,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBott
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
@ -56,6 +59,13 @@ internal interface WalletContentClickIntents {
fun onTokenItemLongClick(accountId: AccountId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onTokenItemLongClickV2(
accountId: AccountId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
offset: Offset,
tokenRowUM: TangemTokenRowUM,
)
fun onApyLabelClick(accountId: AccountId, currencyStatus: CryptoCurrencyStatus, apySource: ApySource, apy: String)
fun onYieldPromoCloseClick()
@ -153,6 +163,47 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
accountId = accountId,
clickIntents = currencyActionsClickIntents,
).convert(actionsState),
offset = Offset.Zero,
tokenRowUM = null,
)
}
}
}
override fun onTokenItemLongClickV2(
accountId: AccountId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
offset: Offset,
tokenRowUM: TangemTokenRowUM,
) {
modelScope.launch {
val userWalletId = accountId.userWalletId
val userWallet = getUserWalletUseCase(userWalletId).getOrElse { exception ->
TangemLogger.e(
"""
Unable to get user wallet
|- ID: $userWalletId
|- Exception: $exception
""".trimIndent(),
)
return@launch
}
getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = cryptoCurrencyStatus)
.take(count = 1)
.collectLatest { actionsState ->
router.openTokenActionSheet(
userWallet = userWallet,
tokenActionList = MultiWalletCurrencyActionsConverter(
userWallet = userWallet,
accountId = accountId,
clickIntents = currencyActionsClickIntents,
).convert(actionsState)
.filter { it.isEnabled }
.toPersistentList(),
offset = offset,
tokenRowUM = tokenRowUM,
)
}
}

View file

@ -40,7 +40,7 @@ internal object WalletScreenPreviewData {
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
tailUM = TangemRowTailUM.Empty,
onItemClick = {},
onItemLongClick = {},
onItemLongClick = { _, _ -> },
)
private val accountRowDefault = TangemTokenRowUM.Content(
@ -61,7 +61,7 @@ internal object WalletScreenPreviewData {
promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty,
tailUM = TangemRowTailUM.Empty,
onItemClick = {},
onItemLongClick = {},
onItemLongClick = { _, _ -> },
)
private val tokenListDefault = WalletTokensListUM.Content(

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.router
import androidx.compose.ui.geometry.Offset
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
@ -8,6 +9,7 @@ import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
@ -161,10 +163,18 @@ internal class DefaultWalletRouter @Inject constructor(
)
}
override fun openTokenActionSheet(userWallet: UserWallet, tokenActionList: ImmutableList<TokenActionButtonUM>) {
override fun openTokenActionSheet(
userWallet: UserWallet,
tokenActionList: ImmutableList<TokenActionButtonUM>,
offset: Offset,
tokenRowUM: TangemTokenRowUM?,
) {
dialogNavigation.activate(
configuration = WalletDialogConfig.TokenActionList(
actionList = tokenActionList,
offsetX = offset.x,
offsetY = offset.y,
tokenRowUM = tokenRowUM,
),
)
}

View file

@ -1,8 +1,10 @@
package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Offset
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.tangem.common.routing.AppRoute
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrency
@ -86,7 +88,12 @@ internal interface InnerWalletRouter {
fun openYieldSupplyEntryScreen(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, apy: String)
/** Open token action sheet */
fun openTokenActionSheet(userWallet: UserWallet, tokenActionList: ImmutableList<TokenActionButtonUM>)
fun openTokenActionSheet(
userWallet: UserWallet,
tokenActionList: ImmutableList<TokenActionButtonUM>,
offset: Offset,
tokenRowUM: TangemTokenRowUM?,
)
/** Open QR scanner screen */
fun openQrScanner()

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Stable
import com.tangem.core.ui.extensions.TextReference
import kotlinx.serialization.Serializable
@ -13,11 +14,14 @@ import kotlinx.serialization.Serializable
* @property isWarning if warning row
* @property isEnabled enabled
*/
@Stable
@Serializable
data class TokenActionButtonUM(
val id: String,
val text: TextReference,
@DrawableRes val iconResId: Int,
val onClick: () -> Unit,
val isWarning: Boolean,
val isEnabled: Boolean = true,
val hasDivider: Boolean = false,
)

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountName
@ -34,6 +35,9 @@ internal sealed interface WalletDialogConfig {
@Serializable
data class TokenActionList(
val actionList: ImmutableList<TokenActionButtonUM>,
val tokenRowUM: TangemTokenRowUM?,
val offsetY: Float,
val offsetX: Float,
) : WalletDialogConfig
@Serializable

View file

@ -23,11 +23,25 @@ internal class MultiWalletCurrencyActionsConverter(
) : Converter<TokenActionsState, ImmutableList<TokenActionButtonUM>> {
override fun convert(value: TokenActionsState): ImmutableList<TokenActionButtonUM> {
return value.states
.filterIfSingleWithToken()
val actionList = value.states.filterIfSingleWithToken()
.mapNotNull {
mapTokenActionState(actionsState = it, cryptoCurrencyStatus = value.cryptoCurrencyStatus)
}
return actionList
.mapIndexed { index, action ->
val analyticsAction = TokenActionsState.ActionState.Analytics::class.java.simpleName
val hideTokenAction = TokenActionsState.ActionState.HideToken::class.java.simpleName
if (
action.id == analyticsAction ||
index != actionList.lastIndex && actionList[index + 1].id == hideTokenAction
) {
action.copy(hasDivider = true)
} else {
action
}
}
.toImmutableList()
}
@ -111,6 +125,7 @@ internal class MultiWalletCurrencyActionsConverter(
}
return TokenActionButtonUM(
id = actionsState::class.java.simpleName,
text = title,
iconResId = icon,
onClick = action,

View file

@ -76,8 +76,13 @@ internal class WalletTokenCurrencyItemConverter(
onItemLongClick = when (value.value) {
CryptoCurrencyStatus.Loading -> null
else -> {
{
clickIntents.onTokenItemLongClick(accountId, value)
{ offset, tokenRowUM ->
clickIntents.onTokenItemLongClickV2(
accountId = accountId,
cryptoCurrencyStatus = value,
offset = offset,
tokenRowUM = tokenRowUM,
)
}
}
},

View file

@ -50,6 +50,7 @@ import com.tangem.core.ui.components.background.northernlights.NorthernLightsBac
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeader
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshSlidingContainer
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.components.rememberIsKeyboardVisible
import com.tangem.core.ui.components.sheetscaffold.*
@ -72,6 +73,8 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.common.Wallet
import com.tangem.feature.wallet.presentation.wallet.ui.utils.lazyListStateMapSaver
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
import com.tangem.features.tangempay.entity.TangemPayMainUM
import dev.chrisbanes.haze.HazeProgressive
import dev.chrisbanes.haze.HazeTint
import kotlinx.coroutines.launch
import kotlin.math.abs
@ -82,6 +85,7 @@ private const val MARKET_HINT_THRESHOLD = 0.5f
internal fun WalletScreen2(
state: WalletScreenState,
tangemPayComponent: TangemPayMainBlockComponent,
modifier: Modifier = Modifier,
bottomSheetContent: @Composable (() -> Unit),
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
@ -130,6 +134,7 @@ internal fun WalletScreen2(
bottomSheetContent = bottomSheetContent,
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
onBottomSheetStateChange = onBottomSheetStateChange,
modifier = modifier,
listStates = listStates,
)
@ -154,6 +159,7 @@ private fun WalletContent2(
tangemPayComponent: TangemPayMainBlockComponent,
behavior: TangemCollapsingAppBarBehavior,
listStates: Map<Int, LazyListState>,
modifier: Modifier = Modifier,
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
bottomSheetContent: @Composable (() -> Unit),
@ -169,6 +175,7 @@ private fun WalletContent2(
}
BaseScaffoldWithMarkets(
modifier = modifier,
state = state,
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
onBottomSheetStateChange = onBottomSheetStateChange,
@ -200,7 +207,7 @@ private fun WalletContent2(
Box(
modifier = Modifier
.fillMaxSize()
.hazeSourceTangem(zIndex = -1f),
.hazeSourceTangem(zIndex = -2f),
) {
NorthernLightsBackground(
containerColor = if (LocalIsInDarkTheme.current) {
@ -220,10 +227,20 @@ private fun WalletContent2(
behavior = behavior,
)
val overlay = TangemTheme.colors2.overlay.overlayPrimary
HorizontalPager(
state = walletsPagerState,
userScrollEnabled = canPagerScroll,
beyondViewportPageCount = 1,
modifier = Modifier.hazeEffectTangem {
fallbackTint = HazeTint(color = overlay)
progressive = HazeProgressive.verticalGradient(
startIntensity = 1f,
endIntensity = 1f,
preferPerformance = true,
)
},
) { currentWalletIndex ->
val listState = listStates[currentWalletIndex] ?: rememberLazyListState()

View file

@ -6,6 +6,7 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@ -17,8 +18,12 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.layout.positionOnScreen
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.semantics
@ -155,12 +160,19 @@ private fun LazyListScope.tokenItem(
backgroundColor = TangemTheme.colors2.surface.level3,
)
var position by remember { mutableStateOf(Offset.Zero) }
when (val tokenRowUM = listItem.tokenRowUM) {
is TangemTokenRowUM -> TangemTokenRow(
tokenRowUM = tokenRowUM,
isBalanceHidden = isBalanceHidden,
reorderableState = null,
modifier = itemModifier,
modifier = itemModifier
.onGloballyPositioned { position = it.positionOnScreen() }
.combinedClickable(
enabled = tokenRowUM.onItemClick != null || tokenRowUM.onItemLongClick != null,
onClick = tokenRowUM.onItemClick ?: {},
onLongClick = { tokenRowUM.onItemLongClick?.invoke(position, tokenRowUM) },
),
)
is TangemHeaderRowUM -> TangemHeaderRow(
headerRowUM = tokenRowUM,
@ -170,6 +182,7 @@ private fun LazyListScope.tokenItem(
}
}
@Suppress("LongMethod")
private fun LazyListScope.portfolioItem(
listItem: TokensListItemUM2.Portfolio,
index: Int,
@ -220,12 +233,23 @@ private fun LazyListScope.portfolioItem(
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
.semantics { lazyListItemPosition = tokenIndex + 1 }
var position by remember { mutableStateOf(Offset.Zero) }
when (val tokenRowUM = item.tokenRowUM) {
is TangemTokenRowUM -> TangemTokenRow(
tokenRowUM = tokenRowUM,
isBalanceHidden = isBalanceHidden,
reorderableState = null,
modifier = itemModifier,
modifier = itemModifier
.onGloballyPositioned {
position = it.positionInWindow()
}
.combinedClickable(
enabled = tokenRowUM.onItemClick != null || tokenRowUM.onItemLongClick != null,
onClick = tokenRowUM.onItemClick ?: {},
onLongClick = {
tokenRowUM.onItemLongClick?.invoke(position, tokenRowUM)
},
),
)
is TangemHeaderRowUM -> TangemHeaderRow(
headerRowUM = tokenRowUM,
@ -343,7 +367,8 @@ internal fun PortfolioRowItem(
val composables = remember {
SharedTokenRowComposables(
icon = { modifier ->
val size = if (isExpandedWrapped) AccountIconSize.ExtraSmall else AccountIconSize.Default
val size =
if (isExpandedWrapped) AccountIconSize.ExtraSmall else AccountIconSize.Default
val headIcon = item.tokenRowUM.headIconUM
val sizedHeadIcon = if (headIcon is TangemIconUM.Currency) {
headIcon.copy(