Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-29 16:01:28 +02:00
parent 8511623421
commit 7095ceec0f
21 changed files with 717 additions and 183 deletions

View file

@ -25,10 +25,8 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
@ -38,7 +36,7 @@ import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.rememberHazeState
@Composable
fun TokenSelectorBottomSheet(config: TangemBottomSheetConfig, stickyFooter: StickyFooter? = null) {
fun TokenSelectorBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet<TokenSelectorContentUM>(
config = config,
type = TangemBottomSheetType.Modal,
@ -47,56 +45,52 @@ fun TokenSelectorBottomSheet(config: TangemBottomSheetConfig, stickyFooter: Stic
TokenSelectorContent(
content = content,
onDismiss = config.onDismissRequest,
stickyFooter = stickyFooter,
embedded = false,
)
},
)
}
data class StickyFooter(val buttonText: TextReference, val isEnabled: Boolean = true, val onClick: () -> Unit)
@Composable
fun TokenSelectorEmbeddedContent(
content: TokenSelectorContentUM,
stickyFooter: StickyFooter?,
scrollBottomInset: Dp,
modifier: Modifier = Modifier,
) {
TokenSelectorContent(
content = content,
onDismiss = {},
stickyFooter = stickyFooter,
embedded = true,
modifier = modifier,
scrollBottomInset = scrollBottomInset,
)
}
@Composable
private fun TokenSelectorContent(
content: TokenSelectorContentUM,
onDismiss: () -> Unit,
stickyFooter: StickyFooter?,
embedded: Boolean,
modifier: Modifier = Modifier,
scrollBottomInset: Dp = 0.dp,
onDismiss: () -> Unit = {},
) {
val hazeState = rememberHazeState()
var topBarHeight by remember { mutableStateOf(0.dp) }
val topContentPadding = if (embedded) {
TangemTheme.dimens2.x4
0.dp
} else {
topBarHeight
}
val footerHeight = TangemTheme.dimens2.x14 + TangemTheme.dimens2.x4
val listBottomPadding = TangemTheme.dimens2.x10 + if (stickyFooter != null) footerHeight else 0.dp
Box(modifier = modifier.fillMaxWidth()) {
val bottomFadeReserve = if (embedded) 0.dp else TangemTheme.dimens2.x10
val bottomListPadding = bottomFadeReserve + scrollBottomInset
LazyColumn(
modifier = Modifier.hazeSourceTangem(state = hazeState, 1f),
contentPadding = PaddingValues(
start = TangemTheme.dimens2.x4,
end = TangemTheme.dimens2.x4,
top = topContentPadding,
bottom = listBottomPadding,
bottom = bottomListPadding,
),
) {
tokenSelectorSectionItems(content.sections)
@ -108,29 +102,11 @@ private fun TokenSelectorContent(
hazeState = hazeState,
onChangeHeight = { topBarHeight = it },
)
}
Fade(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(bottom = if (stickyFooter != null) footerHeight else 0.dp),
height = TangemTheme.dimens2.x10,
)
if (stickyFooter != null) {
TangemButton(
Fade(
modifier = Modifier
.align(Alignment.BottomCenter)
.navigationBarsPadding()
.padding(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x2)
.fillMaxWidth(),
buttonUM = TangemButtonUM(
type = TangemButtonType.Primary,
text = stickyFooter.buttonText,
shape = TangemButtonShape.Rounded,
size = TangemButtonSize.X15,
isEnabled = stickyFooter.isEnabled,
onClick = stickyFooter.onClick,
),
.fillMaxWidth()
.align(Alignment.BottomCenter),
height = TangemTheme.dimens2.x10,
)
}
}

View file

@ -191,6 +191,7 @@ private fun AddButton(state: AddTokenUM.Button, modifier: Modifier = Modifier) {
isEnabled = state.isEnabled,
size = TangemButtonSize.X12,
shape = TangemButtonShape.Rounded,
isLoading = state.showProgress,
)
}

View file

@ -10,10 +10,11 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
@ -36,6 +37,14 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.core.ui.utils.WindowInsetsZero
/**
* Extra bottom inset that scrollable content under a [BasicBottomSheet] should reserve so it
* does not collide with the footer overlay: measured footer height plus the bottom gradient
* (see `gradientHeight` in [FooterOverlay]). Equals `0.dp` when there is no footer or when read
* outside [BasicBottomSheet].
*/
val LocalTangemBottomSheetContentBottomInset = compositionLocalOf { 0.dp }
/**
* Type of [TangemBottomSheet] that defines its behavior and appearance.
* - [Default]: Standard bottom sheet with a draggable header
@ -204,6 +213,9 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
) {
val model = config.content as? T ?: return
val windowSize = LocalWindowSize.current
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
var footerHeightDp by remember { mutableStateOf<Dp?>(null) }
val bsContent: @Composable ColumnScope.() -> Unit = {
val maxHeight = when (type) {
@ -212,17 +224,19 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
}
val contentModifier = when (type) {
Default -> Modifier.clip(
RoundedCornerShape(
topStart = TangemTheme.dimens2.x8,
topEnd = TangemTheme.dimens2.x8,
),
)
Default -> Modifier
.padding(bottom = bottomBarHeight)
.clip(
RoundedCornerShape(
topStart = TangemTheme.dimens2.x8,
topEnd = TangemTheme.dimens2.x8,
),
)
Modal -> Modifier
.padding(
start = TangemTheme.dimens2.x2,
end = TangemTheme.dimens2.x2,
bottom = TangemTheme.dimens2.x2,
bottom = bottomBarHeight,
)
.clip(RoundedCornerShape(TangemTheme.dimens2.x8))
}
@ -236,26 +250,19 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
title(model)
}
Box(modifier = Modifier.fillMaxWidth()) {
content(model)
if (footer != null) {
BottomFade(
modifier = Modifier.align(Alignment.BottomCenter),
gradientBrush = Brush.verticalGradient(
listOf(
TangemTheme.colors2.shadow.fadeMin,
TangemTheme.colors2.shadow.fadeMax,
),
),
FooterOverlay(
measuredFooterHeight = footerHeightDp,
onMeasureFooter = { newHeight ->
if (newHeight != footerHeightDp) {
footerHeightDp = newHeight
}
},
footer = { footer(model) },
content = { content(model) },
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
) {
if (footer != null) {
footer(model)
}
} else {
content(model)
}
}
}
@ -274,6 +281,57 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
)
}
@Composable
fun BoxScope.FooterOverlay(
measuredFooterHeight: Dp?,
onMeasureFooter: (Dp) -> Unit,
footer: @Composable BoxScope.() -> Unit,
content: @Composable () -> Unit,
) {
val density = LocalDensity.current
val gradientHeight = TangemTheme.dimens2.x10
val isFooterRendered = measuredFooterHeight == null || measuredFooterHeight > 0.dp
val contentBottomOverlayHeight = if (isFooterRendered) {
(measuredFooterHeight ?: 0.dp) + gradientHeight
} else {
0.dp
}
val fadeMax = TangemTheme.colors2.surface.level2
CompositionLocalProvider(
LocalTangemBottomSheetContentBottomInset provides contentBottomOverlayHeight,
) {
content()
}
if (isFooterRendered) {
Column(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
) {
Fade(
backgroundColor = fadeMax,
height = gradientHeight,
)
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(measuredFooterHeight ?: 0.dp)
.background(fadeMax),
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.onGloballyPositioned { coordinates ->
onMeasureFooter(with(density) { coordinates.size.height.toDp() })
},
) {
footer()
}
}
// region Preview
@Suppress("LongMethod")
@Composable

View file

@ -46,6 +46,7 @@ fun TangemBadge(badgeUM: TangemBadgeUM, modifier: Modifier = Modifier) {
color = badgeUM.color,
type = badgeUM.type,
iconPosition = badgeUM.iconPosition,
shouldRespectIconTint = badgeUM.shouldRespectIconTint,
onClick = badgeUM.onClick,
modifier = modifier,
)
@ -77,6 +78,7 @@ fun TangemBadge(
color: TangemBadgeColor = TangemBadgeColor.Gray,
type: TangemBadgeType = TangemBadgeType.Solid,
iconPosition: TangemBadgeIconPosition = TangemBadgeIconPosition.None,
shouldRespectIconTint: Boolean = false,
onClick: (() -> Unit)? = null,
) {
val iconColor = getIconColor(type = type, color = color)
@ -95,6 +97,7 @@ fun TangemBadge(
iconPosition = iconPosition,
size = size,
iconColor = iconColor,
shouldRespectIconTint = shouldRespectIconTint,
)
AnimatedVisibility(
visible = text != null,
@ -113,6 +116,7 @@ fun TangemBadge(
iconPosition = iconPosition,
size = size,
iconColor = iconColor,
shouldRespectIconTint = shouldRespectIconTint,
)
}
}
@ -123,6 +127,7 @@ private fun StartIcon(
size: TangemBadgeSize,
iconColor: Color,
tangemIconUM: TangemIconUM? = null,
shouldRespectIconTint: Boolean = false,
) {
AnimatedVisibility(
visible = tangemIconUM != null && iconPosition != TangemBadgeIconPosition.End,
@ -139,7 +144,11 @@ private fun StartIcon(
is TangemIconUM.Url,
TangemIconUM.Empty,
-> wrappedIconRes
is TangemIconUM.Icon -> wrappedIconRes.copy(tintReference = ColorReference2 { iconColor })
is TangemIconUM.Icon -> if (shouldRespectIconTint) {
wrappedIconRes
} else {
wrappedIconRes.copy(tintReference = ColorReference2 { iconColor })
}
},
)
}
@ -151,6 +160,7 @@ private fun EndIcon(
size: TangemBadgeSize,
iconColor: Color,
tangemIconUM: TangemIconUM? = null,
shouldRespectIconTint: Boolean = false,
) {
AnimatedVisibility(
visible = tangemIconUM != null && iconPosition == TangemBadgeIconPosition.End,
@ -167,7 +177,11 @@ private fun EndIcon(
is TangemIconUM.Url,
TangemIconUM.Empty,
-> wrappedIconRes
is TangemIconUM.Icon -> wrappedIconRes.copy(tintReference = ColorReference2 { iconColor })
is TangemIconUM.Icon -> if (shouldRespectIconTint) {
wrappedIconRes
} else {
wrappedIconRes.copy(tintReference = ColorReference2 { iconColor })
}
},
)
}

View file

@ -7,14 +7,16 @@ import com.tangem.core.ui.extensions.TextReference
/**
* UI model for [TangemBadge] component
*
* @param text TextReference for the badge label.
* @param tangemIconUM Model of representation for the icon to be displayed in the badge.
* @param size [TangemBadgeSize] defining the size of the badge.
* @param shape [TangemBadgeShape] defining the shape of the badge.
* @param color [TangemBadgeColor] defining the color scheme of the badge.
* @param type [TangemBadgeType] defining the style of the badge.
* @param iconPosition [TangemBadgeIconPosition] defining icon position of the badge.
* @param onClick Lambda to be invoked when the badge is clicked (optional).
* @param text TextReference for the badge label.
* @param tangemIconUM Model of representation for the icon to be displayed in the badge.
* @param size [TangemBadgeSize] defining the size of the badge.
* @param shape [TangemBadgeShape] defining the shape of the badge.
* @param color [TangemBadgeColor] defining the color scheme of the badge.
* @param type [TangemBadgeType] defining the style of the badge.
* @param iconPosition [TangemBadgeIconPosition] defining icon position of the badge.
* @param shouldRespectIconTint When true, the icon's own tintReference is preserved instead of being overridden by the
* badge color scheme. Useful when the icon carries its own semantic color (e.g. account icons).
* @param onClick Lambda to be invoked when the badge is clicked (optional).
*/
class TangemBadgeUM(
val text: TextReference,
@ -24,5 +26,6 @@ class TangemBadgeUM(
val color: TangemBadgeColor = TangemBadgeColor.Gray,
val type: TangemBadgeType = TangemBadgeType.Solid,
val iconPosition: TangemBadgeIconPosition = TangemBadgeIconPosition.Start,
val shouldRespectIconTint: Boolean = false,
val onClick: (() -> Unit)? = null,
)

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M10,2.5C10.46,2.5 10.833,2.873 10.833,3.333V15.071L16.077,9.827C16.402,9.502 16.93,9.502 17.256,9.827C17.581,10.153 17.581,10.681 17.256,11.006L10.589,17.672L10,18.262L2.744,11.006C2.419,10.68 2.419,10.153 2.744,9.827C3.069,9.502 3.596,9.502 3.922,9.827L9.166,15.071V3.333C9.166,2.873 9.54,2.5 10,2.5Z"
android:fillColor="#ffffff"/>
</vector>

View file

@ -0,0 +1,110 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalHazeState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.commonfeatures.impl.R
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioFooterKind
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.uiSpec
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM
import dev.chrisbanes.haze.rememberHazeState
@Composable
internal fun AddToPortfolioBottomSheetFooter(
currentRoute: AddToPortfolioRoutes,
userPortfolioState: State<UserPortfolioUM?>?,
onBack: () -> Unit,
onAddFromUserPortfolioClick: (() -> Unit)?,
) {
when (currentRoute.uiSpec().footer) {
AddToPortfolioFooterKind.Cancel -> WithLocalHaze {
CancelFooterButton(onClick = onBack)
}
AddToPortfolioFooterKind.UserPortfolioAdd -> {
val state = userPortfolioState?.value ?: return
val onClick = onAddFromUserPortfolioClick ?: return
WithLocalHaze {
UserPortfolioAddFooter(
isEnabled = state.isAddEnabled,
onClick = onClick,
)
}
}
AddToPortfolioFooterKind.None -> Unit
}
}
@Composable
private fun WithLocalHaze(content: @Composable () -> Unit) {
CompositionLocalProvider(LocalHazeState provides rememberHazeState(), content = content)
}
@Composable
private fun CancelFooterButton(onClick: () -> Unit) {
SecondaryTangemButton(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens2.x4)
.padding(bottom = TangemTheme.dimens2.x4),
onClick = onClick,
text = resourceReference(R.string.common_cancel),
size = TangemButtonSize.X12,
shape = TangemButtonShape.Rounded,
)
}
@Composable
private fun UserPortfolioAddFooter(isEnabled: Boolean, onClick: () -> Unit) {
TangemRowContainer(
contentPadding = PaddingValues(
horizontal = TangemTheme.dimens2.x6,
vertical = TangemTheme.dimens2.x5,
),
) {
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
text = stringResourceSafe(R.string.common_add_token),
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
text = stringResourceSafe(R.string.markets_token_add_subtitle),
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
SecondaryTangemButton(
modifier = Modifier
.layoutId(TangemRowLayoutId.TAIL)
.padding(start = TangemTheme.dimens2.x2),
onClick = onClick,
text = resourceReference(R.string.common_add),
size = TangemButtonSize.X9,
shape = TangemButtonShape.Rounded,
isEnabled = isEnabled,
)
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM
@Composable
internal fun AddToPortfolioBottomSheetSwitch(
childStack: State<ChildStack<AddToPortfolioRoutes, ComposableContentComponent>>,
onBack: () -> Unit,
onDismiss: () -> Unit,
userPortfolioState: State<UserPortfolioUM?>? = null,
onAddFromUserPortfolioClick: (() -> Unit)? = null,
) {
if (LocalRedesignEnabled.current) {
AddToPortfolioBottomSheetV2(
childStack = childStack,
onBack = onBack,
onDismiss = onDismiss,
userPortfolioState = userPortfolioState,
onAddFromUserPortfolioClick = onAddFromUserPortfolioClick,
)
} else {
AddToPortfolioBottomSheet(
childStack = childStack,
onBack = onBack,
onDismiss = onDismiss,
)
}
}

View file

@ -0,0 +1,126 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.components.bottomsheets.*
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.extensions.clickableSingle
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.commonfeatures.impl.R
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.uiSpec
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM
@Composable
internal fun AddToPortfolioBottomSheetV2(
childStack: State<ChildStack<AddToPortfolioRoutes, ComposableContentComponent>>,
onBack: () -> Unit,
onDismiss: () -> Unit,
userPortfolioState: State<UserPortfolioUM?>? = null,
onAddFromUserPortfolioClick: (() -> Unit)? = null,
) {
val stack by childStack
val contentStack = remember { mutableStateOf(stack) }
val isNotEmpty = stack.active.configuration != AddToPortfolioRoutes.Empty
if (isNotEmpty) {
contentStack.value = stack
}
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
onBack = onBack,
config = TangemBottomSheetConfig(
isShown = isNotEmpty,
onDismissRequest = onDismiss,
content = TangemBottomSheetConfigContent.Empty,
),
type = TangemBottomSheetType.Modal,
containerColor = TangemTheme.colors2.surface.level2,
title = {
AddToPortfolioBottomSheetTitle(
stack = stack,
onCloseClick = onDismiss,
)
},
content = {
AnimatedContent(targetState = contentStack.value, label = "Content Animation") { animatedStack ->
AddToPortfolioRouteContent(animatedStack = animatedStack)
}
},
footer = {
AnimatedContent(
targetState = contentStack.value.active.configuration,
transitionSpec = { fadeIn() togetherWith fadeOut() },
label = "Footer Animation",
) { route ->
AddToPortfolioBottomSheetFooter(
currentRoute = route,
userPortfolioState = userPortfolioState,
onBack = onBack,
onAddFromUserPortfolioClick = onAddFromUserPortfolioClick,
)
}
},
)
}
@Composable
private fun AddToPortfolioRouteContent(animatedStack: ChildStack<AddToPortfolioRoutes, ComposableContentComponent>) {
val spec = animatedStack.active.configuration.uiSpec()
val baseModifier = if (spec.shouldApplyHorizontalPadding) {
Modifier.padding(horizontal = TangemTheme.dimens2.x4)
} else {
Modifier
}
if (spec.isScrollable) {
val bottomInset = LocalTangemBottomSheetContentBottomInset.current
val scrollBottomReserve = if (bottomInset > 0.dp) bottomInset else TangemTheme.dimens2.x4
Column(modifier = baseModifier.verticalScroll(rememberScrollState())) {
animatedStack.active.instance.Content(modifier = Modifier)
Spacer(modifier = Modifier.height(scrollBottomReserve))
}
} else {
animatedStack.active.instance.Content(modifier = baseModifier)
}
}
@Composable
private fun AddToPortfolioBottomSheetTitle(
stack: ChildStack<AddToPortfolioRoutes, ComposableContentComponent>,
onCloseClick: () -> Unit,
) {
TangemTopBar(
title = stack.active.configuration.uiSpec().title,
type = TangemTopBarType.BottomSheet,
endContent = {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_close_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = Modifier
.size(TangemTheme.dimens2.x11)
.background(
color = TangemTheme.colors2.button.backgroundSecondary,
shape = CircleShape,
)
.clickableSingle(onClick = onCloseClick)
.padding(TangemTheme.dimens2.x2),
)
},
)
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio
import androidx.compose.runtime.Composable
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.backStack
@ -83,10 +84,14 @@ internal class DefaultAddToPortfolioComponent @AssistedInject constructor(
@Composable
override fun BottomSheet() {
AddToPortfolioBottomSheet(
val userPortfolioState = model.userPortfolioStateController.uiState
.collectAsStateWithLifecycle()
AddToPortfolioBottomSheetSwitch(
childStack = childStack.subscribeAsState(),
onBack = ::onBack,
onDismiss = ::dismiss,
userPortfolioState = userPortfolioState,
onAddFromUserPortfolioClick = model::onContinueFromUserPortfolio,
)
}

View file

@ -23,6 +23,7 @@ internal class AddToPortfolioInitialSelectionResolver @Inject constructor(
selectedWallet: UserWallet?,
tokenParams: RawMarketToken,
accountToAdd: AvailableToAddAccount? = null,
preferredNetwork: TokenMarketInfo.Network? = null,
): InitialSelection? {
if (availableToAddData.availableToAddWallets.isEmpty()) return null
val fallbackNetwork = orderedNetworks.firstOrNull() ?: return null
@ -33,13 +34,13 @@ internal class AddToPortfolioInitialSelectionResolver @Inject constructor(
val ownerWallet = walletOrder.firstOrNull { entry ->
entry.availableToAddAccounts.values.any { it === accountToAdd }
} ?: walletOrder.first()
val network = pickAddableNetwork(
val network = pickNetworkForExplicitAccount(
userWallet = ownerWallet.userWallet,
account = accountToAdd,
orderedNetworks = orderedNetworks,
tokenParams = tokenParams,
preferredNetwork = preferredNetwork,
)
?: fallbackNetwork
return InitialSelection(userWallet = ownerWallet.userWallet, account = accountToAdd, network = network)
}
@ -81,6 +82,35 @@ internal class AddToPortfolioInitialSelectionResolver @Inject constructor(
?: walletEntry.availableToAddAccounts.values.firstOrNull()
}
private suspend fun pickNetworkForExplicitAccount(
userWallet: UserWallet,
account: AvailableToAddAccount,
orderedNetworks: List<TokenMarketInfo.Network>,
tokenParams: RawMarketToken,
preferredNetwork: TokenMarketInfo.Network?,
): TokenMarketInfo.Network {
if (preferredNetwork != null) {
val candidate = account.availableToAddNetworks
.firstOrNull { it.networkId == preferredNetwork.networkId }
if (
candidate != null && hasDerivationFor(
userWallet = userWallet,
account = account,
network = candidate,
tokenParams = tokenParams,
)
) {
return candidate
}
}
return pickAddableNetwork(
userWallet = userWallet,
account = account,
orderedNetworks = orderedNetworks,
tokenParams = tokenParams,
) ?: orderedNetworks.first()
}
private suspend fun pickAddableNetwork(
userWallet: UserWallet,
account: AvailableToAddAccount,
@ -92,18 +122,26 @@ internal class AddToPortfolioInitialSelectionResolver @Inject constructor(
}
if (availableOrdered.isEmpty()) return null
val derivationIndex = account.account.account.derivationIndex
val withDerivation = availableOrdered.filter { network ->
val currency = getTokenMarketCryptoCurrency(
userWalletId = userWallet.walletId,
tokenMarketParams = tokenParams,
network = network,
accountIndex = derivationIndex,
) ?: return@filter false
networkHasDerivationUseCase(userWallet, currency.network).getOrElse { false }
val withDerivation = availableOrdered.firstOrNull { network ->
hasDerivationFor(userWallet = userWallet, account = account, network = network, tokenParams = tokenParams)
}
return withDerivation ?: availableOrdered.first()
}
return withDerivation.firstOrNull() ?: availableOrdered.first()
private suspend fun hasDerivationFor(
userWallet: UserWallet,
account: AvailableToAddAccount,
network: TokenMarketInfo.Network,
tokenParams: RawMarketToken,
): Boolean {
val derivationIndex = account.account.account.derivationIndex
val currency = getTokenMarketCryptoCurrency(
userWalletId = userWallet.walletId,
tokenMarketParams = tokenParams,
network = network,
accountIndex = derivationIndex,
) ?: return false
return networkHasDerivationUseCase(userWallet, currency.network).getOrElse { false }
}
data class InitialSelection(

View file

@ -378,23 +378,17 @@ internal class AddToPortfolioModel @Inject constructor(
// drop first selected portfolio or any selected before
.drop(1)
.map { newPortfolio ->
val currentNetwork = selectedNetwork.first().selectedNetwork
val availableToAddNetworks = newPortfolio.account.availableToAddNetworks
val isSelectedNetworkAvailableForNewPortfolio = availableToAddNetworks
.any { it.networkId == currentNetwork.networkId }
val rebuiltSelectedNetwork = selectionResolver.resolve(
availableToAddData = data,
orderedNetworks = orderedNetworks,
selectedWallet = globalSelectedWallet,
tokenParams = tokenParams,
accountToAdd = newPortfolio.account,
preferredNetwork = selectedNetwork.first().selectedNetwork,
)?.toSelectedNetwork()
if (!isSelectedNetworkAvailableForNewPortfolio) {
val selection = selectionResolver.resolve(
availableToAddData = data,
orderedNetworks = orderedNetworks,
selectedWallet = globalSelectedWallet,
tokenParams = tokenParams,
accountToAdd = newPortfolio.account,
)
val newNetwork = selection?.toSelectedNetwork()
if (newNetwork != null) {
this.selectedNetwork.tryEmit(newNetwork)
}
if (rebuiltSelectedNetwork != null) {
this.selectedNetwork.tryEmit(rebuiltSelectedNetwork)
}
selectedPortfolio.tryEmit(newPortfolio)
navigation.popToFirst()

View file

@ -0,0 +1,57 @@
package com.tangem.features.commonfeatures.impl.addtoportfolio.model
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.commonfeatures.impl.R
internal data class AddToPortfolioRouteUiSpec(
val title: TextReference,
val isScrollable: Boolean,
val shouldApplyHorizontalPadding: Boolean,
val footer: AddToPortfolioFooterKind,
)
internal enum class AddToPortfolioFooterKind {
None,
Cancel,
UserPortfolioAdd,
}
internal fun AddToPortfolioRoutes.uiSpec(): AddToPortfolioRouteUiSpec = when (this) {
AddToPortfolioRoutes.AddToken -> AddToPortfolioRouteUiSpec(
title = resourceReference(R.string.common_add_token),
isScrollable = true,
shouldApplyHorizontalPadding = true,
footer = AddToPortfolioFooterKind.None,
)
is AddToPortfolioRoutes.NetworkSelector -> AddToPortfolioRouteUiSpec(
title = resourceReference(R.string.common_add_token),
isScrollable = true,
shouldApplyHorizontalPadding = true,
footer = AddToPortfolioFooterKind.Cancel,
)
AddToPortfolioRoutes.PortfolioSelector -> AddToPortfolioRouteUiSpec(
title = resourceReference(R.string.common_add_token),
isScrollable = false,
shouldApplyHorizontalPadding = true,
footer = AddToPortfolioFooterKind.Cancel,
)
AddToPortfolioRoutes.UserPortfolio -> AddToPortfolioRouteUiSpec(
title = resourceReference(R.string.markets_portfolio_block_title),
isScrollable = false,
shouldApplyHorizontalPadding = false,
footer = AddToPortfolioFooterKind.UserPortfolioAdd,
)
AddToPortfolioRoutes.TokenActions -> AddToPortfolioRouteUiSpec(
title = resourceReference(R.string.common_get_token),
isScrollable = true,
shouldApplyHorizontalPadding = true,
footer = AddToPortfolioFooterKind.None,
)
AddToPortfolioRoutes.Empty -> AddToPortfolioRouteUiSpec(
title = TextReference.EMPTY,
isScrollable = false,
shouldApplyHorizontalPadding = false,
footer = AddToPortfolioFooterKind.None,
)
}

View file

@ -150,6 +150,7 @@ internal class TokenActionsUiBuilder @Inject constructor(
} else {
TangemBadgeIconPosition.End
},
shouldRespectIconTint = cryptoCurrencyData.isAccountMode,
)
}

View file

@ -44,6 +44,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.res.*
import com.tangem.features.commonfeatures.impl.R
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM
import dev.chrisbanes.haze.rememberHazeState
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
import java.util.UUID
@ -79,13 +80,15 @@ internal fun TokenActionsContentV2(state: TokenActionsUM, modifier: Modifier = M
SpacerH(TangemTheme.dimens2.x2)
SecondaryTangemButton(
modifier = Modifier.fillMaxWidth(),
onClick = state.onLaterClick,
text = resourceReference(R.string.common_later),
size = TangemButtonSize.X12,
shape = TangemButtonShape.Rounded,
)
CompositionLocalProvider(LocalHazeState provides rememberHazeState()) {
SecondaryTangemButton(
modifier = Modifier.fillMaxWidth(),
onClick = state.onLaterClick,
text = resourceReference(R.string.common_later),
size = TangemButtonSize.X12,
shape = TangemButtonShape.Rounded,
)
}
}
}

View file

@ -3,12 +3,10 @@ package com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.markets.tokenselector.StickyFooter
import com.tangem.common.ui.markets.tokenselector.TokenSelectorEmbeddedContent
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.commonfeatures.impl.R
import com.tangem.core.ui.components.bottomsheets.LocalTangemBottomSheetContentBottomInset
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@ -20,7 +18,6 @@ internal class DefaultUserPortfolioComponent @AssistedInject constructor(
) : UserPortfolioComponent, AppComponentContext by context {
private val model: UserPortfolioModel = getOrCreateModel(params)
private val onContinueClick: () -> Unit = params.callbacks::onContinueFromUserPortfolio
@Composable
override fun Content(modifier: Modifier) {
@ -29,11 +26,7 @@ internal class DefaultUserPortfolioComponent @AssistedInject constructor(
TokenSelectorEmbeddedContent(
content = state.content,
modifier = modifier,
stickyFooter = StickyFooter(
buttonText = resourceReference(R.string.common_add),
isEnabled = state.isAddEnabled,
onClick = onContinueClick,
),
scrollBottomInset = LocalTangemBottomSheetContentBottomInset.current,
)
}

View file

@ -1,11 +1,13 @@
package com.tangem.features.commonfeatures.impl.portfolioselector
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.bottomsheets.LocalTangemBottomSheetContentBottomInset
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
@ -45,15 +47,18 @@ internal class DefaultPortfolioSelectorComponent @AssistedInject constructor(
@Composable
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
val listBottomPadding = PaddingValues(bottom = LocalTangemBottomSheetContentBottomInset.current)
if (LocalRedesignEnabled.current) {
PortfolioSelectorContentV2(
state = state,
modifier = modifier,
contentPadding = listBottomPadding,
)
} else {
PortfolioSelectorContent(
state = state,
modifier = modifier,
contentPadding = listBottomPadding,
)
}
}

View file

@ -508,6 +508,109 @@ class AddToPortfolioInitialSelectionResolverTest {
Truth.assertThat(result?.network).isEqualTo(BITCOIN)
}
@Test
fun `GIVEN accountToAdd and preferredNetwork is on account and addable WHEN resolve THEN return preferred`() = runTest {
val walletId = UserWalletId(WALLET_ID_A)
val userWallet = userWallet(walletId)
val explicitAccount = availableAccount(availableToAddNetworks = setOf(ETHEREUM, BITCOIN))
val ethereumCurrency = cryptoCurrency()
val bitcoinCurrency = cryptoCurrency()
coEvery { getTokenMarketCryptoCurrency(any(), any(), ETHEREUM, any()) } returns ethereumCurrency
coEvery { getTokenMarketCryptoCurrency(any(), any(), BITCOIN, any()) } returns bitcoinCurrency
every { networkHasDerivationUseCase(any(), ethereumCurrency.network) } returns true.right()
every { networkHasDerivationUseCase(any(), bitcoinCurrency.network) } returns true.right()
val data = availableData(
wallets = mapOf(
walletId to walletEntry(
userWallet = userWallet,
accounts = mapOf(AccountId.forMainCryptoPortfolio(walletId) to availableAccount()),
),
),
)
val result = resolver.resolve(
availableToAddData = data,
orderedNetworks = listOf(BITCOIN, ETHEREUM),
selectedWallet = userWallet,
tokenParams = tokenParams,
accountToAdd = explicitAccount,
preferredNetwork = ETHEREUM,
)
Truth.assertThat(result?.network).isEqualTo(ETHEREUM)
}
@Test
fun `GIVEN accountToAdd and preferred is not on account WHEN resolve THEN use pickAddableNetwork`() = runTest {
val walletId = UserWalletId(WALLET_ID_A)
val userWallet = userWallet(walletId)
val explicitAccount = availableAccount(availableToAddNetworks = setOf(ETHEREUM, BITCOIN))
val ethereumCurrency = cryptoCurrency()
val bitcoinCurrency = cryptoCurrency()
coEvery { getTokenMarketCryptoCurrency(any(), any(), ETHEREUM, any()) } returns ethereumCurrency
coEvery { getTokenMarketCryptoCurrency(any(), any(), BITCOIN, any()) } returns bitcoinCurrency
every { networkHasDerivationUseCase(any(), ethereumCurrency.network) } returns true.right()
every { networkHasDerivationUseCase(any(), bitcoinCurrency.network) } returns true.right()
val data = availableData(
wallets = mapOf(
walletId to walletEntry(
userWallet = userWallet,
accounts = mapOf(AccountId.forMainCryptoPortfolio(walletId) to availableAccount()),
),
),
)
val result = resolver.resolve(
availableToAddData = data,
orderedNetworks = listOf(ETHEREUM, BITCOIN),
selectedWallet = userWallet,
tokenParams = tokenParams,
accountToAdd = explicitAccount,
preferredNetwork = POLYGON,
)
Truth.assertThat(result?.network).isEqualTo(ETHEREUM)
}
@Test
fun `GIVEN accountToAdd and preferred on account has no derivation WHEN resolve THEN fall back to pickAddableNetwork`() =
runTest {
val walletId = UserWalletId(WALLET_ID_A)
val userWallet = userWallet(walletId)
val explicitAccount = availableAccount(availableToAddNetworks = setOf(ETHEREUM, BITCOIN))
val ethereumCurrency = cryptoCurrency()
val bitcoinCurrency = cryptoCurrency()
coEvery { getTokenMarketCryptoCurrency(any(), any(), ETHEREUM, any()) } returns ethereumCurrency
coEvery { getTokenMarketCryptoCurrency(any(), any(), BITCOIN, any()) } returns bitcoinCurrency
every { networkHasDerivationUseCase(any(), ethereumCurrency.network) } returns false.right()
every { networkHasDerivationUseCase(any(), bitcoinCurrency.network) } returns true.right()
val data = availableData(
wallets = mapOf(
walletId to walletEntry(
userWallet = userWallet,
accounts = mapOf(AccountId.forMainCryptoPortfolio(walletId) to availableAccount()),
),
),
)
val result = resolver.resolve(
availableToAddData = data,
orderedNetworks = listOf(ETHEREUM, BITCOIN),
selectedWallet = userWallet,
tokenParams = tokenParams,
accountToAdd = explicitAccount,
preferredNetwork = ETHEREUM,
)
Truth.assertThat(result?.network).isEqualTo(BITCOIN)
}
@Test
fun `GIVEN get token market crypto currency returns null WHEN resolve THEN fall back to first ordered available network`() = runTest {
val walletId = UserWalletId(WALLET_ID_A)
@ -587,5 +690,12 @@ class AddToPortfolioInitialSelectionResolverTest {
contractAddress = null,
decimalCount = 8,
)
val POLYGON = TokenMarketInfo.Network(
networkId = "polygon",
isExchangeable = true,
contractAddress = null,
decimalCount = 18,
)
}
}

View file

@ -10,7 +10,6 @@ import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
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.BottomSheetDefaults
@ -22,6 +21,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -31,6 +31,8 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.button.*
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.row.TangemRowContainer
import com.tangem.core.ui.ds.row.TangemRowLayoutId
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
@ -56,6 +58,7 @@ internal fun PortfolioBlock(state: PortfolioBlockUM, modifier: Modifier = Modifi
}
AnimatedVisibility(
modifier = Modifier.align(Alignment.BottomCenter),
visible = isVisible,
enter = fadeIn(animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)),
exit = fadeOut(animationSpec = tween(durationMillis = 300)),
@ -91,62 +94,55 @@ internal fun PortfolioBlock(state: PortfolioBlockUM, modifier: Modifier = Modifi
@Composable
private fun ContentBlock(state: PortfolioBlockUM.Content, modifier: Modifier = Modifier) {
FloatingCard(modifier = modifier) { // TODO will be handle in [REDACTED_TASK_KEY]
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.weight(1f)
.clickable(onClick = state.onRowClick),
verticalAlignment = Alignment.CenterVertically,
) {
CurrencyIcon(
modifier = Modifier.padding(end = TangemTheme.dimens2.x3),
state = state.tokenIcon,
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = state.tokenName,
style = TangemTheme.typography2.bodyMedium16,
color = TangemTheme.colors2.text.neutral.primary,
maxLines = 1,
)
Text(
text = stringResourceSafe(R.string.markets_portfolio_block_subtitle),
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
)
}
Column(horizontalAlignment = Alignment.End) {
Text(
text = state.totalBalance.orMaskWithStars(state.isBalanceHidden).resolveAnnotatedReference(),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodyMedium16,
)
Text(
text = state.tokenSymbol,
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
)
}
}
FloatingCard(modifier = modifier) {
TangemRowContainer(modifier = Modifier.clickableSingle(onClick = state.onRowClick)) {
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
text = state.totalBalance.orMaskWithStars(state.isBalanceHidden).resolveAnnotatedReference(),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors2.text.neutral.primary,
style = TangemTheme.typography2.bodyMedium16,
)
Text(
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
text = stringResourceSafe(R.string.markets_portfolio_block_subtitle),
style = TangemTheme.typography2.captionMedium12,
color = TangemTheme.colors2.text.neutral.secondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
TangemButton(
modifier = Modifier.padding(start = TangemTheme.dimens2.x3),
modifier = Modifier.layoutId(TangemRowLayoutId.END_TOP),
buttonUM = TangemButtonUM(
text = resourceReference(R.string.common_add_funds),
type = TangemButtonType.Secondary,
tangemIconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_arrow_down_20,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
iconPosition = TangemButtonIconPosition.Start,
shape = TangemButtonShape.Rounded,
size = TangemButtonSize.X9,
onClick = state.onAddFundsClick,
),
)
TangemButton(
modifier = Modifier
.layoutId(TangemRowLayoutId.TAIL)
.padding(start = TangemTheme.dimens2.x3),
buttonUM = TangemButtonUM(
type = TangemButtonType.Secondary,
tangemIconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_chevron_24,
iconRes = R.drawable.ic_arrow_expand_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
shape = TangemButtonShape.Rounded,
size = TangemButtonSize.X9,
onClick = state.onAddFundsClick,
onClick = state.onRowClick,
),
)
}
@ -156,7 +152,12 @@ private fun ContentBlock(state: PortfolioBlockUM.Content, modifier: Modifier = M
@Composable
private fun AddTokenBlock(state: PortfolioBlockUM.AddToken, modifier: Modifier = Modifier) {
FloatingCard(modifier = modifier) {
Row(verticalAlignment = Alignment.CenterVertically) {
Row(
modifier = Modifier
.padding(TangemTheme.dimens2.x3)
.clickableSingle(onClick = state.onAddClick),
verticalAlignment = Alignment.CenterVertically,
) {
CurrencyIcon(state.tokenIcon)
SpacerW(TangemTheme.dimens2.x3)
@ -204,8 +205,7 @@ private fun FloatingCard(modifier: Modifier = Modifier, content: @Composable ()
.background(
color = TangemTheme.colors2.surface.level3,
shape = RoundedCornerShape(size = TangemTheme.dimens2.x5),
)
.padding(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3),
),
) {
content()
}

View file

@ -17,6 +17,7 @@ import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.res.LocalHazeState
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalRedesignEnabled
@ -130,6 +131,12 @@ private fun EntryContentV2(
child.instance.Content(
modifier = Modifier
.fillMaxSize()
.conditionalCompose(
condition = !isOpenedInBottomSheet,
modifier = {
padding(top = topBarHeight)
},
)
.hazeSourceTangem(zIndex = 0f, state = hazeState),
contentPadding = PaddingValues(top = topBarHeight),
bottomSheetState = bottomSheetState,

View file

@ -10,13 +10,7 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
@ -27,11 +21,7 @@ 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 com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.SpacerH4
import com.tangem.core.ui.components.SpacerW4
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
@ -171,7 +161,7 @@ private fun Content(
Modifier
.onSizeChanged { size ->
bottomSpacing = if (size.height > 0) {
with(density) { size.height.toDp() + 16.dp }
with(density) { size.height.toDp() }
} else {
0.dp
}