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.decompose.ui.UiMessageSender
|
||||||
import com.tangem.core.ui.DesignFeatureToggles
|
import com.tangem.core.ui.DesignFeatureToggles
|
||||||
import com.tangem.core.ui.UiDependencies
|
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.haptic.VibratorHapticManager
|
||||||
import com.tangem.core.ui.message.EventMessageHandler
|
import com.tangem.core.ui.message.EventMessageHandler
|
||||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||||
|
|
@ -30,6 +31,7 @@ internal object UiDependenciesModule {
|
||||||
override val vibratorHapticManager = vibratorHapticManager
|
override val vibratorHapticManager = vibratorHapticManager
|
||||||
override val appThemeModeHolder = appThemeModeHolder
|
override val appThemeModeHolder = appThemeModeHolder
|
||||||
override val globalSnackbarHostState: SnackbarHostState = SnackbarHostState()
|
override val globalSnackbarHostState: SnackbarHostState = SnackbarHostState()
|
||||||
|
override val globalTopSnackbarHostState: TangemTopSnackbarHostState = TangemTopSnackbarHostState()
|
||||||
override val eventMessageHandler: EventMessageHandler = EventMessageHandler()
|
override val eventMessageHandler: EventMessageHandler = EventMessageHandler()
|
||||||
override val designFeatureToggles: DesignFeatureToggles = designFeatureToggles
|
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.imePadding
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -28,9 +29,12 @@ import com.arkivanov.essenty.backhandler.BackHandler
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.ui.UiDependencies
|
import com.tangem.core.ui.UiDependencies
|
||||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
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.message.EventMessageEffect
|
||||||
|
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||||
import com.tangem.core.ui.res.LocalRootBackgroundColor
|
import com.tangem.core.ui.res.LocalRootBackgroundColor
|
||||||
import com.tangem.core.ui.res.LocalSnackbarHostState
|
import com.tangem.core.ui.res.LocalSnackbarHostState
|
||||||
|
import com.tangem.core.ui.res.LocalTopSnackbarHostState
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.security.ProvideSecureFlagController
|
import com.tangem.core.ui.security.ProvideSecureFlagController
|
||||||
import com.tangem.tap.routing.component.RoutingComponent
|
import com.tangem.tap.routing.component.RoutingComponent
|
||||||
|
|
@ -93,6 +97,16 @@ internal fun RootContent(
|
||||||
.padding(all = 16.dp),
|
.padding(all = 16.dp),
|
||||||
hostState = snackbarHostState,
|
hostState = snackbarHostState,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (LocalRedesignEnabled.current) {
|
||||||
|
TangemTopSnackbarHost(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
.statusBarsPadding()
|
||||||
|
.padding(all = 16.dp),
|
||||||
|
hostState = LocalTopSnackbarHostState.current,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventMessageEffect()
|
EventMessageEffect()
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ dependencies {
|
||||||
/** Project - Core */
|
/** Project - Core */
|
||||||
implementation(projects.core.res)
|
implementation(projects.core.res)
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
implementation(projects.core.decompose)
|
api(projects.core.decompose)
|
||||||
implementation(projects.core.error)
|
implementation(projects.core.error)
|
||||||
|
|
||||||
/** AndroidX libraries */
|
/** AndroidX libraries */
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.core.ui
|
||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Stable
|
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.haptic.VibratorHapticManager
|
||||||
import com.tangem.core.ui.message.EventMessageHandler
|
import com.tangem.core.ui.message.EventMessageHandler
|
||||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||||
|
|
@ -15,6 +16,8 @@ interface UiDependencies {
|
||||||
|
|
||||||
val globalSnackbarHostState: SnackbarHostState
|
val globalSnackbarHostState: SnackbarHostState
|
||||||
|
|
||||||
|
val globalTopSnackbarHostState: TangemTopSnackbarHostState
|
||||||
|
|
||||||
val eventMessageHandler: EventMessageHandler
|
val eventMessageHandler: EventMessageHandler
|
||||||
|
|
||||||
val designFeatureToggles: DesignFeatureToggles
|
val designFeatureToggles: DesignFeatureToggles
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,27 @@
|
||||||
package com.tangem.core.ui.components.bottomsheets.internal
|
package com.tangem.core.ui.components.bottomsheets.internal
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
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.ColumnScope
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.input.key.*
|
import androidx.compose.ui.input.key.*
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.coroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -31,6 +42,9 @@ fun ModalBottomSheetWithBackHandling(
|
||||||
contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets },
|
contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets },
|
||||||
content: @Composable ColumnScope.() -> Unit,
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val topSnackbarHostState = LocalTopSnackbarHostState.current
|
||||||
|
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -47,7 +61,27 @@ fun ModalBottomSheetWithBackHandling(
|
||||||
shouldDismissOnBackPress = onBack == null,
|
shouldDismissOnBackPress = onBack == null,
|
||||||
),
|
),
|
||||||
content = {
|
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) {
|
BackHandler(enabled = onBack != null && sheetState.targetValue != SheetValue.Hidden) {
|
||||||
onBack?.invoke()
|
onBack?.invoke()
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
|
||||||
val model = config.content as? T ?: return
|
val model = config.content as? T ?: return
|
||||||
|
|
||||||
val bsContent: @Composable ColumnScope.() -> Unit = {
|
val bsContent: @Composable ColumnScope.() -> Unit = {
|
||||||
|
// FIXME: Use LocalWindowSize.current
|
||||||
val maxHeight = LocalConfiguration.current.screenHeightDp * MODAL_SHEET_MAX_HEIGHT
|
val maxHeight = LocalConfiguration.current.screenHeightDp * MODAL_SHEET_MAX_HEIGHT
|
||||||
val initial = 0
|
val initial = 0
|
||||||
val scrollState = rememberScrollState(initial = initial)
|
val scrollState = rememberScrollState(initial = initial)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use TangemTopSnackbarHost instead. Will be removed with redesign")
|
||||||
@Composable
|
@Composable
|
||||||
fun CopiedTextSnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) {
|
fun CopiedTextSnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) {
|
||||||
SnackbarHost(hostState = hostState, modifier = modifier) {
|
SnackbarHost(hostState = hostState, modifier = modifier) {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("With redesign, this component is no longer used. Please use both TangemSnackbar and TangemTopSnackbar")
|
||||||
@Composable
|
@Composable
|
||||||
fun TangemSnackbar(data: SnackbarData, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false) {
|
fun TangemSnackbar(data: SnackbarData, modifier: Modifier = Modifier, actionOnNewLine: Boolean = false) {
|
||||||
Snackbar(
|
Snackbar(
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,10 @@ import com.tangem.core.ui.res.TangemThemePreview
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(
|
||||||
|
"With redesign, this component is no longer used. " +
|
||||||
|
"Please use both TangemSnackbarHost and TangemTopSnackbarHost",
|
||||||
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun TangemSnackbarHost(
|
fun TangemSnackbarHost(
|
||||||
modifier: Modifier = Modifier,
|
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
|
@Immutable
|
||||||
sealed interface EventMessage : UiMessage
|
sealed interface EventMessage : UiMessage
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class ToastMessage(
|
data class ToastMessage(
|
||||||
val message: TextReference,
|
val message: TextReference,
|
||||||
val duration: Duration = ToastMessage.Duration.Short,
|
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 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.
|
* @param action The action to perform when the action button is clicked. Optional, `null` by default.
|
||||||
* */
|
* */
|
||||||
|
@Immutable
|
||||||
data class SnackbarMessage(
|
data class SnackbarMessage(
|
||||||
val message: TextReference,
|
val message: TextReference,
|
||||||
|
@field:DrawableRes val startIconId: Int? = null,
|
||||||
val duration: Duration = Duration.Short,
|
val duration: Duration = Duration.Short,
|
||||||
val onDismissRequest: () -> Unit = {},
|
val onDismissRequest: () -> Unit = {},
|
||||||
val actionLabel: TextReference? = null,
|
val actionLabel: TextReference? = null,
|
||||||
|
|
@ -92,6 +95,7 @@ data class SnackbarMessage(
|
||||||
* `true` by default.
|
* `true` by default.
|
||||||
* @param onDismissRequest The action to perform when the dialog is dismissed.
|
* @param onDismissRequest The action to perform when the dialog is dismissed.
|
||||||
* */
|
* */
|
||||||
|
@Immutable
|
||||||
data class DialogMessage(
|
data class DialogMessage(
|
||||||
val message: TextReference,
|
val message: TextReference,
|
||||||
val title: TextReference? = null,
|
val title: TextReference? = null,
|
||||||
|
|
@ -146,6 +150,7 @@ data class DialogMessage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class GlobalLoadingMessage(val isShow: Boolean) : EventMessage
|
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 secondAction The second action to perform. Optional, `null` by default.
|
||||||
* @param onDismissRequest The action to perform when the bottom sheet is dismissed.
|
* @param onDismissRequest The action to perform when the bottom sheet is dismissed.
|
||||||
* */
|
* */
|
||||||
|
@Immutable
|
||||||
data class BottomSheetMessage(
|
data class BottomSheetMessage(
|
||||||
@DrawableRes val iconResId: Int? = null,
|
@DrawableRes val iconResId: Int? = null,
|
||||||
val title: TextReference? = null,
|
val title: TextReference? = null,
|
||||||
|
|
@ -202,6 +208,7 @@ data class BottomSheetMessage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class BottomSheetMessageV2(
|
data class BottomSheetMessageV2(
|
||||||
val messageBottomSheetUMV2: MessageBottomSheetUMV2,
|
val messageBottomSheetUMV2: MessageBottomSheetUMV2,
|
||||||
) : EventMessage
|
) : 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.MessageBottomSheet
|
||||||
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM
|
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM
|
||||||
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetV2
|
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.event.EventEffect
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.LocalEventMessageHandler
|
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.LocalSnackbarHostState
|
||||||
|
import com.tangem.core.ui.res.LocalTopSnackbarHostState
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -36,6 +39,10 @@ fun EventMessageEffect(
|
||||||
onShowSnackbar: suspend (SnackbarMessage, Context) -> Unit = { message, context ->
|
onShowSnackbar: suspend (SnackbarMessage, Context) -> Unit = { message, context ->
|
||||||
showSnackbar(snackbarHostState, 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) },
|
onShowToast: (ToastMessage, Context) -> Unit = { message, context -> showToast(message, context) },
|
||||||
) {
|
) {
|
||||||
val messageEvent by messageHandler.collectAsState()
|
val messageEvent by messageHandler.collectAsState()
|
||||||
|
|
@ -45,11 +52,16 @@ fun EventMessageEffect(
|
||||||
var bottomSheetMessage: BottomSheetMessage? by remember { mutableStateOf(value = null) }
|
var bottomSheetMessage: BottomSheetMessage? by remember { mutableStateOf(value = null) }
|
||||||
var bottomSheetMessageV2: BottomSheetMessageV2? by remember { mutableStateOf(value = null) }
|
var bottomSheetMessageV2: BottomSheetMessageV2? by remember { mutableStateOf(value = null) }
|
||||||
var loadingMessage: GlobalLoadingMessage? by remember { mutableStateOf(value = null) }
|
var loadingMessage: GlobalLoadingMessage? by remember { mutableStateOf(value = null) }
|
||||||
|
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||||
|
|
||||||
EventEffect(event = messageEvent) { message ->
|
EventEffect(event = messageEvent) { message ->
|
||||||
when (message) {
|
when (message) {
|
||||||
is SnackbarMessage -> {
|
is SnackbarMessage -> {
|
||||||
onShowSnackbar(message, context)
|
if (isRedesignEnabled) {
|
||||||
|
onShowTopSnackbar(message)
|
||||||
|
} else {
|
||||||
|
onShowSnackbar(message, context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is DialogMessage -> {
|
is DialogMessage -> {
|
||||||
dialogMessage = message
|
dialogMessage = message
|
||||||
|
|
@ -220,7 +232,8 @@ private fun showToast(message: ToastMessage, context: Context) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
/* context = */ context,
|
/* context = */ context,
|
||||||
/* text = */ message.message.resolveReference(context.resources),
|
/* text = */ message.message.resolveReference(context.resources),
|
||||||
/* duration = */ when (message.duration) {
|
/* duration = */
|
||||||
|
when (message.duration) {
|
||||||
ToastMessage.Duration.Short -> Toast.LENGTH_SHORT
|
ToastMessage.Duration.Short -> Toast.LENGTH_SHORT
|
||||||
ToastMessage.Duration.Long -> Toast.LENGTH_LONG
|
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.TangemShimmer
|
||||||
import com.tangem.core.ui.components.powersaving.PowerSavingState
|
import com.tangem.core.ui.components.powersaving.PowerSavingState
|
||||||
import com.tangem.core.ui.components.powersaving.rememberPowerSavingState
|
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.BladeAnimation
|
||||||
import com.tangem.core.ui.components.text.rememberBladeAnimation
|
import com.tangem.core.ui.components.text.rememberBladeAnimation
|
||||||
import com.tangem.core.ui.haptic.DefaultHapticManager
|
import com.tangem.core.ui.haptic.DefaultHapticManager
|
||||||
|
|
@ -54,6 +55,7 @@ fun TangemTheme(
|
||||||
vibratorHapticManager = uiDependencies.vibratorHapticManager,
|
vibratorHapticManager = uiDependencies.vibratorHapticManager,
|
||||||
snackbarHostState = uiDependencies.globalSnackbarHostState,
|
snackbarHostState = uiDependencies.globalSnackbarHostState,
|
||||||
eventMessageHandler = uiDependencies.eventMessageHandler,
|
eventMessageHandler = uiDependencies.eventMessageHandler,
|
||||||
|
topSnackbarHostState = uiDependencies.globalTopSnackbarHostState,
|
||||||
overrideSystemBarColors = overrideSystemBarColors,
|
overrideSystemBarColors = overrideSystemBarColors,
|
||||||
typography = typography,
|
typography = typography,
|
||||||
dimens = dimens,
|
dimens = dimens,
|
||||||
|
|
@ -76,6 +78,7 @@ fun TangemTheme(
|
||||||
vibratorHapticManager: VibratorHapticManager? = null,
|
vibratorHapticManager: VibratorHapticManager? = null,
|
||||||
eventMessageHandler: EventMessageHandler = remember { EventMessageHandler() },
|
eventMessageHandler: EventMessageHandler = remember { EventMessageHandler() },
|
||||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||||
|
topSnackbarHostState: TangemTopSnackbarHostState = remember { TangemTopSnackbarHostState() },
|
||||||
overrideSystemBarColors: Boolean = true,
|
overrideSystemBarColors: Boolean = true,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -119,6 +122,7 @@ fun TangemTheme(
|
||||||
LocalIsInDarkTheme provides isDark,
|
LocalIsInDarkTheme provides isDark,
|
||||||
LocalHapticManager provides hapticManager,
|
LocalHapticManager provides hapticManager,
|
||||||
LocalSnackbarHostState provides snackbarHostState,
|
LocalSnackbarHostState provides snackbarHostState,
|
||||||
|
LocalTopSnackbarHostState provides topSnackbarHostState,
|
||||||
LocalEventMessageHandler provides eventMessageHandler,
|
LocalEventMessageHandler provides eventMessageHandler,
|
||||||
LocalWindowSize provides windowSize,
|
LocalWindowSize provides windowSize,
|
||||||
LocalBladeAnimation provides rememberBladeAnimation(),
|
LocalBladeAnimation provides rememberBladeAnimation(),
|
||||||
|
|
@ -389,6 +393,10 @@ val LocalSnackbarHostState = staticCompositionLocalOf<SnackbarHostState> {
|
||||||
error("No SnackbarHostState provided")
|
error("No SnackbarHostState provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val LocalTopSnackbarHostState = staticCompositionLocalOf<TangemTopSnackbarHostState> {
|
||||||
|
error("No TangemTopSnackbarHostState provided")
|
||||||
|
}
|
||||||
|
|
||||||
val LocalEventMessageHandler = staticCompositionLocalOf<EventMessageHandler> {
|
val LocalEventMessageHandler = staticCompositionLocalOf<EventMessageHandler> {
|
||||||
error("No EventMessageHandler provided")
|
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.components.rows.RoundableCornersRow
|
||||||
import com.tangem.core.ui.extensions.pluralStringResourceSafe
|
import com.tangem.core.ui.extensions.pluralStringResourceSafe
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
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.stringReference
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.feature.referral.domain.models.ExpectedAward
|
import com.tangem.feature.referral.domain.models.ExpectedAward
|
||||||
|
|
@ -352,6 +356,8 @@ private fun AdditionalButtons(
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val resources = LocalContext.current.resources
|
val resources = LocalContext.current.resources
|
||||||
|
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||||
|
val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
@ -366,10 +372,19 @@ private fun AdditionalButtons(
|
||||||
clipboardManager.setText(AnnotatedString(code))
|
clipboardManager.setText(AnnotatedString(code))
|
||||||
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
snackbarHostState.showSnackbar(
|
if (isRedesignEnabled) {
|
||||||
message = resources.getStringSafe(R.string.referral_promo_code_copied),
|
tangemTopSnackbarHostState.showSnackbar(
|
||||||
duration = SnackbarDuration.Short,
|
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),
|
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.components.token.state.TokenItemState.FiatAmountState
|
||||||
import com.tangem.core.ui.extensions.stringReference
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
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.TangemColorPalette
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
|
@ -104,10 +107,24 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
|
||||||
val errorSnackbar = stateHolder.errorSnackbar
|
val errorSnackbar = stateHolder.errorSnackbar
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val resources = LocalContext.current.resources
|
val resources = LocalContext.current.resources
|
||||||
|
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||||
|
val topSnackbarHostState = LocalTopSnackbarHostState.current
|
||||||
|
|
||||||
SideEffect {
|
SideEffect {
|
||||||
if (errorSnackbar != null) {
|
if (errorSnackbar != null) {
|
||||||
coroutineScope.launch {
|
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(
|
val result = snackbarHostState.showSnackbar(
|
||||||
message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable),
|
message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable),
|
||||||
actionLabel = resources.getStringSafe(R.string.warning_button_ok),
|
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.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbarHost
|
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
|
@Composable
|
||||||
internal fun ContainerWithSnackbarHost(snackbarHostState: SnackbarHostState, content: @Composable () -> Unit) {
|
internal fun ContainerWithSnackbarHost(snackbarHostState: SnackbarHostState, content: @Composable () -> Unit) {
|
||||||
Box {
|
Box {
|
||||||
content()
|
content()
|
||||||
CopiedTextSnackbarHost(
|
|
||||||
hostState = snackbarHostState,
|
if (LocalRedesignEnabled.current.not()) {
|
||||||
modifier = Modifier
|
CopiedTextSnackbarHost(
|
||||||
.align(Alignment.BottomCenter)
|
hostState = snackbarHostState,
|
||||||
.padding(bottom = 80.dp),
|
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.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.components.notifications.Notification
|
import com.tangem.core.ui.components.notifications.Notification
|
||||||
import com.tangem.core.ui.extensions.*
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
import com.tangem.features.tokenreceive.entity.ReceiveAddress
|
||||||
|
|
@ -203,6 +206,8 @@ private fun AddressBlock(assetsUM: ReceiveAssetsUM, snackbarHostState: SnackbarH
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
|
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||||
|
val topSnackbarHostState = LocalTopSnackbarHostState.current
|
||||||
|
|
||||||
assetsUM.addresses
|
assetsUM.addresses
|
||||||
.fastFilter { it.type is ReceiveAddress.Type.Ens }
|
.fastFilter { it.type is ReceiveAddress.Type.Ens }
|
||||||
|
|
@ -214,11 +219,20 @@ private fun AddressBlock(assetsUM: ReceiveAssetsUM, snackbarHostState: SnackbarH
|
||||||
assetsUM.onCopyClick(address)
|
assetsUM.onCopyClick(address)
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
snackbarHostState.showSnackbar(
|
if (isRedesignEnabled) {
|
||||||
message = resources.getStringSafe(
|
topSnackbarHostState.showSnackbar(
|
||||||
R.string.wallet_notification_address_copied,
|
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,
|
address = address.value,
|
||||||
|
|
@ -413,6 +427,8 @@ private fun ButtonsBlock(snackbarHostState: SnackbarHostState, onCopyClick: () -
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
|
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||||
|
val topSnackbarHostState = LocalTopSnackbarHostState.current
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.width(IntrinsicSize.Min),
|
modifier = Modifier.width(IntrinsicSize.Min),
|
||||||
|
|
@ -427,9 +443,18 @@ private fun ButtonsBlock(snackbarHostState: SnackbarHostState, onCopyClick: () -
|
||||||
onCopyClick()
|
onCopyClick()
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
snackbarHostState.showSnackbar(
|
if (isRedesignEnabled) {
|
||||||
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
|
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.res.getStringSafe
|
||||||
import com.tangem.core.ui.components.*
|
import com.tangem.core.ui.components.*
|
||||||
import com.tangem.core.ui.extensions.*
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
|
import com.tangem.core.ui.test.TokenReceiveQrCodeBottomSheetTestTags
|
||||||
|
|
@ -147,6 +150,9 @@ private fun Buttons(
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
|
|
||||||
|
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||||
|
val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
|
@ -159,9 +165,18 @@ private fun Buttons(
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
onCopyClick()
|
onCopyClick()
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
snackbarHostState.showSnackbar(
|
if (isRedesignEnabled) {
|
||||||
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
|
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.EventEffect
|
||||||
import com.tangem.core.ui.event.StateEvent
|
import com.tangem.core.ui.event.StateEvent
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
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.core.ui.utils.requestPermission
|
||||||
import com.tangem.feature.wallet.impl.R
|
import com.tangem.feature.wallet.impl.R
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
|
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
|
||||||
|
|
@ -69,18 +72,17 @@ internal fun WalletEventEffectLegacy(
|
||||||
@Composable
|
@Composable
|
||||||
internal fun WalletEventEffect(
|
internal fun WalletEventEffect(
|
||||||
walletsPagerState: PagerState,
|
walletsPagerState: PagerState,
|
||||||
snackbarHostState: SnackbarHostState,
|
|
||||||
event: StateEvent<WalletEvent>,
|
event: StateEvent<WalletEvent>,
|
||||||
onCollapseBalance: () -> Unit,
|
onCollapseBalance: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val resources = LocalContext.current.resources
|
|
||||||
|
|
||||||
var showPermissionRequest by remember { mutableStateOf<Pair<() -> Unit, () -> Unit>?>(null) }
|
var showPermissionRequest by remember { mutableStateOf<Pair<() -> Unit, () -> Unit>?>(null) }
|
||||||
HandlePermissionRequest(
|
HandlePermissionRequest(
|
||||||
permissionRequestParams = showPermissionRequest,
|
permissionRequestParams = showPermissionRequest,
|
||||||
onPermissionRequestResult = { showPermissionRequest = null },
|
onPermissionRequestResult = { showPermissionRequest = null },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val tangemTopSnackbarHostState = LocalTopSnackbarHostState.current
|
||||||
|
|
||||||
EventEffect(
|
EventEffect(
|
||||||
event = event,
|
event = event,
|
||||||
onTrigger = { value ->
|
onTrigger = { value ->
|
||||||
|
|
@ -92,12 +94,14 @@ internal fun WalletEventEffect(
|
||||||
walletsPagerState.scrollToPage(page = value.newIndex)
|
walletsPagerState.scrollToPage(page = value.newIndex)
|
||||||
}
|
}
|
||||||
is WalletEvent.ShowError -> {
|
is WalletEvent.ShowError -> {
|
||||||
snackbarHostState.showSnackbar(message = value.text.resolveReference(resources))
|
tangemTopSnackbarHostState.showSnackbar(SnackbarMessage(message = value.text))
|
||||||
}
|
}
|
||||||
is WalletEvent.CopyAddress -> {
|
is WalletEvent.CopyAddress -> {
|
||||||
snackbarHostState.showSnackbar(
|
tangemTopSnackbarHostState.showSnackbar(
|
||||||
message = resources.getStringSafe(R.string.wallet_notification_address_copied),
|
SnackbarMessage(
|
||||||
duration = SnackbarDuration.Short,
|
startIconId = R.drawable.ic_check_24,
|
||||||
|
message = resourceReference(R.string.wallet_notification_address_copied),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is WalletEvent.DemonstrateWalletsScrollPreview -> {
|
is WalletEvent.DemonstrateWalletsScrollPreview -> {
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
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.haze.hazeSourceTangem
|
||||||
import com.tangem.core.ui.components.rememberIsKeyboardVisible
|
import com.tangem.core.ui.components.rememberIsKeyboardVisible
|
||||||
import com.tangem.core.ui.components.sheetscaffold.*
|
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.TangemCollapsingAppBarBehavior
|
||||||
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingTopBar
|
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingTopBar
|
||||||
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
|
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.extensions.TextReference
|
||||||
import com.tangem.core.ui.res.*
|
import com.tangem.core.ui.res.*
|
||||||
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData
|
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.NOT_INITIALIZED_WALLET_INDEX
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM
|
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.state.model.WalletScreenState
|
||||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsHint
|
import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsHint
|
||||||
import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsTooltip
|
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 statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() }
|
||||||
|
|
||||||
val snackbarHostState = remember(::SnackbarHostState)
|
|
||||||
val walletsPagerState = rememberPagerState(
|
val walletsPagerState = rememberPagerState(
|
||||||
initialPage = state.selectedWalletIndex,
|
initialPage = state.selectedWalletIndex,
|
||||||
pageCount = { state.wallets2.size },
|
pageCount = { state.wallets2.size },
|
||||||
|
|
@ -104,7 +97,6 @@ internal fun WalletScreen2(
|
||||||
WalletContent2(
|
WalletContent2(
|
||||||
state = state,
|
state = state,
|
||||||
walletsPagerState = walletsPagerState,
|
walletsPagerState = walletsPagerState,
|
||||||
snackbarHostState = snackbarHostState,
|
|
||||||
behavior = behavior,
|
behavior = behavior,
|
||||||
bottomSheetContent = bottomSheetContent,
|
bottomSheetContent = bottomSheetContent,
|
||||||
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
|
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
|
||||||
|
|
@ -113,7 +105,6 @@ internal fun WalletScreen2(
|
||||||
|
|
||||||
WalletEventEffect(
|
WalletEventEffect(
|
||||||
walletsPagerState = walletsPagerState,
|
walletsPagerState = walletsPagerState,
|
||||||
snackbarHostState = snackbarHostState,
|
|
||||||
event = state.event,
|
event = state.event,
|
||||||
onCollapseBalance = {
|
onCollapseBalance = {
|
||||||
if (behavior.state.collapsedFraction < 1f) {
|
if (behavior.state.collapsedFraction < 1f) {
|
||||||
|
|
@ -131,7 +122,6 @@ private fun WalletContent2(
|
||||||
state: WalletScreenState,
|
state: WalletScreenState,
|
||||||
walletsPagerState: PagerState,
|
walletsPagerState: PagerState,
|
||||||
behavior: TangemCollapsingAppBarBehavior,
|
behavior: TangemCollapsingAppBarBehavior,
|
||||||
snackbarHostState: SnackbarHostState,
|
|
||||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||||
onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
||||||
bottomSheetContent: @Composable (() -> Unit),
|
bottomSheetContent: @Composable (() -> Unit),
|
||||||
|
|
@ -143,7 +133,6 @@ private fun WalletContent2(
|
||||||
|
|
||||||
BaseScaffoldWithMarkets(
|
BaseScaffoldWithMarkets(
|
||||||
state = state,
|
state = state,
|
||||||
snackbarHostState = snackbarHostState,
|
|
||||||
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
|
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
|
||||||
onBottomSheetStateChange = onBottomSheetStateChange,
|
onBottomSheetStateChange = onBottomSheetStateChange,
|
||||||
bottomSheetContent = bottomSheetContent,
|
bottomSheetContent = bottomSheetContent,
|
||||||
|
|
@ -278,7 +267,6 @@ private fun WalletContent2(
|
||||||
@Composable
|
@Composable
|
||||||
private inline fun BaseScaffoldWithMarkets(
|
private inline fun BaseScaffoldWithMarkets(
|
||||||
state: WalletScreenState,
|
state: WalletScreenState,
|
||||||
snackbarHostState: SnackbarHostState,
|
|
||||||
bottomSheetHeaderHeightProvider: () -> Dp,
|
bottomSheetHeaderHeightProvider: () -> Dp,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
|
||||||
|
|
@ -290,10 +278,7 @@ private inline fun BaseScaffoldWithMarkets(
|
||||||
|
|
||||||
val isKeyboardVisible by rememberIsKeyboardVisible()
|
val isKeyboardVisible by rememberIsKeyboardVisible()
|
||||||
|
|
||||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(
|
val scaffoldState = rememberTangemBottomSheetScaffoldState(bottomSheetState = bottomSheetState)
|
||||||
bottomSheetState = bottomSheetState,
|
|
||||||
snackbarHostState = snackbarHostState,
|
|
||||||
)
|
|
||||||
|
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
|
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
|
||||||
|
|
@ -320,15 +305,6 @@ private inline fun BaseScaffoldWithMarkets(
|
||||||
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
TangemBottomSheetScaffold(
|
TangemBottomSheetScaffold(
|
||||||
snackbarHost = { snackbarHostState ->
|
|
||||||
WalletSnackbarHost(
|
|
||||||
snackbarHostState = snackbarHostState,
|
|
||||||
event = state.event,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(bottom = TangemTheme.dimens2.x1)
|
|
||||||
.navigationBarsPadding(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
containerColor = Color.Unspecified,
|
containerColor = Color.Unspecified,
|
||||||
sheetContainerColor = backgroundColor.value,
|
sheetContainerColor = backgroundColor.value,
|
||||||
scaffoldState = scaffoldState,
|
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
|
@Composable
|
||||||
private fun rememberPageAlpha(pagerState: PagerState, currentPageIndex: Int): State<Float> {
|
private fun rememberPageAlpha(pagerState: PagerState, currentPageIndex: Int): State<Float> {
|
||||||
return remember {
|
return remember {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue