From efac753f83636a719adca4aa8c7bdef35a8bf321 Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 18 Jun 2026 15:21:26 +0900 Subject: [PATCH] =?UTF-8?q?fix(creator):=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=20e?= =?UTF-8?q?mpty=EC=99=80=20CTA=20=EB=AA=A9=EB=A1=9D=20=EC=97=AC=EB=B0=B1?= =?UTF-8?q?=EC=9D=84=20=EB=B3=B4=EC=A0=95=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../live/CreatorChannelLiveFragment.kt | 72 ++++-------- .../layout/fragment_creator_channel_live.xml | 57 +++------- .../CreatorChannelLiveFragmentLayoutTest.kt | 105 ++++++++++++++---- 3 files changed, 117 insertions(+), 117 deletions(-) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt index c39309a1..1e042fb5 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt @@ -3,11 +3,7 @@ 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 @@ -34,8 +30,7 @@ class CreatorChannelLiveFragment : BaseFragment - ownerCtaBottomInset = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom - updateOwnerCtaInsets() - insets + fun onCreatorChannelLiveOwnerCtaVisibilityChanged(isVisible: Boolean) = with(binding) { + val bottomPadding = if (isVisible) { + LIVE_OWNER_CTA_LIST_BOTTOM_PADDING_DP.dpToPx().toInt() + } else { + DEFAULT_LIST_BOTTOM_PADDING_DP.dpToPx().toInt() } - ViewCompat.requestApplyInsets(binding.layoutCreatorChannelLiveOwnerCta) + rvCreatorChannelLiveReplays.updatePadding(bottom = bottomPadding) } private fun setupClickListeners() { @@ -99,29 +88,6 @@ class CreatorChannelLiveFragment : BaseFragment { - bottomMargin = baseBottomMargin + ownerCtaBottomInset - } - rvCreatorChannelLiveReplays.updatePadding(bottom = listBottomPadding) } private fun observeViewModel() { @@ -141,7 +107,7 @@ class CreatorChannelLiveFragment : BaseFragment - + tools:visibility="visible"> + + + + app:layout_constraintTop_toTopOf="parent" /> - - - - - diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragmentLayoutTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragmentLayoutTest.kt index 371091ad..f85aaf6e 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragmentLayoutTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragmentLayoutTest.kt @@ -28,22 +28,53 @@ class CreatorChannelLiveFragmentLayoutTest { @Test fun `라이브 fragment layout은 sort current live list empty error owner CTA를 제공한다`() { val root = inflateView(R.layout.fragment_creator_channel_live) + val layout = projectFile("app/src/main/res/layout/fragment_creator_channel_live.xml").readText() val sortBar = requireNotNull(root.findViewById(R.id.layout_creator_channel_live_sort_bar)) val currentLiveCard = requireNotNull(root.findViewById(R.id.layout_creator_channel_live_current_card)) val replayList = requireNotNull(root.findViewById(R.id.rv_creator_channel_live_replays)) val emptyMessage = requireNotNull(root.findViewById(R.id.tv_creator_channel_live_empty_message)) + val emptyContainer = requireNotNull(root.findViewById(R.id.layout_creator_channel_live_empty)) val errorMessage = requireNotNull(root.findViewById(R.id.tv_creator_channel_live_error_message)) val retryButton = requireNotNull(root.findViewById(R.id.btn_creator_channel_live_retry)) - val ownerCta = requireNotNull(root.findViewById(R.id.layout_creator_channel_live_owner_cta)) - assertSame(root, sortBar.parent) assertSame(root, replayList.parent) - assertSame(root, emptyMessage.parent) + assertSame(root, emptyContainer.parent) + assertSame(emptyContainer, emptyMessage.parent) assertSame(root, errorMessage.parent) assertSame(root, retryButton.parent) - assertSame(root, ownerCta.parent) assertNotNull(currentLiveCard.findViewById(R.id.tv_creator_channel_live_current_title)) assertEquals(false, replayList.clipToPadding) + assertTrue(layout.contains("android:layout_height=\"wrap_content\"")) + assertTrue(layout.contains("android:id=\"@+id/layout_creator_channel_live_empty\"")) + assertFalse(layout.contains("android:minHeight=\"360dp\"")) + assertTrue(layout.contains("android:gravity=\"center\"")) + assertFalse( + layout.contains( + "android:id=\"@+id/tv_creator_channel_live_empty_message\"" + + "\n style=\"@style/Typography.Body3\"" + + "\n android:layout_width=\"0dp\"" + + "\n android:layout_height=\"wrap_content\"" + + "\n android:gravity=\"center\"" + + "\n android:text=\"@string/creator_channel_live_empty_message\"" + + "\n android:textColor=\"@color/gray_500\"" + + "\n android:visibility=\"gone\"" + + "\n app:layout_constraintBottom_toBottomOf=\"parent\"" + ) + ) + assertFalse(layout.contains("app:layout_constraintBottom_toTopOf=\"@id/btn_creator_channel_live_retry\"")) + } + + @Test + fun `라이브 empty container 최소 높이는 Activity가 전달한 viewport 높이를 사용한다`() { + val fragment = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/CreatorChannelLiveFragment.kt" + ).readText() + + assertTrue(fragment.contains("private var emptyMinHeight: Int = 0")) + assertTrue(fragment.contains("fun onCreatorChannelLiveViewportHeightChanged(minHeight: Int)")) + assertTrue(fragment.contains("emptyMinHeight = minHeight.coerceAtLeast(0)")) + assertTrue(fragment.contains("layoutCreatorChannelLiveEmpty.minimumHeight = emptyMinHeight")) + assertTrue(fragment.contains("applyEmptyMinHeight()")) } @Test @@ -86,6 +117,11 @@ class CreatorChannelLiveFragmentLayoutTest { @Test fun `라이브 다시듣기 item layout은 썸네일 tag title duration action 영역을 제공한다`() { val item = inflateView(R.layout.item_creator_channel_live_replay) + val itemLayout = projectFile("app/src/main/res/layout/item_creator_channel_live_replay.xml").readText() + val adapter = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveReplayAdapter.kt" + ).readText() + val thumbnail = requireNotNull(item.findViewById(R.id.layout_creator_channel_live_replay_thumbnail)) assertNotNull(item.findViewById(R.id.iv_creator_channel_live_replay_thumbnail)) assertNotNull(item.findViewById(R.id.iv_creator_channel_live_replay_adult_badge)) @@ -100,6 +136,14 @@ class CreatorChannelLiveFragmentLayoutTest { assertNotNull(item.findViewById(R.id.iv_creator_channel_live_replay_can)) assertNotNull(item.findViewById(R.id.layout_creator_channel_live_replay_action_text)) assertNotNull(item.findViewById(R.id.tv_creator_channel_live_replay_action_text)) + assertEquals(dp(88), thumbnail.layoutParams.width) + assertEquals(dp(88), thumbnail.layoutParams.height) + assertTrue(itemLayout.contains("android:layout_marginBottom=\"@dimen/spacing_8\"")) + assertTrue(itemLayout.contains("android:clipToOutline=\"true\"")) + assertTrue(itemLayout.contains("android:outlineProvider=\"background\"")) + assertTrue(itemLayout.contains("@drawable/bg_audio_content_card_thumbnail")) + assertTrue(adapter.contains("layoutCreatorChannelLiveReplayThumbnail.clipToOutline = true")) + assertTrue(adapter.contains("resources.getDimension(R.dimen.radius_14)")) } @Test @@ -163,7 +207,6 @@ class CreatorChannelLiveFragmentLayoutTest { if (creatorId > 0L) """.trimIndent() - assertTrue(layout.contains("android:layout_height=\"match_parent\"")) assertTrue(fragment.contains("R.drawable.ic_new_sort")) assertTrue(fragment.contains("CreatorChannelLiveSortPopup")) assertTrue(fragment.contains("layoutCreatorChannelLiveSortButton.setOnClickListener")) @@ -197,29 +240,43 @@ class CreatorChannelLiveFragmentLayoutTest { } @Test - fun `라이브 owner CTA source는 본인 여부 노출 inset padding click 연결을 포함한다`() { + fun `라이브 owner CTA는 Activity root overlay로 Figma 고정 영역을 제공한다`() { + val activity = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt" + ).readText() + val activityLayout = projectFile("app/src/main/res/layout/activity_creator_channel.xml").readText() 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() + val fragmentLayout = 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")) - 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()")) + assertTrue(activityLayout.contains("android:id=\"@+id/layout_creator_channel_live_owner_cta\"")) + assertTrue(activityLayout.contains("android:layout_height=\"100dp\"")) + assertTrue(activityLayout.contains("android:background=\"@color/black\"")) + assertTrue(activityLayout.contains("android:id=\"@+id/btn_creator_channel_live_owner_cta\"")) + assertTrue(activityLayout.contains("android:layout_marginHorizontal=\"@dimen/spacing_14\"")) + assertTrue(activityLayout.contains("android:layout_marginTop=\"@dimen/spacing_14\"")) + assertTrue(activityLayout.contains("@drawable/bg_creator_channel_owner_fab")) + assertTrue(activityLayout.contains("@drawable/ic_new_create_live")) + assertTrue(activityLayout.contains("@string/creator_channel_live_start_button")) + assertTrue(activity.contains("setupLiveOwnerCtaInsets()")) + assertTrue(activity.contains("updateLiveOwnerCtaVisibility()")) + assertTrue(activity.contains("binding.viewPager.currentItem == CreatorChannelTab.Live.ordinal")) + assertTrue(activity.contains("binding.layoutCreatorChannelLiveOwnerCta.isVisible = shouldShowLiveOwnerCta")) + assertTrue( + activity.contains( + "findLiveFragment()?.onCreatorChannelLiveOwnerCtaVisibilityChanged(shouldShowLiveOwnerCta)" + ) + ) + assertTrue(fragment.contains("fun onCreatorChannelLiveOwnerCtaVisibilityChanged(isVisible: Boolean)")) + assertTrue(fragment.contains("val bottomPadding = if (isVisible)")) + assertTrue(fragment.contains("LIVE_OWNER_CTA_LIST_BOTTOM_PADDING_DP.dpToPx().toInt()")) + assertTrue(fragment.contains("DEFAULT_LIST_BOTTOM_PADDING_DP.dpToPx().toInt()")) + assertTrue(fragment.contains("rvCreatorChannelLiveReplays.updatePadding(bottom = bottomPadding)")) + assertTrue(activity.contains("binding.btnCreatorChannelLiveOwnerCta.setOnClickListener")) + assertTrue(activity.contains("onOwnerFabLiveClicked()")) + assertFalse(fragmentLayout.contains("android:id=\"@+id/layout_creator_channel_live_owner_cta\"")) + assertFalse(fragment.contains("layoutCreatorChannelLiveOwnerCta")) } @Test