feat(home): 장르 크리에이터 그룹 바인딩을 추가한다
This commit is contained in:
@@ -25,6 +25,7 @@ import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentlyAct
|
|||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentDebutCreatorSection
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentDebutCreatorSection
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationUiState
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationUiState
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.model.RecommendedActivityType
|
import kr.co.vividnext.sodalive.v2.main.home.model.RecommendedActivityType
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.model.visibleHomeGenreCreatorGroups
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeAiCharacterAdapter
|
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeAiCharacterAdapter
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeBannerBinder
|
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeBannerBinder
|
||||||
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeCheerCreatorAdapter
|
import kr.co.vividnext.sodalive.v2.main.home.ui.HomeCheerCreatorAdapter
|
||||||
@@ -45,7 +46,7 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter()
|
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter()
|
||||||
private val firstAudioAdapter = HomeFirstAudioAdapter()
|
private val firstAudioAdapter = HomeFirstAudioAdapter()
|
||||||
private val aiCharacterAdapter = HomeAiCharacterAdapter()
|
private val aiCharacterAdapter = HomeAiCharacterAdapter()
|
||||||
private val genreCreatorAdapter = HomeGenreCreatorAdapter()
|
private val genreCreatorAdapter = HomeGenreCreatorAdapter { creatorIds -> onGenreFollowAllClick(creatorIds) }
|
||||||
private val cheerCreatorAdapter = HomeCheerCreatorAdapter()
|
private val cheerCreatorAdapter = HomeCheerCreatorAdapter()
|
||||||
private var bannerBinder: HomeBannerBinder? = null
|
private var bannerBinder: HomeBannerBinder? = null
|
||||||
private var onGenreFollowAllClick: (List<Long>) -> Unit = {}
|
private var onGenreFollowAllClick: (List<Long>) -> Unit = {}
|
||||||
@@ -127,20 +128,9 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun bindGenreCreatorSection(section: HomeRecommendationGenreCreatorSection) {
|
private fun bindGenreCreatorSection(section: HomeRecommendationGenreCreatorSection) {
|
||||||
val group = section.groups.firstOrNull { it.creators.isNotEmpty() }
|
val visibleGroups = section.visibleHomeGenreCreatorGroups()
|
||||||
binding.llHomeGenreCreatorSection.visibility = if (group == null) View.GONE else View.VISIBLE
|
binding.llHomeGenreCreatorSection.visibility = visibleGroups.toSectionVisibility()
|
||||||
if (group == null) {
|
genreCreatorAdapter.submitGroups(visibleGroups)
|
||||||
genreCreatorAdapter.submitItems(emptyList())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
binding.tvHomeGenreCreatorTitleGenre.text = group.genre
|
|
||||||
genreCreatorAdapter.submitItems(group.creators)
|
|
||||||
HomeFollowAllButtonBinder.bind(
|
|
||||||
view = binding.viewHomeGenreFollowAll.root,
|
|
||||||
creatorIds = group.creators.map { it.creatorId },
|
|
||||||
isFollowCompleted = group.isFollowCompleted,
|
|
||||||
onClick = onGenreFollowAllClick
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindCheerCreatorSection(section: HomeRecommendationCheerCreatorSection) {
|
private fun bindCheerCreatorSection(section: HomeRecommendationCheerCreatorSection) {
|
||||||
@@ -252,15 +242,18 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
genreCreators = HomeRecommendationGenreCreatorSection(
|
genreCreators = HomeRecommendationGenreCreatorSection(
|
||||||
listOf(HomeRecommendationGenreCreatorGroupUiModel("로맨스", sampleCreators(601L)))
|
listOf(
|
||||||
|
HomeRecommendationGenreCreatorGroupUiModel("로맨스", sampleCreators(601L, count = 8)),
|
||||||
|
HomeRecommendationGenreCreatorGroupUiModel("판타지", sampleCreators(701L, count = 8))
|
||||||
|
)
|
||||||
),
|
),
|
||||||
cheerCreators = HomeRecommendationCheerCreatorSection(sampleCreators(701L)),
|
cheerCreators = HomeRecommendationCheerCreatorSection(sampleCreators(701L)),
|
||||||
popularCommunityPosts = HomeRecommendationPopularCommunityPostSection(emptyList())
|
popularCommunityPosts = HomeRecommendationPopularCommunityPostSection(emptyList())
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun sampleCreators(startId: Long): List<HomeRecommendationCreatorUiModel> = listOf(
|
private fun sampleCreators(startId: Long, count: Int = 3): List<HomeRecommendationCreatorUiModel> {
|
||||||
HomeRecommendationCreatorUiModel(startId, "아린", null),
|
return List(count) { index ->
|
||||||
HomeRecommendationCreatorUiModel(startId + 1, "유나", null),
|
HomeRecommendationCreatorUiModel(startId + index, "크리에이터${index + 1}", null)
|
||||||
HomeRecommendationCreatorUiModel(startId + 2, "세이", null)
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ data class HomeRecommendationGenreCreatorSection(
|
|||||||
val groups: List<HomeRecommendationGenreCreatorGroupUiModel>
|
val groups: List<HomeRecommendationGenreCreatorGroupUiModel>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun HomeRecommendationGenreCreatorSection.visibleHomeGenreCreatorGroups(): List<HomeRecommendationGenreCreatorGroupUiModel> {
|
||||||
|
return groups.filter { it.creators.isNotEmpty() }.take(HOME_GENRE_CREATOR_MAX_GROUP_COUNT)
|
||||||
|
}
|
||||||
|
|
||||||
data class HomeRecommendationCheerCreatorSection(
|
data class HomeRecommendationCheerCreatorSection(
|
||||||
val items: List<HomeRecommendationCreatorUiModel>,
|
val items: List<HomeRecommendationCreatorUiModel>,
|
||||||
val isFollowCompleted: Boolean = false
|
val isFollowCompleted: Boolean = false
|
||||||
@@ -110,3 +114,5 @@ sealed interface HomeRecommendationPaidStatus {
|
|||||||
val price: Int
|
val price: Int
|
||||||
) : HomeRecommendationPaidStatus
|
) : HomeRecommendationPaidStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val HOME_GENRE_CREATOR_MAX_GROUP_COUNT = 5
|
||||||
|
|||||||
@@ -1,3 +1,137 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.main.home.ui
|
package kr.co.vividnext.sodalive.v2.main.home.ui
|
||||||
|
|
||||||
class HomeGenreCreatorAdapter : HomeCreatorProfileAdapter()
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.GridLayout
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCreatorUiModel
|
||||||
|
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorGroupUiModel
|
||||||
|
|
||||||
|
class HomeGenreCreatorAdapter(
|
||||||
|
private val onFollowAllClick: (List<Long>) -> Unit
|
||||||
|
) : RecyclerView.Adapter<HomeGenreCreatorAdapter.GenreCreatorGroupViewHolder>() {
|
||||||
|
private var groups: List<HomeRecommendationGenreCreatorGroupUiModel> = emptyList()
|
||||||
|
|
||||||
|
fun submitGroups(groups: List<HomeRecommendationGenreCreatorGroupUiModel>) {
|
||||||
|
this.groups = groups.filter { it.creators.isNotEmpty() }.take(MAX_GROUP_COUNT)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenreCreatorGroupViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_home_genre_creator_group, parent, false)
|
||||||
|
view.layoutParams = genreGroupLayoutParams(parent)
|
||||||
|
return GenreCreatorGroupViewHolder(view, parent, onFollowAllClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: GenreCreatorGroupViewHolder, position: Int) {
|
||||||
|
holder.bind(groups[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = groups.size
|
||||||
|
|
||||||
|
class GenreCreatorGroupViewHolder(
|
||||||
|
itemView: View,
|
||||||
|
private val parent: ViewGroup,
|
||||||
|
private val onFollowAllClick: (List<Long>) -> Unit
|
||||||
|
) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
private val genreText = itemView.findViewById<TextView>(R.id.tv_home_genre_creator_group_title_genre)
|
||||||
|
private val creatorGrid = itemView.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles)
|
||||||
|
private val followAllButton = itemView.findViewById<View>(R.id.view_home_genre_group_follow_all)
|
||||||
|
|
||||||
|
fun bind(group: HomeRecommendationGenreCreatorGroupUiModel) {
|
||||||
|
genreText.text = group.genre
|
||||||
|
val creators = group.creators.take(CREATOR_COUNT_PER_GROUP)
|
||||||
|
bindCreators(creators, calculateProfileSize())
|
||||||
|
creatorGrid.post {
|
||||||
|
bindCreators(creators, calculateProfileSize())
|
||||||
|
}
|
||||||
|
HomeFollowAllButtonBinder.bind(
|
||||||
|
view = followAllButton,
|
||||||
|
creatorIds = group.creators.map { it.creatorId },
|
||||||
|
isFollowCompleted = group.isFollowCompleted,
|
||||||
|
onClick = onFollowAllClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateProfileSize(): Int {
|
||||||
|
val gridWidth = creatorGrid.width.takeIf { it > 0 }
|
||||||
|
?: (resolvedCardWidth() - itemView.paddingStart - itemView.paddingEnd)
|
||||||
|
val totalColumnGap = itemView.resources.getDimensionPixelSize(R.dimen.spacing_14) * (GENRE_GRID_COLUMN_COUNT - 1)
|
||||||
|
return (gridWidth - totalColumnGap) / GENRE_GRID_COLUMN_COUNT
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolvedCardWidth(): Int {
|
||||||
|
val parentContentWidth = parentContentWidth()
|
||||||
|
itemView.width.takeIf { it > 0 }?.let { return minOf(it, parentContentWidth) }
|
||||||
|
itemView.layoutParams.width.takeIf { it > 0 }?.let { return minOf(it, parentContentWidth) }
|
||||||
|
|
||||||
|
parentContentWidth.takeIf { it > 0 }?.let { return it }
|
||||||
|
|
||||||
|
return itemView.resources.displayMetrics.widthPixels - parentHorizontalInset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parentContentWidth(): Int {
|
||||||
|
return parent.width - parentHorizontalInset()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parentHorizontalInset(): Int {
|
||||||
|
val paddingInset = parent.paddingStart + parent.paddingEnd
|
||||||
|
return paddingInset.takeIf { it > 0 } ?: itemView.resources.getDimensionPixelSize(R.dimen.spacing_28)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindCreators(creators: List<HomeRecommendationCreatorUiModel>, profileSize: Int) {
|
||||||
|
creatorGrid.removeAllViews()
|
||||||
|
creators.forEachIndexed { index, creator ->
|
||||||
|
val profileView = LayoutInflater.from(creatorGrid.context).inflate(
|
||||||
|
R.layout.item_home_genre_creator_profile,
|
||||||
|
creatorGrid,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
val row = index / GENRE_GRID_COLUMN_COUNT
|
||||||
|
val column = index % GENRE_GRID_COLUMN_COUNT
|
||||||
|
profileView.layoutParams = GridLayout.LayoutParams(
|
||||||
|
GridLayout.spec(row),
|
||||||
|
GridLayout.spec(column)
|
||||||
|
).apply {
|
||||||
|
width = profileSize
|
||||||
|
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
if (column < GENRE_GRID_COLUMN_COUNT - 1) {
|
||||||
|
marginEnd = profileView.resources.getDimensionPixelSize(R.dimen.spacing_14)
|
||||||
|
}
|
||||||
|
if (row > 0) {
|
||||||
|
topMargin = profileView.resources.getDimensionPixelSize(R.dimen.spacing_14)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profileView.findViewById<ImageView>(R.id.iv_home_genre_creator_profile).apply {
|
||||||
|
if (creator.profileImage == null) {
|
||||||
|
setImageDrawable(null)
|
||||||
|
} else {
|
||||||
|
loadUrl(creator.profileImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profileView.findViewById<TextView>(R.id.tv_home_genre_creator_profile_nickname).text = creator.nickname
|
||||||
|
creatorGrid.addView(profileView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MAX_GROUP_COUNT = 5
|
||||||
|
private const val CREATOR_COUNT_PER_GROUP = 8
|
||||||
|
private const val GENRE_GRID_COLUMN_COUNT = 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun genreGroupLayoutParams(parent: ViewGroup): RecyclerView.LayoutParams {
|
||||||
|
val paddingInset = parent.paddingStart + parent.paddingEnd
|
||||||
|
val horizontalInset = paddingInset.takeIf { it > 0 } ?: parent.resources.getDimensionPixelSize(R.dimen.spacing_28)
|
||||||
|
val width = (parent.width - horizontalInset).takeIf { it > 0 } ?: ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
return RecyclerView.LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
|
||||||
|
marginEnd = parent.resources.getDimensionPixelSize(R.dimen.spacing_4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user