Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-17 20:20:52 +05:00
parent 5da322576b
commit c037bec366
15 changed files with 782 additions and 116 deletions

View file

@ -80,6 +80,7 @@ fun GhostTangemButton(
contentColor = contentColor,
isEnabled = isEnabled,
isLoading = isLoading,
hasPadding = false,
size = size,
iconPosition = iconPosition,
iconRes = iconRes,

View file

@ -43,8 +43,9 @@ private const val LOADING_ANIMATION_DURATION = 150
* @param text TextReference for the button label.
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
* @param iconPosition Position of the icon (Start or End).
* @param isEnabled Boolean indicating whether the button is enabled.
* @param isEnabled Boolean indicating whether the button is enabled.
* @param isLoading Boolean indicating whether the button is in a loading state.
* @param hasPadding Boolean indicating whether the button should have padding around its content.
* @param contentColor Color of the button content (text and icon).
* @param size TangemButtonSize defining the size of the button.
*
@ -60,6 +61,7 @@ internal fun TangemButtonInternal(
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
isEnabled: Boolean = true,
isLoading: Boolean = false,
hasPadding: Boolean = true,
contentColor: Color = TangemTheme.colors2.text.neutral.primary,
size: TangemButtonSize = TangemButtonSize.X15,
) {
@ -72,7 +74,7 @@ internal fun TangemButtonInternal(
.conditionalCompose(text == null) {
width(size.toHeightDp())
}
.conditionalCompose(text != null) {
.conditionalCompose(text != null && hasPadding) {
padding(size.toPaddingDp())
}
.animateContentSize(),

View file

@ -0,0 +1,346 @@
package com.tangem.core.ui.ds.field.search
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextOverflow
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.util.fastForEach
import com.tangem.core.ui.R
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.ds.button.GhostTangemButton
import com.tangem.core.ui.ds.button.TangemButtonSize
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
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.BaseSearchBarTestTags
import com.tangem.core.ui.test.SearchBarTestTags
/**
* Shape options for [TangemSearchField].
*/
enum class TangemFieldShape {
RoundedCorners,
Circle,
;
@ReadOnlyComposable
@Composable
internal fun toShape(): Shape {
return when (this) {
RoundedCorners -> RoundedCornerShape(TangemTheme.dimens2.x4)
Circle -> CircleShape
}
}
}
/**
* Custom search field component that provides a user-friendly interface for searching and filtering content.
*
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8445-19217&m=dev)
*
* @param state The state of the search field, including the current query, placeholder text, and active status.
* @param shape The shape of the search field, which can be either rounded corners or a circle.
* @param modifier The modifier to be applied to the search field.
* @param enabled Whether the search field is enabled or not.
* @param focusRequester The [FocusRequester] used to request focus on the search field when it becomes active.
*/
@Composable
fun TangemSearchField(
state: SearchBarUM,
shape: TangemFieldShape,
modifier: Modifier = Modifier,
enabled: Boolean = true,
focusRequester: FocusRequester = remember { FocusRequester() },
) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val interactionSource = remember { MutableInteractionSource() }
var isInitialComposition by rememberSaveable { mutableStateOf(true) }
LaunchedEffect(Unit) {
isInitialComposition = false
}
BasicTextField(
modifier = modifier
.heightIn(min = TangemTheme.dimens2.x11)
.onFocusChanged { focusState ->
if (!isInitialComposition) {
if (focusState.isFocused) {
state.onActiveChange(true)
} else {
state.onActiveChange(false)
}
}
}
.focusRequester(focusRequester)
.testTag(BaseSearchBarTestTags.SEARCH_BAR),
enabled = enabled,
value = state.query,
onValueChange = state.onQueryChange,
interactionSource = interactionSource,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
keyboardController?.hide()
focusManager.clearFocus()
},
),
singleLine = true,
maxLines = 1,
textStyle = TangemTheme.typography2.bodySemibold16.copy(
color = TangemTheme.colors2.text.neutral.primary,
),
cursorBrush = SolidColor(TangemTheme.colors2.graphic.neutral.primary),
decorationBox = @Composable { innerTextField ->
DecorationBox(
state = state,
shape = shape,
innerTextField = innerTextField,
focusManager = focusManager,
interactionSource = interactionSource,
color = TangemTheme.colors2.field.backgroundDefault,
keyboardController = keyboardController,
)
},
)
}
@Suppress("LongParameterList")
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun DecorationBox(
state: SearchBarUM,
shape: TangemFieldShape,
focusManager: FocusManager,
interactionSource: MutableInteractionSource,
color: Color,
keyboardController: SoftwareKeyboardController?,
innerTextField: @Composable () -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
val isFocused by interactionSource.collectIsFocusedAsState()
val alignmentBias by animateFloatAsState(
targetValue = if (isFocused) -1f else 0f,
animationSpec = tween(),
label = "AlignmentBias",
)
Box(
contentAlignment = BiasAlignment(horizontalBias = alignmentBias, verticalBias = 0f),
modifier = Modifier
.weight(1f)
.background(color, shape.toShape())
.padding(TangemTheme.dimens2.x3),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Icon(
modifier = Modifier
.size(TangemTheme.dimens2.x5)
.testTag(SearchBarTestTags.ICON),
painter = painterResource(id = R.drawable.ic_search_default_24),
tint = TangemTheme.colors2.graphic.neutral.tertiaryConstant,
contentDescription = null,
)
Field(
state = state,
innerTextField = innerTextField,
)
ClearButton(
state = state,
)
}
}
CancelButton(
state = state,
keyboardController = keyboardController,
focusManager = focusManager,
isActive = isFocused,
)
}
}
@Composable
private fun Field(state: SearchBarUM, innerTextField: @Composable () -> Unit) {
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier
.heightIn(min = TangemTheme.dimens2.x5)
.width(IntrinsicSize.Max),
) {
innerTextField()
val placeholderOpacity by remember(state.query) {
derivedStateOf {
if (state.query.isNotEmpty()) {
0f
} else {
1f
}
}
}
Text(
text = state.placeholderText.resolveReference(),
color = TangemTheme.colors2.text.neutral.tertiary,
style = TangemTheme.typography2.bodySemibold16,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.testTag(SearchBarTestTags.PLACEHOLDER_TEXT)
.alpha(placeholderOpacity),
)
}
}
@Composable
private fun ClearButton(state: SearchBarUM) {
if (state.query.isNotEmpty()) {
Box(modifier = Modifier.fillMaxWidth()) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_close_new_20),
tint = TangemTheme.colors2.graphic.neutral.tertiary,
contentDescription = null,
modifier = Modifier
.align(Alignment.CenterEnd)
.size(TangemTheme.dimens2.x5)
.clip(CircleShape)
.clickable(
onClick = state.onClearClick,
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = false),
)
.testTag(SearchBarTestTags.CLEAR_BUTTON),
)
}
}
}
@Composable
private fun CancelButton(
state: SearchBarUM,
isActive: Boolean,
focusManager: FocusManager,
keyboardController: SoftwareKeyboardController?,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isActive,
enter = expandHorizontally(),
exit = shrinkHorizontally(),
modifier = modifier,
) {
GhostTangemButton(
text = resourceReference(R.string.common_cancel),
size = TangemButtonSize.X9,
modifier = Modifier.padding(start = TangemTheme.dimens2.x3),
onClick = {
if (state.query.isNotEmpty()) {
state.onQueryChange("")
}
focusManager.clearFocus()
keyboardController?.hide()
state.onActiveChange(false)
state.onClearClick()
},
)
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TangemSearchField_Preview(@PreviewParameter(TangemSearchFieldPreviewProvider::class) params: SearchBarUM) {
var state by remember { mutableStateOf(params) }
TangemThemePreviewRedesign {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x4),
modifier = Modifier
.background(TangemTheme.colors2.surface.level1)
.padding(TangemTheme.dimens2.x4),
) {
TangemFieldShape.entries.fastForEach { shape ->
TangemSearchField(
state = state.copy(
onActiveChange = { state = state.copy(isActive = it) },
onQueryChange = { state = state.copy(query = it) },
onClearClick = { state = state.copy(query = "") },
),
shape = shape,
)
}
}
}
}
private class TangemSearchFieldPreviewProvider : PreviewParameterProvider<SearchBarUM> {
override val values: Sequence<SearchBarUM>
get() = sequenceOf(
SearchBarUM(
placeholderText = stringReference("Crypto, news and more"),
query = "BTC",
onQueryChange = {},
isActive = true,
onActiveChange = {},
),
SearchBarUM(
placeholderText = stringReference("Crypto, news and more"),
query = "",
onQueryChange = {},
isActive = false,
onActiveChange = {},
),
)
}
// endregion

