Updated on 2026-08-14
This commit is contained in:
parent
47db65fdbe
commit
9f2b2eac71
21 changed files with 658 additions and 72 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<WalletEvent>,
|
||||
onCollapseBalance: () -> Unit,
|
||||
) {
|
||||
val resources = LocalContext.current.resources
|
||||
|
||||
var showPermissionRequest by remember { mutableStateOf<Pair<() -> 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 -> {
|
||||
|
|
|
|||
|
|
@ -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<WalletEvent>,
|
||||
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<Float> {
|
||||
return remember {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue