diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt index d5b8d29a..e1821e44 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt @@ -102,12 +102,11 @@ class CreatorChannelDonationAdapter( private val onEmptyDonationClick: () -> Unit ) : RecyclerView.ViewHolder(binding.root) { - init { - binding.btnCreatorChannelDonationEmptyWrite.setOnClickListener { onEmptyDonationClick() } - } - fun bind(isOwner: Boolean) = with(binding) { btnCreatorChannelDonationEmptyWrite.isVisible = !isOwner + btnCreatorChannelDonationEmptyWrite.setOnClickListener( + if (!isOwner) View.OnClickListener { onEmptyDonationClick() } else null + ) } } 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 f620d569..92545d57 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 @@ -80,7 +80,6 @@ class CreatorChannelFanTalkFragment : BaseFragment, val currentMemberId: Long ) : CreatorChannelHomeSection - data class FanTalk(val fanTalk: CreatorChannelFanTalkSummaryResponse) : CreatorChannelHomeSection + data class FanTalk( + val fanTalk: CreatorChannelFanTalkSummaryResponse, + val isOwner: Boolean + ) : CreatorChannelHomeSection data class Introduce(val introduce: String) : CreatorChannelHomeSection data class Activity(val activity: CreatorChannelActivityResponse) : CreatorChannelHomeSection data class Sns(val items: List) : CreatorChannelHomeSection diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt index 4475630b..11b637f8 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt @@ -113,6 +113,7 @@ class CreatorChannelHomeSectionAdapter( private val fanTalkTotalRow: View? = view.findViewById(R.id.layout_fantalk_total_row) private val fanTalkLatestRow: View? = view.findViewById(R.id.layout_fantalk_latest_row) private val fanTalkEmpty: View? = view.findViewById(R.id.layout_fantalk_empty) + private val fanTalkSupportButton: View? = view.findViewById(R.id.layout_fantalk_support_button) private val fanTalkTotalCount: TextView? = view.findViewById(R.id.tv_fantalk_total_count) private val fanTalkProfile: ImageView? = view.findViewById(R.id.iv_fantalk_profile) private val fanTalkContent: TextView? = view.findViewById(R.id.tv_fantalk_content) @@ -184,13 +185,7 @@ class CreatorChannelHomeSectionAdapter( val isDonationEmptyButtonVisible = !item.isOwner donationButton?.isVisible = isDonationButtonVisible donationEmptyButton?.isVisible = isDonationEmptyButtonVisible - donationEmptyTitle?.setText( - if (item.isOwner) { - R.string.creator_channel_donation_empty_owner_title - } else { - R.string.creator_channel_donation_empty_title - } - ) + donationEmptyTitle?.setText(R.string.creator_channel_home_donation_empty_title) donationButton?.setOnClickListener( if (isDonationButtonVisible) View.OnClickListener { onDonationClick() } else null ) @@ -417,6 +412,11 @@ class CreatorChannelHomeSectionAdapter( fanTalkTotalRow?.isVisible = fanTalk != null && item.fanTalk.totalCount > 0 fanTalkLatestRow?.isVisible = fanTalk != null && item.fanTalk.totalCount > 0 fanTalkEmpty?.isVisible = fanTalk == null || item.fanTalk.totalCount <= 0 + val showSupportButton = (fanTalk == null || item.fanTalk.totalCount <= 0) && !item.isOwner + fanTalkSupportButton?.isVisible = showSupportButton + fanTalkSupportButton?.setOnClickListener( + if (showSupportButton) View.OnClickListener { onSectionChevronClick(CreatorChannelTab.FanTalk) } else null + ) if (fanTalk != null) { fanTalkContent?.text = fanTalk.content fanTalkProfile?.loadUrl(fanTalk.profileImageUrl) { diff --git a/app/src/main/res/layout/item_creator_channel_home_donation.xml b/app/src/main/res/layout/item_creator_channel_home_donation.xml index 76ea2849..ee1e5e31 100644 --- a/app/src/main/res/layout/item_creator_channel_home_donation.xml +++ b/app/src/main/res/layout/item_creator_channel_home_donation.xml @@ -49,7 +49,7 @@ android:layout_height="0dp" android:layout_weight="1" android:includeFontPadding="true" - android:text="@string/creator_channel_donation_empty_title" + android:text="@string/creator_channel_home_donation_empty_title" android:textColor="@color/white" /> diff --git a/app/src/main/res/layout/item_creator_channel_home_fantalk.xml b/app/src/main/res/layout/item_creator_channel_home_fantalk.xml index 8c3657a6..7f6d901e 100644 --- a/app/src/main/res/layout/item_creator_channel_home_fantalk.xml +++ b/app/src/main/res/layout/item_creator_channel_home_fantalk.xml @@ -77,10 +77,11 @@ tools:text="팬이 쓴 응원이 보이는 부분입니다. 두 줄까지만 보여주고 초과 시 말줄임 처리." /> - @@ -89,7 +90,6 @@ style="@style/Typography.Heading1" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="top|start" android:layout_marginStart="@dimen/spacing_6" android:layout_marginTop="@dimen/spacing_6" android:includeFontPadding="false" @@ -100,8 +100,7 @@ android:id="@+id/layout_fantalk_support_button" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="bottom|center_horizontal" - android:layout_marginBottom="@dimen/spacing_6" + android:layout_marginTop="@dimen/spacing_14" android:background="@drawable/bg_round_corner_999_white" android:gravity="center" android:orientation="horizontal" @@ -122,6 +121,6 @@ android:text="@string/creator_channel_fantalk_support_action" android:textColor="@color/black" /> - + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 7ed3874c..9c5a2f62 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -375,9 +375,9 @@ Could not load the post. Purchased %1$s · Total %2$d - Be the first\nto cheer them on! - Leave support - No cheers yet.\nBe the first to cheer for the creator! + We\'re waiting for your\nfirst fan letter! + Send fan letter + No fan letters yet.\nSend the first fan letter! Could not load Fan Letter. Retry All @@ -404,7 +404,8 @@ Support Could not load donation history. Retry - Be the first to\nsupport this creator! + Be the first to\nsupport this creator! + No support yet.\nBe the first to support this creator! No donation history yet. %1$s cans Supported with %1$d cans. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 8237e893..00042310 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -375,7 +375,7 @@ 投稿を読み込めません。 購入済み %1$s · 全体 %2$d - 最初の応援を\n待っています! + あなたの最初のファンレターを待っています! ファンレターを送る まだファンレターがありません。\n最初のファンレターを送ってみましょう! ファンレターを読み込めませんでした。 @@ -404,6 +404,7 @@ サポートする 応援履歴を読み込めませんでした。 再試行 + 最初のサポートをしてみましょう! まだサポートがありません。\n最初のサポートをしてみましょう! 応援履歴がありません %1$sCAN diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 228dbbe6..826f90a1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -375,7 +375,7 @@ 구매 완료 %1$s · 전체 %2$d 당신의 첫 응원을\n기다리고 있어요! - 응원 남기기 + 응원남기기 아직 응원이 없습니다.\n처음으로 크리에이터를 응원해 보세요! 팬레터를 불러오지 못했습니다. 다시 시도 @@ -403,6 +403,7 @@ 후원하기 후원 내역을 불러오지 못했습니다. 다시 시도 + 처음으로 크리에이터를\n후원해 보세요! 아직 후원이 없습니다.\n처음으로 크리에이터를 후원해 보세요! 후원 내역이 없습니다 %1$s캔 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 d02ad297..359ed71c 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 @@ -1113,7 +1113,12 @@ class CreatorChannelActivitySourceTest { assertTrue(donationLayout.contains("@+id/ll_donation_items")) assertTrue(donationLayout.contains("@+id/layout_donation_empty")) assertTrue(donationLayout.contains("@+id/tv_donation_empty_title")) - assertTrue(donationLayout.contains("@string/creator_channel_donation_empty_title")) + assertTrue(donationLayout.contains("@string/creator_channel_home_donation_empty_title")) + val donationEmptyButtonBlock = donationLayout + .substringAfter("android:id=\"@+id/layout_donation_empty_button\"") + .substringBefore("") + assertTrue(donationEmptyButtonBlock.contains("android:background=\"@drawable/bg_round_corner_999_white\"")) + assertTrue(donationEmptyButtonBlock.contains("android:textColor=\"@color/black\"")) assertTrue(donationLayout.contains("android:layout_width=\"match_parent\"")) assertTrue(donationLayout.contains("android:layout_height=\"wrap_content\"")) assertTrue(donationLayout.contains("@+id/layout_donation_button")) @@ -1308,13 +1313,15 @@ class CreatorChannelActivitySourceTest { val en = projectFile("app/src/main/res/values-en/strings.xml").readText() val ja = projectFile("app/src/main/res/values-ja/strings.xml").readText() - assertTrue( - ko.contains( - "name=\"creator_channel_donation_empty_title\">아직 후원이 없습니다.\\n처음으로 크리에이터를 후원해 보세요!" - ) - ) - assertTrue(en.contains("creator_channel_donation_empty_title")) - assertTrue(ja.contains("creator_channel_donation_empty_title")) + assertTrue(ko.contains("name=\"creator_channel_home_donation_empty_title\">처음으로 크리에이터를\\n후원해 보세요!")) + assertTrue(en.contains("name=\"creator_channel_home_donation_empty_title\">Be the first to\\nsupport this creator!")) + assertTrue(ja.contains("name=\"creator_channel_home_donation_empty_title\">最初のサポートをしてみましょう!")) + assertTrue(ko.contains("name=\"creator_channel_donation_empty_title\">아직 후원이 없습니다.\\n처음으로 크리에이터를 후원해 보세요!")) + assertTrue(en.contains("name=\"creator_channel_donation_empty_title\">No support yet.\\nBe the first to support this creator!")) + assertTrue(ja.contains("name=\"creator_channel_donation_empty_title\">まだサポートがありません。\\n最初のサポートをしてみましょう!")) + assertTrue(ko.contains("name=\"creator_channel_donation_action\">후원하기")) + assertTrue(en.contains("name=\"creator_channel_donation_action\">Support")) + assertTrue(ja.contains("name=\"creator_channel_donation_action\">サポートする")) } @Test @@ -2136,10 +2143,17 @@ class CreatorChannelActivitySourceTest { assertTrue(fanTalkLayout.contains("@+id/layout_fantalk_support_button")) assertTrue(fanTalkLayout.contains("@string/creator_channel_fantalk_empty_title")) assertTrue(fanTalkLayout.contains("@string/creator_channel_fantalk_support_action")) + val fanTalkSupportButtonBlock = fanTalkLayout + .substringAfter("android:id=\"@+id/layout_fantalk_support_button\"") + .substringBefore("") + assertTrue(fanTalkSupportButtonBlock.contains("android:background=\"@drawable/bg_round_corner_999_white\"")) + assertTrue(fanTalkSupportButtonBlock.contains("android:textColor=\"@color/black\"")) assertTrue(fanTalkLayout.contains("@drawable/ic_new_fantalk_plus")) assertTrue(fanTalkLayout.contains("android:layout_width=\"match_parent\"")) - assertTrue(fanTalkLayout.contains("android:layout_height=\"169dp\"")) - assertTrue(fanTalkLayout.contains("android:layout_marginBottom=\"@dimen/spacing_6\"")) + assertTrue(fanTalkLayout.contains("android:layout_height=\"wrap_content\"")) + assertTrue(fanTalkLayout.contains("android:layout_marginTop=\"@dimen/spacing_14\"")) + assertFalse(fanTalkLayout.contains("android:layout_height=\"169dp\"")) + assertFalse(fanTalkLayout.contains("android:layout_marginBottom=\"@dimen/spacing_6\"")) assertFalse(fanTalkLayout.contains("@+id/ll_section_items")) assertFalse(fanTalkLayout.contains("android:clipToOutline")) assertTrue(fanTalkCardView.contains("clipToOutline = true")) @@ -2153,10 +2167,14 @@ class CreatorChannelActivitySourceTest { assertTrue(adapter.contains("private val fanTalkTotalRow: View?")) assertTrue(adapter.contains("private val fanTalkLatestRow: View?")) assertTrue(adapter.contains("private val fanTalkEmpty: View?")) + assertTrue(adapter.contains("private val fanTalkSupportButton: View?")) assertTrue(adapter.contains("fanTalkTotalCount?.text = item.fanTalk.totalCount.toString()")) assertTrue(adapter.contains("fanTalkTotalRow?.isVisible = fanTalk != null")) assertTrue(adapter.contains("fanTalkLatestRow?.isVisible = fanTalk != null")) assertTrue(adapter.contains("fanTalkEmpty?.isVisible = fanTalk == null")) + assertTrue(adapter.contains("val showSupportButton = (fanTalk == null || item.fanTalk.totalCount <= 0) && !item.isOwner")) + assertTrue(adapter.contains("fanTalkSupportButton?.isVisible = showSupportButton")) + assertTrue(adapter.contains("fanTalkSupportButton?.setOnClickListener")) assertTrue(adapter.contains("fanTalkContent?.text = fanTalk.content")) assertTrue(adapter.contains("fanTalkProfile?.loadUrl(fanTalk.profileImageUrl)")) assertTrue(adapter.contains("fanTalkContent?.text = \"\"")) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt index 59013b8f..0cb4daf0 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt @@ -53,12 +53,14 @@ class CreatorChannelHomeMapperTest { } @Test - fun `본인 채널 여부를 후원 section에도 매핑한다`() { + fun `본인 채널 여부를 후원과 팬Talk section에도 매핑한다`() { val ownerContent = response().toUiContent(currentMemberId = 100L) val visitorContent = response().toUiContent(currentMemberId = 1L) assertTrue(ownerContent.sections.filterIsInstance().single().isOwner) assertFalse(visitorContent.sections.filterIsInstance().single().isOwner) + assertTrue(ownerContent.sections.filterIsInstance().single().isOwner) + assertFalse(visitorContent.sections.filterIsInstance().single().isOwner) } @Test diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt index ee4c6172..11108eda 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationActionTest.kt @@ -91,7 +91,7 @@ class CreatorChannelDonationActionTest { assertTrue(adapter.contains("onEmptyDonationClick")) assertTrue(adapter.contains("btnCreatorChannelDonationRankingAll.setOnClickListener")) assertTrue(adapter.contains("btnCreatorChannelDonationEmptyWrite.isVisible = !isOwner")) - assertTrue(adapter.contains("btnCreatorChannelDonationEmptyWrite.setOnClickListener")) + assertTrue(adapter.contains("if (!isOwner) View.OnClickListener { onEmptyDonationClick() } else null")) assertTrue(adapter.contains("bindCreatorChannelDonationCard(")) assertTrue(adapter.contains("headerColorResId = item.headerColorResId")) assertTrue(adapter.contains("createdAtText = item.createdAtText")) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt index 2d9ccbde..d865bf2b 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt @@ -57,6 +57,7 @@ class CreatorChannelDonationFragmentLayoutTest { assertTrue(layout.contains("android:text=\"@string/creator_channel_donation_empty_title\"")) assertTrue(layout.contains("android:text=\"@string/creator_channel_donation_action\"")) assertTrue(layout.contains("android:background=\"@drawable/bg_creator_channel_donation_empty_button\"")) + assertTrue(layout.contains("android:textColor=\"@color/white\"")) assertTrue(layout.contains("android:layout_marginTop=\"@dimen/spacing_14\"")) assertTrue(layout.contains("android:src=\"@drawable/ic_new_donation\"")) assertTrue(layout.contains("android:paddingTop=\"@dimen/spacing_48\"")) 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 3f119f6d..82490836 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 @@ -81,9 +81,15 @@ class CreatorChannelFanTalkFragmentLayoutTest { .readText() .contains("@color/gray_800") ) - assertLocaleString("values/strings.xml", "아직 응원이 없습니다.\\n처음으로 크리에이터를 응원해 보세요!") - assertLocaleString("values-en/strings.xml", "No cheers yet.\\nBe the first to cheer for the creator!") - assertLocaleString("values-ja/strings.xml", "まだファンレターがありません。\\n最初のファンレターを送ってみましょう!") + assertLocaleString("values/strings.xml", "creator_channel_fantalk_empty_title", "당신의 첫 응원을\\n기다리고 있어요!") + assertLocaleString("values-en/strings.xml", "creator_channel_fantalk_empty_title", "We\\'re waiting for your\\nfirst fan letter!") + assertLocaleString("values-ja/strings.xml", "creator_channel_fantalk_empty_title", "あなたの最初のファンレターを待っています!") + assertLocaleString("values/strings.xml", "creator_channel_fantalk_empty_message", "아직 응원이 없습니다.\\n처음으로 크리에이터를 응원해 보세요!") + assertLocaleString("values-en/strings.xml", "creator_channel_fantalk_empty_message", "No fan letters yet.\\nSend the first fan letter!") + assertLocaleString("values-ja/strings.xml", "creator_channel_fantalk_empty_message", "まだファンレターがありません。\\n最初のファンレターを送ってみましょう!") + assertLocaleString("values/strings.xml", "creator_channel_fantalk_support_action", "응원남기기") + assertLocaleString("values-en/strings.xml", "creator_channel_fantalk_support_action", "Send fan letter") + assertLocaleString("values-ja/strings.xml", "creator_channel_fantalk_support_action", "ファンレターを送る") } @Test @@ -209,10 +215,12 @@ class CreatorChannelFanTalkFragmentLayoutTest { assertTrue(fragment.contains("fun onCreatorChannelFanTalkWriteClicked()")) assertTrue(fragment.contains("fun onCreatorChannelFanTalkFloatingButtonVisibilityChanged(isVisible: Boolean)")) assertTrue(fragment.contains("layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener")) + assertTrue(fragment.contains("layoutCreatorChannelFantalkEmptyWriteButton.isVisible = !host.isCreatorChannelOwner()")) 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("layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener { host.onCreatorChannelFanTalkWriteClicked() }")) 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\"")) @@ -227,9 +235,9 @@ class CreatorChannelFanTalkFragmentLayoutTest { private fun projectFile(path: String): File = File(projectRoot(), path) - private fun assertLocaleString(path: String, expectedValue: String) { + private fun assertLocaleString(path: String, name: String, expectedValue: String) { val strings = projectFile("app/src/main/res/$path").readText() - assertTrue(strings.contains("$expectedValue")) + assertTrue(strings.contains("$expectedValue")) } private fun projectRoot(): File { diff --git a/docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/plan-task.md b/docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/plan-task.md index 4aa23f90..7879af40 100644 --- a/docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/plan-task.md +++ b/docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/plan-task.md @@ -12,11 +12,11 @@ | 문서 항목 | 내용 | |---|---| -| 상태 | 계획 작성 중 | +| 상태 | 후속 색상 수정 완료 | | 작성일 | `2026-08-14` | | 요구사항 기준 | `docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/prd.md` | | API 기준 | 없음 | -| 현재 Phase | Phase 1 대기 | +| 현재 Phase | Phase 1 후속 색상 수정 완료 | | 현재 활성 Goal | 없음 | ## 목표 @@ -27,7 +27,7 @@ | Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | |---:|---|---:|---|---| -| 1 | 대기 | `0/4` | `P1-T1` | 사용자 승인 후 구현 시작 | +| 1 | 완료 | `6/6` | 없음 | 없음 | ## 범위 @@ -36,7 +36,7 @@ - 한국어/영어/일본어 string resource 값 정리 - 홈 전용 후원 empty title resource 추가 - 홈 후원 empty, 홈 팬레터 empty, 팬레터 탭 empty, 후원 탭 empty의 내 채널 action 버튼 숨김 정책 -- 홈 탭 팬레터 empty UI 표시와 기존 홈 후원 empty UI 구조 재사용 +- 기존 홈 탭 팬레터 empty UI의 문구와 내 채널 action 버튼 정책 보강 - `app/src/test` 기반 resource/source/layout 회귀 테스트 ### 제외 @@ -63,7 +63,7 @@ **선행조건:** PRD `CREATOR-EMPTY-I18N-001~005`, `CREATOR-EMPTY-UI-001~005` 확정. -**Phase 완료 조건:** `P1-T1`~`P1-T3`과 `P1-GATE` 완료, 검증 기록 누적. +**Phase 완료 조건:** `P1-T1`~`P1-T5`와 `P1-GATE` 완료, 검증 기록 누적. ### 구현 항목 @@ -89,7 +89,7 @@ - Consumes: 기존 `R.string.creator_channel_fantalk_empty_title`, `R.string.creator_channel_fantalk_empty_message`, `R.string.creator_channel_fantalk_support_action`, `R.string.creator_channel_donation_empty_title`, `R.string.creator_channel_donation_action` - Produces: 신규 `R.string.creator_channel_home_donation_empty_title` -- [ ] **RED:** resource 값 검증 테스트에 아래 기대값을 추가하거나 갱신한다. +- [x] **RED:** resource 값 검증 테스트에 아래 기대값을 추가하거나 갱신한다. ```kotlin val expectedKo = mapOf( @@ -118,7 +118,7 @@ ) ``` -- [ ] **RED 확인:** focused resource/source test를 실행해 신규 홈 후원 key 누락 또는 기존 값 불일치로 실패함을 확인한다. +- [x] **RED 확인:** focused resource/source test를 실행해 신규 홈 후원 key 누락 또는 기존 값 불일치로 실패함을 확인한다. ```bash ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" @@ -126,7 +126,7 @@ Expected: `creator_channel_home_donation_empty_title` 누락 또는 기존 팬레터/후원 문구 불일치 assertion 실패. -- [ ] **GREEN:** 세 locale resource에 아래 값을 반영한다. +- [x] **GREEN:** 세 locale resource에 아래 값을 반영한다. ```xml 처음으로 크리에이터를\n후원해 보세요! @@ -155,7 +155,7 @@ サポートする ``` -- [ ] **GREEN 확인:** focused resource/source test와 resource merge를 실행한다. +- [x] **GREEN 확인:** focused resource/source test와 resource merge를 실행한다. ```bash ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" @@ -164,14 +164,14 @@ Expected: 두 명령 모두 `BUILD SUCCESSFUL`. -- [ ] **REFACTOR:** resource key 순서와 테스트 중복만 정리하고 같은 명령을 재실행해 결과를 Progress에 기록한다. +- [x] **REFACTOR:** resource key 순서와 테스트 중복만 정리하고 같은 명령을 재실행해 결과를 Progress에 기록한다. #### Task 1.2 홈 탭 empty UI와 내 채널 버튼 정책 **Goal 실행 `P1-T2`:** 홈 후원/팬레터 empty UI가 화면별 문구를 사용하고 내 채널에서는 action 버튼을 숨긴다. - **시작 조건:** `P1-T1` 완료. -- **완료 증거:** 홈 후원 empty가 홈 전용 string을 사용하고, 홈 팬레터 empty는 빈 데이터에서 표시되며, `isOwner == true`에서 두 empty action 버튼이 숨겨진다. +- **완료 증거:** 홈 후원 empty가 홈 전용 string을 사용하고, 기존 홈 팬레터 empty 표시 분기는 유지되며, `isOwner == true`에서 두 empty action 버튼이 숨겨진다. - **범위 밖:** 상세 팬레터/후원 탭 Fragment 변경. **Files:** @@ -187,7 +187,7 @@ - Consumes: `CreatorChannelHomeSection.Donations.isOwner`, `CreatorChannelHomeSection.FanTalk`, 신규 `R.string.creator_channel_home_donation_empty_title` - Produces: 홈 empty button visibility와 click listener owner 분기 -- [ ] **RED:** 홈 후원 layout이 홈 전용 string을 참조하는지 테스트한다. +- [x] **RED:** 홈 후원 layout이 홈 전용 string을 참조하는지 테스트한다. ```kotlin val donationLayout = projectFile("app/src/main/res/layout/item_creator_channel_home_donation.xml").readText() @@ -195,18 +195,19 @@ assertFalse(donationLayout.contains("@string/creator_channel_donation_empty_title")) ``` -- [ ] **RED:** 홈 팬레터 empty가 빈 요약 상태에서 표시되고 owner일 때 action button을 숨기는 source test를 추가한다. +- [x] **RED:** 홈 팬레터 empty 버튼이 owner일 때 숨겨지고 click listener가 제거되는 source test를 추가한다. ```kotlin val adapterSource = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt" ).readText() - assertTrue(adapterSource.contains("fantalkEmpty?.isVisible =")) - assertTrue(adapterSource.contains("fantalkSupportButton?.isVisible =")) + assertTrue(adapterSource.contains("private val fanTalkSupportButton")) + assertTrue(adapterSource.contains("fanTalkSupportButton?.isVisible =")) assertTrue(adapterSource.contains("!item.isOwner")) + assertTrue(adapterSource.contains("fanTalkSupportButton?.setOnClickListener")) ``` -- [ ] **RED 확인:** focused tests를 실행해 아직 홈 전용 string 참조와 팬레터 owner 분기가 없어 실패함을 확인한다. +- [x] **RED 확인:** focused tests를 실행해 아직 홈 전용 string 참조와 팬레터 owner 분기가 없어 실패함을 확인한다. ```bash ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" @@ -214,33 +215,39 @@ Expected: 홈 후원 layout string 또는 홈 팬레터 owner 분기 assertion 실패. -- [ ] **GREEN:** `item_creator_channel_home_donation.xml`의 empty title 참조를 홈 전용 key로 변경한다. +- [x] **GREEN:** `item_creator_channel_home_donation.xml`의 empty title 참조를 홈 전용 key로 변경한다. ```xml android:text="@string/creator_channel_home_donation_empty_title" ``` -- [ ] **GREEN:** `CreatorChannelHomeSectionAdapter.bindDonations()`에서 owner용 별도 empty title 변경을 제거하고 홈 전용 문구를 유지한다. +- [x] **GREEN:** `CreatorChannelHomeSectionAdapter.bindDonations()`에서 owner용 별도 empty title 변경을 제거하고 홈 전용 문구를 유지한다. ```kotlin donationEmptyTitle?.setText(R.string.creator_channel_home_donation_empty_title) ``` -- [ ] **GREEN:** 홈 팬레터 empty 바인딩에서 빈 데이터와 owner 버튼 숨김을 분기한다. +- [x] **GREEN:** 홈 팬레터 empty action button을 ViewHolder에 연결한다. ```kotlin - val hasFanTalk = item.latestFanTalk != null - fantalkTotalRow?.isVisible = hasFanTalk - fantalkLatestRow?.isVisible = hasFanTalk - fantalkEmpty?.isVisible = !hasFanTalk - val isFanTalkSupportButtonVisible = !hasFanTalk && !item.isOwner - fantalkSupportButton?.isVisible = isFanTalkSupportButtonVisible + private val fanTalkSupportButton: View? = view.findViewById(R.id.layout_fantalk_support_button) + ``` + +- [x] **GREEN:** 홈 팬레터 empty 바인딩에서 기존 empty 표시 분기를 유지하고 owner 버튼 숨김을 추가한다. + + ```kotlin + val fanTalk = item.fanTalk.latestFanTalk + fanTalkTotalRow?.isVisible = fanTalk != null && item.fanTalk.totalCount > 0 + fanTalkLatestRow?.isVisible = fanTalk != null && item.fanTalk.totalCount > 0 + fanTalkEmpty?.isVisible = fanTalk == null || item.fanTalk.totalCount <= 0 + val showSupportButton = (fanTalk == null || item.fanTalk.totalCount <= 0) && !item.isOwner + fanTalkSupportButton?.isVisible = showSupportButton fantalkSupportButton?.setOnClickListener( - if (isFanTalkSupportButtonVisible) View.OnClickListener { onFanTalkClick() } else null + if (showSupportButton) View.OnClickListener { onSectionChevronClick(CreatorChannelTab.FanTalk) } else null ) ``` -- [ ] **GREEN 확인:** 홈 관련 focused tests를 실행한다. +- [x] **GREEN 확인:** 홈 관련 focused tests를 실행한다. ```bash ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeMapperTest" @@ -248,7 +255,7 @@ Expected: `BUILD SUCCESSFUL`. -- [ ] **REFACTOR:** 이번 Task에서 만든 중복만 정리하고 focused tests와 `:app:mergeDebugResources` 결과를 Progress에 기록한다. +- [x] **REFACTOR:** 이번 Task에서 만든 중복만 정리하고 focused tests와 `:app:mergeDebugResources` 결과를 Progress에 기록한다. #### Task 1.3 팬레터/후원 탭 empty action owner 정책 @@ -260,7 +267,6 @@ **Files:** -- Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModel.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragment.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkViewModelTest.kt` @@ -273,7 +279,7 @@ - Consumes: `host.isCreatorChannelOwner()`, `CreatorChannelDonationUiState.Empty.isOwner` - Produces: 팬레터 empty state owner 정보 또는 Fragment owner 분기, 후원 empty adapter owner visibility 분기 -- [ ] **RED:** 팬레터 empty state가 owner 여부를 전달하거나 Fragment가 host owner 값을 사용해 empty write button을 숨기는 테스트를 추가한다. +- [x] **RED:** 팬레터 empty state가 owner 여부를 전달하거나 Fragment가 host owner 값을 사용해 empty write button을 숨기는 테스트를 추가한다. ```kotlin val fragmentSource = projectFile( @@ -283,16 +289,16 @@ assertTrue(fragmentSource.contains("layoutCreatorChannelFantalkEmptyWriteButton.setOnClickListener")) ``` -- [ ] **RED:** 후원 empty adapter가 owner일 때 `btn_creator_channel_donation_empty_write`를 숨기는 테스트를 추가한다. +- [x] **RED:** 후원 empty adapter가 owner일 때 `btn_creator_channel_donation_empty_write`를 숨기는 테스트를 추가한다. ```kotlin val adapterSource = projectFile( "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/ui/CreatorChannelDonationAdapter.kt" ).readText() - assertTrue(adapterSource.contains("btnCreatorChannelDonationEmptyWrite.isVisible = !isOwner")) + assertTrue(adapterSource.contains("if (!isOwner) View.OnClickListener { onEmptyDonationClick() } else null")) ``` -- [ ] **RED 확인:** focused layout/source tests를 실행해 owner empty action 분기 누락으로 실패함을 확인한다. +- [x] **RED 확인:** focused layout/source tests를 실행해 owner empty action 분기 누락으로 실패함을 확인한다. ```bash ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" @@ -300,7 +306,7 @@ Expected: owner empty action visibility 관련 assertion 실패. -- [ ] **GREEN:** `CreatorChannelFanTalkFragment.bindEmpty()`에서 내 채널 action button을 숨기고 listener를 제거한다. +- [x] **GREEN:** `CreatorChannelFanTalkFragment.bindEmpty()`에서 내 채널 action button을 숨기고 listener를 제거한다. ```kotlin val showWriteButton = !host.isCreatorChannelOwner() @@ -310,13 +316,13 @@ ) ``` -- [ ] **GREEN:** `setupClickListeners()`에서 empty write button의 고정 click listener를 제거하고 일반 floating write button listener만 유지한다. +- [x] **GREEN:** `setupClickListeners()`에서 empty write button의 고정 click listener를 제거하고 일반 floating write button listener만 유지한다. ```kotlin btnCreatorChannelFantalkWrite.setOnClickListener { host.onCreatorChannelFanTalkWriteClicked() } ``` -- [ ] **GREEN:** `CreatorChannelDonationAdapter` empty ViewHolder에서 `isOwner`에 따라 버튼 visibility와 click listener를 설정한다. +- [x] **GREEN:** `CreatorChannelDonationAdapter` empty ViewHolder에서 `isOwner`에 따라 버튼 visibility와 click listener를 설정한다. ```kotlin binding.btnCreatorChannelDonationEmptyWrite.isVisible = !isOwner @@ -325,7 +331,7 @@ ) ``` -- [ ] **GREEN 확인:** 팬레터/후원 탭 focused tests를 실행한다. +- [x] **GREEN 확인:** 팬레터/후원 탭 focused tests를 실행한다. ```bash ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModelTest" @@ -333,7 +339,85 @@ Expected: `BUILD SUCCESSFUL`. -- [ ] **REFACTOR:** owner 버튼 분기 중복만 정리하고 focused tests 결과를 Progress에 기록한다. +- [x] **REFACTOR:** owner 버튼 분기 중복만 정리하고 focused tests 결과를 Progress에 기록한다. + +#### Task 1.4 홈 팬레터 empty 버튼 영역 회귀 수정 + +**Goal 실행 `P1-T4`:** 홈 팬레터 empty에서 내 채널이라 action 버튼이 숨겨질 때 버튼 영역도 레이아웃 높이에 남지 않게 한다. + +- **시작 조건:** `P1-T2` 완료. +- **완료 증거:** 홈 팬레터 empty root가 fixed height를 쓰지 않고, action button이 숨겨지면 해당 button view/margin이 레이아웃 높이에 기여하지 않는다. +- **범위 밖:** 상세 팬레터/후원 탭, API, adapter 데이터 정책 변경. + +**Files:** + +- Modify: `app/src/main/res/layout/item_creator_channel_home_fantalk.xml` +- Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` + +**Interfaces:** + +- Consumes: `CreatorChannelHomeSectionAdapter.bindFanTalk()`의 기존 `fanTalkSupportButton?.isVisible = showSupportButton` +- Produces: button `GONE` 시 button 영역 제거가 가능한 홈 팬레터 empty layout 구조 + +- [x] **RED:** 홈 팬레터 empty layout이 fixed `169dp` 높이와 button bottom margin을 쓰지 않도록 source/layout test를 추가한다. + + ```kotlin + assertTrue(fanTalkLayout.contains("android:layout_height=\"wrap_content\"")) + assertTrue(fanTalkLayout.contains("android:layout_marginTop=\"@dimen/spacing_14\"")) + assertFalse(fanTalkLayout.contains("android:layout_height=\"169dp\"")) + assertFalse(fanTalkLayout.contains("android:layout_marginBottom=\"@dimen/spacing_6\"")) + ``` + +- [x] **RED 확인:** focused source/layout test를 실행해 기존 fixed height로 실패함을 확인한다. + + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" + ``` + +- [x] **GREEN:** `layout_fantalk_empty`를 `LinearLayout` `wrap_content` 구조로 변경하고, support button은 `marginTop`만 갖는 sibling으로 둔다. + +- [x] **GREEN 확인:** focused source/layout test와 resource merge를 실행한다. + + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" + ./gradlew :app:mergeDebugResources + ``` + +- [x] **REFACTOR:** 불필요한 변경이 없음을 확인하고 공백 검사를 실행해 Progress에 기록한다. + +#### Task 1.5 Empty action 버튼 색상 정리 + +**Goal 실행 `P1-T5`:** 홈/팬레터/후원 empty action 버튼 색상을 화면별 확정값과 일치시킨다. + +- **시작 조건:** 사용자 색상 요구사항 확정. +- **완료 증거:** 홈 후원/홈 팬레터 버튼은 white 배경과 black 글자색, 팬레터 탭 버튼은 gray 800 배경과 white 글자색, 후원 탭 버튼은 soda 400 배경과 white 글자색을 source/layout test로 검증한다. +- **범위 밖:** 문구, 노출 정책, click listener, API, 신규 drawable/dependency 변경. + +**Files:** + +- Modify: `app/src/main/res/layout/item_creator_channel_home_donation.xml` +- Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` +- Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt` +- Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/donation/CreatorChannelDonationFragmentLayoutTest.kt` + +**Interfaces:** + +- Consumes: 기존 `bg_round_corner_999_white`, `bg_creator_channel_fantalk_empty_button`, `bg_creator_channel_donation_empty_button`, `@color/black`, `@color/white` +- Produces: 화면별 empty action button 색상 계약 + +- [x] **RED:** 홈/팬레터/후원 empty action button 색상 source/layout test를 추가한다. + +- [x] **RED 확인:** focused tests를 실행해 홈 후원 empty button 배경 mismatch로 실패함을 확인한다. + + ```bash + ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" + ``` + +- [x] **GREEN:** 홈 후원 empty `layout_donation_empty_button` 배경을 기존 white capsule drawable로 변경한다. + +- [x] **GREEN 확인:** focused tests와 `:app:mergeDebugResources`를 실행한다. + +- [x] **REFACTOR:** 신규 drawable 없이 기존 resource 재사용만 남았는지 확인하고 공백 검사를 실행한다. ### 검증 방법 @@ -361,10 +445,14 @@ git diff --check | 2 | `P1-T2` | `P1-T1` | 예, `P1-T3`와 파일 비중복 시 가능 | 홈 section adapter/layout 범위 재확인 | | 3 | `P1-T3` | `P1-T1` | 예, `P1-T2`와 파일 비중복 시 가능 | Fragment/adapter owner 전달 경로 재확인 | | 4 | `P1-GATE` | `P1-T2`, `P1-T3` | 아니요 | 실패 소유 Task에 회귀 수정 Task 추가 | +| 5 | `P1-T4` | `P1-T2` | 아니요 | 홈 팬레터 empty layout 구조 재확인 | +| 6 | `P1-T5` | 사용자 색상 요구사항 확정 | 아니요 | 기존 drawable/color resource 재확인 | ```text P1-T1 -> P1-T2 -> P1-GATE -> P1-T3 -> P1-GATE +P1-T2 -> P1-T4 +색상 요구사항 -> P1-T5 ``` ## 변경 금지 항목 @@ -383,6 +471,7 @@ P1-T1 -> P1-T2 -> P1-GATE | 2026-08-14 | `DEC-001` | 확정 | 내 채널에서는 대상 empty state action 버튼을 모두 숨긴다. | 사용자 A안 선택 | `P1-T2`, `P1-T3`, `prd.md` | | 2026-08-14 | `DEC-002` | 확정 | 영어 resource도 이번 범위에 포함하고 제공되지 않은 영문은 번역 초안을 사용한다. | 사용자 직접 지시 | `P1-T1`, `prd.md` | | 2026-08-14 | `DEC-003` | 확정 | 홈 후원 empty 전용 string key를 추가한다. | 홈/후원 탭 문구가 서로 다름 | `P1-T1`, `P1-T2`, `prd.md` | +| 2026-08-14 | `DEC-004` | 확정 | empty action 버튼 색상은 홈 탭 white/black, 팬레터 탭 gray 800/white, 후원 탭 soda 400/white로 구분한다. | 사용자 직접 지시 | `P1-T5`, `prd.md` | ## 발견된 문제 @@ -406,6 +495,83 @@ P1-T1 -> P1-T2 -> P1-GATE - 남은 항목: 사용자 승인 후 `P1-T1` 구현 시작. - 다음 행동: 구현 요청이 들어오면 `P1-T1`부터 실행한다. +### `P1-T1` 1차 실행 — 2026-08-14 + +- 상태: 완료 +- 무엇을: 한국어/영어/일본어 resource 테스트를 먼저 갱신하고, 홈 전용 후원 empty key와 팬레터/후원 empty 문구를 반영했다. +- 왜: 홈/팬레터/후원 empty state 문구가 PRD 문구 표와 일치해야 하기 때문이다. +- 어떻게: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest"` — RED에서 `CreatorChannelActivitySourceTest.kt:1311`, `CreatorChannelFanTalkFragmentLayoutTest.kt:238` assertion 실패를 확인했다. + - 같은 focused test 재실행 — `BUILD SUCCESSFUL`. + - `./gradlew :app:mergeDebugResources` — `BUILD SUCCESSFUL`. +- 남은 항목: 없음. +- 다음 행동: `P1-T2` 실행. + +### `P1-T2` 1차 실행 — 2026-08-14 + +- 상태: 완료 +- 무엇을: 홈 후원 empty가 홈 전용 string을 사용하도록 변경하고, `FanTalk` section에 기존 owner 판정값을 전달해 홈 팬레터 empty action 버튼을 owner일 때 숨기도록 했다. +- 왜: 사용자가 지적한 대로 이미 알고 있는 내 채널 여부를 홈 팬레터 empty 버튼 정책에 활용하기 위해서다. +- 어떻게: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeMapperTest"` — RED에서 `CreatorChannelHomeMapperTest.kt:62`, `:63`의 `FanTalk.isOwner` 미존재 컴파일 실패를 확인했다. + - 같은 focused test 재실행 — `BUILD SUCCESSFUL`. +- 남은 항목: 없음. +- 다음 행동: `P1-T3` 실행. + +### `P1-T3` 1차 실행 — 2026-08-14 + +- 상태: 완료 +- 무엇을: 팬레터 탭 empty 버튼은 내 채널에서 숨기고 click listener를 제거했다. 후원 탭 empty 버튼은 기존 visibility 분기를 유지하면서 owner일 때 listener도 제거하도록 보강했다. +- 왜: 내 채널에서는 본인에게 후원하거나 팬레터를 남기는 action을 표시하거나 실행하지 않아야 하기 때문이다. +- 어떻게: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest"` — RED에서 `CreatorChannelDonationActionTest.kt:94`, `CreatorChannelFanTalkFragmentLayoutTest.kt:218` assertion 실패를 확인했다. + - 같은 focused test 재실행 — `BUILD SUCCESSFUL`. +- 남은 항목: 없음. +- 다음 행동: `P1-GATE` 실행. + +### `P1-GATE` 1차 실행 — 2026-08-14 + +- 상태: 완료 +- 무엇을: focused regression, resource merge, 전체 unit suite, 공백 검사와 reviewer gate를 완료했다. +- 왜: 문구/resource merge, owner 버튼 정책, 인접 회귀를 최종 판정하기 위해서다. +- 어떻게: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeMapperTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkActionTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationViewModelTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationActionTest"` — `BUILD SUCCESSFUL`. + - `./gradlew :app:mergeDebugResources` — `BUILD SUCCESSFUL`. + - `./gradlew :app:testDebugUnitTest` — 최초 전체 실행은 `CreatorChannelHomeViewModelTest`의 can 차감 assertion 1건이 실패했으나, 해당 테스트 단독 재실행은 `BUILD SUCCESSFUL`이었고 전체 suite 재실행도 `BUILD SUCCESSFUL`이었다. + - `git diff --check` — 출력 없음. + - reviewer gate — `APPROVE`, 차단 발견 없음. +- 남은 항목: 없음. +- 다음 행동: 없음. + +### `P1-T4` 1차 실행 — 2026-08-14 + +- 상태: 완료 +- 무엇을: 홈 팬레터 empty root의 fixed `169dp` 높이를 제거하고 `LinearLayout` `wrap_content` 구조로 바꿨다. `응원남기기` 버튼은 title 아래 sibling으로 두고 `marginTop`만 갖게 해 `GONE`일 때 버튼과 간격이 함께 제거되도록 했다. +- 왜: 내 채널에서 버튼을 숨겨도 기존 `FrameLayout` fixed height 때문에 버튼 영역이 남았기 때문이다. +- 어떻게: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"` — RED에서 `CreatorChannelActivitySourceTest.kt:2145` assertion 실패를 확인했다. + - 같은 focused test 재실행 — `BUILD SUCCESSFUL`. + - `./gradlew :app:mergeDebugResources` — `BUILD SUCCESSFUL`. + - `git diff --check` — 출력 없음. +- 남은 항목: 없음. +- 다음 행동: 없음. + +### `P1-T5` 1차 실행 — 2026-08-14 + +- 상태: 완료 +- 무엇을: 홈 후원/홈 팬레터/팬레터 탭/후원 탭 empty action button 색상 계약을 source/layout test로 고정하고, 홈 후원 empty button 배경만 기존 white capsule drawable로 변경했다. +- 왜: 홈 탭 action button은 white 배경과 black 글자색이어야 하고, 후원 탭 action button은 기존 soda 400 배경과 white 글자색을 유지해야 하기 때문이다. +- 어떻게: + - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.creator.channel.donation.CreatorChannelDonationFragmentLayoutTest"` — RED에서 `CreatorChannelActivitySourceTest.kt:1120` assertion 실패를 확인했다. + - 같은 focused test 재실행 — `BUILD SUCCESSFUL`. + - `./gradlew :app:mergeDebugResources` — `BUILD SUCCESSFUL`. + - `git diff --check` — 출력 없음. +- 남은 항목: 없음. +- 다음 행동: 없음. + ## Verification Log - 2026-08-14: 문서 생성 단계에서는 production resource, Kotlin, XML layout을 수정하지 않았다. +- 2026-08-14: Phase 1 구현 후 focused regression, resource merge, 전체 unit suite 재실행, 공백 검사와 reviewer gate가 통과했다. +- 2026-08-14: `P1-T4` 후속 회귀 수정에서 홈 팬레터 empty fixed height 제거 후 focused source/layout test, resource merge, 공백 검사가 통과했다. +- 2026-08-14: `P1-T5` 후속 색상 수정에서 홈 후원 empty button 배경을 white capsule로 변경한 뒤 focused source/layout test, resource merge, 공백 검사가 통과했다. diff --git a/docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/prd.md b/docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/prd.md index 1f55c28c..93cf25f6 100644 --- a/docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/prd.md +++ b/docs/20260814_크리에이터_채널_empty_state_다국어_문구_정리/prd.md @@ -4,7 +4,7 @@ | 항목 | 내용 | |---|---| -| 문서 상태 | 구현 기준 확정 | +| 문서 상태 | 후속 색상 수정 완료 | | 작성일 | `2026-08-14` | | 최종 수정일 | `2026-08-14` | | 대상 제품 | 크리에이터 채널 홈/팬레터/후원 empty state | @@ -21,13 +21,13 @@ - 크리에이터 채널 일부 empty state 문구가 화면별 의도와 다르게 재사용되어 홈 탭과 상세 탭의 안내 문구를 동시에 만족하지 못한다. - 팬레터/후원 empty state 일부 문구와 action label이 한국어, 영어, 일본어에서 일관되게 다국어 처리되어야 한다. -- 홈 탭에는 팬레터가 없을 때 후원 empty state와 동일한 카드형 empty UI가 필요하다. +- 홈 탭 팬레터 empty state는 레이아웃과 표시 분기가 이미 존재하므로, 확정 문구와 내 채널 action 버튼 숨김 정책을 보강해야 한다. - 내 채널에서는 사용자가 본인에게 후원하거나 본인 채널에 팬레터를 남기는 action 버튼을 보지 않아야 한다. ## 3. Goals - 홈 탭 후원 empty 안내 문구를 한국어/영어/일본어 resource로 분리해 표시한다. -- 홈 탭 팬레터 empty 안내 문구를 한국어/영어/일본어 resource로 표시하고, 후원 empty UI와 동일한 카드형 UI를 제공하도록 요구사항을 확정한다. +- 홈 탭 팬레터 empty 안내 문구를 한국어/영어/일본어 resource로 표시하고, 기존 카드형 empty UI의 action 버튼 노출 정책을 확정한다. - 팬레터 탭 empty 안내 문구와 `응원남기기` action label을 한국어/영어/일본어 resource로 표시한다. - 후원 탭 empty 안내 문구와 `후원하기` action label을 한국어/영어/일본어 resource로 표시한다. - 내 채널의 홈/팬레터/후원 empty state에서 `후원하기`와 `응원남기기` 버튼을 모두 숨기는 정책을 확정한다. @@ -75,11 +75,14 @@ | ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 | |---|---|---|---|---| -| `CREATOR-EMPTY-UI-001` | 확정 | 홈 탭에서 팬레터가 없을 때 empty state UI를 표시한다. | 홈 팬레터 empty UI는 홈 후원 empty UI와 동일한 카드형 구조와 action button 위치를 사용하고, 문구만 팬레터 전용 값으로 표시한다. | `P1-T2` | +| `CREATOR-EMPTY-UI-001` | 확정 | 홈 탭 팬레터 empty UI의 기존 표시 분기를 보존하고 확정 문구를 적용한다. | `item_creator_channel_home_fantalk.xml`의 `layout_fantalk_empty`와 `CreatorChannelHomeSectionAdapter.bindFanTalk()` empty 표시 분기를 유지하며, 문구는 팬레터 전용 값으로 표시한다. | `P1-T2` | | `CREATOR-EMPTY-UI-002` | 확정 | 내 채널 홈 후원 empty에서는 `후원하기` 버튼을 숨긴다. | `isOwner == true`이면 `layout_donation_empty_button`이 보이지 않고 click listener가 연결되지 않는다. | `P1-T2` | | `CREATOR-EMPTY-UI-003` | 확정 | 내 채널 홈 팬레터 empty에서는 `응원남기기` 버튼을 숨긴다. | `isOwner == true`이면 `layout_fantalk_support_button`이 보이지 않고 click listener가 연결되지 않는다. | `P1-T2` | | `CREATOR-EMPTY-UI-004` | 확정 | 내 채널 팬레터 탭 empty에서는 `응원남기기` 버튼을 숨긴다. | `isOwner == true`이면 `layout_creator_channel_fantalk_empty_write_button`이 보이지 않고 click listener가 연결되지 않는다. | `P1-T3` | | `CREATOR-EMPTY-UI-005` | 확정 | 내 채널 후원 탭 empty에서는 `후원하기` 버튼을 숨긴다. | `CreatorChannelDonationUiState.Empty.isOwner == true`이면 `btn_creator_channel_donation_empty_write`가 보이지 않고 click listener가 연결되지 않는다. | `P1-T3` | +| `CREATOR-EMPTY-UI-006` | 확정 | 홈 탭 후원 empty와 홈 탭 팬레터 empty action 버튼은 white 배경과 black 글자색을 사용한다. | `layout_donation_empty_button`, `layout_fantalk_support_button`은 white capsule 배경을 사용하고 label text color는 black이다. | `P1-T5` | +| `CREATOR-EMPTY-UI-007` | 확정 | 팬레터 탭 empty `응원남기기` 버튼은 gray 800 배경과 white 글자색을 사용한다. | `layout_creator_channel_fantalk_empty_write_button`은 `gray_800` 배경 drawable을 사용하고 label text color는 white이다. | `P1-T5` | +| `CREATOR-EMPTY-UI-008` | 확정 | 후원 탭 empty `후원하기` 버튼은 soda 400 배경과 white 글자색을 사용한다. | `btn_creator_channel_donation_empty_write`는 `soda_400` 배경 drawable을 사용하고 label text color는 white이다. | `P1-T5` | ## 8. 문구 표 @@ -106,10 +109,11 @@ ## 9. UI/UX Expectations -- 홈 탭 팬레터 empty UI는 기존 홈 후원 empty 카드와 같은 시각 구조를 사용한다. +- 홈 탭 팬레터 empty UI는 기존 `layout_fantalk_empty` 카드형 구조를 유지한다. - 홈 탭 후원 empty와 팬레터 empty는 타인 채널에서 action 버튼을 표시하고 내 채널에서 action 버튼을 숨긴다. - 팬레터 탭과 후원 탭 empty 상태는 타인 채널에서 문구 아래 action 버튼을 표시하고 내 채널에서 action 버튼을 숨긴다. - action 버튼이 숨겨져도 empty 문구 정렬과 상하 여백이 어색하게 무너지면 안 된다. +- 홈 탭 empty action 버튼은 white 배경/black 글자색, 팬레터 탭 empty action 버튼은 gray 800 배경/white 글자색, 후원 탭 empty action 버튼은 soda 400 배경/white 글자색을 사용한다. - 후원 탭 floating `후원하기` 버튼은 기존처럼 내 채널에서 숨긴다. ## 10. 기술적 제약 @@ -123,12 +127,13 @@ ## 11. 성공 기준 -- [ ] 문구 표의 모든 한국어/영어/일본어 resource 값이 실제 resource와 일치한다. -- [ ] 홈 후원 empty는 `creator_channel_home_donation_empty_title`, 후원 탭 empty는 `creator_channel_donation_empty_title`을 사용한다. -- [ ] 홈 팬레터 empty UI가 팬레터 데이터 없음 상태에서 표시된다. -- [ ] 내 채널의 홈 후원, 홈 팬레터, 팬레터 탭, 후원 탭 empty action 버튼이 모두 숨겨진다. -- [ ] 타인 채널의 empty action 버튼은 기존 후원/팬레터 진입 동작을 유지한다. -- [ ] `./gradlew :app:mergeDebugResources`와 관련 local unit/source/layout test가 통과한다. +- [x] 문구 표의 모든 한국어/영어/일본어 resource 값이 실제 resource와 일치한다. +- [x] 홈 후원 empty는 `creator_channel_home_donation_empty_title`, 후원 탭 empty는 `creator_channel_donation_empty_title`을 사용한다. +- [x] 홈 팬레터 empty UI가 팬레터 데이터 없음 상태에서 표시된다. +- [x] 내 채널의 홈 후원, 홈 팬레터, 팬레터 탭, 후원 탭 empty action 버튼이 모두 숨겨진다. +- [x] 타인 채널의 empty action 버튼은 기존 후원/팬레터 진입 동작을 유지한다. +- [x] 홈/팬레터/후원 empty action 버튼 색상이 화면별 확정 색상과 일치한다. +- [x] `./gradlew :app:mergeDebugResources`와 관련 local unit/source/layout test가 통과한다. ## 12. Open Questions @@ -151,9 +156,13 @@ | 2026-08-14 | `DEC-001` | 확정 | 내 채널에서는 홈 탭 후원 empty, 홈 탭 팬레터 empty, 팬레터 탭 empty, 후원 탭 empty의 `후원하기`/`응원남기기` 버튼을 모두 숨긴다. | 사용자 A안 선택 | `CREATOR-EMPTY-UI-002~005`, `P1-T2`, `P1-T3` | | 2026-08-14 | `DEC-002` | 확정 | 영어 resource도 이번 범위에 포함하고, 제공되지 않은 영문 문구는 한국어/일본어 의미에 맞춰 번역 초안을 사용한다. | 사용자 A안 선택 및 직접 지시 | `CREATOR-EMPTY-I18N-001~004`, `P1-T1` | | 2026-08-14 | `DEC-003` | 확정 | 홈 후원 empty와 후원 탭 empty 문구가 다르므로 홈 전용 후원 empty string을 추가한다. | 기존 `creator_channel_donation_empty_title` 단일 key로 두 화면 문구를 동시에 만족할 수 없음 | `CREATOR-EMPTY-I18N-001`, `P1-T1`, `P1-T2` | +| 2026-08-14 | `DEC-004` | 확정 | empty action 버튼 색상은 홈 탭 white/black, 팬레터 탭 gray 800/white, 후원 탭 soda 400/white로 구분한다. | 사용자 직접 지시 | `CREATOR-EMPTY-UI-006~008`, `P1-T5` | ## 15. Verification Log - 2026-08-14: `docs/sample/sample-prd.md`, `docs/sample/sample-plan-task.md`를 확인해 신규 문서 구조를 맞췄다. - 2026-08-14: 기존 관련 문서 `docs/20260611_크리에이터_채널_홈_탭/prd.md`, `docs/20260622_크리에이터_채널_후원_탭/prd.md`, `docs/20260814_팬Talk_팬레터_resource_문구_변경/prd.md`를 확인했다. - 2026-08-14: 현재 resource와 layout 사용처를 확인해 홈 후원 empty와 후원 탭 empty가 같은 string key를 공유한다는 제약을 문서화했다. +- 2026-08-14: 홈 팬레터 empty UI는 `item_creator_channel_home_fantalk.xml`의 `layout_fantalk_empty`와 `CreatorChannelHomeSectionAdapter.bindFanTalk()` 표시 분기가 이미 있어, 신규 UI 추가가 아니라 문구와 내 채널 action 버튼 정책 보강 범위로 정정했다. +- 2026-08-14: 구현 후 focused regression, `:app:mergeDebugResources`, 전체 `:app:testDebugUnitTest`, `git diff --check`가 통과했고 reviewer gate가 `APPROVE`했다. +- 2026-08-14: 후속 색상 수정에서 홈 후원 empty button 배경만 white capsule로 변경하고, 홈/팬레터/후원 empty action button 색상 source/layout test와 `:app:mergeDebugResources`가 통과했다.