feat(home): 라이브 섹션 전체 아이템을 추가한다
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.main.home.ui
|
package kr.co.vividnext.sodalive.v2.main.home.ui
|
||||||
|
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
@@ -11,10 +14,12 @@ import kr.co.vividnext.sodalive.v2.widget.livethumbnail.LiveThumbnailSimpleView
|
|||||||
|
|
||||||
class HomeLiveAdapter : RecyclerView.Adapter<HomeLiveAdapter.LiveViewHolder>() {
|
class HomeLiveAdapter : RecyclerView.Adapter<HomeLiveAdapter.LiveViewHolder>() {
|
||||||
private var items: List<HomeRecommendationLiveUiModel> = emptyList()
|
private var items: List<HomeRecommendationLiveUiModel> = emptyList()
|
||||||
|
private var hasMore: Boolean = false
|
||||||
private var onClick: ((HomeRecommendationLiveUiModel) -> Unit)? = null
|
private var onClick: ((HomeRecommendationLiveUiModel) -> Unit)? = null
|
||||||
|
|
||||||
fun submitItems(items: List<HomeRecommendationLiveUiModel>) {
|
fun submitItems(items: List<HomeRecommendationLiveUiModel>) {
|
||||||
this.items = items
|
this.items = items.take(MAX_VISIBLE_LIVE_COUNT)
|
||||||
|
hasMore = items.size > MAX_VISIBLE_LIVE_COUNT
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,20 +28,54 @@ class HomeLiveAdapter : RecyclerView.Adapter<HomeLiveAdapter.LiveViewHolder>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LiveViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LiveViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.view_live_thumbnail_simple, parent, false)
|
return if (viewType == VIEW_TYPE_MORE) {
|
||||||
view.layoutParams = recyclerItemLayoutParams(parent)
|
MoreViewHolder(createMoreView(parent))
|
||||||
return LiveViewHolder(view as LiveThumbnailSimpleView)
|
} else {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.view_live_thumbnail_simple, parent, false)
|
||||||
|
view.layoutParams = liveItemLayoutParams(parent)
|
||||||
|
LiveItemViewHolder(view as LiveThumbnailSimpleView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: LiveViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: LiveViewHolder, position: Int) {
|
||||||
holder.bind(items[position], onClick)
|
when (holder) {
|
||||||
|
is LiveItemViewHolder -> holder.bind(items[position], onClick)
|
||||||
|
is MoreViewHolder -> holder.bind()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size + if (hasMore) 1 else 0
|
||||||
|
|
||||||
class LiveViewHolder(
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return if (hasMore && position == itemCount - 1) VIEW_TYPE_MORE else VIEW_TYPE_LIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun liveItemLayoutParams(parent: ViewGroup): RecyclerView.LayoutParams {
|
||||||
|
return RecyclerView.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply { marginEnd = parent.resources.getDimensionPixelSize(R.dimen.spacing_14) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMoreView(parent: ViewGroup): TextView {
|
||||||
|
return TextView(parent.context).apply {
|
||||||
|
layoutParams = RecyclerView.LayoutParams(
|
||||||
|
parent.resources.getDimensionPixelSize(R.dimen.home_live_more_width),
|
||||||
|
parent.resources.getDimensionPixelSize(R.dimen.home_live_row_height)
|
||||||
|
)
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
setBackgroundResource(R.color.black)
|
||||||
|
setText(R.string.screen_home_theme_all)
|
||||||
|
setTextAppearance(R.style.Typography_Body5)
|
||||||
|
setTextColor(parent.context.getColor(R.color.soda_400))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class LiveViewHolder(view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
|
class LiveItemViewHolder(
|
||||||
private val view: LiveThumbnailSimpleView
|
private val view: LiveThumbnailSimpleView
|
||||||
) : RecyclerView.ViewHolder(view) {
|
) : LiveViewHolder(view) {
|
||||||
fun bind(
|
fun bind(
|
||||||
item: HomeRecommendationLiveUiModel,
|
item: HomeRecommendationLiveUiModel,
|
||||||
onClick: ((HomeRecommendationLiveUiModel) -> Unit)?
|
onClick: ((HomeRecommendationLiveUiModel) -> Unit)?
|
||||||
@@ -54,4 +93,18 @@ class HomeLiveAdapter : RecyclerView.Adapter<HomeLiveAdapter.LiveViewHolder>() {
|
|||||||
view.setOnLiveThumbnailClick(if (onClick == null) null else { _: LiveThumbnailItem -> onClick.invoke(item) })
|
view.setOnLiveThumbnailClick(if (onClick == null) null else { _: LiveThumbnailItem -> onClick.invoke(item) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MoreViewHolder(
|
||||||
|
private val view: TextView
|
||||||
|
) : LiveViewHolder(view) {
|
||||||
|
fun bind() {
|
||||||
|
view.setOnClickListener(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MAX_VISIBLE_LIVE_COUNT = 20
|
||||||
|
private const val VIEW_TYPE_LIVE = 0
|
||||||
|
private const val VIEW_TYPE_MORE = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,19 +39,20 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="@dimen/spacing_32">
|
android:paddingBottom="@dimen/spacing_28">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/ll_home_live_section"
|
android:id="@+id/ll_home_live_section"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_marginTop="@dimen/spacing_12"
|
||||||
android:paddingTop="@dimen/spacing_24">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rv_home_lives"
|
android:id="@+id/rv_home_lives"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="120dp"
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingHorizontal="@dimen/spacing_20"
|
android:paddingHorizontal="@dimen/spacing_20"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingTop="@dimen/spacing_24">
|
android:paddingTop="@dimen/spacing_28">
|
||||||
|
|
||||||
<kr.co.vividnext.sodalive.v2.widget.banner.BannerView
|
<kr.co.vividnext.sodalive.v2.widget.banner.BannerView
|
||||||
android:id="@+id/rv_home_banners"
|
android:id="@+id/rv_home_banners"
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
<dimen name="spacing_32">32dp</dimen>
|
<dimen name="spacing_32">32dp</dimen>
|
||||||
<dimen name="spacing_48">48dp</dimen>
|
<dimen name="spacing_48">48dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="home_live_more_width">58dp</dimen>
|
||||||
|
<dimen name="home_live_row_height">102dp</dimen>
|
||||||
|
|
||||||
<dimen name="radius_4">4dp</dimen>
|
<dimen name="radius_4">4dp</dimen>
|
||||||
<dimen name="radius_8">8dp</dimen>
|
<dimen name="radius_8">8dp</dimen>
|
||||||
<dimen name="radius_14">14dp</dimen>
|
<dimen name="radius_14">14dp</dimen>
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ package kr.co.vividnext.sodalive.v2.main.home
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.widget.NestedScrollView
|
import androidx.core.widget.NestedScrollView
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveUiModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeLiveAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.widget.TextTabBarView
|
import kr.co.vividnext.sodalive.v2.widget.TextTabBarView
|
||||||
import kr.co.vividnext.sodalive.v2.widget.banner.BannerView
|
import kr.co.vividnext.sodalive.v2.widget.banner.BannerView
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
@@ -119,6 +124,56 @@ class HomeMainFragmentLayoutTest {
|
|||||||
assertEquals(View.GONE, popularCommunitySection.visibility)
|
assertEquals(View.GONE, popularCommunitySection.visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home live section matches figma row dimensions`() {
|
||||||
|
val root = inflateView(R.layout.fragment_v2_main_home)
|
||||||
|
val liveList = root.findViewById<RecyclerView>(R.id.rv_home_lives)
|
||||||
|
|
||||||
|
assertNotNull(liveList)
|
||||||
|
assertEquals(102.dpToPx(), liveList.layoutParams.height)
|
||||||
|
assertEquals(20.dpToPx(), liveList.paddingStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home live adapter uses figma item gap`() {
|
||||||
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
|
val parent = RecyclerView(context)
|
||||||
|
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
|
||||||
|
val viewHolder = HomeLiveAdapter().onCreateViewHolder(parent, 0)
|
||||||
|
val layoutParams = viewHolder.itemView.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
|
|
||||||
|
assertEquals(14.dpToPx(), layoutParams.marginEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home live adapter appends all item after twenty lives`() {
|
||||||
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
|
val parent = RecyclerView(context)
|
||||||
|
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
|
||||||
|
val adapter = HomeLiveAdapter()
|
||||||
|
|
||||||
|
adapter.submitItems((1L..21L).map(::liveItem))
|
||||||
|
val viewHolder = adapter.onCreateViewHolder(parent, adapter.getItemViewType(20))
|
||||||
|
adapter.onBindViewHolder(viewHolder, 20)
|
||||||
|
val moreText = viewHolder.itemView as TextView
|
||||||
|
|
||||||
|
assertEquals(21, adapter.itemCount)
|
||||||
|
assertEquals(context.getString(R.string.screen_home_theme_all), moreText.text.toString())
|
||||||
|
assertEquals(58.dpToPx(), moreText.layoutParams.width)
|
||||||
|
assertEquals(102.dpToPx(), moreText.layoutParams.height)
|
||||||
|
assertEquals(context.getColor(R.color.soda_400), moreText.currentTextColor)
|
||||||
|
assertEquals(context.getColor(R.color.black), (moreText.background as ColorDrawable).color)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `home live adapter caps lives before all item`() {
|
||||||
|
val adapter = HomeLiveAdapter()
|
||||||
|
|
||||||
|
adapter.submitItems((1L..22L).map(::liveItem))
|
||||||
|
|
||||||
|
assertEquals(21, adapter.itemCount)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `home layout uses section title components and custom genre title row`() {
|
fun `home layout uses section title components and custom genre title row`() {
|
||||||
val root = inflateView(R.layout.fragment_v2_main_home)
|
val root = inflateView(R.layout.fragment_v2_main_home)
|
||||||
@@ -193,4 +248,15 @@ class HomeMainFragmentLayoutTest {
|
|||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
return (this * context.resources.displayMetrics.density).toInt()
|
return (this * context.resources.displayMetrics.density).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun liveItem(id: Long): HomeRecommendationLiveUiModel {
|
||||||
|
return HomeRecommendationLiveUiModel(
|
||||||
|
liveId = id,
|
||||||
|
creatorId = id,
|
||||||
|
imageUrl = null,
|
||||||
|
title = "title$id",
|
||||||
|
creatorNickname = "creator$id",
|
||||||
|
beginDateTime = null
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user