View file

@ -5,23 +5,18 @@ 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.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
@ -48,6 +43,7 @@ fun TangemTopBar(
title: TextReference? = null,
subtitle: TextReference? = null,
startAction: TangemTopBarActionUM? = null,
type: TangemTopBarType = TangemTopBarType.Default,
endActions: ImmutableList<TangemTopBarActionUM> = persistentListOf(),
@DrawableRes titleIconRes: Int? = null,
) {
@ -55,17 +51,21 @@ fun TangemTopBar(
title = title,
subtitle = subtitle,
titleIconRes = titleIconRes,
type = type,
modifier = modifier,
startContent = if (startAction != null) {
{ TangemTopBarActionContent(startAction) }
{ TangemTopBarActionContent(actionUM = startAction, type = type) }
} else {
null
},
endContent = if (endActions.isNotEmpty()) {
{
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x5)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x5),
) {
endActions.forEach { action ->
TangemTopBarActionContent(action)
TangemTopBarActionContent(actionUM = action, type = type)
}
}
}
@ -88,54 +88,88 @@ fun TangemTopBar(
@Composable
fun TangemTopBar(
modifier: Modifier = Modifier,
type: TangemTopBarType = TangemTopBarType.Default,
title: TextReference? = null,
subtitle: TextReference? = null,
@DrawableRes titleIconRes: Int? = null,
startContent: @Composable (() -> Unit)? = null,
endContent: @Composable (() -> Unit)? = null,
) {
Box(
TangemTopBar(
modifier = modifier,
type = type,
startContent = startContent,
endContent = endContent,
content = {
Column(
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,
)
}
}
},
)
}
/**
* 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 modifier Modifier to be applied to the top bar.
* @param type Type of the top bar, which determines its size and padding.
* @param content Composable content to be displayed in the center of the top bar
* @param startContent Optional composable content to be displayed at the start (left) of the top bar.
* @param endContent Optional composable content to be displayed at the end (right) of the top bar.
*/
@Composable
fun TangemTopBar(
modifier: Modifier = Modifier,
type: TangemTopBarType = TangemTopBarType.Default,
content: @Composable () -> Unit,
startContent: @Composable (() -> Unit)? = null,
endContent: @Composable (() -> Unit)? = null,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier
.fillMaxWidth()
.height(TangemTheme.dimens2.x16)
.padding(TangemTheme.dimens2.x4, TangemTheme.dimens2.x3),
.heightIn(min = type.getSize())
.padding(type.getPadding()),
) {
AnimatedVisibility(
visible = startContent != null,
modifier = Modifier.align(Alignment.CenterStart),
AnimatedContent(
targetState = startContent != null,
modifier = Modifier.size(TangemTheme.dimens2.x11),
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,
)
) { isVisible ->
if (isVisible) {
startContent?.invoke()
}
}
AnimatedVisibility(
visible = endContent != null,
modifier = Modifier.align(Alignment.CenterEnd),
content()
AnimatedContent(
targetState = endContent != null,
modifier = Modifier.size(TangemTheme.dimens2.x11),
label = "End Content Visibility",
) {
endContent?.invoke()
) { isVisible ->
if (isVisible) {
endContent?.invoke()
}
}
}
}
@ -151,7 +185,6 @@ private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes:
val wrappedTitle = remember(this) { requireNotNull(title) }
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(
space = TangemTheme.dimens2.x1,
alignment = Alignment.CenterHorizontally,
@ -184,33 +217,6 @@ private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes:
}
}
@Composable
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 = actionUM.iconRes),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
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),
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 375)
@ -221,6 +227,7 @@ private fun TangemTopBar_Preview(@PreviewParameter(PreviewProvider::class) param
title = params.title,
subtitle = params.subtitle,
titleIconRes = params.titleIconRes,
type = TangemTopBarType.BottomSheet,
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
startContent = params.startActionUM?.let { { TangemTopBarActionContent(it) } },
endContent = if (params.endActions.isNotEmpty()) {
@ -256,11 +263,13 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
onClick = {},
isActionable = false,
),
endActions = persistentListOf(TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
)),
endActions = persistentListOf(
TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
),
),
),
TangemTopBarPreviewData(
title = stringReference("Title"),
@ -271,12 +280,14 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
isActionable = true,
ghostModeProgress = 1f,
),
endActions = persistentListOf(TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = true,
ghostModeProgress = 1f,
)),
endActions = persistentListOf(
TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = true,
ghostModeProgress = 1f,
),
),
),
TangemTopBarPreviewData(
title = stringReference("Title"),
@ -287,12 +298,14 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
onClick = {},
isActionable = false,
),
endActions = persistentListOf(TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = true,
ghostModeProgress = 1f,
)),
endActions = persistentListOf(
TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = true,
ghostModeProgress = 1f,
),
),
),
TangemTopBarPreviewData(
subtitle = stringReference("Subtitle"),
@ -302,19 +315,23 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
onClick = {},
isActionable = true,
),
endActions = persistentListOf(TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
)),
endActions = persistentListOf(
TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
),
),
),
TangemTopBarPreviewData(
title = stringReference("Title"),
endActions = persistentListOf(TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
)),
endActions = persistentListOf(
TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
),
),
),
TangemTopBarPreviewData(
title = stringReference("Title"),
@ -353,11 +370,13 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
onClick = {},
isActionable = true,
),
endActions = persistentListOf(TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
)),
endActions = persistentListOf(
TangemTopBarActionUM(
iconRes = R.drawable.ic_more_default_24,
onClick = {},
isActionable = false,
),
),
),
)
}

