Updated on 2026-08-14
This commit is contained in:
parent
b3fe4cbe05
commit
e1ee680173
4 changed files with 230 additions and 39 deletions
|
|
@ -1,11 +1,18 @@
|
|||
package com.tangem.core.ui.components.bottomsheets
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.SheetState
|
||||
import androidx.compose.material3.SheetValue.Expanded
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
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.utils.WindowInsetsZero
|
||||
|
|
@ -13,45 +20,79 @@ import kotlinx.coroutines.coroutineScope
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Tangem bottom sheet with custom draggable header and config
|
||||
*
|
||||
* @param config data model containing logic and ui models
|
||||
* @param
|
||||
* @param content custom bottom sheet content
|
||||
* Bottom sheet with [content], [titleText] and optional [titleAction].
|
||||
* */
|
||||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
titleText: TextReference,
|
||||
titleAction: TopAppBarButtonUM? = null,
|
||||
containerColor: Color = TangemTheme.colors.background.primary,
|
||||
addBottomInsets: Boolean = true,
|
||||
crossinline content: @Composable ColumnScope.(T) -> Unit,
|
||||
) {
|
||||
TangemBottomSheet(
|
||||
config = config,
|
||||
containerColor = containerColor,
|
||||
addBottomInsets = addBottomInsets,
|
||||
title = { TangemBottomSheetTitle(title = titleText, endButton = titleAction) },
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bottom sheet with [content] and optional [title].
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
containerColor: Color = TangemTheme.colors.background.primary,
|
||||
addBottomInsets: Boolean = true,
|
||||
crossinline title: @Composable BoxScope.(T) -> Unit = {},
|
||||
crossinline content: @Composable ColumnScope.(T) -> Unit,
|
||||
) {
|
||||
val isAlwaysVisible = LocalBottomSheetAlwaysVisible.current
|
||||
|
||||
if (isAlwaysVisible) {
|
||||
PreviewBottomSheet<T>(
|
||||
config = config,
|
||||
containerColor = containerColor,
|
||||
addBottomInsets = addBottomInsets,
|
||||
title = title,
|
||||
content = content,
|
||||
)
|
||||
} else {
|
||||
DefaultBottomSheet<T>(
|
||||
config = config,
|
||||
containerColor = containerColor,
|
||||
addBottomInsets = addBottomInsets,
|
||||
title = title,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> DefaultBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
containerColor: Color,
|
||||
addBottomInsets: Boolean,
|
||||
crossinline title: @Composable (BoxScope.(T) -> Unit),
|
||||
crossinline content: @Composable (ColumnScope.(T) -> Unit),
|
||||
) {
|
||||
var isVisible by remember { mutableStateOf(value = config.isShow) }
|
||||
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
||||
if (isVisible && config.content is T) {
|
||||
ModalBottomSheet(
|
||||
// FIXME temporary solution to fix height of the bottom sheet
|
||||
modifier = Modifier.sizeIn(maxHeight = LocalWindowSize.current.height - statusBarHeight),
|
||||
onDismissRequest = config.onDismissRequest,
|
||||
BasicBottomSheet<T>(
|
||||
config = config,
|
||||
sheetState = sheetState,
|
||||
containerColor = containerColor,
|
||||
shape = TangemTheme.shapes.bottomSheetLarge,
|
||||
windowInsets = WindowInsetsZero,
|
||||
dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) },
|
||||
) {
|
||||
if (addBottomInsets) {
|
||||
Column(
|
||||
// FIXME temporary solution to fix height of the bottom sheet
|
||||
modifier = Modifier.navigationBarsPadding(),
|
||||
) {
|
||||
content(config.content)
|
||||
}
|
||||
} else {
|
||||
content(config.content)
|
||||
}
|
||||
}
|
||||
addBottomInsets = addBottomInsets,
|
||||
title = title,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = config.isShow) {
|
||||
|
|
@ -63,6 +104,74 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> PreviewBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
containerColor: Color,
|
||||
addBottomInsets: Boolean,
|
||||
crossinline title: @Composable (BoxScope.(T) -> Unit),
|
||||
crossinline content: @Composable (ColumnScope.(T) -> Unit),
|
||||
) {
|
||||
BasicBottomSheet<T>(
|
||||
config = config,
|
||||
sheetState = SheetState(
|
||||
skipPartiallyExpanded = true,
|
||||
initialValue = Expanded,
|
||||
density = LocalDensity.current,
|
||||
),
|
||||
containerColor = containerColor,
|
||||
addBottomInsets = addBottomInsets,
|
||||
title = title,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
sheetState: SheetState,
|
||||
containerColor: Color,
|
||||
addBottomInsets: Boolean,
|
||||
crossinline title: @Composable (BoxScope.(T) -> Unit),
|
||||
crossinline content: @Composable (ColumnScope.(T) -> Unit),
|
||||
) {
|
||||
val model = config.content as? T ?: return
|
||||
|
||||
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getTop(this).toDp() }
|
||||
|
||||
ModalBottomSheet(
|
||||
// FIXME temporary solution to fix height of the bottom sheet
|
||||
modifier = Modifier.sizeIn(maxHeight = LocalWindowSize.current.height - statusBarHeight),
|
||||
onDismissRequest = config.onDismissRequest,
|
||||
sheetState = sheetState,
|
||||
containerColor = containerColor,
|
||||
shape = TangemTheme.shapes.bottomSheetLarge,
|
||||
windowInsets = WindowInsetsZero,
|
||||
dragHandle = { TangemBottomSheetDraggableHeader(color = containerColor) },
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.let {
|
||||
if (addBottomInsets) {
|
||||
it.navigationBarsPadding()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
},
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
title(model)
|
||||
}
|
||||
|
||||
content(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
suspend fun SheetState.collapse(onCollapsed: () -> Unit) {
|
||||
coroutineScope {
|
||||
|
|
|
|||
|
|
@ -5,7 +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.material.Surface
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -14,8 +14,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
@Composable
|
||||
fun TangemBottomSheetDraggableHeader(color: Color = TangemTheme.colors.background.primary) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.height(TangemTheme.dimens.size20),
|
||||
modifier = Modifier.height(TangemTheme.dimens.size20),
|
||||
color = color,
|
||||
) {
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.core.ui.components.bottomsheets
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBarHeight
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
fun TangemBottomSheetTitle(
|
||||
title: TextReference?,
|
||||
modifier: Modifier = Modifier,
|
||||
endButton: TopAppBarButtonUM? = null,
|
||||
containerColor: Color = Color.Transparent,
|
||||
) {
|
||||
TangemBottomSheetTitle(
|
||||
modifier = modifier,
|
||||
title = title?.resolveReference(),
|
||||
endButton = endButton,
|
||||
containerColor = containerColor,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TangemBottomSheetTitle(
|
||||
title: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
endButton: TopAppBarButtonUM? = null,
|
||||
containerColor: Color = Color.Transparent,
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = modifier,
|
||||
title = title,
|
||||
endButton = endButton,
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
iconTint = TangemTheme.colors.icon.informative,
|
||||
titleAlignment = Alignment.CenterHorizontally,
|
||||
containerColor = containerColor,
|
||||
height = TangemTopAppBarHeight.BOTTOM_SHEET,
|
||||
)
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_TangemBottomSheetTitle() {
|
||||
TangemThemePreview {
|
||||
TangemBottomSheetTitle(
|
||||
title = "Title",
|
||||
endButton = TopAppBarButtonUM(
|
||||
iconRes = R.drawable.ic_information_24,
|
||||
onIconClicked = {},
|
||||
),
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -3,6 +3,9 @@ package com.tangem.core.ui.res
|
|||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import com.tangem.core.ui.haptic.MockHapticManager
|
||||
import com.tangem.core.ui.windowsize.rememberWindowSizePreview
|
||||
|
|
@ -12,18 +15,30 @@ fun TangemThemePreview(
|
|||
isDark: Boolean? = null,
|
||||
typography: TangemTypography = TangemTheme.typography,
|
||||
dimens: TangemDimens = TangemTheme.dimens,
|
||||
alwaysShowBottomSheets: Boolean = true,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val isDarkTheme = isDark ?: isSystemInDarkTheme()
|
||||
|
||||
BoxWithConstraints {
|
||||
TangemTheme(
|
||||
isDark = isDarkTheme,
|
||||
typography = typography,
|
||||
dimens = dimens,
|
||||
windowSize = rememberWindowSizePreview(maxWidth, maxHeight),
|
||||
hapticManager = MockHapticManager(LocalHapticFeedback.current),
|
||||
content = content,
|
||||
)
|
||||
CompositionLocalProvider(
|
||||
LocalBottomSheetAlwaysVisible provides alwaysShowBottomSheets,
|
||||
) {
|
||||
BoxWithConstraints {
|
||||
TangemTheme(
|
||||
isDark = isDarkTheme,
|
||||
typography = typography,
|
||||
dimens = dimens,
|
||||
windowSize = rememberWindowSizePreview(maxWidth, maxHeight),
|
||||
hapticManager = MockHapticManager(LocalHapticFeedback.current),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to make the bottom sheet always visible in the Preview and should be `true` only in the Preview.
|
||||
* */
|
||||
val LocalBottomSheetAlwaysVisible: ProvidableCompositionLocal<Boolean> = compositionLocalOf {
|
||||
false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue