From 02adbd03c52fa8f8f8b36c1bbfbe0cd62962f9a7 Mon Sep 17 00:00:00 2001 From: kim0hoon Date: Mon, 21 Aug 2023 00:16:59 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=EB=8D=94=20=EB=B6=88=EB=9F=AC?= =?UTF-8?q?=EC=98=A4=EA=B8=B0=20=EA=B5=AC=ED=98=84=20https://github.com/PO?= =?UTF-8?q?LZZAK/POLZZAK-Android/issues/68?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/item/LoadMoreLoadingSpinnerItem.kt | 49 +++++++++++++++++++ .../NotificationItemDecoration.kt | 19 +++++-- .../notification/NotificationViewModel.kt | 9 +++- .../base/BaseNotificationFragment.kt | 31 ++++++------ .../notification/item/NotificationItem.kt | 3 +- .../notification/model/NotificationModel.kt | 6 +++ .../res/layout/item_load_more_loading.xml | 19 +++++++ 7 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/polzzak_android/presentation/common/item/LoadMoreLoadingSpinnerItem.kt create mode 100644 app/src/main/res/layout/item_load_more_loading.xml diff --git a/app/src/main/java/com/polzzak_android/presentation/common/item/LoadMoreLoadingSpinnerItem.kt b/app/src/main/java/com/polzzak_android/presentation/common/item/LoadMoreLoadingSpinnerItem.kt new file mode 100644 index 00000000..c762a448 --- /dev/null +++ b/app/src/main/java/com/polzzak_android/presentation/common/item/LoadMoreLoadingSpinnerItem.kt @@ -0,0 +1,49 @@ +package com.polzzak_android.presentation.common.item + +import android.view.animation.Animation +import android.view.animation.LinearInterpolator +import android.view.animation.RotateAnimation +import androidx.constraintlayout.widget.ConstraintLayout +import com.polzzak_android.R +import com.polzzak_android.databinding.ItemLoadMoreLoadingBinding +import com.polzzak_android.presentation.common.util.BindableItem +import com.polzzak_android.presentation.common.util.toPx + +class LoadMoreLoadingSpinnerItem( + private val marginTopDp: Int = DEFAULT_VERTICAL_MARGIN_DP, + private val marginBottomDp: Int = DEFAULT_VERTICAL_MARGIN_DP +) : + BindableItem() { + override val layoutRes: Int = R.layout.item_load_more_loading + + override fun areItemsTheSame(other: BindableItem<*>): Boolean = + other is LoadMoreLoadingSpinnerItem + + override fun areContentsTheSame(other: BindableItem<*>): Boolean = + other is LoadMoreLoadingSpinnerItem && this.marginTopDp == other.marginTopDp && + this.marginBottomDp == other.marginBottomDp + + override fun bind(binding: ItemLoadMoreLoadingBinding, position: Int) { + val context = binding.root.context + (binding.ivSpinner.layoutParams as? ConstraintLayout.LayoutParams)?.let { lp -> + binding.ivSpinner.layoutParams = lp.apply { + topMargin = marginTopDp.toPx(context) + bottomMargin = marginBottomDp.toPx(context) + } + } + val rotateAnimation = RotateAnimation( + 0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, + Animation.RELATIVE_TO_SELF, 0.5f + ).apply { + duration = 1000 + interpolator = LinearInterpolator() + repeatCount = Animation.INFINITE + } + binding.ivSpinner.startAnimation(rotateAnimation) + + } + + companion object { + private const val DEFAULT_VERTICAL_MARGIN_DP = 24 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/polzzak_android/presentation/feature/notification/NotificationItemDecoration.kt b/app/src/main/java/com/polzzak_android/presentation/feature/notification/NotificationItemDecoration.kt index 3e4c6bb5..b8548fb2 100644 --- a/app/src/main/java/com/polzzak_android/presentation/feature/notification/NotificationItemDecoration.kt +++ b/app/src/main/java/com/polzzak_android/presentation/feature/notification/NotificationItemDecoration.kt @@ -5,11 +5,13 @@ import android.view.View import androidx.annotation.Px import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ItemDecoration +import com.polzzak_android.presentation.common.util.BindableItemAdapter +import com.polzzak_android.presentation.feature.notification.item.NotificationItem +import com.polzzak_android.presentation.feature.notification.item.NotificationSkeletonLoadingItem class NotificationItemDecoration( @Px private val paddingPx: Int, @Px private val betweenMarginPx: Int, - private val offset: Int = 0 ) : ItemDecoration() { override fun getItemOffsets( outRect: Rect, @@ -18,11 +20,20 @@ class NotificationItemDecoration( state: RecyclerView.State ) { super.getItemOffsets(outRect, view, parent, state) + val adapter = (parent.adapter as? BindableItemAdapter) ?: return val position = parent.getChildAdapterPosition(view) - if (position < offset) return - outRect.top = if (position == offset) paddingPx else 0 + val currentItem = adapter.currentList.getOrNull(position) + if (!isContentItem(currentItem)) return + val prevItem = adapter.currentList.getOrNull(position - 1) + val nextItem = adapter.currentList.getOrNull(position + 1) + outRect.top = + if (isContentItem(prevItem)) 0 else paddingPx outRect.left = paddingPx outRect.right = paddingPx - outRect.bottom = if (position == parent.childCount - 1) paddingPx else betweenMarginPx + outRect.bottom = + if (isContentItem(nextItem)) betweenMarginPx else paddingPx } + + private fun isContentItem(item: Any?): Boolean = + item is NotificationItem || item is NotificationSkeletonLoadingItem } \ No newline at end of file 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 268badb5..b80da5ea 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 @@ -78,7 +78,8 @@ class NotificationViewModel @Inject constructor() : ViewModel() { //TODO test용 delay를 위해 suspend 붙여줌(제거 필요) private suspend fun requestNotifications() { - val prevData = notificationLiveData.value?.data ?: NotificationsModel() + val prevData = + notificationLiveData.value?.data.takeIf { !isRefreshed } ?: NotificationsModel() //TODO api 연동(현재 mock data) //onSuccess delay(2000) @@ -106,7 +107,7 @@ class NotificationViewModel @Inject constructor() : ViewModel() { private fun getMockData(nextOffset: Int, pageSize: Int): NotificationsModel { return NotificationsModel( - hasNextPage = nextOffset + pageSize >= mockNotification.size, + hasNextPage = nextOffset + pageSize < mockNotification.size, nextOffset = nextOffset + pageSize, items = mockNotification.subList( nextOffset, @@ -118,6 +119,7 @@ private fun getMockData(nextOffset: Int, pageSize: Int): NotificationsModel { private val mockNotification = List(187) { when (it % 4) { 0 -> NotificationModel.CompleteLink( + id = it, date = "${it}일 전", content = SpannableString("연동 완료"), nickName = "닉네임${it}", @@ -125,16 +127,19 @@ private val mockNotification = List(187) { ) 1 -> NotificationModel.LevelDown( + id = it, date = "${it}일 전", content = SpannableString("레벨 감소"), ) 2 -> NotificationModel.LevelUp( + id = it, date = "${it}일 전", content = SpannableString("레벨 업") ) else -> NotificationModel.RequestLink( + id = it, date = "${it}일 전", content = SpannableString("연동 요청"), nickName = "닉네임${it}", diff --git a/app/src/main/java/com/polzzak_android/presentation/feature/notification/base/BaseNotificationFragment.kt b/app/src/main/java/com/polzzak_android/presentation/feature/notification/base/BaseNotificationFragment.kt index 90c51209..78ca3c5c 100644 --- a/app/src/main/java/com/polzzak_android/presentation/feature/notification/base/BaseNotificationFragment.kt +++ b/app/src/main/java/com/polzzak_android/presentation/feature/notification/base/BaseNotificationFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.polzzak_android.R import com.polzzak_android.databinding.FragmentNotificationBinding import com.polzzak_android.presentation.common.base.BaseFragment +import com.polzzak_android.presentation.common.item.LoadMoreLoadingSpinnerItem import com.polzzak_android.presentation.common.model.ModelState import com.polzzak_android.presentation.common.util.BindableItem import com.polzzak_android.presentation.common.util.BindableItemAdapter @@ -53,8 +54,7 @@ abstract class BaseNotificationFragment : BaseFragment { if (it.data?.items == null) { items.addAll(createSkeletonLoadingItems()) - } else if (it.data?.items?.isEmpty() == true) { - items.add(NotificationEmptyItem()) - } else items.addAll( - createNotificationItems( - data = it.data?.items ?: emptyList() + } else { + items.addAll(createNotificationItems(data = it.data?.items)) + if (!notificationViewModel.isRefreshed) items.add( + LoadMoreLoadingSpinnerItem( + marginTopDp = 8 + ) ) - ) + } } is ModelState.Success -> { - if (it.data.items.isNullOrEmpty()) { - items.add(NotificationEmptyItem()) - } else { - items.addAll(createNotificationItems(data = it.data.items)) - } + items.addAll(createNotificationItems(data = it.data.items)) if (notificationViewModel.isRefreshed) { updateCallback = { layoutManager.scrollToPositionWithOffset(1, 0) @@ -151,8 +148,12 @@ abstract class BaseNotificationFragment : BaseFragment): List { - return data.map { NotificationItem(model = it) } + private fun createNotificationItems(data: List?): List> { + return if (data.isNullOrEmpty()) listOf(NotificationEmptyItem()) else data.map { + NotificationItem( + model = it + ) + } } companion object { diff --git a/app/src/main/java/com/polzzak_android/presentation/feature/notification/item/NotificationItem.kt b/app/src/main/java/com/polzzak_android/presentation/feature/notification/item/NotificationItem.kt index 86053dcf..8dcc7740 100644 --- a/app/src/main/java/com/polzzak_android/presentation/feature/notification/item/NotificationItem.kt +++ b/app/src/main/java/com/polzzak_android/presentation/feature/notification/item/NotificationItem.kt @@ -10,7 +10,8 @@ import com.polzzak_android.presentation.feature.notification.model.NotificationM class NotificationItem(private val model: NotificationModel) : BindableItem() { override val layoutRes: Int = R.layout.item_notification - override fun areItemsTheSame(other: BindableItem<*>): Boolean = other is NotificationItem + override fun areItemsTheSame(other: BindableItem<*>): Boolean = + other is NotificationItem && this.model.id == other.model.id override fun areContentsTheSame(other: BindableItem<*>): Boolean = other is NotificationItem && this.model == other.model diff --git a/app/src/main/java/com/polzzak_android/presentation/feature/notification/model/NotificationModel.kt b/app/src/main/java/com/polzzak_android/presentation/feature/notification/model/NotificationModel.kt index 52e39e88..31fed89b 100644 --- a/app/src/main/java/com/polzzak_android/presentation/feature/notification/model/NotificationModel.kt +++ b/app/src/main/java/com/polzzak_android/presentation/feature/notification/model/NotificationModel.kt @@ -6,6 +6,8 @@ import com.polzzak_android.R //TODO api response 확인 후 알림타입추가 sealed interface NotificationModel { + val id: Int + @get:StringRes val emojiStringRes: Int @@ -22,6 +24,7 @@ sealed interface NotificationModel { ) class RequestLink( + override val id: Int, override val date: String, override val content: Spannable, nickName: String, @@ -35,6 +38,7 @@ sealed interface NotificationModel { } class CompleteLink( + override val id: Int, override val date: String, override val content: Spannable, nickName: String, @@ -48,6 +52,7 @@ sealed interface NotificationModel { } class LevelUp( + override val id: Int, override val date: String, override val content: Spannable ) : NotificationModel { @@ -58,6 +63,7 @@ sealed interface NotificationModel { } class LevelDown( + override val id: Int, override val date: String, override val content: Spannable ) : NotificationModel { diff --git a/app/src/main/res/layout/item_load_more_loading.xml b/app/src/main/res/layout/item_load_more_loading.xml new file mode 100644 index 00000000..cc75d2b9 --- /dev/null +++ b/app/src/main/res/layout/item_load_more_loading.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file