View file

@ -0,0 +1,53 @@
package com.tangem.core.ui.ds.topbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
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 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
/**
* Composable for top bar action content
*
* @param actionUM user model for top bar action
* @param modifier modifier for this composable
* @param type type of top bar, affects size and padding of action content
*/
@Composable
fun TangemTopBarActionContent(
actionUM: TangemTopBarActionUM,
modifier: Modifier = Modifier,
type: TangemTopBarType = TangemTopBarType.Default,
) {
val background = lerp(
start = Color.Transparent,
stop = TangemTheme.colors2.button.backgroundSecondary,
fraction = actionUM.ghostModeProgress,
)
val padding = (TangemTheme.dimens2.x11 - type.getSideContentSize()) / 2
Icon(
imageVector = ImageVector.vectorResource(id = actionUM.iconRes),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.primary,
modifier = modifier
.size(TangemTheme.dimens2.x11)
.clip(CircleShape)
.conditional(actionUM.isActionable) { background(background) }
.conditionalCompose(actionUM.isActionable && actionUM.onClick != null) {
clickableSingle(onClick = requireNotNull(actionUM.onClick))
}
.padding(padding),
)
}

View file

@ -0,0 +1,45 @@
package com.tangem.core.ui.ds.topbar
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.res.TangemTheme
/**
* Type of top bar, affects size and padding of content
*
* @see TangemTopBar
*/
enum class TangemTopBarType {
Default,
BottomSheet,
;
@ReadOnlyComposable
@Composable
fun getSize(): Dp {
return when (this) {
Default -> TangemTheme.dimens2.x16
BottomSheet -> TangemTheme.dimens2.x19
}
}
@ReadOnlyComposable
@Composable
fun getSideContentSize(): Dp {
return when (this) {
Default -> TangemTheme.dimens2.x8
BottomSheet -> TangemTheme.dimens2.x7
}
}
@ReadOnlyComposable
@Composable
fun getPadding(): PaddingValues {
return when (this) {
Default -> PaddingValues(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3)
BottomSheet -> PaddingValues(TangemTheme.dimens2.x4)
}
}
}

