Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -79,4 +81,8 @@ abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindActivityRecordRepository(impl: NetworkUserRepository): UserActivityRepository

@Binds
@Singleton
abstract fun bindHomeNotificationRepository(impl: NetworkHomeNotificationRepository): HomeNotificationRepository
}
67 changes: 29 additions & 38 deletions app/src/main/java/com/egobook/app/ui/home/NotificationViewModel.kt
Original file line number Diff line number Diff line change
@@ -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<Notification> {
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<List<Notification>>(emptyList())
val notifications: StateFlow<List<Notification>> = _notifications.asStateFlow()
fun loadNotifications() {
Log.d("jang", "loadNotifications")
viewModelScope.launch {
val updatedList = mutableListOf<Notification>()
repository.loadNotifications()
.collect { notification ->
// 3. 데이터가 올 때마다 리스트에 추가하고 StateFlow 업데이트
updatedList.add(notification)
_notifications.value = updatedList.toList()

// 디버깅용 로그: 데이터가 실제로 오는지 확인
Log.d("NotificationViewModel", "새 알림 수신: ${notification.content}")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Notification>
}

data class NotificationGroupDto(
val content: List<NotificationDto>,
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<NotificationGroupDto>
}


@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<Notification> {
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
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Notification>) :
RecyclerView.Adapter<NotificationAdapter.NotificationViewHodler>() {
class NotificationAdapter: ListAdapter<Notification, NotificationAdapter.NotificationViewHodler>(DiffCallback) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
Expand All @@ -31,11 +32,9 @@ class NotificationAdapter(private val notifications: List<Notification>) :
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)
Expand Down Expand Up @@ -98,8 +97,18 @@ class NotificationAdapter(private val notifications: List<Notification>) :
time.setTextColor(neutralColor)
}
}
}
}

companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Notification>() {
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
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down