@@ -33,36 +33,49 @@ import androidx.appcompat.app.AppCompatActivity
3333import androidx.compose.foundation.Image
3434import androidx.compose.foundation.clickable
3535import androidx.compose.foundation.layout.Arrangement
36+ import androidx.compose.foundation.layout.Box
3637import androidx.compose.foundation.layout.Column
3738import androidx.compose.foundation.layout.Row
3839import androidx.compose.foundation.layout.Spacer
3940import androidx.compose.foundation.layout.fillMaxSize
4041import androidx.compose.foundation.layout.height
4142import androidx.compose.foundation.layout.padding
4243import androidx.compose.foundation.lazy.LazyColumn
44+ import androidx.compose.material.ExperimentalMaterialApi
45+ import androidx.compose.material.pullrefresh.PullRefreshIndicator
46+ import androidx.compose.material.pullrefresh.pullRefresh
47+ import androidx.compose.material.pullrefresh.rememberPullRefreshState
4348import androidx.compose.material3.CenterAlignedTopAppBar
4449import androidx.compose.material3.ExperimentalMaterial3Api
4550import androidx.compose.material3.Icon
4651import androidx.compose.material3.IconButton
4752import androidx.compose.material3.Scaffold
4853import androidx.compose.material3.Text
4954import androidx.compose.material3.TopAppBarDefaults
55+ import androidx.compose.runtime.LaunchedEffect
5056import androidx.compose.runtime.getValue
57+ import androidx.compose.runtime.mutableStateOf
5158import androidx.compose.runtime.remember
59+ import androidx.compose.runtime.rememberCoroutineScope
60+ import androidx.compose.runtime.setValue
5261import androidx.compose.ui.Alignment
5362import androidx.compose.ui.Modifier
5463import androidx.compose.ui.graphics.vector.ImageVector
5564import androidx.compose.ui.platform.LocalContext
5665import androidx.compose.ui.res.vectorResource
5766import androidx.compose.ui.unit.dp
5867import androidx.lifecycle.compose.collectAsStateWithLifecycle
68+ import androidx.paging.LoadState
5969import androidx.paging.compose.collectAsLazyPagingItems
6070import dagger.hilt.android.AndroidEntryPoint
6171import dagger.hilt.android.EntryPointAccessors
72+ import kotlinx.coroutines.launch
6273import org.sopt.official.common.navigator.NavigatorEntryPoint
6374import org.sopt.official.common.view.toast
6475import org.sopt.official.designsystem.Orange400
6576import org.sopt.official.designsystem.SoptTheme
77+ import org.sopt.official.designsystem.component.dialog.NetworkErrorDialog
78+ import org.sopt.official.designsystem.component.indicator.LoadingIndicator
6679import org.sopt.official.feature.notification.R
6780import org.sopt.official.feature.notification.all.component.NotificationCategoryChip
6881import org.sopt.official.feature.notification.all.component.NotificationInfoItem
@@ -71,18 +84,38 @@ import org.sopt.official.feature.notification.all.component.NotificationInfoItem
7184class NotificationActivity : AppCompatActivity () {
7285 private val viewModel by viewModels<NotificationViewModel >()
7386
74- @OptIn(ExperimentalMaterial3Api ::class )
87+ @OptIn(ExperimentalMaterial3Api ::class , ExperimentalMaterialApi :: class )
7588 override fun onCreate (savedInstanceState : Bundle ? ) {
7689 super .onCreate(savedInstanceState)
7790 setContent {
7891 val notifications = viewModel.notifications.collectAsLazyPagingItems()
92+ var isPullRefreshing by remember { mutableStateOf(false ) }
93+
94+ val refreshLoadState = notifications.loadState.refresh
95+ val refreshState = rememberPullRefreshState(
96+ refreshing = isPullRefreshing,
97+ onRefresh = {
98+ isPullRefreshing = true
99+ notifications.refresh()
100+ }
101+ )
102+
103+ val coroutineScope = rememberCoroutineScope()
104+
79105 val context = LocalContext .current
80106 val navigator = remember {
81107 EntryPointAccessors .fromApplication(
82108 context,
83109 NavigatorEntryPoint ::class .java
84110 ).navigatorProvider()
85111 }
112+
113+ LaunchedEffect (refreshLoadState) {
114+ if (refreshLoadState is LoadState .NotLoading || refreshLoadState is LoadState .Error ) {
115+ isPullRefreshing = false
116+ }
117+ }
118+
86119 SoptTheme {
87120 val state by viewModel.state.collectAsStateWithLifecycle()
88121
@@ -115,8 +148,11 @@ class NotificationActivity : AppCompatActivity() {
115148 modifier = Modifier
116149 .padding(end = 20 .dp)
117150 .clickable{
118- viewModel.updateEntireNotificationReadingState()
119- notifications.refresh()
151+ coroutineScope.launch {
152+ viewModel.updateEntireNotificationReadingState().onSuccess {
153+ notifications.refresh()
154+ }
155+ }
120156 }
121157 .padding(vertical = 8 .dp, horizontal = 4 .dp)
122158 )
@@ -152,41 +188,70 @@ class NotificationActivity : AppCompatActivity() {
152188 }
153189 }
154190
155- if (notifications.itemCount > 0 ) {
156- LazyColumn {
157- items(notifications.itemCount) {
158- val notification = notifications[it]
159-
160- NotificationInfoItem (
161- notification = notification,
162- onCLick = {
163- if (notification?.notificationId == null ) {
164- toast(" 문제가 발생했습니다." )
165- } else {
166- context.startActivity(navigator.getNotificationDetailActivityIntent(notification.notificationId))
191+ Box (
192+ modifier = Modifier
193+ .fillMaxSize()
194+ .pullRefresh(refreshState)
195+ ) {
196+ when (refreshLoadState) {
197+ is LoadState .Loading -> {
198+ if (notifications.itemCount == 0 && ! isPullRefreshing) {
199+ LoadingIndicator ()
200+ }
201+ }
202+ is LoadState .NotLoading -> {
203+ if (notifications.itemCount > 0 ) {
204+ LazyColumn {
205+ items(
206+ count = notifications.itemCount,
207+ key = { index -> notifications[index]?.notificationId ? : index }
208+ ) { index ->
209+ val notification = notifications[index]
210+ NotificationInfoItem (
211+ notification = notification,
212+ onCLick = {
213+ if (notification?.notificationId == null ) {
214+ context.toast(" 문제가 발생했습니다." )
215+ } else {
216+ context.startActivity(navigator.getNotificationDetailActivityIntent(notification.notificationId))
217+ }
218+ }
219+ )
167220 }
168221 }
222+ } else {
223+ Column (
224+ modifier = Modifier .fillMaxSize(),
225+ verticalArrangement = Arrangement .Center ,
226+ horizontalAlignment = Alignment .CenterHorizontally
227+ ) {
228+ Image (
229+ imageVector = ImageVector .vectorResource(R .drawable.icon_notification_empty),
230+ contentDescription = " 알림이 없습니다."
231+ )
232+ Spacer (modifier = Modifier .height(24 .dp))
233+ Text (
234+ text = " 아직 도착한 알림이 없어요." ,
235+ style = SoptTheme .typography.heading18B,
236+ color = SoptTheme .colors.onSurface400
237+ )
238+ }
239+ }
240+ }
241+ is LoadState .Error -> {
242+ NetworkErrorDialog (
243+ onConfirm = {
244+ notifications.refresh()
245+ }
169246 )
170247 }
171248 }
172- } else {
173- Column (
174- modifier = Modifier
175- .fillMaxSize(),
176- verticalArrangement = Arrangement .Center ,
177- horizontalAlignment = Alignment .CenterHorizontally
178- ) {
179- Image (
180- imageVector = ImageVector .vectorResource(R .drawable.icon_notification_empty),
181- contentDescription = " 알림이 없습니다."
182- )
183- Spacer (modifier = Modifier .height(24 .dp))
184- Text (
185- text = " 아직 도착한 알림이 없어요." ,
186- style = SoptTheme .typography.heading18B,
187- color = SoptTheme .colors.onSurface400
188- )
189- }
249+
250+ PullRefreshIndicator (
251+ refreshing = isPullRefreshing,
252+ state = refreshState,
253+ modifier = Modifier .align(Alignment .TopCenter )
254+ )
190255 }
191256 }
192257 }
0 commit comments