Skip to content

Commit

Permalink
[Paywalls V2] Adds support for shadows (#1952)
Browse files Browse the repository at this point in the history
  • Loading branch information
JayShortway authored Dec 3, 2024
1 parent 8efc7f7 commit 38b605e
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.revenuecat.purchases.paywalls.components.properties

import com.revenuecat.purchases.InternalRevenueCatAPI
import dev.drewhamilton.poko.Poko
import kotlinx.serialization.Serializable

@InternalRevenueCatAPI
@Poko
@Serializable
internal data class Shadow(
class Shadow(
@get:JvmSynthetic val color: ColorScheme,
@get:JvmSynthetic val radius: Double,
@get:JvmSynthetic val x: Double,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
@file:JvmSynthetic

package com.revenuecat.purchases.ui.revenuecatui.components.composable

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.BlurredEdgeTreatment
import androidx.compose.ui.draw.blur
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.revenuecat.purchases.paywalls.components.properties.ColorInfo
import com.revenuecat.purchases.paywalls.components.properties.ColorScheme
import com.revenuecat.purchases.paywalls.components.properties.Shadow
import com.revenuecat.purchases.ui.revenuecatui.components.modifier.background
import com.revenuecat.purchases.ui.revenuecatui.components.properties.ShadowStyle
import com.revenuecat.purchases.ui.revenuecatui.components.properties.toShadowStyle

/**
* Adds a [shadow] in the provided [shape] to the [content].
*
* @param margin If your [content] has a margin, provide that here. Otherwise the transparent pixels of the margin will
* participate in the shadow.
*/
@Composable
internal fun WithShadow(
shadow: ShadowStyle,
shape: Shape,
margin: PaddingValues = PaddingValues(all = 0.dp),
content: @Composable () -> Unit,
) {
// We cannot use the built-in .shadow() modifier, as it takes very different parameters from what we have in our
// ShadowStyle. WithShadow also can't be a modifier itself, because the modifiers we use to draw the shadow
// (offset, blur and background) cannot be part of the same modifier chain as those applied to the content. If they
// are, they will be combined in ways we don't want.
// We might be able to achieve a similar result using a .drawBehind() modifier, but we would need to figure out how
// to draw gradients on a canvas.
Layout(
content = {
// Content
content()

// Shadow
Box(
Modifier
.offset { IntOffset(x = shadow.x.roundToPx(), y = shadow.y.roundToPx()) }
.blur(shadow.radius, edgeTreatment = BlurredEdgeTreatment.Unbounded)
.background(shadow.color, shape),
)
},
) { measurables, constraints ->
val contentMeasurable = measurables[0]
val shadowMeasurable = measurables[1]

val startMarginPx = margin.calculateStartPadding(layoutDirection).roundToPx()
val topMarginPx = margin.calculateTopPadding().roundToPx()
val endMarginPx = margin.calculateEndPadding(layoutDirection).roundToPx()
val bottomMarginPx = margin.calculateBottomPadding().roundToPx()

// We measure the content first, then make the shadow exactly as big, minus any margins.
val contentPlaceable = contentMeasurable.measure(constraints)
val shadowPlaceable = shadowMeasurable.measure(
constraints = Constraints(
minWidth = contentPlaceable.measuredWidth - startMarginPx - endMarginPx,
maxWidth = contentPlaceable.measuredWidth - startMarginPx - endMarginPx,
minHeight = contentPlaceable.measuredHeight - topMarginPx - bottomMarginPx,
maxHeight = contentPlaceable.measuredHeight - topMarginPx - bottomMarginPx,
),
)

layout(width = contentPlaceable.measuredWidth, height = contentPlaceable.measuredHeight) {
// We place the shadow and the content on top of each other.
shadowPlaceable.placeRelative(x = startMarginPx, y = topMarginPx)
contentPlaceable.placeRelative(x = 0, y = 0)
}
}
}

@Suppress("MagicNumber")
@Preview("Circle")
@Composable
private fun Shadow_Preview_Circle() {
val shape = CircleShape
Box(
modifier = Modifier
.requiredSize(200.dp),
contentAlignment = Alignment.Center,
) {
WithShadow(
shadow = Shadow(
color = ColorScheme(
light = ColorInfo.Hex(Color.Black.toArgb()),
),
x = 5.0,
y = 5.0,
radius = 0.0,
).toShadowStyle(),
shape = shape,
) {
Box(
modifier = Modifier
.requiredSize(100.dp)
.background(Color.Red, shape = shape),
)
}
}
}

@Suppress("MagicNumber")
@Preview("Square")
@Composable
private fun Shadow_Preview_Square() {
val shape = RectangleShape
Box(
modifier = Modifier
.requiredSize(200.dp),
contentAlignment = Alignment.Center,
) {
WithShadow(
shadow = Shadow(
color = ColorScheme(
light = ColorInfo.Hex(Color.Black.toArgb()),
),
x = 10.0,
y = 5.0,
radius = 20.0,
).toShadowStyle(),
shape = shape,
) {
Box(
modifier = Modifier
.requiredSize(100.dp)
.background(Color.Red, shape = shape),
)
}
}
}

@Suppress("MagicNumber")
@Preview("Gradient+CustomShape")
@Composable
private fun Shadow_Preview_Gradient_CustomShape() {
val shape = RoundedCornerShape(50)
Box(
modifier = Modifier
.requiredSize(200.dp),
contentAlignment = Alignment.Center,
) {
WithShadow(
shadow = Shadow(
color = ColorScheme(
light = ColorInfo.Gradient.Linear(
degrees = 0f,
points = listOf(
ColorInfo.Gradient.Point(
color = Color.Red.toArgb(),
percent = 0.1f,
),
ColorInfo.Gradient.Point(
color = Color.Green.toArgb(),
percent = 0.5f,
),
ColorInfo.Gradient.Point(
color = Color.Blue.toArgb(),
percent = 0.9f,
),
),
),
),
x = 0.0,
y = 5.0,
radius = 10.0,
).toShadowStyle(),
shape = shape,
) {
Text(
text = "GET UNLIMITED RGB",
modifier = Modifier
.background(Color.Black, shape = shape)
.padding(horizontal = 24.dp, vertical = 16.dp),
color = Color.White,
style = MaterialTheme.typography.titleSmall,
)
}
}
}

@Suppress("MagicNumber")
@Preview("Margin")
@Composable
private fun Shadow_Preview_Margin() {
val margin = PaddingValues(start = 8.dp, top = 16.dp, end = 4.dp, bottom = 24.dp)
val shape = RectangleShape
Column(
modifier = Modifier
.requiredSize(width = 100.dp, height = 200.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
WithShadow(
shadow = Shadow(
color = ColorScheme(
light = ColorInfo.Hex(Color.Black.toArgb()),
),
x = 0.0,
y = 5.0,
radius = 20.0,
).toShadowStyle(),
shape = shape,
margin = margin,
) {
Box(
modifier = Modifier
.padding(margin)
.requiredSize(width = 50.dp, height = 50.dp)
.background(Color.Red, shape)
.border(width = 2.dp, Color.Blue, shape)
.padding(all = 16.dp),
)
}

WithShadow(
shadow = Shadow(
color = ColorScheme(
light = ColorInfo.Hex(Color.Black.toArgb()),
),
x = 0.0,
y = 5.0,
radius = 20.0,
).toShadowStyle(),
shape = shape,
margin = margin,
) {
Box(
modifier = Modifier
.padding(margin)
.requiredSize(width = 50.dp, height = 50.dp)
.background(Color.Red, shape)
.border(width = 2.dp, Color.Blue, shape)
.padding(all = 16.dp),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import com.revenuecat.purchases.ui.revenuecatui.components.ColorStyle
import com.revenuecat.purchases.ui.revenuecatui.components.properties.ColorStyle

@JvmSynthetic
@Stable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.revenuecat.purchases.ui.revenuecatui.components
package com.revenuecat.purchases.ui.revenuecatui.components.properties

import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
Expand Down Expand Up @@ -26,6 +26,9 @@ import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin

/**
* Ready to use color properties for the current theme.
*/
internal sealed interface ColorStyle {

@JvmInline
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.revenuecat.purchases.ui.revenuecatui.components.properties

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.revenuecat.purchases.paywalls.components.properties.Shadow

/**
* Ready to use shadow properties for the current theme.
*/
@Immutable
internal data class ShadowStyle(
@get:JvmSynthetic val color: ColorStyle,
@get:JvmSynthetic val radius: Dp,
@get:JvmSynthetic val x: Dp,
@get:JvmSynthetic val y: Dp,
)

@JvmSynthetic
@Composable
internal fun Shadow.toShadowStyle(): ShadowStyle =
ShadowStyle(
color = color.toColorStyle(),
radius = radius.dp,
x = x.dp,
y = y.dp,
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import com.revenuecat.purchases.paywalls.components.properties.FontSize
import com.revenuecat.purchases.paywalls.components.properties.HorizontalAlignment
import com.revenuecat.purchases.paywalls.components.properties.Padding
import com.revenuecat.purchases.paywalls.components.properties.Size
import com.revenuecat.purchases.ui.revenuecatui.components.ColorStyle
import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toAlignment
import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toFontWeight
import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toPaddingValues
import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextAlign
import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextUnit
import com.revenuecat.purchases.ui.revenuecatui.components.toColorStyle
import com.revenuecat.purchases.ui.revenuecatui.components.properties.ColorStyle
import com.revenuecat.purchases.ui.revenuecatui.components.properties.toColorStyle
import com.revenuecat.purchases.paywalls.components.properties.FontWeight as RcFontWeight

@Suppress("LongParameterList")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

package com.revenuecat.purchases.ui.revenuecatui.components.text

import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.LocalTextStyle
Expand All @@ -23,9 +22,9 @@ import com.revenuecat.purchases.paywalls.components.properties.Size
import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint
import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint.Fill
import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint.Fit
import com.revenuecat.purchases.ui.revenuecatui.components.ColorStyle
import com.revenuecat.purchases.ui.revenuecatui.components.modifier.background
import com.revenuecat.purchases.ui.revenuecatui.components.modifier.size
import com.revenuecat.purchases.ui.revenuecatui.components.properties.ColorStyle
import com.revenuecat.purchases.ui.revenuecatui.composables.Markdown
import com.revenuecat.purchases.ui.revenuecatui.extensions.conditional

Expand Down

0 comments on commit 38b605e

Please sign in to comment.