Skip to content

Commit

Permalink
[FEAT] 공지사항 화면 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
kim0hoon committed Sep 3, 2023
1 parent a1c2d12 commit f5dc4bd
Show file tree
Hide file tree
Showing 15 changed files with 406 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.polzzak_android.presentation.feature.myPage.kid

import androidx.navigation.fragment.findNavController
import com.polzzak_android.R
import com.polzzak_android.presentation.common.base.BaseFragment
import com.polzzak_android.databinding.FragmentKidMyPageBinding
Expand Down Expand Up @@ -73,7 +74,7 @@ class KidMyPageFragment : BaseFragment<FragmentKidMyPageBinding>(), ToolbarIconI
}

fun onClickNotice() {
// todo: 공지사항 클릭
findNavController().navigate(R.id.action_kidMyPageFragment_to_myNoticeFragment)
}

fun onClickManageAccount() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.polzzak_android.presentation.feature.myPage.notice

import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.polzzak_android.R
import com.polzzak_android.databinding.FragmentMyNoticeBinding
import com.polzzak_android.presentation.common.base.BaseFragment
import com.polzzak_android.presentation.common.item.FullLoadingItem
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
import com.polzzak_android.presentation.common.util.toPx
import com.polzzak_android.presentation.component.toolbar.ToolbarData
import com.polzzak_android.presentation.component.toolbar.ToolbarHelper
import com.polzzak_android.presentation.feature.myPage.notice.item.MyNoticeEmptyItem
import com.polzzak_android.presentation.feature.myPage.notice.item.MyNoticeItem
import com.polzzak_android.presentation.feature.myPage.notice.model.MyNoticesModel

//TODO 바텀네비 invisible
class MyNoticeFragment : BaseFragment<FragmentMyNoticeBinding>() {
override val layoutResId: Int = R.layout.fragment_my_notice

private val noticeViewModel by viewModels<MyNoticeViewModel>()

override fun initView() {
super.initView()
initToolbar()
initRecyclerView()
}

private fun initToolbar() {
with(binding) {
ToolbarHelper(
data = ToolbarData(
popStack = findNavController(),
titleText = getString(R.string.common_notice)
),
toolbar = inToolbar
).set()
}
}

private fun initRecyclerView() {
val context = binding.root.context ?: return
with(binding.rvNotices) {
layoutManager = LinearLayoutManager(context)
adapter = BindableItemAdapter()
val marginPx = ITEM_MARGIN_DP.toPx(context)
addItemDecoration(MyNoticeItemDecoration(marginPx = marginPx))
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (!recyclerView.canScrollVertically(1) && noticeViewModel.noticesLiveData.value is ModelState.Success) noticeViewModel.requestMoreNotices()
}
})
}
}

override fun initObserver() {
super.initObserver()
noticeViewModel.noticesLiveData.observe(viewLifecycleOwner) {
val adapter = (binding.rvNotices.adapter as? BindableItemAdapter) ?: return@observe
val noticeItem = createNoticeItem(it.data ?: MyNoticesModel())
val items = mutableListOf<BindableItem<*>>()
when (it) {
is ModelState.Loading -> {
if (it.data?.notices.isNullOrEmpty()) items.add(FullLoadingItem())
else items.addAll(noticeItem + LoadMoreLoadingSpinnerItem())
}

is ModelState.Success -> items.addAll(noticeItem)
is ModelState.Error -> {
//TODO error handling
}
}
adapter.updateItem(item = items)
}
}

private fun createNoticeItem(model: MyNoticesModel) = model.notices.map {
MyNoticeItem(model = it)
}.ifEmpty { listOf(MyNoticeEmptyItem()) }

