Skip to content

Commit

Permalink
[FEAT] 알림 아이템, mock data 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
kim0hoon committed Aug 10, 2023
1 parent 2720423 commit f28827e
Show file tree
Hide file tree
Showing 12 changed files with 371 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.polzzak_android.presentation.feature.notification

import android.graphics.Rect
import android.view.View
import androidx.annotation.Px
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration

class NotificationItemDecoration(
@Px private val paddingPx: Int,
@Px private val betweenMarginPx: Int
) : ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
val position = parent.getChildAdapterPosition(view)
outRect.top = if (position == 0) paddingPx else 0
outRect.left = paddingPx
outRect.right = paddingPx
outRect.bottom = if (position == parent.childCount - 1) paddingPx else betweenMarginPx
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.polzzak_android.presentation.feature.notification

import android.text.SpannableString
import androidx.lifecycle.LiveData
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.feature.notification.model.NotificationModel
import com.polzzak_android.presentation.feature.notification.model.NotificationsModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class NotificationViewModel @Inject constructor() : ViewModel() {
private val _notificationLiveData = MutableLiveData<ModelState<NotificationsModel>>()
val notificationLiveData: LiveData<ModelState<NotificationsModel>> = _notificationLiveData
private var requestNotificationJob: Job? = null

init {
requestNotifications(hasPriority = true)
}

fun requestMoreNotifications() {
requestNotifications(hasPriority = false)
}

private fun requestNotifications(hasPriority: Boolean) {
if (hasPriority) requestNotificationJob?.cancel()
else if (requestNotificationJob?.isCompleted == false) return
requestNotificationJob = viewModelScope.launch {
val prevData = notificationLiveData.value?.data ?: NotificationsModel()
_notificationLiveData.value = ModelState.Loading(prevData)
//TODO api 호출
delay(2000)
val nextData = getMockData(prevData.nextOffset, NOTIFICATION_PAGE_SIZE)
_notificationLiveData.value =
ModelState.Success(nextData.copy(items = prevData.items + nextData.items))
}

}

companion object {
const val NOTIFICATION_PAGE_SIZE = 10
}
}

private fun getMockData(nextOffset: Int, pageSize: Int): NotificationsModel {
return NotificationsModel(
hasNextPage = nextOffset + pageSize >= mockNotification.size,
nextOffset = nextOffset + pageSize,
items = mockNotification.subList(
nextOffset,
minOf(mockNotification.size, nextOffset + pageSize)
)
)
}

private val mockNotification = List(187) {
when (it % 4) {
0 -> NotificationModel.CompleteLink(
date = "${it}일 전",
content = SpannableString("연동 완료"),
nickName = "닉네임${it}",
profileImageUrl = "https://picsum.photos/id/${it + 1}/200/300"
)

1 -> NotificationModel.LevelDown(
date = "${it}일 전",
content = SpannableString("레벨 감소"),
)

2 -> NotificationModel.LevelUp(
date = "${it}일 전",
content = SpannableString("레벨 업")
)

else -> NotificationModel.RequestLink(
date = "${it}일 전",
content = SpannableString("연동 요청"),
nickName = "닉네임${it}",
profileImageUrl = "https://picsum.photos/id/${it + 1}/200/300"
)
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,90 @@
package com.polzzak_android.presentation.feature.notification.base

import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
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.model.ModelState
import com.polzzak_android.presentation.common.util.BindableItem
import com.polzzak_android.presentation.common.util.BindableItemAdapter
import com.polzzak_android.presentation.common.util.toPx
import com.polzzak_android.presentation.feature.notification.NotificationItemDecoration
import com.polzzak_android.presentation.feature.notification.NotificationViewModel
import com.polzzak_android.presentation.feature.notification.item.NotificationItem
import com.polzzak_android.presentation.feature.notification.item.NotificationSkeletonLoadingItem
import com.polzzak_android.presentation.feature.notification.model.NotificationModel

abstract class BaseNotificationFragment : BaseFragment<FragmentNotificationBinding>() {
override val layoutResId: Int = R.layout.fragment_notification
private val notificationViewModel by viewModels<NotificationViewModel>()

override fun initView() {
super.initView()
initRecyclerView()
//TODO refresh
binding.ivBtnSetting.setOnClickListener {
//TODO move to setting page
}
}

private fun initRecyclerView() {
with(binding.rvNotifications) {
layoutManager = LinearLayoutManager(context)
val paddingPx = NOTIFICATIONS_PADDING_DP.toPx(context)
val betweenMarginPx = NOTIFICATIONS_BETWEEN_MARGIN_DP.toPx(context)
val itemDecoration = NotificationItemDecoration(
paddingPx = paddingPx,
betweenMarginPx = betweenMarginPx
)
addItemDecoration(itemDecoration)
adapter = BindableItemAdapter()
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
//TODO 무한스크롤
}
})
}
}

