fix(home): 장르 크리에이터 섹션을 숨긴다

This commit is contained in:
2026-07-12 05:29:37 +09:00
parent 6ed2be4173
commit 4debf4654d
5 changed files with 47 additions and 77 deletions

View File

@@ -41,7 +41,6 @@ import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacter
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerSection import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerUiModel import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCheerCreatorSection import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCheerCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorGroupUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorSection import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveSection import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveUiModel import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveUiModel
@@ -57,7 +56,6 @@ import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharact
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterRoute import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationAiCharacterRoute
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorIntent import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorIntent
import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorRoute import kr.co.vividnext.sodalive.v2.main.home.model.toHomeRecommendationRecentlyActiveCreatorRoute
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.HomeBusinessInfoBinder import kr.co.vividnext.sodalive.v2.main.home.ui.HomeBusinessInfoBinder
@@ -100,10 +98,7 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) } private val recentActivityCreatorAdapter = HomeRecentActivityCreatorAdapter { onRecentActivityClick(it) }
private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) } private val recentDebutCreatorAdapter = HomeRecentDebutCreatorAdapter { openCreatorProfile(it.creatorId) }
private val aiCharacterAdapter = HomeAiCharacterAdapter { onAiCharacterClick(it) } private val aiCharacterAdapter = HomeAiCharacterAdapter { onAiCharacterClick(it) }
private val genreCreatorAdapter = HomeGenreCreatorAdapter( private val genreCreatorAdapter = HomeGenreCreatorAdapter(onFollowAllClick = {})
onFollowAllClick = { creatorIds -> onGenreFollowAllClick(creatorIds) },
onCreatorClick = { creator -> openCreatorProfile(creator.creatorId) }
)
private val cheerCreatorAdapter = HomeCheerCreatorAdapter( private val cheerCreatorAdapter = HomeCheerCreatorAdapter(
onFollowAllClick = { creatorIds -> onCheerFollowAllClick(creatorIds) }, onFollowAllClick = { creatorIds -> onCheerFollowAllClick(creatorIds) },
onCreatorClick = { creator -> openCreatorProfile(creator.creatorId) } onCreatorClick = { creator -> openCreatorProfile(creator.creatorId) }
@@ -119,7 +114,6 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
private val followingScheduleAdapter = HomeFollowingScheduleAdapter { onFollowingScheduleClick(it) } private val followingScheduleAdapter = HomeFollowingScheduleAdapter { onFollowingScheduleClick(it) }
private val followingNewsAdapter = HomeFollowingNewsAdapter { onFollowingNewsClick(it) } private val followingNewsAdapter = HomeFollowingNewsAdapter { onFollowingNewsClick(it) }
private var bannerBinder: HomeBannerBinder? = null private var bannerBinder: HomeBannerBinder? = null
private var onGenreFollowAllClick: (List<Long>) -> Unit = {}
private var onCheerFollowAllClick: (List<Long>) -> Unit = {} private var onCheerFollowAllClick: (List<Long>) -> Unit = {}
private var hasLoadedCreatorRankings = false private var hasLoadedCreatorRankings = false
private var hasLoadedFollowing = false private var hasLoadedFollowing = false
@@ -218,9 +212,6 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
adapter = popularCommunityAdapter adapter = popularCommunityAdapter
} }
onGenreFollowAllClick = { creatorIds ->
homeRecommendationViewModel.followCreators(genreSectionKey(creatorIds), creatorIds)
}
onCheerFollowAllClick = { creatorIds -> onCheerFollowAllClick = { creatorIds ->
homeRecommendationViewModel.followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds) homeRecommendationViewModel.followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds)
} }
@@ -458,9 +449,8 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
} }
private fun bindGenreCreatorSection(section: HomeRecommendationGenreCreatorSection) { private fun bindGenreCreatorSection(section: HomeRecommendationGenreCreatorSection) {
val visibleGroups = section.visibleHomeGenreCreatorGroups() binding.llHomeGenreCreatorSection.visibility = View.GONE
binding.llHomeGenreCreatorSection.visibility = visibleGroups.toSectionVisibility() genreCreatorAdapter.submitGroups(emptyList())
genreCreatorAdapter.submitGroups(visibleGroups)
} }
private fun bindCheerCreatorSection(section: HomeRecommendationCheerCreatorSection) { private fun bindCheerCreatorSection(section: HomeRecommendationCheerCreatorSection) {
@@ -634,19 +624,6 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
?: toastMessage.resId?.let { resId -> showToast(getString(resId)) } ?: toastMessage.resId?.let { resId -> showToast(getString(resId)) }
} }
private fun genreSectionKey(creatorIds: List<Long>): String {
val group = currentVisibleGenreGroups().firstOrNull { group ->
group.creators.map { it.creatorId } == creatorIds
}
return "$SECTION_KEY_GENRE_CREATORS:${group?.genre.orEmpty()}"
}
private fun currentVisibleGenreGroups(): List<HomeRecommendationGenreCreatorGroupUiModel> {
val content = homeRecommendationViewModel.recommendationStateLiveData.value as? HomeRecommendationUiState.Content
?: return emptyList()
return content.genreCreators.visibleHomeGenreCreatorGroups()
}
private fun emptyHomeRecommendationContent(): HomeRecommendationUiState.Content { private fun emptyHomeRecommendationContent(): HomeRecommendationUiState.Content {
return HomeRecommendationUiState.Content( return HomeRecommendationUiState.Content(
lives = HomeRecommendationLiveSection(emptyList()), lives = HomeRecommendationLiveSection(emptyList()),
@@ -667,7 +644,6 @@ class HomeMainFragment : BaseFragment<FragmentV2MainHomeBinding>(
const val HOME_TAB_RANKING = 1 const val HOME_TAB_RANKING = 1
const val HOME_TAB_FOLLOWING = 2 const val HOME_TAB_FOLLOWING = 2
const val SECTION_KEY_CHEER_CREATORS = "cheerCreators" const val SECTION_KEY_CHEER_CREATORS = "cheerCreators"
const val SECTION_KEY_GENRE_CREATORS = "genreCreators"
} }
} }

View File

@@ -11,7 +11,6 @@ import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.ToastMessage import kr.co.vividnext.sodalive.common.ToastMessage
import kr.co.vividnext.sodalive.v2.main.home.data.FollowRecommendedCreatorsRequest import kr.co.vividnext.sodalive.v2.main.home.data.FollowRecommendedCreatorsRequest
import kr.co.vividnext.sodalive.v2.main.home.data.HomeRecommendationRepository import kr.co.vividnext.sodalive.v2.main.home.data.HomeRecommendationRepository
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorSection
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.toContent import kr.co.vividnext.sodalive.v2.main.home.model.toContent
@@ -94,22 +93,10 @@ class HomeRecommendationViewModel(
private fun updateFollowCompleted(sectionKey: String) { private fun updateFollowCompleted(sectionKey: String) {
val content = _recommendationStateLiveData.value as? HomeRecommendationUiState.Content ?: return val content = _recommendationStateLiveData.value as? HomeRecommendationUiState.Content ?: return
_recommendationStateLiveData.value = when (sectionKey) { if (sectionKey != SECTION_KEY_CHEER_CREATORS) return
SECTION_KEY_CHEER_CREATORS -> content.copy( _recommendationStateLiveData.value = content.copy(
cheerCreators = content.cheerCreators.copy(isFollowCompleted = true) cheerCreators = content.cheerCreators.copy(isFollowCompleted = true)
) )
else -> content.copy(
genreCreators = HomeRecommendationGenreCreatorSection(
groups = content.genreCreators.groups.map { group ->
if (group.genre == sectionKey || sectionKey == "$SECTION_KEY_GENRE_CREATORS:${group.genre}") {
group.copy(isFollowCompleted = true)
} else {
group
}
}
)
)
}
} }
private fun showUnknownError(message: String?) { private fun showUnknownError(message: String?) {
@@ -121,6 +108,5 @@ class HomeRecommendationViewModel(
private companion object { private companion object {
const val SECTION_KEY_CHEER_CREATORS = "cheerCreators" const val SECTION_KEY_CHEER_CREATORS = "cheerCreators"
const val SECTION_KEY_GENRE_CREATORS = "genreCreators"
} }
} }

View File

@@ -19,7 +19,6 @@ sealed interface HomeRecommendationUiState {
recentlyActiveCreators.items, recentlyActiveCreators.items,
recentDebutCreators.items, recentDebutCreators.items,
aiCharacters.items, aiCharacters.items,
genreCreators.groups,
cheerCreators.items, cheerCreators.items,
popularCommunityPosts.items popularCommunityPosts.items
).all { it.isEmpty() } ).all { it.isEmpty() }

View File

@@ -141,7 +141,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_48" android:layout_marginTop="@dimen/spacing_48"
android:orientation="vertical"> android:orientation="vertical"
android:visibility="gone">
<kr.co.vividnext.sodalive.v2.main.home.ui.HomePagerSnapRecyclerView <kr.co.vividnext.sodalive.v2.main.home.ui.HomePagerSnapRecyclerView
android:id="@+id/rv_home_genre_creators" android:id="@+id/rv_home_genre_creators"

View File

@@ -40,6 +40,12 @@ import kr.co.vividnext.sodalive.v2.main.home.data.HomeLiveItem
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerSection import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerRoute import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerRoute
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerUiModel import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationBannerUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationUiState
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentlyActiveCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationRecentDebutCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationLiveSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCheerCreatorSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterSection
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterRoute import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationAiCharacterRoute
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCreatorUiModel import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationCreatorUiModel
import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorGroupUiModel import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationGenreCreatorGroupUiModel
@@ -119,7 +125,6 @@ class HomeMainFragmentLayoutTest {
R.id.ll_home_recent_activity_section, R.id.ll_home_recent_activity_section,
R.id.ll_home_recent_debut_section, R.id.ll_home_recent_debut_section,
R.id.ll_home_ai_character_section, R.id.ll_home_ai_character_section,
R.id.ll_home_genre_creator_section,
R.id.ll_home_cheer_creator_section, R.id.ll_home_cheer_creator_section,
R.id.ll_home_popular_community_section R.id.ll_home_popular_community_section
) )
@@ -128,7 +133,6 @@ class HomeMainFragmentLayoutTest {
R.id.rv_home_recent_activity_creators, R.id.rv_home_recent_activity_creators,
R.id.rv_home_recent_debut_creators, R.id.rv_home_recent_debut_creators,
R.id.rv_home_ai_characters, R.id.rv_home_ai_characters,
R.id.rv_home_genre_creators,
R.id.rv_home_cheer_creators R.id.rv_home_cheer_creators
) )
@@ -144,6 +148,37 @@ class HomeMainFragmentLayoutTest {
assertNotNull(root.findViewById<View>(R.id.ll_home_business_info)) assertNotNull(root.findViewById<View>(R.id.ll_home_business_info))
} }
@Test
fun `home recommendation does not display genre creator section`() {
val root = inflateView(R.layout.fragment_v2_main_home)
val layoutSource = homeMainLayoutSource()
val source = homeMainFragmentSource()
val genreSection = root.findViewById<LinearLayout>(R.id.ll_home_genre_creator_section)
assertEquals(View.GONE, genreSection.visibility)
assertTrue(layoutSource.contains("android:id=\"@+id/ll_home_genre_creator_section\""))
assertTrue(layoutSource.contains("android:visibility=\"gone\""))
assertTrue(source.contains("private fun bindGenreCreatorSection(section: HomeRecommendationGenreCreatorSection)"))
assertTrue(source.contains("binding.llHomeGenreCreatorSection.visibility = View.GONE"))
assertTrue(source.contains("genreCreatorAdapter.submitGroups(emptyList())"))
}
@Test
fun `home recommendation empty state ignores hidden genre creators`() {
val content = HomeRecommendationUiState.Content(
lives = HomeRecommendationLiveSection(emptyList()),
banners = HomeRecommendationBannerSection(emptyList()),
recentlyActiveCreators = HomeRecommendationRecentlyActiveCreatorSection(emptyList()),
recentDebutCreators = HomeRecommendationRecentDebutCreatorSection(emptyList()),
aiCharacters = HomeRecommendationAiCharacterSection(emptyList()),
genreCreators = HomeRecommendationGenreCreatorSection(listOf(genreGroup("genre", genreCreators(1L)))),
cheerCreators = HomeRecommendationCheerCreatorSection(emptyList()),
popularCommunityPosts = HomeRecommendationPopularCommunityPostSection(emptyList())
)
assertTrue(content.isEmpty)
}
@Test @Test
fun `home phase6 item layouts expose required binding views`() { fun `home phase6 item layouts expose required binding views`() {
val recentActivity = inflateView(R.layout.item_home_recent_activity_creator) val recentActivity = inflateView(R.layout.item_home_recent_activity_creator)
@@ -437,17 +472,6 @@ class HomeMainFragmentLayoutTest {
assertEquals(14f, nickname.textSize / nickname.resources.displayMetrics.scaledDensity) assertEquals(14f, nickname.textSize / nickname.resources.displayMetrics.scaledDensity)
} }
@Test
fun `home genre creator section uses pager snapping list`() {
val root = inflateView(R.layout.fragment_v2_main_home)
val genreList = root.findViewById<RecyclerView>(R.id.rv_home_genre_creators)
assertEquals(ViewGroup.LayoutParams.WRAP_CONTENT, genreList.layoutParams.height)
assertEquals(14.dpToPx(), genreList.paddingStart)
assertEquals(14.dpToPx(), genreList.paddingEnd)
assertNotNull(genreList.onFlingListener)
}
@Test @Test
fun `genre creator adapter renders max five non empty genre groups`() { fun `genre creator adapter renders max five non empty genre groups`() {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
@@ -474,22 +498,6 @@ class HomeMainFragmentLayoutTest {
assertEquals(8, viewHolder.itemView.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles).childCount) assertEquals(8, viewHolder.itemView.findViewById<GridLayout>(R.id.gl_home_genre_creator_profiles).childCount)
} }
@Test
fun `genre creator adapter follow all uses ids from its page group`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val parent = RecyclerView(context)
parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
val clickedCreatorIds = mutableListOf<Long>()
val adapter = HomeGenreCreatorAdapter(onFollowAllClick = { clickedCreatorIds.addAll(it) })
adapter.submitGroups(listOf(genreGroup("genre", genreCreators(10L))))
val viewHolder = adapter.onCreateViewHolder(parent, 0)
adapter.onBindViewHolder(viewHolder, 0)
viewHolder.itemView.findViewById<View>(R.id.view_home_genre_group_follow_all).performClick()
assertEquals((10L..17L).toList(), clickedCreatorIds)
}
@Test @Test
fun `cheer creator group item matches figma card structure`() { fun `cheer creator group item matches figma card structure`() {
val group = inflateViewWithParent(R.layout.item_home_cheer_creator_group) val group = inflateViewWithParent(R.layout.item_home_cheer_creator_group)
@@ -1257,7 +1265,7 @@ class HomeMainFragmentLayoutTest {
assertTrue(source.contains("toastLiveData.observe(viewLifecycleOwner)")) assertTrue(source.contains("toastLiveData.observe(viewLifecycleOwner)"))
assertTrue(source.contains("showToast")) assertTrue(source.contains("showToast"))
assertTrue(source.contains("followCreators(SECTION_KEY_CHEER_CREATORS")) assertTrue(source.contains("followCreators(SECTION_KEY_CHEER_CREATORS"))
assertTrue(source.contains("followCreators(genreSectionKey")) assertFalse(source.contains("followCreators(genreSectionKey"))
} }
@Test @Test