From 721ab042e0401e58f2580fd23d49e33081a9c051 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 24 Dec 2025 20:54:19 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/core/ui/ds/topbar/TangemTopBar.kt | 239 ++++++++++++++++++ .../core/ui/ds/topbar/TangemTopBarInner.kt | 90 +++++++ 2 files changed, 329 insertions(+) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/topbar/TangemTopBar.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/ds/topbar/TangemTopBarInner.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/topbar/TangemTopBar.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/topbar/TangemTopBar.kt new file mode 100644 index 0000000000..b96dd8d788 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/topbar/TangemTopBar.kt @@ -0,0 +1,239 @@ +package com.tangem.core.ui.ds.topbar + +import android.content.res.Configuration +import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +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.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 com.tangem.core.ui.R +import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign + +/** + * 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. + * @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. + * +[REDACTED_AUTHOR] + */ +@Composable +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, + @DrawableRes titleIconRes: Int? = null, + titleStyle: TextStyle = TangemTheme.typography2.headingSemibold17, + isGhostButtons: Boolean = false, +) { + TangemTopBarInner( + modifier = modifier, + content = { + Column( + 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) } + } else { + null + }, + onStartContentClick = onStartContentClick, + endContent = if (endIconRes != null) { + { TangemTopBarIcon(iconRes = endIconRes) } + } else { + null + }, + onEndContentClick = onEndContentClick, + isGhostButtons = isGhostButtons, + ) +} + +@Composable +private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes: Int?, titleStyle: TextStyle) { + AnimatedVisibility( + visible = title != null, + label = "Title Visibility", + ) { + val wrappedTitle = remember(this) { requireNotNull(title) } + + Row( + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1), + verticalAlignment = Alignment.CenterVertically, + ) { + AnimatedVisibility( + visible = titleIconRes != null, + label = "Title Icon Visibility", + ) { + val wrappedTitleIconRes = remember(this) { + requireNotNull(titleIconRes) + } + Icon( + imageVector = ImageVector.vectorResource(id = wrappedTitleIconRes), + contentDescription = null, + tint = TangemTheme.colors2.graphic.neutral.primary, + modifier = Modifier.size(TangemTheme.dimens2.x4), + ) + } + + Text( + text = wrappedTitle.resolveAnnotatedReference(), + color = TangemTheme.colors2.text.neutral.primary, + style = titleStyle, + textAlign = TextAlign.Center, + maxLines = 1, + ) + } + } +} + +@Composable +private fun TangemTopBarIcon(@DrawableRes iconRes: Int) { + Icon( + imageVector = ImageVector.vectorResource(id = iconRes), + contentDescription = null, + tint = TangemTheme.colors2.graphic.neutral.primary, + modifier = Modifier.fillMaxSize(), + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 375) +@Preview(showBackground = true, widthDp = 375, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun TangemTopBar_Preview(@PreviewParameter(PreviewProvider::class) params: TangemTopBarPreviewData) { + TangemThemePreviewRedesign { + 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), + ) + } +} + +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, +) + +private class PreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + TangemTopBarPreviewData( + title = stringReference("Title"), + startIconRes = R.drawable.ic_tangem_24, + endIconRes = R.drawable.ic_more_vertical_24, + isGhostButtons = true, + ), + TangemTopBarPreviewData( + title = stringReference("Title"), + subtitle = stringReference("Subtitle"), + startIconRes = R.drawable.ic_tangem_24, + endIconRes = R.drawable.ic_more_vertical_24, + isGhostButtons = true, + ), + 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, + ), + 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, + ), + TangemTopBarPreviewData( + title = stringReference("Title"), + endIconRes = R.drawable.ic_more_vertical_24, + isGhostButtons = true, + ), + TangemTopBarPreviewData( + title = stringReference("Title"), + startIconRes = R.drawable.ic_tangem_24, + isGhostButtons = true, + ), + TangemTopBarPreviewData( + title = combinedReference( + stringReference("$ 46,112"), + styledStringReference( + value = ".30", + spanStyleReference = { + TangemTheme.typography.caption1.copy(TangemTheme.colors2.text.neutral.secondary) + .toSpanStyle() + }, + ), + ), + ), + TangemTopBarPreviewData( + title = combinedReference( + stringReference("$ 46,112"), + styledStringReference( + value = ".30", + spanStyleReference = { + TangemTheme.typography.caption1.copy(TangemTheme.colors2.text.neutral.secondary) + .toSpanStyle() + }, + ), + ), + startIconRes = R.drawable.ic_tangem_24, + endIconRes = R.drawable.ic_more_vertical_24, + ), + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/topbar/TangemTopBarInner.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/topbar/TangemTopBarInner.kt new file mode 100644 index 0000000000..1014c640e4 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/topbar/TangemTopBarInner.kt @@ -0,0 +1,90 @@ +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 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, +) { + Box( + modifier = modifier + .height(TangemTheme.dimens2.x16) + .fillMaxWidth() + .padding(TangemTheme.dimens2.x4, TangemTheme.dimens2.x3), + ) { + val iconModifier = Modifier + .size(TangemTheme.dimens2.x10) + .clip(RoundedCornerShape(TangemTheme.dimens2.x25)) + .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() + } + } + } +} \ No newline at end of file