override fun initObserver() {
super.initObserver()
notificationViewModel.notificationLiveData.observe(viewLifecycleOwner) {
val items = mutableListOf<BindableItem<*>>()
val adapter =
(binding.rvNotifications.adapter as? BindableItemAdapter) ?: return@observe
when (it) {
is ModelState.Loading -> {
if (it.data?.items.isNullOrEmpty()) items.addAll(createSkeletonLoadingItems())
else items.addAll(createNotificationItems(data = it.data?.items ?: emptyList()))
}

is ModelState.Success -> {
//TODO 비어있는경우 대응
items.addAll(createNotificationItems(data = it.data.items))
}

is ModelState.Error -> {

}
}
adapter.updateItem(item = items)
}
}

private fun createSkeletonLoadingItems() = List(LOADING_SKELETON_ITEM_COUNT) {
NotificationSkeletonLoadingItem()
}

private fun createNotificationItems(data: List<NotificationModel>): List<NotificationItem> {
return data.map { NotificationItem(model = it) }
}

companion object {
private const val LOADING_SKELETON_ITEM_COUNT = 5
private const val NOTIFICATIONS_PADDING_DP = 16
private const val NOTIFICATIONS_BETWEEN_MARGIN_DP = 8
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.polzzak_android.presentation.feature.notification.item

import androidx.core.view.isVisible
import com.polzzak_android.R
import com.polzzak_android.databinding.ItemNotificationBinding
import com.polzzak_android.presentation.common.util.BindableItem
import com.polzzak_android.presentation.common.util.loadCircleImageUrl
import com.polzzak_android.presentation.feature.notification.model.NotificationModel

class NotificationItem(private val model: NotificationModel) :
BindableItem<ItemNotificationBinding>() {
override val layoutRes: Int = R.layout.item_notification
override fun areItemsTheSame(other: BindableItem<*>): Boolean = other is NotificationItem

override fun areContentsTheSame(other: BindableItem<*>): Boolean =
other is NotificationItem && this.model == other.model

override fun bind(binding: ItemNotificationBinding, position: Int) {
with(binding) {
val context = root.context
tvEmoji.text = context.getString(model.emojiStringRes)
tvTitle.text = context.getString(model.titleStringRes)
tvDate.text = model.date
tvContent.text = model.content
bindBtnLayout(binding = binding)
bindProfile(binding = binding)
}
}

private fun bindBtnLayout(binding: ItemNotificationBinding) {
with(binding) {
clBtnLayout.isVisible = model.isButtonVisible
tvBtnAccept.setOnClickListener {
//TODO 수락 버튼 클릭
}
tvBtnReject.setOnClickListener {
//TODO 거절 버튼 클릭
}
}

}

private fun bindProfile(binding: ItemNotificationBinding) {
with(binding) {
clProfile.isVisible = (model.userInfo != null)
ivProfileImage.loadCircleImageUrl(imageUrl = model.userInfo?.profileImageUrl)
tvNickName.text = model.userInfo?.nickName
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.polzzak_android.presentation.feature.notification.item

import com.polzzak_android.R
import com.polzzak_android.databinding.ItemNotificationLoadingSkeletonBinding
import com.polzzak_android.presentation.common.util.BindableItem

class NotificationSkeletonLoadingItem : BindableItem<ItemNotificationLoadingSkeletonBinding>() {
override val layoutRes: Int = R.layout.item_notification_loading_skeleton
override fun areItemsTheSame(other: BindableItem<*>): Boolean =
other is NotificationSkeletonLoadingItem

override fun areContentsTheSame(other: BindableItem<*>): Boolean =
other is NotificationSkeletonLoadingItem

override fun bind(binding: ItemNotificationLoadingSkeletonBinding, position: Int) {
//do nothing
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.polzzak_android.presentation.feature.notification.kid

import com.polzzak_android.R
import com.polzzak_android.presentation.common.base.BaseFragment
import com.polzzak_android.databinding.FragmentKidNotificationBinding
import com.polzzak_android.presentation.feature.notification.base.BaseNotificationFragment

class KidNotificationFragment : BaseFragment<FragmentKidNotificationBinding>() {
override val layoutResId: Int = R.layout.fragment_kid_notification
class KidNotificationFragment:BaseNotificationFragment(){

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.polzzak_android.presentation.feature.notification.model

import android.text.Spannable
import androidx.annotation.StringRes
import com.polzzak_android.R

//TODO api response 확인 후 알림타입추가
sealed interface NotificationModel {
@get:StringRes
val emojiStringRes: Int

@get:StringRes
val titleStringRes: Int
val date: String
val content: Spannable
val isButtonVisible: Boolean
val userInfo: NotificationUserModel?

data class NotificationUserModel(
val profileImageUrl: String,
val nickName: String
)

class RequestLink(
override val date: String,
override val content: Spannable,
nickName: String,
profileImageUrl: String,
) : NotificationModel {
override val emojiStringRes: Int = R.string.notification_request_link_emoji
override val titleStringRes: Int = R.string.notification_request_link_title
override val isButtonVisible = true
override val userInfo =
NotificationUserModel(profileImageUrl = profileImageUrl, nickName = nickName)
}

class CompleteLink(
override val date: String,
override val content: Spannable,
nickName: String,
profileImageUrl: String,
) : NotificationModel {
override val emojiStringRes: Int = R.string.notification_complete_link_emoji
override val titleStringRes: Int = R.string.notification_complete_link_title
override val isButtonVisible = false
override val userInfo =
NotificationUserModel(profileImageUrl = profileImageUrl, nickName = nickName)
}

class LevelUp(
override val date: String,
override val content: Spannable
) : NotificationModel {
override val emojiStringRes: Int = R.string.notification_level_up_emoji
override val titleStringRes: Int = R.string.notification_level_up_title
override val isButtonVisible = false
override val userInfo = null
}

class LevelDown(
override val date: String,
override val content: Spannable
) : NotificationModel {
override val emojiStringRes: Int = R.string.notification_level_down_emoji
override val titleStringRes: Int = R.string.notification_level_down_title
override val isButtonVisible = false
override val userInfo = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.polzzak_android.presentation.feature.notification.model

//TODO API 모델 확인 후 수정 필요
data class NotificationsModel(
val hasNextPage: Boolean = true,
val nextOffset: Int = 0,
val items: List<NotificationModel> = emptyList()
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.polzzak_android.presentation.feature.notification.protector

import com.polzzak_android.R
import com.polzzak_android.presentation.common.base.BaseFragment
import com.polzzak_android.databinding.FragmentProtectorNotificationBinding
import com.polzzak_android.presentation.feature.notification.base.BaseNotificationFragment

class ProtectorNotificationFragment : BaseNotificationFragment() {

class ProtectorNotificationFragment : BaseFragment<FragmentProtectorNotificationBinding>() {
override val layoutResId: Int = R.layout.fragment_protector_notification
}
Loading

0 comments on commit f28827e

Please sign in to comment.