Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-06 17:11:59 +03:00
parent 47db65fdbe
commit 9f2b2eac71
21 changed files with 658 additions and 72 deletions

View file

@ -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

View file

@ -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()

View file

@ -167,6 +167,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> 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)

View file

@ -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) {

View file

@ -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(

View file

@ -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,

View file

@ -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",
),
),
)
}
}
}

View file

@ -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<SnackbarMessage?>(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<SnackbarMessage?>() }
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<T> {
var current: Any? = Any()
var items = mutableListOf<ScaleFromTopItem<T>>()
var scope: RecomposeScope? = null
}
private data class ScaleFromTopItem<T>(
val key: T,
val transition: @Composable (content: @Composable () -> Unit) -> Unit,
)
@Composable
private fun animatedOpacity(
animation: AnimationSpec<Float>,
visible: Boolean,
onAnimationFinish: () -> Unit = {},
): State<Float> {
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<Float>,
exitAnimation: AnimationSpec<Float>,
visible: Boolean,
): State<Float> {
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()
}

View file

@ -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

View file

@ -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
},

View file

@ -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<SnackbarHostState> {
error("No SnackbarHostState provided")
}
val LocalTopSnackbarHostState = staticCompositionLocalOf<TangemTopSnackbarHostState> {
error("No TangemTopSnackbarHostState provided")
}
val LocalEventMessageHandler = staticCompositionLocalOf<EventMessageHandler> {
error("No EventMessageHandler provided")
}