companion object {
private const val ITEM_MARGIN_DP = 16
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.polzzak_android.presentation.feature.myPage.notice

import android.graphics.Rect
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.myPage.notice.item.MyNoticeItem

class MyNoticeItemDecoration(@Px private val marginPx: 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)
val adapter = (parent.adapter as? BindableItemAdapter) ?: return
if (adapter.currentList.getOrNull(position) !is MyNoticeItem) return
outRect.top = marginPx
outRect.bottom = if (position == adapter.currentList.lastIndex) marginPx else 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.polzzak_android.presentation.feature.myPage.notice

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.common.util.toLocalDate
import com.polzzak_android.presentation.feature.myPage.notice.model.MyNoticeModel
import com.polzzak_android.presentation.feature.myPage.notice.model.MyNoticesModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.LocalDate

//TODO api 연동
class MyNoticeViewModel : ViewModel() {
private val _noticesLiveData = MutableLiveData<ModelState<MyNoticesModel>>()
val noticesLiveData: LiveData<ModelState<MyNoticesModel>> = _noticesLiveData

private var requestNoticesJob: Job? = null

init {
initNotices()
}

private fun initNotices() {
requestNoticesJob?.cancel()
requestNoticesJob = viewModelScope.launch {
_noticesLiveData.value = ModelState.Loading(MyNoticesModel())
requestNotices()
}
}

fun requestMoreNotices() {
if (noticesLiveData.value?.data?.hasNextPage == false) return
if (requestNoticesJob?.isCompleted == false) return
requestNoticesJob = viewModelScope.launch {
_noticesLiveData.value =
ModelState.Loading(_noticesLiveData.value?.data ?: MyNoticesModel())
requestNotices()
}
}

private suspend fun requestNotices() {
val prevData = noticesLiveData.value?.data ?: MyNoticesModel()
delay(2000)
//TODO api 연동
//on Success
val nextData = getMockNotices(nextId = prevData.nextId, pageSize = PAGE_SIZE)
val updatedData = nextData.copy(notices = prevData.notices + nextData.notices)
_noticesLiveData.value = ModelState.Success(updatedData)
}

companion object {
private const val PAGE_SIZE = 10
}
}

private fun getMockNotices(nextId: Int?, pageSize: Int): MyNoticesModel {
val startIdx = nextId ?: 0
val nextIdx = minOf(mockNotices.size, startIdx + pageSize)
val nId = mockNotices.getOrNull(nextIdx)?.id
return MyNoticesModel(
notices = mockNotices.subList(startIdx, nextIdx),
nextId = nId,
hasNextPage = (nId != null)
)
}

private val mockNotices = List(27) {
MyNoticeModel(
id = it,
title = "title$it".repeat((it % 12) + 1),
date = "2023-06-04T20:08:23.745393551".toLocalDate() ?: (LocalDate.now()),
content = "content \n\n\n content1 \n\n content \n"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.polzzak_android.presentation.feature.myPage.notice.item

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

class MyNoticeEmptyItem : BindableItem<ItemMyNoticieEmptyBinding>() {
override val layoutRes: Int = R.layout.item_my_noticie_empty
override fun areItemsTheSame(other: BindableItem<*>): Boolean = other is MyNoticeEmptyItem

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

override fun bind(binding: ItemMyNoticieEmptyBinding, position: Int) {
//do nothing
}

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

import com.polzzak_android.R
import com.polzzak_android.databinding.ItemMyNoticeBinding
import com.polzzak_android.presentation.common.util.BindableItem
import com.polzzak_android.presentation.common.util.toDateString
import com.polzzak_android.presentation.feature.myPage.notice.model.MyNoticeModel

class MyNoticeItem(private val model: MyNoticeModel) : BindableItem<ItemMyNoticeBinding>() {
override val layoutRes: Int = R.layout.item_my_notice
override fun areItemsTheSame(other: BindableItem<*>): Boolean =
other is MyNoticeItem && this.model.id == other.model.id

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

override fun bind(binding: ItemMyNoticeBinding, position: Int) {
with(binding) {
tvTitle.text = model.title
tvDate.text = model.date.toDateString()
tvContent.text = model.content.replace(Regex("(\\r\\n|\\r|\\n)+"), " ")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.polzzak_android.presentation.feature.myPage.notice.model

import java.time.LocalDate

data class MyNoticeModel(
val id: Int,
val title: String,
val date: LocalDate,
val content: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.polzzak_android.presentation.feature.myPage.notice.model

data class MyNoticesModel(
val notices: List<MyNoticeModel> = emptyList(),
val nextId: Int? = null,
val hasNextPage: Boolean = true
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.polzzak_android.presentation.feature.myPage.protector

import androidx.navigation.fragment.findNavController
import com.polzzak_android.R
import com.polzzak_android.databinding.FragmentProtectorMyPageBinding
import com.polzzak_android.presentation.common.base.BaseFragment
Expand Down Expand Up @@ -32,7 +33,7 @@ class ProtectorMyPageFragment : BaseFragment<FragmentProtectorMyPageBinding>(),
setUpPointView()
}

private fun setUpPointView(){
private fun setUpPointView() {
with(binding.pointRanking) {
text = "폴짝 랭킹"
icon.setImageResource(R.drawable.ic_point_rank)
Expand Down Expand Up @@ -74,7 +75,8 @@ class ProtectorMyPageFragment : BaseFragment<FragmentProtectorMyPageBinding>(),
}

fun onClickNotice() {
// todo: 공지사항 클릭
findNavController().navigate(R.id.action_protectorMyPageFragment_to_myNoticeFragment)

}

fun onClickManageAccount() {
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/res/layout/fragment_my_notice.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<include
android:id="@+id/inToolbar"
layout="@layout/layout_toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvNotices"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/gray_200"
android:paddingHorizontal="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/inToolbar"
tools:itemCount="5"
tools:listitem="@layout/item_my_notice" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Loading

0 comments on commit f5dc4bd

Please sign in to comment.