Updated on 2026-08-14
This commit is contained in:
parent
1ffec50319
commit
95116dc872
16 changed files with 688 additions and 185 deletions
|
|
@ -79,7 +79,7 @@ private fun DropdownMenuContent(
|
|||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
// Menu open/close animation.
|
||||
val transition = updateTransition(expandedStates, "DropDownMenu")
|
||||
val transition = rememberTransition(expandedStates, "DropDownMenu")
|
||||
|
||||
val scale by transition.animateFloat(
|
||||
transitionSpec = {
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ fun TangemCheckbox(
|
|||
isEnabled: Boolean = true,
|
||||
) {
|
||||
val shape = if (isRounded) {
|
||||
RoundedCornerShape(TangemTheme.dimens2.x1)
|
||||
} else {
|
||||
CircleShape
|
||||
} else {
|
||||
RoundedCornerShape(TangemTheme.dimens2.x1)
|
||||
}
|
||||
Box(
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,288 @@
|
|||
package com.tangem.core.ui.ds.contextmenu
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.window.PopupPositionProvider
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.test.PopUpMenuTestTags
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Just copy paste [DropdownMenu] from material3 with deleting vertical paddings.
|
||||
*/
|
||||
@Composable
|
||||
fun TangemContextMenu(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
offset: DpOffset = DpOffset.Zero,
|
||||
properties: PopupProperties = PopupProperties(focusable = true),
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val expandedStates = remember { MutableTransitionState(false) }
|
||||
expandedStates.targetState = expanded
|
||||
|
||||
if (expandedStates.currentState || expandedStates.targetState) {
|
||||
val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) }
|
||||
val density = LocalDensity.current
|
||||
val popupPositionProvider = DropdownMenuPositionProvider(
|
||||
offset,
|
||||
density,
|
||||
) { parentBounds, menuBounds ->
|
||||
transformOriginState.value = calculateTransformOrigin(parentBounds, menuBounds)
|
||||
}
|
||||
|
||||
Popup(
|
||||
onDismissRequest = onDismissRequest,
|
||||
popupPositionProvider = popupPositionProvider,
|
||||
properties = properties,
|
||||
) {
|
||||
DropdownMenuContent(
|
||||
expandedStates = expandedStates,
|
||||
transformOriginState = transformOriginState,
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val IN_TRANSITION_DURATION = 120
|
||||
private const val OUT_TRANSITION_DURATION = 75
|
||||
|
||||
@Suppress("ReusedModifierInstance", "MagicNumber")
|
||||
@Composable
|
||||
private fun DropdownMenuContent(
|
||||
expandedStates: MutableTransitionState<Boolean>,
|
||||
transformOriginState: MutableState<TransformOrigin>,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
// Menu open/close animation.
|
||||
val transition = rememberTransition(expandedStates, "DropDownMenu")
|
||||
|
||||
val scale by transition.animateFloat(
|
||||
transitionSpec = {
|
||||
if (false isTransitioningTo true) {
|
||||
// Dismissed to expanded
|
||||
tween(
|
||||
durationMillis = IN_TRANSITION_DURATION,
|
||||
easing = LinearOutSlowInEasing,
|
||||
)
|
||||
} else {
|
||||
// Expanded to dismissed.
|
||||
tween(
|
||||
durationMillis = 1,
|
||||
delayMillis = OUT_TRANSITION_DURATION - 1,
|
||||
)
|
||||
}
|
||||
},
|
||||
label = "",
|
||||
) { isExpanded ->
|
||||
if (isExpanded) {
|
||||
// Menu is expanded.
|
||||
1f
|
||||
} else {
|
||||
// Menu is dismissed.
|
||||
0.8f
|
||||
}
|
||||
}
|
||||
|
||||
val alpha by transition.animateFloat(
|
||||
transitionSpec = {
|
||||
if (false isTransitioningTo true) {
|
||||
// Dismissed to expanded
|
||||
tween(durationMillis = 30)
|
||||
} else {
|
||||
// Expanded to dismissed.
|
||||
tween(durationMillis = OUT_TRANSITION_DURATION)
|
||||
}
|
||||
},
|
||||
label = "",
|
||||
) { isExpanded ->
|
||||
if (isExpanded) {
|
||||
// Menu is expanded.
|
||||
1f
|
||||
} else {
|
||||
// Menu is dismissed.
|
||||
0f
|
||||
}
|
||||
}
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens2.x5))
|
||||
.graphicsLayer {
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
this.alpha = alpha
|
||||
transformOrigin = transformOriginState.value
|
||||
},
|
||||
elevation = CardDefaults.cardElevation(),
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.width(IntrinsicSize.Max)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens2.x5))
|
||||
.background(TangemTheme.colors2.contextMenu.background)
|
||||
.testTag(PopUpMenuTestTags.CONTAINER),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateTransformOrigin(parentBounds: IntRect, menuBounds: IntRect): TransformOrigin {
|
||||
val pivotX = when {
|
||||
menuBounds.left >= parentBounds.right -> 0f
|
||||
menuBounds.right <= parentBounds.left -> 1f
|
||||
menuBounds.width == 0 -> 0f
|
||||
else -> {
|
||||
val intersectionCenter =
|
||||
(max(parentBounds.left, menuBounds.left) + min(parentBounds.right, menuBounds.right)) / 2
|
||||
(intersectionCenter - menuBounds.left).toFloat() / menuBounds.width
|
||||
}
|
||||
}
|
||||
val pivotY = when {
|
||||
menuBounds.top >= parentBounds.bottom -> 0f
|
||||
menuBounds.bottom <= parentBounds.top -> 1f
|
||||
menuBounds.height == 0 -> 0f
|
||||
else -> {
|
||||
val intersectionCenter =
|
||||
(max(parentBounds.top, menuBounds.top) + min(parentBounds.bottom, menuBounds.bottom)) / 2
|
||||
(intersectionCenter - menuBounds.top).toFloat() / menuBounds.height
|
||||
}
|
||||
}
|
||||
return TransformOrigin(pivotX, pivotY)
|
||||
}
|
||||
|
||||
private val MenuVerticalMargin = 48.dp
|
||||
|
||||
@Immutable
|
||||
internal data class DropdownMenuPositionProvider(
|
||||
val contentOffset: DpOffset,
|
||||
val density: Density,
|
||||
val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> },
|
||||
) : PopupPositionProvider {
|
||||
override fun calculatePosition(
|
||||
anchorBounds: IntRect,
|
||||
windowSize: IntSize,
|
||||
layoutDirection: LayoutDirection,
|
||||
popupContentSize: IntSize,
|
||||
): IntOffset {
|
||||
// The min margin above and below the menu, relative to the screen.
|
||||
val verticalMargin = with(density) { MenuVerticalMargin.roundToPx() }
|
||||
// The content offset specified using the dropdown offset parameter.
|
||||
val contentOffsetX = with(density) { contentOffset.x.roundToPx() }
|
||||
val contentOffsetY = with(density) { contentOffset.y.roundToPx() }
|
||||
|
||||
// Compute horizontal position.
|
||||
val toRight = anchorBounds.left + contentOffsetX
|
||||
val toLeft = anchorBounds.right - contentOffsetX - popupContentSize.width
|
||||
val toDisplayRight = windowSize.width - popupContentSize.width
|
||||
val toDisplayLeft = 0
|
||||
val x = if (layoutDirection == LayoutDirection.Ltr) {
|
||||
sequenceOf(
|
||||
toRight,
|
||||
toLeft,
|
||||
// If the anchor gets outside of the window on the left, we want to position
|
||||
// toDisplayLeft for proximity to the anchor. Otherwise, toDisplayRight.
|
||||
if (anchorBounds.left >= 0) toDisplayRight else toDisplayLeft,
|
||||
)
|
||||
} else {
|
||||
sequenceOf(
|
||||
toLeft,
|
||||
toRight,
|
||||
// If the anchor gets outside of the window on the right, we want to position
|
||||
// toDisplayRight for proximity to the anchor. Otherwise, toDisplayLeft.
|
||||
if (anchorBounds.right <= windowSize.width) toDisplayLeft else toDisplayRight,
|
||||
)
|
||||
}.firstOrNull {
|
||||
it >= 0 && it + popupContentSize.width <= windowSize.width
|
||||
} ?: toLeft
|
||||
|
||||
// Compute vertical position.
|
||||
val toBottom = maxOf(anchorBounds.bottom + contentOffsetY, verticalMargin)
|
||||
val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height
|
||||
val toCenter = anchorBounds.top - popupContentSize.height / 2
|
||||
val toDisplayBottom = windowSize.height - popupContentSize.height - verticalMargin
|
||||
val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull { element ->
|
||||
element >= verticalMargin &&
|
||||
element + popupContentSize.height <= windowSize.height - verticalMargin
|
||||
} ?: toTop
|
||||
|
||||
onPositionCalculated(
|
||||
anchorBounds,
|
||||
IntRect(
|
||||
left = x,
|
||||
top = y,
|
||||
right = x + popupContentSize.width,
|
||||
bottom = y + popupContentSize.height,
|
||||
),
|
||||
)
|
||||
return IntOffset(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TangemContextMenu_Preview() {
|
||||
TangemThemePreviewRedesign {
|
||||
val hazeState = rememberHazeState()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors2.surface.level1)
|
||||
.hazeSourceTangem(state = hazeState, zIndex = -1f),
|
||||
) {
|
||||
TangemContextMenu(
|
||||
expanded = true,
|
||||
onDismissRequest = { },
|
||||
modifier = Modifier.hazeEffectTangem(state = hazeState),
|
||||
) {
|
||||
TangemContextMenuCheckboxItem(
|
||||
title = stringReference("Sort by balance"),
|
||||
isChecked = true,
|
||||
onClick = {},
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = 0.5.dp,
|
||||
color = TangemTheme.colors2.border.neutral.quaternary,
|
||||
)
|
||||
TangemContextMenuCheckboxItem(
|
||||
title = stringReference("Group tokens"),
|
||||
isChecked = false,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.core.ui.ds.contextmenu
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.ds.checkbox.TangemCheckbox
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Item with checkbox for [TangemContextMenu].
|
||||
*/
|
||||
@Composable
|
||||
fun TangemContextMenuCheckboxItem(title: TextReference, isChecked: Boolean, onClick: () -> Unit) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.width(238.dp)
|
||||
.clickableSingle(onClick = onClick)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens2.x5,
|
||||
horizontal = TangemTheme.dimens2.x4,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography2.headingSemibold17,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
TangemCheckbox(
|
||||
modifier = Modifier,
|
||||
isRounded = true,
|
||||
isChecked = isChecked,
|
||||
onCheckedChange = { /* no-op */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,19 +5,23 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.R
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -30,11 +34,9 @@ import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
|||
* @param title The title text to be displayed in the center of the top bar.
|
||||
* @param modifier Modifier to be applied to the top bar.
|
||||
* @param subtitle Optional subtitle text to be displayed below the title.
|
||||
* @param startIconRes Optional drawable resource ID for the start icon.
|
||||
* @param onStartContentClick Optional click action for the start icon.
|
||||
* @param endIconRes Optional drawable resource ID for the end icon.
|
||||
* @param onEndContentClick Optional click action for the end icon.
|
||||
* @param isGhostButtons Flag to determine if ghost button styling should be applied.
|
||||
* @param startActionUM Optional action data for the start action icon.
|
||||
* @param endActionUM Optional action data for the end action icon.
|
||||
* @param titleIconRes Optional drawable resource ID for the icon to be displayed next to the title.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -43,56 +45,95 @@ fun TangemTopBar(
|
|||
modifier: Modifier = Modifier,
|
||||
title: TextReference? = null,
|
||||
subtitle: TextReference? = null,
|
||||
@DrawableRes startIconRes: Int? = null,
|
||||
onStartContentClick: (() -> Unit)? = null,
|
||||
@DrawableRes endIconRes: Int? = null,
|
||||
onEndContentClick: (() -> Unit)? = null,
|
||||
startActionUM: TangemTopBarActionUM? = null,
|
||||
endActionUM: TangemTopBarActionUM? = null,
|
||||
@DrawableRes titleIconRes: Int? = null,
|
||||
titleStyle: TextStyle = TangemTheme.typography2.headingSemibold17,
|
||||
isGhostButtons: Boolean = false,
|
||||
) {
|
||||
TangemTopBarInner(
|
||||
TangemTopBar(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
titleIconRes = titleIconRes,
|
||||
modifier = modifier,
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
|
||||
) {
|
||||
TangemTopBarTitle(title = title, titleIconRes = titleIconRes, titleStyle = titleStyle)
|
||||
AnimatedVisibility(
|
||||
visible = subtitle != null,
|
||||
label = "Subtitle Visibility",
|
||||
) {
|
||||
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
|
||||
Text(
|
||||
text = wrappedSubtitle.resolveAnnotatedReference(),
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
style = TangemTheme.typography2.bodyRegular15,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
startContent = if (startIconRes != null) {
|
||||
{ TangemTopBarIcon(iconRes = startIconRes) }
|
||||
startContent = if (startActionUM != null) {
|
||||
{ TangemTopBarActionContent(startActionUM) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
onStartContentClick = onStartContentClick,
|
||||
endContent = if (endIconRes != null) {
|
||||
{ TangemTopBarIcon(iconRes = endIconRes) }
|
||||
endContent = if (endActionUM != null) {
|
||||
{ TangemTopBarActionContent(endActionUM) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
onEndContentClick = onEndContentClick,
|
||||
isGhostButtons = isGhostButtons,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A top bar composable that displays a title and optional start and end icons.
|
||||
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8435-74860&m=dev)
|
||||
*
|
||||
* @param title The title text to be displayed in the center of the top bar.
|
||||
* @param modifier Modifier to be applied to the top bar.
|
||||
* @param subtitle Optional subtitle text to be displayed below the title.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes: Int?, titleStyle: TextStyle) {
|
||||
fun TangemTopBar(
|
||||
modifier: Modifier = Modifier,
|
||||
title: TextReference? = null,
|
||||
subtitle: TextReference? = null,
|
||||
@DrawableRes titleIconRes: Int? = null,
|
||||
startContent: @Composable (() -> Unit)? = null,
|
||||
endContent: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens2.x16)
|
||||
.padding(TangemTheme.dimens2.x4, TangemTheme.dimens2.x3),
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = startContent != null,
|
||||
modifier = Modifier.align(Alignment.CenterStart),
|
||||
label = "Start Content Visibility",
|
||||
) {
|
||||
startContent?.invoke()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
|
||||
) {
|
||||
TangemTopBarTitle(title = title, titleIconRes = titleIconRes)
|
||||
AnimatedVisibility(
|
||||
visible = subtitle != null,
|
||||
label = "Subtitle Visibility",
|
||||
) {
|
||||
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
|
||||
Text(
|
||||
text = wrappedSubtitle.resolveAnnotatedReference(),
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
style = TangemTheme.typography2.bodyRegular15,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = endContent != null,
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
label = "End Content Visibility",
|
||||
) {
|
||||
endContent?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes: Int?) {
|
||||
AnimatedVisibility(
|
||||
visible = title != null,
|
||||
label = "Title Visibility",
|
||||
|
|
@ -127,7 +168,7 @@ private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes:
|
|||
Text(
|
||||
text = wrappedTitle.resolveAnnotatedReference(),
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
style = titleStyle,
|
||||
style = TangemTheme.typography2.headingSemibold17,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
|
@ -136,12 +177,29 @@ private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes:
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemTopBarIcon(@DrawableRes iconRes: Int) {
|
||||
fun TangemTopBarActionContent(
|
||||
actionUM: TangemTopBarActionUM,
|
||||
modifier: Modifier = Modifier,
|
||||
iconSize: Dp = TangemTheme.dimens2.x8,
|
||||
) {
|
||||
val background = lerp(
|
||||
start = Color.Transparent,
|
||||
stop = TangemTheme.colors2.button.backgroundSecondary,
|
||||
fraction = actionUM.ghostModeProgress,
|
||||
)
|
||||
val padding = (TangemTheme.dimens2.x10 - iconSize) / 2
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = iconRes),
|
||||
imageVector = ImageVector.vectorResource(id = actionUM.iconRes),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.clip(CircleShape)
|
||||
.conditional(actionUM.isActionable) { background(background) }
|
||||
.conditionalCompose(actionUM.isActionable && actionUM.onClick != null) {
|
||||
clickableSingle(onClick = requireNotNull(actionUM.onClick))
|
||||
}
|
||||
.padding(padding),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -154,13 +212,10 @@ private fun TangemTopBar_Preview(@PreviewParameter(PreviewProvider::class) param
|
|||
TangemTopBar(
|
||||
title = params.title,
|
||||
subtitle = params.subtitle,
|
||||
startIconRes = params.startIconRes,
|
||||
endIconRes = params.endIconRes,
|
||||
titleIconRes = params.titleIconRes,
|
||||
isGhostButtons = params.isGhostButtons,
|
||||
onStartContentClick = {},
|
||||
onEndContentClick = {},
|
||||
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
|
||||
startContent = params.startActionUM?.let { { TangemTopBarActionContent(it) } },
|
||||
endContent = params.endActionUM?.let { { TangemTopBarActionContent(it) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -168,10 +223,9 @@ private fun TangemTopBar_Preview(@PreviewParameter(PreviewProvider::class) param
|
|||
private class TangemTopBarPreviewData(
|
||||
val title: TextReference? = null,
|
||||
val subtitle: TextReference? = null,
|
||||
val isGhostButtons: Boolean = false,
|
||||
val titleIconRes: Int? = null,
|
||||
val startIconRes: Int? = null,
|
||||
val endIconRes: Int? = null,
|
||||
val startActionUM: TangemTopBarActionUM? = null,
|
||||
val endActionUM: TangemTopBarActionUM? = null,
|
||||
)
|
||||
|
||||
private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData> {
|
||||
|
|
@ -179,41 +233,79 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
|
|||
get() = sequenceOf(
|
||||
TangemTopBarPreviewData(
|
||||
title = stringReference("Title"),
|
||||
startIconRes = R.drawable.ic_tangem_24,
|
||||
endIconRes = R.drawable.ic_more_vertical_24,
|
||||
isGhostButtons = true,
|
||||
startActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = {},
|
||||
isActionable = false,
|
||||
),
|
||||
endActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
onClick = {},
|
||||
isActionable = false,
|
||||
),
|
||||
),
|
||||
TangemTopBarPreviewData(
|
||||
title = stringReference("Title"),
|
||||
subtitle = stringReference("Subtitle"),
|
||||
startIconRes = R.drawable.ic_tangem_24,
|
||||
endIconRes = R.drawable.ic_more_vertical_24,
|
||||
isGhostButtons = true,
|
||||
startActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = {},
|
||||
isActionable = true,
|
||||
ghostModeProgress = 1f,
|
||||
),
|
||||
endActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
onClick = {},
|
||||
isActionable = true,
|
||||
ghostModeProgress = 1f,
|
||||
),
|
||||
),
|
||||
TangemTopBarPreviewData(
|
||||
title = stringReference("Title"),
|
||||
subtitle = stringReference("Subtitle"),
|
||||
titleIconRes = R.drawable.ic_tangem_24,
|
||||
startIconRes = R.drawable.ic_tangem_24,
|
||||
endIconRes = R.drawable.ic_more_vertical_24,
|
||||
isGhostButtons = true,
|
||||
startActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = {},
|
||||
isActionable = false,
|
||||
),
|
||||
endActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
onClick = {},
|
||||
isActionable = true,
|
||||
ghostModeProgress = 1f,
|
||||
),
|
||||
),
|
||||
TangemTopBarPreviewData(
|
||||
subtitle = stringReference("Subtitle"),
|
||||
titleIconRes = R.drawable.ic_tangem_24,
|
||||
startIconRes = R.drawable.ic_tangem_24,
|
||||
endIconRes = R.drawable.ic_more_vertical_24,
|
||||
isGhostButtons = true,
|
||||
startActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = {},
|
||||
isActionable = true,
|
||||
),
|
||||
endActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
onClick = {},
|
||||
isActionable = false,
|
||||
),
|
||||
),
|
||||
TangemTopBarPreviewData(
|
||||
title = stringReference("Title"),
|
||||
endIconRes = R.drawable.ic_more_vertical_24,
|
||||
isGhostButtons = true,
|
||||
endActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
onClick = {},
|
||||
isActionable = false,
|
||||
),
|
||||
),
|
||||
TangemTopBarPreviewData(
|
||||
title = stringReference("Title"),
|
||||
startIconRes = R.drawable.ic_tangem_24,
|
||||
isGhostButtons = true,
|
||||
startActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = {},
|
||||
isActionable = true,
|
||||
ghostModeProgress = 1f,
|
||||
),
|
||||
),
|
||||
TangemTopBarPreviewData(
|
||||
title = combinedReference(
|
||||
|
|
@ -238,8 +330,16 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
|
|||
},
|
||||
),
|
||||
),
|
||||
startIconRes = R.drawable.ic_tangem_24,
|
||||
endIconRes = R.drawable.ic_more_vertical_24,
|
||||
startActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = {},
|
||||
isActionable = true,
|
||||
),
|
||||
endActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
onClick = {},
|
||||
isActionable = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.core.ui.ds.topbar
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.FloatRange
|
||||
|
||||
/**
|
||||
* User model for top bar action
|
||||
*
|
||||
* @property iconRes resource id of action icon
|
||||
* @property isActionable if true, action will be clickable, otherwise - not
|
||||
* @property onClick lambda be invoked when action component is clicked. If null, action will not be clickable
|
||||
* @property ghostModeProgress progress of ghost mode animation, from 0f to 1f.
|
||||
*/
|
||||
data class TangemTopBarActionUM(
|
||||
@param:DrawableRes val iconRes: Int,
|
||||
val isActionable: Boolean = true,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
@param:FloatRange(0.0, 1.0) val ghostModeProgress: Float = 0f,
|
||||
)
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
package com.tangem.core.ui.ds.topbar
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Internal top bar composable that arranges optional start, center, and end content.
|
||||
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8435-74860&m=dev)
|
||||
*
|
||||
* @param modifier Modifier to be applied to the top bar.
|
||||
* @param content Center content of the top bar.
|
||||
* @param startContent Optional start content of the top bar.
|
||||
* @param onStartContentClick Optional click action for the start content.
|
||||
* @param endContent Optional end content of the top bar.
|
||||
* @param onEndContentClick Optional click action for the end content.
|
||||
* @param isGhostButtons Flag to determine if ghost button styling should be applied.
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
internal fun TangemTopBarInner(
|
||||
modifier: Modifier = Modifier,
|
||||
content: (@Composable () -> Unit)? = null,
|
||||
startContent: (@Composable () -> Unit)? = null,
|
||||
onStartContentClick: (() -> Unit)? = null,
|
||||
endContent: (@Composable () -> Unit)? = null,
|
||||
onEndContentClick: (() -> Unit)? = null,
|
||||
isGhostButtons: Boolean = false,
|
||||
) {
|
||||
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(density = this).toDp() }
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(TangemTheme.dimens2.x16 + statusBarHeight)
|
||||
.fillMaxWidth()
|
||||
.padding(top = statusBarHeight)
|
||||
.padding(TangemTheme.dimens2.x4, TangemTheme.dimens2.x3),
|
||||
) {
|
||||
val iconModifier = Modifier
|
||||
.size(TangemTheme.dimens2.x10)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens2.x25))
|
||||
.conditionalCompose(isGhostButtons) {
|
||||
background(TangemTheme.colors2.button.backgroundSecondary)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = startContent != null,
|
||||
modifier = Modifier.align(Alignment.CenterStart),
|
||||
label = "Start Content Visibility",
|
||||
) {
|
||||
Box(
|
||||
modifier = iconModifier
|
||||
.conditional(onStartContentClick != null) {
|
||||
clickableSingle { onStartContentClick?.invoke() }
|
||||
}
|
||||
.conditionalCompose(isGhostButtons) { padding(TangemTheme.dimens2.x1) },
|
||||
) {
|
||||
startContent?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = content != null,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
) {
|
||||
content?.invoke()
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = endContent != null,
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
label = "End Content Visibility",
|
||||
) {
|
||||
Box(
|
||||
modifier = iconModifier
|
||||
.conditional(onEndContentClick != null) {
|
||||
clickableSingle { onEndContentClick?.invoke() }
|
||||
}
|
||||
.conditionalCompose(isGhostButtons) { padding(TangemTheme.dimens2.x1) },
|
||||
) {
|
||||
endContent?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ object TangemColorPalette {
|
|||
// endregion Dark
|
||||
|
||||
// region Dark Alpha
|
||||
val Dark_05 = Color(0x0D1E1E1E)
|
||||
val Dark_10 = Color(0x1A1E1E1E)
|
||||
val Dark_20 = Color(0x331E1E1E)
|
||||
val Dark_30 = Color(0x4D1E1E1E)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class TangemColors2 internal constructor(
|
|||
val skeleton: Skeleton,
|
||||
val markers: Markers,
|
||||
val tabs: Tabs,
|
||||
val contextMenu: ContextMenu,
|
||||
) {
|
||||
|
||||
@Stable
|
||||
|
|
@ -332,15 +333,23 @@ class TangemColors2 internal constructor(
|
|||
class Neutral internal constructor(
|
||||
primary: Color,
|
||||
secondary: Color,
|
||||
tertiary: Color,
|
||||
quaternary: Color,
|
||||
) {
|
||||
var primary by mutableStateOf(primary)
|
||||
private set
|
||||
var secondary by mutableStateOf(secondary)
|
||||
private set
|
||||
var tertiary by mutableStateOf(tertiary)
|
||||
private set
|
||||
var quaternary by mutableStateOf(quaternary)
|
||||
private set
|
||||
|
||||
fun update(other: Neutral) {
|
||||
primary = other.primary
|
||||
secondary = other.secondary
|
||||
tertiary = other.tertiary
|
||||
quaternary = other.quaternary
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -612,6 +621,18 @@ class TangemColors2 internal constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
class ContextMenu internal constructor(
|
||||
background: Color,
|
||||
) {
|
||||
var background by mutableStateOf(background)
|
||||
private set
|
||||
|
||||
fun update(other: ContextMenu) {
|
||||
background = other.background
|
||||
}
|
||||
}
|
||||
|
||||
fun update(other: TangemColors2) {
|
||||
text.update(other.text)
|
||||
graphic.update(other.graphic)
|
||||
|
|
@ -625,5 +646,6 @@ class TangemColors2 internal constructor(
|
|||
skeleton.update(other.skeleton)
|
||||
markers.update(other.markers)
|
||||
tabs.update(other.tabs)
|
||||
contextMenu.update(other.contextMenu)
|
||||
}
|
||||
}
|
||||
|
|
@ -79,6 +79,8 @@ private fun lightThemeColors2(): TangemColors2 {
|
|||
neutral = TangemColors2.Border.Neutral(
|
||||
primary = TangemColorPalette.Light3,
|
||||
secondary = TangemColorPalette.Light5,
|
||||
tertiary = TangemColorPalette.Light_10,
|
||||
quaternary = TangemColorPalette.Dark_10,
|
||||
),
|
||||
status = TangemColors2.Border.Status(
|
||||
accent = TangemColorPalette.Azure,
|
||||
|
|
@ -186,6 +188,9 @@ private fun lightThemeColors2(): TangemColors2 {
|
|||
backgroundTertiary = TangemColorPalette.White,
|
||||
backgroundQuaternary = TangemColorPalette.Dark_20,
|
||||
)
|
||||
val contextMenu = TangemColors2.ContextMenu(
|
||||
background = TangemColorPalette.Dark_05,
|
||||
)
|
||||
return TangemColors2(
|
||||
text = text,
|
||||
graphic = graphic,
|
||||
|
|
@ -199,6 +204,7 @@ private fun lightThemeColors2(): TangemColors2 {
|
|||
skeleton = skeleton,
|
||||
markers = markers,
|
||||
tabs = tabs,
|
||||
contextMenu = contextMenu,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -241,6 +247,8 @@ private fun darkThemeColors2(): TangemColors2 {
|
|||
neutral = TangemColors2.Border.Neutral(
|
||||
primary = TangemColorPalette.Dark4,
|
||||
secondary = TangemColorPalette.Dark4,
|
||||
tertiary = TangemColorPalette.Light_10,
|
||||
quaternary = TangemColorPalette.Light_10,
|
||||
),
|
||||
status = TangemColors2.Border.Status(
|
||||
accent = TangemColorPalette.Azure,
|
||||
|
|
@ -348,6 +356,9 @@ private fun darkThemeColors2(): TangemColors2 {
|
|||
backgroundTertiary = TangemColorPalette.Light_10,
|
||||
backgroundQuaternary = TangemColorPalette.Light_10,
|
||||
)
|
||||
val contextMenu = TangemColors2.ContextMenu(
|
||||
background = TangemColorPalette.Light_10,
|
||||
)
|
||||
return TangemColors2(
|
||||
text = text,
|
||||
graphic = graphic,
|
||||
|
|
@ -361,5 +372,6 @@ private fun darkThemeColors2(): TangemColors2 {
|
|||
skeleton = skeleton,
|
||||
markers = markers,
|
||||
tabs = tabs,
|
||||
contextMenu = contextMenu,
|
||||
)
|
||||
}
|
||||
|
|
@ -45,6 +45,11 @@ internal data class TangemTokenRowStory(
|
|||
val onBalanceHiddenToggle: () -> Unit,
|
||||
) : StoryBookPage
|
||||
|
||||
internal data class TangemContextMenuStory(
|
||||
val isExpanded: Boolean,
|
||||
val onExpandedChange: (Boolean) -> Unit,
|
||||
) : StoryBookPage
|
||||
|
||||
internal data class TangemHeaderRowStory(
|
||||
val isBalanceHidden: Boolean,
|
||||
val onBalanceHiddenToggle: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.feature.tester.presentation.storybook.page.contextmenu
|
||||
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemContextMenuStory
|
||||
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
|
||||
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
|
||||
|
||||
internal fun StateUpdater<TangemContextMenuStory>.build(): TangemContextMenuStory {
|
||||
return TangemContextMenuStory(
|
||||
isExpanded = false,
|
||||
onExpandedChange = { expanded ->
|
||||
updateStory { it.copy(isExpanded = expanded) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal val tangemContextMenuStoryFactory
|
||||
get() = storyPageFactory(StateUpdater<TangemContextMenuStory>::build)
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.feature.tester.presentation.storybook.page.contextmenu
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.ds.contextmenu.TangemContextMenu
|
||||
import com.tangem.core.ui.ds.contextmenu.TangemContextMenuCheckboxItem
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemContextMenuStory as TangemContextMenuStoryState
|
||||
|
||||
@Composable
|
||||
internal fun TangemContextMenuStory(state: TangemContextMenuStoryState, modifier: Modifier = Modifier) {
|
||||
val hazeState = rememberHazeState()
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = modifier
|
||||
.statusBarsPadding()
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors2.surface.level1)
|
||||
.hazeSourceTangem(state = hazeState, zIndex = -1f),
|
||||
) {
|
||||
item("context_menu") {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "TangemContextMenu",
|
||||
style = TangemTheme.typography2.headingSemibold17,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
)
|
||||
|
||||
Box {
|
||||
PrimaryButton(
|
||||
text = "Show Context Menu",
|
||||
onClick = { state.onExpandedChange(true) },
|
||||
)
|
||||
|
||||
TangemContextMenu(
|
||||
expanded = state.isExpanded,
|
||||
onDismissRequest = { state.onExpandedChange(false) },
|
||||
offset = DpOffset(0.dp, 4.dp),
|
||||
modifier = Modifier.hazeEffectTangem(hazeState),
|
||||
) {
|
||||
TangemContextMenuCheckboxItem(
|
||||
title = TextReference.Str("Sort by balance"),
|
||||
isChecked = true,
|
||||
onClick = {},
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = 0.5.dp,
|
||||
color = TangemTheme.colors2.border.neutral.quaternary,
|
||||
)
|
||||
TangemContextMenuCheckboxItem(
|
||||
title = TextReference.Str("Group tokens"),
|
||||
isChecked = false,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,12 +18,13 @@ import com.tangem.feature.tester.presentation.storybook.entity.StoryPageFactory
|
|||
import com.tangem.feature.tester.presentation.storybook.page.background.northernLightsStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.badge.tangemBadgeStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.buttons.buttonsStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.opportunities.opportunitiesBGStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.message.tangemMessageStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.checkbox.tangemCheckboxStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.contextmenu.tangemContextMenuStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.headerrow.tangemHeaderRowStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.message.tangemMessageStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.opportunities.opportunitiesBGStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.tabs.tangemSegmentedPickerStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.tangemTokenRowStoryFactory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.headerrow.tangemHeaderRowStoryFactory
|
||||
|
||||
private data class StoryItem(val title: String, val factory: StoryPageFactory)
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ private fun buildStories() = listOf(
|
|||
StoryItem(title = "☑️ Checkbox", factory = tangemCheckboxStoryFactory),
|
||||
StoryItem(title = "🪙 Token Row", factory = tangemTokenRowStoryFactory),
|
||||
StoryItem(title = "📑 Header Row", factory = tangemHeaderRowStoryFactory),
|
||||
StoryItem(title = "📋 Context Menu", factory = tangemContextMenuStoryFactory),
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.feature.tester.presentation.storybook.entity.StoryBookUM
|
|||
import com.tangem.feature.tester.presentation.storybook.entity.StoryList
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckboxStory
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemHeaderRowStory
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemContextMenuStory
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemMessageStory
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemSegmentedPickerStory
|
||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowStory
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.feature.tester.presentation.storybook.page.message.TangemMessa
|
|||
import com.tangem.feature.tester.presentation.storybook.page.tabs.TangemSegmentedPickerStory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.TangemTokenRowStory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.headerrow.TangemHeaderRowStory
|
||||
import com.tangem.feature.tester.presentation.storybook.page.contextmenu.TangemContextMenuStory
|
||||
|
||||
@Composable
|
||||
internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -45,6 +47,7 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier)
|
|||
TangemSegmentedPickerStory -> TangemSegmentedPickerStory()
|
||||
is TangemTokenRowStory -> TangemTokenRowStory(state = storyState)
|
||||
is TangemHeaderRowStory -> TangemHeaderRowStory(state = storyState)
|
||||
is TangemContextMenuStory -> TangemContextMenuStory(state = storyState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,22 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
|
||||
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.LocalPowerSavingState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
|
|
@ -46,23 +45,27 @@ internal fun WalletTopBar(
|
|||
color = Color.Unspecified,
|
||||
contentColor = Color.Unspecified,
|
||||
modifier = Modifier.hazeEffectTangem {
|
||||
progressive =
|
||||
HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f)
|
||||
progressive = HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f)
|
||||
},
|
||||
) {
|
||||
val isPowerSaving by LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsStateWithLifecycle()
|
||||
|
||||
val wrappedBalance = remember(behavior.state.collapsedFraction) {
|
||||
if (behavior.state.collapsedFraction > VISIBILITY_THRESHOLD) walletBalance else null
|
||||
}
|
||||
|
||||
TangemTopBar(
|
||||
title = wrappedBalance,
|
||||
startIconRes = R.drawable.ic_tangem_24,
|
||||
endIconRes = R.drawable.ic_more_default_24,
|
||||
onEndContentClick = topBarConfig.onDetailsClick,
|
||||
isGhostButtons = !isPowerSaving,
|
||||
startActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
isActionable = false,
|
||||
),
|
||||
endActionUM = TangemTopBarActionUM(
|
||||
iconRes = R.drawable.ic_more_default_24,
|
||||
isActionable = true,
|
||||
onClick = topBarConfig.onDetailsClick,
|
||||
ghostModeProgress = behavior.state.collapsedFraction,
|
||||
),
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.testTag(MainScreenTestTags.TOP_BAR),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue