fix(content): 전체 탭 grid 폭을 주입한다
This commit is contained in:
@@ -8,6 +8,7 @@ import android.view.ViewGroup
|
|||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.doOnLayout
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -45,6 +46,7 @@ import kr.co.vividnext.sodalive.v2.main.content.model.toContentBannerIntent
|
|||||||
import kr.co.vividnext.sodalive.v2.main.content.model.toContentBannerRoute
|
import kr.co.vividnext.sodalive.v2.main.content.model.toContentBannerRoute
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.model.usesDayOfWeekQuery
|
import kr.co.vividnext.sodalive.v2.main.content.model.usesDayOfWeekQuery
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.model.usesSeriesItems
|
import kr.co.vividnext.sodalive.v2.main.content.model.usesSeriesItems
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.content.ui.CONTENT_ALL_GRID_SPAN_COUNT
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.CONTENT_RECOMMENDED_GRID_SPAN_COUNT
|
import kr.co.vividnext.sodalive.v2.main.content.ui.CONTENT_RECOMMENDED_GRID_SPAN_COUNT
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllAudioCardAdapter
|
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllAudioCardAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllSeriesCardAdapter
|
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentAllSeriesCardAdapter
|
||||||
@@ -55,6 +57,7 @@ import kr.co.vividnext.sodalive.v2.main.content.ui.ContentNewAndHotAdapter
|
|||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentOriginalSeriesAdapter
|
import kr.co.vividnext.sodalive.v2.main.content.ui.ContentOriginalSeriesAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.addContentGridItemSpacing
|
import kr.co.vividnext.sodalive.v2.main.content.ui.addContentGridItemSpacing
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.ui.addContentHorizontalItemSpacing
|
import kr.co.vividnext.sodalive.v2.main.content.ui.addContentHorizontalItemSpacing
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.content.ui.calculateContentGridItemWidthPx
|
||||||
import kr.co.vividnext.sodalive.v2.widget.AudioContentCardSize
|
import kr.co.vividnext.sodalive.v2.widget.AudioContentCardSize
|
||||||
import kr.co.vividnext.sodalive.v2.widget.contentranking.ContentRankingAdapter
|
import kr.co.vividnext.sodalive.v2.widget.contentranking.ContentRankingAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.widget.contentranking.ContentRankingItem
|
import kr.co.vividnext.sodalive.v2.widget.contentranking.ContentRankingItem
|
||||||
@@ -281,6 +284,7 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
layoutManager = contentAllGridLayoutManager
|
layoutManager = contentAllGridLayoutManager
|
||||||
adapter = contentAllAudioCardAdapter
|
adapter = contentAllAudioCardAdapter
|
||||||
addContentGridItemSpacing(CONTENT_ALL_GRID_SPAN_COUNT)
|
addContentGridItemSpacing(CONTENT_ALL_GRID_SPAN_COUNT)
|
||||||
|
doOnLayout { updateAllTabGridItemWidth() }
|
||||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
@@ -357,6 +361,7 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
bindAllTabControls(state)
|
bindAllTabControls(state)
|
||||||
binding.layoutContentAllSurface.visibility = View.VISIBLE
|
binding.layoutContentAllSurface.visibility = View.VISIBLE
|
||||||
hideAllTabEmptyError()
|
hideAllTabEmptyError()
|
||||||
|
updateAllTabGridItemWidth()
|
||||||
if (state.selectedType.usesSeriesItems()) {
|
if (state.selectedType.usesSeriesItems()) {
|
||||||
binding.rvContentAllItems.adapter = contentAllSeriesCardAdapter
|
binding.rvContentAllItems.adapter = contentAllSeriesCardAdapter
|
||||||
contentAllAudioCardAdapter.submitItems(emptyList())
|
contentAllAudioCardAdapter.submitItems(emptyList())
|
||||||
@@ -372,6 +377,12 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateAllTabGridItemWidth() {
|
||||||
|
val widthPx = binding.rvContentAllItems.calculateContentGridItemWidthPx(CONTENT_ALL_GRID_SPAN_COUNT)
|
||||||
|
contentAllAudioCardAdapter.setGridItemWidthPx(widthPx)
|
||||||
|
contentAllSeriesCardAdapter.setGridItemWidthPx(widthPx)
|
||||||
|
}
|
||||||
|
|
||||||
private fun bindAllTabEmpty(state: MainContentAllTabUiState.Empty) {
|
private fun bindAllTabEmpty(state: MainContentAllTabUiState.Empty) {
|
||||||
bindAllTabControls(state)
|
bindAllTabControls(state)
|
||||||
binding.layoutContentAllSurface.visibility = View.VISIBLE
|
binding.layoutContentAllSurface.visibility = View.VISIBLE
|
||||||
@@ -589,6 +600,5 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
private const val CONTENT_TAB_RECOMMENDATION = 0
|
private const val CONTENT_TAB_RECOMMENDATION = 0
|
||||||
private const val CONTENT_TAB_RANKING = 1
|
private const val CONTENT_TAB_RANKING = 1
|
||||||
private const val CONTENT_TAB_ALL = 2
|
private const val CONTENT_TAB_ALL = 2
|
||||||
private const val CONTENT_ALL_GRID_SPAN_COUNT = 3
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import kr.co.vividnext.sodalive.databinding.ItemContentAudioCardBinding
|
import kr.co.vividnext.sodalive.databinding.ItemContentAudioCardBinding
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.model.MainContentAllAudioUiModel
|
import kr.co.vividnext.sodalive.v2.main.content.model.MainContentAllAudioUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.widget.AudioContentCardSize
|
|
||||||
|
|
||||||
class ContentAllAudioCardAdapter(
|
class ContentAllAudioCardAdapter(
|
||||||
private val onAudioClick: (Long) -> Unit = {}
|
private val onAudioClick: (Long) -> Unit = {}
|
||||||
) : RecyclerView.Adapter<ContentAllAudioCardAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<ContentAllAudioCardAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var items: List<MainContentAllAudioUiModel> = emptyList()
|
private var items: List<MainContentAllAudioUiModel> = emptyList()
|
||||||
|
private var gridItemWidthPx: Int = 0
|
||||||
|
|
||||||
|
fun setGridItemWidthPx(widthPx: Int) {
|
||||||
|
if (widthPx <= 0 || gridItemWidthPx == widthPx) return
|
||||||
|
gridItemWidthPx = widthPx
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
fun submitItems(items: List<MainContentAllAudioUiModel>) {
|
fun submitItems(items: List<MainContentAllAudioUiModel>) {
|
||||||
this.items = items
|
this.items = items
|
||||||
@@ -27,7 +33,10 @@ class ContentAllAudioCardAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
holder.bind(items[position])
|
holder.bind(
|
||||||
|
item = items[position],
|
||||||
|
gridItemWidthPx = gridItemWidthPx
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
@@ -36,8 +45,8 @@ class ContentAllAudioCardAdapter(
|
|||||||
private val binding: ItemContentAudioCardBinding,
|
private val binding: ItemContentAudioCardBinding,
|
||||||
private val onAudioClick: (Long) -> Unit
|
private val onAudioClick: (Long) -> Unit
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(item: MainContentAllAudioUiModel) = with(binding.audioContentCard.root) {
|
fun bind(item: MainContentAllAudioUiModel, gridItemWidthPx: Int) = with(binding.audioContentCard.root) {
|
||||||
setSize(AudioContentCardSize.Small)
|
setGridItemWidthPx(gridItemWidthPx)
|
||||||
setContent(item.title, item.creatorNickname)
|
setContent(item.title, item.creatorNickname)
|
||||||
setTags(item.tags)
|
setTags(item.tags)
|
||||||
setAdultVisible(item.showAdultBadge)
|
setAdultVisible(item.showAdultBadge)
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import kr.co.vividnext.sodalive.databinding.ItemContentAllSeriesCardBinding
|
import kr.co.vividnext.sodalive.databinding.ItemContentAllSeriesCardBinding
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.model.MainContentAllSeriesUiModel
|
import kr.co.vividnext.sodalive.v2.main.content.model.MainContentAllSeriesUiModel
|
||||||
import kr.co.vividnext.sodalive.v2.widget.SeriesContentCardSize
|
|
||||||
|
|
||||||
class ContentAllSeriesCardAdapter(
|
class ContentAllSeriesCardAdapter(
|
||||||
private val onSeriesClick: (Long) -> Unit = {}
|
private val onSeriesClick: (Long) -> Unit = {}
|
||||||
) : RecyclerView.Adapter<ContentAllSeriesCardAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<ContentAllSeriesCardAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var items: List<MainContentAllSeriesUiModel> = emptyList()
|
private var items: List<MainContentAllSeriesUiModel> = emptyList()
|
||||||
|
private var gridItemWidthPx: Int = 0
|
||||||
|
|
||||||
|
fun setGridItemWidthPx(widthPx: Int) {
|
||||||
|
if (widthPx <= 0 || gridItemWidthPx == widthPx) return
|
||||||
|
gridItemWidthPx = widthPx
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
fun submitItems(items: List<MainContentAllSeriesUiModel>) {
|
fun submitItems(items: List<MainContentAllSeriesUiModel>) {
|
||||||
this.items = items
|
this.items = items
|
||||||
@@ -27,7 +33,10 @@ class ContentAllSeriesCardAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
holder.bind(items[position])
|
holder.bind(
|
||||||
|
item = items[position],
|
||||||
|
gridItemWidthPx = gridItemWidthPx
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
@@ -36,8 +45,8 @@ class ContentAllSeriesCardAdapter(
|
|||||||
private val binding: ItemContentAllSeriesCardBinding,
|
private val binding: ItemContentAllSeriesCardBinding,
|
||||||
private val onSeriesClick: (Long) -> Unit
|
private val onSeriesClick: (Long) -> Unit
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(item: MainContentAllSeriesUiModel) = with(binding.seriesContentCard.root) {
|
fun bind(item: MainContentAllSeriesUiModel, gridItemWidthPx: Int) = with(binding.seriesContentCard.root) {
|
||||||
setSize(SeriesContentCardSize.Small)
|
setGridItemWidthPx(gridItemWidthPx)
|
||||||
setContent(item.title, item.creatorNickname)
|
setContent(item.title, item.creatorNickname)
|
||||||
setOriginalVisible(item.showOriginalTag)
|
setOriginalVisible(item.showOriginalTag)
|
||||||
setAdultVisible(item.showAdultBadge)
|
setAdultVisible(item.showAdultBadge)
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ fun RecyclerView.addContentGridItemSpacing(spanCount: Int = CONTENT_RECOMMENDED_
|
|||||||
if (itemDecorationCount == 0) addItemDecoration(ContentGridItemDecoration(spanCount))
|
if (itemDecorationCount == 0) addItemDecoration(ContentGridItemDecoration(spanCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun RecyclerView.calculateContentGridItemWidthPx(spanCount: Int): Int {
|
||||||
|
val availableWidth = measuredWidth - paddingLeft - paddingRight
|
||||||
|
if (availableWidth <= 0 || spanCount <= 0) return 0
|
||||||
|
val totalGap = GRID_ITEM_GAP_DP.dpToPx() * (spanCount - 1)
|
||||||
|
return ((availableWidth - totalGap) / spanCount).roundToInt()
|
||||||
|
}
|
||||||
|
|
||||||
private class ContentHorizontalItemDecoration : RecyclerView.ItemDecoration() {
|
private class ContentHorizontalItemDecoration : RecyclerView.ItemDecoration() {
|
||||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
val position = parent.getChildAdapterPosition(view)
|
val position = parent.getChildAdapterPosition(view)
|
||||||
@@ -41,3 +48,4 @@ private const val HORIZONTAL_ITEM_GAP_DP = 8
|
|||||||
private const val GRID_ITEM_GAP_DP = 8
|
private const val GRID_ITEM_GAP_DP = 8
|
||||||
private const val GRID_ITEM_VERTICAL_GAP_DP = 28
|
private const val GRID_ITEM_VERTICAL_GAP_DP = 28
|
||||||
const val CONTENT_RECOMMENDED_GRID_SPAN_COUNT = 2
|
const val CONTENT_RECOMMENDED_GRID_SPAN_COUNT = 2
|
||||||
|
const val CONTENT_ALL_GRID_SPAN_COUNT = 3
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ class ContentMainFragmentSourceTest {
|
|||||||
assertTrue(source.contains("CONTENT_ALL_GRID_SPAN_COUNT"))
|
assertTrue(source.contains("CONTENT_ALL_GRID_SPAN_COUNT"))
|
||||||
assertTrue(source.contains("GridLayoutManager(requireContext(), CONTENT_ALL_GRID_SPAN_COUNT)"))
|
assertTrue(source.contains("GridLayoutManager(requireContext(), CONTENT_ALL_GRID_SPAN_COUNT)"))
|
||||||
assertTrue(source.contains("addContentGridItemSpacing(CONTENT_ALL_GRID_SPAN_COUNT)"))
|
assertTrue(source.contains("addContentGridItemSpacing(CONTENT_ALL_GRID_SPAN_COUNT)"))
|
||||||
|
assertTrue(source.contains("import androidx.core.view.doOnLayout"))
|
||||||
|
assertTrue(source.contains("doOnLayout { updateAllTabGridItemWidth() }"))
|
||||||
|
assertTrue(source.contains("private fun updateAllTabGridItemWidth()"))
|
||||||
|
assertTrue(source.contains("binding.rvContentAllItems.calculateContentGridItemWidthPx(CONTENT_ALL_GRID_SPAN_COUNT)"))
|
||||||
assertTrue(source.contains("MainContentAllType.AUDIO"))
|
assertTrue(source.contains("MainContentAllType.AUDIO"))
|
||||||
assertTrue(source.contains("MainContentAllType.SERIES"))
|
assertTrue(source.contains("MainContentAllType.SERIES"))
|
||||||
assertTrue(source.contains("MainContentAllType.ORIGINAL"))
|
assertTrue(source.contains("MainContentAllType.ORIGINAL"))
|
||||||
@@ -358,6 +362,56 @@ class ContentMainFragmentSourceTest {
|
|||||||
assertEquals(null, banner(link = "mailto:test@example.com").toContentBannerRoute())
|
assertEquals(null, banner(link = "mailto:test@example.com").toContentBannerRoute())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `content 전체 탭 adapter는 fixed Small card width 대신 grid item width를 사용한다`() {
|
||||||
|
val fragment = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt"
|
||||||
|
).readText()
|
||||||
|
val audioAdapter = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentAllAudioCardAdapter.kt"
|
||||||
|
).readText()
|
||||||
|
val seriesAdapter = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentAllSeriesCardAdapter.kt"
|
||||||
|
).readText()
|
||||||
|
val layoutParams = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ui/ContentRecyclerItemLayoutParams.kt"
|
||||||
|
).readText()
|
||||||
|
val audioCard = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt"
|
||||||
|
).readText()
|
||||||
|
val seriesCard = projectFile(
|
||||||
|
"app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardView.kt"
|
||||||
|
).readText()
|
||||||
|
|
||||||
|
assertFalse(audioAdapter.contains("setSize(AudioContentCardSize.Small)"))
|
||||||
|
assertFalse(seriesAdapter.contains("setSize(SeriesContentCardSize.Small)"))
|
||||||
|
assertFalse(audioAdapter.contains("holder.itemView.parent as? RecyclerView"))
|
||||||
|
assertFalse(seriesAdapter.contains("holder.itemView.parent as? RecyclerView"))
|
||||||
|
assertSourceContains(audioAdapter, "private var gridItemWidthPx: Int = 0")
|
||||||
|
assertSourceContains(seriesAdapter, "private var gridItemWidthPx: Int = 0")
|
||||||
|
assertSourceContains(audioAdapter, "fun setGridItemWidthPx(widthPx: Int)")
|
||||||
|
assertSourceContains(seriesAdapter, "fun setGridItemWidthPx(widthPx: Int)")
|
||||||
|
assertSourceContains(audioAdapter, "if (widthPx <= 0 || gridItemWidthPx == widthPx) return")
|
||||||
|
assertSourceContains(seriesAdapter, "if (widthPx <= 0 || gridItemWidthPx == widthPx) return")
|
||||||
|
assertSourceContains(fragment, "private fun updateAllTabGridItemWidth()")
|
||||||
|
assertSourceContains(fragment, "binding.rvContentAllItems.calculateContentGridItemWidthPx(CONTENT_ALL_GRID_SPAN_COUNT)")
|
||||||
|
assertSourceContains(fragment, "contentAllAudioCardAdapter.setGridItemWidthPx(widthPx)")
|
||||||
|
assertSourceContains(fragment, "contentAllSeriesCardAdapter.setGridItemWidthPx(widthPx)")
|
||||||
|
assertSourceContains(fragment, "doOnLayout { updateAllTabGridItemWidth() }")
|
||||||
|
assertTrue(
|
||||||
|
"전체 탭 content bind는 item submit 전에 최신 grid width를 adapter에 주입해야 한다.",
|
||||||
|
fragment.indexOf("updateAllTabGridItemWidth()") <
|
||||||
|
fragment.indexOf("contentAllSeriesCardAdapter.submitItems(state.seriesItems)")
|
||||||
|
)
|
||||||
|
assertSourceContains(audioAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
||||||
|
assertSourceContains(seriesAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
||||||
|
assertSourceContains(layoutParams, "measuredWidth - paddingLeft - paddingRight")
|
||||||
|
assertSourceContains(layoutParams, "GRID_ITEM_GAP_DP.dpToPx() * (spanCount - 1)")
|
||||||
|
assertSourceContains(audioCard, "fun setGridItemWidthPx(widthPx: Int)")
|
||||||
|
assertSourceContains(seriesCard, "fun setGridItemWidthPx(widthPx: Int)")
|
||||||
|
assertSourceContains(seriesCard, "172f / 122f")
|
||||||
|
}
|
||||||
|
|
||||||
private fun banner(
|
private fun banner(
|
||||||
eventItem: EventItem? = null,
|
eventItem: EventItem? = null,
|
||||||
creatorId: Long? = null,
|
creatorId: Long? = null,
|
||||||
|
|||||||
Reference in New Issue
Block a user