diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt index 9951442f..fb49a428 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt @@ -3,11 +3,9 @@ package kr.co.vividnext.sodalive.v2.creator.channel import android.content.Context import android.content.Intent import android.graphics.Color -import android.graphics.Typeface -import android.view.Gravity import android.view.View +import android.view.View.MeasureSpec import android.widget.LinearLayout -import android.widget.TextView import android.widget.Toast import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat @@ -15,7 +13,9 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.tabs.TabLayoutMediator import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity import kr.co.vividnext.sodalive.base.BaseActivity @@ -31,25 +31,22 @@ import kr.co.vividnext.sodalive.v2.common.CreatorActivityType import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel -import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHomeUiState import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelScrollState import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTab import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelTitleBarState -import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelHomeSectionAdapter import kr.co.vividnext.sodalive.v2.main.chat.dm.DmChatRoomActivity -import org.koin.androidx.viewmodel.ext.android.viewModel -class CreatorChannelActivity : BaseActivity( - ActivityCreatorChannelBinding::inflate -) { +class CreatorChannelActivity : + BaseActivity(ActivityCreatorChannelBinding::inflate), + CreatorChannelHomeFragment.Host { - private val viewModel: CreatorChannelHomeViewModel by viewModel() - private val sectionAdapter = CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked) private var creatorId: Long = 0L private var currentHeader: CreatorChannelHeaderUiModel? = null - private var selectedTab: CreatorChannelTab = CreatorChannelTab.Home + private var homeActionDelegate: CreatorChannelHomeFragment.HomeActionDelegate? = null private var isFollowInProgress: Boolean = false private var statusBarHeight: Int = 0 + private var tabLayoutMediator: TabLayoutMediator? = null + private var pageChangeCallback: ViewPager2.OnPageChangeCallback? = null private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() } override val shouldApplySystemBarTopInset: Boolean = false @@ -61,18 +58,11 @@ class CreatorChannelActivity : BaseActivity( return } - setupRecyclerView() + setupTabsAndPager() setStatusBarIconAppearance() setTitleBarTopInset() setupScrollListener() setupClickListeners() - observeViewModel() - viewModel.loadHome(creatorId) - } - - private fun setupRecyclerView() { - binding.rvHomeSections.layoutManager = LinearLayoutManager(this) - binding.rvHomeSections.adapter = sectionAdapter } private fun setupClickListeners() { @@ -81,47 +71,13 @@ class CreatorChannelActivity : BaseActivity( binding.layoutFollowCapsule.setOnClickListener { onFollowActionClicked() } binding.ivBell.setOnClickListener { onFollowActionClicked() } binding.tvChatButton.setOnClickListener { - currentHeader?.characterId?.let { characterId -> viewModel.createChatRoom(characterId) } + currentHeader?.characterId?.let { characterId -> homeActionDelegate?.createChatRoom(characterId) } } binding.tvDmButton.setOnClickListener { startActivity(DmChatRoomActivity.newIntentByCreatorId(this, creatorId)) } } - private fun observeViewModel() { - viewModel.homeStateLiveData.observe(this) { state -> - when (state) { - is CreatorChannelHomeUiState.Content -> bindContent(state) - is CreatorChannelHomeUiState.Error -> Unit - CreatorChannelHomeUiState.Empty -> Unit - CreatorChannelHomeUiState.Loading -> Unit - } - } - viewModel.chatRoomIdLiveData.observe(this) { event -> - event.consume()?.let { chatRoomId -> - startActivity(ChatRoomActivity.newIntent(this, chatRoomId)) - } - } - viewModel.toastLiveData.observe(this) { event -> - event.consume()?.let { - val message = it.message ?: it.resId?.let(::getString) - message?.let { text -> Toast.makeText(applicationContext, text, Toast.LENGTH_LONG).show() } - } - } - viewModel.isFollowInProgressLiveData.observe(this) { inProgress -> - isFollowInProgress = inProgress - currentHeader?.let(::bindTitleBar) - } - } - - private fun bindContent(content: CreatorChannelHomeUiState.Content) { - currentHeader = content.header - bindHeader(content.header) - bindTitleBar(content.header) - bindTabs(content.tabs) - sectionAdapter.submitItems(content.sections) - } - private fun bindHeader(header: CreatorChannelHeaderUiModel) { binding.tvNickname.text = header.nickname binding.tvFollowerCount.text = getString( @@ -166,54 +122,11 @@ class CreatorChannelActivity : BaseActivity( } } - private fun bindTabs(tabs: List) { - binding.tabContainer.removeAllViews() - tabs.forEach { tab -> - binding.tabContainer.addView(createTabView(tab, isSelected = tab == selectedTab)) - } - } - - private fun createTabView(tab: CreatorChannelTab, isSelected: Boolean): LinearLayout { - val tabText = TextView(this).apply { - text = getString(tab.labelResId) - gravity = Gravity.CENTER - setTextColor(getColor(if (isSelected) R.color.white else R.color.gray_500)) - setTypeface(null, Typeface.NORMAL) - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - 0, - 1f - ) - } - tabText.textSize = 16f - val indicator = View(this).apply { - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - 3.dpToPx().toInt() - ) - } - indicator.setBackgroundColor(getColor(R.color.soda_400)) - indicator.isVisible = isSelected - return LinearLayout(this).apply { - orientation = LinearLayout.VERTICAL - gravity = Gravity.CENTER - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.MATCH_PARENT - ).apply { - width = 110.dpToPx().toInt() - } - addView(tabText) - addView(indicator) - setOnClickListener { onTabClicked(tab) } - } - } - private fun onFollowActionClicked() { if (isFollowInProgress) return val header = currentHeader ?: return if (!header.isFollow) { - viewModel.follow(follow = true, notify = true) + homeActionDelegate?.follow(follow = true, notify = true) return } @@ -222,20 +135,13 @@ class CreatorChannelActivity : BaseActivity( private fun showFollowNotifyFragment() { val notifyFragment = CreatorFollowNotifyFragment( - onClickNotifyAll = { viewModel.follow(follow = true, notify = true) }, - onClickNotifyNone = { viewModel.follow(follow = true, notify = false) }, - onClickUnFollow = { viewModel.follow(follow = false, notify = false) } + onClickNotifyAll = { homeActionDelegate?.follow(follow = true, notify = true) }, + onClickNotifyNone = { homeActionDelegate?.follow(follow = true, notify = false) }, + onClickUnFollow = { homeActionDelegate?.follow(follow = false, notify = false) } ) notifyFragment.show(supportFragmentManager, CreatorFollowNotifyFragment::class.java.simpleName) } - private fun onTabClicked(tab: CreatorChannelTab) { - if (tab != CreatorChannelTab.Home) return - selectedTab = CreatorChannelTab.Home - val content = viewModel.homeStateLiveData.value as? CreatorChannelHomeUiState.Content ?: return - bindTabs(content.tabs) - } - private fun setTitleBarTopInset() { ViewCompat.setOnApplyWindowInsetsListener(binding.titleBarContainer) { view, insets -> val topInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top @@ -262,7 +168,7 @@ class CreatorChannelActivity : BaseActivity( if (headerHeight <= 0) return val tabTranslationY = (scrollY - (headerHeight - stickyTop)).coerceAtLeast(0) - binding.horizontalTabScrollView.translationY = tabTranslationY.toFloat() + binding.tabLayout.translationY = tabTranslationY.toFloat() val tabBarTop = headerHeight - scrollY + tabTranslationY val profileVisibleHeight = (headerHeight - scrollY).coerceIn(0, headerHeight) @@ -293,6 +199,73 @@ class CreatorChannelActivity : BaseActivity( Toast.makeText(applicationContext, getString(R.string.creator_channel_more_ready), Toast.LENGTH_SHORT).show() } + private fun setupTabsAndPager() { + binding.viewPager.adapter = CreatorChannelPagerAdapter(this, creatorId) + binding.viewPager.isUserInputEnabled = true + binding.viewPager.offscreenPageLimit = CreatorChannelTab.entries.size - 1 + tabLayoutMediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = getString(CreatorChannelTab.entries[position].labelResId) + }.also { + it.attach() + } + val callback = object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + updateViewPagerHeight() + } + } + pageChangeCallback = callback + binding.viewPager.registerOnPageChangeCallback(callback) + } + + override fun onCreatorChannelHeaderChanged(header: CreatorChannelHeaderUiModel) { + currentHeader = header + bindHeader(header) + bindTitleBar(header) + } + + override fun onCreatorChannelFollowProgressChanged(inProgress: Boolean) { + isFollowInProgress = inProgress + currentHeader?.let(::bindTitleBar) + } + + override fun onCreatorChannelChatRoomCreated(chatRoomId: Long) { + startActivity(ChatRoomActivity.newIntent(this, chatRoomId)) + } + + override fun onCreatorChannelScheduleClicked(schedule: CreatorChannelScheduleResponse) { + onScheduleClicked(schedule) + } + + override fun onCreatorChannelAudioContentClicked(audioContent: CreatorChannelAudioContentResponse) { + onAudioContentClicked(audioContent) + } + + override fun onCreatorChannelHomeActionDelegateReady( + delegate: CreatorChannelHomeFragment.HomeActionDelegate? + ) { + homeActionDelegate = delegate + } + + override fun onCreatorChannelHomeContentChanged() { + updateViewPagerHeight() + } + + private fun updateViewPagerHeight() { + binding.viewPager.post { + val recyclerView = binding.viewPager.getChildAt(0) as? RecyclerView ?: return@post + val currentPage = recyclerView.layoutManager?.findViewByPosition(binding.viewPager.currentItem) ?: return@post + val widthSpec = MeasureSpec.makeMeasureSpec(binding.viewPager.width, MeasureSpec.EXACTLY) + val heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) + currentPage.measure(widthSpec, heightSpec) + val measuredHeight = currentPage.measuredHeight + if (measuredHeight <= 0 || binding.viewPager.layoutParams.height == measuredHeight) return@post + + binding.viewPager.updateLayoutParams { + height = measuredHeight + } + } + } + private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse) { when (schedule.type) { CreatorActivityType.Audio, @@ -330,6 +303,15 @@ class CreatorChannelActivity : BaseActivity( detailFragment.show(supportFragmentManager, detailFragment.tag) } + override fun onDestroy() { + tabLayoutMediator?.detach() + pageChangeCallback?.let { callback -> + binding.viewPager.unregisterOnPageChangeCallback(callback) + } + pageChangeCallback = null + super.onDestroy() + } + companion object { const val EXTRA_CREATOR_ID: String = "extra_creator_id" diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt index 4c8f218a..839e3f24 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt @@ -45,6 +45,7 @@ class CreatorChannelHomeFragment : BaseFragment { host.onCreatorChannelHeaderChanged(state.header) sectionAdapter.submitItems(state.sections) + host.onCreatorChannelHomeContentChanged() } is CreatorChannelHomeUiState.Error -> Unit CreatorChannelHomeUiState.Empty -> Unit @@ -90,6 +92,7 @@ class CreatorChannelHomeFragment : BaseFragment - + app:tabIndicatorColor="@color/soda_400" + app:tabMode="scrollable" + app:tabSelectedTextColor="@color/white" + app:tabTextColor="@color/gray_500" /> - - - - + android:layout_height="1dp" + android:nestedScrollingEnabled="false" /> diff --git a/app/src/main/res/layout/fragment_creator_channel_placeholder.xml b/app/src/main/res/layout/fragment_creator_channel_placeholder.xml index 83ec3d34..aa283901 100644 --- a/app/src/main/res/layout/fragment_creator_channel_placeholder.xml +++ b/app/src/main/res/layout/fragment_creator_channel_placeholder.xml @@ -2,7 +2,7 @@ diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt index 04fab3b3..dd0d483f 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt @@ -35,17 +35,12 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("fun newIntent(context: Context, creatorId: Long): Intent")) assertTrue(source.contains("Intent(context, CreatorChannelActivity::class.java)")) assertTrue(source.contains("putExtra(EXTRA_CREATOR_ID, creatorId)")) - assertTrue(source.contains("private val viewModel: CreatorChannelHomeViewModel by viewModel()")) assertTrue(source.contains("if (creatorId <= 0L)")) assertTrue(source.contains("finish()")) - assertTrue(source.contains("viewModel.loadHome(creatorId)")) - assertTrue(source.contains("viewModel.homeStateLiveData.observe(this)")) - assertTrue(source.contains("viewModel.chatRoomIdLiveData.observe(this)")) assertFalse(source.contains("is CreatorChannelHomeUiState.Error -> showToast")) - assertTrue(source.contains("event.consume()?.let")) assertTrue(source.contains("ChatRoomActivity.newIntent(this, chatRoomId)")) assertTrue(source.contains("DmChatRoomActivity.newIntentByCreatorId(this, creatorId)")) - assertTrue(source.contains("viewModel.createChatRoom(characterId)")) + assertTrue(source.contains("homeActionDelegate?.createChatRoom(characterId)")) assertTrue(source.contains("updateActionButtonLayout")) assertTrue(source.contains("marginStart = if (isChatVisible && isDmVisible)")) } @@ -61,32 +56,90 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("binding.ivBell.setOnClickListener")) assertTrue(source.contains("private fun onFollowActionClicked")) assertTrue(source.contains("if (!header.isFollow)")) - assertTrue(source.contains("viewModel.follow(follow = true, notify = true)")) + assertTrue(source.contains("homeActionDelegate?.follow(follow = true, notify = true)")) assertTrue(source.contains("showFollowNotifyFragment")) - assertTrue(source.contains("onClickNotifyAll = { viewModel.follow(follow = true, notify = true) }")) - assertTrue(source.contains("onClickNotifyNone = { viewModel.follow(follow = true, notify = false) }")) - assertTrue(source.contains("onClickUnFollow = { viewModel.follow(follow = false, notify = false) }")) - assertTrue(source.contains("viewModel.isFollowInProgressLiveData.observe(this)")) + assertTrue(source.contains("onClickNotifyAll = { homeActionDelegate?.follow(follow = true, notify = true) }")) + assertTrue(source.contains("onClickNotifyNone = { homeActionDelegate?.follow(follow = true, notify = false) }")) + assertTrue(source.contains("onClickUnFollow = { homeActionDelegate?.follow(follow = false, notify = false) }")) + assertTrue(source.contains("onCreatorChannelFollowProgressChanged")) assertTrue(source.contains("binding.layoutFollowCapsule.isEnabled = titleBarState.isActionEnabled")) assertTrue(source.contains("binding.ivBell.isEnabled = titleBarState.isActionEnabled")) } @Test - fun `layout source는 HorizontalScrollView 기반 7개 탭 컨테이너와 RecyclerView를 가진다`() { + fun `Phase 10 컨테이너 layout은 TabLayout과 ViewPager2를 가진다`() { val layout = projectFile("app/src/main/res/layout/activity_creator_channel.xml").readText() + + assertTrue(layout.contains("")) + assertTrue(source.contains("private const val ARG_CREATOR_ID")) + assertTrue(source.contains("fun newInstance(creatorId: Long): CreatorChannelHomeFragment")) + assertTrue(source.contains("arguments = Bundle().apply")) + assertTrue(source.contains("putLong(ARG_CREATOR_ID, creatorId)")) + assertTrue(source.contains("private val viewModel: CreatorChannelHomeViewModel by viewModel()")) + assertTrue(source.contains("CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)")) + assertTrue(source.contains("binding.rvHomeSections.layoutManager = LinearLayoutManager(requireContext())")) + assertTrue(source.contains("binding.rvHomeSections.adapter = sectionAdapter")) + assertTrue(source.contains("viewModel.homeStateLiveData.observe(viewLifecycleOwner)")) + assertTrue(source.contains("viewModel.chatRoomIdLiveData.observe(viewLifecycleOwner)")) + assertTrue(source.contains("viewModel.toastLiveData.observe(viewLifecycleOwner)")) + assertTrue(source.contains("viewModel.isFollowInProgressLiveData.observe(viewLifecycleOwner)")) + assertTrue(source.contains("host.onCreatorChannelHomeContentChanged()")) + assertTrue(source.contains("binding.rvHomeSections.adapter = null")) + assertTrue(source.contains("if (creatorId > 0L)")) + assertTrue(source.contains("viewModel.loadHome(creatorId)")) assertTrue(layout.contains("@+id/rv_home_sections")) + } + + @Test + fun `layout source는 Activity 컨테이너와 홈 Fragment RecyclerView를 분리한다`() { + val layout = projectFile("app/src/main/res/layout/activity_creator_channel.xml").readText() + val fragmentLayout = projectFile("app/src/main/res/layout/fragment_creator_channel_home.xml").readText() + + assertTrue(layout.contains("@+id/tab_layout")) + assertTrue(layout.contains("@+id/view_pager")) + assertTrue(layout.contains("android:layout_height=\"1dp\"")) + assertFalse(layout.contains("@+id/horizontal_tab_scroll_view")) + assertFalse(layout.contains("@+id/tab_container")) + assertFalse(layout.contains("@+id/rv_home_sections")) + assertTrue(fragmentLayout.contains("@+id/rv_home_sections")) + assertTrue(fragmentLayout.contains("tools:listitem=\"@layout/item_creator_channel_home_audio\"")) assertTrue(layout.contains("android:drawableStart=\"@drawable/ic_new_talk\"")) assertTrue(layout.contains("android:drawableStart=\"@drawable/ic_new_dm\"")) - assertFalse(layout.contains("TextTabBarView")) - assertTrue(source.contains("getString(tab.labelResId)")) } @Test @@ -121,7 +174,7 @@ class CreatorChannelActivitySourceTest { } @Test - fun `scroll source는 tab sticky와 title bar black 전환을 연결한다`() { + fun `scroll source는 tab sticky와 title bar black 전환을 유지한다`() { val source = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" ).readText() @@ -129,7 +182,8 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("setupScrollListener")) assertTrue(source.contains("binding.nestedScrollView.setOnScrollChangeListener")) assertTrue(source.contains("CreatorChannelScrollState.calculateStickyTop")) - assertTrue(source.contains("binding.horizontalTabScrollView.translationY")) + assertTrue(source.contains("binding.tabLayout.translationY = tabTranslationY.toFloat()")) + assertTrue(source.contains("val tabBarTop = headerHeight - scrollY + tabTranslationY")) assertTrue(source.contains("CreatorChannelScrollState.shouldUseBlackTitleBar")) assertTrue(source.contains("binding.titleBarContainer.setBackgroundColor")) assertTrue(source.contains("Color.BLACK")) @@ -147,29 +201,67 @@ class CreatorChannelActivitySourceTest { } @Test - fun `tab source는 Figma 기준 selected indicator와 16sp 고정 폭 탭을 사용한다`() { - val source = projectFile( - "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" + fun `pager adapter source는 7개 탭 순서와 홈 placeholder Fragment를 연결한다`() { + val adapter = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPagerAdapter.kt" + ).readText() + val placeholder = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelPlaceholderFragment.kt" + ).readText() + val placeholderLayout = projectFile( + "app/src/main/res/layout/fragment_creator_channel_placeholder.xml" ).readText() - assertTrue(source.contains("createTabView(tab, isSelected = tab == selectedTab)")) - assertTrue(source.contains("tabText.textSize = 16f")) - assertTrue(source.contains("width = 110.dpToPx().toInt()")) - assertTrue(source.contains("indicator.setBackgroundColor(getColor(R.color.soda_400))")) - assertTrue(source.contains("indicator.isVisible = isSelected")) + assertTrue(adapter.contains("class CreatorChannelPagerAdapter")) + assertTrue(adapter.contains("FragmentStateAdapter")) + assertTrue(adapter.contains("private val tabs: List = CreatorChannelTab.entries")) + assertTrue(adapter.contains("override fun getItemCount(): Int = tabs.size")) + assertTrue(adapter.contains("CreatorChannelTab.Home -> CreatorChannelHomeFragment.newInstance(creatorId)")) + assertTrue(adapter.contains("else -> CreatorChannelPlaceholderFragment.newInstance(tab)")) + assertTrue(placeholder.contains("private const val ARG_TAB_NAME")) + assertTrue(placeholder.contains("fun newInstance(tab: CreatorChannelTab): CreatorChannelPlaceholderFragment")) + assertTrue(placeholderLayout.contains("@+id/tv_placeholder")) + assertTrue(placeholderLayout.contains("android:layout_height=\"160dp\"")) + assertFalse(placeholderLayout.contains("android:visibility=\"gone\"")) } @Test - fun `tab source는 홈 기본 선택과 홈 외 탭 no op 정책을 명시한다`() { + fun `tab source는 TabLayoutMediator와 ViewPager2를 연결한다`() { val source = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" ).readText() - assertTrue(source.contains("private var selectedTab: CreatorChannelTab = CreatorChannelTab.Home")) - assertTrue(source.contains("createTabView(tab, isSelected = tab == selectedTab)")) - assertTrue(source.contains("setOnClickListener { onTabClicked(tab) }")) - assertTrue(source.contains("private fun onTabClicked(tab: CreatorChannelTab)")) - assertTrue(source.contains("if (tab != CreatorChannelTab.Home) return")) + assertTrue(source.contains("TabLayoutMediator")) + assertTrue(source.contains("private var tabLayoutMediator: TabLayoutMediator? = null")) + assertTrue(source.contains("private var pageChangeCallback: ViewPager2.OnPageChangeCallback? = null")) + assertTrue(source.contains("binding.viewPager.adapter = CreatorChannelPagerAdapter(this, creatorId)")) + assertTrue(source.contains("tabLayoutMediator = TabLayoutMediator(binding.tabLayout, binding.viewPager)")) + assertTrue(source.contains("tab.text = getString(CreatorChannelTab.entries[position].labelResId)")) + assertTrue(source.contains(".attach()")) + assertTrue(source.contains("binding.viewPager.isUserInputEnabled = true")) + assertTrue(source.contains("binding.viewPager.offscreenPageLimit = CreatorChannelTab.entries.size - 1")) + assertTrue(source.contains("binding.viewPager.registerOnPageChangeCallback(callback)")) + assertTrue(source.contains("override fun onDestroy()")) + assertTrue(source.contains("tabLayoutMediator?.detach()")) + assertTrue(source.contains("binding.viewPager.unregisterOnPageChangeCallback(callback)")) + assertTrue(source.contains("private fun updateViewPagerHeight()")) + assertTrue(source.contains("findViewByPosition(binding.viewPager.currentItem)")) + assertTrue(source.contains("currentPage.measure(widthSpec, heightSpec)")) + assertTrue(source.contains("binding.viewPager.updateLayoutParams")) + assertFalse(source.contains("binding.tabLayout.addTab")) + assertFalse(source.contains("private fun createTabView")) + } + + @Test + fun `tab source는 기존 custom tab no op 정책을 제거한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" + ).readText() + + assertFalse(source.contains("private var selectedTab: CreatorChannelTab = CreatorChannelTab.Home")) + assertFalse(source.contains("setOnClickListener { onTabClicked(tab) }")) + assertFalse(source.contains("private fun onTabClicked(tab: CreatorChannelTab)")) + assertFalse(source.contains("if (tab != CreatorChannelTab.Home) return")) assertFalse(source.contains("CreatorChannelTab.Live ->")) assertFalse(source.contains("CreatorChannelTab.Audio ->")) assertFalse(source.contains("CreatorChannelTab.Series ->")) @@ -589,8 +681,13 @@ class CreatorChannelActivitySourceTest { val source = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" ).readText() + val fragment = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt" + ).readText() - assertTrue(source.contains("CreatorChannelHomeSectionAdapter(::onScheduleClicked")) + assertTrue(fragment.contains("CreatorChannelHomeSectionAdapter(::onScheduleClicked")) + assertTrue(fragment.contains("private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse)")) + assertTrue(fragment.contains("host.onCreatorChannelScheduleClicked(schedule)")) assertTrue(source.contains("private fun onScheduleClicked(schedule: CreatorChannelScheduleResponse)")) assertTrue(source.contains("CreatorActivityType.Audio")) assertTrue(source.contains("CreatorActivityType.LiveReplay")) @@ -663,8 +760,13 @@ class CreatorChannelActivitySourceTest { val source = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" ).readText() + val fragment = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt" + ).readText() - assertTrue(source.contains("CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)")) + assertTrue(fragment.contains("CreatorChannelHomeSectionAdapter(::onScheduleClicked, ::onAudioContentClicked)")) + assertTrue(fragment.contains("private fun onAudioContentClicked(audioContent: CreatorChannelAudioContentResponse)")) + assertTrue(fragment.contains("host.onCreatorChannelAudioContentClicked(audioContent)")) assertTrue(source.contains("private fun onAudioContentClicked(audioContent: CreatorChannelAudioContentResponse)")) assertTrue(source.contains("AudioContentDetailActivity::class.java")) assertTrue(source.contains("putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, audioContent.audioContentId)"))