Skip to content

Commit

Permalink
[AN/USER] feat: 안드로이드 메인 화면 하단을 구성한다. (#668) (#670)
Browse files Browse the repository at this point in the history
* feat(festival-list): 리스트 추가

* feat(festival-list): 아티스트 추가

* feat(festival-list): 축제 목록 리사이클러 뷰 연동

* feat(festival-list): 축제 목록 모두 스크롤 되도록 멀티 타입 리사이클러 뷰로 변경

* feat(festival-list): 축제 목록 탭 레이아웃 추가

* feat(festival-list): 뷰홀더에서 역할 이동

* feat(festival-list): Tab 인디케이터 와 글자 색상 변경

* feat(festival-list): Tab 고정된 글자 변경

* feat(festival-list): D-Day 추가

* refactor(festival-list): 탭 레이아 설정을 프로그래밍으로 변경

* refactor(festival-list): diffUtil 적용

* refactor(festival-list): lint 적용

* refactor(festival-list): 사용하지 않는 context 제거

* refactor(festival-list): 데코레이션에서 사용하지 않는 context 제거 후

* refactor(festival-list): 디자인 포멧 일치

* refactor(festival-list): 디자인 변경사항 반영

* refactor(festival-list): 리사이클러 뷰 마지막 마진 추가
  • Loading branch information
re4rk authored Jan 25, 2024
1 parent 325a5c2 commit df4eb0d
Show file tree
Hide file tree
Showing 19 changed files with 665 additions and 72 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
package com.festago.festago.presentation.ui.home.festivallist

import android.content.res.Resources
import android.graphics.Rect
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.RecyclerView
import com.festago.festago.presentation.databinding.FragmentFestivalListBinding
import com.festago.festago.presentation.ui.home.festivallist.popularfestival.PopularFestivalViewPagerAdapter
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.FestivalTabUiState
import com.festago.festago.presentation.util.repeatOnStarted
import com.google.android.material.tabs.TabLayoutMediator

class FestivalListFragment : Fragment() {

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

private lateinit var popularFestivalViewPager: PopularFestivalViewPagerAdapter
private lateinit var festivalListAdapter: FestivalListAdapter

private val vm: FestivalListViewModel by viewModels()

Expand Down Expand Up @@ -49,18 +54,35 @@ class FestivalListFragment : Fragment() {
private fun initView() {
initViewPager()
vm.loadPopularFestival()
initRecyclerView()
}

private fun initViewPager() {
popularFestivalViewPager = PopularFestivalViewPagerAdapter(
foregroundViewPager = binding.vpPopularFestivalForeground,
backgroundViewPager = binding.vpPopularFestivalBackground,
)
festivalListAdapter = FestivalListAdapter()
binding.rvList.adapter = festivalListAdapter
}

TabLayoutMediator(
binding.tlDotIndicator,
binding.vpPopularFestivalForeground,
) { tab, position -> }.attach()
private fun initRecyclerView() {
binding.rvList.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State,
) {
super.getItemOffsets(outRect, view, parent, state)
if (parent.getChildAdapterPosition(view) == state.itemCount - 1) {
outRect.bottom = 32.dpToPx
}
}

private val Int.dpToPx: Int
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics,
).toInt()
})
}

