Updated on 2026-08-14
This commit is contained in:
parent
00c5775f03
commit
5d4fd522ac
12 changed files with 1038 additions and 60 deletions
|
|
@ -4,8 +4,9 @@ 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.*
|
||||
import androidx.compose.material3.SheetValue.Expanded
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -31,6 +32,9 @@ 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.components.sheetscaffold.TangemSheetState
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue
|
||||
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
|
||||
import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible
|
||||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -131,14 +135,14 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheetW
|
|||
var isVisible by remember { mutableStateOf(value = config.isShown) }
|
||||
|
||||
val sheetState = if (config.dismissOnClickOutside == null) {
|
||||
rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
|
||||
rememberSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
|
||||
} else {
|
||||
rememberModalBottomSheetState(
|
||||
rememberSheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
confirmValueChange = { sheetValue ->
|
||||
if (config.dismissOnClickOutside().not()) {
|
||||
// Ignore transitions to hidden (prevents dismiss on outside click/back press)
|
||||
sheetValue != SheetValue.Hidden
|
||||
sheetValue != TangemSheetValue.Hidden
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
|
@ -182,11 +186,9 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheetW
|
|||
) {
|
||||
BasicBottomSheet<T>(
|
||||
config = config,
|
||||
sheetState = SheetState(
|
||||
sheetState = rememberSheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
initialValue = Expanded,
|
||||
positionalThreshold = { 0f },
|
||||
velocityThreshold = { 0f },
|
||||
initialValue = TangemSheetValue.Expanded,
|
||||
),
|
||||
onBack = null,
|
||||
containerColor = containerColor,
|
||||
|
|
@ -197,12 +199,12 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheetW
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@Suppress("LongParameterList", "LongMethod", "ComposableParametersOrdering")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
sheetState: SheetState,
|
||||
sheetState: TangemSheetState = rememberSheetState(),
|
||||
containerColor: Color,
|
||||
type: TangemBottomSheetType,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -216,13 +218,12 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
|||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
var footerHeightDp by remember { mutableStateOf<Dp?>(null) }
|
||||
val maxHeight = when (type) {
|
||||
Default -> Dp.Unspecified
|
||||
Modal -> windowSize.height * MODAL_SHEET_MAX_HEIGHT
|
||||
}
|
||||
|
||||
val bsContent: @Composable ColumnScope.() -> Unit = {
|
||||
val maxHeight = when (type) {
|
||||
Default -> Dp.Unspecified
|
||||
Modal -> windowSize.height * MODAL_SHEET_MAX_HEIGHT
|
||||
}
|
||||
|
||||
val contentModifier = when (type) {
|
||||
Default -> Modifier
|
||||
.clip(
|
||||
|
|
@ -276,6 +277,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
|||
onBack = onBack,
|
||||
dragHandle = type.getDragHandle(),
|
||||
content = bsContent,
|
||||
peekHeightDp = maxHeight,
|
||||
scrimColor = TangemTheme.colors2.overlay.overlaySecondary,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tangem.core.ui.components.bottomsheets.copy
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.AnimationVector1D
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.anchoredDraggable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SheetValue.Hidden
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.semantics.isTraversalGroup
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.lerp
|
||||
import com.tangem.core.ui.components.bottomsheets.copy.internal.DragHandleWithTooltip
|
||||
import com.tangem.core.ui.components.bottomsheets.copy.internal.ModalBottomSheetDialog
|
||||
import com.tangem.core.ui.components.bottomsheets.copy.internal.ModalBottomSheetProperties
|
||||
import com.tangem.core.ui.components.bottomsheets.copy.internal.StandardMotionTokens
|
||||
import com.tangem.core.ui.components.sheetscaffold.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* [Material Design modal bottom sheet](https://m3.material.io/components/bottom-sheets/overview)
|
||||
*
|
||||
* Modal bottom sheets are used as an alternative to inline menus or simple dialogs on mobile,
|
||||
* especially when offering a long list of action items, or when items require longer descriptions
|
||||
* and icons. Like dialogs, modal bottom sheets appear in front of app content, disabling all other
|
||||
* app functionality when they appear, and remaining on screen until confirmed, dismissed, or a
|
||||
* required action has been taken.
|
||||
*
|
||||
* 
|
||||
*
|
||||
* A simple example of a modal bottom sheet looks like this:
|
||||
*
|
||||
* @sample androidx.compose.material3.samples.ModalBottomSheetSample
|
||||
* @param onDismissRequest Executes when the user clicks outside of the bottom sheet, after sheet
|
||||
* animates to [Hidden].
|
||||
* @param modifier Optional [Modifier] for the bottom sheet.
|
||||
* @param sheetState The state of the bottom sheet.
|
||||
* @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 sheetGesturesEnabled Whether the bottom sheet can be interacted with by gestures.
|
||||
* @param shape The shape of the bottom sheet.
|
||||
* @param containerColor The color used for the background of this bottom sheet
|
||||
* @param contentColor The preferred color for content inside this bottom sheet. Defaults to either
|
||||
* the matching content color for [containerColor], or to the current [LocalContentColor] if
|
||||
* [containerColor] is not a color from the theme.
|
||||
* @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
|
||||
* overlay is applied on top of the container. A higher tonal elevation value will result in a
|
||||
* darker color in light theme and lighter color in dark theme. See also: [Surface].
|
||||
* @param scrimColor Color of the scrim that obscures content when the bottom sheet is open.
|
||||
* @param dragHandle Optional visual marker to swipe the bottom sheet.
|
||||
* @param contentWindowInsets callback which provides window insets to be passed to the bottom sheet
|
||||
* content via [Modifier.windowInsetsPadding]. [ModalBottomSheet] will pre-emptively consume top
|
||||
* insets based on it's current offset. This keeps content outside of the expected window insets
|
||||
* at any position.
|
||||
* @param properties [ModalBottomSheetProperties] for further customization of this modal bottom
|
||||
* sheet's window behavior.
|
||||
* @param content The content to be displayed inside the bottom sheet.
|
||||
*/
|
||||
@Composable
|
||||
@ExperimentalMaterial3Api
|
||||
@Suppress(
|
||||
"LongParameterList",
|
||||
"LongMethod",
|
||||
"MagicNumber",
|
||||
"ComposableEventParameterNaming",
|
||||
"ComposableParametersOrdering",
|
||||
"ReusedModifierInstance",
|
||||
)
|
||||
fun ModalBottomSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
sheetState: TangemSheetState = rememberTangemStandardBottomSheetState(),
|
||||
sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
|
||||
sheetGesturesEnabled: Boolean = true,
|
||||
shape: Shape = BottomSheetDefaults.ExpandedShape,
|
||||
containerColor: Color = BottomSheetDefaults.ContainerColor,
|
||||
contentColor: Color = contentColorFor(containerColor),
|
||||
tonalElevation: Dp = 0.dp,
|
||||
peekHeightDp: Dp,
|
||||
scrimColor: Color = BottomSheetDefaults.ScrimColor,
|
||||
dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
|
||||
contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets },
|
||||
properties: ModalBottomSheetProperties = ModalBottomSheetProperties(),
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val animateToDismiss: () -> Unit = {
|
||||
scope
|
||||
.launch { sheetState.hide() }
|
||||
.invokeOnCompletion {
|
||||
if (!sheetState.isVisible) {
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
val settleToDismiss: (velocity: Float) -> Unit = {
|
||||
scope
|
||||
.launch { sheetState.settle(it) }
|
||||
.invokeOnCompletion { if (!sheetState.isVisible) onDismissRequest() }
|
||||
}
|
||||
|
||||
val predictiveBackProgress = remember { Animatable(initialValue = 0f) }
|
||||
|
||||
ModalBottomSheetDialog(
|
||||
properties = properties,
|
||||
contentColor = contentColor,
|
||||
onDismissRequest = {
|
||||
if (sheetState.currentValue == TangemSheetValue.Expanded && sheetState.hasPartiallyExpandedState) {
|
||||
// Smoothly animate away predictive back transformations since we are not fully
|
||||
// dismissing. We don't need to do this in the else below because we want to
|
||||
// preserve the predictive back transformations (scale) during the hide animation.
|
||||
scope.launch { predictiveBackProgress.animateTo(0f) }
|
||||
scope.launch { sheetState.partialExpand() }
|
||||
} else { // Is expanded without collapsed state or is collapsed.
|
||||
scope.launch { sheetState.hide() }.invokeOnCompletion { onDismissRequest() }
|
||||
}
|
||||
},
|
||||
predictiveBackProgress = predictiveBackProgress,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.semantics { isTraversalGroup = true },
|
||||
) {
|
||||
Scrim(
|
||||
color = scrimColor,
|
||||
onDismissRequest = animateToDismiss,
|
||||
visible = sheetState.targetValue != TangemSheetValue.Hidden,
|
||||
dismissEnabled = properties.shouldDismissOnClickOutside,
|
||||
)
|
||||
ModalBottomSheetContent(
|
||||
predictiveBackProgress = predictiveBackProgress,
|
||||
scope = scope,
|
||||
animateToDismiss = animateToDismiss,
|
||||
settleToDismiss = settleToDismiss,
|
||||
modifier = modifier,
|
||||
sheetState = sheetState,
|
||||
sheetMaxWidth = sheetMaxWidth,
|
||||
sheetGesturesEnabled = sheetGesturesEnabled,
|
||||
shape = shape,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
tonalElevation = tonalElevation,
|
||||
peekHeightDp = peekHeightDp,
|
||||
dragHandle = dragHandle,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (sheetState.hasExpandedState) {
|
||||
LaunchedEffect(sheetState) { sheetState.show() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ExperimentalMaterial3Api
|
||||
@Suppress(
|
||||
"LongParameterList",
|
||||
"LongMethod",
|
||||
"MagicNumber",
|
||||
"ComposableEventParameterNaming",
|
||||
"ComposableParametersOrdering",
|
||||
)
|
||||
internal fun BoxScope.ModalBottomSheetContent(
|
||||
predictiveBackProgress: Animatable<Float, AnimationVector1D>,
|
||||
scope: CoroutineScope,
|
||||
animateToDismiss: () -> Unit,
|
||||
settleToDismiss: (velocity: Float) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
sheetState: TangemSheetState = rememberTangemStandardBottomSheetState(),
|
||||
sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
|
||||
sheetGesturesEnabled: Boolean = true,
|
||||
shape: Shape = BottomSheetDefaults.ExpandedShape,
|
||||
containerColor: Color = BottomSheetDefaults.ContainerColor,
|
||||
contentColor: Color = contentColorFor(containerColor),
|
||||
tonalElevation: Dp = BottomSheetDefaults.Elevation,
|
||||
peekHeightDp: Dp,
|
||||
dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
|
||||
contentWindowInsets: @Composable () -> WindowInsets = { BottomSheetDefaults.windowInsets },
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val orientation = Orientation.Vertical
|
||||
val peekHeightPx = with(LocalDensity.current) { peekHeightDp.toPx() }
|
||||
|
||||
Surface(
|
||||
modifier =
|
||||
modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.widthIn(max = sheetMaxWidth)
|
||||
.fillMaxWidth()
|
||||
.then(
|
||||
if (sheetGesturesEnabled) {
|
||||
Modifier.nestedScroll(
|
||||
remember(sheetState) {
|
||||
consumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
|
||||
sheetState = sheetState,
|
||||
orientation = Orientation.Vertical,
|
||||
onFling = settleToDismiss,
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
},
|
||||
)
|
||||
.bottomSheetDraggableAnchor(sheetState, Orientation.Vertical, peekHeightPx)
|
||||
.anchoredDraggable(
|
||||
state = sheetState.anchoredDraggableState,
|
||||
orientation = orientation,
|
||||
enabled = sheetGesturesEnabled,
|
||||
)
|
||||
.consumeWindowInsets(WindowInsets(top = sheetState.offset.toInt().coerceAtLeast(0)))
|
||||
.graphicsLayer {
|
||||
val sheetOffset = sheetState.anchoredDraggableState.offset
|
||||
val sheetHeight = size.height
|
||||
if (!sheetOffset.isNaN() && !sheetHeight.isNaN() && sheetHeight != 0f) {
|
||||
val progress = predictiveBackProgress.value
|
||||
scaleX = calculatePredictiveBackScaleX(progress)
|
||||
scaleY = calculatePredictiveBackScaleY(progress)
|
||||
@Suppress("MagicNumber")
|
||||
transformOrigin =
|
||||
TransformOrigin(0.5f, (sheetOffset + sheetHeight) / sheetHeight)
|
||||
}
|
||||
},
|
||||
shape = shape,
|
||||
color = containerColor,
|
||||
contentColor = contentColor,
|
||||
tonalElevation = tonalElevation,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.windowInsetsPadding(contentWindowInsets())
|
||||
.graphicsLayer {
|
||||
val progress = predictiveBackProgress.value
|
||||
val predictiveBackScaleX = calculatePredictiveBackScaleX(progress)
|
||||
val predictiveBackScaleY = calculatePredictiveBackScaleY(progress)
|
||||
|
||||
// Preserve the original aspect ratio and alignment of the child content.
|
||||
scaleY =
|
||||
if (predictiveBackScaleY != 0f) {
|
||||
predictiveBackScaleX / predictiveBackScaleY
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
transformOrigin = PredictiveBackChildTransformOrigin
|
||||
},
|
||||
) {
|
||||
if (dragHandle != null) {
|
||||
DragHandleWithTooltip {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.clickable {
|
||||
when (sheetState.currentValue) {
|
||||
TangemSheetValue.Expanded -> animateToDismiss()
|
||||
TangemSheetValue.PartiallyExpanded -> scope.launch { sheetState.expand() }
|
||||
else -> scope.launch { sheetState.show() }
|
||||
}
|
||||
},
|
||||
) {
|
||||
dragHandle()
|
||||
}
|
||||
}
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun GraphicsLayerScope.calculatePredictiveBackScaleX(progress: Float): Float {
|
||||
val width = size.width
|
||||
return if (width.isNaN() || width == 0f) {
|
||||
1f
|
||||
} else {
|
||||
1f - lerp(0f, min(PredictiveBackMaxScaleXDistance.toPx(), width), progress) / width
|
||||
}
|
||||
}
|
||||
|
||||
private fun GraphicsLayerScope.calculatePredictiveBackScaleY(progress: Float): Float {
|
||||
val height = size.height
|
||||
return if (height.isNaN() || height == 0f) {
|
||||
1f
|
||||
} else {
|
||||
1f - lerp(0f, min(PredictiveBackMaxScaleYDistance.toPx(), height), progress) / height
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Scrim(color: Color, onDismissRequest: () -> Unit, visible: Boolean, dismissEnabled: Boolean) {
|
||||
// TODO Load the motionScheme tokens from the component tokens file
|
||||
if (color.isSpecified) {
|
||||
val alpha by
|
||||
animateFloatAsState(
|
||||
targetValue = if (visible) 1f else 0f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = StandardMotionTokens.SpringDefaultEffectsDamping,
|
||||
stiffness = StandardMotionTokens.SpringDefaultEffectsStiffness,
|
||||
),
|
||||
)
|
||||
val dismissSheet =
|
||||
if (dismissEnabled) {
|
||||
Modifier
|
||||
.pointerInput(onDismissRequest) { detectTapGestures { onDismissRequest() } }
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Canvas(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.then(dismissSheet),
|
||||
) {
|
||||
drawRect(color = color, alpha = alpha.coerceIn(0f, 1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val PredictiveBackMaxScaleXDistance = 48.dp
|
||||
private val PredictiveBackMaxScaleYDistance = 24.dp
|
||||
private val PredictiveBackChildTransformOrigin = TransformOrigin(0.5f, 0f)
|
||||
|
|
@ -0,0 +1,515 @@
|
|||
/*
|
||||
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tangem.core.ui.components.bottomsheets.copy.internal
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Outline
|
||||
import android.os.Build
|
||||
import android.view.*
|
||||
import androidx.activity.BackEventCompat
|
||||
import androidx.activity.ComponentDialog
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.AnimationVector1D
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.Easing
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.R
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.platform.*
|
||||
import androidx.compose.ui.semantics.dialog
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import androidx.compose.ui.window.SecureFlagPolicy
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.findViewTreeViewModelStoreOwner
|
||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.setViewTreeViewModelStoreOwner
|
||||
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
// Logic forked from androidx.compose.ui.window.DialogProperties. Removed dismissOnClickOutside
|
||||
// and usePlatformDefaultWidth as they are not relevant for fullscreen experience.
|
||||
/**
|
||||
* Properties used to customize the behavior of a [ModalBottomSheet].
|
||||
*
|
||||
* @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom
|
||||
* sheet's window.
|
||||
* @param shouldDismissOnBackPress Whether the modal bottom sheet can be dismissed by pressing the
|
||||
* back button. If true, pressing the back button will call onDismissRequest.
|
||||
*/
|
||||
@Immutable
|
||||
@ExperimentalMaterial3Api
|
||||
class ModalBottomSheetProperties {
|
||||
val securePolicy: SecureFlagPolicy
|
||||
val shouldDismissOnBackPress: Boolean
|
||||
|
||||
@get:JvmName("shouldDismissOnClickOutside") val shouldDismissOnClickOutside: Boolean
|
||||
internal val isAppearanceLightStatusBars: Boolean?
|
||||
internal val isAppearanceLightNavigationBars: Boolean?
|
||||
|
||||
/**
|
||||
* Properties used to customize the behavior of a [ModalBottomSheet].
|
||||
*
|
||||
* This constructor provides default behavior for [ModalBottomSheet]. See other constructors for
|
||||
* customization options.
|
||||
*/
|
||||
constructor() {
|
||||
this.securePolicy = SecureFlagPolicy.Inherit
|
||||
this.shouldDismissOnBackPress = true
|
||||
this.shouldDismissOnClickOutside = true
|
||||
this.isAppearanceLightStatusBars = null
|
||||
this.isAppearanceLightNavigationBars = null
|
||||
}
|
||||
|
||||
constructor(shouldDismissOnBackPress: Boolean, shouldDismissOnClickOutside: Boolean) {
|
||||
this.securePolicy = SecureFlagPolicy.Inherit
|
||||
this.shouldDismissOnBackPress = shouldDismissOnBackPress
|
||||
this.shouldDismissOnClickOutside = shouldDismissOnClickOutside
|
||||
this.isAppearanceLightNavigationBars = null
|
||||
this.isAppearanceLightStatusBars = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties used to customize the behavior of a [ModalBottomSheet].
|
||||
*
|
||||
* @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom
|
||||
* sheet's window.
|
||||
* @param shouldDismissOnBackPress Whether the modal bottom sheet can be dismissed by pressing
|
||||
* the back button. If true, pressing the back button will call onDismissRequest.
|
||||
* @param shouldDismissOnClickOutside Whether the modal bottom sheet can be dismissed by
|
||||
* clicking on the scrim.
|
||||
*/
|
||||
constructor(
|
||||
securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
|
||||
shouldDismissOnBackPress: Boolean = true,
|
||||
shouldDismissOnClickOutside: Boolean = true,
|
||||
) {
|
||||
this.securePolicy = securePolicy
|
||||
this.shouldDismissOnBackPress = shouldDismissOnBackPress
|
||||
this.shouldDismissOnClickOutside = shouldDismissOnClickOutside
|
||||
this.isAppearanceLightNavigationBars = null
|
||||
this.isAppearanceLightStatusBars = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties used to customize the behavior of a [ModalBottomSheet].
|
||||
*
|
||||
* Use this constructor to customize the behavior of status and navigation bars on the
|
||||
* [ModalBottomSheet] window.
|
||||
*
|
||||
* @param isAppearanceLightStatusBars If true, changes the foreground color of the status bars
|
||||
* to light so that the items on the bar can be read clearly. If false, reverts to the default
|
||||
* appearance.
|
||||
* @param isAppearanceLightNavigationBars If true, changes the foreground color of the
|
||||
* navigation bars to light so that the items on the bar can be read clearly. If false,
|
||||
* reverts to the default appearance.
|
||||
* @param securePolicy Policy for setting [WindowManager.LayoutParams.FLAG_SECURE] on the bottom
|
||||
* sheet's window.
|
||||
* @param shouldDismissOnBackPress Whether the modal bottom sheet can be dismissed by pressing
|
||||
* the back button. If true, pressing the back button will call onDismissRequest.
|
||||
* @param shouldDismissOnClickOutside Whether the modal bottom sheet can be dismissed by
|
||||
* clicking on the scrim.
|
||||
*/
|
||||
constructor(
|
||||
isAppearanceLightStatusBars: Boolean,
|
||||
isAppearanceLightNavigationBars: Boolean,
|
||||
securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
|
||||
shouldDismissOnBackPress: Boolean = true,
|
||||
shouldDismissOnClickOutside: Boolean = true,
|
||||
) {
|
||||
this.shouldDismissOnBackPress = shouldDismissOnBackPress
|
||||
this.shouldDismissOnClickOutside = shouldDismissOnClickOutside
|
||||
this.securePolicy = securePolicy
|
||||
this.isAppearanceLightStatusBars = isAppearanceLightStatusBars
|
||||
this.isAppearanceLightNavigationBars = isAppearanceLightNavigationBars
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Use empty constructor or constructor including shouldDismissOnScrimClick param.",
|
||||
level = DeprecationLevel.HIDDEN,
|
||||
)
|
||||
constructor(
|
||||
securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
|
||||
shouldDismissOnBackPress: Boolean = true,
|
||||
) : this(securePolicy, shouldDismissOnBackPress, true)
|
||||
|
||||
@Deprecated(
|
||||
message = "Use empty constructor or constructor including shouldDismissOnScrimClick param.",
|
||||
level = DeprecationLevel.HIDDEN,
|
||||
)
|
||||
constructor(
|
||||
isAppearanceLightStatusBars: Boolean,
|
||||
isAppearanceLightNavigationBars: Boolean,
|
||||
securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
|
||||
shouldDismissOnBackPress: Boolean = true,
|
||||
) {
|
||||
this.shouldDismissOnBackPress = shouldDismissOnBackPress
|
||||
this.shouldDismissOnClickOutside = true
|
||||
this.securePolicy = securePolicy
|
||||
this.isAppearanceLightStatusBars = isAppearanceLightStatusBars
|
||||
this.isAppearanceLightNavigationBars = isAppearanceLightNavigationBars
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is ModalBottomSheetProperties) return false
|
||||
if (securePolicy != other.securePolicy) return false
|
||||
if (isAppearanceLightStatusBars != other.isAppearanceLightStatusBars) return false
|
||||
if (isAppearanceLightNavigationBars != other.isAppearanceLightNavigationBars) return false
|
||||
if (shouldDismissOnClickOutside != other.shouldDismissOnClickOutside) return false
|
||||
if (shouldDismissOnBackPress != other.shouldDismissOnBackPress) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = securePolicy.hashCode()
|
||||
result = 31 * result + shouldDismissOnBackPress.hashCode()
|
||||
result = 31 * result + (isAppearanceLightStatusBars?.hashCode() ?: 0)
|
||||
result = 31 * result + (isAppearanceLightNavigationBars?.hashCode() ?: 0)
|
||||
result = 31 * result + shouldDismissOnClickOutside.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// Fork of androidx.compose.ui.window.AndroidDialog_androidKt.Dialog
|
||||
// Added predictiveBackProgress param to pass into BottomSheetDialogWrapper.
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun ModalBottomSheetDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
contentColor: Color,
|
||||
properties: ModalBottomSheetProperties,
|
||||
predictiveBackProgress: Animatable<Float, AnimationVector1D>,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val density = LocalDensity.current
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val composition = rememberCompositionContext()
|
||||
val currentContent by rememberUpdatedState(content)
|
||||
val dialogId = rememberSaveable { UUID.randomUUID() }
|
||||
val scope = rememberCoroutineScope()
|
||||
val dialog =
|
||||
remember(view, density) {
|
||||
ModalBottomSheetDialogWrapper(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = properties,
|
||||
contentColor = contentColor,
|
||||
composeView = view,
|
||||
layoutDirection = layoutDirection,
|
||||
density = density,
|
||||
dialogId = dialogId,
|
||||
predictiveBackProgress = predictiveBackProgress,
|
||||
scope = scope,
|
||||
)
|
||||
.apply {
|
||||
setContent(composition) {
|
||||
Box(Modifier.semantics { dialog() }) { currentContent() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(dialog) {
|
||||
dialog.show()
|
||||
|
||||
onDispose {
|
||||
dialog.dismiss()
|
||||
dialog.disposeComposition()
|
||||
}
|
||||
}
|
||||
|
||||
SideEffect {
|
||||
dialog.updateParameters(
|
||||
onDismissRequest = onDismissRequest,
|
||||
properties = properties,
|
||||
contentColor = contentColor,
|
||||
layoutDirection = layoutDirection,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Fork of androidx.compose.ui.window.DialogLayout
|
||||
// Additional parameters required for current predictive back implementation.
|
||||
@Suppress("ViewConstructor")
|
||||
private class ModalBottomSheetDialogLayout(context: Context, override val window: Window) :
|
||||
AbstractComposeView(context), DialogWindowProvider {
|
||||
|
||||
private var content: @Composable () -> Unit by mutableStateOf({})
|
||||
|
||||
override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
|
||||
private set
|
||||
|
||||
fun setContent(parent: CompositionContext, content: @Composable () -> Unit) {
|
||||
setParentCompositionContext(parent)
|
||||
this.content = content
|
||||
shouldCreateCompositionOnAttachedToWindow = true
|
||||
createComposition()
|
||||
}
|
||||
|
||||
// Display width and height logic removed, size will always span fillMaxSize().
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
// Fork of androidx.compose.ui.window.DialogWrapper.
|
||||
// predictiveBackProgress and scope params added for predictive back implementation.
|
||||
// EdgeToEdgeFloatingDialogWindowTheme provided to allow theme to extend into status bar.
|
||||
@ExperimentalMaterial3Api
|
||||
@Suppress("LongParameterList", "NamedArguments")
|
||||
private class ModalBottomSheetDialogWrapper(
|
||||
private var onDismissRequest: () -> Unit,
|
||||
private var properties: ModalBottomSheetProperties,
|
||||
private var contentColor: Color,
|
||||
private val composeView: View,
|
||||
layoutDirection: LayoutDirection,
|
||||
density: Density,
|
||||
dialogId: UUID,
|
||||
predictiveBackProgress: Animatable<Float, AnimationVector1D>,
|
||||
scope: CoroutineScope,
|
||||
) :
|
||||
ComponentDialog(
|
||||
ContextThemeWrapper(
|
||||
composeView.context,
|
||||
androidx.compose.material3.R.style.EdgeToEdgeFloatingDialogWindowTheme,
|
||||
),
|
||||
),
|
||||
ViewRootForInspector {
|
||||
|
||||
private val dialogLayout: ModalBottomSheetDialogLayout
|
||||
|
||||
// On systems older than Android S, there is a bug in the surface insets matrix math used by
|
||||
// elevation, so high values of maxSupportedElevation break accessibility services: b/232788477.
|
||||
private val maxSupportedElevation = 8.dp
|
||||
|
||||
override val subCompositionView: AbstractComposeView
|
||||
get() = dialogLayout
|
||||
|
||||
init {
|
||||
val window = window ?: error("Dialog has no window")
|
||||
window.requestFeature(Window.FEATURE_NO_TITLE)
|
||||
window.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
dialogLayout =
|
||||
ModalBottomSheetDialogLayout(context, window).apply {
|
||||
// Set unique id for AbstractComposeView. This allows state restoration for the
|
||||
// state defined inside the Dialog via rememberSaveable()
|
||||
setTag(R.id.compose_view_saveable_id_tag, "Dialog:$dialogId")
|
||||
// Enable children to draw their shadow by not clipping them
|
||||
clipChildren = false
|
||||
// Allocate space for elevation
|
||||
with(density) { elevation = maxSupportedElevation.toPx() }
|
||||
// Simple outline to force window manager to allocate space for shadow.
|
||||
// Note that the outline affects clickable area for the dismiss listener. In
|
||||
// case of shapes like circle the area for dismiss might be to small
|
||||
// (rectangular outline consuming clicks outside of the circle).
|
||||
outlineProvider =
|
||||
object : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View, result: Outline) {
|
||||
result.setRect(0, 0, view.width, view.height)
|
||||
// We set alpha to 0 to hide the view's shadow and let the
|
||||
// composable to draw its own shadow. This still enables us to get
|
||||
// the extra space needed in the surface.
|
||||
result.alpha = 0f
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clipping logic removed because we are spanning edge to edge.
|
||||
|
||||
setContentView(dialogLayout)
|
||||
dialogLayout.setViewTreeLifecycleOwner(composeView.findViewTreeLifecycleOwner())
|
||||
dialogLayout.setViewTreeViewModelStoreOwner(composeView.findViewTreeViewModelStoreOwner())
|
||||
dialogLayout.setViewTreeSavedStateRegistryOwner(
|
||||
composeView.findViewTreeSavedStateRegistryOwner(),
|
||||
)
|
||||
|
||||
// Initial setup
|
||||
updateParameters(onDismissRequest, properties, contentColor, layoutDirection)
|
||||
|
||||
WindowCompat.getInsetsController(window, window.decorView).apply {
|
||||
// Theme system bars based on content color. Light system bars provide dark icons
|
||||
// and vice-versa. This maintains visible system bars for the bottom sheet window.
|
||||
isAppearanceLightStatusBars =
|
||||
properties.isAppearanceLightStatusBars ?: contentColor.isDark()
|
||||
isAppearanceLightNavigationBars =
|
||||
properties.isAppearanceLightNavigationBars ?: contentColor.isDark()
|
||||
}
|
||||
// Due to how the onDismissRequest callback works
|
||||
// (it enforces a just-in-time decision on whether to update the state to hide the dialog)
|
||||
// we need to provide a custom onBackPressedCallback to provide predictive back animations
|
||||
// for this component while handling onDismissRequest.
|
||||
onBackPressedDispatcher.addCallback(
|
||||
owner = this,
|
||||
onBackPressedCallback =
|
||||
PredictiveBackOnBackPressedCallback(
|
||||
isEnabled = properties.shouldDismissOnBackPress,
|
||||
scope = scope,
|
||||
predictiveBackProgress = predictiveBackProgress,
|
||||
onDismissRequest = {
|
||||
this.onDismissRequest()
|
||||
}, // Ensure lambda captures current onDismissRequest
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun setLayoutDirection(layoutDirection: LayoutDirection) {
|
||||
dialogLayout.layoutDirection =
|
||||
when (layoutDirection) {
|
||||
LayoutDirection.Ltr -> android.util.LayoutDirection.LTR
|
||||
LayoutDirection.Rtl -> android.util.LayoutDirection.RTL
|
||||
}
|
||||
}
|
||||
|
||||
fun setContent(parentComposition: CompositionContext, children: @Composable () -> Unit) {
|
||||
dialogLayout.setContent(parentComposition, children)
|
||||
}
|
||||
|
||||
@Suppress("BooleanPropertyNaming", "UnsafeCallOnNullableType")
|
||||
private fun setSecurePolicy(securePolicy: SecureFlagPolicy) {
|
||||
val secureFlagEnabled =
|
||||
securePolicy.shouldApplySecureFlag(composeView.isFlagSecureEnabled())
|
||||
window!!.setFlags(
|
||||
if (secureFlagEnabled) {
|
||||
WindowManager.LayoutParams.FLAG_SECURE
|
||||
} else {
|
||||
WindowManager.LayoutParams.FLAG_SECURE.inv()
|
||||
},
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun updateParameters(
|
||||
onDismissRequest: () -> Unit,
|
||||
properties: ModalBottomSheetProperties,
|
||||
contentColor: Color,
|
||||
layoutDirection: LayoutDirection,
|
||||
) {
|
||||
this.onDismissRequest = onDismissRequest
|
||||
this.properties = properties
|
||||
this.contentColor = contentColor
|
||||
setSecurePolicy(properties.securePolicy)
|
||||
setLayoutDirection(layoutDirection)
|
||||
|
||||
// Window flags to span parent window.
|
||||
window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
)
|
||||
window?.setSoftInputMode(
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
|
||||
} else {
|
||||
@Suppress("DEPRECATION") WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun disposeComposition() {
|
||||
dialogLayout.disposeComposition()
|
||||
}
|
||||
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
val result = super.onTouchEvent(event)
|
||||
if (result) {
|
||||
onDismissRequest()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
// Prevents the dialog from dismissing itself
|
||||
return
|
||||
}
|
||||
|
||||
private class PredictiveBackOnBackPressedCallback(
|
||||
isEnabled: Boolean,
|
||||
val scope: CoroutineScope,
|
||||
val predictiveBackProgress: Animatable<Float, AnimationVector1D>,
|
||||
var onDismissRequest: () -> Unit,
|
||||
) : OnBackPressedCallback(isEnabled) {
|
||||
|
||||
override fun handleOnBackStarted(backEvent: BackEventCompat) {
|
||||
scope.launch {
|
||||
predictiveBackProgress.snapTo(PredictiveBack.transform(backEvent.progress))
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleOnBackProgressed(backEvent: BackEventCompat) {
|
||||
scope.launch {
|
||||
// Use snapTo for immediate feedback during the gesture
|
||||
predictiveBackProgress.snapTo(PredictiveBack.transform(backEvent.progress))
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
// Back gesture completed successfully, invoke dismiss
|
||||
onDismissRequest()
|
||||
}
|
||||
|
||||
override fun handleOnBackCancelled() {
|
||||
// Back gesture cancelled, animate back to 0
|
||||
scope.launch { predictiveBackProgress.animateTo(0f) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun View.isFlagSecureEnabled(): Boolean {
|
||||
val windowParams = rootView.layoutParams as? WindowManager.LayoutParams
|
||||
if (windowParams != null) {
|
||||
return windowParams.flags and WindowManager.LayoutParams.FLAG_SECURE != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** Determines if a color should be considered light or dark. */
|
||||
@Suppress("MagicNumber")
|
||||
internal fun Color.isDark(): Boolean {
|
||||
return this != Color.Transparent && luminance() <= 0.5
|
||||
}
|
||||
|
||||
private val PredictiveBackEasing: Easing = CubicBezierEasing(a = 0.1f, b = 0.1f, c = 0f, d = 1f)
|
||||
|
||||
internal object PredictiveBack {
|
||||
internal fun transform(progress: Float) = PredictiveBackEasing.transform(progress)
|
||||
}
|
||||
|
||||
// Taken from AndroidPopup.android.kt
|
||||
internal fun SecureFlagPolicy.shouldApplySecureFlag(isSecureFlagSetOnParent: Boolean): Boolean {
|
||||
return when (this) {
|
||||
SecureFlagPolicy.SecureOff -> false
|
||||
SecureFlagPolicy.SecureOn -> true
|
||||
SecureFlagPolicy.Inherit -> isSecureFlagSetOnParent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.tangem.core.ui.components.bottomsheets.copy.internal
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun ColumnScope.DragHandleWithTooltip(content: @Composable (() -> Unit)) {
|
||||
val dragHandleDescription = ""
|
||||
// We need outer box for alignment because TooltipBox's modifier is only applied to its anchor.
|
||||
Box(Modifier.align(CenterHorizontally)) {
|
||||
TooltipBox(
|
||||
positionProvider =
|
||||
TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
|
||||
tooltip = { PlainTooltip { Text(dragHandleDescription) } },
|
||||
state = rememberTooltipState(),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// VERSION: v0_14_0
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
package com.tangem.core.ui.components.bottomsheets.copy.internal
|
||||
internal object StandardMotionTokens {
|
||||
const val SpringDefaultEffectsDamping = 1.0f
|
||||
const val SpringDefaultEffectsStiffness = 1600.0f
|
||||
}
|
||||
|
|
@ -11,8 +11,13 @@ 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.bottomsheets.copy.ModalBottomSheet
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetState
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue
|
||||
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
|
||||
import com.tangem.core.ui.components.snackbar.TangemTopSnackbarHost
|
||||
import com.tangem.core.ui.components.bottomsheets.copy.internal.ModalBottomSheetProperties
|
||||
import com.tangem.core.ui.res.LocalHazeState
|
||||
import com.tangem.core.ui.res.LocalTopSnackbarHostState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -22,11 +27,13 @@ import kotlinx.coroutines.launch
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Suppress("LongParameterList", "LongMethod", "ComposableParametersOrdering")
|
||||
fun InternalBottomSheet(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onBack: (() -> Unit)? = null,
|
||||
sheetState: SheetState = rememberModalBottomSheetState(),
|
||||
sheetState: TangemSheetState = rememberSheetState(),
|
||||
peekHeightDp: Dp,
|
||||
sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
|
||||
shape: Shape = BottomSheetDefaults.ExpandedShape,
|
||||
containerColor: Color = BottomSheetDefaults.ContainerColor,
|
||||
|
|
@ -54,6 +61,7 @@ fun InternalBottomSheet(
|
|||
properties = ModalBottomSheetProperties(
|
||||
shouldDismissOnBackPress = onBack == null,
|
||||
),
|
||||
peekHeightDp = peekHeightDp,
|
||||
content = {
|
||||
Box {
|
||||
val hazeState = rememberHazeState()
|
||||
|
|
@ -73,7 +81,7 @@ fun InternalBottomSheet(
|
|||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = onBack != null && sheetState.targetValue != SheetValue.Hidden) {
|
||||
BackHandler(enabled = onBack != null && sheetState.targetValue != TangemSheetValue.Hidden) {
|
||||
onBack?.invoke()
|
||||
}
|
||||
},
|
||||
|
|
@ -81,7 +89,7 @@ fun InternalBottomSheet(
|
|||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
suspend fun SheetState.collapse(onCollapsed: () -> Unit) {
|
||||
suspend fun TangemSheetState.collapse(onCollapsed: () -> Unit) {
|
||||
coroutineScope {
|
||||
launch { hide() }.invokeOnCompletion { onCollapsed() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,21 +3,30 @@ package com.tangem.core.ui.components.bottomsheets.internal
|
|||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import com.tangem.core.ui.components.bottomsheets.copy.internal.ModalBottomSheetProperties
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
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.bottomsheets.copy.ModalBottomSheet
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetState
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue
|
||||
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@Suppress("LongParameterList", "LongMethod", "ComposableParametersOrdering")
|
||||
fun ModalBottomSheetWithBackHandling(
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onBack: (() -> Unit)? = null,
|
||||
sheetState: SheetState = rememberModalBottomSheetState(),
|
||||
sheetState: TangemSheetState = rememberSheetState(),
|
||||
peekHeightDp: Dp,
|
||||
sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
|
||||
shape: Shape = BottomSheetDefaults.ExpandedShape,
|
||||
containerColor: Color = BottomSheetDefaults.ContainerColor,
|
||||
|
|
@ -40,12 +49,13 @@ fun ModalBottomSheetWithBackHandling(
|
|||
scrimColor = scrimColor,
|
||||
dragHandle = dragHandle,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
peekHeightDp = peekHeightDp,
|
||||
properties = ModalBottomSheetProperties(
|
||||
shouldDismissOnBackPress = onBack == null,
|
||||
),
|
||||
content = {
|
||||
content()
|
||||
BackHandler(enabled = onBack != null && sheetState.targetValue != SheetValue.Hidden) {
|
||||
BackHandler(enabled = onBack != null && sheetState.targetValue != TangemSheetValue.Hidden) {
|
||||
onBack?.invoke()
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.SheetValue.Expanded
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -23,6 +24,7 @@ import androidx.compose.ui.platform.LocalConfiguration
|
|||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
|
|
@ -34,10 +36,10 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.internal.ModalBottomSheetWithBackHandling
|
||||
import com.tangem.core.ui.components.bottomsheets.internal.collapse
|
||||
import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible
|
||||
import com.tangem.core.ui.res.LocalCanScrollBackward
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetState
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue
|
||||
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
|
||||
import com.tangem.core.ui.res.*
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
|
||||
const val MODAL_SHEET_MAX_HEIGHT = 0.8f
|
||||
|
|
@ -98,23 +100,26 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheet(
|
|||
crossinline content: @Composable (ColumnScope.(T) -> Unit),
|
||||
) {
|
||||
var isVisible by remember { mutableStateOf(value = config.isShown) }
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
val sheetState = rememberSheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
confirmValueChange = { sheetValue ->
|
||||
if (!dismissOnClickOutside) {
|
||||
// Ignore transitions to hidden (prevents dismiss on outside click/back press)
|
||||
sheetValue != SheetValue.Hidden
|
||||
sheetValue != TangemSheetValue.Hidden
|
||||
} else {
|
||||
true
|
||||
}
|
||||
},
|
||||
)
|
||||
val windowSize = LocalWindowSize.current
|
||||
val maxHeight = windowSize.height * MODAL_SHEET_MAX_HEIGHT
|
||||
|
||||
if (isVisible && config.content is T) {
|
||||
BasicModalBottomSheet<T>(
|
||||
config = config,
|
||||
sheetState = sheetState,
|
||||
onBack = onBack,
|
||||
peekHeightDp = maxHeight,
|
||||
bsContent = {
|
||||
BsContent(
|
||||
config = config,
|
||||
|
|
@ -146,15 +151,16 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheet(
|
|||
crossinline title: @Composable (BoxScope.(T) -> Unit),
|
||||
crossinline content: @Composable (ColumnScope.(T) -> Unit),
|
||||
) {
|
||||
val windowSize = LocalWindowSize.current
|
||||
val maxHeight = windowSize.height * MODAL_SHEET_MAX_HEIGHT
|
||||
BasicModalBottomSheet<T>(
|
||||
config = config,
|
||||
sheetState = SheetState(
|
||||
sheetState = rememberSheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
initialValue = Expanded,
|
||||
positionalThreshold = { 0f },
|
||||
velocityThreshold = { 0f },
|
||||
initialValue = TangemSheetValue.Expanded,
|
||||
),
|
||||
onBack = null,
|
||||
peekHeightDp = maxHeight,
|
||||
bsContent = {
|
||||
BsContent(
|
||||
config = config,
|
||||
|
|
@ -221,7 +227,8 @@ inline fun <reified T : TangemBottomSheetConfigContent> BsContent(
|
|||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
sheetState: SheetState,
|
||||
sheetState: TangemSheetState,
|
||||
peekHeightDp: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
noinline bsContent: @Composable ColumnScope.() -> Unit,
|
||||
|
|
@ -236,6 +243,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheet(
|
|||
onBack = onBack,
|
||||
dragHandle = null,
|
||||
content = bsContent,
|
||||
peekHeightDp = peekHeightDp,
|
||||
scrimColor = TangemTheme.colors.overlay.secondary,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.SheetValue.Expanded
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -17,7 +18,6 @@ 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.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -28,7 +28,11 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.internal.ModalBottomSheetWithBackHandling
|
||||
import com.tangem.core.ui.components.bottomsheets.internal.collapse
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetState
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue
|
||||
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
|
||||
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.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
|
|
@ -89,14 +93,14 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheetW
|
|||
var isVisible by remember { mutableStateOf(value = config.isShown) }
|
||||
|
||||
val sheetState = if (config.dismissOnClickOutside == null) {
|
||||
rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
|
||||
rememberSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
|
||||
} else {
|
||||
rememberModalBottomSheetState(
|
||||
rememberSheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
confirmValueChange = { sheetValue ->
|
||||
if (config.dismissOnClickOutside().not()) {
|
||||
// Ignore transitions to hidden (prevents dismiss on outside click/back press)
|
||||
sheetValue != SheetValue.Hidden
|
||||
sheetValue != TangemSheetValue.Hidden
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
|
@ -137,11 +141,9 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheetW
|
|||
) {
|
||||
BasicModalBottomSheetWithFooter<T>(
|
||||
config = config,
|
||||
sheetState = SheetState(
|
||||
sheetState = rememberSheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
initialValue = Expanded,
|
||||
positionalThreshold = { 0f },
|
||||
velocityThreshold = { 0f },
|
||||
initialValue = TangemSheetValue.Expanded,
|
||||
),
|
||||
onBack = null,
|
||||
containerColor = containerColor,
|
||||
|
|
@ -156,7 +158,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheetW
|
|||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWithFooter(
|
||||
config: TangemBottomSheetConfig,
|
||||
sheetState: SheetState,
|
||||
sheetState: TangemSheetState,
|
||||
containerColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
noinline onBack: (() -> Unit)? = null,
|
||||
|
|
@ -166,9 +168,11 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
|
|||
) {
|
||||
val model = config.content as? T ?: return
|
||||
|
||||
val windowSize = LocalWindowSize.current
|
||||
val maxHeight = windowSize.height * MODAL_SHEET_MAX_HEIGHT
|
||||
|
||||
val bsContent: @Composable ColumnScope.() -> Unit = {
|
||||
// FIXME: Use LocalWindowSize.current
|
||||
val maxHeight = LocalConfiguration.current.screenHeightDp * MODAL_SHEET_MAX_HEIGHT
|
||||
val initial = 0
|
||||
val scrollState = rememberScrollState(initial = initial)
|
||||
|
||||
|
|
@ -198,7 +202,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
|
|||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
.clip(TangemTheme.shapes.roundedCornersLarge)
|
||||
.background(containerColor)
|
||||
.heightIn(max = maxHeight.dp)
|
||||
.heightIn(max = maxHeight)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
|
|
@ -259,6 +263,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
|
|||
dragHandle = null,
|
||||
content = bsContent,
|
||||
scrimColor = TangemTheme.colors.overlay.secondary,
|
||||
peekHeightDp = maxHeight,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,20 @@ package com.tangem.core.ui.components.bottomsheets.sheet
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
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 androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.internal.ModalBottomSheetWithBackHandling
|
||||
import com.tangem.core.ui.components.bottomsheets.internal.collapse
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetState
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue
|
||||
import com.tangem.core.ui.components.sheetscaffold.rememberSheetState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -94,7 +95,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultBottomSheet(
|
|||
crossinline content: @Composable (ColumnScope.(T) -> Unit),
|
||||
) {
|
||||
var isVisible by remember { mutableStateOf(value = config.isShown) }
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
|
||||
val sheetState = rememberSheetState(skipPartiallyExpanded = skipPartiallyExpanded)
|
||||
|
||||
if (isVisible && config.content is T) {
|
||||
BasicBottomSheet<T>(
|
||||
|
|
@ -130,11 +131,9 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewBottomSheet(
|
|||
BasicBottomSheet<T>(
|
||||
modifier = Modifier.width(360.dp),
|
||||
config = config,
|
||||
sheetState = SheetState(
|
||||
sheetState = rememberSheetState(
|
||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||
initialValue = Expanded,
|
||||
positionalThreshold = { 0f },
|
||||
velocityThreshold = { 0f },
|
||||
initialValue = TangemSheetValue.Expanded,
|
||||
),
|
||||
onBack = null,
|
||||
containerColor = containerColor,
|
||||
|
|
@ -149,7 +148,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewBottomSheet(
|
|||
@Composable
|
||||
inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
||||
config: TangemBottomSheetConfig,
|
||||
sheetState: SheetState,
|
||||
sheetState: TangemSheetState,
|
||||
containerColor: Color,
|
||||
addBottomInsets: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -192,5 +191,6 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicBottomSheet(
|
|||
onBack = onBack,
|
||||
content = bsContent,
|
||||
scrimColor = TangemTheme.colors.overlay.secondary,
|
||||
peekHeightDp = Dp.Unspecified,
|
||||
)
|
||||
}
|
||||
|
|
@ -253,7 +253,7 @@ private fun BottomSheetScaffoldLayout(
|
|||
}
|
||||
}
|
||||
|
||||
private fun Modifier.bottomSheetDraggableAnchor(
|
||||
internal fun Modifier.bottomSheetDraggableAnchor(
|
||||
state: TangemSheetState,
|
||||
orientation: Orientation,
|
||||
peekHeightPx: Float,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
package com.tangem.core.ui.components.sheetscaffold
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.animation.core.AnimationSpec
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.exponentialDecay
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.*
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -16,6 +19,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetState.Companion.Saver
|
||||
import com.tangem.core.ui.components.sheetscaffold.TangemSheetValue.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
|
|
@ -302,7 +306,7 @@ internal fun consumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
|
|||
}
|
||||
|
||||
@Composable
|
||||
internal fun rememberSheetState(
|
||||
fun rememberSheetState(
|
||||
skipPartiallyExpanded: Boolean = false,
|
||||
confirmValueChange: (TangemSheetValue) -> Boolean = { true },
|
||||
initialValue: TangemSheetValue = Hidden,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue