Skip to content

Commit 3ea39ca

Browse files
committed
fix: 관리 화면 UI topAppBar 적용 및 LazyColumn 수정 (#64)
- top bar action 에 그룹 추가 버튼 적용 - title, lazyColumn, item, stickyHeader 설정
1 parent e7f1012 commit 3ea39ca

File tree

3 files changed

+190
-39
lines changed

3 files changed

+190
-39
lines changed

presentation/home/src/main/java/com/yapp/breake/presentation/home/HomeScreen.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ private fun HomeContent(
111111
onEditClick = {
112112
onShowEditScreen(it.id)
113113
},
114+
onAddClick = onShowAddScreen,
114115
)
115116
}
116117

presentation/home/src/main/java/com/yapp/breake/presentation/home/component/AppGroupList.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ internal fun AppGroupList(
3737
}
3838

3939
@Composable
40-
private fun AppGroupItem(
40+
internal fun AppGroupItem(
4141
appGroup: AppGroup,
4242
onEditClick: () -> Unit,
4343
modifier: Modifier = Modifier,
Lines changed: 188 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,219 @@
11
package com.yapp.breake.presentation.home.screen
22

3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.animateContentSize
5+
import androidx.compose.animation.core.LinearEasing
6+
import androidx.compose.animation.core.LinearOutSlowInEasing
7+
import androidx.compose.animation.core.animateDpAsState
8+
import androidx.compose.animation.core.animateFloatAsState
9+
import androidx.compose.animation.core.tween
10+
import androidx.compose.animation.fadeIn
11+
import androidx.compose.animation.fadeOut
12+
import androidx.compose.foundation.Image
13+
import androidx.compose.foundation.LocalOverscrollFactory
14+
import androidx.compose.foundation.background
315
import androidx.compose.foundation.layout.Column
416
import androidx.compose.foundation.layout.Row
517
import androidx.compose.foundation.layout.fillMaxSize
618
import androidx.compose.foundation.layout.fillMaxWidth
719
import androidx.compose.foundation.layout.padding
8-
import androidx.compose.foundation.layout.statusBarsPadding
20+
import androidx.compose.foundation.lazy.LazyColumn
21+
import androidx.compose.foundation.lazy.itemsIndexed
22+
import androidx.compose.foundation.lazy.rememberLazyListState
23+
import androidx.compose.material.icons.Icons
24+
import androidx.compose.material.icons.filled.Add
25+
import androidx.compose.material3.ExperimentalMaterial3Api
26+
import androidx.compose.material3.Icon
27+
import androidx.compose.material3.IconButton
928
import androidx.compose.material3.MaterialTheme
29+
import androidx.compose.material3.Scaffold
1030
import androidx.compose.material3.Text
31+
import androidx.compose.material3.TopAppBar
32+
import androidx.compose.material3.TopAppBarDefaults
1133
import androidx.compose.runtime.Composable
34+
import androidx.compose.runtime.CompositionLocalProvider
35+
import androidx.compose.runtime.derivedStateOf
36+
import androidx.compose.runtime.getValue
37+
import androidx.compose.runtime.remember
1238
import androidx.compose.ui.Alignment
1339
import androidx.compose.ui.Modifier
40+
import androidx.compose.ui.input.nestedscroll.nestedScroll
41+
import androidx.compose.ui.layout.ContentScale
42+
import androidx.compose.ui.res.painterResource
1443
import androidx.compose.ui.res.stringResource
44+
import androidx.compose.ui.text.style.TextAlign
1545
import androidx.compose.ui.tooling.preview.Preview
1646
import androidx.compose.ui.unit.dp
1747
import com.yapp.breake.core.designsystem.component.HorizontalSpacer
1848
import com.yapp.breake.core.designsystem.component.VerticalSpacer
1949
import com.yapp.breake.core.designsystem.theme.BrakeTheme
50+
import com.yapp.breake.core.designsystem.theme.Gray100
2051
import com.yapp.breake.core.designsystem.theme.Gray200
52+
import com.yapp.breake.core.designsystem.theme.Gray900
2153
import com.yapp.breake.core.model.app.AppGroup
2254
import com.yapp.breake.presentation.home.R
23-
import com.yapp.breake.presentation.home.component.AppGroupList
24-
import com.yapp.breake.presentation.home.component.ImageTextBox
55+
import com.yapp.breake.presentation.home.component.AppGroupItem
2556

57+
@OptIn(ExperimentalMaterial3Api::class)
2658
@Composable
2759
internal fun ListScreen(
2860
appGroups: List<AppGroup>,
2961
onEditClick: (AppGroup) -> Unit,
62+
onAddClick: () -> Unit,
3063
) {
31-
Column(
32-
modifier = Modifier.fillMaxSize(),
33-
horizontalAlignment = Alignment.CenterHorizontally,
34-
) {
35-
ImageTextBox(
36-
imageRes = R.drawable.img_home_list,
37-
text = stringResource(R.string.list_screen_description),
38-
modifier = Modifier.statusBarsPadding(),
39-
)
40-
VerticalSpacer(30.dp)
41-
Row(
42-
verticalAlignment = Alignment.Bottom,
43-
modifier = Modifier
44-
.fillMaxWidth()
45-
.padding(horizontal = 24.dp),
46-
) {
47-
Text(
48-
text = stringResource(R.string.group),
49-
style = BrakeTheme.typography.subtitle22SB,
50-
color = MaterialTheme.colorScheme.onSurface,
51-
)
52-
HorizontalSpacer(1f)
53-
Text(
54-
text = stringResource(R.string.group_count_format, appGroups.size),
55-
style = BrakeTheme.typography.body12M,
56-
color = Gray200,
57-
)
64+
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
65+
val listState = rememberLazyListState()
66+
val showTitle by remember {
67+
derivedStateOf {
68+
listState.firstVisibleItemIndex > 0
5869
}
59-
VerticalSpacer(16.dp)
60-
AppGroupList(
61-
appGroups = appGroups,
62-
onEditClick = onEditClick,
63-
modifier = Modifier
64-
.fillMaxWidth()
65-
.padding(horizontal = 16.dp),
66-
)
6770
}
71+
val alpha by animateFloatAsState(
72+
targetValue = if (showTitle) 1f else 0f,
73+
animationSpec = tween(20, easing = LinearEasing),
74+
label = "appbarAlpha",
75+
)
76+
val container = Gray900.copy(alpha = alpha)
77+
val headerKey = "groupsHeader"
78+
79+
Scaffold(
80+
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
81+
topBar = {
82+
TopAppBar(
83+
title = {
84+
AnimatedVisibility(
85+
visible = showTitle,
86+
modifier = Modifier.fillMaxWidth(),
87+
enter = fadeIn(),
88+
exit = fadeOut(),
89+
) {
90+
Text(
91+
text = stringResource(R.string.list_screen_title),
92+
style = BrakeTheme.typography.subtitle16SB,
93+
color = Gray100,
94+
textAlign = TextAlign.Center,
95+
)
96+
}
97+
},
98+
modifier = Modifier.animateContentSize(),
99+
navigationIcon = {
100+
// 공간만 차지하는 네비게이션 아이콘
101+
HorizontalSpacer(48.dp)
102+
},
103+
actions = {
104+
IconButton(onClick = onAddClick) {
105+
Icon(
106+
imageVector = Icons.Default.Add,
107+
contentDescription = stringResource(R.string.add_button_content_description),
108+
)
109+
}
110+
},
111+
scrollBehavior = scrollBehavior,
112+
colors = TopAppBarDefaults.topAppBarColors(
113+
containerColor = container,
114+
scrolledContainerColor = container,
115+
navigationIconContentColor = Gray100,
116+
titleContentColor = Gray100,
117+
actionIconContentColor = Gray100,
118+
),
119+
)
120+
},
121+
content = { innerPadding ->
122+
CompositionLocalProvider(LocalOverscrollFactory provides null) {
123+
LazyColumn(
124+
modifier = Modifier
125+
.fillMaxSize()
126+
.padding(bottom = 16.dp),
127+
state = listState,
128+
horizontalAlignment = Alignment.CenterHorizontally,
129+
) {
130+
item {
131+
Column(
132+
modifier = Modifier
133+
.fillMaxWidth()
134+
.padding(horizontal = 16.dp),
135+
horizontalAlignment = Alignment.CenterHorizontally,
136+
) {
137+
Image(
138+
painter = painterResource(id = R.drawable.img_home_list),
139+
contentDescription = null,
140+
modifier = Modifier.fillMaxWidth(),
141+
contentScale = ContentScale.FillWidth,
142+
)
143+
VerticalSpacer(12.dp)
144+
Text(
145+
text = stringResource(R.string.list_screen_description),
146+
style = BrakeTheme.typography.subtitle22SB,
147+
color = Gray100,
148+
textAlign = TextAlign.Center,
149+
)
150+
}
151+
}
152+
153+
stickyHeader(key = headerKey) {
154+
val isPinned by remember {
155+
derivedStateOf {
156+
val info = listState.layoutInfo.visibleItemsInfo
157+
.firstOrNull { it.key == headerKey }
158+
info?.offset == 0
159+
}
160+
}
161+
162+
// 핀일 때만 AppBar 높이 적용, 아니면 0
163+
val topInset = innerPadding.calculateTopPadding()
164+
val spacer by animateDpAsState(
165+
targetValue = if (isPinned) topInset else 36.dp,
166+
animationSpec = tween(500, easing = LinearOutSlowInEasing),
167+
label = "HeaderTopInset",
168+
)
169+
Column(
170+
modifier = Modifier.background(container),
171+
) {
172+
VerticalSpacer(spacer)
173+
Row(
174+
verticalAlignment = Alignment.Bottom,
175+
modifier = Modifier
176+
.fillMaxWidth()
177+
.padding(top = 4.dp, bottom = 16.dp)
178+
.padding(horizontal = 24.dp),
179+
) {
180+
Text(
181+
text = stringResource(R.string.group),
182+
style = BrakeTheme.typography.subtitle22SB,
183+
color = MaterialTheme.colorScheme.onSurface,
184+
)
185+
HorizontalSpacer(1f)
186+
Text(
187+
text = stringResource(
188+
R.string.group_count_format,
189+
appGroups.size,
190+
),
191+
style = BrakeTheme.typography.body12M,
192+
color = Gray200,
193+
)
194+
}
195+
}
196+
}
197+
198+
itemsIndexed(
199+
appGroups,
200+
key = { _, appGroup -> appGroup.id },
201+
) { index, appGroup ->
202+
AppGroupItem(
203+
appGroup = appGroup,
204+
onEditClick = { onEditClick(appGroup) },
205+
modifier = Modifier
206+
.fillMaxWidth()
207+
.padding(horizontal = 16.dp),
208+
)
209+
if (index != appGroups.lastIndex) {
210+
VerticalSpacer(12.dp)
211+
}
212+
}
213+
}
214+
}
215+
},
216+
)
68217
}
69218

70219
@Preview
@@ -74,6 +223,7 @@ private fun ListScreenPreview() {
74223
ListScreen(
75224
appGroups = listOf(AppGroup.sample),
76225
onEditClick = { /* TODO: Handle app group click */ },
226+
onAddClick = { /* Handle add click */ },
77227
)
78228
}
79229
}

0 commit comments

Comments
 (0)