feat(creator-channel): FanTalk 글쓰기 진입점을 연결한다

This commit is contained in:
2026-07-08 03:44:54 +09:00
parent 25af312d91
commit 8d205690b3
6 changed files with 130 additions and 5 deletions

View File

@@ -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.CreatorChannelScheduleResponse
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelSeriesResponse 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.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.donation.CreatorChannelDonationFragment
import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment import kr.co.vividnext.sodalive.v2.creator.channel.live.CreatorChannelLiveFragment
import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel import kr.co.vividnext.sodalive.v2.creator.channel.model.CreatorChannelHeaderUiModel
@@ -103,6 +104,7 @@ class CreatorChannelActivity :
private var isOwnerFabExpanded: Boolean = false private var isOwnerFabExpanded: Boolean = false
private var isOwnerFabAnimating: Boolean = false private var isOwnerFabAnimating: Boolean = false
private var isDonationFloatingButtonVisible: Boolean = false private var isDonationFloatingButtonVisible: Boolean = false
private var isFanTalkFixedPlusVisible: Boolean = false
private lateinit var loadingDialog: LoadingDialog private lateinit var loadingDialog: LoadingDialog
private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() } private val baseTitleBarHeight: Int by lazy { 60.dpToPx().toInt() }
private val liveCoordinator: CreatorChannelLiveCoordinator by lazy { private val liveCoordinator: CreatorChannelLiveCoordinator by lazy {
@@ -130,6 +132,14 @@ class CreatorChannelActivity :
refreshCreatorChannelCommunity() refreshCreatorChannelCommunity()
} }
} }
private val fanTalkWriteLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
refreshCreatorChannelFanTalk()
homeActionDelegate?.refreshHome()
}
}
private val liveRoomCreateLauncher = registerForActivityResult( private val liveRoomCreateLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->
@@ -192,6 +202,7 @@ class CreatorChannelActivity :
binding.btnCreatorChannelDonationWrite.setOnClickListener { binding.btnCreatorChannelDonationWrite.setOnClickListener {
findDonationFragment()?.onCreatorChannelDonationFloatingButtonClicked() findDonationFragment()?.onCreatorChannelDonationFloatingButtonClicked()
} }
binding.btnCreatorChannelFantalkFixedPlus.setOnClickListener { openFanTalkWrite() }
binding.btnCreatorChannelOwnerCta.setOnClickListener { onOwnerCtaClicked() } binding.btnCreatorChannelOwnerCta.setOnClickListener { onOwnerCtaClicked() }
binding.tvChatButton.setOnClickListener { binding.tvChatButton.setOnClickListener {
currentHeader?.characterId?.let { characterId -> homeActionDelegate?.createChatRoom(characterId) } currentHeader?.characterId?.let { characterId -> homeActionDelegate?.createChatRoom(characterId) }
@@ -410,6 +421,7 @@ class CreatorChannelActivity :
} }
updateOwnerFabVisibility() updateOwnerFabVisibility()
updateDonationFloatingButtonVisibility() updateDonationFloatingButtonVisibility()
updateFanTalkFixedPlusVisibility()
updateOwnerCtaVisibility() updateOwnerCtaVisibility()
updateCreatorChannelTabViewportHeight() updateCreatorChannelTabViewportHeight()
updateViewPagerHeight() updateViewPagerHeight()
@@ -445,6 +457,7 @@ class CreatorChannelActivity :
bindTitleBar(header) bindTitleBar(header)
updateOwnerFabVisibility() updateOwnerFabVisibility()
updateDonationFloatingButtonVisibility() updateDonationFloatingButtonVisibility()
updateFanTalkFixedPlusVisibility()
updateOwnerCtaVisibility() updateOwnerCtaVisibility()
if (binding.viewPager.currentItem == CreatorChannelTab.Audio.ordinal) { if (binding.viewPager.currentItem == CreatorChannelTab.Audio.ordinal) {
binding.viewPager.post { binding.viewPager.post {
@@ -551,6 +564,15 @@ class CreatorChannelActivity :
} }
} }
override fun onCreatorChannelFanTalkWriteClicked() {
openFanTalkWrite()
}
override fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean) {
isFanTalkFixedPlusVisible = isVisible
updateFanTalkFixedPlusVisibility()
}
override fun onCreatorChannelDonationContentChanged() { override fun onCreatorChannelDonationContentChanged() {
updateViewPagerHeight { updateViewPagerHeight {
postCheckCreatorChannelCurrentTabNeedsMore() postCheckCreatorChannelCurrentTabNeedsMore()
@@ -822,6 +844,21 @@ class CreatorChannelActivity :
binding.btnCreatorChannelDonationWrite.isVisible = shouldShowDonationFloatingButton 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() { private fun onOwnerFabCommunityClicked() {
collapseOwnerFab(animate = false) collapseOwnerFab(animate = false)
communityWriteLauncher.launch(Intent(this, CreatorCommunityWriteActivity::class.java)) communityWriteLauncher.launch(Intent(this, CreatorCommunityWriteActivity::class.java))

View File

@@ -43,6 +43,7 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
morePopup?.dismiss() morePopup?.dismiss()
morePopup = null morePopup = null
lastContentLayoutKey = null lastContentLayoutKey = null
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)
binding.rvCreatorChannelFantalk.adapter = null binding.rvCreatorChannelFantalk.adapter = null
super.onDestroyView() super.onDestroyView()
} }
@@ -77,8 +78,8 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
btnCreatorChannelFantalkRetry.setOnClickListener { btnCreatorChannelFantalkRetry.setOnClickListener {
viewModel.retryFanTalks() viewModel.retryFanTalks()
} }
btnCreatorChannelFantalkWrite.setOnClickListener { } btnCreatorChannelFantalkWrite.setOnClickListener { host.onCreatorChannelFanTalkWriteClicked() }
layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener { } layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener { host.onCreatorChannelFanTalkWriteClicked() }
} }
private fun observeViewModel() { private fun observeViewModel() {
@@ -100,6 +101,7 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
tvCreatorChannelFantalkErrorMessage.isVisible = false tvCreatorChannelFantalkErrorMessage.isVisible = false
btnCreatorChannelFantalkRetry.isVisible = false btnCreatorChannelFantalkRetry.isVisible = false
btnCreatorChannelFantalkWrite.isVisible = false btnCreatorChannelFantalkWrite.isVisible = false
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)
} }
private fun bindEmpty() = with(binding) { private fun bindEmpty() = with(binding) {
@@ -110,6 +112,7 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
tvCreatorChannelFantalkErrorMessage.isVisible = false tvCreatorChannelFantalkErrorMessage.isVisible = false
btnCreatorChannelFantalkRetry.isVisible = false btnCreatorChannelFantalkRetry.isVisible = false
btnCreatorChannelFantalkWrite.isVisible = false btnCreatorChannelFantalkWrite.isVisible = false
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)
host.onCreatorChannelFanTalkContentChanged() host.onCreatorChannelFanTalkContentChanged()
} }
@@ -122,6 +125,7 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
tvCreatorChannelFantalkErrorMessage.text = state.message ?: getString(R.string.creator_channel_fantalk_error_message) tvCreatorChannelFantalkErrorMessage.text = state.message ?: getString(R.string.creator_channel_fantalk_error_message)
btnCreatorChannelFantalkRetry.isVisible = true btnCreatorChannelFantalkRetry.isVisible = true
btnCreatorChannelFantalkWrite.isVisible = false btnCreatorChannelFantalkWrite.isVisible = false
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(false)
host.onCreatorChannelFanTalkContentChanged() host.onCreatorChannelFanTalkContentChanged()
} }
@@ -133,7 +137,8 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
layoutCreatorChannelFantalkEmpty.isVisible = false layoutCreatorChannelFantalkEmpty.isVisible = false
tvCreatorChannelFantalkErrorMessage.isVisible = false tvCreatorChannelFantalkErrorMessage.isVisible = false
btnCreatorChannelFantalkRetry.isVisible = false btnCreatorChannelFantalkRetry.isVisible = false
btnCreatorChannelFantalkWrite.isVisible = true btnCreatorChannelFantalkWrite.isVisible = false
host.onCreatorChannelFanTalkFloatingButtonVisibilityChanged(true)
notifyContentChangedIfLayoutChanged(state) notifyContentChangedIfLayoutChanged(state)
state.paginationErrorMessage?.let { state.paginationErrorMessage?.let {
Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show()
@@ -178,6 +183,8 @@ class CreatorChannelFanTalkFragment : BaseFragment<FragmentCreatorChannelFantalk
interface Host { interface Host {
fun isCreatorChannelOwner(): Boolean fun isCreatorChannelOwner(): Boolean
fun onCreatorChannelFanTalkContentChanged() fun onCreatorChannelFanTalkContentChanged()
fun onCreatorChannelFanTalkWriteClicked()
fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean)
fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long) fun onCreatorChannelFanTalkDeleteClicked(fanTalkId: Long)
} }