private fun updateUi(uiState: FestivalListUiState) {
Expand All @@ -74,7 +96,18 @@ class FestivalListFragment : Fragment() {
}

private fun handleSuccess(uiState: FestivalListUiState.Success) {
popularFestivalViewPager.submitList(uiState.festivals)
festivalListAdapter.submitList(
listOf(
uiState,
FestivalTabUiState(
{
Toast.makeText(requireContext(), "Clicked $it", Toast.LENGTH_SHORT).show()
/* TODO: Handle tab click */
vm.loadPopularFestival()
},
),
) + uiState.festivals,
)
}

override fun onDestroyView() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.festago.festago.presentation.ui.home.festivallist.festival

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.FestivalListUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalTabUiState

class FestivalListAdapter : ListAdapter<Any, FestivalListViewHolder>(diffUtil) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FestivalListViewHolder {
return when (viewType) {
1 -> FestivalListPopularViewHolder.of(parent)
2 -> FestivalListFestivalViewHolder.of(parent)
3 -> FestivalListTabViewHolder.of(parent)
else -> throw IllegalArgumentException("Invalid viewType")
}
}

override fun onBindViewHolder(holder: FestivalListViewHolder, position: Int) {
val item = getItem(position)
return when (holder) {
is FestivalListPopularViewHolder -> holder.bind((item as FestivalListUiState.Success).festivals)
is FestivalListFestivalViewHolder -> holder.bind(item as FestivalItemUiState)
is FestivalListTabViewHolder -> holder.bind(item as FestivalTabUiState)
}
}

override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is FestivalListUiState -> 1
is FestivalItemUiState -> 2
is FestivalTabUiState -> 3
else -> throw IllegalArgumentException("Invalid item")
}
}

