diff --git a/app/src/main/java/com/polzzak_android/presentation/feature/notification/NotificationViewModel.kt b/app/src/main/java/com/polzzak_android/presentation/feature/notification/NotificationViewModel.kt index ad180751..113b7926 100644 --- a/app/src/main/java/com/polzzak_android/presentation/feature/notification/NotificationViewModel.kt +++ b/app/src/main/java/com/polzzak_android/presentation/feature/notification/NotificationViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.polzzak_android.presentation.common.model.ModelState +import com.polzzak_android.presentation.common.model.copyWithData import com.polzzak_android.presentation.feature.notification.list.NotificationItemStateController import com.polzzak_android.presentation.feature.notification.list.model.NotificationModel import com.polzzak_android.presentation.feature.notification.list.model.NotificationRefreshStatusType @@ -16,6 +17,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex import javax.inject.Inject @HiltViewModel @@ -24,6 +26,9 @@ class NotificationViewModel @Inject constructor() : ViewModel(), NotificationIte val notificationLiveData: LiveData> = _notificationLiveData private var requestNotificationJobData: NotificationJobData? = null + //TODO 추가 삭제 등 알림목록 수정 이벤트 + private var updateNotificationJobMap = HashMap() + var isRefreshed = false private set @@ -33,6 +38,8 @@ class NotificationViewModel @Inject constructor() : ViewModel(), NotificationIte val settingMenusLiveData: LiveData> = _settingMenusLiveData private var requestSettingMenusJob: Job? = null + private val notificationMutex = Mutex() + init { initNotifications() } @@ -43,9 +50,11 @@ class NotificationViewModel @Inject constructor() : ViewModel(), NotificationIte else if (requestNotificationJobData?.job?.isCompleted == false) return requestNotificationJobData = NotificationJobData( priority = priority, - job = viewModelScope.launch { + job = createJobWithUnlockOnCompleted { isRefreshed = true + notificationMutex.lock() _notificationLiveData.value = ModelState.Loading(NotificationsModel()) + notificationMutex.unlock() requestNotifications() }, ) @@ -57,11 +66,13 @@ class NotificationViewModel @Inject constructor() : ViewModel(), NotificationIte else if (requestNotificationJobData?.job?.isCompleted == false) return requestNotificationJobData = NotificationJobData( priority = priority, - job = viewModelScope.launch { + job = createJobWithUnlockOnCompleted { isRefreshed = true + notificationMutex.lock() val prevData = notificationLiveData.value?.data ?: NotificationsModel() _notificationLiveData.value = ModelState.Loading(prevData.copy(refreshStatusType = NotificationRefreshStatusType.Loading)) + notificationMutex.unlock() requestNotifications() }, ) @@ -74,11 +85,13 @@ class NotificationViewModel @Inject constructor() : ViewModel(), NotificationIte else if (requestNotificationJobData?.job?.isCompleted == false) return requestNotificationJobData = NotificationJobData( priority = priority, - job = viewModelScope.launch { + job = createJobWithUnlockOnCompleted { isRefreshed = false + notificationMutex.lock() val prevData = notificationLiveData.value?.data ?: NotificationsModel() _notificationLiveData.value = ModelState.Loading(prevData.copy(refreshStatusType = NotificationRefreshStatusType.Normal)) + notificationMutex.unlock() requestNotifications() }, ) @@ -86,12 +99,15 @@ class NotificationViewModel @Inject constructor() : ViewModel(), NotificationIte //TODO test용 delay를 위해 suspend 붙여줌(제거 필요) private suspend fun requestNotifications() { - val prevData = - notificationLiveData.value?.data.takeIf { !isRefreshed } ?: NotificationsModel() //TODO api 연동(현재 mock data) - //onSuccess + val nextOffset = notificationLiveData.value?.data?.nextOffset.takeIf { !isRefreshed } ?: 0 delay(2000) - val nextData = getMockData(prevData.nextOffset, NOTIFICATION_PAGE_SIZE) + val nextData = getMockNotificationData(nextOffset, NOTIFICATION_PAGE_SIZE) + + //onSuccess + notificationMutex.lock() + val prevData = + notificationLiveData.value?.data.takeIf { !isRefreshed } ?: NotificationsModel() if (isRefreshed) notificationHorizontalScrollPositionMap.clear() _notificationLiveData.value = ModelState.Success( @@ -100,10 +116,44 @@ class NotificationViewModel @Inject constructor() : ViewModel(), NotificationIte refreshStatusType = NotificationRefreshStatusType.Normal ) ) + notificationMutex.unlock() } - fun deleteNotification() { - //TODO 알림 삭제 문의 중 + fun deleteNotification(id: Int) { + if (updateNotificationJobMap[id]?.isCompleted == false) return + updateNotificationJobMap[id] = createJobWithUnlockOnCompleted { + //TODO api 적용 + delay(1000) + deleteMockNotificationData(id = id) + + //onSuccess + notificationMutex.lock() + val updatedList = notificationLiveData.value?.data?.items?.toMutableList()?.apply { + removeIf { it.id == id } + } + val updatedData = + notificationLiveData.value?.data?.copy(items = updatedList) ?: NotificationsModel() + _notificationLiveData.value = + _notificationLiveData.value?.copyWithData(newData = updatedData) + notificationMutex.unlock() + //TODO onError 이벤트 추가(Livedata, eventWrapper 등 필요할 수도 있음) + } + } + + fun addNotification(model: NotificationModel) { + if (updateNotificationJobMap[model.id]?.isCompleted == false) return + updateNotificationJobMap[model.id] = createJobWithUnlockOnCompleted { + //TODO 푸쉬알림으로 인한 알림 목록 추가 + + } + } + + private fun createJobWithUnlockOnCompleted(action: suspend () -> Unit) = viewModelScope.launch { + action.invoke() + }.apply { + invokeOnCompletion { + if (notificationMutex.isLocked) notificationMutex.unlock() + } } fun requestSettingMenu() { @@ -136,7 +186,7 @@ class NotificationViewModel @Inject constructor() : ViewModel(), NotificationIte } } -private fun getMockData(nextOffset: Int, pageSize: Int): NotificationsModel { +private fun getMockNotificationData(nextOffset: Int, pageSize: Int): NotificationsModel { return NotificationsModel( hasNextPage = nextOffset + pageSize < mockNotification.size, nextOffset = nextOffset + pageSize, @@ -147,7 +197,11 @@ private fun getMockData(nextOffset: Int, pageSize: Int): NotificationsModel { ) } -private val mockNotification = List(187) { +private fun deleteMockNotificationData(id: Int) { + mockNotification.removeIf { it.id == id } +} + +private val mockNotification = MutableList(187) { when (it % 4) { 0 -> NotificationModel.CompleteLink( id = it, @@ -179,7 +233,7 @@ private val mockNotification = List(187) { } } -private val mockSettingMenus = listOf( +private val mockSettingMenus = mutableListOf( SettingMenuModel(type = SettingMenuType.All, isChecked = false), SettingMenuModel(type = SettingMenuType.Menu.Link, isChecked = false), SettingMenuModel(type = SettingMenuType.Menu.Level, isChecked = false), diff --git a/app/src/main/java/com/polzzak_android/presentation/feature/notification/list/NotificationListClickListener.kt b/app/src/main/java/com/polzzak_android/presentation/feature/notification/list/NotificationListClickListener.kt new file mode 100644 index 00000000..f1ad9260 --- /dev/null +++ b/app/src/main/java/com/polzzak_android/presentation/feature/notification/list/NotificationListClickListener.kt @@ -0,0 +1,5 @@ +package com.polzzak_android.presentation.feature.notification.list + +interface NotificationListClickListener { + fun onClickDeleteNotification(id: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/polzzak_android/presentation/feature/notification/list/base/BaseNotificationListFragment.kt b/app/src/main/java/com/polzzak_android/presentation/feature/notification/list/base/BaseNotificationListFragment.kt index 8f83eeb7..85d92489 100644 --- a/app/src/main/java/com/polzzak_android/presentation/feature/notification/list/base/BaseNotificationListFragment.kt +++ b/app/src/main/java/com/polzzak_android/presentation/feature/notification/list/base/BaseNotificationListFragment.kt @@ -18,6 +18,7 @@ import com.polzzak_android.presentation.common.util.BindableItemAdapter import com.polzzak_android.presentation.common.util.toPx import com.polzzak_android.presentation.feature.notification.NotificationViewModel import com.polzzak_android.presentation.feature.notification.list.NotificationItemDecoration +import com.polzzak_android.presentation.feature.notification.list.NotificationListClickListener import com.polzzak_android.presentation.feature.notification.list.item.NotificationEmptyItem import com.polzzak_android.presentation.feature.notification.list.item.NotificationItem import com.polzzak_android.presentation.feature.notification.list.item.NotificationRefreshItem @@ -29,7 +30,8 @@ import dagger.hilt.android.AndroidEntryPoint //TODO 하단 네비게이션 바 만큼 marign 필요 @AndroidEntryPoint -abstract class BaseNotificationListFragment : BaseFragment() { +abstract class BaseNotificationListFragment : BaseFragment(), + NotificationListClickListener { override val layoutResId: Int = R.layout.fragment_notification_list private val notificationViewModel by viewModels(ownerProducer = { @@ -166,11 +168,16 @@ abstract class BaseNotificationListFragment : BaseFragment() { override val layoutRes: Int = R.layout.item_notification override fun areItemsTheSame(other: BindableItem<*>): Boolean = @@ -32,7 +34,7 @@ class NotificationItem( tvDate.text = model.date tvContent.text = model.content ivBtnRemoveNotification.setOnClickListener { - + clickListener.onClickDeleteNotification(id = model.id) } bindBtnLayout(binding = binding) bindProfile(binding = binding)