diff --git a/app/src/main/java/com/texthip/thip/data/model/notification/response/NotificationExistsUncheckedResponse.kt b/app/src/main/java/com/texthip/thip/data/model/notification/response/NotificationExistsUncheckedResponse.kt new file mode 100644 index 00000000..095b90f2 --- /dev/null +++ b/app/src/main/java/com/texthip/thip/data/model/notification/response/NotificationExistsUncheckedResponse.kt @@ -0,0 +1,9 @@ +package com.texthip.thip.data.model.notification.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class NotificationExistsUncheckedResponse( + @SerialName("exists") val exists: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/repository/NotificationRepository.kt b/app/src/main/java/com/texthip/thip/data/repository/NotificationRepository.kt index e9669fd2..f2dbe95a 100644 --- a/app/src/main/java/com/texthip/thip/data/repository/NotificationRepository.kt +++ b/app/src/main/java/com/texthip/thip/data/repository/NotificationRepository.kt @@ -9,6 +9,7 @@ import com.texthip.thip.data.model.notification.request.NotificationCheckRequest import com.texthip.thip.data.model.notification.response.NotificationEnabledResponse import com.texthip.thip.data.model.notification.response.NotificationListResponse import com.texthip.thip.data.model.notification.response.NotificationCheckResponse +import com.texthip.thip.data.model.notification.response.NotificationExistsUncheckedResponse import com.texthip.thip.data.service.NotificationService import com.texthip.thip.utils.auth.getAppScopeDeviceId import dagger.hilt.android.qualifiers.ApplicationContext @@ -108,4 +109,11 @@ class NotificationRepository @Inject constructor( fun onNotificationReceived() { _notificationRefreshFlow.tryEmit(Unit) } + + suspend fun existsUncheckedNotifications(): Result { + return runCatching { + val response = notificationService.existsUncheckedNotifications() + response.handleBaseResponse().getOrNull() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/data/service/NotificationService.kt b/app/src/main/java/com/texthip/thip/data/service/NotificationService.kt index db0575ef..87875e2f 100644 --- a/app/src/main/java/com/texthip/thip/data/service/NotificationService.kt +++ b/app/src/main/java/com/texthip/thip/data/service/NotificationService.kt @@ -7,6 +7,7 @@ import com.texthip.thip.data.model.notification.request.NotificationCheckRequest import com.texthip.thip.data.model.notification.response.NotificationEnabledResponse import com.texthip.thip.data.model.notification.response.NotificationListResponse import com.texthip.thip.data.model.notification.response.NotificationCheckResponse +import com.texthip.thip.data.model.notification.response.NotificationExistsUncheckedResponse import com.texthip.thip.data.model.base.BaseResponse import retrofit2.http.Body import retrofit2.http.DELETE @@ -46,4 +47,7 @@ interface NotificationService { suspend fun checkNotification( @Body request: NotificationCheckRequest ): BaseResponse + + @GET("notifications/exists-unchecked") + suspend fun existsUncheckedNotifications(): BaseResponse } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmUiState.kt b/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmUiState.kt index 24e685ae..22ed4624 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmUiState.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmUiState.kt @@ -9,8 +9,8 @@ data class AlarmUiState( val isLoading: Boolean = false, val isLoadingMore: Boolean = false, val hasMore: Boolean = true, - val error: String? = null + val error: String? = null, + val hasUnreadNotifications: Boolean = false // API에서 가져온 읽지 않은 알림 존재 여부 ) { val canLoadMore: Boolean get() = !isLoading && !isLoadingMore && hasMore - val hasUnreadNotifications: Boolean get() = notifications.any { !it.isChecked } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmViewModel.kt b/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmViewModel.kt index 6b0d540b..dc75f35c 100644 --- a/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmViewModel.kt +++ b/app/src/main/java/com/texthip/thip/ui/common/alarmpage/viewmodel/AlarmViewModel.kt @@ -31,19 +31,20 @@ class AlarmViewModel @Inject constructor( } init { - loadNotifications(reset = true) + checkUnreadNotifications() // Repository의 알림 업데이트 이벤트 구독 viewModelScope.launch { repository.notificationUpdateFlow.collect { notificationId -> updateNotificationAsRead(notificationId) + checkUnreadNotifications() } } - // 푸시 알림 도착 시 새로고침 이벤트 구독 + // 푸시 알림 도착 시 아이콘 상태만 갱신 viewModelScope.launch { repository.notificationRefreshFlow.collect { - refreshData() + checkUnreadNotifications() } } } @@ -54,14 +55,14 @@ class AlarmViewModel @Inject constructor( loadJob?.cancel() loadJob = null } - + // 중복 로드 방지 (reset이 아닌 경우에만) if (isLoadingData && !reset) return if (isLastPage && !reset) return // launch 전에 isLoadingData 선반영 (플리커 방지) isLoadingData = true - + // UI 상태 즉시 반영 if (reset) { updateState { @@ -126,6 +127,7 @@ class AlarmViewModel @Inject constructor( fun refreshData() { loadNotifications(reset = true) + checkUnreadNotifications() } fun changeNotificationType(notificationType: NotificationType) { @@ -161,4 +163,21 @@ class AlarmViewModel @Inject constructor( } updateState { it.copy(notifications = updatedNotifications) } } + + fun checkUnreadNotifications() { + viewModelScope.launch { + repository.existsUncheckedNotifications() + .onSuccess { response -> + response?.let { + updateState { state -> + state.copy(hasUnreadNotifications = it.exists) + } + } + } + .onFailure { exception -> + // 에러 발생 시 기존 상태 유지 (로그만 남김) + updateState { it.copy(error = exception.message) } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt index 24d30f6e..488868cf 100644 --- a/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt @@ -224,7 +224,7 @@ fun FeedScreen( LaunchedEffect(onFeedTabReselected) { if (onFeedTabReselected > 0) { feedViewModel.refreshOnBottomNavReselect() - alarmViewModel.refreshData() + alarmViewModel.checkUnreadNotifications() currentListState.scrollToItem(0) } } @@ -288,7 +288,7 @@ fun FeedScreen( onChangeFeedSave = feedViewModel::changeFeedSave, onPullToRefresh = { feedViewModel.pullToRefresh() - alarmViewModel.refreshData() + alarmViewModel.checkUnreadNotifications() } ) } diff --git a/app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt b/app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt index ee41ae90..9f9e3aba 100644 --- a/app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt +++ b/app/src/main/java/com/texthip/thip/ui/group/screen/GroupScreen.kt @@ -64,7 +64,7 @@ fun GroupScreen( // 화면 재진입 시 데이터 새로고침 LaunchedEffect(Unit) { viewModel.resetToInitialState() - alarmViewModel.refreshData() + alarmViewModel.checkUnreadNotifications() } val uiState by viewModel.uiState.collectAsState() val alarmUiState by alarmViewModel.uiState.collectAsState() @@ -80,9 +80,9 @@ fun GroupScreen( onNavigateToGroupRecruit = onNavigateToGroupRecruit, onNavigateToGroupRoom = onNavigateToGroupRoom, onNavigateToGroupSearchAllRooms = onNavigateToGroupSearchAllRooms, - onRefreshGroupData = { + onRefreshGroupData = { viewModel.refreshGroupData() - alarmViewModel.refreshData() + alarmViewModel.checkUnreadNotifications() }, onCardVisible = { cardIndex -> viewModel.loadMoreGroups() }, onSelectGenre = { genreIndex -> viewModel.selectGenre(genreIndex) },