companion object {
private val diffUtil = object : DiffUtil.ItemCallback<Any>() {
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean = when {
oldItem is FestivalListUiState && newItem is FestivalListUiState -> true
oldItem is FestivalItemUiState && newItem is FestivalItemUiState -> oldItem.id == newItem.id
oldItem is FestivalTabUiState && newItem is FestivalTabUiState -> true
else -> false
}

override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean = when {
oldItem is FestivalListUiState && newItem is FestivalListUiState
-> oldItem as FestivalListUiState == newItem

oldItem is FestivalItemUiState && newItem is FestivalItemUiState
-> oldItem as FestivalItemUiState == newItem

oldItem is FestivalTabUiState && newItem is FestivalTabUiState
-> oldItem as FestivalTabUiState == newItem

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

import android.content.res.Resources
import android.graphics.Rect
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import com.festago.festago.presentation.R
import com.festago.festago.presentation.databinding.ItemFestivalListFestivalBinding
import com.festago.festago.presentation.ui.home.festivallist.festival.artistlist.ArtistAdapter
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalItemUiState
import java.time.LocalDate

class FestivalListFestivalViewHolder(private val binding: ItemFestivalListFestivalBinding) :
FestivalListViewHolder(binding) {
private val artistAdapter = ArtistAdapter()

init {
binding.rvFestivalArtists.adapter = artistAdapter
binding.rvFestivalArtists.addItemDecoration(ArtistItemDecoration())
}

fun bind(item: FestivalItemUiState) {
binding.item = item
artistAdapter.submitList(item.artists)
bindDDayView(item)
}

private fun bindDDayView(item: FestivalItemUiState) {
val context = binding.root.context

val dDayView = binding.tvFestivalDDay
when {
LocalDate.now() > item.endDate -> Unit

LocalDate.now() >= item.startDate -> {
dDayView.text = context.getString(R.string.festival_list_tv_dday_in_progress)
dDayView.setTextColor(context.getColor(R.color.secondary_pink_01))
dDayView.background = AppCompatResources.getDrawable(
context,
R.drawable.bg_festival_list_dday_in_progress,
)
}

LocalDate.now() == item.startDate.minusDays(1) -> {
dDayView.text = context.getString(
R.string.festival_list_tv_dday_format,
item.startDate.compareTo(LocalDate.now()).toString(),
)
dDayView.setBackgroundColor(0xffff1273.toInt())
}

else -> binding.tvFestivalDDay.apply {
dDayView.text = context.getString(
R.string.festival_list_tv_dday_format,
item.startDate.compareTo(LocalDate.now()).toString(),
)
dDayView.setBackgroundColor(context.getColor(android.R.color.black))
}
}
}

private class ArtistItemDecoration : ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State,
) {
super.getItemOffsets(outRect, view, parent, state)
outRect.right = 8.dpToPx
}

private val Int.dpToPx: Int
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics,
).toInt()
}

companion object {
fun of(parent: ViewGroup): FestivalListFestivalViewHolder {
val binding = ItemFestivalListFestivalBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false,
)
return FestivalListFestivalViewHolder(binding)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.festago.festago.presentation.ui.home.festivallist.festival

import android.view.LayoutInflater
import android.view.ViewGroup
import com.festago.festago.presentation.databinding.ItemFestivalListPopularBinding
import com.festago.festago.presentation.ui.home.festivallist.popularfestival.PopularFestivalViewPagerAdapter
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalItemUiState
import com.festago.festago.presentation.ui.home.festivallist.uistate.FestivalListUiState
import com.google.android.material.tabs.TabLayoutMediator

class FestivalListPopularViewHolder(val binding: ItemFestivalListPopularBinding) :
FestivalListViewHolder(binding) {
private val popularFestivalViewPager: PopularFestivalViewPagerAdapter =
PopularFestivalViewPagerAdapter(
foregroundViewPager = binding.vpPopularFestivalForeground,
backgroundViewPager = binding.vpPopularFestivalBackground,
)

init {
TabLayoutMediator(
binding.tlDotIndicator,
binding.vpPopularFestivalForeground,
) { tab, position -> }.attach()
}

fun bind(festivals: List<FestivalItemUiState>) {
popularFestivalViewPager.submitList(festivals)
binding.uiState = FestivalListUiState.Success(
popularFestivals = festivals,
festivals = festivals,
)
}

companion object {
fun of(parent: ViewGroup): FestivalListPopularViewHolder {
val binding = ItemFestivalListPopularBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false,
)
return FestivalListPopularViewHolder(binding)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.festago.festago.presentation.ui.home.festivallist.festival

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.FestivalTabUiState
import com.google.android.material.tabs.TabLayout

class FestivalListTabViewHolder(val binding: ItemFestivalListTabBinding) :
FestivalListViewHolder(binding) {
init {
binding.tlFestivalListTab.addTab(
binding.tlFestivalListTab.newTab().setText(
binding.root.context.getText(R.string.festival_list_ti_current_festival),
),
)
binding.tlFestivalListTab.addTab(
binding.tlFestivalListTab.newTab().setText(
binding.root.context.getText(R.string.festival_list_ti_upcoming_festival),
),
)
}

fun bind(festivalTabUiState: FestivalTabUiState) {
binding.tlFestivalListTab.addOnTabSelectedListener(
object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
festivalTabUiState.onClick(tab.position)
}

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

companion object {
fun of(parent: ViewGroup): FestivalListTabViewHolder {
val binding = ItemFestivalListTabBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false,
)
return FestivalListTabViewHolder(binding)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.festago.festago.presentation.ui.home.festivallist.festival

import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView

sealed class FestivalListViewHolder(binding: ViewDataBinding) :
RecyclerView.ViewHolder(binding.root)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.festago.festago.presentation.ui.home.festivallist.festival.artistlist

import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.festago.festago.presentation.ui.home.festivallist.uistate.ArtistUiState

class ArtistAdapter : ListAdapter<ArtistUiState, ArtistViewHolder>(diffUtil) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArtistViewHolder {
return ArtistViewHolder.of(parent)
}

override fun onBindViewHolder(holder: ArtistViewHolder, position: Int) {
holder.bind(getItem(position))
}

companion object {
private val diffUtil = object : DiffUtil.ItemCallback<ArtistUiState>() {
override fun areItemsTheSame(
oldItem: ArtistUiState,
newItem: ArtistUiState,
): Boolean {
return oldItem.id == newItem.id
}

override fun areContentsTheSame(
oldItem: ArtistUiState,
newItem: ArtistUiState,
): Boolean {
return oldItem == newItem
}
}
}
}
Loading

0 comments on commit df4eb0d

Please sign in to comment.