Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import kotlin.math.roundToInt
import kotlin.math.sin

enum class DrawType { TOP, BOTTOM, START, END, ALL }
enum class WavyAlign { INNER, OUTER }

fun Modifier.wavyStroke(
color: Color,
Expand All @@ -35,6 +36,7 @@ fun Modifier.wavyStroke(
fillColor: Color? = null,
contentPadding: Dp = 0.dp,
clipContent: Boolean = false,
wavyAlign: WavyAlign = WavyAlign.INNER,
): Modifier = this.then(
Modifier
.drawWithCache {
Expand All @@ -43,17 +45,22 @@ fun Modifier.wavyStroke(
val spacingPx = spacing.toPx().coerceAtLeast(1f)

val pathInset = ampPx + strokePx / 2f
val signedInset = if (wavyAlign == WavyAlign.INNER) pathInset else -pathInset

val rect = Rect(
left = pathInset,
top = pathInset,
right = size.width - pathInset,
bottom = size.height - pathInset,
left = signedInset,
top = signedInset,
right = size.width - signedInset,
bottom = size.height - signedInset,
)

val radius = (cornerRadius.toPx() - pathInset)
.coerceAtLeast(0f)
.coerceAtMost(min(rect.width, rect.height) / 2f)
val radius = if (wavyAlign == WavyAlign.INNER) {
(cornerRadius.toPx() - pathInset)
.coerceAtLeast(0f)
.coerceAtMost(min(rect.width, rect.height) / 2f)
} else {
cornerRadius.toPx() + pathInset // λ°”κΉ₯ μ‚¬κ°ν˜•μ΄ μ»€μ§€λ―€λ‘œ λͺ¨μ„œλ¦¬λ„ 킀움
}

val path = when (drawType) {
DrawType.TOP -> makeTopWavyPath(
Expand Down Expand Up @@ -97,110 +104,52 @@ fun Modifier.wavyStroke(
}
}

onDrawWithContent {
fillColor?.let {
drawPath(path = path, color = it)
}

if (clipContent) {
clipPath(path) {
this@onDrawWithContent.drawContent()
val fillPath = if (drawType == DrawType.ALL) {
path // ALL은 이미 λ‹«νžŒ Path 이닀
} else {
Path().apply {
addPath(path)
when (drawType) {
DrawType.TOP -> {
lineTo(size.width, size.height)
lineTo(0f, size.height)
lineTo(0f, 0f)
}
DrawType.BOTTOM -> {
lineTo(size.width, 0f)
lineTo(0f, 0f)
lineTo(0f, size.height)
}
DrawType.START -> {
lineTo(size.width, size.height)
lineTo(size.width, 0f)
lineTo(0f, 0f)
}
DrawType.END -> {
lineTo(0f, size.height)
lineTo(0f, 0f)
lineTo(size.width, 0f)
}
}
} else {
drawContent()
close()
}

drawPath(
path = path,
color = color,
style = Stroke(
width = strokePx,
cap = StrokeCap.Round,
join = StrokeJoin.Round,
),
)
}
}
.padding(contentPadding)
)

fun Modifier.wavyBackground(
color: Color,
drawType: DrawType = DrawType.ALL,
strokeWidth: Dp = 3.dp,
cornerRadius: Dp = 12.dp,
amplitude: Dp = 2.dp,
spacing: Dp = 6.dp,
seed: Long = 0xC0FFEE_BABEL,
fillColor: Color = color,
contentPadding: Dp = 0.dp,
clipContent: Boolean = false,
): Modifier = this.then(
Modifier
.drawWithCache {
val strokePx = strokeWidth.toPx()
val ampPx = amplitude.toPx()
val spacingPx = spacing.toPx().coerceAtLeast(1f)

val pathInset = ampPx + strokePx / 2f

val rect = Rect(
left = pathInset,
top = pathInset,
right = size.width - pathInset,
bottom = size.height - pathInset,
)

val radius = (cornerRadius.toPx() - pathInset)
.coerceAtLeast(0f)
.coerceAtMost(min(rect.width, rect.height) / 2f)

val paths = when (drawType) {
DrawType.ALL -> {
val path = makeWavyPath(
rect = rect,
cornerRadius = radius,
spacing = spacingPx,
amplitude = ampPx,
seed = seed,
)

WavyBackgroundPath(
fillPath = path,
strokePath = path,
)
}

DrawType.TOP,
DrawType.BOTTOM,
DrawType.START,
DrawType.END -> makeEdgeWavyBackgroundPath(
width = size.width,
height = size.height,
edge = drawType,
spacing = spacingPx,
amplitude = ampPx,
seed = seed,
strokeWidth = strokePx,
)
}

onDrawWithContent {
drawPath(
path = paths.fillPath,
color = fillColor,
)
fillColor?.let {
drawPath(path = fillPath, color = it)
}

if (clipContent) {
clipPath(paths.fillPath) {
clipPath(fillPath) {
this@onDrawWithContent.drawContent()
}
} else {
drawContent()
}

drawPath(
path = paths.strokePath,
path = path,
color = color,
style = Stroke(
width = strokePx,
Expand All @@ -213,9 +162,8 @@ fun Modifier.wavyBackground(
.padding(contentPadding)
)

private data class WavyData(val point: Offset, val normal: Offset)

private data class WavyBackgroundPath(val fillPath: Path, val strokePath: Path)
private data class WavyData(val point: Offset, val normal: Offset)

private fun makeWavyPath(
rect: Rect,
Expand Down Expand Up @@ -419,127 +367,4 @@ private fun makeSingleAxisWavyPath(
}

private fun midpoint(a: Offset, b: Offset): Offset =
Offset((a.x + b.x) / 2f, (a.y + b.y) / 2f)

private fun makeEdgeWavyBackgroundPath(
width: Float,
height: Float,
edge: DrawType,
spacing: Float,
amplitude: Float,
seed: Long,
strokeWidth: Float,
): WavyBackgroundPath {
val inset = amplitude + strokeWidth / 2f

val count = when (edge) {
DrawType.TOP,
DrawType.BOTTOM -> max(1, (width / spacing).toInt())

DrawType.START,
DrawType.END -> max(1, (height / spacing).toInt())

DrawType.ALL -> 1
}

var nextSeed = seed

val points = buildList {
repeat(count + 1) { i ->
val t = i / count.toFloat()

nextSeed = nextSeed * 6364136223846793005L + 1442695040888963407L
val random = ((nextSeed ushr 1) % 2000L) / 1000f - 1f
val offset = random * amplitude

add(
when (edge) {
DrawType.TOP -> Offset(
x = width * t,
y = inset + offset,
)

DrawType.BOTTOM -> Offset(
x = width * t,
y = height - inset + offset,
)

DrawType.START -> Offset(
x = inset + offset,
y = height * t,
)

DrawType.END -> Offset(
x = width - inset + offset,
y = height * t,
)

DrawType.ALL -> Offset.Zero
}
)
}
}

val strokePath = makeOpenSmoothPath(points)

val fillPath = Path().apply {
val first = points.first()
moveTo(first.x, first.y)

points.zipWithNext { current, next ->
val mid = midpoint(current, next)
quadraticTo(current.x, current.y, mid.x, mid.y)
}

val last = points.last()
lineTo(last.x, last.y)

when (edge) {
DrawType.TOP -> {
lineTo(width, height)
lineTo(0f, height)
}

DrawType.BOTTOM -> {
lineTo(width, 0f)
lineTo(0f, 0f)
}

DrawType.START -> {
lineTo(width, height)
lineTo(width, 0f)
}

DrawType.END -> {
lineTo(0f, height)
lineTo(0f, 0f)
}

DrawType.ALL -> Unit
}

close()
}

return WavyBackgroundPath(
fillPath = fillPath,
strokePath = strokePath,
)
}

private fun makeOpenSmoothPath(points: List<Offset>): Path {
return Path().apply {
if (points.isEmpty()) return@apply

val first = points.first()
moveTo(first.x, first.y)

points.zipWithNext { current, next ->
val mid = midpoint(current, next)
quadraticTo(current.x, current.y, mid.x, mid.y)
}

val last = points.last()
lineTo(last.x, last.y)
}
}
Offset((a.x + b.x) / 2f, (a.y + b.y) / 2f)
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import com.idiotfrogs.designsystem.util.keyboardAutoScroll
import com.idiotfrogs.designsystem.util.noRippleClickable
import com.idiotfrogs.designsystem.util.rememberKeyboardVisibility
import com.idiotfrogs.designsystem.util.rememberPickerState
import com.idiotfrogs.designsystem.util.wavyBackground
import com.idiotfrogs.designsystem.util.wavyStroke
import com.idiotfrogs.extension.toFile
import com.idiotfrogs.navigation.LocalComposeMSNavigator
Expand Down Expand Up @@ -192,10 +191,9 @@ private fun CreateScreen(
.fillMaxWidth()
.height(48.dp)
.padding(horizontal = buttonHorizontalPadding)
.wavyBackground(
.wavyStroke(
color = if (enabled) MSTheme.color.primaryNormal else MSTheme.color.primaryLight,
drawType = if (isShowKeyboard) DrawType.TOP else DrawType.ALL,
contentPadding = if (isShowKeyboard) 0.dp else 5.dp,
clipContent = true,
),
enabled = enabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ import com.idiotfrogs.designsystem.component.MSToast
import com.idiotfrogs.designsystem.component.button.MSButton
import com.idiotfrogs.designsystem.theme.MSTheme
import com.idiotfrogs.designsystem.util.DrawType
import com.idiotfrogs.designsystem.util.WavyAlign
import com.idiotfrogs.designsystem.util.noRippleClickable
import com.idiotfrogs.designsystem.util.wavyBackground
import com.idiotfrogs.designsystem.util.wavyStroke
import com.idiotfrogs.extension.toDday
import com.idiotfrogs.extension.toOpenRemainingText
import com.idiotfrogs.extension.toYearMonthDayWeek
import com.idiotfrogs.model.timecapsule.TimeCapsuleRole
Expand Down Expand Up @@ -201,20 +200,21 @@ fun DetailScreen(
.fillMaxSize()
.hazeSource(hazeState)
.navigationBarsPadding()
.background(MSTheme.color.bgNormal)
.background(Color.White)
.verticalScroll(scrollState),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(420.dp)
.wavyBackground(
.wavyStroke(
color = Color.Black,
drawType = DrawType.BOTTOM,
strokeWidth = 5.dp,
amplitude = 2.dp,
spacing = 3.dp,
clipContent = true,
wavyAlign = WavyAlign.OUTER
)
) {
capsule?.mainImageUrl?.let {
Expand Down Expand Up @@ -260,7 +260,6 @@ fun DetailScreen(
Column(
modifier = Modifier
.fillMaxWidth()
.background(MSTheme.color.white)
.padding(horizontal = 20.dp, vertical = 24.dp)
) {
if (isBuried) {
Expand Down
Loading