Updated on 2026-08-14
This commit is contained in:
parent
5f4dfefc2e
commit
8411911466
13 changed files with 858 additions and 304 deletions
|
|
@ -0,0 +1,440 @@
|
|||
package com.tangem.core.ui.components.bottomsheets
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.SheetValue.Expanded
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType.Default
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType.Modal
|
||||
import com.tangem.core.ui.components.bottomsheets.internal.InternalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.internal.collapse
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.MODAL_SHEET_MAX_HEIGHT
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeader
|
||||
import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible
|
||||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
|
||||
/**
|
||||
* Type of [TangemBottomSheet] that defines its behavior and appearance.
|
||||
* - [Default]: Standard bottom sheet with a draggable header
|
||||
* - [Modal]: Modal bottom sheet without a draggable header
|
||||
*/
|
||||
enum class TangemBottomSheetType {
|
||||
Default, Modal;
|
||||
|
||||
fun getDragHandle(): (@Composable (() -> Unit))? = when (this) {
|
||||
Default -> {
|
||||
{ TangemBottomSheetDraggableHeader() }
|
||||
}
|
||||
Modal -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modal bottom sheet with [content] and optional [title] and [footer].
|
||||
*
|
||||
* @param config Configuration for the bottom sheet, including visibility and content data.
|
||||
* @param type Type of the bottom sheet that defines its behavior and appearance.
|
||||
* @param containerColor Background color of the bottom sheet container.
|
||||
* @param skipPartiallyExpanded Whether to skip the partially expanded state when dragging the sheet.
|
||||
* @param onBack Optional callback for handling back press when the sheet is visible.
|
||||
* @param title Optional composable for rendering the title section of the sheet, receiving the content
|
||||
* model as a parameter.
|
||||
* @param content Composable for rendering the main content of the sheet, receiving the content model
|
||||
* as a parameter.
|
||||
* @param footer Optional composable for rendering the footer section of the sheet, receiving the content
|
||||
* model as a parameter.
|
||||
*
|
||||
* [Show in Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8454-23749&m=dev)
|
||||
*/
|
||||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
type: TangemBottomSheetType = Default,
|
||||
containerColor: Color = TangemTheme.colors2.surface.level2,
|
||||
skipPartiallyExpanded: Boolean = true,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
crossinline title: @Composable BoxScope.(T) -> Unit = {},
|
||||
crossinline content: @Composable (T) -> Unit,
|
||||
noinline footer: @Composable (BoxScope.(T) -> Unit)? = null,
|
||||
) {
|
||||
val isAlwaysVisible = LocalBottomSheetAlwaysVisible.current
|
||||
|
||||
if (isAlwaysVisible) {
|
||||
PreviewModalBottomSheetWithFooter<T>(
|
||||
config = config,
|
||||
containerColor = containerColor,
|
||||
type = type,
|
||||
title = title,
|
||||
content = content,
|
||||
footer = footer,
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
)
|
||||
} else {
|
||||
DefaultModalBottomSheetWithFooter<T>(
|
||||
config = config,
|
||||
containerColor = containerColor,
|
||||
type = type,
|
||||
title = title,
|
||||
content = content,
|
||||
footer = footer,
|
||||
onBack = onBack,
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheetWithFooter(
|
||||
config: TangemBottomSheetConfig,
|
||||
containerColor: Color,
|
||||
type: TangemBottomSheetType,
|
||||
skipPartiallyExpanded: Boolean = true,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
crossinline title: @Composable BoxScope.(T) -> Unit,
|
||||
crossinline content: @Composable (T) -> Unit,
|
||||
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
|
||||
) {
|
||||
var isVisible by remember { mutableStateOf(value = config.isShown) }
|
||||
|
||||
val sheetState = if (config.dismissOnClickOutside == null) {
|
||||
rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
|
||||
} else {
|
||||
rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
confirmValueChange = { sheetValue ->
|
||||
if (config.dismissOnClickOutside().not()) {
|
||||
// Ignore transitions to hidden (prevents dismiss on outside click/back press)
|
||||
sheetValue != SheetValue.Hidden
|
||||
} else {
|
||||
true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (isVisible && config.content is T) {
|
||||
BasicBottomSheet<T>(
|
||||
config = config,
|
||||
sheetState = sheetState,
|
||||
containerColor = containerColor,
|
||||
type = type,
|
||||
title = title,
|
||||
onBack = onBack,
|
||||
content = content,
|
||||
footer = footer,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = config.isShown) {
|
||||
if (config.isShown) {
|
||||
isVisible = true
|
||||
} else {
|
||||
sheetState.collapse { isVisible = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheetWithFooter(
|
||||
config: TangemBottomSheetConfig,
|
||||
containerColor: Color,
|
||||
type: TangemBottomSheetType,
|
||||
skipPartiallyExpanded: Boolean = true,
|
||||
crossinline title: @Composable BoxScope.(T) -> Unit,
|
||||
crossinline content: @Composable (T) -> Unit,
|
||||
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
|
||||
) {
|
||||
BasicBottomSheet<T>(
|
||||
config = config,
|
||||
sheetState = SheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
initialValue = Expanded,
|
||||
positionalThreshold = { 0f },
|
||||
velocityThreshold = { 0f },
|
||||
),
|
||||
onBack = null,
|
||||
containerColor = containerColor,
|
||||
type = type,
|
||||
title = title,
|
||||
content = content,
|
||||
footer = footer,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
sheetState: SheetState,
|
||||
containerColor: Color,
|
||||
type: TangemBottomSheetType,
|
||||
modifier: Modifier = Modifier,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
crossinline title: @Composable BoxScope.(T) -> Unit,
|
||||
crossinline content: @Composable (T) -> Unit,
|
||||
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
|
||||
) {
|
||||
val model = config.content as? T ?: return
|
||||
val windowSize = LocalWindowSize.current
|
||||
|
||||
val bsContent: @Composable ColumnScope.() -> Unit = {
|
||||
val maxHeight = when (type) {
|
||||
Default -> Dp.Unspecified
|
||||
Modal -> windowSize.height * MODAL_SHEET_MAX_HEIGHT
|
||||
}
|
||||
|
||||
val buttonHeight by animateDpAsState(
|
||||
if (footer != null) {
|
||||
80.dp
|
||||
} else {
|
||||
0.dp
|
||||
},
|
||||
)
|
||||
|
||||
val contentModifier = when (type) {
|
||||
Default -> Modifier.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens2.x8,
|
||||
topEnd = TangemTheme.dimens2.x8,
|
||||
),
|
||||
)
|
||||
Modal -> Modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens2.x2,
|
||||
end = TangemTheme.dimens2.x2,
|
||||
bottom = TangemTheme.dimens2.x2,
|
||||
)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens2.x8))
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = contentModifier
|
||||
.background(containerColor)
|
||||
.heightIn(max = maxHeight),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
title(model)
|
||||
}
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
content(model)
|
||||
if (footer != null) {
|
||||
BottomFade(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
gradientBrush = Brush.verticalGradient(
|
||||
listOf(
|
||||
TangemTheme.colors2.shadow.fadeMin,
|
||||
TangemTheme.colors2.shadow.fadeMax,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(buttonHeight)
|
||||
.align(Alignment.BottomCenter),
|
||||
) {
|
||||
if (footer != null) {
|
||||
footer(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InternalBottomSheet(
|
||||
modifier = modifier.statusBarsPadding(),
|
||||
onDismissRequest = config.onDismissRequest,
|
||||
sheetState = sheetState,
|
||||
containerColor = Color.Transparent,
|
||||
contentWindowInsets = { WindowInsetsZero },
|
||||
onBack = onBack,
|
||||
dragHandle = type.getDragHandle(),
|
||||
content = bsContent,
|
||||
scrimColor = TangemTheme.colors2.overlay.overlaySecondary,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 800)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 800, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TangemModalBottomSheetWithFooter_Preview(
|
||||
@PreviewParameter(TangemBottomSheetPreviewProvider::class) params: TangemBottomSheetType,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
type = params,
|
||||
title = { TangemModalBottomSheetTitle(endIconRes = R.drawable.ic_close_24, onEndClick = {}) },
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(56.dp)
|
||||
.clip(RoundedCornerShape(100))
|
||||
.background(TangemTheme.colors.icon.informative.copy(alpha = 0.1f))
|
||||
.padding(12.dp),
|
||||
painter = rememberVectorPainter(
|
||||
ImageVector.vectorResource(R.drawable.ic_alert_24),
|
||||
),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerH24()
|
||||
Text(
|
||||
text = "Unsuported networks",
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerH8()
|
||||
Text(
|
||||
text = "Tangem does not currently support a required network by React App.",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerH(48.dp)
|
||||
Text(
|
||||
text = "Long text to show scrollable content and bottom fade." +
|
||||
"\nLorem ipsum dolor sit amet, consectetur adipiscing elit. In imperdiet metus non leo " +
|
||||
"ultricies pulvinar. Pellentesque sed condimentum odio. Sed venenatis ac felis non " +
|
||||
"consequat. Nunc erat dolor, maximus nec mattis a, tempus at eros. Duis sit amet neque " +
|
||||
"dui. Donec consectetur nisl id dui convallis, in posuere dolor eleifend. Pellentesque " +
|
||||
"habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. " +
|
||||
"Pellentesque consequat scelerisque justo quis tristique. Mauris laoreet venenatis " +
|
||||
"pharetra. Morbi sed faucibus leo. Praesent elementum pretium posuere. Morbi et felis a " +
|
||||
"turpis pellentesque rhoncus.",
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
text = "Go it",
|
||||
onClick = {},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 800)
|
||||
@Preview(showBackground = true, widthDp = 360, heightDp = 800, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun TangemModalBottomSheetWithFooter_Preview2(
|
||||
@PreviewParameter(TangemBottomSheetPreviewProvider::class) params: TangemBottomSheetType,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
type = params,
|
||||
title = { TangemModalBottomSheetTitle(endIconRes = R.drawable.ic_close_24, onEndClick = {}) },
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.size(56.dp)
|
||||
.clip(RoundedCornerShape(100))
|
||||
.background(TangemTheme.colors.icon.informative.copy(alpha = 0.1f))
|
||||
.padding(12.dp),
|
||||
painter = rememberVectorPainter(
|
||||
ImageVector.vectorResource(R.drawable.ic_alert_24),
|
||||
),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerH24()
|
||||
Text(
|
||||
text = "Unsuported networks",
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerH8()
|
||||
Text(
|
||||
text = "Tangem does not currently support a required network by React App.",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerH(48.dp)
|
||||
Text(
|
||||
text = "Long text to show scrollable content and bottom fade." +
|
||||
"\nLorem ipsum dolor sit amet, consectetur adipiscing elit. In imperdiet metus non leo " +
|
||||
"ultricies pulvinar. Pellentesque sed condimentum odio. Sed venenatis ac felis non " +
|
||||
"consequat. Nunc erat dolor, maximus nec mattis a, tempus at eros. Duis sit amet neque " +
|
||||
"dui. Donec consectetur nisl id dui convallis, in posuere dolor eleifend. Pellentesque " +
|
||||
"habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. " +
|
||||
"Pellentesque consequat scelerisque justo quis tristique. Mauris laoreet venenatis " +
|
||||
"pharetra. Morbi sed faucibus leo. Praesent elementum pretium posuere. Morbi et felis a " +
|
||||
"turpis pellentesque rhoncus.",
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class TangemBottomSheetPreviewProvider : PreviewParameterProvider<TangemBottomSheetType> {
|
||||
override val values: Sequence<TangemBottomSheetType>
|
||||
get() = sequenceOf(
|
||||
Default,
|
||||
Modal,
|
||||
)
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.core.ui.components.bottomsheets.internal
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.*
|
||||
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.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.LocalTopSnackbarHostState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun InternalBottomSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onBack: (() -> Unit)? = null,
|
||||
sheetState: SheetState = rememberModalBottomSheetState(),
|
||||
sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
|
||||
shape: Shape = BottomSheetDefaults.ExpandedShape,
|
||||
containerColor: Color = BottomSheetDefaults.ContainerColor,
|
||||
contentColor: Color = contentColorFor(containerColor),
|
||||
tonalElevation: Dp = 0.dp,
|
||||
scrimColor: Color = BottomSheetDefaults.ScrimColor,
|
||||
dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
|
||||
contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets },
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val topSnackbarHostState = LocalTopSnackbarHostState.current
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
sheetState = sheetState,
|
||||
sheetMaxWidth = sheetMaxWidth,
|
||||
shape = shape,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
tonalElevation = tonalElevation,
|
||||
scrimColor = scrimColor,
|
||||
dragHandle = dragHandle,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
properties = ModalBottomSheetProperties(
|
||||
shouldDismissOnBackPress = onBack == null,
|
||||
),
|
||||
content = {
|
||||
Box {
|
||||
val hazeState = rememberHazeState()
|
||||
|
||||
Column(Modifier.hazeSourceTangem(hazeState)) {
|
||||
content()
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalHazeState provides hazeState) {
|
||||
TangemTopSnackbarHost(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.padding(horizontal = TangemTheme.dimens2.x4)
|
||||
.padding(top = TangemTheme.dimens2.x6),
|
||||
hostState = topSnackbarHostState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = onBack != null && sheetState.targetValue != SheetValue.Hidden) {
|
||||
onBack?.invoke()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
suspend fun SheetState.collapse(onCollapsed: () -> Unit) {
|
||||
coroutineScope {
|
||||
launch { hide() }.invokeOnCompletion { onCollapsed() }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +1,15 @@
|
|||
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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -42,9 +28,6 @@ fun ModalBottomSheetWithBackHandling(
|
|||
contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets },
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val topSnackbarHostState = LocalTopSnackbarHostState.current
|
||||
val isRedesignEnabled = LocalRedesignEnabled.current
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
|
|
@ -61,38 +44,10 @@ fun ModalBottomSheetWithBackHandling(
|
|||
shouldDismissOnBackPress = onBack == null,
|
||||
),
|
||||
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()
|
||||
}
|
||||
|
||||
content()
|
||||
BackHandler(enabled = onBack != null && sheetState.targetValue != SheetValue.Hidden) {
|
||||
onBack?.invoke()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
suspend fun SheetState.collapse(onCollapsed: () -> Unit) {
|
||||
coroutineScope {
|
||||
launch { hide() }.invokeOnCompletion { onCollapsed() }
|
||||
}
|
||||
}
|
||||
|
|
@ -188,7 +188,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
|||
containerColor = containerColor,
|
||||
shape = TangemTheme.shapes.bottomSheetLarge,
|
||||
contentWindowInsets = { WindowInsetsZero },
|
||||
dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) },
|
||||
dragHandle = { TangemBottomSheetDraggableHeaderLegacy(color = containerColor) },
|
||||
onBack = onBack,
|
||||
content = bsContent,
|
||||
scrimColor = TangemTheme.colors.overlay.secondary,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -12,7 +13,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun TangemBottomSheetDraggableHeader(color: Color = TangemTheme.colors.background.primary) {
|
||||
fun TangemBottomSheetDraggableHeaderLegacy(color: Color = TangemTheme.colors.background.primary) {
|
||||
Surface(
|
||||
modifier = Modifier.height(TangemTheme.dimens.size20),
|
||||
color = color,
|
||||
|
|
@ -30,4 +31,21 @@ fun TangemBottomSheetDraggableHeader(color: Color = TangemTheme.colors.backgroun
|
|||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TangemBottomSheetDraggableHeader() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(TangemTheme.dimens2.x3)
|
||||
.padding(vertical = TangemTheme.dimens2.x1)
|
||||
.size(
|
||||
width = TangemTheme.dimens2.x10,
|
||||
height = TangemTheme.dimens2.x1,
|
||||
)
|
||||
.background(
|
||||
color = TangemTheme.colors2.graphic.neutral.primaryInverted,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens2.x0_5),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -2,57 +2,34 @@
|
|||
|
||||
package com.tangem.core.ui.components.sheetscaffold
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BlurMaskFilter
|
||||
import android.renderscript.Allocation
|
||||
import android.renderscript.Element
|
||||
import android.renderscript.RenderScript
|
||||
import android.renderscript.ScriptIntrinsicBlur
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.DraggableAnchors
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.anchoredDraggable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.geometry.center
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.clipPath
|
||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.onPlaced
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastMap
|
||||
import androidx.compose.ui.util.fastMaxOfOrNull
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.core.graphics.withSave
|
||||
import androidx.core.graphics.withTranslation
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue.*
|
||||
import com.tangem.core.ui.extensions.softLayerShadow
|
||||
import com.tangem.core.ui.extensions.conditionalCompose
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.toPx
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
|
|
@ -63,25 +40,17 @@ import kotlin.math.roundToInt
|
|||
* viewing and interacting with both regions. They are commonly used to keep a feature or secondary
|
||||
* content visible on screen when content in main UI region is frequently scrolled or panned.
|
||||
*
|
||||
* 
|
||||
* 
|
||||
*
|
||||
* This component provides API to put together several material components to construct your screen,
|
||||
* by ensuring proper layout strategy for them and collecting necessary data so these components
|
||||
* will work together correctly.
|
||||
*
|
||||
* @param sheetContent the content of the bottom sheet
|
||||
* @param sheetPeekHeight the height of the bottom sheet when it is collapsed
|
||||
* @param modifier the [Modifier] to be applied to this scaffold
|
||||
* @param scaffoldState the state of the bottom sheet scaffold
|
||||
* @param sheetPeekHeight the height of the bottom sheet when it is collapsed
|
||||
* @param sheetMaxWidth [Dp] that defines what the maximum width the sheet will take. Pass in
|
||||
* [Dp.Unspecified] for a sheet that spans the entire screen width.
|
||||
* @param sheetShape the shape of the bottom sheet
|
||||
* @param sheetContainerColor the background color of the bottom sheet
|
||||
* @param sheetSwipeEnabled whether the sheet swiping is enabled and should react to the user's
|
||||
* input
|
||||
* @param topBar top app bar of the screen, typically a [SmallTopAppBar]
|
||||
* @param snackbarHost component to host [Snackbar]s that are pushed to be shown via
|
||||
* @param topBar top app bar of the screen.
|
||||
* @param snackbarHost component to host [TangemTopSnackbar]s that are pushed to be shown via
|
||||
* [SnackbarHostState.showSnackbar], typically a [SnackbarHost]
|
||||
* @param containerColor the color used for the background of this scaffold. Use [Color.Transparent]
|
||||
* to have no color.
|
||||
|
|
@ -95,18 +64,14 @@ import kotlin.math.roundToInt
|
|||
*/
|
||||
@Composable
|
||||
fun TangemBottomSheetScaffold(
|
||||
sheetContent: @Composable ColumnScope.() -> Unit,
|
||||
sheetPeekHeight: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
scaffoldState: TangemBottomSheetScaffoldState = rememberTangemBottomSheetScaffoldState(),
|
||||
sheetPeekHeight: Dp,
|
||||
sheetMaxWidth: Dp = 640.dp,
|
||||
sheetShape: Shape = TangemTheme.shapes.bottomSheetLarge,
|
||||
sheetContainerColor: Color = Color.White,
|
||||
sheetSwipeEnabled: Boolean = true,
|
||||
topBar: @Composable (() -> Unit)? = null,
|
||||
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
|
||||
containerColor: Color = TangemTheme.colors.background.secondary,
|
||||
contentColor: Color = contentColorFor(containerColor),
|
||||
bottomSheet: @Composable () -> Unit = {},
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
BottomSheetScaffoldLayout(
|
||||
|
|
@ -118,17 +83,7 @@ fun TangemBottomSheetScaffold(
|
|||
sheetState = scaffoldState.bottomSheetState,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
bottomSheet = {
|
||||
StandardBottomSheet(
|
||||
state = scaffoldState.bottomSheetState,
|
||||
peekHeight = sheetPeekHeight,
|
||||
sheetMaxWidth = sheetMaxWidth,
|
||||
sheetSwipeEnabled = sheetSwipeEnabled,
|
||||
shape = sheetShape,
|
||||
containerColor = sheetContainerColor,
|
||||
content = sheetContent,
|
||||
)
|
||||
},
|
||||
bottomSheet = bottomSheet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -183,35 +138,31 @@ fun rememberTangemStandardBottomSheetState(
|
|||
skipHiddenState = skipHiddenState,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun StandardBottomSheet(
|
||||
fun CustomBottomSheet(
|
||||
state: TangemSheetState,
|
||||
peekHeight: Dp,
|
||||
sheetMaxWidth: Dp,
|
||||
sheetSwipeEnabled: Boolean,
|
||||
shape: Shape,
|
||||
containerColor: Color,
|
||||
sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
|
||||
sheetSwipeEnabled: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val orientation = Orientation.Vertical
|
||||
|
||||
val peekHeightPx = with(LocalDensity.current) { peekHeight.toPx() }
|
||||
val nestedScroll =
|
||||
if (sheetSwipeEnabled) {
|
||||
Modifier.nestedScroll(
|
||||
remember(state.anchoredDraggableState) {
|
||||
consumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
|
||||
sheetState = state,
|
||||
orientation = orientation,
|
||||
onFling = { scope.launch { state.settle(it) } },
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
val nestedScroll = Modifier.conditionalCompose(sheetSwipeEnabled) {
|
||||
nestedScroll(
|
||||
remember(state.anchoredDraggableState) {
|
||||
consumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
|
||||
sheetState = state,
|
||||
orientation = orientation,
|
||||
onFling = { scope.launch { state.settle(it) } },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -219,61 +170,17 @@ private fun StandardBottomSheet(
|
|||
.fillMaxWidth()
|
||||
.requiredHeightIn(min = peekHeight)
|
||||
.then(nestedScroll)
|
||||
.draggableAnchors(
|
||||
state = state.anchoredDraggableState,
|
||||
.bottomSheetDraggableAnchor(
|
||||
state = state,
|
||||
orientation = orientation,
|
||||
anchors = { sheetSize, constraints ->
|
||||
val layoutHeight = constraints.maxHeight.toFloat()
|
||||
val sheetHeight = sheetSize.height.toFloat()
|
||||
|
||||
val newAnchors = DraggableAnchors {
|
||||
if (!state.skipPartiallyExpanded) {
|
||||
PartiallyExpanded at (layoutHeight - peekHeightPx)
|
||||
}
|
||||
if (sheetHeight != peekHeightPx) {
|
||||
Expanded at maxOf(layoutHeight - sheetHeight, 0f)
|
||||
}
|
||||
if (!state.skipHiddenState) {
|
||||
Hidden at layoutHeight
|
||||
}
|
||||
}
|
||||
val newTarget =
|
||||
when (val oldTarget = state.anchoredDraggableState.targetValue) {
|
||||
Hidden -> if (newAnchors.hasPositionFor(Hidden)) Hidden else oldTarget
|
||||
PartiallyExpanded ->
|
||||
when {
|
||||
newAnchors.hasPositionFor(PartiallyExpanded) -> PartiallyExpanded
|
||||
newAnchors.hasPositionFor(Expanded) -> Expanded
|
||||
newAnchors.hasPositionFor(Hidden) -> Hidden
|
||||
else -> oldTarget
|
||||
}
|
||||
Expanded ->
|
||||
when {
|
||||
newAnchors.hasPositionFor(Expanded) -> Expanded
|
||||
newAnchors.hasPositionFor(PartiallyExpanded) -> PartiallyExpanded
|
||||
newAnchors.hasPositionFor(Hidden) -> Hidden
|
||||
else -> oldTarget
|
||||
}
|
||||
}
|
||||
newAnchors to newTarget
|
||||
},
|
||||
peekHeightPx = peekHeightPx,
|
||||
)
|
||||
.anchoredDraggable(
|
||||
state = state.anchoredDraggableState,
|
||||
orientation = orientation,
|
||||
enabled = sheetSwipeEnabled,
|
||||
)
|
||||
.softLayerShadow(
|
||||
radius = 8.dp,
|
||||
color = Color.Black.copy(
|
||||
alpha = if (isSystemInDarkTheme()) .16f else .08f
|
||||
),
|
||||
shape = shape,
|
||||
offset = DpOffset(x = 0.dp, y = (-4).dp),
|
||||
isAlphaContentClip = true
|
||||
)
|
||||
.background(containerColor, shape)
|
||||
.clip(shape),
|
||||
.then(modifier),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
|
@ -292,8 +199,7 @@ private fun BottomSheetScaffoldLayout(
|
|||
contentColor: Color,
|
||||
) {
|
||||
Layout(
|
||||
contents =
|
||||
listOf<@Composable () -> Unit>(
|
||||
contents = listOf(
|
||||
topBar ?: {},
|
||||
{
|
||||
Surface(
|
||||
|
|
@ -331,13 +237,12 @@ private fun BottomSheetScaffoldLayout(
|
|||
val snackbarWidth = snackbarPlaceables.fastMaxOfOrNull { it.width } ?: 0
|
||||
val snackbarHeight = snackbarPlaceables.fastMaxOfOrNull { it.height } ?: 0
|
||||
val snackbarOffsetX = (layoutWidth - snackbarWidth) / 2
|
||||
val snackbarOffsetY =
|
||||
when (sheetState.currentValue) {
|
||||
PartiallyExpanded -> sheetOffset().roundToInt() - snackbarHeight
|
||||
Expanded,
|
||||
Hidden,
|
||||
-> layoutHeight - snackbarHeight
|
||||
}
|
||||
val snackbarOffsetY = when (sheetState.currentValue) {
|
||||
PartiallyExpanded -> sheetOffset().roundToInt() - snackbarHeight
|
||||
Expanded,
|
||||
Hidden,
|
||||
-> layoutHeight - snackbarHeight
|
||||
}
|
||||
|
||||
// Placement order is important for elevation
|
||||
bodyPlaceables.fastForEach { it.placeRelative(0, topBarHeight) }
|
||||
|
|
@ -346,4 +251,50 @@ private fun BottomSheetScaffoldLayout(
|
|||
snackbarPlaceables.fastForEach { it.placeRelative(snackbarOffsetX, snackbarOffsetY) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.bottomSheetDraggableAnchor(
|
||||
state: TangemSheetState,
|
||||
orientation: Orientation,
|
||||
peekHeightPx: Float,
|
||||
): Modifier {
|
||||
return draggableAnchors(
|
||||
state = state.anchoredDraggableState,
|
||||
orientation = orientation,
|
||||
anchors = { sheetSize, constraints ->
|
||||
val layoutHeight = constraints.maxHeight.toFloat()
|
||||
val sheetHeight = sheetSize.height.toFloat()
|
||||
|
||||
val newAnchors = DraggableAnchors {
|
||||
if (!state.skipPartiallyExpanded) {
|
||||
PartiallyExpanded at (layoutHeight - peekHeightPx)
|
||||
}
|
||||
if (sheetHeight != peekHeightPx) {
|
||||
Expanded at maxOf(layoutHeight - sheetHeight, 0f)
|
||||
}
|
||||
if (!state.skipHiddenState) {
|
||||
Hidden at layoutHeight
|
||||
}
|
||||
}
|
||||
val newTarget =
|
||||
when (val oldTarget = state.anchoredDraggableState.targetValue) {
|
||||
Hidden -> if (newAnchors.hasPositionFor(Hidden)) Hidden else oldTarget
|
||||
PartiallyExpanded ->
|
||||
when {
|
||||
newAnchors.hasPositionFor(PartiallyExpanded) -> PartiallyExpanded
|
||||
newAnchors.hasPositionFor(Expanded) -> Expanded
|
||||
newAnchors.hasPositionFor(Hidden) -> Hidden
|
||||
else -> oldTarget
|
||||
}
|
||||
Expanded ->
|
||||
when {
|
||||
newAnchors.hasPositionFor(Expanded) -> Expanded
|
||||
newAnchors.hasPositionFor(PartiallyExpanded) -> PartiallyExpanded
|
||||
newAnchors.hasPositionFor(Hidden) -> Hidden
|
||||
else -> oldTarget
|
||||
}
|
||||
}
|
||||
newAnchors to newTarget
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -6,7 +6,9 @@ import androidx.compose.ui.graphics.Color
|
|||
object TangemColorPalette {
|
||||
// region Base
|
||||
val Black = Color(0xFF000000)
|
||||
val BlackZero = Color(0x00000000)
|
||||
val White = Color(0xFFFFFFFF)
|
||||
val WhiteZero = Color(0x00FFFFFF)
|
||||
// endregion Base
|
||||
|
||||
// region Dark
|
||||
|
|
@ -17,6 +19,7 @@ object TangemColorPalette {
|
|||
val Dark5 = Color(0xFF303030)
|
||||
val Dark6 = Color(0xFF1E1E1E)
|
||||
val Dark7 = Color(0xFF171717)
|
||||
val Dark8 = Color(0xFF0F0F0F)
|
||||
// endregion Dark
|
||||
|
||||
// region Dark Alpha
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class TangemColors2 internal constructor(
|
|||
val markers: Markers,
|
||||
val tabs: Tabs,
|
||||
val contextMenu: ContextMenu,
|
||||
val shadow: Shadow,
|
||||
) {
|
||||
|
||||
@Stable
|
||||
|
|
@ -637,6 +638,30 @@ class TangemColors2 internal constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
class Shadow internal constructor(
|
||||
min: Color,
|
||||
max: Color,
|
||||
fadeMin: Color,
|
||||
fadeMax: Color,
|
||||
) {
|
||||
var min by mutableStateOf(min)
|
||||
private set
|
||||
var max by mutableStateOf(max)
|
||||
private set
|
||||
var fadeMin by mutableStateOf(fadeMin)
|
||||
private set
|
||||
var fadeMax by mutableStateOf(fadeMax)
|
||||
private set
|
||||
|
||||
fun update(other: Shadow) {
|
||||
min = other.min
|
||||
max = other.max
|
||||
fadeMin = other.fadeMin
|
||||
fadeMax = other.fadeMax
|
||||
}
|
||||
}
|
||||
|
||||
fun update(other: TangemColors2) {
|
||||
text.update(other.text)
|
||||
graphic.update(other.graphic)
|
||||
|
|
@ -651,5 +676,6 @@ class TangemColors2 internal constructor(
|
|||
markers.update(other.markers)
|
||||
tabs.update(other.tabs)
|
||||
contextMenu.update(other.contextMenu)
|
||||
shadow.update(other.shadow)
|
||||
}
|
||||
}
|
||||
|
|
@ -192,6 +192,12 @@ private fun lightThemeColors2(): TangemColors2 {
|
|||
val contextMenu = TangemColors2.ContextMenu(
|
||||
background = TangemColorPalette.Dark_05,
|
||||
)
|
||||
val shadow = TangemColors2.Shadow(
|
||||
min = TangemColorPalette.BlackZero,
|
||||
max = TangemColorPalette.Dark_20,
|
||||
fadeMin = TangemColorPalette.WhiteZero,
|
||||
fadeMax = TangemColorPalette.White,
|
||||
)
|
||||
return TangemColors2(
|
||||
text = text,
|
||||
graphic = graphic,
|
||||
|
|
@ -206,6 +212,7 @@ private fun lightThemeColors2(): TangemColors2 {
|
|||
markers = markers,
|
||||
tabs = tabs,
|
||||
contextMenu = contextMenu,
|
||||
shadow = shadow,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -361,6 +368,12 @@ private fun darkThemeColors2(): TangemColors2 {
|
|||
val contextMenu = TangemColors2.ContextMenu(
|
||||
background = TangemColorPalette.Light_10,
|
||||
)
|
||||
val shadow = TangemColors2.Shadow(
|
||||
min = TangemColorPalette.BlackZero,
|
||||
max = TangemColorPalette.Dark8,
|
||||
fadeMin = TangemColorPalette.BlackZero,
|
||||
fadeMax = TangemColorPalette.Black,
|
||||
)
|
||||
return TangemColors2(
|
||||
text = text,
|
||||
graphic = graphic,
|
||||
|
|
@ -375,5 +388,6 @@ private fun darkThemeColors2(): TangemColors2 {
|
|||
markers = markers,
|
||||
tabs = tabs,
|
||||
contextMenu = contextMenu,
|
||||
shadow = shadow,
|
||||
)
|
||||
}
|
||||
|
|
@ -193,6 +193,7 @@ private fun BadgeTypeRow(
|
|||
color = color,
|
||||
type = type,
|
||||
iconPosition = TangemBadgeIconPosition.Start,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
Box(
|
||||
|
|
@ -205,6 +206,7 @@ private fun BadgeTypeRow(
|
|||
shape = shape,
|
||||
color = color,
|
||||
type = type,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
Box(
|
||||
|
|
@ -217,7 +219,7 @@ private fun BadgeTypeRow(
|
|||
shape = shape,
|
||||
color = color,
|
||||
type = type,
|
||||
iconPosition = TangemBadgeIconPosition.Start,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
|
|
@ -28,10 +26,10 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.ds.button.TangemButton
|
||||
|
|
@ -75,7 +73,6 @@ internal fun OrganizeTokensContent(
|
|||
onDismissRequest = onDismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
addBottomInsets = false,
|
||||
containerColor = TangemTheme.colors2.surface.level2,
|
||||
title = {
|
||||
TangemTopBar(
|
||||
|
|
@ -99,11 +96,14 @@ internal fun OrganizeTokensContent(
|
|||
},
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
BottomButtons(organizeTokensUM = organizeTokensUM)
|
||||
},
|
||||
content = {
|
||||
TokenList(
|
||||
organizeTokensUM = organizeTokensUM,
|
||||
dragAndDropIntents = dragAndDropIntents,
|
||||
modifier = Modifier.hazeSourceTangem(hazeState),
|
||||
modifier = Modifier.hazeSourceTangem(hazeState, zIndex = -1f),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -121,9 +121,7 @@ private fun TokenList(
|
|||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val tokenList = organizeTokensUM.tokenList
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors2.surface.level2),
|
||||
modifier = modifier.background(TangemTheme.colors2.surface.level2),
|
||||
) {
|
||||
val onDragEnd: (Int, Int) -> Unit = remember {
|
||||
{ _, _ ->
|
||||
|
|
@ -175,20 +173,11 @@ private fun TokenList(
|
|||
isBalanceHidden = organizeTokensUM.isBalanceHidden,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SpacerH(TangemTheme.dimens2.x20)
|
||||
}
|
||||
}
|
||||
|
||||
BottomFade(
|
||||
gradientBrush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
TangemTheme.colors2.surface.level2.copy(0.9f),
|
||||
TangemTheme.colors2.surface.level2,
|
||||
),
|
||||
),
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
)
|
||||
|
||||
BottomButtons(organizeTokensUM = organizeTokensUM)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.Canvas
|
|||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
|
|
@ -20,16 +21,17 @@ import androidx.compose.material3.*
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.geometry.*
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.geometry.RoundRect
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Outline
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
|
|
@ -40,19 +42,16 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet
|
||||
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.expressTransactionsItems
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeaderLegacy
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
|
||||
import com.tangem.core.ui.components.rememberIsKeyboardVisible
|
||||
|
|
@ -61,6 +60,7 @@ import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
|
|||
import com.tangem.core.ui.components.snackbar.TangemSnackbar
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.extensions.softLayerShadow
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
|
|
@ -290,15 +290,8 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
crossinline bottomSheetContent: @Composable () -> Unit,
|
||||
crossinline content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
|
||||
val isKeyboardVisible by rememberIsKeyboardVisible()
|
||||
|
||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(
|
||||
bottomSheetState = bottomSheetState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
)
|
||||
|
||||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
|
||||
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
|
||||
|
|
@ -308,6 +301,12 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
val coroutineScope = rememberCoroutineScope()
|
||||
val background = TangemTheme.colors.background.tertiary
|
||||
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(
|
||||
bottomSheetState = bottomSheetState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
)
|
||||
|
||||
val showMarketsHint by remember {
|
||||
derivedStateOf {
|
||||
// Show hint only when there are items in the list
|
||||
|
|
@ -321,7 +320,7 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
|
||||
) {
|
||||
val backgroundColor = LocalMainBottomSheetColor.current
|
||||
val backgroundColor by LocalMainBottomSheetColor.current
|
||||
var isSearchFieldFocused by remember { mutableStateOf(false) }
|
||||
val isNavBarVisible = remember { mutableStateOf(true) }
|
||||
|
||||
|
|
@ -343,43 +342,61 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
.navigationBarsPadding(),
|
||||
)
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
sheetContainerColor = backgroundColor.value,
|
||||
scaffoldState = scaffoldState,
|
||||
sheetPeekHeight = peekHeight,
|
||||
sheetShape = TangemTheme.shapes.bottomSheetLarge,
|
||||
sheetContent = {
|
||||
// hide bottom sheet when back pressed
|
||||
BackHandler(
|
||||
isKeyboardVisible.not() &&
|
||||
bottomSheetState.currentValue == TangemSheetValue.Expanded,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
}
|
||||
|
||||
Column(
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
scaffoldState = scaffoldState,
|
||||
bottomSheet = {
|
||||
CustomBottomSheet(
|
||||
state = scaffoldState.bottomSheetState,
|
||||
peekHeight = peekHeight,
|
||||
modifier = Modifier
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
.softLayerShadow(
|
||||
radius = 8.dp,
|
||||
color = Color.Black.copy(
|
||||
alpha = if (isSystemInDarkTheme()) .16f else .08f,
|
||||
),
|
||||
shape = TangemTheme.shapes.bottomSheetLarge,
|
||||
offset = DpOffset(x = 0.dp, y = (-4).dp),
|
||||
isAlphaContentClip = true,
|
||||
)
|
||||
.clip(TangemTheme.shapes.bottomSheetLarge)
|
||||
.background(backgroundColor),
|
||||
content = {
|
||||
// hide bottom sheet when back pressed
|
||||
BackHandler(
|
||||
isKeyboardVisible.not() &&
|
||||
bottomSheetState.currentValue == TangemSheetValue.Expanded,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onFocusChanged {
|
||||
isSearchFieldFocused = it.isFocused
|
||||
},
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
TangemBottomSheetDraggableHeaderLegacy(backgroundColor)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onFocusChanged {
|
||||
isSearchFieldFocused = it.isFocused
|
||||
},
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
Box {
|
||||
|
|
@ -428,7 +445,7 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
Box(
|
||||
Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(backgroundColor.value)
|
||||
.background(backgroundColor)
|
||||
.height(bottomBarHeight)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
|
|
@ -25,8 +26,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusState
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
|
|
@ -39,9 +42,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.arkivanov.decompose.ExperimentalDecomposeApi
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
|
||||
import com.tangem.core.ui.components.background.northernlights.NorthernLightsBackground
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeader
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshSlidingContainer
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
|
|
@ -295,25 +299,20 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
crossinline bottomSheetContent: @Composable () -> Unit,
|
||||
crossinline content: @Composable (PaddingValues, TangemSheetState) -> Unit,
|
||||
) {
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
|
||||
val isKeyboardVisible by rememberIsKeyboardVisible()
|
||||
|
||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(bottomSheetState = bottomSheetState)
|
||||
|
||||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
|
||||
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
|
||||
val peekHeight = bottomSheetHeaderHeightProvider() + handComposableComponentHeight + bottomBarHeight
|
||||
val maxHeight = LocalWindowSize.current.height
|
||||
val peekHeight = bottomSheetHeaderHeightProvider() + TangemTheme.dimens2.x3 + bottomBarHeight
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val background = TangemTheme.colors2.surface.level3
|
||||
|
||||
val bottomSheetState = rememberTangemStandardBottomSheetState()
|
||||
val scaffoldState = rememberTangemBottomSheetScaffoldState(bottomSheetState = bottomSheetState)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
|
||||
) {
|
||||
val backgroundColor = LocalMainBottomSheetColor.current
|
||||
val backgroundColor by LocalMainBottomSheetColor.current
|
||||
var isSearchFieldFocused by remember { mutableStateOf(false) }
|
||||
val isNavBarVisible = remember { mutableStateOf(true) }
|
||||
|
||||
|
|
@ -327,41 +326,18 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
Box(modifier = modifier) {
|
||||
TangemBottomSheetScaffold(
|
||||
containerColor = Color.Unspecified,
|
||||
sheetContainerColor = backgroundColor.value,
|
||||
scaffoldState = scaffoldState,
|
||||
sheetPeekHeight = peekHeight,
|
||||
sheetShape = TangemTheme.shapes.bottomSheetLarge,
|
||||
sheetContent = {
|
||||
// hide bottom sheet when back pressed
|
||||
BackHandler(
|
||||
isKeyboardVisible.not() &&
|
||||
bottomSheetState.currentValue == TangemSheetValue.Expanded,
|
||||
bottomSheet = {
|
||||
BottomSheet(
|
||||
bottomSheetState = bottomSheetState,
|
||||
backgroundColor = backgroundColor,
|
||||
peekHeight = peekHeight,
|
||||
onFocusChange = { focusState ->
|
||||
isSearchFieldFocused = focusState.isFocused
|
||||
},
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onFocusChanged {
|
||||
isSearchFieldFocused = it.isFocused
|
||||
},
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
bottomSheetContent()
|
||||
}
|
||||
},
|
||||
content = { paddingValues ->
|
||||
|
|
@ -385,7 +361,7 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
Box(
|
||||
Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(backgroundColor.value)
|
||||
.background(backgroundColor)
|
||||
.height(bottomBarHeight)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
|
@ -400,6 +376,81 @@ private inline fun BaseScaffoldWithMarkets(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomSheet(
|
||||
bottomSheetState: TangemSheetState,
|
||||
backgroundColor: Color,
|
||||
peekHeight: Dp,
|
||||
onFocusChange: (FocusState) -> Unit,
|
||||
bottomSheetContent: @Composable () -> Unit,
|
||||
) {
|
||||
val isKeyboardVisible by rememberIsKeyboardVisible()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val density = LocalDensity.current
|
||||
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
|
||||
|
||||
val maxHeight = LocalWindowSize.current.height
|
||||
val shape = RoundedCornerShape(
|
||||
topStart = TangemTheme.dimens2.x8,
|
||||
topEnd = TangemTheme.dimens2.x8,
|
||||
)
|
||||
CustomBottomSheet(
|
||||
state = bottomSheetState,
|
||||
peekHeight = peekHeight,
|
||||
content = {
|
||||
// hide bottom sheet when back pressed
|
||||
BackHandler(
|
||||
isKeyboardVisible.not() &&
|
||||
bottomSheetState.currentValue == TangemSheetValue.Expanded,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.partialExpand() }
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
// expand bottom sheet when clicked on the header
|
||||
.clickable(
|
||||
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
|
||||
indication = null,
|
||||
interactionSource = null,
|
||||
) {
|
||||
coroutineScope.launch { bottomSheetState.expand() }
|
||||
}
|
||||
.sizeIn(maxHeight = maxHeight - statusBarHeight),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
BottomFade(
|
||||
gradientBrush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
TangemTheme.colors2.shadow.min,
|
||||
TangemTheme.colors2.shadow.max,
|
||||
),
|
||||
),
|
||||
modifier = Modifier
|
||||
.offset(y = TangemTheme.dimens2.x5.unaryMinus()),
|
||||
)
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TangemBottomSheetDraggableHeader()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(shape)
|
||||
.background(backgroundColor)
|
||||
.onFocusChanged(onFocusChange),
|
||||
) {
|
||||
bottomSheetContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomSheetScrim(color: Color, visible: Boolean, onDismissRequest: () -> Unit) {
|
||||
val alpha by animateFloatAsState(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue