From 7f538503b2450cdd0ec17f2e24a668d3c7d3e614 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 14 Oct 2025 15:07:56 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[feat]:=20=EB=AF=B8=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?api=20response=20=EC=83=9D=EC=84=B1=20(#145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/NotificationExistsUncheckedResponse.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/src/main/java/com/texthip/thip/data/model/notification/response/NotificationExistsUncheckedResponse.kt 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 From 3553448f5733244296ff1ceac2759a67eef570b6 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 14 Oct 2025 15:08:27 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[feat]:=20=EB=AF=B8=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?api=EC=9D=98=20service,=20repository=20=EA=B5=AC=ED=98=84=20(#1?= =?UTF-8?q?45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/data/repository/NotificationRepository.kt | 8 ++++++++ .../com/texthip/thip/data/service/NotificationService.kt | 4 ++++ 2 files changed, 12 insertions(+) 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 From a00d49a0a883b8e78cd9420ec56c7bb190f55c5f Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 14 Oct 2025 15:09:09 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[feat]:=20=EA=B8=B0=EC=A1=B4=EC=9D=98=20?= =?UTF-8?q?=EB=AF=B8=ED=99=95=EC=9D=B8=20=EC=97=AC=EB=B6=80=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EB=A1=9C=EC=A7=81=EC=9D=84=20api=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarmpage/viewmodel/AlarmUiState.kt | 4 +-- .../alarmpage/viewmodel/AlarmViewModel.kt | 29 +++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) 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 From 363c9a8c128cfb02296c20445dd5b5bd7ae2daa8 Mon Sep 17 00:00:00 2001 From: rbqks529 Date: Tue, 14 Oct 2025 15:09:58 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[feat]:=20=EA=B7=B8=EB=A3=B9=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EA=B3=BC=20=ED=94=BC=EB=93=9C=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=A1=9C=EC=A7=81=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?(#145)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/texthip/thip/ui/feed/screen/FeedScreen.kt | 4 ++-- .../java/com/texthip/thip/ui/group/screen/GroupScreen.kt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) 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) },