Skip to content

Commit

Permalink
[AN/USER] feat: 축제 목록 화면 페이징 작업(#673) (#724)
Browse files Browse the repository at this point in the history
* feat(FakeFestivalRepository): 가짜 축제 저장소를 필터링 작업에 용이하게 변경

* feat(FestivalFilterUiState): FestivalFilterUiState 는 tabPosition 을 가진다

* feat(FestivalTabUiState): selected 람다에 포지션이 아닌 FestivalFilterUiState 를 넘긴다

* feat(FakeFestivalRepository): 가짜 축제 저장소는 예정된 축제를 반환할 수 있다

* feat(FestivalListViewModel): 같은 필터가 아니면 축제 목록을 변경한다

* feat(FestivalListMoreItemViewHolder): more item 프로그래스바 뷰 아이템 생성

* feat(FestivalListAdapter): 축제 목록에 더보기 아이템 추가

* feat(FestivalListAdapter): isLast 가 false 일때만 더보기를 추가한다.

* feat(FestivalListViewModel): 현재 축제 목록 불러오기 함수 분리

* feat(FestivalList): Success State recyclerView get items 로직 병합

* feat(FestivalListViewModel): 축제 목록 초기화와 축제 목록 추가 로드를 분리한다
  • Loading branch information
SeongHoonC authored Mar 6, 2024
1 parent 465a484 commit 0bcd8d7
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import javax.inject.Inject
class FakeFestivalRepository @Inject constructor() : FestivalRepository {

override suspend fun loadPopularFestivals(): Result<PopularFestivals> {
return Result.success(PopularFestivals("인기 축제 목록", FakeFestivals.popularFestivals))
return Result.success(
PopularFestivals(
title = "인기 축제 목록",
festivals = FakeFestivals.popularFestivals,
),
)
}

override suspend fun loadFestivals(
Expand All @@ -27,18 +32,24 @@ class FakeFestivalRepository @Inject constructor() : FestivalRepository {
val notNullSize = size ?: DEFAULT_SIZE
val notNullLastFestivalId = lastFestivalId ?: DEFAULT_LAST_FESTIVAL_ID

if (festivalFilter == FestivalFilter.PLANNED) {
return Result.success(
FestivalsPage(isLastPage = true, festivals = FakeFestivals.plannedFestivals),
)
}

if (notNullLastFestivalId + notNullSize < LAST_ITEM_ID) {
return Result.success(
FestivalsPage(
false,
getFestivals((notNullLastFestivalId + 1)..(notNullLastFestivalId + notNullSize)),
isLastPage = false,
festivals = getFestivals((notNullLastFestivalId + 1)..(notNullLastFestivalId + notNullSize)),
),
)
}
return Result.success(
FestivalsPage(
true,
getFestivals((notNullLastFestivalId + 1)..LAST_ITEM_ID),
isLastPage = true,
festivals = getFestivals((notNullLastFestivalId + 1)..LAST_ITEM_ID),
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ object FakeFestivals {

val plannedFestivals = listOf(
Festival(
id = 1,
id = 30,
name = "뉴진스 콘서트",
startDate = LocalDate.now().plusDays(1L),
endDate = LocalDate.MAX,
Expand All @@ -142,7 +142,7 @@ object FakeFestivals {
),
),
Festival(
id = 2,
id = 31,
name = "아이브 콘서트",
startDate = LocalDate.now().plusDays(3L),
endDate = LocalDate.MAX,
Expand Down Expand Up @@ -177,7 +177,7 @@ object FakeFestivals {
),
),
Festival(
id = 3,
id = 32,
name = "아이들 콘서트",
startDate = LocalDate.now().plusDays(5L),
endDate = LocalDate.MAX,
Expand All @@ -192,7 +192,7 @@ object FakeFestivals {
),
),
Festival(
id = 4,
id = 33,
name = "뉴진스 콘서트",
startDate = LocalDate.now().plusDays(20L),
endDate = LocalDate.MAX,
Expand All @@ -207,7 +207,7 @@ object FakeFestivals {
),
),
Festival(
id = 5,
id = 34,
name = "아이브 콘서트",
startDate = LocalDate.now().plusDays(40L),
endDate = LocalDate.MAX,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.festago.festago.presentation.R
import com.festago.festago.presentation.databinding.FragmentFestivalListBinding
import com.festago.festago.presentation.ui.artistdetail.ArtistDetailFragment
import com.festago.festago.presentation.ui.home.festivallist.festival.FestivalListAdapter
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalFilterUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalListUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalMoreItemUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalTabUiState
import com.festago.festago.presentation.ui.schooldetail.SchoolDetailFragment
import com.festago.festago.presentation.util.repeatOnStarted
Expand Down Expand Up @@ -66,15 +67,15 @@ class FestivalListFragment : Fragment() {
}

private fun initView() {
vm.loadFestivals()
vm.initFestivalList()
initViewPager()
initRecyclerView()
initRefresh()
}

private fun initRefresh() {
binding.srlFestivalList.setOnRefreshListener {
vm.loadFestivals()
vm.initFestivalList()
binding.srlFestivalList.isRefreshing = false
}
binding.ivSearch.setOnClickListener { // 임시 연결
Expand All @@ -97,6 +98,30 @@ class FestivalListFragment : Fragment() {
}

private fun initRecyclerView() {
initScrollEvent()
initDecoration()
}

private fun initScrollEvent() {
binding.rvFestivalList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)

val festivalListUiState = vm.uiState.value as? FestivalListUiState.Success ?: return
if (festivalListUiState.isLastPage) return

val lastVisibleItemPosition =
(recyclerView.layoutManager as LinearLayoutManager?)!!.findLastCompletelyVisibleItemPosition()

val itemTotalCount = recyclerView.adapter!!.itemCount - 1
if (lastVisibleItemPosition == itemTotalCount) {
vm.loadFestivals()
}
}
})
}

private fun initDecoration() {
binding.rvFestivalList.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
Expand Down Expand Up @@ -135,22 +160,14 @@ class FestivalListFragment : Fragment() {
}

private fun FestivalListUiState.Success.getItems(): List<Any> {
val items = mutableListOf<Any>()
if (popularFestivalUiState.festivals.isNotEmpty()) {
items.add(popularFestivalUiState)
}
items.add(
FestivalTabUiState {
val festivalFilter = when (it) {
0 -> FestivalFilterUiState.PROGRESS
1 -> FestivalFilterUiState.PLANNED
else -> FestivalFilterUiState.PROGRESS
}
vm.loadFestivals(festivalFilter)
},
)
items.addAll(festivals)
return items.toList()
return mutableListOf<Any>().apply {
if (popularFestivalUiState.festivals.isNotEmpty()) {
add(popularFestivalUiState)
}
add(FestivalTabUiState(festivalFilter) { vm.loadFestivals(it) })
addAll(festivals)
if (!isLastPage) add(FestivalMoreItemUiState)
}.toList()
}

private fun showSchoolDetail() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,12 @@ class FestivalListViewModel @Inject constructor(

private var festivalFilter: FestivalFilter = FestivalFilter.PROGRESS

fun loadFestivals(festivalFilterUiState: FestivalFilterUiState? = null) {
fun initFestivalList() {
viewModelScope.launch {
if (festivalFilterUiState != null) {
festivalFilter = festivalFilterUiState.toDomain()
}

val deferredPopularFestivals = async { festivalRepository.loadPopularFestivals() }
val deferredFestivals =
async { festivalRepository.loadFestivals(festivalFilter = festivalFilter) }

val deferredFestivals = async {
festivalRepository.loadFestivals(festivalFilter = festivalFilter)
}
runCatching {
val festivalsPage = deferredFestivals.await().getOrThrow()
val popularFestivals = deferredPopularFestivals.await().getOrThrow()
Expand All @@ -52,6 +48,7 @@ class FestivalListViewModel @Inject constructor(
festivals = popularFestivals.festivals.map { it.toUiState() },
),
festivals = festivalsPage.festivals.map { it.toUiState() },
festivalFilter = festivalFilter.toUiState(),
isLastPage = festivalsPage.isLastPage,
)
}.onFailure {
Expand All @@ -64,11 +61,51 @@ class FestivalListViewModel @Inject constructor(
}
}

fun loadFestivals(festivalFilterUiState: FestivalFilterUiState? = null) {
val successUiState = uiState.value as? FestivalListUiState.Success ?: return

viewModelScope.launch {
val currentFestivals = getCurrentFestivals(festivalFilterUiState)

festivalRepository.loadFestivals(
festivalFilter = festivalFilter,
lastFestivalId = currentFestivals.lastOrNull()?.id,
lastStartDate = currentFestivals.lastOrNull()?.startDate,
).onSuccess { festivalsPage ->
_uiState.value = FestivalListUiState.Success(
PopularFestivalUiState(
title = successUiState.popularFestivalUiState.title,
festivals = successUiState.popularFestivalUiState.festivals,
),
festivals = currentFestivals + festivalsPage.festivals.map { it.toUiState() },
festivalFilter = festivalFilter.toUiState(),
isLastPage = festivalsPage.isLastPage,
)
}
}
}

private fun getCurrentFestivals(festivalFilterUiState: FestivalFilterUiState?): List<FestivalItemUiState> {
var festivals = (uiState.value as? FestivalListUiState.Success)?.festivals ?: listOf()

if (festivalFilterUiState != null && festivalFilter != festivalFilterUiState.toDomain()) {
festivalFilter = festivalFilterUiState.toDomain()
festivals = listOf()
}
return festivals
}

private fun FestivalFilterUiState.toDomain() = when (this) {
FestivalFilterUiState.PLANNED -> FestivalFilter.PLANNED
FestivalFilterUiState.PROGRESS -> FestivalFilter.PROGRESS
}

private fun FestivalFilter.toUiState() = when (this) {
FestivalFilter.PLANNED -> FestivalFilterUiState.PLANNED
FestivalFilter.PROGRESS -> FestivalFilterUiState.PROGRESS
else -> FestivalFilterUiState.PLANNED
}

private fun Festival.toUiState() = FestivalItemUiState(
id = id,
name = name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalItemUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalMoreItemUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalTabUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.PopularFestivalUiState

Expand All @@ -16,6 +17,7 @@ class FestivalListAdapter(
1 -> FestivalListPopularViewHolder.of(parent)
2 -> FestivalListFestivalViewHolder.of(parent, onArtistClick)
3 -> FestivalListTabViewHolder.of(parent)
4 -> FestivalListMoreItemViewHolder.of(parent)
else -> throw IllegalArgumentException("Invalid viewType")
}
}
Expand All @@ -26,6 +28,7 @@ class FestivalListAdapter(
is FestivalListPopularViewHolder -> holder.bind(item as PopularFestivalUiState)
is FestivalListFestivalViewHolder -> holder.bind(item as FestivalItemUiState)
is FestivalListTabViewHolder -> holder.bind(item as FestivalTabUiState)
is FestivalListMoreItemViewHolder -> holder.bind(item as FestivalMoreItemUiState)
}
}

Expand All @@ -34,6 +37,7 @@ class FestivalListAdapter(
is PopularFestivalUiState -> 1
is FestivalItemUiState -> 2
is FestivalTabUiState -> 3
is FestivalMoreItemUiState -> 4
else -> throw IllegalArgumentException("Invalid item")
}
}
Expand All @@ -44,6 +48,7 @@ class FestivalListAdapter(
oldItem is PopularFestivalUiState && newItem is PopularFestivalUiState -> true
oldItem is FestivalItemUiState && newItem is FestivalItemUiState -> oldItem.id == newItem.id
oldItem is FestivalTabUiState && newItem is FestivalTabUiState -> true
oldItem is FestivalMoreItemUiState && newItem is FestivalMoreItemUiState -> true
else -> false
}

Expand All @@ -54,8 +59,9 @@ class FestivalListAdapter(
oldItem is FestivalItemUiState && newItem is FestivalItemUiState
-> oldItem as FestivalItemUiState == newItem

oldItem is FestivalTabUiState && newItem is FestivalTabUiState
-> true
oldItem is FestivalTabUiState && newItem is FestivalTabUiState -> true

oldItem is FestivalMoreItemUiState && newItem is FestivalMoreItemUiState -> true

else -> false
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.festago.festago.presentation.ui.home.festivallist.festival

import android.view.LayoutInflater
import android.view.ViewGroup
import com.festago.festago.presentation.databinding.ItemFestivalListMoreItemBinding
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalMoreItemUiState

class FestivalListMoreItemViewHolder(val binding: ItemFestivalListMoreItemBinding) :
FestivalListViewHolder(binding) {

fun bind(festivalMoreItemUiState: FestivalMoreItemUiState) {
}

companion object {
fun of(parent: ViewGroup): FestivalListMoreItemViewHolder {
val binding = ItemFestivalListMoreItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false,
)
return FestivalListMoreItemViewHolder(binding)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import com.festago.festago.presentation.R
import com.festago.festago.presentation.databinding.ItemFestivalListTabBinding
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalFilterUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalTabUiState
import com.google.android.material.tabs.TabLayout

Expand All @@ -23,19 +24,26 @@ class FestivalListTabViewHolder(val binding: ItemFestivalListTabBinding) :
}

fun bind(festivalTabUiState: FestivalTabUiState) {
binding.tlFestivalListTab.clearOnTabSelectedListeners()
binding.tlFestivalListTab.addOnTabSelectedListener(
object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
festivalTabUiState.onClick(tab.position)
}
with(binding.tlFestivalListTab) {
clearOnTabSelectedListeners()
selectTab(getTabAt(festivalTabUiState.selectedFilter.tabPosition))
addOnTabSelectedListener(
object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
festivalTabUiState.onFilterSelected(getFestivalFilterAt(tab.position))
}

override fun onTabUnselected(tab: TabLayout.Tab) = Unit
override fun onTabReselected(tab: TabLayout.Tab) = Unit
},
)
override fun onTabUnselected(tab: TabLayout.Tab) = Unit
override fun onTabReselected(tab: TabLayout.Tab) = Unit
},
)
}
}

private fun getFestivalFilterAt(position: Int): FestivalFilterUiState =
FestivalFilterUiState.values().find { it.tabPosition == position }
?: FestivalFilterUiState.PROGRESS

companion object {
fun of(parent: ViewGroup): FestivalListTabViewHolder {
val binding = ItemFestivalListTabBinding.inflate(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.festago.festago.presentation.ui.home.festivallist.uistate

enum class FestivalFilterUiState {
PROGRESS, PLANNED
enum class FestivalFilterUiState(val tabPosition: Int) {
PROGRESS(0), PLANNED(1)
}
Loading

0 comments on commit 0bcd8d7

Please sign in to comment.