From 8d205690b331cc426911984ff57691e31193f028 Mon Sep 17 00:00:00 2001 From: klaus Date: Wed, 8 Jul 2026 03:44:54 +0900 Subject: [PATCH] =?UTF-8?q?feat(creator-channel):=20FanTalk=20=EA=B8=80?= =?UTF-8?q?=EC=93=B0=EA=B8=B0=20=EC=A7=84=EC=9E=85=EC=A0=90=EC=9D=84=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../creator/channel/CreatorChannelActivity.kt | 37 +++++++++++++++++++ .../fantalk/CreatorChannelFanTalkFragment.kt | 13 +++++-- .../res/layout/activity_creator_channel.xml | 25 +++++++++++++ .../fragment_creator_channel_fantalk.xml | 4 +- .../CreatorChannelActivitySourceTest.kt | 32 ++++++++++++++++ ...CreatorChannelFanTalkFragmentLayoutTest.kt | 24 +++++++++++- 6 files changed, 130 insertions(+), 5 deletions(-) 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 14b533e9..70432bd0 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 @@ -65,6 +65,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveRespon import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelScheduleResponse import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragment +import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.write.CreatorChannelFanTalkWriteActivity import kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragment import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel @@ -103,6 +104,7 @@ class CreatorChannelActivity : private var isOwnerFabExpanded: Boolean = false private var isOwnerFabAnimating: Boolean = false private var isDonationFloatingButtonVisible: Boolean = false + private var isFanTalkFixedPlusVisible: Boolean = false private lateinit var loadingDialog: LoadingDialog private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() } private val liveCoordinator: CreatorChannelLiveCoordinator by lazy { @@ -130,6 +132,14 @@ class CreatorChannelActivity : refreshCreatorChannelCommunity() } } + private val fanTalkWriteLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == RESULT_OK) { + refreshCreatorChannelFanTalk() + homeActionDelegate?.refreshHome() + } + } private val liveRoomCreateLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> @@ -192,6 +202,7 @@ class CreatorChannelActivity : binding.btnCreatorChannelDonationWrite.setOnClickListener { findDonationFragment()?.onCreatorChannelDonationFloatingButtonClicked() } + binding.btnCreatorChannelFantalkFixedPlus.setOnClickListener { openFanTalkWrite() } binding.btnCreatorChannelOwnerCta.setOnClickListener { onOwnerCtaClicked() } binding.tvChatButton.setOnClickListener { currentHeader?.characterId?.let { characterId -> homeActionDelegate?.createChatRoom(characterId) } @@ -410,6 +421,7 @@ class CreatorChannelActivity : } updateOwnerFabVisibility() updateDonationFloatingButtonVisibility() + updateFanTalkFixedPlusVisibility() updateOwnerCtaVisibility() updateCreatorChannelTabViewportHeight() updateViewPagerHeight() @@ -445,6 +457,7 @@ class CreatorChannelActivity : bindTitleBar(header) updateOwnerFabVisibility() updateDonationFloatingButtonVisibility() + updateFanTalkFixedPlusVisibility() updateOwnerCtaVisibility() if (binding.viewPager.currentItem == CreatorChannelTab.Audio.ordinal) { binding.viewPager.post { @@ -551,6 +564,15 @@ class CreatorChannelActivity : } } + override fun onCreatorChannelFanTalkWriteClicked() { + openFanTalkWrite() + } + + override fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean) { + isFanTalkFixedPlusVisible = isVisible + updateFanTalkFixedPlusVisibility() + } + override fun onCreatorChannelDonationContentChanged() { updateViewPagerHeight { postCheckCreatorChannelCurrentTabNeedsMore() @@ -822,6 +844,21 @@ class CreatorChannelActivity : binding.btnCreatorChannelDonationWrite.isVisible = shouldShowDonationFloatingButton } + private fun updateFanTalkFixedPlusVisibility() { + val shouldShowFanTalkFixedPlus = + isFanTalkFixedPlusVisible && binding.viewPager.currentItem == CreatorChannelTab.FanTalk.ordinal + binding.btnCreatorChannelFantalkFixedPlus.isVisible = shouldShowFanTalkFixedPlus + } + + private fun openFanTalkWrite() { + if (creatorId <= 0L) return + if (SharedPreferenceManager.token.isBlank()) { + showLoginActivity() + return + } + fanTalkWriteLauncher.launch(CreatorChannelFanTalkWriteActivity.newIntent(this, creatorId)) + } + private fun onOwnerFabCommunityClicked() { collapseOwnerFab(animate = false) communityWriteLauncher.launch(Intent(this, CreatorCommunityWriteActivity::class.java)) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt index 7191f7aa..44e59a36 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt @@ -43,6 +43,7 @@ class CreatorChannelFanTalkFragment : BaseFragment + + + + + 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 26ba7ed9..0b65c31a 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 @@ -164,6 +164,38 @@ class CreatorChannelActivitySourceTest { assertTrue(source.contains("DmChatRoomActivity.newIntentByCreatorId(this, creatorId)")) } + @Test + fun `FanTalk fixed plus source는 Activity overlay와 write result refresh 계약을 연결한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" + ).readText() + val layout = projectFile("app/src/main/res/layout/activity_creator_channel.xml").readText() + val repository = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt" + ).readText() + val appDi = projectFile("app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt").readText() + + assertTrue(layout.contains("android:id=\"@+id/btn_creator_channel_fantalk_fixed_plus\"")) + assertTrue(layout.contains("android:src=\"@drawable/ic_plus_no_bg\"")) + assertTrue(layout.contains("app:tint=\"@color/white\"")) + assertTrue(layout.contains("app:layout_constraintBottom_toBottomOf=\"parent\"")) + assertTrue(layout.contains("app:layout_constraintEnd_toEndOf=\"parent\"")) + assertTrue(source.contains("private val fanTalkWriteLauncher = registerForActivityResult")) + assertTrue(source.contains("CreatorChannelFanTalkWriteActivity.newIntent(this, creatorId)")) + assertTrue(source.contains("private fun openFanTalkWrite()")) + assertTrue(source.contains("if (creatorId <= 0L) return")) + assertTrue(source.contains("if (SharedPreferenceManager.token.isBlank())")) + assertTrue(source.contains("showLoginActivity()")) + assertTrue(source.contains("refreshCreatorChannelFanTalk()")) + assertTrue(source.contains("homeActionDelegate?.refreshHome()")) + assertTrue(source.contains("onCreatorChannelFanTalkWriteClicked()")) + assertTrue(source.contains("onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean)")) + assertTrue(source.contains("updateFanTalkFixedPlusVisibility()")) + assertTrue(repository.contains("fun writeFanTalk(creatorId: Long, content: String, token: String)")) + assertTrue(repository.contains("parentCheersId = null")) + assertTrue(appDi.contains("CreatorChannelFanTalkWriteViewModel")) + } + @Test fun `Phase 10 컨테이너 layout은 TabLayout과 ViewPager2를 가진다`() { val layout = projectFile("app/src/main/res/layout/activity_creator_channel.xml").readText() diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt index 7dde5418..6c3e97a5 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt @@ -72,7 +72,8 @@ class CreatorChannelFanTalkFragmentLayoutTest { assertTrue(layout.contains("android:background=\"@drawable/bg_creator_channel_fantalk_empty_button\"")) assertTrue(layout.contains("android:layout_marginStart=\"@dimen/spacing_6\"")) assertTrue(layout.contains("tools:listitem=\"@layout/item_creator_channel_fantalk\"")) - assertTrue(layout.contains("@drawable/ic_new_fantalk_plus")) + assertFalse(layout.contains("@drawable/ic_new_fantalk_plus")) + assertTrue(layout.contains("@drawable/ic_plus_no_bg")) assertEquals(ContextCompat.getColor(root.context, R.color.gray_500), emptyMessage.currentTextColor) assertEquals(ContextCompat.getColor(root.context, R.color.white), emptyButtonLabel.currentTextColor) assertTrue( @@ -193,6 +194,27 @@ class CreatorChannelFanTalkFragmentLayoutTest { assertTrue(adapter.contains("tvCreatorChannelFantalkReport.setOnClickListener { onReportClick(item) }")) } + @Test + fun `팬톡 write entry source는 empty 버튼과 fixed plus visibility를 Host로 위임한다`() { + val fragment = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt" + ).readText() + val layout = projectFile("app/src/main/res/layout/fragment_creator_channel_fantalk.xml").readText() + + assertTrue(fragment.contains("fun onCreatorChannelFanTalkWriteClicked()")) + assertTrue(fragment.contains("fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean)")) + assertTrue(fragment.contains("layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener")) + assertTrue(fragment.contains("host.onCreatorChannelFanTalkWriteClicked()")) + assertTrue(fragment.contains("host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(true)")) + assertTrue(fragment.contains("host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)")) + assertFalse(fragment.contains("btnCreatorChannelFantalkWrite.setOnClickListener { }")) + assertFalse(fragment.contains("btnCreatorChannelFantalkWrite.isVisible = true")) + assertTrue(layout.contains("@+id/layout_creator_channel_fantalk_empty_write_button")) + assertTrue(layout.contains("android:id=\"@+id/btn_creator_channel_fantalk_write\"")) + assertTrue(layout.contains("android:visibility=\"gone\"")) + assertFalse(layout.contains("@drawable/ic_new_fantalk_plus")) + } + private fun inflateView(layoutResId: Int): View { val context = ApplicationProvider.getApplicationContext() return LayoutInflater.from(context).inflate(layoutResId, null, false)