feat(home): 장르 크리에이터 그룹 바인딩을 추가한다

This commit is contained in:
2026-06-04 19:38:03 +09:00
parent c714a9d4c8
commit c733796aeb
3 changed files with 155 additions and 22 deletions

View File

@@ -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) }
) }
} }

View File

@@ -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

View File

@@ -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)
}
}