diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/contextmenu/TangemContextMenu.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/contextmenu/TangemContextMenu.kt index 69a475c2a8..3800173371 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/contextmenu/TangemContextMenu.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/contextmenu/TangemContextMenu.kt @@ -43,6 +43,7 @@ fun TangemContextMenu( modifier: Modifier = Modifier, offset: DpOffset = DpOffset.Zero, properties: PopupProperties = PopupProperties(focusable = true), + positionProvider: PopupPositionProvider? = null, content: @Composable ColumnScope.() -> Unit, ) { val expandedStates = remember { MutableTransitionState(false) } @@ -51,7 +52,7 @@ fun TangemContextMenu( if (expandedStates.currentState || expandedStates.targetState) { val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) } val density = LocalDensity.current - val popupPositionProvider = DropdownMenuPositionProvider( + val popupPositionProvider = positionProvider ?: DropdownMenuPositionProvider( offset, density, ) { parentBounds, menuBounds -> @@ -249,6 +250,57 @@ internal data class DropdownMenuPositionProvider( } } +/** + * A [PopupPositionProvider] that centers the popup horizontally on the screen + * and positions it below the anchor. If there is not enough space below, + * it positions the popup above the anchor. If there is no space in either direction, + * it reports the required vertical shift via [onAnchorShiftRequired] so the caller + * can move the anchor upward to make room below. + */ +@Immutable +class CenteredContextMenuPositionProvider( + private val contentOffset: DpOffset, + private val density: Density, + private val onAnchorShiftRequired: (Int) -> Unit = {}, +) : PopupPositionProvider { + override fun calculatePosition( + anchorBounds: IntRect, + windowSize: IntSize, + layoutDirection: LayoutDirection, + popupContentSize: IntSize, + ): IntOffset { + val contentOffsetY = with(density) { contentOffset.y.roundToPx() } + val x = (windowSize.width - popupContentSize.width) / 2 + + val yBelow = anchorBounds.bottom + contentOffsetY + val yAbove = anchorBounds.top - contentOffsetY - popupContentSize.height + + val isFitsBelow = yBelow + popupContentSize.height <= windowSize.height + val isFitsAbove = yAbove >= 0 + + val y = when { + isFitsBelow -> { + onAnchorShiftRequired(0) + yBelow + } + isFitsAbove -> { + onAnchorShiftRequired(0) + yAbove + } + else -> { + // Neither fits — calculate how much the anchor must shift up + // so the popup fits below. Place popup at bottom edge of screen. + val desiredY = windowSize.height - popupContentSize.height + val shift = yBelow - desiredY + onAnchorShiftRequired(shift) + desiredY + } + } + + return IntOffset(x, y) + } +} + // region Preview @Composable @Preview(showBackground = true, widthDp = 360) diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRow.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRow.kt index 307dafb258..67d8918c7f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRow.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRow.kt @@ -1,18 +1,13 @@ package com.tangem.core.ui.ds.row.token import android.content.res.Configuration -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.layout.layoutId -import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -110,7 +105,7 @@ fun TangemTokenRow( .fillMaxWidth(), ) }, - modifier = modifier.tokenClickable(tokenRowUM = tokenRowUM), + modifier = modifier, ) } @@ -198,36 +193,10 @@ fun TangemTokenRow( .testTag(tag = TokenElementsTestTags.TOKEN_NON_FIAT_BLOCK), ) }, - modifier = modifier.tokenClickable(tokenRowUM = tokenRowUM), + modifier = modifier, ) } -@OptIn(ExperimentalFoundationApi::class) -private fun Modifier.tokenClickable(tokenRowUM: TangemTokenRowUM): Modifier = composed { - val hapticFeedback = LocalHapticFeedback.current - - val onClick = tokenRowUM.onItemClick - val onLongClick = tokenRowUM.onItemLongClick - val onHapticLongClick = if (onLongClick != null) { - { - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - onLongClick() - } - } else { - null - } - - when { - onClick == null && onLongClick == null -> this - onClick == null && onLongClick != null -> combinedClickable(onClick = {}, onLongClick = onHapticLongClick) - onClick != null && onLongClick == null -> combinedClickable(onClick = onClick) - onClick != null && onLongClick != null -> { - combinedClickable(onClick = onClick, onLongClick = onHapticLongClick) - } - else -> this - } -} - // region Preview @Composable @Preview(showBackground = true, widthDp = 360) diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRowUM.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRowUM.kt index 814e4dbfa3..b246291079 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRowUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/TangemTokenRowUM.kt @@ -2,6 +2,7 @@ package com.tangem.core.ui.ds.row.token import androidx.annotation.DrawableRes import androidx.compose.runtime.Immutable +import androidx.compose.ui.geometry.Offset import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.marketprice.PriceChangeState import com.tangem.core.ui.ds.badge.TangemBadgeUM @@ -11,7 +12,9 @@ import com.tangem.core.ui.ds.row.internal.TangemRowTailUM import com.tangem.core.ui.extensions.TextReference import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.serialization.Serializable +@Serializable @Immutable sealed class TangemTokenRowUM : TangemRowUM { @@ -43,11 +46,12 @@ sealed class TangemTokenRowUM : TangemRowUM { abstract val onItemClick: (() -> Unit)? /** Callback which will be called when an item is long clicked */ - abstract val onItemLongClick: (() -> Unit)? + abstract val onItemLongClick: ((Offset, TangemTokenRowUM) -> Any)? /** * Content state of [TangemTokenRowUM] */ + @Serializable data class Content( override val id: String, override val headIconUM: TangemIconUM.Currency, @@ -58,12 +62,13 @@ sealed class TangemTokenRowUM : TangemRowUM { override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty, override val tailUM: TangemRowTailUM = TangemRowTailUM.Empty, override val onItemClick: (() -> Unit)?, - override val onItemLongClick: (() -> Unit)?, + override val onItemLongClick: ((Offset, TangemTokenRowUM) -> Any)?, ) : TangemTokenRowUM() /** * Loading state of [TangemTokenRowUM] */ + @Serializable data class Loading( override val id: String, override val headIconUM: TangemIconUM.Currency = TangemIconUM.Currency(CurrencyIconState.Loading), @@ -75,12 +80,13 @@ sealed class TangemTokenRowUM : TangemRowUM { override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty override val tailUM: TangemRowTailUM = TangemRowTailUM.Empty override val onItemClick: (() -> Unit)? = null - override val onItemLongClick: (() -> Unit)? = null + override val onItemLongClick: ((Offset, TangemTokenRowUM) -> Unit)? = null } /** * Loading state of [TangemTokenRowUM] */ + @Serializable data class Empty( override val id: String, ) : TangemTokenRowUM() { @@ -92,12 +98,13 @@ sealed class TangemTokenRowUM : TangemRowUM { override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty override val tailUM: TangemRowTailUM = TangemRowTailUM.Empty override val onItemClick: (() -> Unit)? = null - override val onItemLongClick: (() -> Unit)? = null + override val onItemLongClick: ((Offset, TangemTokenRowUM) -> Unit)? = null } /** * Actionable state of [TangemTokenRowUM] */ + @Serializable data class Actionable( override val id: String, override val headIconUM: TangemIconUM.Currency, @@ -105,16 +112,17 @@ sealed class TangemTokenRowUM : TangemRowUM { override val subtitleUM: SubtitleUM, override val tailUM: TangemRowTailUM, override val onItemClick: (() -> Unit)?, - override val onItemLongClick: (() -> Unit)?, + override val onItemLongClick: ((Offset, TangemTokenRowUM) -> Unit)?, override val topEndContentUM: EndContentUM = EndContentUM.Empty, override val bottomEndContentUM: EndContentUM = EndContentUM.Empty, ) : TangemTokenRowUM() { override val promoBannerUM: PromoBannerUM = PromoBannerUM.Empty } + @Serializable @Immutable sealed class TitleUM { - + @Serializable data class Content( val text: TextReference, val hasPending: Boolean = false, @@ -123,16 +131,20 @@ sealed class TangemTokenRowUM : TangemRowUM { val onBadgeClick: (() -> Unit)? = null, ) : TitleUM() + @Serializable data object Loading : TitleUM() + @Serializable data object Placeholder : TitleUM() + @Serializable data object Empty : TitleUM() } + @Serializable @Immutable sealed class SubtitleUM { - + @Serializable data class Content( val text: TextReference, val isAvailable: Boolean = true, @@ -142,16 +154,20 @@ sealed class TangemTokenRowUM : TangemRowUM { val badge: TangemBadgeUM? = null, ) : SubtitleUM() + @Serializable data object Loading : SubtitleUM() + @Serializable data object Placeholder : SubtitleUM() + @Serializable data object Empty : SubtitleUM() } + @Serializable @Immutable sealed class EndContentUM { - + @Serializable data class Content( val text: TextReference, val isAvailable: Boolean = true, @@ -161,13 +177,17 @@ sealed class TangemTokenRowUM : TangemRowUM { val priceChangeUM: PriceChangeState = PriceChangeState.Unknown, ) : EndContentUM() + @Serializable data object Loading : EndContentUM() + @Serializable data object Placeholder : EndContentUM() + @Serializable data object Empty : EndContentUM() } + @Serializable @Immutable sealed class PromoBannerUM { data class Content( @@ -184,6 +204,7 @@ sealed class TangemTokenRowUM : TangemRowUM { } } + @Serializable data object Empty : PromoBannerUM() } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/internal/TangemTokenRowPreviewData.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/internal/TangemTokenRowPreviewData.kt index bd003a5ee9..93e7e86184 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/internal/TangemTokenRowPreviewData.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/row/token/internal/TangemTokenRowPreviewData.kt @@ -128,7 +128,7 @@ object TangemTokenRowPreviewData { promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty, tailUM = TangemRowTailUM.Empty, onItemClick = {}, - onItemLongClick = {}, + onItemLongClick = { _, _ -> }, ) val defaultEllipsisState: TangemTokenRowUM.Content @@ -155,7 +155,7 @@ object TangemTokenRowPreviewData { promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty, tailUM = TangemRowTailUM.Empty, onItemClick = {}, - onItemLongClick = {}, + onItemLongClick = { _, _ -> }, ) val tokenState: TangemTokenRowUM.Content @@ -169,7 +169,7 @@ object TangemTokenRowPreviewData { promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty, tailUM = TangemRowTailUM.Empty, onItemClick = {}, - onItemLongClick = {}, + onItemLongClick = { _, _ -> }, ) val customTokenState: TangemTokenRowUM.Content @@ -183,7 +183,7 @@ object TangemTokenRowPreviewData { promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty, tailUM = TangemRowTailUM.Empty, onItemClick = {}, - onItemLongClick = {}, + onItemLongClick = { _, _ -> }, ) val draggableState: TangemTokenRowUM.Actionable @@ -194,7 +194,7 @@ object TangemTokenRowPreviewData { subtitleUM = subtitleUM, tailUM = TangemRowTailUM.Draggable(R.drawable.ic_drag_24), onItemClick = {}, - onItemLongClick = {}, + onItemLongClick = { _, _ -> }, ) val draggableStateV2: TangemTokenRowUM.Actionable @@ -207,7 +207,7 @@ object TangemTokenRowPreviewData { bottomEndContentUM = bottomEndContentUM, tailUM = TangemRowTailUM.Draggable(R.drawable.ic_drag_24), onItemClick = {}, - onItemLongClick = {}, + onItemLongClick = { _, _ -> }, ) val loadingState: TangemTokenRowUM.Loading @@ -252,7 +252,7 @@ object TangemTokenRowPreviewData { priceChangeUM = priceChangeState, ), onItemClick = {}, - onItemLongClick = {}, + onItemLongClick = { _, _ -> }, ) val accountLetterState: TangemTokenRowUM.Content diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/DefaultTokenActionsComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/DefaultTokenActionsComponent.kt new file mode 100644 index 0000000000..12dede4099 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/DefaultTokenActionsComponent.kt @@ -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 + 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( + 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 + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionContent.kt new file mode 100644 index 0000000000..aebf8404a9 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionContent.kt @@ -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, + 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, 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 \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionsComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionsComponent.kt index aa0ae8e9d7..106516a350 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionsComponent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/TokenActionsComponent.kt @@ -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( - 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, + val actions: ImmutableList, + val tokenRowUM: TangemTokenRowUM?, + val offsetX: Float, + val offsetY: Float, val onDismiss: () -> Unit, ) + + interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/di/TokenActionsModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/di/TokenActionsModule.kt new file mode 100644 index 0000000000..61733c285a --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/tokenActions/di/TokenActionsModule.kt @@ -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 +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt index a40b3e413a..f15fbdfb75 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt @@ -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 -> {} } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index 92136614c6..8158d28f2b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -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, ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index 44b3aa8329..5668d743d8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -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( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 95c94d246c..1eadd2b07b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -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) { + override fun openTokenActionSheet( + userWallet: UserWallet, + tokenActionList: ImmutableList, + offset: Offset, + tokenRowUM: TangemTokenRowUM?, + ) { dialogNavigation.activate( configuration = WalletDialogConfig.TokenActionList( actionList = tokenActionList, + offsetX = offset.x, + offsetY = offset.y, + tokenRowUM = tokenRowUM, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index fb55f96999..7d772e539c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -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) + fun openTokenActionSheet( + userWallet: UserWallet, + tokenActionList: ImmutableList, + offset: Offset, + tokenRowUM: TangemTokenRowUM?, + ) /** Open QR scanner screen */ fun openQrScanner() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TokenActionButtonUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TokenActionButtonUM.kt index 1b678f372d..46f75be772 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TokenActionButtonUM.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/TokenActionButtonUM.kt @@ -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, ) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt index a8844378c1..8322d1c8df 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt @@ -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, + val tokenRowUM: TangemTokenRowUM?, + val offsetY: Float, + val offsetX: Float, ) : WalletDialogConfig @Serializable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt index 4044cedde1..95105edff0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletCurrencyActionsConverter.kt @@ -23,11 +23,25 @@ internal class MultiWalletCurrencyActionsConverter( ) : Converter> { override fun convert(value: TokenActionsState): ImmutableList { - 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, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenCurrencyItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenCurrencyItemConverter.kt index c1794a9422..8697f5a9eb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenCurrencyItemConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenCurrencyItemConverter.kt @@ -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, + ) } } }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt index a8c9e5a8c0..376948e5ae 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt @@ -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, + 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() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt index 26556fb7a8..a04c4bbd1e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyContent.kt @@ -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(