diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemBottomSheetScaffold.kt b/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemBottomSheetScaffold.kt index aa68f71190..ccab322580 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemBottomSheetScaffold.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemBottomSheetScaffold.kt @@ -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 diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/Shadow.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/Shadow.kt new file mode 100644 index 0000000000..bc1ce31389 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/Shadow.kt @@ -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, + ) +} \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt index daaa385754..abd9b2ccdc 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt @@ -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, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index f9f733cd80..1d289a12df 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -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