Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-16 12:58:48 +05:00
parent 16a83cf56d
commit 9fb5124c38
30 changed files with 528 additions and 801 deletions

View file

@ -1,156 +1,293 @@
package com.tangem.core.ui.components.bottomsheets.message
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
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.res.painterResource
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 androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.R
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.isNullOrEmpty
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM.Button.IconOrder
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.components.icons.HighlightedIcon
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@Composable
fun MessageBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(config) { notification: MessageBottomSheetUM ->
Content(model = notification)
fun MessageBottomSheet(state: MessageBottomSheetUM, onDismissRequest: () -> Unit) {
val stateWithOnDismiss = remember(state) {
state.copy(
onDismissRequest = {
state.onDismissRequest.invoke()
onDismissRequest()
},
)
}
val config = TangemBottomSheetConfig(
isShown = true,
content = stateWithOnDismiss,
onDismissRequest = stateWithOnDismiss.onDismissRequest,
)
TangemModalBottomSheet(
config = config,
title = {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = stateWithOnDismiss.onDismissRequest,
)
},
content = { content: MessageBottomSheetUM -> MessageBottomSheetContent(content) },
)
}
@Composable
fun MessageBottomSheetContent(state: MessageBottomSheetUM, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
state.elements.fastForEach { element ->
when (element) {
is MessageBottomSheetUM.InfoBlock -> {
ContentContainer(
modifier = Modifier
.heightIn(min = TangemTheme.dimens.size180)
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(bottom = 32.dp),
state = element,
)
}
else -> Unit
}
}
ButtonsContainer(
modifier = Modifier.fillMaxWidth(),
closeScope = state.closeScope,
buttons = state.elements.filterIsInstance<MessageBottomSheetUM.Button>().toPersistentList(),
)
}
}
@Composable
internal fun Content(model: MessageBottomSheetUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(40.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(modifier = Modifier)
if (model.iconResId != null) {
Icon(
modifier = Modifier.size(48.dp),
painter = painterResource(model.iconResId),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
private fun ContentContainer(state: MessageBottomSheetUM.InfoBlock, modifier: Modifier = Modifier) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
BottomSheetIconContainer(state.icon, state.iconImage)
state.title?.let { title ->
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing24),
text = title.resolveReference(),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
}
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (!model.title.isNullOrEmpty()) {
Text(
text = model.title.resolveReference(),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
}
state.body?.let { body ->
Text(
text = model.message.resolveReference(),
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing8),
text = body.resolveAnnotatedReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
}
Column(
modifier = Modifier
.padding(bottom = 16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (model.primaryAction != null) {
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = model.primaryAction.text.resolveReference(),
enabled = model.primaryAction.isEnabled,
onClick = model.primaryAction.onClick,
)
}
if (model.secondaryAction != null) {
SecondaryButton(
modifier = Modifier.fillMaxWidth(),
text = model.secondaryAction.text.resolveReference(),
enabled = model.secondaryAction.isEnabled,
onClick = model.secondaryAction.onClick,
)
}
state.chip?.let { chip ->
BottomSheetChip(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing16),
chip = chip,
)
}
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun Preview_NotificationBottomSheet(
@PreviewParameter(MessageBottomSheetUMPreviewProvider::class) params: MessageBottomSheetUM,
private fun BottomSheetIconContainer(
icon: MessageBottomSheetUM.Icon?,
iconImage: MessageBottomSheetUM.IconImage?,
modifier: Modifier = Modifier,
) {
TangemThemePreview {
MessageBottomSheet(
config = TangemBottomSheetConfig(
content = params,
isShown = true,
onDismissRequest = {},
),
if (icon != null) {
BottomSheetIcon(icon, modifier)
} else if (iconImage != null) {
Image(
modifier = modifier
.size(TangemTheme.dimens.size56)
.clip(CircleShape),
painter = painterResource(id = iconImage.res),
contentDescription = null,
)
}
}
private class MessageBottomSheetUMPreviewProvider : PreviewParameterProvider<MessageBottomSheetUM> {
val balancesHiddenMessage = MessageBottomSheetUM(
iconResId = R.drawable.ic_eye_off_outline_24,
title = resourceReference(R.string.balance_hidden_title),
message = resourceReference(R.string.balance_hidden_description),
primaryAction = MessageBottomSheetUM.ActionUM(
text = resourceReference(R.string.balance_hidden_got_it_button),
onClick = {},
),
secondaryAction = MessageBottomSheetUM.ActionUM(
text = resourceReference(R.string.balance_hidden_do_not_show_button),
onClick = {},
),
)
@Composable
private fun BottomSheetIcon(icon: MessageBottomSheetUM.Icon, modifier: Modifier = Modifier) {
val tint = when (icon.type) {
MessageBottomSheetUM.Icon.Type.Unspecified -> Color.Unspecified
MessageBottomSheetUM.Icon.Type.Accent -> TangemTheme.colors.icon.accent
MessageBottomSheetUM.Icon.Type.Informative -> TangemTheme.colors.icon.informative
MessageBottomSheetUM.Icon.Type.Attention -> TangemTheme.colors.icon.attention
MessageBottomSheetUM.Icon.Type.Warning -> TangemTheme.colors.icon.warning
}
override val values: Sequence<MessageBottomSheetUM>
get() = sequenceOf(
balancesHiddenMessage,
balancesHiddenMessage.copy(title = null),
balancesHiddenMessage.copy(iconResId = null),
balancesHiddenMessage.copy(primaryAction = null),
balancesHiddenMessage.copy(secondaryAction = null),
balancesHiddenMessage.copy(
primaryAction = null,
secondaryAction = null,
),
balancesHiddenMessage.copy(
title = null,
iconResId = null,
primaryAction = null,
secondaryAction = null,
),
)
val backgroundColor = when (icon.backgroundType) {
MessageBottomSheetUM.Icon.BackgroundType.Unspecified -> TangemTheme.colors.icon.informative
MessageBottomSheetUM.Icon.BackgroundType.SameAsTint -> tint
MessageBottomSheetUM.Icon.BackgroundType.Accent -> TangemTheme.colors.icon.accent
MessageBottomSheetUM.Icon.BackgroundType.Informative -> TangemTheme.colors.icon.informative
MessageBottomSheetUM.Icon.BackgroundType.Attention -> TangemTheme.colors.icon.attention
MessageBottomSheetUM.Icon.BackgroundType.Warning -> TangemTheme.colors.icon.warning
}
HighlightedIcon(
modifier = modifier,
icon = icon.res,
iconTint = tint,
backgroundColor = backgroundColor,
)
}
// endregion Preview
@Composable
private fun BottomSheetChip(chip: MessageBottomSheetUM.Chip, modifier: Modifier = Modifier) {
val color = when (chip.type) {
MessageBottomSheetUM.Chip.Type.Unspecified -> TangemTheme.colors.text.primary1
MessageBottomSheetUM.Chip.Type.Warning -> TangemTheme.colors.text.warning
}
Text(
modifier = modifier
.background(
shape = RoundedCornerShape(TangemTheme.dimens.radius16),
color = color.copy(alpha = 0.1F),
)
.padding(vertical = TangemTheme.dimens.spacing4, horizontal = TangemTheme.dimens.spacing12),
text = chip.text.resolveReference(),
style = TangemTheme.typography.caption1,
color = color,
)
}
@Suppress("LongMethod")
@Composable
private fun ButtonsContainer(
buttons: ImmutableList<MessageBottomSheetUM.Button>,
closeScope: MessageBottomSheetUM.CloseScope,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.padding(all = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
buttons.fastForEach { button ->
val icon = button.icon?.let { iconResId ->
when (button.iconOrder) {
IconOrder.Start -> TangemButtonIconPosition.Start(iconResId)
IconOrder.End -> TangemButtonIconPosition.End(iconResId)
}
} ?: TangemButtonIconPosition.None
TangemButton(
modifier = Modifier.fillMaxWidth(),
text = button.text?.resolveReference().orEmpty(),
icon = icon,
onClick = { button.onClick?.invoke(closeScope) },
colors = if (button.isPrimary) {
TangemButtonsDefaults.primaryButtonColors
} else {
TangemButtonsDefaults.secondaryButtonColors
},
enabled = true,
showProgress = false,
textStyle = TangemTheme.typography.subtitle1,
)
}
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
TangemThemePreview {
MessageBottomSheet(
messageBottomSheetUM {
infoBlock {
icon(R.drawable.img_knight_shield_32) {
type = MessageBottomSheetUM.Icon.Type.Attention
backgroundType = MessageBottomSheetUM.Icon.BackgroundType.SameAsTint
}
title = TextReference.Str("Title Title Title")
body = TextReference.Str("Body")
chip(text = TextReference.Str("Some chip information"))
}
primaryButton {
text = TextReference.Str("Test")
icon = R.drawable.ic_tangem_24
}
secondaryButton {
icon = R.drawable.ic_tangem_24
text = TextReference.Str("asdasd")
onClick {
closeBs()
}
}
},
onDismissRequest = {},
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview2() {
TangemThemePreview {
MessageBottomSheet(
messageBottomSheetUM {
infoBlock {
iconImage = MessageBottomSheetUM.IconImage(R.drawable.img_visa_notification)
title = TextReference.Str("Title Title Title")
body = TextReference.Str("Body")
chip(text = TextReference.Str("Some chip information"))
}
primaryButton {
text = TextReference.Str("Test")
icon = R.drawable.ic_tangem_24
}
secondaryButton {
icon = R.drawable.ic_tangem_24
text = TextReference.Str("asdasd")
onClick {
closeBs()
}
}
},
onDismissRequest = {},
)
}
}

View file

@ -1,20 +1,143 @@
package com.tangem.core.ui.components.bottomsheets.message
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
@Immutable
data class MessageBottomSheetUM(
@DrawableRes val iconResId: Int?,
val title: TextReference?,
val message: TextReference,
val primaryAction: ActionUM?,
val secondaryAction: ActionUM?,
var elements: ImmutableList<Element> = persistentListOf(),
var onDismissRequest: () -> Unit = {},
) : TangemBottomSheetConfigContent {
data class ActionUM(
val text: TextReference,
val isEnabled: Boolean = true,
val onClick: () -> Unit,
)
}
@Immutable
inner class CloseScope {
fun closeBs() {
onDismissRequest()
}
}
val closeScope = CloseScope()
@Immutable
sealed interface Element
@Immutable
data class Icon(
@DrawableRes internal var res: Int,
var type: Type = Type.Unspecified,
var backgroundType: BackgroundType = BackgroundType.Unspecified,
) : Element {
enum class Type {
Unspecified, Accent, Informative, Attention, Warning,
}
enum class BackgroundType {
Unspecified, SameAsTint, Accent, Informative, Attention, Warning,
}
}
@Immutable
data class IconImage(@DrawableRes internal var res: Int) : Element
@Immutable
data class Chip(
internal var text: TextReference,
var type: Type = Type.Unspecified,
) : Element {
enum class Type {
Unspecified, Warning
}
}
@Immutable
data class InfoBlock(
internal var icon: Icon? = null,
internal var iconImage: IconImage? = null,
internal var chip: Chip? = null,
var title: TextReference? = null,
var body: TextReference? = null,
) : Element
@Immutable
data class Button(
internal var isPrimary: Boolean = false,
var text: TextReference? = null,
@DrawableRes internal var iconInternal: Int? = null,
internal var iconOrder: IconOrder = IconOrder.Start,
var onClick: (CloseScope.() -> Unit)? = null,
) : Element {
var icon: Int? = iconInternal
set(value) {
iconOrder = if (text == null) {
IconOrder.Start
} else {
IconOrder.End
}
field = value
}
enum class IconOrder {
Start, End
}
}
}
@Target(AnnotationTarget.TYPE)
@DslMarker
annotation class MessageBottomSheetDsl
// region: DSL
fun messageBottomSheetUM(init: @MessageBottomSheetDsl MessageBottomSheetUM.() -> Unit) =
MessageBottomSheetUM().apply(init)
fun MessageBottomSheetUM.onDismiss(block: () -> Unit) = apply { onDismissRequest = block }
@Suppress("NestedScopeFunctions")
fun MessageBottomSheetUM.infoBlock(init: @MessageBottomSheetDsl MessageBottomSheetUM.InfoBlock.() -> Unit) = apply {
val element = MessageBottomSheetUM.InfoBlock().apply(init)
elements = (elements + element).toPersistentList()
}
@Suppress("NestedScopeFunctions")
fun MessageBottomSheetUM.InfoBlock.icon(@DrawableRes res: Int, init: MessageBottomSheetUM.Icon.() -> Unit = {}) =
apply {
icon = MessageBottomSheetUM.Icon(res).apply(init)
}
fun MessageBottomSheetUM.InfoBlock.iconImage(@DrawableRes res: Int) = apply {
iconImage = MessageBottomSheetUM.IconImage(res)
}
@Suppress("NestedScopeFunctions")
fun MessageBottomSheetUM.InfoBlock.chip(text: TextReference, init: MessageBottomSheetUM.Chip.() -> Unit = {}) = apply {
chip = MessageBottomSheetUM.Chip(text).apply(init)
}
@Suppress("NestedScopeFunctions")
internal fun MessageBottomSheetUM.button(init: @MessageBottomSheetDsl MessageBottomSheetUM.Button.() -> Unit) = apply {
val element = MessageBottomSheetUM.Button().apply(init)
elements = (elements + element).toPersistentList()
}
@Suppress("NestedScopeFunctions")
fun MessageBottomSheetUM.primaryButton(init: @MessageBottomSheetDsl MessageBottomSheetUM.Button.() -> Unit) = apply {
button { isPrimary = true; apply(init) }
}
@Suppress("NestedScopeFunctions")
fun MessageBottomSheetUM.secondaryButton(init: @MessageBottomSheetDsl MessageBottomSheetUM.Button.() -> Unit) = apply {
button { isPrimary = false; apply(init) }
}
fun MessageBottomSheetUM.Button.onClick(block: MessageBottomSheetUM.CloseScope.() -> Unit) = apply {
onClick = block
}
// endregion

View file

@ -1,142 +0,0 @@
package com.tangem.core.ui.components.bottomsheets.message
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
@Immutable
data class MessageBottomSheetUMV2(
var elements: ImmutableList<Element> = persistentListOf(),
var onDismissRequest: () -> Unit = {},
) : TangemBottomSheetConfigContent {
@Immutable
inner class CloseScope {
fun closeBs() {
onDismissRequest()
}
}
val closeScope = CloseScope()
@Immutable
sealed interface Element
@Immutable
data class Icon(
@DrawableRes internal var res: Int,
var type: Type = Type.Unspecified,
var backgroundType: BackgroundType = BackgroundType.Unspecified,
) : Element {
enum class Type {
Unspecified, Accent, Informative, Attention, Warning,
}
enum class BackgroundType {
Unspecified, SameAsTint, Accent, Informative, Attention, Warning,
}
}
@Immutable
data class IconImage(@DrawableRes internal var res: Int) : Element
@Immutable
data class Chip(
internal var text: TextReference,
var type: Type = Type.Unspecified,
) : Element {
enum class Type {
Unspecified, Warning
}
}
@Immutable
data class InfoBlock(
internal var icon: Icon? = null,
internal var iconImage: IconImage? = null,
internal var chip: Chip? = null,
var title: TextReference? = null,
var body: TextReference? = null,
) : Element
@Immutable
data class Button(
internal var isPrimary: Boolean = false,
var text: TextReference? = null,
@DrawableRes internal var iconInternal: Int? = null,
internal var iconOrder: IconOrder = IconOrder.Start,
var onClick: (CloseScope.() -> Unit)? = null,
) : Element {
var icon: Int? = iconInternal
set(value) {
iconOrder = if (text == null) {
IconOrder.Start
} else {
IconOrder.End
}
field = value
}
enum class IconOrder {
Start, End
}
}
}
@Target(AnnotationTarget.TYPE)
@DslMarker
annotation class MessageBottomSheetV2Dsl
// region: DSL
fun messageBottomSheetUM(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.() -> Unit) =
MessageBottomSheetUMV2().apply(init)
fun MessageBottomSheetUMV2.onDismiss(block: () -> Unit) = MessageBottomSheetUMV2().apply { onDismissRequest = block }
fun MessageBottomSheetUMV2.infoBlock(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.InfoBlock.() -> Unit) =
apply {
val element = MessageBottomSheetUMV2.InfoBlock().apply(init)
elements = (elements + element).toPersistentList()
}
fun MessageBottomSheetUMV2.InfoBlock.icon(@DrawableRes res: Int, init: MessageBottomSheetUMV2.Icon.() -> Unit = {}) =
apply {
icon = MessageBottomSheetUMV2.Icon(res).apply(init)
}
fun MessageBottomSheetUMV2.InfoBlock.iconImage(@DrawableRes res: Int) = apply {
iconImage = MessageBottomSheetUMV2.IconImage(res)
}
fun MessageBottomSheetUMV2.InfoBlock.chip(text: TextReference, init: MessageBottomSheetUMV2.Chip.() -> Unit = {}) =
apply {
chip = MessageBottomSheetUMV2.Chip(text).apply(init)
}
internal fun MessageBottomSheetUMV2.button(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.Button.() -> Unit) =
apply {
val element = MessageBottomSheetUMV2.Button().apply(init)
elements = (elements + element).toPersistentList()
}
fun MessageBottomSheetUMV2.primaryButton(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.Button.() -> Unit) =
apply {
button { isPrimary = true; apply(init) }
}
fun MessageBottomSheetUMV2.secondaryButton(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.Button.() -> Unit) =
apply {
button { isPrimary = false; apply(init) }
}
fun MessageBottomSheetUMV2.Button.onClick(block: MessageBottomSheetUMV2.CloseScope.() -> Unit) = apply {
onClick = block
}
// endregion

View file

@ -1,290 +0,0 @@
package com.tangem.core.ui.components.bottomsheets.message
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
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.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2.Button.IconOrder
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.components.icons.HighlightedIcon
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@Composable
fun MessageBottomSheetV2(state: MessageBottomSheetUMV2, onDismissRequest: () -> Unit) {
val stateWithOnDismiss = remember(state) {
state.copy(
onDismissRequest = {
state.onDismissRequest.invoke()
onDismissRequest()
},
)
}
val config = TangemBottomSheetConfig(
isShown = true,
content = stateWithOnDismiss,
onDismissRequest = stateWithOnDismiss.onDismissRequest,
)
TangemModalBottomSheet(
config = config,
title = {
TangemModalBottomSheetTitle(
endIconRes = R.drawable.ic_close_24,
onEndClick = stateWithOnDismiss.onDismissRequest,
)
},
content = { content: MessageBottomSheetUMV2 -> MessageBottomSheetV2Content(content) },
)
}
@Composable
fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
state.elements.fastForEach { element ->
when (element) {
is MessageBottomSheetUMV2.InfoBlock -> {
ContentContainer(
modifier = Modifier
.heightIn(min = TangemTheme.dimens.size180)
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(bottom = 32.dp),
state = element,
)
}
else -> Unit
}
}
ButtonsContainer(
modifier = Modifier.fillMaxWidth(),
closeScope = state.closeScope,
buttons = state.elements.filterIsInstance<MessageBottomSheetUMV2.Button>().toPersistentList(),
)
}
}
@Composable
private fun ContentContainer(state: MessageBottomSheetUMV2.InfoBlock, modifier: Modifier = Modifier) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
BottomSheetIconContainer(state.icon, state.iconImage)
state.title?.let { title ->
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing24),
text = title.resolveReference(),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
}
state.body?.let { body ->
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = TangemTheme.dimens.spacing8),
text = body.resolveAnnotatedReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
}
state.chip?.let { chip ->
BottomSheetChip(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing16),
chip = chip,
)
}
}
}
@Composable
private fun BottomSheetIconContainer(
icon: MessageBottomSheetUMV2.Icon?,
iconImage: MessageBottomSheetUMV2.IconImage?,
modifier: Modifier = Modifier,
) {
if (icon != null) {
BottomSheetIcon(icon, modifier)
} else if (iconImage != null) {
Image(
modifier = modifier
.size(TangemTheme.dimens.size56)
.clip(CircleShape),
painter = painterResource(id = iconImage.res),
contentDescription = null,
)
}
}
@Composable
private fun BottomSheetIcon(icon: MessageBottomSheetUMV2.Icon, modifier: Modifier = Modifier) {
val tint = when (icon.type) {
MessageBottomSheetUMV2.Icon.Type.Unspecified -> Color.Unspecified
MessageBottomSheetUMV2.Icon.Type.Accent -> TangemTheme.colors.icon.accent
MessageBottomSheetUMV2.Icon.Type.Informative -> TangemTheme.colors.icon.informative
MessageBottomSheetUMV2.Icon.Type.Attention -> TangemTheme.colors.icon.attention
MessageBottomSheetUMV2.Icon.Type.Warning -> TangemTheme.colors.icon.warning
}
val backgroundColor = when (icon.backgroundType) {
MessageBottomSheetUMV2.Icon.BackgroundType.Unspecified -> TangemTheme.colors.icon.informative
MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint -> tint
MessageBottomSheetUMV2.Icon.BackgroundType.Accent -> TangemTheme.colors.icon.accent
MessageBottomSheetUMV2.Icon.BackgroundType.Informative -> TangemTheme.colors.icon.informative
MessageBottomSheetUMV2.Icon.BackgroundType.Attention -> TangemTheme.colors.icon.attention
MessageBottomSheetUMV2.Icon.BackgroundType.Warning -> TangemTheme.colors.icon.warning
}
HighlightedIcon(
modifier = modifier,
icon = icon.res,
iconTint = tint,
backgroundColor = backgroundColor,
)
}
@Composable
private fun BottomSheetChip(chip: MessageBottomSheetUMV2.Chip, modifier: Modifier = Modifier) {
val color = when (chip.type) {
MessageBottomSheetUMV2.Chip.Type.Unspecified -> TangemTheme.colors.text.primary1
MessageBottomSheetUMV2.Chip.Type.Warning -> TangemTheme.colors.text.warning
}
Text(
modifier = modifier
.background(
shape = RoundedCornerShape(TangemTheme.dimens.radius16),
color = color.copy(alpha = 0.1F),
)
.padding(vertical = TangemTheme.dimens.spacing4, horizontal = TangemTheme.dimens.spacing12),
text = chip.text.resolveReference(),
style = TangemTheme.typography.caption1,
color = color,
)
}
@Suppress("LongMethod")
@Composable
private fun ButtonsContainer(
buttons: ImmutableList<MessageBottomSheetUMV2.Button>,
closeScope: MessageBottomSheetUMV2.CloseScope,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.padding(all = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
buttons.fastForEach { button ->
val icon = button.icon?.let { iconResId ->
when (button.iconOrder) {
IconOrder.Start -> TangemButtonIconPosition.Start(iconResId)
IconOrder.End -> TangemButtonIconPosition.End(iconResId)
}
} ?: TangemButtonIconPosition.None
TangemButton(
modifier = Modifier.fillMaxWidth(),
text = button.text?.resolveReference().orEmpty(),
icon = icon,
onClick = { button.onClick?.invoke(closeScope) },
colors = if (button.isPrimary) {
TangemButtonsDefaults.primaryButtonColors
} else {
TangemButtonsDefaults.secondaryButtonColors
},
enabled = true,
showProgress = false,
textStyle = TangemTheme.typography.subtitle1,
)
}
}
}
@Preview
@Composable
private fun Preview() {
TangemThemePreview {
MessageBottomSheetV2(
messageBottomSheetUM {
infoBlock {
icon(R.drawable.img_knight_shield_32) {
type = MessageBottomSheetUMV2.Icon.Type.Attention
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint
}
title = TextReference.Str("Title Title Title")
body = TextReference.Str("Body")
chip(text = TextReference.Str("Some chip information"))
}
primaryButton {
text = TextReference.Str("Test")
icon = R.drawable.ic_tangem_24
}
secondaryButton {
icon = R.drawable.ic_tangem_24
text = TextReference.Str("asdasd")
onClick {
closeBs()
}
}
},
onDismissRequest = {},
)
}
}
@Preview
@Composable
private fun Preview2() {
TangemThemePreview {
MessageBottomSheetV2(
messageBottomSheetUM {
infoBlock {
iconImage = MessageBottomSheetUMV2.IconImage(R.drawable.img_visa_notification)
title = TextReference.Str("Title Title Title")
body = TextReference.Str("Body")
chip(text = TextReference.Str("Some chip information"))
}
primaryButton {
text = TextReference.Str("Test")
icon = R.drawable.ic_tangem_24
}
secondaryButton {
icon = R.drawable.ic_tangem_24
text = TextReference.Str("asdasd")
onClick {
closeBs()
}
}
},
onDismissRequest = {},
)
}
}

View file

@ -4,8 +4,8 @@ import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.decompose.ui.UiMessage
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetV2Dsl
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetDsl
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM
import com.tangem.core.ui.components.bottomsheets.message.messageBottomSheetUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
@ -156,65 +156,22 @@ data class GlobalLoadingMessage(val isShow: Boolean) : EventMessage
/**
* Shows a bottom sheet.
*
* @param iconResId The icon to show in the bottom sheet.
* Optional, `null` by default.
* @param title The title of the bottom sheet. Optional, `null` by default.
* @param message The message to show in the bottom sheet.
* @param firstAction The first action to perform. Optional, `null` by default.
* @param secondAction The second action to perform. Optional, `null` by default.
* @param onDismissRequest The action to perform when the bottom sheet is dismissed.
* @param messageBottomSheetUM The content of the bottom sheet.
* */
@Immutable
data class BottomSheetMessage(
@DrawableRes val iconResId: Int? = null,
val title: TextReference? = null,
val message: TextReference,
val firstAction: EventMessageAction? = null,
val secondAction: EventMessageAction? = null,
val onDismissRequest: () -> Unit = {},
) : EventMessage {
companion object {
/**
* Builder for [BottomSheetMessage].
*
* @param iconResId The icon to show in the bottom sheet. Optional, `null` by default.
* @param title The title of the bottom sheet. Optional, `null` by default.
* @param message The message to show in the bottom sheet.
* @param onDismissRequest The action to perform when the bottom sheet is dismissed.
* @param firstActionBuilder The builder for the first action. Optional, `null` by default.
* @param secondActionBuilder The builder for the second action. Optional, `null` by default.
* */
operator fun invoke(
@DrawableRes iconResId: Int?,
title: TextReference?,
message: TextReference,
onDismissRequest: () -> Unit = {},
firstActionBuilder: (EventMessageAction.BuilderScope.() -> EventMessageAction)? = null,
secondActionBuilder: (EventMessageAction.BuilderScope.() -> EventMessageAction)? = null,
): BottomSheetMessage {
val buttonsScope = EventMessageAction.BuilderScope(onDismissRequest)
return BottomSheetMessage(
iconResId = iconResId,
title = title,
message = message,
onDismissRequest = onDismissRequest,
firstAction = firstActionBuilder?.invoke(buttonsScope),
secondAction = secondActionBuilder?.invoke(buttonsScope),
)
}
}
}
@Immutable
data class BottomSheetMessageV2(
val messageBottomSheetUMV2: MessageBottomSheetUMV2,
val messageBottomSheetUM: MessageBottomSheetUM,
) : EventMessage
fun bottomSheetMessage(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.() -> Unit) =
BottomSheetMessageV2(messageBottomSheetUM(init))
/**
* Builder for [BottomSheetMessage].
*
* @param init The builder lambda with receiver of type [MessageBottomSheetUM].
*
* @return [BottomSheetMessage] with the specified content.
*/
fun bottomSheetMessage(init: @MessageBottomSheetDsl MessageBottomSheetUM.() -> Unit) =
BottomSheetMessage(messageBottomSheetUM(init))
/**
* Represents an action button in the dialog.

View file

@ -19,18 +19,11 @@ import androidx.compose.ui.window.DialogProperties
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheet
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetV2
import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHostState
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.LocalEventMessageHandler
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.LocalSnackbarHostState
import com.tangem.core.ui.res.LocalTopSnackbarHostState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.*
@Composable
fun EventMessageEffect(
@ -50,7 +43,6 @@ fun EventMessageEffect(
var dialogMessage: DialogMessage? by remember { mutableStateOf(value = null) }
var bottomSheetMessage: BottomSheetMessage? by remember { mutableStateOf(value = null) }
var bottomSheetMessageV2: BottomSheetMessageV2? by remember { mutableStateOf(value = null) }
var loadingMessage: GlobalLoadingMessage? by remember { mutableStateOf(value = null) }
val isRedesignEnabled = LocalRedesignEnabled.current
@ -69,9 +61,6 @@ fun EventMessageEffect(
is BottomSheetMessage -> {
bottomSheetMessage = message
}
is BottomSheetMessageV2 -> {
bottomSheetMessageV2 = message
}
is ToastMessage -> {
onShowToast(message, context)
}
@ -97,18 +86,8 @@ fun EventMessageEffect(
bottomSheetMessage?.let { message ->
MessageBottomSheet(
message = message,
onDismissRequest = {
bottomSheetMessage = null
message.onDismissRequest()
},
)
}
bottomSheetMessageV2?.let { message ->
MessageBottomSheetV2(
state = message.messageBottomSheetUMV2,
onDismissRequest = { bottomSheetMessageV2 = null },
state = message.messageBottomSheetUM,
onDismissRequest = { bottomSheetMessage = null },
)
}
@ -141,41 +120,6 @@ private fun LoadingDialog() {
}
}
@Composable
private fun MessageBottomSheet(message: BottomSheetMessage, onDismissRequest: () -> Unit) {
val config = TangemBottomSheetConfig(
isShown = true,
content = MessageBottomSheetUM(
iconResId = message.iconResId,
title = message.title,
message = message.message,
primaryAction = message.firstAction?.let { action ->
MessageBottomSheetUM.ActionUM(
text = action.title,
isEnabled = action.isEnabled,
onClick = {
action.onClick()
onDismissRequest()
},
)
},
secondaryAction = message.secondAction?.let { action ->
MessageBottomSheetUM.ActionUM(
text = action.title,
isEnabled = action.isEnabled,
onClick = {
action.onClick()
onDismissRequest()
},
)
},
),
onDismissRequest = onDismissRequest,
)
MessageBottomSheet(config)
}
@Composable
private fun MessageDialog(message: DialogMessage, onDismissRequest: () -> Unit) {
BasicDialog(