From f2d589eca22ac4f2322d8d2f872eb12d3c5f49d3 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Tue, 7 Jul 2026 15:44:45 +0900 Subject: [PATCH] =?UTF-8?q?fix(creator):=20=ED=8C=ACTalk=20=EC=95=A1?= =?UTF-8?q?=EC=85=98=20=ED=8C=9D=EC=97=85=EC=9D=84=20=EC=A0=95=EB=A6=AC?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive/Sources/I18n/I18n.swift | 8 +++ .../CreatorChannel/CreatorChannelView.swift | 28 +++++++++ .../CreatorChannelFanTalkActionPopup.swift | 32 +++++++--- .../CreatorChannelFanTalkTabView.swift | 31 ++-------- ...CreatorChannelReplyDetailActionPopup.swift | 34 +++-------- .../CreatorChannelReplyDetailView.swift | 26 ++++---- .../plan-task.md | 60 +++++++++++++++++++ .../20260704_크리에이터_채널_팬Talk_탭/prd.md | 29 ++++++--- 8 files changed, 170 insertions(+), 78 deletions(-) diff --git a/SodaLive/Sources/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index f35adfd8..363bd0e9 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -3608,6 +3608,14 @@ If you block this user, the following features will be restricted. pick(ko: "수정", en: "Edit", ja: "編集") } + static var editAction: String { + pick(ko: "수정하기", en: "Edit", ja: "編集する") + } + + static var deleteAction: String { + pick(ko: "삭제하기", en: "Delete", ja: "削除する") + } + static var report: String { pick(ko: "신고", en: "Report", ja: "通報") } diff --git a/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift b/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift index 7085b6df..11bc1654 100644 --- a/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift +++ b/SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift @@ -156,6 +156,10 @@ struct CreatorChannelView: View { authConfirmDialog } + if viewModel.selectedTab == .fanTalk && fanTalkViewModel.isShowDeleteDialog { + fanTalkDeleteDialog + } + if isShowChannelDonationDialog { LiveRoomDonationDialogView( isShowing: $isShowChannelDonationDialog, @@ -338,6 +342,30 @@ struct CreatorChannelView: View { ) } + private var fanTalkDeleteDialog: some View { + SodaV2ActionModal( + title: I18n.MemberChannel.cheersDeleteTitle, + message: I18n.Common.confirmDeleteQuestion, + button1: SodaV2ActionModalButton( + label: I18n.Common.delete, + action: { + fanTalkViewModel.isShowDeleteDialog = false + fanTalkViewModel.deleteSelectedFanTalk(creatorId: creatorId) + } + ), + button2: SodaV2ActionModalButton( + label: I18n.Common.cancel, + action: dismissFanTalkDeleteDialog + ), + onDimmedTap: dismissFanTalkDeleteDialog + ) + } + + private func dismissFanTalkDeleteDialog() { + fanTalkViewModel.isShowDeleteDialog = false + fanTalkViewModel.selectedFanTalkId = 0 + } + private var authView: some View { BootpayUI(payload: payload, requestType: BootpayRequest.TYPE_AUTHENTICATION) .onConfirm { _ in true } diff --git a/SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift b/SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift index e9b04166..d391d99d 100644 --- a/SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift +++ b/SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift @@ -1,19 +1,21 @@ import SwiftUI -struct CreatorChannelFanTalkActionPopup: View { - let isMine: Bool - let isOwnCreatorChannel: Bool +struct CreatorChannelActionPopup: View { + let canEdit: Bool + let canDelete: Bool let onTapEdit: () -> Void let onTapDelete: () -> Void + var editTitle = I18n.Explorer.editAction + var deleteTitle = I18n.Explorer.deleteAction var body: some View { VStack(alignment: .leading, spacing: 0) { - if isMine { - actionButton(title: "수정하기", action: onTapEdit) + if canEdit { + actionButton(title: editTitle, action: onTapEdit) } - if isMine || isOwnCreatorChannel { - actionButton(title: "삭제하기", action: onTapDelete) + if canDelete { + actionButton(title: deleteTitle, action: onTapDelete) } } .background(Color.gray900) @@ -37,3 +39,19 @@ struct CreatorChannelFanTalkActionPopup: View { .buttonStyle(.plain) } } + +struct CreatorChannelFanTalkActionPopup: View { + let isMine: Bool + let isOwnCreatorChannel: Bool + let onTapEdit: () -> Void + let onTapDelete: () -> Void + + var body: some View { + CreatorChannelActionPopup( + canEdit: isMine, + canDelete: isMine || isOwnCreatorChannel, + onTapEdit: onTapEdit, + onTapDelete: onTapDelete + ) + } +} diff --git a/SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift b/SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift index 43c7ee87..e7fa40e0 100644 --- a/SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift +++ b/SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift @@ -30,7 +30,6 @@ struct CreatorChannelFanTalkTabView: View { } .sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 1) .overlay(reportDialog) - .overlay(deleteDialog) } private var content: some View { @@ -100,9 +99,9 @@ struct CreatorChannelFanTalkTabView: View { let itemFrame = fanTalkItemFrames[fanTalk.fanTalkId] { let isMine = fanTalk.writerId == UserDefaults.int(forKey: .userId) - CreatorChannelFanTalkActionPopup( - isMine: isMine, - isOwnCreatorChannel: isOwnCreatorChannel, + CreatorChannelActionPopup( + canEdit: isMine, + canDelete: isMine || isOwnCreatorChannel, onTapEdit: { actionPopupFanTalk = nil onTapEdit(fanTalk) @@ -110,7 +109,9 @@ struct CreatorChannelFanTalkTabView: View { onTapDelete: { actionPopupFanTalk = nil viewModel.presentDeleteDialog(fanTalkId: fanTalk.fanTalkId) - } + }, + editTitle: I18n.Explorer.editAction, + deleteTitle: I18n.Explorer.deleteAction ) .padding(.top, itemFrame.minY + 44) .padding(.trailing, SodaSpacing.s14) @@ -134,26 +135,6 @@ struct CreatorChannelFanTalkTabView: View { } } - @ViewBuilder - private var deleteDialog: some View { - if viewModel.isShowDeleteDialog { - SodaDialog( - title: I18n.MemberChannel.cheersDeleteTitle, - desc: I18n.Common.confirmDeleteQuestion, - confirmButtonTitle: I18n.Common.delete, - confirmButtonAction: { - viewModel.isShowDeleteDialog = false - viewModel.deleteSelectedFanTalk(creatorId: creatorId) - }, - cancelButtonTitle: I18n.Common.cancel, - cancelButtonAction: { - viewModel.isShowDeleteDialog = false - viewModel.selectedFanTalkId = 0 - }, - textAlignment: .center - ) - } - } private static let coordinateSpaceName = "CreatorChannelFanTalkTabView" } diff --git a/SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift b/SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift index 369c7e50..9980aa18 100644 --- a/SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift +++ b/SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift @@ -7,33 +7,13 @@ struct CreatorChannelReplyDetailActionPopup: View { let onTapDelete: () -> Void var body: some View { - VStack(alignment: .leading, spacing: 0) { - if canEdit { - actionButton(title: I18n.Explorer.edit, action: onTapEdit) - } - - if canDelete { - actionButton(title: I18n.Common.delete, action: onTapDelete) - } - } - .background(Color.gray900) - .cornerRadius(14) - .overlay( - RoundedRectangle(cornerRadius: 14, style: .continuous) - .stroke(Color.gray700, lineWidth: 1) + CreatorChannelActionPopup( + canEdit: canEdit, + canDelete: canDelete, + onTapEdit: onTapEdit, + onTapDelete: onTapDelete, + editTitle: I18n.Explorer.editAction, + deleteTitle: I18n.Explorer.deleteAction ) } - - private func actionButton(title: String, action: @escaping () -> Void) -> some View { - Button(action: action) { - Text(title) - .appFont(size: 14, weight: .medium) - .foregroundColor(.white) - .lineLimit(1) - .frame(minWidth: 80, alignment: .leading) - .padding(.horizontal, SodaSpacing.s12) - .padding(.vertical, SodaSpacing.s8) - } - .buttonStyle(.plain) - } } diff --git a/SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift b/SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift index f2bb4263..4594a376 100644 --- a/SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift +++ b/SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift @@ -45,17 +45,18 @@ struct CreatorChannelReplyDetailView: View { } if viewModel.isShowDeleteDialog { - SodaDialog( + SodaV2ActionModal( title: I18n.Common.commentDeleteTitle, - desc: I18n.Common.confirmDeleteQuestion, - confirmButtonTitle: I18n.Common.delete, - confirmButtonAction: viewModel.confirmDelete, - cancelButtonTitle: I18n.Common.cancel, - cancelButtonAction: { - viewModel.isShowDeleteDialog = false - viewModel.deletingItem = nil - }, - textAlignment: .center + message: I18n.Common.confirmDeleteQuestion, + button1: SodaV2ActionModalButton( + label: I18n.Common.delete, + action: viewModel.confirmDelete + ), + button2: SodaV2ActionModalButton( + label: I18n.Common.cancel, + action: dismissDeleteDialog + ), + onDimmedTap: dismissDeleteDialog ) } } @@ -65,4 +66,9 @@ struct CreatorChannelReplyDetailView: View { } .sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 1) } + + private func dismissDeleteDialog() { + viewModel.isShowDeleteDialog = false + viewModel.deletingItem = nil + } } diff --git a/docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md b/docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md index 0f6b79fa..efe8992c 100644 --- a/docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md +++ b/docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md @@ -362,6 +362,59 @@ - 기대 결과: `BUILD SUCCEEDED`. - workspace 인식 실패 등 환경 문제가 있으면 `xcodebuild -project "SodaLive.xcodeproj" -scheme "SodaLive-dev" -configuration Debug build`를 대체 검증으로 실행하고 실패 원인을 검증 기록에 남긴다. +### Phase 10: 팬Talk 팝업 위치와 문구 통일 + +- [x] **Task 10.1: 삭제 확인 모달 표시 위치 상위 이동** + - 대상 파일: + - 수정: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` + - 수정: `SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift` + - 작업 내용: + - 팬Talk row 더보기의 `삭제` 선택 시 기존 선택 상태와 삭제 API 흐름은 유지한다. + - 삭제 확인 모달 렌더링은 팬Talk 탭 내부 overlay에서 제거하고 `CreatorChannelView`의 상위 `ZStack`에서 표시한다. + - 삭제 확인 모달은 기존 `SodaDialog` 대신 `SodaV2ActionModal`을 사용한다. + - 신고 사유 선택 UI인 `CheersReportDialogView`는 기존 신고 플로우 재사용 범위로 유지하고 이번 확인형 V2 popup dialog 전환 대상에서 제외한다. + - 검증 기준: + - 실행 명령: `rg "fanTalkDeleteDialog|SodaV2ActionModal|isShowDeleteDialog|deleteSelectedFanTalk|overlay\(deleteDialog\)|private var deleteDialog" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift` + - 기대 결과: `CreatorChannelView`에 `fanTalkDeleteDialog`와 `SodaV2ActionModal`이 있고, `CreatorChannelFanTalkTabView`에는 삭제 dialog overlay가 남지 않는다. + +- [x] **Task 10.2: 팬Talk 리스트/상세 anchored context popup 공용화** + - 대상 파일: + - 수정: `SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift` + - 수정: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift` + - 확인: `SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift` + - 확인: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailFeedView.swift` + - 작업 내용: + - 공용 `CreatorChannelActionPopup`을 사용해 리스트와 상세의 anchored context popup UI와 문구를 통일한다. + - 팬Talk 목록 row의 사용자 노출 문구는 `I18n.Explorer.editAction`, `I18n.Explorer.deleteAction`을 사용해 `수정하기`, `삭제하기`로 표시한다. + - 검증 기준: + - 실행 명령: `rg "CreatorChannelActionPopup|I18n\.Explorer\.editAction|I18n\.Explorer\.deleteAction|수정하기|삭제하기" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/I18n/I18n.swift` + - 기대 결과: 팬Talk 목록 row가 공용 popup에 action 문구를 주입하고, `수정하기`/`삭제하기` 문구는 I18n 키에서 확인된다. + +- [x] **Task 10.3: 팬Talk 상세 삭제 dialog V2 모달 적용** + - 대상 파일: + - 수정: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift` + - 작업 내용: + - 팬Talk 상세/답글 화면의 삭제 확인 dialog도 `SodaV2ActionModal`로 교체한다. + - 확인, 취소, dimmed tap에서 기존 삭제/닫기 상태 흐름을 유지한다. + - 검증 기준: + - 실행 명령: `rg "SodaV2ActionModal|dismissDeleteDialog|confirmDelete|SodaDialog" SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift` + - 기대 결과: 상세 삭제 확인 dialog가 `SodaV2ActionModal`을 사용하고 `SodaDialog`는 남지 않는다. + +- [x] **Task 10.4: 정적/빌드 검증** + - 대상 파일: + - 확인: `docs/20260704_크리에이터_채널_팬Talk_탭/prd.md` + - 확인: `docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md` + - 확인: `SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift` + - 확인: `SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift` + - 확인: `SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift` + - 확인: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift` + - 확인: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift` + - 검증 기준: + - 실행 명령: `git diff --check -- docs/20260704_크리에이터_채널_팬Talk_탭 SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift` + - 기대 결과: 공백 오류 없이 성공한다. + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - 기대 결과: `BUILD SUCCEEDED`. + ## 검증 기록 - 2026-07-04: Figma `get_design_context`와 `get_screenshot`으로 `290:9139`, `290:9145`, `290:9147`, `666:36114`, `290:9001`을 확인했다. @@ -389,3 +442,10 @@ - 2026-07-06: `plutil -lint SodaLive.xcodeproj/project.pbxproj` 결과 `OK`를 확인했다. - 2026-07-06: `git diff --check -- docs/20260704_크리에이터_채널_팬Talk_탭 SodaLive/Sources/V2/Component/Modal SodaLive/Sources/V2/CreatorChannel/FanTalk/Write SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/I18n/I18n.swift SodaLive.xcodeproj/project.pbxproj` 실행 결과 출력 없이 종료되어 공백 오류가 없음을 확인했다. - 2026-07-06: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 결과 `BUILD SUCCEEDED`를 확인했다. 빌드 중 `Alamofire` dependency scan warning이 출력되었으나 빌드는 성공했다. +- 2026-07-07: `rg "fanTalkDeleteDialog|SodaV2ActionModal|isShowDeleteDialog|deleteSelectedFanTalk|overlay\(deleteDialog\)|private var deleteDialog" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift`로 팬Talk 삭제 모달이 상위 `CreatorChannelView`에서 `SodaV2ActionModal`로 표시되고, 팬Talk 탭 내부 삭제 dialog overlay가 제거된 것을 확인했다. +- 2026-07-07: `rg "CreatorChannelActionPopup|I18n\.Explorer\.edit|I18n\.Common\.delete|수정하기|삭제하기" SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkActionPopup.swift SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailActionPopup.swift SodaLive/Sources/V2/CreatorChannel/FanTalk/CreatorChannelFanTalkTabView.swift SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Components/CreatorChannelReplyDetailFeedView.swift`로 리스트와 상세 anchored context popup이 공용 컴포넌트와 동일 `I18n` 문구를 사용하는 것을 확인했다. +- 2026-07-07: `rg "SodaV2ActionModal|dismissDeleteDialog|confirmDelete|SodaDialog" SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift`로 상세 삭제 확인 dialog가 `SodaV2ActionModal`을 사용하는 것을 확인했다. + +- 2026-07-07: Reviewer gate 지적에 따라 PRD 본문 요구사항/성공 기준/제약 조건에 삭제 확인 모달의 상위 `CreatorChannelView` 표시, `SodaV2ActionModal` 사용, list/detail anchored context popup 공용화, `CheersReportDialogView` 제외 범위를 명시했다. + +- 2026-07-07: 사용자 후속 요청에 따라 팬Talk 목록 row anchored context popup 문구를 `수정`/`삭제`에서 `수정하기`/`삭제하기`로 변경했다. 상세 popup 기본 문구는 기존 공용 기본값을 유지하고, 팬Talk row 호출부에서 `I18n.Explorer.editAction`, `I18n.Explorer.deleteAction`을 주입한다. diff --git a/docs/20260704_크리에이터_채널_팬Talk_탭/prd.md b/docs/20260704_크리에이터_채널_팬Talk_탭/prd.md index a2f91f88..9ed8ef0d 100644 --- a/docs/20260704_크리에이터_채널_팬Talk_탭/prd.md +++ b/docs/20260704_크리에이터_채널_팬Talk_탭/prd.md @@ -35,9 +35,9 @@ Figma 참조: - 답글이 있으면 Figma처럼 좌측 라인과 답글 카드 영역을 표시한다. - 다른 유저의 글에는 `신고` 버튼만 표시하고, 기존 `UserProfileView`의 응원글 신고 기능과 동일하게 `CheersReportDialogView` 및 `ReportType.CHEERS` 흐름을 사용한다. - 내가 쓴 글 또는 내 채널의 타인 글에는 우측 액션으로 `ic_new_more` 더보기 버튼을 표시한다. -- 내가 쓴 글의 더보기 메뉴에는 `수정하기`, `삭제하기`를 표시한다. -- 내가 쓴 글이 아니지만 내 채널인 경우 더보기 메뉴에는 `삭제하기`만 표시한다. -- `삭제하기`는 기존 FanTalk에서 사용하던 삭제 API와 동일하게 `ExplorerRepository.modifyCheers(..., isActive: false)`를 사용한다. +- 내가 쓴 글의 더보기 메뉴에는 공용 `I18n` 문구 기준의 `수정`, `삭제` 액션을 표시한다. +- 내가 쓴 글이 아니지만 내 채널인 경우 더보기 메뉴에는 공용 `I18n` 문구 기준의 `삭제` 액션만 표시한다. +- `삭제` 액션은 기존 FanTalk에서 사용하던 삭제 API와 동일하게 `ExplorerRepository.modifyCheers(..., isActive: false)`를 사용한다. - `fanTalkCount == 0`이면 Figma empty view(`290:9001`)를 표시한다. - 내 채널인 경우 우측하단 글쓰기 floating button과 empty view 내부 `응원 남기기` 버튼을 모두 표시하지 않는다. - 크리에이터 채널 팬Talk 탭의 우측하단 plus button과 empty state `응원 남기기` 버튼에서 팬Talk 글 입력 화면으로 진입한다. @@ -54,6 +54,7 @@ Figma 참조: - 신고 API나 신고 dialog를 새로 만들지 않는다. - 삭제 API를 새로 만들지 않고 기존 FanTalk 삭제 흐름을 재사용한다. - 기존 구 UI `SodaDialog`를 V2 공용 모달로 대체하는 전역 리팩터링은 하지 않는다. +- 신고 사유 선택 UI인 `CheersReportDialogView`는 기존 신고 플로우 재사용 범위로 유지하고, 이번 V2 popup dialog 전환 대상에서 제외한다. - Figma localhost asset URL을 앱 코드에 직접 사용하지 않는다. - `Pods/**`, `generated/**`, `build/**`는 수정하지 않는다. @@ -133,15 +134,19 @@ data class CreatorChannelFanTalkReplyResponse( - `isMine || isOwnCreatorChannel`이면 우측에 `ic_new_more` 더보기 버튼을 표시한다. - `!isMine && !isOwnCreatorChannel`이면 우측에 `신고` 버튼만 표시한다. - 더보기 버튼을 터치하면 Figma `666:36114` 기준 anchored context popup을 표시한다. -- 내가 쓴 글이면 `수정하기`, `삭제하기`를 표시한다. -- 내가 쓴 글이 아니지만 내 채널이면 `삭제하기`만 표시한다. -- `수정하기`를 터치하면 이번에 작성하는 팬Talk 쓰기 페이지를 수정 모드로 연다. -- `삭제하기`는 확인 dialog 후 기존 삭제 API를 호출한다. +- 팬Talk 목록과 답글 상세의 anchored context popup은 동일한 공용 popup 컴포넌트를 사용한다. +- 팬Talk 목록 row의 anchored context popup은 `수정하기`, `삭제하기` 문구를 표시한다. +- 내가 쓴 글이면 `수정하기`, `삭제하기` 액션을 표시한다. +- 내가 쓴 글이 아니지만 내 채널이면 `삭제하기` 액션만 표시한다. +- `수정하기` 액션을 터치하면 이번에 작성하는 팬Talk 쓰기 페이지를 수정 모드로 연다. +- `삭제하기` 액션은 확인 dialog 후 기존 삭제 API를 호출한다. ### 5.6 Report and delete - 신고 버튼을 터치하면 기존 크리에이터 페이지의 응원글 신고와 동일하게 `CheersReportDialogView`를 표시한다. - 신고 confirm 시 `ReportRequest(type: .CHEERS, cheersId: fanTalkId, reason: reason)` 흐름을 사용한다. - 삭제 confirm dialog는 기존 `I18n.MemberChannel.cheersDeleteTitle`, `I18n.Common.confirmDeleteQuestion`, `I18n.Common.delete`, `I18n.Common.cancel`을 재사용한다. +- 삭제 confirm dialog는 `SodaV2ActionModal`을 사용한다. +- 팬Talk 탭 목록의 삭제 confirm dialog는 스크롤되는 탭 내부가 아니라 `CreatorChannelView`의 상위 wrapping `ZStack`에서 표시한다. - 삭제 API는 기존 FanTalk 삭제 흐름과 동일하게 `ExplorerRepository.modifyCheers(cheersId: fanTalkId, content: nil, isActive: false)`를 호출한다. - 삭제 성공 후 팬Talk 탭 첫 페이지를 다시 조회한다. @@ -167,7 +172,7 @@ data class CreatorChannelFanTalkReplyResponse( ### 5.9 FanTalk write/modify entry - 쓰기 페이지는 크리에이터 채널 팬Talk 탭에서 진입한다. - 진입 경로는 `fanTalkCount > 0`일 때 표시되는 우측하단 plus button과 `fanTalkCount == 0`일 때 empty state에 표시되는 `응원 남기기` 버튼이다. -- 내가 쓴 글의 더보기 메뉴에서 `수정하기`를 터치하면 같은 쓰기 페이지를 수정 모드로 연다. +- 내가 쓴 글의 더보기 메뉴에서 `수정하기` 액션을 터치하면 같은 쓰기 페이지를 수정 모드로 연다. - 수정 모드로 진입할 때는 수정 대상 `fanTalkId`와 기존 글 content를 반드시 함께 전달한다. - 내 채널인 경우 기존 규칙대로 plus button과 empty state `응원 남기기` 버튼을 모두 표시하지 않으므로 작성 모드로 진입할 수 없다. - 쓰기 route는 `AppStep`에 추가하고 `creatorId`, 작성/수정 mode, 수정 대상 `fanTalkId`, 초기 content, `onSuccess`를 전달할 수 있게 한다. @@ -214,6 +219,9 @@ data class CreatorChannelFanTalkReplyResponse( - 타인의 글이면서 내 채널이 아니면 `신고` 버튼만 표시되고 신고 confirm 시 기존 응원글 신고 API가 호출된다. - 내가 쓴 글은 `ic_new_more` 버튼과 `수정하기`, `삭제하기` 메뉴를 표시한다. - 내가 쓴 글이 아니지만 내 채널이면 `ic_new_more` 버튼과 `삭제하기` 메뉴만 표시한다. +- 팬Talk 목록과 답글 상세의 anchored context popup은 동일한 공용 popup 컴포넌트를 사용한다. +- 삭제 confirm dialog는 `SodaV2ActionModal`로 표시된다. +- 팬Talk 탭 목록의 삭제 confirm dialog는 `CreatorChannelView` 상위 wrapping `ZStack`에서 표시되어 긴 목록 스크롤 위치와 무관하게 화면 기준으로 중앙 표시된다. - 삭제 confirm 시 기존 FanTalk 삭제 API가 호출되고 성공 후 첫 페이지가 갱신된다. - `fanTalkCount == 0`이면 Figma `290:9001` empty view가 표시된다. - 내 채널인 경우 empty view 내부 `응원 남기기` 버튼과 우측하단 글쓰기 floating button이 모두 표시되지 않는다. @@ -225,7 +233,7 @@ data class CreatorChannelFanTalkReplyResponse( - 입력 값이 있는 상태에서 취소 버튼을 터치하면 입력 종료 안내 모달이 표시된다. - 입력 종료 안내 모달의 dimmed를 터치하면 모달만 닫히고 입력 화면과 입력 값은 유지된다. - 작성 성공 후 팬Talk 탭 첫 페이지가 다시 조회된다. -- 내가 쓴 글의 `수정하기` tap 시 같은 팬Talk 쓰기 페이지가 수정 모드로 열린다. +- 내가 쓴 글의 `수정하기` 액션 tap 시 같은 팬Talk 쓰기 페이지가 수정 모드로 열린다. - 수정 모드 진입 시 수정 대상 `fanTalkId`와 기존 글 content가 함께 전달된다. - 수정 모드는 선택한 fanTalk의 기존 content를 입력값으로 표시한다. - 수정 성공 후 팬Talk 탭 첫 페이지가 다시 조회된다. @@ -240,6 +248,7 @@ data class CreatorChannelFanTalkReplyResponse( - 작성 API 재사용을 위해 기존 `ExplorerRepository.writeCheers`를 호출한다. - 수정 API 재사용을 위해 기존 `ExplorerRepository.modifyCheers`를 호출한다. - 신고 기능 재사용을 위해 기존 `ReportRepository`, `ReportRequest`, `CheersReportDialogView`를 사용한다. +- `CheersReportDialogView`는 신고 사유 선택 전용 UI이므로 이번 확인형 V2 popup dialog 전환 범위에서 제외한다. - 신규 문구가 필요하면 `SodaLive/Sources/I18n/I18n.swift`에 ko/en/ja를 추가한다. - 프로젝트 설정 변경은 신규 Swift 파일 target 등록이 필요한 경우에만 수행한다. @@ -261,3 +270,5 @@ data class CreatorChannelFanTalkReplyResponse( - 2026-07-06: `rg -n "no-op|수정 페이지는|수정 mutation|작성용 새 API|재사용 가능 여부|불가능한 것이 확인|미결정 사항|수정하기 tap은 수정 페이지" docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md` 실행 결과 매칭이 없어 이전 충돌 문구가 남지 않았음을 확인했다. - 2026-07-06: 사용자 확인에 따라 수정 모드 진입 시 수정 대상 `fanTalkId`와 기존 글 content를 반드시 함께 전달하도록 PRD를 보완했다. - 2026-07-06: `rg '수정 모드.*fanTalkId|fanTalkId.*기존 글 content|fanTalkId.*초기 content|fanTalkId.*initialContent|기존 content.*content 초기값|수정 대상.*fanTalkId.*기존 글 content|fanTalkId.*0보다 커야' docs/20260704_크리에이터_채널_팬Talk_탭/prd.md docs/20260704_크리에이터_채널_팬Talk_탭/plan-task.md`로 수정 모드 route에 `fanTalkId`와 기존 글 content를 함께 전달한다는 조건을 확인했다. + +- 2026-07-07: 사용자 후속 요청에 따라 팬Talk row 더보기의 삭제 확인 dialog는 팬Talk 탭 내부가 아니라 `CreatorChannelView` 상위 wrapping `ZStack`에서 표시한다. 삭제 확인 UI는 이번에 추가된 V2 공용 모달 `SodaV2ActionModal`을 사용하며, 팬Talk 리스트와 상세보기의 anchored context popup은 동일한 공용 컴포넌트와 동일한 `I18n` 문구를 사용한다. 신고 사유 선택 UI인 `CheersReportDialogView`는 기존 신고 플로우 재사용 범위로 유지하고 이번 확인형 V2 popup dialog 전환 범위에서 제외한다.