diff --git a/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt b/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt index a6a39116..7f8b0850 100644 --- a/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt +++ b/app/src/main/java/com/egobook/app/di/module/RepositoryModule.kt @@ -19,6 +19,8 @@ import dagger.Binds import com.egobook.app.domain.repository.QuestionRepository import com.egobook.app.domain.repository.account.AccountRepository import com.egobook.app.domain.repository.diary.DiaryRepository +import com.egobook.app.ui.home.repository.HomeNotificationRepository +import com.egobook.app.ui.home.repository.NetworkHomeNotificationRepository import com.egobook.app.ui.home.repository.NetworkTendencyLevelService import com.egobook.app.ui.home.repository.NetworkUserRepository import com.egobook.app.ui.home.repository.UserActivityRepository @@ -79,4 +81,8 @@ abstract class RepositoryModule { @Binds @Singleton abstract fun bindActivityRecordRepository(impl: NetworkUserRepository): UserActivityRepository + + @Binds + @Singleton + abstract fun bindHomeNotificationRepository(impl: NetworkHomeNotificationRepository): HomeNotificationRepository } diff --git a/app/src/main/java/com/egobook/app/ui/home/NotificationViewModel.kt b/app/src/main/java/com/egobook/app/ui/home/NotificationViewModel.kt index ddc90f81..045af5e4 100644 --- a/app/src/main/java/com/egobook/app/ui/home/NotificationViewModel.kt +++ b/app/src/main/java/com/egobook/app/ui/home/NotificationViewModel.kt @@ -1,45 +1,36 @@ package com.egobook.app.ui.home +import android.util.Log import androidx.lifecycle.ViewModel -import com.egobook.app.ui.home.notification.EgoRoomType +import androidx.lifecycle.viewModelScope import com.egobook.app.ui.home.notification.Notification -import com.egobook.app.ui.home.notification.NotificationPublisher -import com.egobook.app.ui.home.notification.NotificationStatus -import com.egobook.app.ui.home.notification.NotificationTime -import com.egobook.app.ui.home.notification.NotificationType -import java.time.LocalDateTime +import com.egobook.app.ui.home.repository.HomeNotificationRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject -class NotificationViewModel: ViewModel() { - fun loadNotifications(): List { - return listOf( - Notification( - "답장에관한 내용이 들어가는 자리", - NotificationType.Letter, - NotificationStatus.UNREAD, - NotificationPublisher.Admin, - NotificationTime(LocalDateTime.of(2026, 1, 19, 13, 53)) - ), - Notification( - "답장에관한 내용이 들어가는 자리", - NotificationType.Letter, - NotificationStatus.READ, - NotificationPublisher.User("jan_gu", "철수철수"), - NotificationTime(LocalDateTime.of(2026, 1, 19, 13, 53)) - ), - Notification( - "지난주 주간 리포트가 도착했어요!", - NotificationType.EgoRoom(EgoRoomType.WEAKLY_REPORT), - NotificationStatus.READ, - NotificationPublisher.Admin, - NotificationTime(LocalDateTime.of(2026, 1, 19, 13, 53)) - ), - Notification( - "11.17 일간 칭찬서가 도착했어요!", - NotificationType.EgoRoom(EgoRoomType.DAILY_PRAISE), - NotificationStatus.UNREAD, - NotificationPublisher.Admin, - NotificationTime(LocalDateTime.of(2025, 11, 17, 0, 1)) - ), - ) +@HiltViewModel +class NotificationViewModel @Inject constructor( + private val repository: HomeNotificationRepository +): ViewModel() { + private val _notifications = MutableStateFlow>(emptyList()) + val notifications: StateFlow> = _notifications.asStateFlow() + fun loadNotifications() { + Log.d("jang", "loadNotifications") + viewModelScope.launch { + val updatedList = mutableListOf() + repository.loadNotifications() + .collect { notification -> + // 3. 데이터가 올 때마다 리스트에 추가하고 StateFlow 업데이트 + updatedList.add(notification) + _notifications.value = updatedList.toList() + + // 디버깅용 로그: 데이터가 실제로 오는지 확인 + Log.d("NotificationViewModel", "새 알림 수신: ${notification.content}") + } + } } } diff --git a/app/src/main/java/com/egobook/app/ui/home/repository/HomeNotificationRepository.kt b/app/src/main/java/com/egobook/app/ui/home/repository/HomeNotificationRepository.kt new file mode 100644 index 00000000..4ca05056 --- /dev/null +++ b/app/src/main/java/com/egobook/app/ui/home/repository/HomeNotificationRepository.kt @@ -0,0 +1,108 @@ +package com.egobook.app.ui.home.repository + +import android.util.Log +import com.egobook.app.di.qualifier.BackendApi +import com.egobook.app.ui.home.notification.EgoRoomType +import com.egobook.app.ui.home.notification.Notification +import com.egobook.app.ui.home.notification.NotificationPublisher +import com.egobook.app.ui.home.notification.NotificationStatus +import com.egobook.app.ui.home.notification.NotificationTime +import com.egobook.app.ui.home.notification.NotificationType +import com.egobook.app.ui.shop.BaseResponse +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import retrofit2.Retrofit +import retrofit2.http.GET +import retrofit2.http.Query +import java.time.LocalDateTime +import javax.inject.Inject +import javax.inject.Singleton + +interface HomeNotificationRepository { + suspend fun loadNotifications(): Flow +} + +data class NotificationGroupDto( + val content: List, + val page: Int, + val size: Int, + val hasNext: Boolean +) + +fun String.toNotificationType(): NotificationType = when (this) { + "LETTER_REPLY", "LETTER_REPLY_FRIEND", "LETTER_NEW", "LETTER_NEW_FRIEND" -> NotificationType.Letter + "PRAISE" -> NotificationType.EgoRoom(EgoRoomType.DAILY_PRAISE) + "REPORT" -> NotificationType.EgoRoom(EgoRoomType.WEAKLY_REPORT) + else -> throw IllegalArgumentException("${this}은 알 수 없는 알림 타입 이름입니다") +} + +data class NotificationDto( + val type: String, + val title: String, + val content: String?, + val isRead: Boolean, + val targetId: Int, + val createdAt: String +) { + fun toDomain(): Notification { + return Notification( + content = content ?: "내용이 없습니다", + type = type.toNotificationType(), + status = if (isRead) NotificationStatus.READ else NotificationStatus.UNREAD, + publisher = NotificationPublisher.User("admin", "운영자"), + publishedDate = NotificationTime(LocalDateTime.parse(createdAt)), + ) + } + +} + +interface NetworkNotificationLoadingService { + @GET("/notifications") + suspend fun loadNotificationsResponse( + @Query("page") page: Int, + @Query("size") size: Int + ): BaseResponse +} + + +@Singleton +class NetworkHomeNotificationRepository @Inject constructor( + @BackendApi private val retrofit: Retrofit +) : HomeNotificationRepository { + + private val loadingNotificationService by lazy { + retrofit.create(NetworkNotificationLoadingService::class.java) + } + + override suspend fun loadNotifications(): Flow { + return flow { + var currentPage = 1 + while (true) { + try { + val response = loadingNotificationService.loadNotificationsResponse( + page = currentPage, + size = 10 + ).data + + response.content.forEach { dto -> + Log.d("jang", "$dto") + Log.d("jang", "${NotificationTime(LocalDateTime.parse(dto.createdAt))}") + emit(dto.toDomain()) + } + + if (!response.hasNext) { + break + } + currentPage++ + + } catch (e: Exception) { + Log.e( + "jang", + "페이지 $currentPage 로드 중 에러 발생: $e" + ) + break + } + } + } + } +} diff --git a/app/src/main/java/com/egobook/app/ui/home/ui/NotificationAdapter.kt b/app/src/main/java/com/egobook/app/ui/home/ui/NotificationAdapter.kt index 59a17deb..b476e1db 100644 --- a/app/src/main/java/com/egobook/app/ui/home/ui/NotificationAdapter.kt +++ b/app/src/main/java/com/egobook/app/ui/home/ui/NotificationAdapter.kt @@ -6,6 +6,8 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.egobook.app.R import com.egobook.app.ui.home.notification.EgoRoomType @@ -16,8 +18,7 @@ import com.egobook.app.ui.home.notification.NotificationTime import com.egobook.app.ui.home.notification.NotificationType import java.time.LocalDateTime -class NotificationAdapter(private val notifications: List) : - RecyclerView.Adapter() { +class NotificationAdapter: ListAdapter(DiffCallback) { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int @@ -31,11 +32,9 @@ class NotificationAdapter(private val notifications: List) : holder: NotificationViewHodler, position: Int ) { - holder.bind(notifications[position]) + holder.bind(getItem(position)) } - override fun getItemCount() = notifications.size - class NotificationViewHodler(view: View) : RecyclerView.ViewHolder(view) { val title: TextView = view.findViewById(R.id.tv_notification_title) val content: TextView = view.findViewById(R.id.tv_notification_content) @@ -98,8 +97,18 @@ class NotificationAdapter(private val notifications: List) : time.setTextColor(neutralColor) } } + } + } + companion object { + private val DiffCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Notification, newItem: Notification): Boolean { + return oldItem.content == newItem.content && oldItem.publishedDate == newItem.publishedDate + } + override fun areContentsTheSame(oldItem: Notification, newItem: Notification): Boolean { + return oldItem == newItem + } } } diff --git a/app/src/main/java/com/egobook/app/ui/home/ui/NotificationFragment.kt b/app/src/main/java/com/egobook/app/ui/home/ui/NotificationFragment.kt index 3dba2bbf..dc6251dc 100644 --- a/app/src/main/java/com/egobook/app/ui/home/ui/NotificationFragment.kt +++ b/app/src/main/java/com/egobook/app/ui/home/ui/NotificationFragment.kt @@ -6,31 +6,46 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager import com.egobook.app.databinding.FragmentNotificationBinding import com.egobook.app.ui.home.NotificationViewModel +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +@AndroidEntryPoint class NotificationFragment: Fragment() { private var _binding: FragmentNotificationBinding? = null private val binding get() = checkNotNull(_binding) { "Fragment가 제거되었습니다." } - + private val viewModel: NotificationViewModel by viewModels() + private val notificationAdapter = NotificationAdapter() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - _binding = FragmentNotificationBinding.inflate(inflater) + _binding = FragmentNotificationBinding.inflate(inflater, container,false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val viewModel: NotificationViewModel by viewModels() + viewModel.loadNotifications() binding.rvNotification.apply { - adapter = NotificationAdapter(viewModel.loadNotifications()) + adapter = notificationAdapter layoutManager = LinearLayoutManager(context) } + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.notifications.collect { list -> + notificationAdapter.submitList(list) + } + } + } } override fun onDestroyView() {