11package 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
315import androidx.compose.foundation.layout.Column
416import androidx.compose.foundation.layout.Row
517import androidx.compose.foundation.layout.fillMaxSize
618import androidx.compose.foundation.layout.fillMaxWidth
719import 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
928import androidx.compose.material3.MaterialTheme
29+ import androidx.compose.material3.Scaffold
1030import androidx.compose.material3.Text
31+ import androidx.compose.material3.TopAppBar
32+ import androidx.compose.material3.TopAppBarDefaults
1133import 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
1238import androidx.compose.ui.Alignment
1339import androidx.compose.ui.Modifier
40+ import androidx.compose.ui.layout.ContentScale
41+ import androidx.compose.ui.res.painterResource
1442import androidx.compose.ui.res.stringResource
43+ import androidx.compose.ui.text.style.TextAlign
1544import androidx.compose.ui.tooling.preview.Preview
1645import androidx.compose.ui.unit.dp
1746import com.yapp.breake.core.designsystem.component.HorizontalSpacer
1847import com.yapp.breake.core.designsystem.component.VerticalSpacer
1948import com.yapp.breake.core.designsystem.theme.BrakeTheme
49+ import com.yapp.breake.core.designsystem.theme.Gray100
2050import com.yapp.breake.core.designsystem.theme.Gray200
51+ import com.yapp.breake.core.designsystem.theme.Gray900
2152import com.yapp.breake.core.model.app.AppGroup
2253import com.yapp.breake.presentation.home.R
23- import com.yapp.breake.presentation.home.component.AppGroupList
24- import com.yapp.breake.presentation.home.component.ImageTextBox
54+ import com.yapp.breake.presentation.home.component.AppGroupItem
2555
56+ @OptIn(ExperimentalMaterial3Api ::class )
2657@Composable
2758internal fun ListScreen (
2859 appGroups : List <AppGroup >,
2960 onEditClick : (AppGroup ) -> Unit ,
61+ onAddClick : () -> Unit ,
3062) {
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- )
63+ val listState = rememberLazyListState()
64+ val showTitle by remember {
65+ derivedStateOf {
66+ listState.firstVisibleItemIndex > 0
5867 }
59- VerticalSpacer (16 .dp)
60- AppGroupList (
61- appGroups = appGroups,
62- onEditClick = onEditClick,
63- modifier = Modifier
64- .fillMaxWidth()
65- .padding(horizontal = 16 .dp),
66- )
6768 }
69+ val alpha by animateFloatAsState(
70+ targetValue = if (showTitle) 1f else 0f ,
71+ animationSpec = tween(20 , easing = LinearEasing ),
72+ label = " appbarAlpha" ,
73+ )
74+ val container = Gray900 .copy(alpha = alpha)
75+ val headerKey = " groupsHeader"
76+
77+ Scaffold (
78+ topBar = {
79+ TopAppBar (
80+ title = {
81+ AnimatedVisibility (
82+ visible = showTitle,
83+ modifier = Modifier .fillMaxWidth(),
84+ enter = fadeIn(),
85+ exit = fadeOut(),
86+ ) {
87+ Text (
88+ text = stringResource(R .string.list_screen_title),
89+ style = BrakeTheme .typography.subtitle16SB,
90+ color = Gray100 ,
91+ textAlign = TextAlign .Center ,
92+ )
93+ }
94+ },
95+ modifier = Modifier .animateContentSize(),
96+ navigationIcon = {
97+ // 공간만 차지하는 네비게이션 아이콘
98+ HorizontalSpacer (48 .dp)
99+ },
100+ actions = {
101+ IconButton (onClick = onAddClick) {
102+ Icon (
103+ imageVector = Icons .Default .Add ,
104+ contentDescription = stringResource(R .string.add_button_content_description),
105+ )
106+ }
107+ },
108+ colors = TopAppBarDefaults .topAppBarColors(
109+ containerColor = container,
110+ scrolledContainerColor = container,
111+ navigationIconContentColor = Gray100 ,
112+ titleContentColor = Gray100 ,
113+ actionIconContentColor = Gray100 ,
114+ ),
115+ )
116+ },
117+ content = { innerPadding ->
118+ CompositionLocalProvider (LocalOverscrollFactory provides null ) {
119+ LazyColumn (
120+ modifier = Modifier
121+ .fillMaxSize()
122+ .padding(bottom = 16 .dp),
123+ state = listState,
124+ horizontalAlignment = Alignment .CenterHorizontally ,
125+ ) {
126+ item {
127+ Column (
128+ modifier = Modifier
129+ .fillMaxWidth()
130+ .padding(horizontal = 16 .dp),
131+ horizontalAlignment = Alignment .CenterHorizontally ,
132+ ) {
133+ Image (
134+ painter = painterResource(id = R .drawable.img_home_list),
135+ contentDescription = null ,
136+ modifier = Modifier .fillMaxWidth(),
137+ contentScale = ContentScale .FillWidth ,
138+ )
139+ VerticalSpacer (12 .dp)
140+ Text (
141+ text = stringResource(R .string.list_screen_description),
142+ style = BrakeTheme .typography.subtitle22SB,
143+ color = Gray100 ,
144+ textAlign = TextAlign .Center ,
145+ )
146+ }
147+ }
148+
149+ stickyHeader(key = headerKey) {
150+ val isPinned by remember {
151+ derivedStateOf {
152+ val info = listState.layoutInfo.visibleItemsInfo
153+ .firstOrNull { it.key == headerKey }
154+ info?.offset == 0
155+ }
156+ }
157+
158+ // 핀일 때만 AppBar 높이 적용, 아니면 0
159+ val topInset = innerPadding.calculateTopPadding()
160+ val spacer by animateDpAsState(
161+ targetValue = if (isPinned) topInset else 36 .dp,
162+ animationSpec = tween(500 , easing = LinearOutSlowInEasing ),
163+ label = " HeaderTopInset" ,
164+ )
165+ Column (
166+ modifier = Modifier .background(container),
167+ ) {
168+ VerticalSpacer (spacer)
169+ Row (
170+ verticalAlignment = Alignment .Bottom ,
171+ modifier = Modifier
172+ .fillMaxWidth()
173+ .padding(top = 4 .dp, bottom = 16 .dp)
174+ .padding(horizontal = 24 .dp),
175+ ) {
176+ Text (
177+ text = stringResource(R .string.group),
178+ style = BrakeTheme .typography.subtitle22SB,
179+ color = MaterialTheme .colorScheme.onSurface,
180+ )
181+ HorizontalSpacer (1f )
182+ Text (
183+ text = stringResource(
184+ R .string.group_count_format,
185+ appGroups.size,
186+ ),
187+ style = BrakeTheme .typography.body12M,
188+ color = Gray200 ,
189+ )
190+ }
191+ }
192+ }
193+
194+ itemsIndexed(
195+ appGroups,
196+ key = { _, appGroup -> appGroup.id },
197+ ) { index, appGroup ->
198+ AppGroupItem (
199+ appGroup = appGroup,
200+ onEditClick = { onEditClick(appGroup) },
201+ modifier = Modifier
202+ .fillMaxWidth()
203+ .padding(horizontal = 16 .dp),
204+ )
205+ if (index != appGroups.lastIndex) {
206+ VerticalSpacer (12 .dp)
207+ }
208+ }
209+ }
210+ }
211+ },
212+ )
68213}
69214
70215@Preview
@@ -74,6 +219,7 @@ private fun ListScreenPreview() {
74219 ListScreen (
75220 appGroups = listOf (AppGroup .sample),
76221 onEditClick = { /* TODO: Handle app group click */ },
222+ onAddClick = { /* Handle add click */ },
77223 )
78224 }
79225}
0 commit comments