Skip to content

Commit d5c0288

Browse files
committed
[UI/#182] BottomSheet 상단 stroke 추가
1 parent edca4ef commit d5c0288

File tree

2 files changed

+143
-56
lines changed

2 files changed

+143
-56
lines changed

core/ui/src/main/java/com/yapp/ui/component/OrbitBottomSheet.kt

+79-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.yapp.ui.component
22

3+
import androidx.compose.foundation.Canvas
34
import androidx.compose.foundation.layout.Box
4-
import androidx.compose.foundation.layout.ColumnScope
55
import androidx.compose.foundation.layout.fillMaxWidth
66
import androidx.compose.foundation.layout.height
77
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -19,9 +19,14 @@ import androidx.compose.runtime.saveable.rememberSaveable
1919
import androidx.compose.runtime.setValue
2020
import androidx.compose.ui.Alignment
2121
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.geometry.Rect
2223
import androidx.compose.ui.graphics.Color
24+
import androidx.compose.ui.graphics.Path
25+
import androidx.compose.ui.graphics.PathMeasure
2326
import androidx.compose.ui.graphics.Shape
27+
import androidx.compose.ui.graphics.drawscope.Stroke
2428
import androidx.compose.ui.tooling.preview.Preview
29+
import androidx.compose.ui.unit.Dp
2530
import androidx.compose.ui.unit.dp
2631
import com.yapp.designsystem.theme.OrbitTheme
2732
import kotlinx.coroutines.launch
@@ -37,7 +42,9 @@ fun OrbitBottomSheet(
3742
onDismissRequest: () -> Unit = {},
3843
shape: Shape = RoundedCornerShape(topStart = 30.dp, topEnd = 30.dp),
3944
containerColor: Color = OrbitTheme.colors.gray_800,
40-
content: @Composable ColumnScope.() -> Unit,
45+
strokeColor: Color = OrbitTheme.colors.gray_600,
46+
strokeThickness: Dp = 2.dp,
47+
content: @Composable () -> Unit,
4148
) {
4249
val scope = rememberCoroutineScope()
4350
if (isSheetOpen) {
@@ -54,7 +61,76 @@ fun OrbitBottomSheet(
5461
containerColor = containerColor,
5562
dragHandle = null,
5663
) {
57-
content()
64+
Box {
65+
content()
66+
BottomSheetTopRoundedStroke(
67+
strokeColor = strokeColor,
68+
strokeThickness = strokeThickness,
69+
)
70+
}
71+
}
72+
}
73+
}
74+
75+
@Composable
76+
fun BottomSheetTopRoundedStroke(
77+
modifier: Modifier = Modifier,
78+
strokeColor: Color,
79+
strokeThickness: Dp = 1.dp,
80+
radius: Dp = 30.dp,
81+
) {
82+
Canvas(
83+
modifier = modifier
84+
.fillMaxWidth()
85+
.height(radius + strokeThickness), // Stroke 고려
86+
) {
87+
val width = size.width
88+
val height = size.height
89+
val radiusPx = radius.toPx()
90+
val fadeWidth = radiusPx // 양 끝에서 선이 얇아지는 범위
91+
92+
val path = Path().apply {
93+
moveTo(0f, height) // 왼쪽 끝
94+
arcTo(
95+
rect = Rect(0f, 0f, radiusPx * 2, radiusPx * 2),
96+
startAngleDegrees = 180f,
97+
sweepAngleDegrees = 90f,
98+
forceMoveTo = false,
99+
)
100+
lineTo(width - radiusPx, 0f)
101+
arcTo(
102+
rect = Rect(width - radiusPx * 2, 0f, width, radiusPx * 2),
103+
startAngleDegrees = 270f,
104+
sweepAngleDegrees = 90f,
105+
forceMoveTo = false,
106+
)
107+
}
108+
109+
val pathMeasure = PathMeasure().apply { setPath(path, false) }
110+
val totalLength = pathMeasure.length
111+
val segmentCount = 100
112+
113+
for (i in 0 until segmentCount) {
114+
val start = i * (totalLength / segmentCount)
115+
val end = (i + 1) * (totalLength / segmentCount)
116+
117+
val segmentPath = Path()
118+
if (pathMeasure.getSegment(start, end, segmentPath, true)) {
119+
val minThickness = 0.dp.toPx()
120+
val maxThickness = strokeThickness.toPx()
121+
122+
val thickness = when {
123+
start < fadeWidth -> minThickness + (maxThickness - minThickness) * (start / fadeWidth)
124+
start > totalLength - fadeWidth -> minThickness + (maxThickness - minThickness) * ((totalLength - start) / fadeWidth)
125+
else -> maxThickness
126+
}
127+
128+
drawPath(
129+
path = segmentPath,
130+
color = strokeColor,
131+
style = Stroke(width = thickness),
132+
)
133+
}
58134
}
59135
}
60136
}

feature/home/src/main/java/com/yapp/home/component/bottomsheet/AlarmListBottomSheet.kt

+64-53
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import com.yapp.alarm.component.AlarmListItem
5151
import com.yapp.designsystem.theme.OrbitTheme
5252
import com.yapp.domain.model.Alarm
5353
import com.yapp.home.component.AlarmListDropDownMenu
54+
import com.yapp.ui.component.BottomSheetTopRoundedStroke
5455
import com.yapp.ui.component.checkbox.OrbitCheckBox
5556
import com.yapp.ui.utils.OnLoadMore
5657
import feature.home.R
@@ -195,66 +196,76 @@ internal fun AlarmBottomSheetContent(
195196
onLoadMore()
196197
}
197198

198-
Column(
199-
modifier = modifier
200-
.fillMaxSize()
201-
.background(
202-
color = OrbitTheme.colors.gray_900,
203-
shape = RoundedCornerShape(topStart = cornerRadius, topEnd = cornerRadius),
204-
),
205-
horizontalAlignment = Alignment.CenterHorizontally,
206-
) {
207-
Spacer(modifier = Modifier.height(topPadding))
208-
209-
if (isSelectionMode) {
210-
AlarmSelectionTopBar(
211-
checked = isAllSelected,
212-
onClickCheckAll = onClickCheckAll,
213-
onClickClose = onClickClose,
214-
)
215-
} else {
216-
AlarmListTopBar(
217-
menuExpanded = menuExpanded,
218-
onClickAdd = onClickAdd,
219-
onClickMore = onClickMore,
220-
onDismissRequest = onDismissRequest,
221-
onClickEdit = onClickEdit,
222-
)
223-
}
224-
225-
LazyColumn(
226-
state = listState,
199+
Box {
200+
Column(
201+
modifier = modifier
202+
.fillMaxSize()
203+
.background(
204+
color = OrbitTheme.colors.gray_900,
205+
shape = RoundedCornerShape(topStart = cornerRadius, topEnd = cornerRadius),
206+
),
207+
horizontalAlignment = Alignment.CenterHorizontally,
227208
) {
228-
itemsIndexed(alarms) { index, alarm ->
229-
AlarmListItem(
230-
id = alarm.id,
231-
repeatDays = alarm.repeatDays,
232-
isHolidayAlarmOff = alarm.isHolidayAlarmOff,
233-
selectable = isSelectionMode,
234-
selected = selectedAlarmIds.contains(alarm.id),
235-
onClick = onClickAlarm,
236-
onToggleSelect = onToggleSelect,
237-
isAm = alarm.isAm,
238-
hour = alarm.hour,
239-
minute = alarm.minute,
240-
isActive = alarm.isAlarmActive,
241-
onToggleActive = onToggleActive,
209+
Spacer(modifier = Modifier.height(topPadding))
210+
211+
if (isSelectionMode) {
212+
AlarmSelectionTopBar(
213+
checked = isAllSelected,
214+
onClickCheckAll = onClickCheckAll,
215+
onClickClose = onClickClose,
242216
)
243-
if (index != alarms.size - 1) {
244-
Spacer(
245-
modifier = Modifier
246-
.fillMaxWidth()
247-
.height(1.dp)
248-
.background(OrbitTheme.colors.gray_800)
249-
.padding(horizontal = 24.dp),
217+
} else {
218+
AlarmListTopBar(
219+
menuExpanded = menuExpanded,
220+
onClickAdd = onClickAdd,
221+
onClickMore = onClickMore,
222+
onDismissRequest = onDismissRequest,
223+
onClickEdit = onClickEdit,
224+
)
225+
}
226+
227+
LazyColumn(
228+
state = listState,
229+
) {
230+
itemsIndexed(alarms) { index, alarm ->
231+
AlarmListItem(
232+
id = alarm.id,
233+
repeatDays = alarm.repeatDays,
234+
isHolidayAlarmOff = alarm.isHolidayAlarmOff,
235+
selectable = isSelectionMode,
236+
selected = selectedAlarmIds.contains(alarm.id),
237+
onClick = onClickAlarm,
238+
onToggleSelect = onToggleSelect,
239+
isAm = alarm.isAm,
240+
hour = alarm.hour,
241+
minute = alarm.minute,
242+
isActive = alarm.isAlarmActive,
243+
onToggleActive = onToggleActive,
250244
)
245+
if (index != alarms.size - 1) {
246+
Spacer(
247+
modifier = Modifier
248+
.fillMaxWidth()
249+
.height(1.dp)
250+
.background(OrbitTheme.colors.gray_800)
251+
.padding(horizontal = 24.dp),
252+
)
253+
}
251254
}
252-
}
253255

254-
item {
255-
Spacer(modifier = Modifier.height(104.dp))
256+
item {
257+
Spacer(modifier = Modifier.height(104.dp))
258+
}
256259
}
257260
}
261+
262+
if (cornerRadius > 0.dp) {
263+
BottomSheetTopRoundedStroke(
264+
strokeColor = OrbitTheme.colors.gray_600,
265+
strokeThickness = 1.dp,
266+
radius = cornerRadius,
267+
)
268+
}
258269
}
259270
}
260271

0 commit comments

Comments
 (0)