View File

@@ -407,6 +407,31 @@
app:tint="@color/white" /> app:tint="@color/white" />
</FrameLayout> </FrameLayout>
<FrameLayout
android:id="@+id/btn_creator_channel_fantalk_fixed_plus"
android:layout_width="66dp"
android:layout_height="66dp"
android:layout_marginEnd="@dimen/spacing_14"
android:layout_marginBottom="@dimen/spacing_14"
android:background="@drawable/bg_creator_channel_owner_fab"
android:clickable="true"
android:contentDescription="@string/creator_channel_fantalk_support_action"
android:elevation="8dp"
android:focusable="true"
android:padding="@dimen/spacing_14"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible">
<ImageView
android:layout_width="38dp"
android:layout_height="38dp"
android:contentDescription="@null"
android:src="@drawable/ic_plus_no_bg"
app:tint="@color/white" />
</FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/owner_fab_button" android:id="@+id/owner_fab_button"
android:layout_width="66dp" android:layout_width="66dp"

View File

@@ -151,7 +151,9 @@
android:background="@drawable/bg_creator_channel_fantalk_write_button" android:background="@drawable/bg_creator_channel_fantalk_write_button"
android:contentDescription="@string/creator_channel_fantalk_support_action" android:contentDescription="@string/creator_channel_fantalk_support_action"
android:padding="@dimen/spacing_16" android:padding="@dimen/spacing_16"
android:src="@drawable/ic_new_fantalk_plus" android:src="@drawable/ic_plus_no_bg"
android:visibility="gone"
app:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -164,6 +164,38 @@ class CreatorChannelActivitySourceTest {
assertTrue(source.contains("DmChatRoomActivity.newIntentByCreatorId(this, creatorId)")) 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 @Test
fun `Phase 10 컨테이너 layout은 TabLayout과 ViewPager2를 가진다`() { fun `Phase 10 컨테이너 layout은 TabLayout과 ViewPager2를 가진다`() {
val layout = projectFile("app/src/main/res/layout/activity_creator_channel.xml").readText() val layout = projectFile("app/src/main/res/layout/activity_creator_channel.xml").readText()

View File

@@ -72,7 +72,8 @@ class CreatorChannelFanTalkFragmentLayoutTest {
assertTrue(layout.contains("android:background=\"@drawable/bg_creator_channel_fantalk_empty_button\"")) assertTrue(layout.contains("android:background=\"@drawable/bg_creator_channel_fantalk_empty_button\""))
assertTrue(layout.contains("android:layout_marginStart=\"@dimen/spacing_6\"")) assertTrue(layout.contains("android:layout_marginStart=\"@dimen/spacing_6\""))
assertTrue(layout.contains("tools:listitem=\"@layout/item_creator_channel_fantalk\"")) 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.gray_500), emptyMessage.currentTextColor)
assertEquals(ContextCompat.getColor(root.context, R.color.white), emptyButtonLabel.currentTextColor) assertEquals(ContextCompat.getColor(root.context, R.color.white), emptyButtonLabel.currentTextColor)
assertTrue( assertTrue(
@@ -193,6 +194,27 @@ class CreatorChannelFanTalkFragmentLayoutTest {
assertTrue(adapter.contains("tvCreatorChannelFantalkReport.setOnClickListener { onReportClick(item) }")) 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 { private fun inflateView(layoutResId: Int): View {
val context = ApplicationProvider.getApplicationContext<Context>() val context = ApplicationProvider.getApplicationContext<Context>()
return LayoutInflater.from(context).inflate(layoutResId, null, false) return LayoutInflater.from(context).inflate(layoutResId, null, false)