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.ContentOverviewActivity
|
||||||
import kr.co.vividnext.sodalive.v2.main.content.overview.data.ContentOverviewType
|
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_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.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
|
||||||
@@ -318,7 +319,8 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
binding.rvContentRecommendedAudios.apply {
|
binding.rvContentRecommendedAudios.apply {
|
||||||
layoutManager = GridLayoutManager(requireContext(), CONTENT_RECOMMENDED_GRID_SPAN_COUNT)
|
layoutManager = GridLayoutManager(requireContext(), CONTENT_RECOMMENDED_GRID_SPAN_COUNT)
|
||||||
adapter = recommendedAudioAdapter
|
adapter = recommendedAudioAdapter
|
||||||
addContentGridItemSpacing()
|
addContentGridItemSpacing(gapDp = CONTENT_RECOMMENDED_GRID_GAP_DP)
|
||||||
|
doOnLayout { updateRecommendedAudioGridItemWidth() }
|
||||||
}
|
}
|
||||||
binding.rvContentRankings.apply {
|
binding.rvContentRankings.apply {
|
||||||
layoutManager = ContentRankingAdapter.createGridLayoutManager(requireContext())
|
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() {
|
private fun updateAllTabGridItemWidth() {
|
||||||
val widthPx = binding.rvContentAllItems.calculateContentGridItemWidthPx(CONTENT_ALL_GRID_SPAN_COUNT)
|
val widthPx = binding.rvContentAllItems.calculateContentGridItemWidthPx(CONTENT_ALL_GRID_SPAN_COUNT)
|
||||||
contentAllAudioCardAdapter.setGridItemWidthPx(widthPx)
|
contentAllAudioCardAdapter.setGridItemWidthPx(widthPx)
|
||||||
@@ -546,6 +556,7 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
|
|||||||
|
|
||||||
private fun bindRecommendedAudioSection(section: ContentAudioCardSection) {
|
private fun bindRecommendedAudioSection(section: ContentAudioCardSection) {
|
||||||
binding.llContentRecommendedAudioSection.visibility = section.items.toSectionVisibility()
|
binding.llContentRecommendedAudioSection.visibility = section.items.toSectionVisibility()
|
||||||
|
updateRecommendedAudioGridItemWidth()
|
||||||
recommendedAudioAdapter.submitItems(section.items)
|
recommendedAudioAdapter.submitItems(section.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,35 @@ class ContentAudioCardAdapter(
|
|||||||
) : RecyclerView.Adapter<ContentAudioCardAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<ContentAudioCardAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var items: List<ContentAudioCardUiModel> = emptyList()
|
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>) {
|
fun submitItems(items: List<ContentAudioCardUiModel>) {
|
||||||
|
val previousItemCount = this.items.size
|
||||||
this.items = items
|
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 {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
@@ -29,7 +54,10 @@ class ContentAudioCardAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -39,8 +67,12 @@ class ContentAudioCardAdapter(
|
|||||||
private val cardSize: AudioContentCardSize,
|
private val cardSize: AudioContentCardSize,
|
||||||
private val onAudioClick: (ContentAudioCardUiModel) -> Unit
|
private val onAudioClick: (ContentAudioCardUiModel) -> Unit
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(item: ContentAudioCardUiModel) = with(binding.audioContentCard.root) {
|
fun bind(item: ContentAudioCardUiModel, gridItemWidthPx: Int) = with(binding.audioContentCard.root) {
|
||||||
setSize(cardSize)
|
if (gridItemWidthPx > 0) {
|
||||||
|
setGridItemWidthPx(gridItemWidthPx)
|
||||||
|
} else {
|
||||||
|
setSize(cardSize)
|
||||||
|
}
|
||||||
setContent(item.title, item.creatorNickname)
|
setContent(item.title, item.creatorNickname)
|
||||||
setTags(item.tags)
|
setTags(item.tags)
|
||||||
setAdultVisible(item.showAdultBadge)
|
setAdultVisible(item.showAdultBadge)
|
||||||
|
|||||||
@@ -10,14 +10,20 @@ fun RecyclerView.addContentHorizontalItemSpacing() {
|
|||||||
if (itemDecorationCount == 0) addItemDecoration(ContentHorizontalItemDecoration())
|
if (itemDecorationCount == 0) addItemDecoration(ContentHorizontalItemDecoration())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RecyclerView.addContentGridItemSpacing(spanCount: Int = CONTENT_RECOMMENDED_GRID_SPAN_COUNT) {
|
fun RecyclerView.addContentGridItemSpacing(
|
||||||
if (itemDecorationCount == 0) addItemDecoration(ContentGridItemDecoration(spanCount))
|
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
|
val availableWidth = measuredWidth - paddingLeft - paddingRight
|
||||||
if (availableWidth <= 0 || spanCount <= 0) return 0
|
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()
|
return ((availableWidth - totalGap) / spanCount).roundToInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,13 +37,14 @@ private class ContentHorizontalItemDecoration : RecyclerView.ItemDecoration() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ContentGridItemDecoration(
|
private class ContentGridItemDecoration(
|
||||||
private val spanCount: Int
|
private val spanCount: Int,
|
||||||
|
private val gapDp: Int
|
||||||
) : RecyclerView.ItemDecoration() {
|
) : 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)
|
||||||
if (position == RecyclerView.NO_POSITION) return
|
if (position == RecyclerView.NO_POSITION) return
|
||||||
val columnIndex = position % spanCount
|
val columnIndex = position % spanCount
|
||||||
val gap = GRID_ITEM_GAP_DP.dpToPx()
|
val gap = gapDp.dpToPx()
|
||||||
outRect.left = (columnIndex * gap / spanCount).roundToInt()
|
outRect.left = (columnIndex * gap / spanCount).roundToInt()
|
||||||
outRect.right = (gap - (columnIndex + 1) * gap / spanCount).roundToInt()
|
outRect.right = (gap - (columnIndex + 1) * gap / spanCount).roundToInt()
|
||||||
if (position >= spanCount) outRect.top = GRID_ITEM_VERTICAL_GAP_DP.dpToPx().toInt()
|
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
|
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_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
|
||||||
|
|||||||
@@ -494,6 +494,34 @@ 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 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
|
@Test
|
||||||
fun `content 전체 탭 adapter는 fixed Small card width 대신 grid item width를 사용한다`() {
|
fun `content 전체 탭 adapter는 fixed Small card width 대신 grid item width를 사용한다`() {
|
||||||
val fragment = projectFile(
|
val fragment = projectFile(
|
||||||
@@ -538,7 +566,7 @@ class ContentMainFragmentSourceTest {
|
|||||||
assertSourceContains(audioAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
assertSourceContains(audioAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
||||||
assertSourceContains(seriesAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
assertSourceContains(seriesAdapter, "setGridItemWidthPx(gridItemWidthPx)")
|
||||||
assertSourceContains(layoutParams, "measuredWidth - paddingLeft - paddingRight")
|
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(audioCard, "fun setGridItemWidthPx(widthPx: Int)")
|
||||||
assertSourceContains(seriesCard, "fun setGridItemWidthPx(widthPx: Int)")
|
assertSourceContains(seriesCard, "fun setGridItemWidthPx(widthPx: Int)")
|
||||||
assertSourceContains(seriesCard, "172f / 122f")
|
assertSourceContains(seriesCard, "172f / 122f")
|
||||||
|
|||||||
Reference in New Issue
Block a user