Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AN/USER] feat: 축제 목록 화면 지역별 필터링 구현 (#768) #800

Merged
merged 12 commits into from
Apr 2, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.festago.festago.domain.model.festival.SchoolRegion
import com.festago.festago.presentation.databinding.FragmentFestivalListBinding
import com.festago.festago.presentation.ui.home.festivallist.FestivalListFragmentDirections.actionFestivalListFragmentToSearchFragment
import com.festago.festago.presentation.ui.home.festivallist.bottomsheet.RegionBottomSheetDialogFragment
import com.festago.festago.presentation.ui.home.festivallist.festival.FestivalListAdapter
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.home.festivallist.uistate.SchoolRegionUiState
import com.festago.festago.presentation.ui.notificationlist.NotificationListActivity
import com.festago.festago.presentation.util.repeatOnStarted
import com.festago.festago.presentation.util.setOnApplyWindowInsetsCompatListener
Expand Down Expand Up @@ -114,13 +117,17 @@ class FestivalListFragment : Fragment() {

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

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

val itemTotalCount = recyclerView.adapter!!.itemCount - 1
if (lastVisibleItemPosition == itemTotalCount) {
vm.loadFestivals()
vm.loadFestivals(
schoolRegion = festivalListUiState.schoolRegion,
isLoadMore = true,
)
}
}
})
Expand Down Expand Up @@ -174,19 +181,50 @@ class FestivalListFragment : Fragment() {
private fun handleSuccess(uiState: FestivalListUiState.Success) {
val items = uiState.getItems()
festivalListAdapter.submitList(items)
binding.rvFestivalList.itemAnimator = null
}

private fun FestivalListUiState.Success.getItems(): List<Any> {
val schoolRegions = SchoolRegion.values().map {
SchoolRegionUiState(it, it == this.schoolRegion)
}
val dialog = createRegionDialog(schoolRegions)

return mutableListOf<Any>().apply {
if (popularFestivalUiState.festivals.isNotEmpty()) {
add(popularFestivalUiState)
}
add(FestivalTabUiState(festivalFilter) { vm.loadFestivals(it) })
add(
FestivalTabUiState(
selectedFilter = festivalFilter,
selectedRegion = schoolRegion,
onFilterSelected = { vm.loadFestivals(it, schoolRegion) },
) {
dialog.show(
parentFragmentManager,
RegionBottomSheetDialogFragment::class.java.name,
)
},
)
addAll(festivals)
if (!isLastPage) add(FestivalMoreItemUiState)
}.toList()
}

private fun FestivalListUiState.Success.createRegionDialog(
schoolRegions: List<SchoolRegionUiState>,
) = RegionBottomSheetDialogFragment.newInstance(
items = schoolRegions,
listener = object : RegionBottomSheetDialogFragment.OnRegionSelectListener {
override fun onRegionSelect(region: SchoolRegion) {
vm.loadFestivals(
festivalFilterUiState = festivalFilter,
schoolRegion = if (region == schoolRegion) null else region,
)
}
},
)

private fun showSchoolDetail() {
findNavController().navigate(actionFestivalListFragmentToSearchFragment())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.festago.festago.common.analytics.AnalyticsHelper
import com.festago.festago.common.analytics.logNetworkFailure
import com.festago.festago.domain.model.festival.Festival
import com.festago.festago.domain.model.festival.FestivalFilter
import com.festago.festago.domain.model.festival.SchoolRegion
import com.festago.festago.domain.repository.FestivalRepository
import com.festago.festago.presentation.ui.home.festivallist.uistate.ArtistUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalFilterUiState
Expand Down Expand Up @@ -40,9 +41,13 @@ class FestivalListViewModel @Inject constructor(

fun initFestivalList() {
viewModelScope.launch {
val schoolRegion = (uiState.value as? FestivalListUiState.Success)?.schoolRegion
val deferredPopularFestivals = async { festivalRepository.loadPopularFestivals() }
val deferredFestivals = async {
festivalRepository.loadFestivals(festivalFilter = festivalFilter)
festivalRepository.loadFestivals(
schoolRegion = schoolRegion,
festivalFilter = festivalFilter,
)
}
runCatching {
val festivalsPage = deferredFestivals.await().getOrThrow()
Expand All @@ -56,6 +61,7 @@ class FestivalListViewModel @Inject constructor(
festivals = festivalsPage.festivals.map { it.toUiState() },
festivalFilter = festivalFilter.toUiState(),
isLastPage = festivalsPage.isLastPage,
schoolRegion = schoolRegion,
)
}.onFailure {
_uiState.value = FestivalListUiState.Error
Expand All @@ -67,30 +73,58 @@ class FestivalListViewModel @Inject constructor(
}
}

fun loadFestivals(festivalFilterUiState: FestivalFilterUiState? = null) {
fun loadFestivals(
festivalFilterUiState: FestivalFilterUiState? = null,
schoolRegion: SchoolRegion? = null,
isLoadMore: Boolean = false,
) {
val successUiState = uiState.value as? FestivalListUiState.Success ?: return

viewModelScope.launch {
val currentFestivals = getCurrentFestivals(festivalFilterUiState)
updateFestivalsState(festivalFilterUiState, successUiState)

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

private fun updateFestivalsState(
festivalFilterUiState: FestivalFilterUiState?,
successUiState: FestivalListUiState.Success,
) {
if (festivalFilterUiState == null) return
_uiState.value = successUiState.copy(
festivals = listOf(),
isLastPage = false,
festivalFilter = festivalFilterUiState,
)
}

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.festago.festago.presentation.ui.home.festivallist.bottomsheet

import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.festago.festago.domain.model.festival.SchoolRegion
import com.festago.festago.presentation.ui.home.festivallist.uistate.SchoolRegionUiState

class RegionAdapter(
private val items: List<SchoolRegionUiState>,
private val onRegionSelect: (SchoolRegion) -> Unit,
private val onDismiss: () -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return RegionItemViewHolder.of(parent, onRegionSelect, onDismiss)
}

override fun getItemCount(): Int {
return items.size
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as RegionItemViewHolder).bind(items[position])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.festago.festago.presentation.ui.home.festivallist.bottomsheet

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.festago.festago.domain.model.festival.SchoolRegion
import com.festago.festago.presentation.databinding.FragmentRegionBottomSheetBinding
import com.festago.festago.presentation.ui.home.festivallist.uistate.SchoolRegionUiState
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class RegionBottomSheetDialogFragment() : BottomSheetDialogFragment() {

private var _binding: FragmentRegionBottomSheetBinding? = null
private val binding: FragmentRegionBottomSheetBinding get() = _binding!!

private lateinit var listener: OnRegionSelectListener
private lateinit var items: List<SchoolRegionUiState>

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentRegionBottomSheetBinding.inflate(inflater)
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initView()
}

private fun initView() {
binding.rvRegionList.adapter = RegionAdapter(
items = items,
onRegionSelect = listener::onRegionSelect
) { dismiss() }
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

interface OnRegionSelectListener {
fun onRegionSelect(region: SchoolRegion)
}

companion object {
fun newInstance(
items: List<SchoolRegionUiState>,
listener: OnRegionSelectListener,
): RegionBottomSheetDialogFragment = RegionBottomSheetDialogFragment().apply {
this.listener = listener
this.items = items
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.festago.festago.presentation.ui.home.festivallist.bottomsheet

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.festago.festago.domain.model.festival.SchoolRegion
import com.festago.festago.presentation.databinding.ItemRegionBinding
import com.festago.festago.presentation.ui.home.festivallist.uistate.SchoolRegionUiState

class RegionItemViewHolder(
private val binding: ItemRegionBinding,
private val onRegionSelect: (SchoolRegion) -> Unit,
private val onDismiss: () -> Unit
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: SchoolRegionUiState) {
binding.item = item
with(binding.tvRegion) {
this.isSelected = item.isSelected
setOnClickListener {
onRegionSelect(item.schoolRegion)
onDismiss.invoke()
}
}
}

companion object {
fun of(
parent: ViewGroup,
onRegionSelect: (SchoolRegion) -> Unit,
onDismiss: () -> Unit
): RegionItemViewHolder {
val binding = ItemRegionBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false,
)
return RegionItemViewHolder(binding, onRegionSelect, onDismiss)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class FestivalListAdapter(
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean = when {
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 FestivalTabUiState && newItem is FestivalTabUiState -> oldItem.selectedRegion == newItem.selectedRegion
oldItem is FestivalMoreItemUiState && newItem is FestivalMoreItemUiState -> true
else -> false
}
Expand All @@ -59,7 +59,8 @@ 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
-> oldItem as FestivalTabUiState == newItem

oldItem is FestivalMoreItemUiState && newItem is FestivalMoreItemUiState -> true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ class FestivalListTabViewHolder(val binding: ItemFestivalListTabBinding) :
},
)
}
binding.schoolRegion = festivalTabUiState.selectedRegion
binding.tvRegion.setOnClickListener {
festivalTabUiState.onRegionClick.invoke()
}
if (festivalTabUiState.selectedRegion == null) {
binding.tvRegion.isSelected = false
binding.tvRegion.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_pin_normal, 0, 0, 0
)
} else {
binding.tvRegion.isSelected = true
binding.tvRegion.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_pin_select, 0, 0, 0
)
}
}

private fun getFestivalFilterAt(position: Int): FestivalFilterUiState =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.festago.festago.presentation.ui.home.festivallist.uistate

import com.festago.festago.domain.model.festival.SchoolRegion

sealed interface FestivalListUiState {
object Loading : FestivalListUiState

Expand All @@ -8,6 +10,7 @@ sealed interface FestivalListUiState {
val festivals: List<FestivalItemUiState>,
val festivalFilter: FestivalFilterUiState,
val isLastPage: Boolean,
val schoolRegion: SchoolRegion? = null,
) : FestivalListUiState

object Error : FestivalListUiState
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.festago.festago.presentation.ui.home.festivallist.uistate

import com.festago.festago.domain.model.festival.SchoolRegion

data class FestivalTabUiState(
val selectedFilter: FestivalFilterUiState,
val selectedRegion: SchoolRegion?,
val onFilterSelected: (FestivalFilterUiState) -> Unit,
val onRegionClick: () -> Unit,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.festago.festago.presentation.ui.home.festivallist.uistate

import com.festago.festago.domain.model.festival.SchoolRegion

data class SchoolRegionUiState(
val schoolRegion: SchoolRegion,
val isSelected: Boolean,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="16dp" />
<stroke
android:width="1dp"
android:color="@color/primary_blue_07" />
<solid android:color="@color/primary_blue_01" />
</shape>
Loading
Loading