From 9f2b2eac718e781057dae9c44b67eb6a3c93ada7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 6 Mar 2026 17:11:59 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/tap/di/UiDependenciesModule.kt | 2 + .../com/tangem/tap/routing/RootContent.kt | 14 + core/ui/build.gradle.kts | 2 +- .../java/com/tangem/core/ui/UiDependencies.kt | 3 + .../ModalBottomSheetWithBackHandling.kt | 36 ++- .../modal/TangemModalBottomSheetWithFooter.kt | 1 + .../snackbar/CopiedTextSnackbarHost.kt | 1 + .../ui/components/snackbar/TangemSnackbar.kt | 1 + .../components/snackbar/TangemSnackbarHost.kt | 4 + .../components/snackbar/TangemTopSnackbar.kt | 180 ++++++++++++ .../snackbar/TangemTopSnackbarHost.kt | 274 ++++++++++++++++++ .../tangem/core/ui/message/EventMessage.kt | 7 + .../core/ui/message/EventMessageEffect.kt | 17 +- .../com/tangem/core/ui/res/TangemTheme.kt | 8 + .../referral/ui/ParticipateBottomBlock.kt | 23 +- .../feature/referral/ui/ReferralScreen.kt | 17 ++ .../ui/ContainerWithSnackbarHost.kt | 19 +- .../ui/TokenReceiveAssetsContent.kt | 41 ++- .../ui/TokenReceiveQrCodeContent.kt | 21 +- .../wallet/ui/WalletEventEffect.kt | 18 +- .../presentation/wallet/ui/WalletScreen2.kt | 41 +-- 21 files changed, 658 insertions(+), 72 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemTopSnackbar.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemTopSnackbarHost.kt diff --git a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt index b5b3c5ea29..bf500d19e8 100644 --- a/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt +++ b/app/src/main/java/com/tangem/tap/di/UiDependenciesModule.kt @@ -6,6 +6,7 @@ import com.tangem.core.decompose.ui.DefaultUiMessageSender import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.DesignFeatureToggles import com.tangem.core.ui.UiDependencies +import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHostState import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.core.ui.message.EventMessageHandler import com.tangem.core.ui.theme.AppThemeModeHolder @@ -30,6 +31,7 @@ internal object UiDependenciesModule { override val vibratorHapticManager = vibratorHapticManager override val appThemeModeHolder = appThemeModeHolder override val globalSnackbarHostState: SnackbarHostState = SnackbarHostState() + override val globalTopSnackbarHostState: TangemTopSnackbarHostState = TangemTopSnackbarHostState() override val eventMessageHandler: EventMessageHandler = EventMessageHandler() override val designFeatureToggles: DesignFeatureToggles = designFeatureToggles } diff --git a/app/src/main/java/com/tangem/tap/routing/RootContent.kt b/app/src/main/java/com/tangem/tap/routing/RootContent.kt index c21f7d2617..3c941cecd3 100644 --- a/app/src/main/java/com/tangem/tap/routing/RootContent.kt +++ b/app/src/main/java/com/tangem/tap/routing/RootContent.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment @@ -28,9 +29,12 @@ import com.arkivanov.essenty.backhandler.BackHandler import com.tangem.common.routing.AppRoute import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.snackbar.TangemSnackbarHost +import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHost import com.tangem.core.ui.message.EventMessageEffect +import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.core.ui.res.LocalRootBackgroundColor 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.security.ProvideSecureFlagController import com.tangem.tap.routing.component.RoutingComponent @@ -93,6 +97,16 @@ internal fun RootContent( .padding(all = 16.dp), hostState = snackbarHostState, ) + + if (LocalRedesignEnabled.current) { + TangemTopSnackbarHost( + modifier = Modifier + .align(Alignment.TopCenter) + .statusBarsPadding() + .padding(all = 16.dp), + hostState = LocalTopSnackbarHostState.current, + ) + } } } EventMessageEffect() diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index f9b0a562a0..9be1ed52f9 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -24,7 +24,7 @@ dependencies { /** Project - Core */ implementation(projects.core.res) implementation(projects.core.utils) - implementation(projects.core.decompose) + api(projects.core.decompose) implementation(projects.core.error) /** AndroidX libraries */ diff --git a/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt b/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt index 10c50f5742..84b5333435 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/UiDependencies.kt @@ -2,6 +2,7 @@ package com.tangem.core.ui import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Stable +import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHostState import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.core.ui.message.EventMessageHandler import com.tangem.core.ui.theme.AppThemeModeHolder @@ -15,6 +16,8 @@ interface UiDependencies { val globalSnackbarHostState: SnackbarHostState + val globalTopSnackbarHostState: TangemTopSnackbarHostState + val eventMessageHandler: EventMessageHandler val designFeatureToggles: DesignFeatureToggles diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/internal/ModalBottomSheetWithBackHandling.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/internal/ModalBottomSheetWithBackHandling.kt index 8b8f7d3fd4..a62e171f5f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/internal/ModalBottomSheetWithBackHandling.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/internal/ModalBottomSheetWithBackHandling.kt @@ -1,16 +1,27 @@ package com.tangem.core.ui.components.bottomsheets.internal import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.padding import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.input.key.* import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.haze.hazeSourceTangem +import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHost +import com.tangem.core.ui.res.LocalHazeState +import com.tangem.core.ui.res.LocalRedesignEnabled +import com.tangem.core.ui.res.LocalTopSnackbarHostState +import dev.chrisbanes.haze.rememberHazeState import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -31,6 +42,9 @@ fun ModalBottomSheetWithBackHandling( contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets }, content: @Composable ColumnScope.() -> Unit, ) { + val topSnackbarHostState = LocalTopSnackbarHostState.current + val isRedesignEnabled = LocalRedesignEnabled.current + ModalBottomSheet( onDismissRequest = onDismissRequest, modifier = modifier, @@ -47,7 +61,27 @@ fun ModalBottomSheetWithBackHandling( shouldDismissOnBackPress = onBack == null, ), content = { - content() + if (isRedesignEnabled) { + Box { + val hazeState = rememberHazeState() + + Column(Modifier.hazeSourceTangem(hazeState, zIndex = -1f)) { + content() + } + + CompositionLocalProvider(LocalHazeState provides hazeState) { + TangemTopSnackbarHost( + modifier = Modifier + .align(Alignment.TopCenter) + .padding(horizontal = 16.dp) + .padding(top = 24.dp), + hostState = topSnackbarHostState, + ) + } + } + } else { + content() + } BackHandler(enabled = onBack != null && sheetState.targetValue != SheetValue.Hidden) { onBack?.invoke() diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt index 4f05ab4074..53d05fca82 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt @@ -167,6 +167,7 @@ inline fun BasicModalBottomSheetWit val model = config.content as? T ?: return val bsContent: @Composable ColumnScope.() -> Unit = { + // FIXME: Use LocalWindowSize.current val maxHeight = LocalConfiguration.current.screenHeightDp * MODAL_SHEET_MAX_HEIGHT val initial = 0 val scrollState = rememberScrollState(initial = initial) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbarHost.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbarHost.kt index 3cff3dd5c0..0cfe9b7b65 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbarHost.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/CopiedTextSnackbarHost.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier * [REDACTED_AUTHOR] */ +@Deprecated("Use TangemTopSnackbarHost instead. Will be removed with redesign") @Composable fun CopiedTextSnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) { SnackbarHost(hostState = hostState, modifier = modifier) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbar.kt index 27603516a5..2ab7bcbf76 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbar.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbar.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.res.TangemThemePreview * [REDACTED_AUTHOR] */ +@Deprecated("With redesign, this component is no longer used. Please use both TangemSnackbar and TangemTopSnackbar") @Composable fun TangemSnackbar(data: SnackbarData, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false) { Snackbar( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt index 6ad91b3281..5b1abbc198 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemSnackbarHost.kt @@ -27,6 +27,10 @@ import com.tangem.core.ui.res.TangemThemePreview * [REDACTED_AUTHOR] */ +@Deprecated( + "With redesign, this component is no longer used. " + + "Please use both TangemSnackbarHost and TangemTopSnackbarHost", +) @Composable fun TangemSnackbarHost( modifier: Modifier = Modifier, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemTopSnackbar.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemTopSnackbar.kt new file mode 100644 index 0000000000..a35b7a0e09 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemTopSnackbar.kt @@ -0,0 +1,180 @@ +package com.tangem.core.ui.components.snackbar + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.components.SpacerW +import com.tangem.core.ui.components.haze.hazeEffectTangem +import com.tangem.core.ui.ds.button.SecondaryTangemButton +import com.tangem.core.ui.ds.button.TangemButtonShape +import com.tangem.core.ui.ds.button.TangemButtonSize +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign + +/** + * Top snackbar with an optional leading icon and an optional action button. + * Intended to be used with [TangemTopSnackbarHost]. + * + * @param snackbarMessage message data + * @param modifier modifier + */ +@Suppress("LongMethod") +@Composable +fun TangemTopSnackbar(snackbarMessage: SnackbarMessage, modifier: Modifier = Modifier) { + val actionLabel = snackbarMessage.actionLabel + val action = snackbarMessage.action + val hasAction = actionLabel != null && action != null + + var isTextOverflowing by remember { mutableStateOf(false) } + val shape = if (isTextOverflowing) { + RoundedCornerShape(TangemTheme.dimens2.x5) + } else { + TangemTheme.shapes.roundedCornersXLarge + } + + Column( + modifier = modifier + .shadow(elevation = TangemTheme.dimens.elevation4, shape = shape, clip = false) + .background(color = TangemTheme.colors2.controls.backgroundDefault, shape = shape) + .clip(shape) + .hazeEffectTangem() + .sizeIn(minHeight = TangemTheme.dimens2.x11) + .padding(start = TangemTheme.dimens2.x5, end = TangemTheme.dimens2.x1) + .padding(vertical = TangemTheme.dimens2.x1), + verticalArrangement = Arrangement.Center, + ) { + Row( + modifier = Modifier.padding(top = if (isTextOverflowing) TangemTheme.dimens2.x3 else 0.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (snackbarMessage.startIconId != null) { + Icon( + painter = painterResource(id = snackbarMessage.startIconId), + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens2.x5), + tint = TangemTheme.colors2.graphic.neutral.secondary, + ) + + SpacerW(TangemTheme.dimens2.x2) + } + + Text( + text = snackbarMessage.message.resolveReference(), + modifier = Modifier.weight(1f, fill = false), + color = TangemTheme.colors.text.secondary, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = TangemTheme.typography.body2, + onTextLayout = { if (hasAction) isTextOverflowing = it.hasVisualOverflow }, + ) + + SpacerW(TangemTheme.dimens2.x4) + + if (hasAction && !isTextOverflowing) { + SecondaryTangemButton( + text = actionLabel, + onClick = action, + size = TangemButtonSize.X9, + shape = TangemButtonShape.Rounded, + ) + } + } + + if (hasAction && isTextOverflowing) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding( + top = TangemTheme.dimens2.x4, + bottom = TangemTheme.dimens2.x1, + ), + contentAlignment = Alignment.CenterEnd, + ) { + SecondaryTangemButton( + text = actionLabel, + onClick = action, + size = TangemButtonSize.X9, + shape = TangemButtonShape.Rounded, + ) + } + } + } +} + +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_TangemTopSnackbar_WithAction() { + TangemThemePreviewRedesign { + Column( + modifier = Modifier.padding(TangemTheme.dimens.spacing16), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + ) { + TangemTopSnackbar( + snackbarMessage = SnackbarMessage( + startIconId = R.drawable.ic_eye_off_outline_24, + message = stringReference("Balances hidden"), + actionLabel = stringReference("Undo"), + action = {}, + ), + ) + TangemTopSnackbar( + snackbarMessage = SnackbarMessage( + startIconId = R.drawable.ic_eye_off_outline_24, + message = stringReference( + "Balances hidden long long text that should be truncated with ellipsis at the end", + ), + actionLabel = stringReference("Undo"), + action = {}, + ), + ) + } + } +} + +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_TangemTopSnackbar_NoAction() { + TangemThemePreviewRedesign { + Column( + modifier = Modifier.padding(TangemTheme.dimens.spacing16), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + ) { + TangemTopSnackbar( + snackbarMessage = SnackbarMessage( + startIconId = R.drawable.ic_check_24, + message = stringReference("Text copied to clipboard"), + ), + ) + TangemTopSnackbar( + snackbarMessage = SnackbarMessage( + message = stringReference("Operation completed"), + ), + ) + TangemTopSnackbar( + snackbarMessage = SnackbarMessage( + message = stringReference( + "Operation completed long long text that should be truncated with ellipsis at the end", + ), + ), + ) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemTopSnackbarHost.kt b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemTopSnackbarHost.kt new file mode 100644 index 0000000000..dac23bfd15 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/snackbar/TangemTopSnackbarHost.kt @@ -0,0 +1,274 @@ +@file:Suppress("MagicNumber") +package com.tangem.core.ui.components.snackbar + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.AccessibilityManager +import androidx.compose.ui.platform.LocalAccessibilityManager +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.core.ui.res.TangemTheme +import kotlinx.coroutines.delay +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.coroutines.resume + +/** + * Snackbar host that displays a [TangemTopSnackbar] sliding and scaling in from the top of the screen. + * Typical placement: at the top of the screen's root [Box], above the main content. + * + * ``` + * Box(Modifier.fillMaxSize()) { + * MainContent() + * TangemTopSnackbarHost( + * hostState = topSnackbarHostState, + * modifier = Modifier + * .align(Alignment.TopCenter) + * .statusBarsPadding() + * .padding(top = TangemTheme.dimens.spacing8), + * ) + * } + * ``` + * + * @param hostState state that controls which snackbar is shown + * @param modifier modifier applied to the host container + */ +@Composable +fun TangemTopSnackbarHost(hostState: TangemTopSnackbarHostState, modifier: Modifier = Modifier) { + val myDepth = remember(hostState) { hostState.registerHost() } + DisposableEffect(hostState) { + onDispose { hostState.unregisterHost(myDepth) } + } + + // Only the deepest host in the composition tree handles the snackbar. + val isDeepest = hostState.activeHostDepth == myDepth + val currentSnackbar = if (isDeepest) hostState.currentSnackbar else null + val accessibilityManager = LocalAccessibilityManager.current + + LaunchedEffect(currentSnackbar) { + if (currentSnackbar == null) return@LaunchedEffect + val duration = currentSnackbar.duration.toMillis( + hasAction = currentSnackbar.action != null, + accessibilityManager = accessibilityManager, + ) + delay(duration) + currentSnackbar.onDismissRequest() + } + + ScaleFromTopWithFade( + current = currentSnackbar, + modifier = modifier, + ) { snackbar -> + TangemTopSnackbar( + snackbarMessage = snackbar, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), + ) + } +} + +private fun SnackbarMessage.Duration.toMillis(hasAction: Boolean, accessibilityManager: AccessibilityManager?): Long { + val original = + when (this) { + SnackbarMessage.Duration.Indefinite -> Long.MAX_VALUE + SnackbarMessage.Duration.Long -> 10000L + SnackbarMessage.Duration.Short -> 4000L + } + if (accessibilityManager == null) { + return original + } + return accessibilityManager.calculateRecommendedTimeoutMillis( + originalTimeoutMillis = original, + containsIcons = true, + containsText = true, + containsControls = hasAction, + ) +} + +@Stable +class TangemTopSnackbarHostState { + + private val mutex = Mutex() + + var currentSnackbar by mutableStateOf(null) + + // Tracks which depth level is the deepest registered host. + // Composed as observable state so hosts recompose when a deeper/shallower one is added/removed. + var activeHostDepth by mutableIntStateOf(0) + private set + + private var hostDepthCounter = 0 + + internal fun registerHost(): Int { + hostDepthCounter++ + activeHostDepth = hostDepthCounter + return hostDepthCounter + } + + internal fun unregisterHost(depth: Int) { + if (depth == hostDepthCounter) { + hostDepthCounter-- + activeHostDepth = hostDepthCounter + } + } + + suspend fun showSnackbar( + message: String, + actionLabel: String? = null, + withDismissAction: Boolean = false, + duration: SnackbarMessage.Duration = + if (actionLabel == null) SnackbarMessage.Duration.Short else SnackbarMessage.Duration.Indefinite, + ) { + showSnackbar( + SnackbarMessage( + message = stringReference(message), + actionLabel = actionLabel?.let { stringReference(it) }, + duration = duration, + onDismissRequest = {}, + action = if (withDismissAction) { + null + } else { + {} + }, + ), + ) + } + + suspend fun showSnackbar(message: SnackbarMessage) { + mutex.withLock { + try { + suspendCancellableCoroutine { continuation -> + currentSnackbar = message.withDismiss( + onDismiss = { if (continuation.isActive) continuation.resume(Unit) }, + ) + } + } finally { + currentSnackbar = null + } + } + } +} + +private fun SnackbarMessage.withDismiss(onDismiss: () -> Unit): SnackbarMessage { + return copy( + onDismissRequest = { + onDismissRequest() + onDismiss() + }, + action = if (action != null) { + { + action.invoke() + onDismiss() + } + } else { + null + }, + ) +} + +// Adapted from Material3's FadeInFadeOutWithScale, with scale pivot anchored at the top center. +@Suppress("UnsafeCallOnNullableType") +@Composable +private fun ScaleFromTopWithFade( + current: SnackbarMessage?, + modifier: Modifier = Modifier, + content: @Composable (SnackbarMessage) -> Unit, +) { + val state = remember { ScaleFromTopState() } + if (current != state.current) { + state.current = current + val keys = state.items.map { it.key }.toMutableList() + if (!keys.contains(current)) keys.add(current) + state.items.clear() + keys.filterNotNull().mapTo(state.items) { key -> + ScaleFromTopItem(key) { children -> + val isVisible = key == current + val opacity = animatedOpacity( + animation = tween(durationMillis = 200), + visible = isVisible, + onAnimationFinish = { + if (key != state.current) { + state.items.removeAll { it.key == key } + state.scope?.invalidate() + } + }, + ) + val scale = animatedScale( + enterAnimation = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessMedium, + ), + exitAnimation = tween(durationMillis = 180), + visible = isVisible, + ) + Box( + Modifier.graphicsLayer( + scaleX = scale.value, + scaleY = scale.value, + alpha = opacity.value, + transformOrigin = TransformOrigin(pivotFractionX = 0.5f, pivotFractionY = 0f), + ), + ) { + children() + } + } + } + } + Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.TopCenter) { + state.scope = currentRecomposeScope + state.items.forEach { (item, transition) -> key(item) { transition { content(item!!) } } } + } +} + +@Suppress("DoubleMutabilityForCollection") +private class ScaleFromTopState { + var current: Any? = Any() + var items = mutableListOf>() + var scope: RecomposeScope? = null +} + +private data class ScaleFromTopItem( + val key: T, + val transition: @Composable (content: @Composable () -> Unit) -> Unit, +) + +@Composable +private fun animatedOpacity( + animation: AnimationSpec, + visible: Boolean, + onAnimationFinish: () -> Unit = {}, +): State { + val alpha = remember { Animatable(if (visible) 0f else 1f) } + LaunchedEffect(visible) { + alpha.animateTo(if (visible) 1f else 0f, animationSpec = animation) + onAnimationFinish() + } + return alpha.asState() +} + +@Composable +private fun animatedScale( + enterAnimation: AnimationSpec, + exitAnimation: AnimationSpec, + visible: Boolean, +): State { + val scale = remember { Animatable(if (visible) 0.85f else 1f) } + LaunchedEffect(visible) { + scale.animateTo( + targetValue = if (visible) 1f else 0.85f, + animationSpec = if (visible) enterAnimation else exitAnimation, + ) + } + return scale.asState() +} \ 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 3da6b245e1..b0131e2484 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 @@ -18,6 +18,7 @@ import com.tangem.core.ui.extensions.resourceReference @Immutable sealed interface EventMessage : UiMessage +@Immutable data class ToastMessage( val message: TextReference, val duration: Duration = ToastMessage.Duration.Short, @@ -48,8 +49,10 @@ data class ToastMessage( * @param actionLabel The label of the action button. Optional, `null` by default. * @param action The action to perform when the action button is clicked. Optional, `null` by default. * */ +@Immutable data class SnackbarMessage( val message: TextReference, + @field:DrawableRes val startIconId: Int? = null, val duration: Duration = Duration.Short, val onDismissRequest: () -> Unit = {}, val actionLabel: TextReference? = null, @@ -92,6 +95,7 @@ data class SnackbarMessage( * `true` by default. * @param onDismissRequest The action to perform when the dialog is dismissed. * */ +@Immutable data class DialogMessage( val message: TextReference, val title: TextReference? = null, @@ -146,6 +150,7 @@ data class DialogMessage( } } +@Immutable data class GlobalLoadingMessage(val isShow: Boolean) : EventMessage /** @@ -159,6 +164,7 @@ data class GlobalLoadingMessage(val isShow: Boolean) : EventMessage * @param secondAction The second action to perform. Optional, `null` by default. * @param onDismissRequest The action to perform when the bottom sheet is dismissed. * */ +@Immutable data class BottomSheetMessage( @DrawableRes val iconResId: Int? = null, val title: TextReference? = null, @@ -202,6 +208,7 @@ data class BottomSheetMessage( } } +@Immutable data class BottomSheetMessageV2( val messageBottomSheetUMV2: MessageBottomSheetUMV2, ) : EventMessage 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 c7e40837a1..75d08a8dfc 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 @@ -23,10 +23,13 @@ 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 @Composable @@ -36,6 +39,10 @@ fun EventMessageEffect( onShowSnackbar: suspend (SnackbarMessage, Context) -> Unit = { message, context -> showSnackbar(snackbarHostState, message, context) }, + topSnackbarHostState: TangemTopSnackbarHostState = LocalTopSnackbarHostState.current, + onShowTopSnackbar: suspend (SnackbarMessage) -> Unit = { message -> + topSnackbarHostState.showSnackbar(message) + }, onShowToast: (ToastMessage, Context) -> Unit = { message, context -> showToast(message, context) }, ) { val messageEvent by messageHandler.collectAsState() @@ -45,11 +52,16 @@ fun EventMessageEffect( 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 EventEffect(event = messageEvent) { message -> when (message) { is SnackbarMessage -> { - onShowSnackbar(message, context) + if (isRedesignEnabled) { + onShowTopSnackbar(message) + } else { + onShowSnackbar(message, context) + } } is DialogMessage -> { dialogMessage = message @@ -220,7 +232,8 @@ private fun showToast(message: ToastMessage, context: Context) { Toast.makeText( /* context = */ context, /* text = */ message.message.resolveReference(context.resources), - /* duration = */ when (message.duration) { + /* duration = */ + when (message.duration) { ToastMessage.Duration.Short -> Toast.LENGTH_SHORT ToastMessage.Duration.Long -> Toast.LENGTH_LONG }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index 829a8ff0e1..46b8751e71 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -20,6 +20,7 @@ import com.tangem.core.ui.components.SystemBarsIconsController import com.tangem.core.ui.components.TangemShimmer import com.tangem.core.ui.components.powersaving.PowerSavingState import com.tangem.core.ui.components.powersaving.rememberPowerSavingState +import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHostState import com.tangem.core.ui.components.text.BladeAnimation import com.tangem.core.ui.components.text.rememberBladeAnimation import com.tangem.core.ui.haptic.DefaultHapticManager @@ -54,6 +55,7 @@ fun TangemTheme( vibratorHapticManager = uiDependencies.vibratorHapticManager, snackbarHostState = uiDependencies.globalSnackbarHostState, eventMessageHandler = uiDependencies.eventMessageHandler, + topSnackbarHostState = uiDependencies.globalTopSnackbarHostState, overrideSystemBarColors = overrideSystemBarColors, typography = typography, dimens = dimens, @@ -76,6 +78,7 @@ fun TangemTheme( vibratorHapticManager: VibratorHapticManager? = null, eventMessageHandler: EventMessageHandler = remember { EventMessageHandler() }, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, + topSnackbarHostState: TangemTopSnackbarHostState = remember { TangemTopSnackbarHostState() }, overrideSystemBarColors: Boolean = true, content: @Composable () -> Unit, ) { @@ -119,6 +122,7 @@ fun TangemTheme( LocalIsInDarkTheme provides isDark, LocalHapticManager provides hapticManager, LocalSnackbarHostState provides snackbarHostState, + LocalTopSnackbarHostState provides topSnackbarHostState, LocalEventMessageHandler provides eventMessageHandler, LocalWindowSize provides windowSize, LocalBladeAnimation provides rememberBladeAnimation(), @@ -389,6 +393,10 @@ val LocalSnackbarHostState = staticCompositionLocalOf { error("No SnackbarHostState provided") } +val LocalTopSnackbarHostState = staticCompositionLocalOf { + error("No TangemTopSnackbarHostState provided") +} + val LocalEventMessageHandler = staticCompositionLocalOf { error("No EventMessageHandler provided") } diff --git a/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index a9fb37777a..1795f345ba 100644 --- a/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -33,8 +33,12 @@ import com.tangem.core.ui.components.account.AccountIconSize import com.tangem.core.ui.components.rows.RoundableCornersRow import com.tangem.core.ui.extensions.pluralStringResourceSafe 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.extensions.stringResourceSafe +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.core.ui.res.LocalRedesignEnabled +import com.tangem.core.ui.res.LocalTopSnackbarHostState import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.referral.domain.models.ExpectedAward @@ -352,6 +356,8 @@ private fun AdditionalButtons( val coroutineScope = rememberCoroutineScope() val resources = LocalContext.current.resources + val isRedesignEnabled = LocalRedesignEnabled.current + val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current Row( modifier = Modifier.fillMaxWidth(), @@ -366,10 +372,19 @@ private fun AdditionalButtons( clipboardManager.setText(AnnotatedString(code)) coroutineScope.launch { - snackbarHostState.showSnackbar( - message = resources.getStringSafe(R.string.referral_promo_code_copied), - duration = SnackbarDuration.Short, - ) + if (isRedesignEnabled) { + tangemTopSnackbarHostState.showSnackbar( + SnackbarMessage( + startIconId = R.drawable.ic_check_24, + message = resourceReference(R.string.referral_promo_code_copied), + ), + ) + } else { + snackbarHostState.showSnackbar( + message = resources.getStringSafe(R.string.referral_promo_code_copied), + duration = SnackbarDuration.Short, + ) + } } }, modifier = Modifier.weight(1f), diff --git a/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index 76efe5d729..a9590eae22 100644 --- a/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -45,6 +45,9 @@ import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.core.ui.res.LocalRedesignEnabled +import com.tangem.core.ui.res.LocalTopSnackbarHostState import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -104,10 +107,24 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) { val errorSnackbar = stateHolder.errorSnackbar val coroutineScope = rememberCoroutineScope() val resources = LocalContext.current.resources + val isRedesignEnabled = LocalRedesignEnabled.current + val topSnackbarHostState = LocalTopSnackbarHostState.current SideEffect { if (errorSnackbar != null) { coroutineScope.launch { + if (isRedesignEnabled) { + topSnackbarHostState.showSnackbar( + message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable), + actionLabel = resources.getStringSafe(R.string.warning_button_ok), + duration = SnackbarMessage.Duration.Indefinite, + ) + + errorSnackbar.onOkClicked() + + return@launch + } + val result = snackbarHostState.showSnackbar( message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable), actionLabel = resources.getStringSafe(R.string.warning_button_ok), diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/ContainerWithSnackbarHost.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/ContainerWithSnackbarHost.kt index 94415b927d..c0258ad337 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/ContainerWithSnackbarHost.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/ContainerWithSnackbarHost.kt @@ -8,16 +8,23 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.snackbar.CopiedTextSnackbarHost +import com.tangem.core.ui.res.LocalRedesignEnabled +@Deprecated( + "Use TangemTopSnackbarHost instead. Will be removed with redesign", +) @Composable internal fun ContainerWithSnackbarHost(snackbarHostState: SnackbarHostState, content: @Composable () -> Unit) { Box { content() - CopiedTextSnackbarHost( - hostState = snackbarHostState, - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(bottom = 80.dp), - ) + + if (LocalRedesignEnabled.current.not()) { + CopiedTextSnackbarHost( + hostState = snackbarHostState, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 80.dp), + ) + } } } \ No newline at end of file diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt index c18b1c9440..63202e22b0 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt @@ -46,6 +46,9 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIcon import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.core.ui.res.LocalRedesignEnabled +import com.tangem.core.ui.res.LocalTopSnackbarHostState import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.tokenreceive.entity.ReceiveAddress @@ -203,6 +206,8 @@ private fun AddressBlock(assetsUM: ReceiveAssetsUM, snackbarHostState: SnackbarH val coroutineScope = rememberCoroutineScope() val context = LocalContext.current val resources = context.resources + val isRedesignEnabled = LocalRedesignEnabled.current + val topSnackbarHostState = LocalTopSnackbarHostState.current assetsUM.addresses .fastFilter { it.type is ReceiveAddress.Type.Ens } @@ -214,11 +219,20 @@ private fun AddressBlock(assetsUM: ReceiveAssetsUM, snackbarHostState: SnackbarH assetsUM.onCopyClick(address) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) coroutineScope.launch { - snackbarHostState.showSnackbar( - message = resources.getStringSafe( - R.string.wallet_notification_address_copied, - ), - ) + if (isRedesignEnabled) { + topSnackbarHostState.showSnackbar( + SnackbarMessage( + startIconId = R.drawable.ic_check_24, + message = resourceReference(R.string.wallet_notification_address_copied), + ), + ) + } else { + snackbarHostState.showSnackbar( + message = resources.getStringSafe( + R.string.wallet_notification_address_copied, + ), + ) + } } }, address = address.value, @@ -413,6 +427,8 @@ private fun ButtonsBlock(snackbarHostState: SnackbarHostState, onCopyClick: () - val coroutineScope = rememberCoroutineScope() val context = LocalContext.current val resources = context.resources + val isRedesignEnabled = LocalRedesignEnabled.current + val topSnackbarHostState = LocalTopSnackbarHostState.current Row( modifier = Modifier.width(IntrinsicSize.Min), @@ -427,9 +443,18 @@ private fun ButtonsBlock(snackbarHostState: SnackbarHostState, onCopyClick: () - onCopyClick() hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) coroutineScope.launch { - snackbarHostState.showSnackbar( - message = resources.getStringSafe(R.string.wallet_notification_address_copied), - ) + if (isRedesignEnabled) { + topSnackbarHostState.showSnackbar( + SnackbarMessage( + startIconId = R.drawable.ic_check_24, + message = resourceReference(R.string.wallet_notification_address_copied), + ), + ) + } else { + snackbarHostState.showSnackbar( + message = resources.getStringSafe(R.string.wallet_notification_address_copied), + ) + } } }, ), diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt index 39b2363e21..0c4a083cec 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveQrCodeContent.kt @@ -31,6 +31,9 @@ import androidx.compose.ui.unit.dp import com.tangem.core.res.getStringSafe import com.tangem.core.ui.components.* import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.core.ui.res.LocalRedesignEnabled +import com.tangem.core.ui.res.LocalTopSnackbarHostState import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags @@ -147,6 +150,9 @@ private fun Buttons( val context = LocalContext.current val resources = context.resources + val isRedesignEnabled = LocalRedesignEnabled.current + val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current + Row( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(16.dp), @@ -159,9 +165,18 @@ private fun Buttons( hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) onCopyClick() coroutineScope.launch { - snackbarHostState.showSnackbar( - message = resources.getStringSafe(R.string.wallet_notification_address_copied), - ) + if (isRedesignEnabled) { + tangemTopSnackbarHostState.showSnackbar( + SnackbarMessage( + startIconId = R.drawable.ic_check_24, + message = resourceReference(R.string.wallet_notification_address_copied), + ), + ) + } else { + snackbarHostState.showSnackbar( + message = resources.getStringSafe(R.string.wallet_notification_address_copied), + ) + } } }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index 838acf9e43..480382ce94 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -10,6 +10,9 @@ import com.tangem.core.res.getStringSafe import com.tangem.core.ui.event.EventEffect import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.core.ui.res.LocalTopSnackbarHostState import com.tangem.core.ui.utils.requestPermission import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent @@ -69,18 +72,17 @@ internal fun WalletEventEffectLegacy( @Composable internal fun WalletEventEffect( walletsPagerState: PagerState, - snackbarHostState: SnackbarHostState, event: StateEvent, onCollapseBalance: () -> Unit, ) { - val resources = LocalContext.current.resources - var showPermissionRequest by remember { mutableStateOf Unit, () -> Unit>?>(null) } HandlePermissionRequest( permissionRequestParams = showPermissionRequest, onPermissionRequestResult = { showPermissionRequest = null }, ) + val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current + EventEffect( event = event, onTrigger = { value -> @@ -92,12 +94,14 @@ internal fun WalletEventEffect( walletsPagerState.scrollToPage(page = value.newIndex) } is WalletEvent.ShowError -> { - snackbarHostState.showSnackbar(message = value.text.resolveReference(resources)) + tangemTopSnackbarHostState.showSnackbar(SnackbarMessage(message = value.text)) } is WalletEvent.CopyAddress -> { - snackbarHostState.showSnackbar( - message = resources.getStringSafe(R.string.wallet_notification_address_copied), - duration = SnackbarDuration.Short, + tangemTopSnackbarHostState.showSnackbar( + SnackbarMessage( + startIconId = R.drawable.ic_check_24, + message = resourceReference(R.string.wallet_notification_address_copied), + ), ) } is WalletEvent.DemonstrateWalletsScrollPreview -> { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt index a874aec01e..6e0ea2edc8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt @@ -18,8 +18,6 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -47,18 +45,14 @@ import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState import com.tangem.core.ui.components.haze.hazeSourceTangem import com.tangem.core.ui.components.rememberIsKeyboardVisible import com.tangem.core.ui.components.sheetscaffold.* -import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar -import com.tangem.core.ui.components.snackbar.TangemSnackbar import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingTopBar import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior -import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.* import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsHint import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsTooltip @@ -85,7 +79,6 @@ internal fun WalletScreen2( val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() } - val snackbarHostState = remember(::SnackbarHostState) val walletsPagerState = rememberPagerState( initialPage = state.selectedWalletIndex, pageCount = { state.wallets2.size }, @@ -104,7 +97,6 @@ internal fun WalletScreen2( WalletContent2( state = state, walletsPagerState = walletsPagerState, - snackbarHostState = snackbarHostState, behavior = behavior, bottomSheetContent = bottomSheetContent, bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider, @@ -113,7 +105,6 @@ internal fun WalletScreen2( WalletEventEffect( walletsPagerState = walletsPagerState, - snackbarHostState = snackbarHostState, event = state.event, onCollapseBalance = { if (behavior.state.collapsedFraction < 1f) { @@ -131,7 +122,6 @@ private fun WalletContent2( state: WalletScreenState, walletsPagerState: PagerState, behavior: TangemCollapsingAppBarBehavior, - snackbarHostState: SnackbarHostState, bottomSheetHeaderHeightProvider: () -> Dp, onBottomSheetStateChange: (BottomSheetState) -> Unit, bottomSheetContent: @Composable (() -> Unit), @@ -143,7 +133,6 @@ private fun WalletContent2( BaseScaffoldWithMarkets( state = state, - snackbarHostState = snackbarHostState, bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider, onBottomSheetStateChange = onBottomSheetStateChange, bottomSheetContent = bottomSheetContent, @@ -278,7 +267,6 @@ private fun WalletContent2( @Composable private inline fun BaseScaffoldWithMarkets( state: WalletScreenState, - snackbarHostState: SnackbarHostState, bottomSheetHeaderHeightProvider: () -> Dp, modifier: Modifier = Modifier, noinline onBottomSheetStateChange: (BottomSheetState) -> Unit, @@ -290,10 +278,7 @@ private inline fun BaseScaffoldWithMarkets( val isKeyboardVisible by rememberIsKeyboardVisible() - val scaffoldState = rememberTangemBottomSheetScaffoldState( - bottomSheetState = bottomSheetState, - snackbarHostState = snackbarHostState, - ) + val scaffoldState = rememberTangemBottomSheetScaffoldState(bottomSheetState = bottomSheetState) val density = LocalDensity.current val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() } @@ -320,15 +305,6 @@ private inline fun BaseScaffoldWithMarkets( Box(modifier = modifier) { TangemBottomSheetScaffold( - snackbarHost = { snackbarHostState -> - WalletSnackbarHost( - snackbarHostState = snackbarHostState, - event = state.event, - modifier = Modifier - .padding(bottom = TangemTheme.dimens2.x1) - .navigationBarsPadding(), - ) - }, containerColor = Color.Unspecified, sheetContainerColor = backgroundColor.value, scaffoldState = scaffoldState, @@ -482,21 +458,6 @@ private fun BottomSheetStateEffects( } } -@Composable -private fun WalletSnackbarHost( - snackbarHostState: SnackbarHostState, - event: StateEvent, - modifier: Modifier = Modifier, -) { - SnackbarHost(hostState = snackbarHostState, modifier = modifier) { data -> - if (event is StateEvent.Triggered && event.data is WalletEvent.CopyAddress) { - CopiedTextSnackbar(data) - } else { - TangemSnackbar(data) - } - } -} - @Composable private fun rememberPageAlpha(pagerState: PagerState, currentPageIndex: Int): State { return remember {