View file

@ -3,6 +3,8 @@
package com.tangem.core.ui.res
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@ -31,15 +33,27 @@ fun TangemThemeRedesign(content: @Composable () -> Unit) {
LocalTangemTypography2 provides TangemTypography2(InterFamily),
LocalRootBackgroundColor provides remember(rootBackgroundColor) { mutableStateOf(rootBackgroundColor) },
) {
ProvideHaze {
Box(Modifier.hazeSourceTangem(zIndex = 1f)) {
content()
CompositionLocalProvider(
LocalTextSelectionColors provides TangemTextSelectionColors2,
) {
ProvideHaze {
Box(Modifier.hazeSourceTangem(zIndex = 1f)) {
content()
}
}
}
}
}
}
private val TangemTextSelectionColors2: TextSelectionColors
@Composable
@ReadOnlyComposable
get() = TextSelectionColors(
handleColor = TangemTheme.colors2.text.neutral.primary,
backgroundColor = TangemTheme.colors2.text.neutral.primary.copy(alpha = 0.3f),
)
@Composable
@ReadOnlyComposable
private fun lightThemeColors2(): TangemColors2 {
@ -48,7 +62,7 @@ private fun lightThemeColors2(): TangemColors2 {
primary = TangemColorPalette.Dark6,
primaryInverted = TangemColorPalette.White,
secondary = TangemColorPalette.Dark2,
tertiary = TangemColorPalette.Dark3,
tertiary = TangemColorPalette.Dark1,
primaryInvertedConstant = TangemColorPalette.White,
),
status = TangemColors2.Text.Status(
@ -67,7 +81,7 @@ private fun lightThemeColors2(): TangemColors2 {
tertiary = TangemColorPalette.Dark3,
quaternary = TangemColorPalette.Light4,
primaryInvertedConstant = TangemColorPalette.White,
tertiaryConstant = TangemColorPalette.Dark3,
tertiaryConstant = TangemColorPalette.Dark1,
),
status = TangemColors2.Graphic.Status(
accent = TangemColorPalette.Azure,
@ -243,7 +257,7 @@ private fun darkThemeColors2(): TangemColors2 {
secondary = TangemColorPalette.Light5,
tertiary = TangemColorPalette.Dark3,
quaternary = TangemColorPalette.Dark3,
tertiaryConstant = TangemColorPalette.Dark3,
tertiaryConstant = TangemColorPalette.Dark1,
primaryInvertedConstant = TangemColorPalette.White,
),
status = TangemColors2.Graphic.Status(

View file

@ -1,10 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M9.5,3C11.224,3 12.877,3.685 14.096,4.904C15.315,6.123 16,7.776 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16C7.776,16 6.123,15.315 4.904,14.096C3.685,12.877 3,11.224 3,9.5C3,7.776 3.685,6.123 4.904,4.904C6.123,3.685 7.776,3 9.5,3ZM9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" />
<path
android:pathData="M4,10.535C4,9.636 4.17,8.793 4.51,8.006C4.856,7.214 5.331,6.518 5.936,5.919C6.541,5.314 7.241,4.843 8.034,4.506C8.834,4.169 9.687,4 10.594,4C11.507,4 12.361,4.169 13.154,4.506C13.954,4.843 14.656,5.314 15.261,5.919C15.866,6.518 16.338,7.214 16.678,8.006C17.024,8.793 17.197,9.636 17.197,10.535C17.197,11.241 17.086,11.913 16.865,12.55C16.649,13.187 16.349,13.767 15.966,14.292L19.66,17.969C19.773,18.076 19.857,18.202 19.911,18.346C19.97,18.491 20,18.643 20,18.804C20,19.029 19.949,19.232 19.846,19.414C19.743,19.596 19.603,19.738 19.425,19.839C19.247,19.947 19.041,20 18.809,20C18.647,20 18.49,19.971 18.339,19.912C18.193,19.858 18.061,19.772 17.942,19.655L14.224,15.97C13.705,16.312 13.138,16.583 12.523,16.781C11.912,16.973 11.269,17.07 10.594,17.07C9.687,17.07 8.834,16.901 8.034,16.564C7.241,16.227 6.541,15.759 5.936,15.159C5.331,14.56 4.856,13.866 4.51,13.08C4.17,12.288 4,11.439 4,10.535ZM5.726,10.535C5.726,11.204 5.85,11.83 6.098,12.413C6.352,12.997 6.703,13.511 7.151,13.955C7.6,14.394 8.118,14.739 8.707,14.991C9.296,15.242 9.925,15.368 10.594,15.368C11.269,15.368 11.901,15.242 12.49,14.991C13.079,14.739 13.595,14.394 14.038,13.955C14.486,13.511 14.837,12.997 15.091,12.413C15.344,11.83 15.471,11.204 15.471,10.535C15.471,9.871 15.344,9.248 15.091,8.664C14.837,8.081 14.486,7.567 14.038,7.123C13.595,6.679 13.079,6.333 12.49,6.087C11.901,5.836 11.269,5.71 10.594,5.71C9.925,5.71 9.296,5.836 8.707,6.087C8.118,6.333 7.6,6.679 7.151,7.123C6.703,7.567 6.352,8.081 6.098,8.664C5.85,9.248 5.726,9.871 5.726,10.535Z"
android:fillColor="#000000"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="20" android:viewportWidth="20" android:width="24dp">
<path android:fillColor="#919191" android:pathData="M8.666,2.333C12.164,2.333 15,5.169 15,8.666C15,10.054 14.551,11.336 13.794,12.38L17.373,15.959C17.764,16.35 17.764,16.983 17.373,17.373C16.983,17.764 16.35,17.764 15.959,17.373L12.38,13.794C11.336,14.551 10.054,15 8.666,15C5.169,15 2.333,12.164 2.333,8.666C2.333,5.169 5.169,2.333 8.666,2.333ZM8.666,4.333C6.273,4.333 4.333,6.273 4.333,8.666C4.333,11.059 6.273,13 8.666,13C11.059,13 13,11.059 13,8.666C13,6.273 11.059,4.333 8.666,4.333Z"/>
</vector>