diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUMV2.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUMV2.kt new file mode 100644 index 0000000000..3617352f54 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUMV2.kt @@ -0,0 +1,134 @@ +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 = 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 + } + } + + @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 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 + +internal 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.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 \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt new file mode 100644 index 0000000000..541625fde2 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt @@ -0,0 +1,238 @@ +package com.tangem.core.ui.components.bottomsheets.message + +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.Icon +import androidx.compose.material3.Text +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.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.extensions.TextReference +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 config = TangemBottomSheetConfig( + isShown = true, + content = state, + onDismissRequest = onDismissRequest, + ) + + TangemModalBottomSheet( + config, + title = { + TangemModalBottomSheetTitle( + endIconRes = R.drawable.ic_close_24, + onEndClick = state.onDismissRequest, + ) + }, + ) { content: MessageBottomSheetUMV2 -> + MessageBottomSheetV2Content(content.copy(onDismissRequest = onDismissRequest)) + } +} + +@Composable +private fun MessageBottomSheetV2Content(state: MessageBottomSheetUMV2, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + state.elements.fastForEach { + when (it) { + is MessageBottomSheetUMV2.InfoBlock -> { + ContentContainer( + modifier = Modifier + .heightIn(min = TangemTheme.dimens.size180) + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing16) + .padding(bottom = 32.dp), + state = it, + ) + } + else -> Unit + } + } + + ButtonsContainer( + modifier = Modifier.fillMaxWidth(), + closeScope = state.closeScope, + buttons = state.elements.filterIsInstance().toPersistentList(), + ) + } +} + +@Composable +private fun ContentContainer(state: MessageBottomSheetUMV2.InfoBlock, modifier: Modifier = Modifier) { + Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { + state.icon?.let { + BottomSheetIcon(it) + } + state.title?.let { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = TangemTheme.dimens.spacing24), + text = it.resolveReference(), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + } + state.body?.let { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(top = TangemTheme.dimens.spacing8), + text = it.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + } + state.chip?.let { + BottomSheetChip( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing16), + chip = it, + ) + } + } +} + +@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 + } + + Box( + modifier = modifier + .size(TangemTheme.dimens.size56) + .clip(CircleShape) + .background(backgroundColor.copy(alpha = 0.1F)), + contentAlignment = Alignment.Center, + content = { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size32), + painter = painterResource(icon.res), + contentDescription = null, + tint = tint, + ) + }, + ) +} + +@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, + 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 { + when (button.iconOrder) { + IconOrder.Start -> TangemButtonIconPosition.Start(it) + IconOrder.End -> TangemButtonIconPosition.End(it) + } + } ?: TangemButtonIconPosition.None + + TangemButton( + modifier = Modifier.fillMaxWidth(), + text = button.text?.resolveReference() ?: "", + 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 = {}, + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt index 5f02c59f7f..028e2901a0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessage.kt @@ -4,6 +4,9 @@ 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.messageBottomSheetUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage.Duration @@ -177,6 +180,13 @@ data class BottomSheetMessage( } } +data class BottomSheetMessageV2( + val messageBottomSheetUMV2: MessageBottomSheetUMV2, +) : EventMessage + +internal fun bottomSheetMessage(init: @MessageBottomSheetV2Dsl MessageBottomSheetUMV2.() -> Unit) = + BottomSheetMessageV2(messageBottomSheetUM(init)) + /** * Represents an action button in the dialog. * diff --git a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt index d742b59879..17a9c0edb0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/message/EventMessageEffect.kt @@ -11,6 +11,7 @@ import com.tangem.core.ui.components.DialogButtonUM 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.event.EventEffect import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.LocalEventMessageHandler @@ -29,6 +30,7 @@ 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) } EventEffect(event = messageEvent) { message -> when (message) { @@ -41,6 +43,9 @@ fun EventMessageEffect( is BottomSheetMessage -> { bottomSheetMessage = message } + is BottomSheetMessageV2 -> { + bottomSheetMessageV2 = message + } } } @@ -63,6 +68,13 @@ fun EventMessageEffect( }, ) } + + bottomSheetMessageV2?.let { message -> + MessageBottomSheetV2( + state = message.messageBottomSheetUMV2, + onDismissRequest = { bottomSheetMessageV2 = null }, + ) + } } @Composable