diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt index 19c2f4b0..f6fa5007 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt @@ -47,11 +47,13 @@ import kr.co.vividnext.sodalive.mypage.MyPageFragment import kr.co.vividnext.sodalive.settings.event.EventDetailActivity import kr.co.vividnext.sodalive.settings.notification.NotificationSettingsDialog import kr.co.vividnext.sodalive.user.login.LoginActivity +import kr.co.vividnext.sodalive.v2.common.data.ContentSort import kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivity import kr.co.vividnext.sodalive.v2.main.chat.ChatMainFragment import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomFilter import kr.co.vividnext.sodalive.v2.main.content.ContentMainFragment +import kr.co.vividnext.sodalive.v2.main.content.data.MainContentAllType import kr.co.vividnext.sodalive.v2.main.home.HomeMainFragment import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect @@ -168,6 +170,16 @@ class MainV2Activity : BaseActivity(ActivityMainV2Binding selectChatTabWithLoginGuard() } + fun openContentAllTab(type: MainContentAllType, sort: ContentSort = ContentSort.LATEST) { + if (viewModel.currentTab.value != MainV2Tab.CONTENT) { + viewModel.clickTab(MainV2Tab.CONTENT) + changeFragment(MainV2Tab.CONTENT) + } + + (supportFragmentManager.findFragmentByTag(MainV2Tab.CONTENT.toString()) as? ContentMainFragment) + ?.selectAllTab(type, sort) + } + private fun consumeInitialChatFilter(): ChatRoomFilter? { return intent.getStringExtra(EXTRA_CHAT_FILTER) ?.let { runCatching { ChatRoomFilter.valueOf(it) }.getOrNull() } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt index ce1b5214..c5234996 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt @@ -26,6 +26,7 @@ import kr.co.vividnext.sodalive.home.SeriesPublishedDaysOfWeek import kr.co.vividnext.sodalive.v2.common.data.ContentSort import kr.co.vividnext.sodalive.v2.creator.channel.model.toLabelResId import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelSortPopup +import kr.co.vividnext.sodalive.v2.main.MainV2Activity import kr.co.vividnext.sodalive.v2.main.ensureMainV2NavigationAllowed import kr.co.vividnext.sodalive.v2.main.content.data.AudioRankingType import kr.co.vividnext.sodalive.v2.main.content.data.MainContentAllType @@ -101,6 +102,7 @@ class ContentMainFragment : BaseFragment( private var hasSelectedAllTab = false private var currentContentTab = CONTENT_TAB_RECOMMENDATION private var currentAllTabState: MainContentAllTabUiState? = null + private var pendingAllTabSelection: ContentAllTabSelection? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -114,6 +116,7 @@ class ContentMainFragment : BaseFragment( setUpAdapters() bindObservers() contentMainViewModel.loadRecommendations() + applyPendingAllTabSelection() } override fun onDestroyView() { @@ -124,6 +127,22 @@ class ContentMainFragment : BaseFragment( super.onDestroyView() } + fun selectAllTab(type: MainContentAllType, sort: ContentSort = ContentSort.LATEST) { + pendingAllTabSelection = ContentAllTabSelection(type, sort) + if (view != null) { + applyPendingAllTabSelection() + } + } + + private fun applyPendingAllTabSelection() { + val selection = pendingAllTabSelection ?: return + pendingAllTabSelection = null + hasSelectedAllTab = true + binding.textTabBarContent.root.selectTab(CONTENT_TAB_ALL) + showContentTab(CONTENT_TAB_ALL) + contentAllTabViewModel.selectTypeAndSort(selection.type, selection.sort) + } + private fun setUpTextTabs() { binding.textTabBarContent.root.setMenus( listOf( @@ -529,6 +548,18 @@ class ContentMainFragment : BaseFragment( ) binding.viewContentMostCommentedAudioTitle.setTitle(R.string.content_recommendation_section_most_commented_audio) binding.viewContentRecommendedAudioTitle.setTitle(R.string.content_recommendation_section_recommended_audio) + binding.viewContentOriginalSeriesTitle.ivSectionTitleChevron.setOnClickListener { + openContentAllTab(MainContentAllType.ORIGINAL, ContentSort.LATEST) + } + binding.viewContentLatestAudioTitle.ivSectionTitleChevron.setOnClickListener { + openContentAllTab(MainContentAllType.AUDIO, ContentSort.LATEST) + } + binding.viewContentFreeAudioTitle.ivSectionTitleChevron.setOnClickListener { + openContentAllTab(MainContentAllType.FREE, ContentSort.POPULAR) + } + binding.viewContentPointAudioTitle.ivSectionTitleChevron.setOnClickListener { + openContentAllTab(MainContentAllType.POINT, ContentSort.POPULAR) + } } private fun ViewSectionTitleBinding.setTitle( @@ -546,6 +577,12 @@ class ContentMainFragment : BaseFragment( } } + private fun openContentAllTab(type: MainContentAllType, sort: ContentSort) { + ensureMainV2NavigationAllowed { + (activity as? MainV2Activity)?.openContentAllTab(type, sort) + } + } + private fun openAudioContentDetail(item: ContentAudioCardUiModel) { openAudioContentDetail(item.audioContentId, item.showAdultBadge) } @@ -644,4 +681,9 @@ class ContentMainFragment : BaseFragment( private const val CONTENT_TAB_RANKING = 1 private const val CONTENT_TAB_ALL = 2 } + + private data class ContentAllTabSelection( + val type: MainContentAllType, + val sort: ContentSort + ) } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt index df69560f..79d12c0c 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/MainV2ActivitySourceTest.kt @@ -73,6 +73,24 @@ class MainV2ActivitySourceTest { assertTrue(navigationSource.contains("R.id.menu_main_v2_chat -> selectChatTabWithLoginGuard()")) } + @Test + fun `MainV2Activity는 콘텐츠 전체 탭 type sort 진입 contract를 제공한다`() { + val source = projectFile("app/src/main/java/kr/co/vividnext/sodalive/v2/main/MainV2Activity.kt").readText() + + assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.common.data.ContentSort")) + assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.main.content.data.MainContentAllType")) + assertTrue(source.contains("fun openContentAllTab(type: MainContentAllType, sort: ContentSort = ContentSort.LATEST)")) + + val openContentSource = source.substringFrom("fun openContentAllTab(type: MainContentAllType") + assertTrue(openContentSource.contains("if (viewModel.currentTab.value != MainV2Tab.CONTENT)")) + assertTrue(openContentSource.contains("viewModel.clickTab(MainV2Tab.CONTENT)")) + assertTrue(openContentSource.contains("changeFragment(MainV2Tab.CONTENT)")) + assertTrue(openContentSource.contains("supportFragmentManager.findFragmentByTag(MainV2Tab.CONTENT.toString())")) + assertTrue(openContentSource.contains("as? ContentMainFragment")) + assertTrue(openContentSource.contains("?.selectAllTab(type, sort)")) + assertBefore(openContentSource, "changeFragment(MainV2Tab.CONTENT)", "?.selectAllTab(type, sort)") + } + private fun assertBefore(source: String, expectedBefore: String, expectedAfter: String) { val beforeIndex = source.indexOf(expectedBefore) val afterIndex = source.indexOf(expectedAfter, beforeIndex) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt index 3503097e..40c836d7 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragmentSourceTest.kt @@ -335,6 +335,50 @@ class ContentMainFragmentSourceTest { assertFalse(source.contains("viewContentNewAndHotTitle.ivSectionTitleChevron.setOnClickListener")) } + @Test + fun `content 전체 탭 외부 선택 source는 pending 처리와 중복 초기 로드 방지를 포함한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt" + ).readText() + + assertSourceContains(source, "fun selectAllTab(type: MainContentAllType, sort: ContentSort = ContentSort.LATEST)") + assertSourceContains(source, "private var pendingAllTabSelection: ContentAllTabSelection? = null") + assertSourceContains(source, "pendingAllTabSelection = ContentAllTabSelection(type, sort)") + assertSourceContains(source, "if (view != null)") + assertSourceContains(source, "applyPendingAllTabSelection()") + assertSourceContains(source, "private fun applyPendingAllTabSelection()") + assertSourceContains(source, "hasSelectedAllTab = true") + assertSourceContains(source, "binding.textTabBarContent.root.selectTab(CONTENT_TAB_ALL)") + assertSourceContains(source, "showContentTab(CONTENT_TAB_ALL)") + assertSourceContains(source, "contentAllTabViewModel.selectTypeAndSort(selection.type, selection.sort)") + assertTrue( + "전체 탭 selectTab listener가 loadInitial을 일으키지 않도록 hasSelectedAllTab을 먼저 설정해야 한다.", + source.indexOf("hasSelectedAllTab = true") < + source.indexOf("binding.textTabBarContent.root.selectTab(CONTENT_TAB_ALL)") + ) + } + + @Test + fun `content 추천 기존 전체보기 chevron은 MainV2Activity 전체 탭 routing을 호출한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/ContentMainFragment.kt" + ).readText() + + assertSourceContains(source, "import kr.co.vividnext.sodalive.v2.main.MainV2Activity") + assertSourceContains(source, "private fun openContentAllTab(type: MainContentAllType, sort: ContentSort)") + assertSourceContains(source, "ensureMainV2NavigationAllowed") + assertSourceContains(source, "(activity as? MainV2Activity)?.openContentAllTab(type, sort)") + assertSourceContains(source, "binding.viewContentOriginalSeriesTitle.ivSectionTitleChevron.setOnClickListener") + assertSourceContains(source, "openContentAllTab(MainContentAllType.ORIGINAL, ContentSort.LATEST)") + assertSourceContains(source, "binding.viewContentLatestAudioTitle.ivSectionTitleChevron.setOnClickListener") + assertSourceContains(source, "openContentAllTab(MainContentAllType.AUDIO, ContentSort.LATEST)") + assertSourceContains(source, "binding.viewContentFreeAudioTitle.ivSectionTitleChevron.setOnClickListener") + assertSourceContains(source, "openContentAllTab(MainContentAllType.FREE, ContentSort.POPULAR)") + assertSourceContains(source, "binding.viewContentPointAudioTitle.ivSectionTitleChevron.setOnClickListener") + assertSourceContains(source, "openContentAllTab(MainContentAllType.POINT, ContentSort.POPULAR)") + assertFalse(source.contains("viewContentNewAndHotTitle.ivSectionTitleChevron.setOnClickListener")) + } + @Test fun `Phase 4~5 adapter source는 grouping과 badge comment 정책을 포함한다`() { val cardView = projectFile(