feat(creator): 라이브 탭 본인 CTA를 연결한다

This commit is contained in:
2026-06-18 11:12:55 +09:00
parent 0d11839a96
commit a49951c51f
4 changed files with 110 additions and 0 deletions

View File

@@ -387,6 +387,7 @@ class CreatorChannelActivity :
bindHeader(header)
bindTitleBar(header)
updateOwnerFabVisibility()
findLiveFragment()?.onCreatorChannelOwnerChanged(header.isOwner)
}
override fun onCreatorChannelFollowProgressChanged(inProgress: Boolean) {
@@ -425,6 +426,10 @@ class CreatorChannelActivity :
postCheckCreatorChannelLiveNeedsMore()
}
override fun isCreatorChannelOwner(): Boolean {
return currentHeader?.isOwner == true
}
private fun setupOwnerFabInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.ownerFabButton) { _, insets ->
val navigationBottomInset = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
@@ -556,6 +561,10 @@ class CreatorChannelActivity :
startAudioContentDetail(audioContentId)
}
override fun onCreatorChannelLiveStartClicked() {
onOwnerFabLiveClicked()
}
private fun findLiveFragment(): CreatorChannelLiveFragment? {
val fragmentTag = "f${CreatorChannelTab.Live.ordinal}"
return supportFragmentManager.findFragmentByTag(fragmentTag) as? CreatorChannelLiveFragment

View File

@@ -3,11 +3,17 @@ package kr.co.vividnext.sodalive.v2.creator.channel.live
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat
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 kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.databinding.FragmentCreatorChannelLiveBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelLiveResponse
import kr.co.vividnext.sodalive.v2.creator.channel.live.model.toLabelResId
@@ -28,6 +34,8 @@ class CreatorChannelLiveFragment : BaseFragment<FragmentCreatorChannelLiveBindin
private var lastContentLayoutKey: CreatorChannelLiveContentLayoutKey? = null
private var sortPopup: CreatorChannelLiveSortPopup? = null
private var currentContentState: CreatorChannelLiveUiState.Content? = null
private var isOwner: Boolean = false
private var ownerCtaBottomInset: Int = 0
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
private val host: Host
get() = requireActivity() as Host
@@ -36,8 +44,15 @@ class CreatorChannelLiveFragment : BaseFragment<FragmentCreatorChannelLiveBindin
super.onViewCreated(view, savedInstanceState)
bindLoading()
setupReplayList()
setupOwnerCtaInsets()
setupClickListeners()
observeViewModel()
bindOwnerCta(host.isCreatorChannelOwner())
}
override fun onResume() {
super.onResume()
binding.layoutCreatorChannelLiveOwnerCta.isEnabled = true
}
fun onCreatorChannelLiveTabSelected() {
@@ -63,6 +78,19 @@ class CreatorChannelLiveFragment : BaseFragment<FragmentCreatorChannelLiveBindin
viewModel.loadMore()
}
fun onCreatorChannelOwnerChanged(isOwner: Boolean) {
bindOwnerCta(isOwner)
}
private fun setupOwnerCtaInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.layoutCreatorChannelLiveOwnerCta) { _, insets ->
ownerCtaBottomInset = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
updateOwnerCtaInsets()
insets
}
ViewCompat.requestApplyInsets(binding.layoutCreatorChannelLiveOwnerCta)
}
private fun setupClickListeners() {
binding.ivCreatorChannelLiveSort.setImageResource(R.drawable.ic_new_sort)
binding.layoutCreatorChannelLiveSortButton.setOnClickListener {
@@ -71,6 +99,29 @@ class CreatorChannelLiveFragment : BaseFragment<FragmentCreatorChannelLiveBindin
binding.btnCreatorChannelLiveRetry.setOnClickListener {
viewModel.retryLive()
}
binding.layoutCreatorChannelLiveOwnerCta.setOnClickListener {
binding.layoutCreatorChannelLiveOwnerCta.isEnabled = false
host.onCreatorChannelLiveStartClicked()
}
}
private fun bindOwnerCta(isOwner: Boolean) = with(binding) {
this@CreatorChannelLiveFragment.isOwner = isOwner
layoutCreatorChannelLiveOwnerCta.isVisible = isOwner
updateOwnerCtaInsets()
}
private fun updateOwnerCtaInsets() = with(binding) {
val baseBottomMargin = OWNER_CTA_BASE_MARGIN_DP.dpToPx().toInt()
val listBottomPadding = if (isOwner) {
OWNER_CTA_LIST_BOTTOM_PADDING_DP.dpToPx().toInt() + ownerCtaBottomInset
} else {
DEFAULT_LIST_BOTTOM_PADDING_DP.dpToPx().toInt()
}
layoutCreatorChannelLiveOwnerCta.updateLayoutParams<ConstraintLayout.LayoutParams> {
bottomMargin = baseBottomMargin + ownerCtaBottomInset
}
rvCreatorChannelLiveReplays.updatePadding(bottom = listBottomPadding)
}
private fun observeViewModel() {
@@ -174,13 +225,18 @@ class CreatorChannelLiveFragment : BaseFragment<FragmentCreatorChannelLiveBindin
}
interface Host {
fun isCreatorChannelOwner(): Boolean
fun onCreatorChannelCurrentLiveClicked(live: CreatorChannelLiveResponse)
fun onCreatorChannelLiveReplayClicked(audioContentId: Long)
fun onCreatorChannelLiveStartClicked()
fun onCreatorChannelLiveContentChanged()
}
companion object {
private const val ARG_CREATOR_ID: String = "arg_creator_id"
private const val DEFAULT_LIST_BOTTOM_PADDING_DP = 32
private const val OWNER_CTA_BASE_MARGIN_DP = 14
private const val OWNER_CTA_LIST_BOTTOM_PADDING_DP = 102
fun newInstance(creatorId: Long): CreatorChannelLiveFragment {
return CreatorChannelLiveFragment().apply {

View File

@@ -994,6 +994,25 @@ class CreatorChannelActivitySourceTest {
assertFalse(source.contains("startActivity(Intent(this, LiveRoomCreateActivity::class.java))"))
}
@Test
fun `라이브 탭 owner CTA는 header 본인 여부와 기존 라이브 생성 플로우를 재사용한다`() {
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/live/CreatorChannelLiveFragment.kt"
).readText()
assertTrue(source.contains("findLiveFragment()?.onCreatorChannelOwnerChanged(header.isOwner)"))
assertTrue(source.contains("override fun isCreatorChannelOwner(): Boolean"))
assertTrue(source.contains("return currentHeader?.isOwner == true"))
assertTrue(source.contains("override fun onCreatorChannelLiveStartClicked()"))
assertTrue(source.contains("onOwnerFabLiveClicked()"))
assertTrue(source.contains("liveRoomCreateLauncher.launch(Intent(this, LiveRoomCreateActivity::class.java))"))
assertTrue(fragment.contains("fun onCreatorChannelOwnerChanged(isOwner: Boolean)"))
assertTrue(fragment.contains("host.onCreatorChannelLiveStartClicked()"))
}
@Test
fun `크리에이터 채널 라이브 로직은 coordinator로 분리한다`() {
val activity = projectFile(

View File

@@ -196,6 +196,32 @@ class CreatorChannelLiveFragmentLayoutTest {
assertTrue(adapter.contains("layoutCreatorChannelLiveReplayActionText.isVisible = true"))
}
@Test
fun `라이브 owner CTA source는 본인 여부 노출 inset padding click 연결을 포함한다`() {
val fragment = projectFile(
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt"
).readText()
val layout = projectFile("app/src/main/res/layout/fragment_creator_channel_live.xml").readText()
assertTrue(layout.contains("android:id=\"@+id/layout_creator_channel_live_owner_cta\""))
assertTrue(layout.contains("@drawable/ic_new_create_live"))
assertTrue(layout.contains("@string/creator_channel_live_start_button"))
assertTrue(fragment.contains("setupOwnerCtaInsets()"))
assertTrue(fragment.contains("WindowInsetsCompat.Type.navigationBars()"))
assertTrue(fragment.contains("layoutCreatorChannelLiveOwnerCta.updateLayoutParams<ConstraintLayout.LayoutParams>"))
assertTrue(fragment.contains("rvCreatorChannelLiveReplays.updatePadding"))
assertTrue(fragment.contains("onCreatorChannelOwnerChanged(isOwner: Boolean)"))
assertTrue(fragment.contains("bindOwnerCta(host.isCreatorChannelOwner())"))
assertTrue(fragment.contains("layoutCreatorChannelLiveOwnerCta.isVisible = isOwner"))
assertTrue(fragment.contains("layoutCreatorChannelLiveOwnerCta.setOnClickListener"))
assertTrue(fragment.contains("host.onCreatorChannelLiveStartClicked()"))
assertTrue(fragment.contains("layoutCreatorChannelLiveOwnerCta.isEnabled = false"))
assertTrue(fragment.contains("onResume()"))
assertTrue(fragment.contains("interface Host"))
assertTrue(fragment.contains("fun isCreatorChannelOwner(): Boolean"))
assertTrue(fragment.contains("fun onCreatorChannelLiveStartClicked()"))
}
@Test
fun `라이브 sort popup source는 outside dismiss와 화면 밖 보정 및 같은 정렬 dismiss를 포함한다`() {
val popup = projectFile(