fix(content): 추천 오디오 카드 폭을 보정한다
This commit is contained in:
@@ -54,6 +54,7 @@ import kr.co.vividnext.sodalive.v2.main.content.model.usesSeriesItems
|
||||
import kr.co.vividnext.sodalive.v2.main.content.overview.ContentOverviewActivity
|
||||
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewType
|
||||
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_GAP_DP
|
||||
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.ContentAllSeriesCardAdapter
|
||||
@@ -318,7 +319,8 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
||||
binding.rvContentRecommendedAudios.apply {
|
||||
layoutManager = GridLayoutManager(requireContext(), CONTENT_RECOMMENDED_GRID_SPAN_COUNT)
|
||||
adapter = recommendedAudioAdapter
|
||||
addContentGridItemSpacing()
|
||||
addContentGridItemSpacing(gapDp = CONTENT_RECOMMENDED_GRID_GAP_DP)
|
||||
doOnLayout { updateRecommendedAudioGridItemWidth() }
|
||||
}
|
||||
binding.rvContentRankings.apply {
|
||||
layoutManager = ContentRankingAdapter.createGridLayoutManager(requireContext())
|
||||
@@ -423,6 +425,14 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRecommendedAudioGridItemWidth() {
|
||||
val widthPx = binding.rvContentRecommendedAudios.calculateContentGridItemWidthPx(
|
||||
CONTENT_RECOMMENDED_GRID_SPAN_COUNT,
|
||||
gapDp = CONTENT_RECOMMENDED_GRID_GAP_DP
|
||||
)
|
||||
recommendedAudioAdapter.setGridItemWidthPx(widthPx)
|
||||
}
|
||||
|
||||
private fun updateAllTabGridItemWidth() {
|
||||
val widthPx = binding.rvContentAllItems.calculateContentGridItemWidthPx(CONTENT_ALL_GRID_SPAN_COUNT)
|
||||
contentAllAudioCardAdapter.setGridItemWidthPx(widthPx)
|
||||
@@ -546,6 +556,7 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
||||
|
||||
private fun bindRecommendedAudioSection(section: ContentAudioCardSection) {
|
||||
binding.llContentRecommendedAudioSection.visibility = section.items.toSectionVisibility()
|
||||
updateRecommendedAudioGridItemWidth()
|
||||
recommendedAudioAdapter.submitItems(section.items)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,35 @@ class ContentAudioCardAdapter(
|
||||
) : RecyclerView.Adapter<ContentAudioCardAdapter.ViewHolder>() {
|
||||
|
||||
private var items: List<ContentAudioCardUiModel> = emptyList()
|
||||
private var gridItemWidthPx: Int = 0
|
||||
|
||||
fun setGridItemWidthPx(widthPx: Int) {
|
||||
if (widthPx <= 0 || gridItemWidthPx == widthPx) return
|
||||
gridItemWidthPx = widthPx
|
||||
if (items.isNotEmpty()) notifyItemRangeChanged(0, items.size)
|
||||
}
|
||||
|
||||
fun submitItems(items: List<ContentAudioCardUiModel>) {
|
||||
val previousItemCount = this.items.size
|
||||
this.items = items
|
||||
notifyDataSetChanged()
|
||||
notifyItemChanges(previousItemCount, items.size)
|
||||
}
|
||||
|
||||
private fun notifyItemChanges(previousItemCount: Int, currentItemCount: Int) {
|
||||
when {
|
||||
previousItemCount == 0 && currentItemCount > 0 -> notifyItemRangeInserted(0, currentItemCount)
|
||||
previousItemCount > 0 && currentItemCount == 0 -> notifyItemRangeRemoved(0, previousItemCount)
|
||||
previousItemCount == currentItemCount -> notifyItemRangeChanged(0, currentItemCount)
|
||||
else -> {
|
||||
val changedItemCount = minOf(previousItemCount, currentItemCount)
|
||||
if (changedItemCount > 0) notifyItemRangeChanged(0, changedItemCount)
|
||||
if (currentItemCount > previousItemCount) {
|
||||
notifyItemRangeInserted(previousItemCount, currentItemCount - previousItemCount)
|
||||
} else {
|
||||
notifyItemRangeRemoved(currentItemCount, previousItemCount - currentItemCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
@@ -29,7 +54,10 @@ class ContentAudioCardAdapter(
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(items[position])
|
||||
holder.bind(
|
||||
item = items[position],
|
||||
gridItemWidthPx = gridItemWidthPx
|
||||
)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
@@ -39,8 +67,12 @@ class ContentAudioCardAdapter(
|
||||
private val cardSize: AudioContentCardSize,
|
||||
private val onAudioClick: (ContentAudioCardUiModel) -> Unit
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(item: ContentAudioCardUiModel) = with(binding.audioContentCard.root) {
|
||||
setSize(cardSize)
|
||||
fun bind(item: ContentAudioCardUiModel, gridItemWidthPx: Int) = with(binding.audioContentCard.root) {
|
||||
if (gridItemWidthPx > 0) {
|
||||
setGridItemWidthPx(gridItemWidthPx)
|
||||
} else {
|
||||
setSize(cardSize)
|
||||
}
|
||||
setContent(item.title, item.creatorNickname)
|
||||
setTags(item.tags)
|
||||
setAdultVisible(item.showAdultBadge)
|
||||
|
||||
@@ -10,14 +10,20 @@ fun RecyclerView.addContentHorizontalItemSpacing() {
|
||||
if (itemDecorationCount == 0) addItemDecoration(ContentHorizontalItemDecoration())
|
||||
}
|
||||
|
||||
fun RecyclerView.addContentGridItemSpacing(spanCount: Int = CONTENT_RECOMMENDED_GRID_SPAN_COUNT) {
|
||||
if (itemDecorationCount == 0) addItemDecoration(ContentGridItemDecoration(spanCount))
|
||||
fun RecyclerView.addContentGridItemSpacing(
|
||||
spanCount: Int = CONTENT_RECOMMENDED_GRID_SPAN_COUNT,
|
||||
gapDp: Int = GRID_ITEM_GAP_DP
|
||||
) {
|
||||
if (itemDecorationCount == 0) addItemDecoration(ContentGridItemDecoration(spanCount, gapDp))
|
||||
}
|
||||
|
||||
fun RecyclerView.calculateContentGridItemWidthPx(spanCount: Int): Int {
|
||||
fun RecyclerView.calculateContentGridItemWidthPx(
|
||||
spanCount: Int,
|
||||
gapDp: Int = GRID_ITEM_GAP_DP
|
||||
): Int {
|
||||
val availableWidth = measuredWidth - paddingLeft - paddingRight
|
||||
if (availableWidth <= 0 || spanCount <= 0) return 0
|
||||
val totalGap = GRID_ITEM_GAP_DP.dpToPx() * (spanCount - 1)
|
||||
val totalGap = gapDp.dpToPx() * (spanCount - 1)
|
||||
return ((availableWidth - totalGap) / spanCount).roundToInt()
|
||||
}
|
||||
|
||||
@@ -31,13 +37,14 @@ private class ContentHorizontalItemDecoration : RecyclerView.ItemDecoration() {
|
||||
}
|
||||
|
||||
private class ContentGridItemDecoration(
|
||||
private val spanCount: Int
|
||||
private val spanCount: Int,
|
||||
private val gapDp: Int
|
||||
) : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
val position = parent.getChildAdapterPosition(view)
|
||||
if (position == RecyclerView.NO_POSITION) return
|
||||
val columnIndex = position % spanCount
|
||||
val gap = GRID_ITEM_GAP_DP.dpToPx()
|
||||
val gap = gapDp.dpToPx()
|
||||
outRect.left = (columnIndex * gap / spanCount).roundToInt()
|
||||
outRect.right = (gap - (columnIndex + 1) * gap / spanCount).roundToInt()
|
||||
if (position >= spanCount) outRect.top = GRID_ITEM_VERTICAL_GAP_DP.dpToPx().toInt()
|
||||
@@ -45,6 +52,7 @@ private class ContentGridItemDecoration(
|
||||
}
|
||||
|
||||
private const val HORIZONTAL_ITEM_GAP_DP = 8
|
||||
const val CONTENT_RECOMMENDED_GRID_GAP_DP = 4
|
||||
private const val GRID_ITEM_GAP_DP = 8
|
||||
private const val GRID_ITEM_VERTICAL_GAP_DP = 28
|
||||
const val CONTENT_RECOMMENDED_GRID_SPAN_COUNT = 2
|
||||
|
||||
@@ -494,6 +494,34 @@ class ContentMainFragmentSourceTest {
|
||||
assertEquals(null, banner(link = "mailto:test@example.com").toContentBannerRoute())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `content 추천 오디오 adapter는 fixed Large card width 대신 2열 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/ContentAudioCardAdapter.kt"
|
||||
).readText()
|
||||
|
||||
assertSourceContains(audioAdapter, "private var gridItemWidthPx: Int = 0")
|
||||
assertSourceContains(audioAdapter, "fun setGridItemWidthPx(widthPx: Int)")
|
||||
assertSourceContains(audioAdapter, "if (widthPx <= 0 || gridItemWidthPx == widthPx) return")
|
||||
assertFalse(audioAdapter.contains("notifyDataSetChanged()"))
|
||||
assertSourceContains(audioAdapter, "notifyItemRangeChanged(0, items.size)")
|
||||
assertSourceContains(audioAdapter, "notifyItemRangeInserted(previousItemCount, currentItemCount - previousItemCount)")
|
||||
assertSourceContains(audioAdapter, "notifyItemRangeRemoved(currentItemCount, previousItemCount - currentItemCount)")
|
||||
assertSourceContains(fragment, "private fun updateRecommendedAudioGridItemWidth()")
|
||||
assertSourceContains(
|
||||
fragment,
|
||||
"binding.rvContentRecommendedAudios.calculateContentGridItemWidthPx("
|
||||
)
|
||||
assertSourceContains(fragment, "CONTENT_RECOMMENDED_GRID_SPAN_COUNT")
|
||||
assertSourceContains(fragment, "gapDp = CONTENT_RECOMMENDED_GRID_GAP_DP")
|
||||
assertSourceContains(fragment, "recommendedAudioAdapter.setGridItemWidthPx(widthPx)")
|
||||
assertSourceContains(fragment, "doOnLayout { updateRecommendedAudioGridItemWidth() }")
|
||||
assertSourceContains(audioAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `content 전체 탭 adapter는 fixed Small card width 대신 grid item width를 사용한다`() {
|
||||
val fragment = projectFile(
|
||||
@@ -538,7 +566,7 @@ class ContentMainFragmentSourceTest {
|
||||
assertSourceContains(audioAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
||||
assertSourceContains(seriesAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
||||
assertSourceContains(layoutParams, "measuredWidth - paddingLeft - paddingRight")
|
||||
assertSourceContains(layoutParams, "GRID_ITEM_GAP_DP.dpToPx() * (spanCount - 1)")
|
||||
assertSourceContains(layoutParams, "gapDp.dpToPx() * (spanCount - 1)")
|
||||
assertSourceContains(audioCard, "fun setGridItemWidthPx(widthPx: Int)")
|
||||
assertSourceContains(seriesCard, "fun setGridItemWidthPx(widthPx: Int)")
|
||||
assertSourceContains(seriesCard, "172f / 122f")
|
||||
|
||||
Reference in New Issue
Block a user