diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt index 04ad1a29..ab6a5a37 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt @@ -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.HomeRecommendationUiState 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.HomeBannerBinder import kr.co.vividnext.sodalive.v2.main.home.ui.HomeCheerCreatorAdapter @@ -45,7 +46,7 @@ class HomeMainFragment : BaseFragment( private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter() private val firstAudioAdapter = HomeFirstAudioAdapter() private val aiCharacterAdapter = HomeAiCharacterAdapter() - private val genreCreatorAdapter = HomeGenreCreatorAdapter() + private val genreCreatorAdapter = HomeGenreCreatorAdapter { creatorIds -> onGenreFollowAllClick(creatorIds) } private val cheerCreatorAdapter = HomeCheerCreatorAdapter() private var bannerBinder: HomeBannerBinder? = null private var onGenreFollowAllClick: (List) -> Unit = {} @@ -127,20 +128,9 @@ class HomeMainFragment : BaseFragment( } private fun bindGenreCreatorSection(section: HomeRecommendationGenreCreatorSection) { - val group = section.groups.firstOrNull { it.creators.isNotEmpty() } - binding.llHomeGenreCreatorSection.visibility = if (group == null) View.GONE else View.VISIBLE - if (group == null) { - 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 - ) + val visibleGroups = section.visibleHomeGenreCreatorGroups() + binding.llHomeGenreCreatorSection.visibility = visibleGroups.toSectionVisibility() + genreCreatorAdapter.submitGroups(visibleGroups) } private fun bindCheerCreatorSection(section: HomeRecommendationCheerCreatorSection) { @@ -252,15 +242,18 @@ class HomeMainFragment : BaseFragment( ) ), genreCreators = HomeRecommendationGenreCreatorSection( - listOf(HomeRecommendationGenreCreatorGroupUiModel("로맨스", sampleCreators(601L))) + listOf( + HomeRecommendationGenreCreatorGroupUiModel("로맨스", sampleCreators(601L, count = 8)), + HomeRecommendationGenreCreatorGroupUiModel("판타지", sampleCreators(701L, count = 8)) + ) ), cheerCreators = HomeRecommendationCheerCreatorSection(sampleCreators(701L)), popularCommunityPosts = HomeRecommendationPopularCommunityPostSection(emptyList()) ) - private fun sampleCreators(startId: Long): List = listOf( - HomeRecommendationCreatorUiModel(startId, "아린", null), - HomeRecommendationCreatorUiModel(startId + 1, "유나", null), - HomeRecommendationCreatorUiModel(startId + 2, "세이", null) - ) + private fun sampleCreators(startId: Long, count: Int = 3): List { + return List(count) { index -> + HomeRecommendationCreatorUiModel(startId + index, "크리에이터${index + 1}", null) + } + } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt index 3bf8170d..82a81e9d 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt @@ -32,6 +32,10 @@ data class HomeRecommendationGenreCreatorSection( val groups: List ) +fun HomeRecommendationGenreCreatorSection.visibleHomeGenreCreatorGroups(): List { + return groups.filter { it.creators.isNotEmpty() }.take(HOME_GENRE_CREATOR_MAX_GROUP_COUNT) +} + data class HomeRecommendationCheerCreatorSection( val items: List, val isFollowCompleted: Boolean = false @@ -110,3 +114,5 @@ sealed interface HomeRecommendationPaidStatus { val price: Int ) : HomeRecommendationPaidStatus } + +private const val HOME_GENRE_CREATOR_MAX_GROUP_COUNT = 5 diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt index ccac62cd..00714f3f 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt @@ -1,3 +1,137 @@ 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) -> Unit +) : RecyclerView.Adapter() { + private var groups: List = emptyList() + + fun submitGroups(groups: List) { + 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) -> Unit + ) : RecyclerView.ViewHolder(itemView) { + private val genreText = itemView.findViewById(R.id.tv_home_genre_creator_group_title_genre) + private val creatorGrid = itemView.findViewById(R.id.gl_home_genre_creator_profiles) + private val followAllButton = itemView.findViewById(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, 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(R.id.iv_home_genre_creator_profile).apply { + if (creator.profileImage == null) { + setImageDrawable(null) + } else { + loadUrl(creator.profileImage) + } + } + profileView.findViewById(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) + } +}