Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-17 12:33:12 +03:00
parent 479b1ea1c8
commit 095587921d
4 changed files with 165 additions and 38 deletions

View file

@ -2,30 +2,57 @@
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.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
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.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.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.*
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.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
/**
@ -51,13 +78,6 @@ import kotlin.math.roundToInt
* [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 sheetContentColor the preferred content color provided by the bottom sheet to its
* children. Defaults to the matching content color for [sheetContainerColor], or if that is not a
* color from the theme, this will keep the same content color set above the bottom sheet.
* @param sheetTonalElevation when [sheetContainerColor] 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 sheetShadowElevation the shadow elevation 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]
@ -81,10 +101,7 @@ fun TangemBottomSheetScaffold(
sheetPeekHeight: Dp,
sheetMaxWidth: Dp = 640.dp,
sheetShape: Shape = TangemTheme.shapes.bottomSheetLarge,
sheetContainerColor: Color = Color.White, // FIXME
sheetContentColor: Color = contentColorFor(sheetContainerColor),
sheetTonalElevation: Dp = 0.dp,
sheetShadowElevation: Dp = 1.dp,
sheetContainerColor: Color = Color.White,
sheetSwipeEnabled: Boolean = true,
topBar: @Composable (() -> Unit)? = null,
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
@ -109,9 +126,6 @@ fun TangemBottomSheetScaffold(
sheetSwipeEnabled = sheetSwipeEnabled,
shape = sheetShape,
containerColor = sheetContainerColor,
contentColor = sheetContentColor,
tonalElevation = sheetTonalElevation,
shadowElevation = sheetShadowElevation,
content = sheetContent,
)
},
@ -178,9 +192,6 @@ private fun StandardBottomSheet(
sheetSwipeEnabled: Boolean,
shape: Shape,
containerColor: Color,
contentColor: Color,
tonalElevation: Dp,
shadowElevation: Dp,
content: @Composable ColumnScope.() -> Unit,
) {
val scope = rememberCoroutineScope()
@ -202,7 +213,7 @@ private fun StandardBottomSheet(
Modifier
}
Surface(
Column(
modifier = Modifier
.widthIn(max = sheetMaxWidth)
.fillMaxWidth()
@ -251,16 +262,20 @@ private fun StandardBottomSheet(
state = state.anchoredDraggableState,
orientation = orientation,
enabled = sheetSwipeEnabled,
),
shape = shape,
color = containerColor,
contentColor = contentColor,
tonalElevation = tonalElevation,
shadowElevation = shadowElevation,
)
.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),
) {
Column(Modifier.fillMaxWidth()) {
content()
}
content()
}
}
@ -293,7 +308,7 @@ private fun BottomSheetScaffoldLayout(
),
) {
(topBarMeasurables, bodyMeasurables, bottomSheetMeasurables, snackbarHostMeasurables),
constraints,
constraints,
->
val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight
@ -321,7 +336,7 @@ private fun BottomSheetScaffoldLayout(
PartiallyExpanded -> sheetOffset().roundToInt() - snackbarHeight
Expanded,
Hidden,
-> layoutHeight - snackbarHeight
-> layoutHeight - snackbarHeight
}
// Placement order is important for elevation

View file

@ -0,0 +1,95 @@
package com.tangem.core.ui.extensions
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
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.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
fun Modifier.softLayerShadow(
radius: Dp = 8.dp,
color: Color = Color.Black.copy(alpha = .23f),
shape: Shape = RectangleShape,
spread: Dp = 0.dp,
offset: DpOffset = DpOffset(x = 0.dp, y = 2.dp),
isAlphaContentClip: Boolean = false,
): Modifier = this.drawWithCache {
val radiusPx = radius.toPx()
require(radiusPx > 0.0F)
val paint = Paint().apply {
this.color = color
asFrameworkPaint().apply {
isDither = true
isAntiAlias = true
setShadowLayer(
radiusPx,
offset.x.toPx(),
offset.y.toPx(),
color.toArgb(),
)
}
}
val shapeOutline = shape.createOutline(
size = size,
layoutDirection = LayoutDirection.Rtl,
density = this,
)
val shapePath = Path().apply {
addOutline(outline = shapeOutline)
}
val drawShadowBlock: DrawScope.() -> Unit = {
drawIntoCanvas { canvas ->
canvas.withSave {
if (spread.value != 0.0F) {
canvas.scale(
sx = spreadScale(
spread = spread.toPx(),
size = size.width,
),
sy = spreadScale(
spread = spread.toPx(),
size = size.height,
),
pivotX = center.x,
pivotY = center.y,
)
}
canvas.drawOutline(
outline = shapeOutline,
paint = paint,
)
}
}
}
onDrawBehind {
if (isAlphaContentClip) {
clipShadowByPath(
path = shapePath,
block = drawShadowBlock,
)
} else {
drawShadowBlock()
}
}
}
@Suppress("UnnecessaryParentheses")
private fun spreadScale(spread: Float, size: Float): Float = 1.0F + ((spread / size) * 2.0F)
private fun DrawScope.clipShadowByPath(path: Path, block: DrawScope.() -> Unit) {
clipPath(
path = path,
clipOp = ClipOp.Difference,
block = block,
)
}

View file

@ -8,12 +8,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SmallButtonShimmer
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.block.information.InformationBlock
@ -76,10 +78,26 @@ private fun Title() {
private fun AddButton(state: AddButtonState, onClick: () -> Unit) {
when (state) {
AddButtonState.Loading -> {
SmallButtonShimmer(
modifier = Modifier.size(width = TangemTheme.dimens.size63, height = TangemTheme.dimens.size18),
shape = RoundedCornerShape(TangemTheme.dimens.radius3),
)
Box {
SmallButtonShimmer(
modifier = Modifier.width(width = TangemTheme.dimens.size63),
shape = RoundedCornerShape(TangemTheme.dimens.radius3),
withIcon = true,
)
Box(
Modifier
.matchParentSize()
.background(TangemTheme.colors.background.action),
)
RectangleShimmer(
modifier = Modifier
.align(Alignment.Center)
.size(width = TangemTheme.dimens.size63, height = TangemTheme.dimens.size18),
radius = TangemTheme.dimens.radius3,
)
}
}
AddButtonState.Available,
AddButtonState.Unavailable,

View file

@ -391,7 +391,6 @@ private inline fun BaseScaffoldWithMarkets(
sheetContainerColor = backgroundColor.value,
scaffoldState = scaffoldState,
sheetPeekHeight = peekHeight,
sheetShadowElevation = 8.dp,
sheetShape = TangemTheme.shapes.bottomSheetLarge,
sheetContent = {
// hide bottom sheet when back pressed