From ce191539f2c44f998195aecf94b3a87a754e7c90 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Fri, 10 Jul 2026 13:04:06 +0900 Subject: [PATCH] =?UTF-8?q?fix(main):=20=EC=B6=94=EC=B2=9C=20=EC=BB=A4?= =?UTF-8?q?=EB=AE=A4=EB=8B=88=ED=8B=B0=20=EC=83=81=EC=84=B8=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V2/Component/Card/CommunityPostCard.swift | 5 ++ .../Sources/V2/Main/Home/MainHomeView.swift | 6 +- .../MainHomeActiveCreatorSection.swift | 8 +-- .../MainHomePopularCommunitySection.swift | 11 +-- .../MainHomeRecommendationView.swift | 8 +-- SodaLive/Sources/V2/Main/MainView.swift | 12 ++-- .../plan-task.md | 68 +++++++++++++++++++ docs/20260602_메인_홈_추천_UI_API_연동/prd.md | 6 +- 8 files changed, 103 insertions(+), 21 deletions(-) diff --git a/SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift b/SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift index cb44d107..adb4f742 100644 --- a/SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift +++ b/SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift @@ -14,6 +14,7 @@ struct CommunityPostCard: View { let isLike: Bool let likeCount: Int let commentCount: Int + let onTapPost: () -> Void let onTapLike: () -> Void let onTapComment: () -> Void let onTapPurchase: () -> Void @@ -36,6 +37,7 @@ struct CommunityPostCard: View { isLike: Bool, likeCount: Int, commentCount: Int, + onTapPost: @escaping () -> Void = {}, onTapLike: @escaping () -> Void = {}, onTapComment: @escaping () -> Void = {}, onTapPurchase: @escaping () -> Void = {} @@ -52,6 +54,7 @@ struct CommunityPostCard: View { self.isLike = isLike self.likeCount = likeCount self.commentCount = commentCount + self.onTapPost = onTapPost self.onTapLike = onTapLike self.onTapComment = onTapComment self.onTapPurchase = onTapPurchase @@ -76,6 +79,8 @@ struct CommunityPostCard: View { .padding(SodaSpacing.s14) .background(Color.gray900) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) + .contentShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) + .onTapGesture(perform: onTapPost) .onChange(of: isLike) { newValue in localIsLike = newValue } diff --git a/SodaLive/Sources/V2/Main/Home/MainHomeView.swift b/SodaLive/Sources/V2/Main/Home/MainHomeView.swift index fe5c9813..060f36e5 100644 --- a/SodaLive/Sources/V2/Main/Home/MainHomeView.swift +++ b/SodaLive/Sources/V2/Main/Home/MainHomeView.swift @@ -8,7 +8,7 @@ struct MainHomeView: View { let onTapCreator: (Int) -> Void let onTapContent: (Int) -> Void let onTapCharacter: (Int) -> Void - let onTapCommunity: (Int) -> Void + let onTapCommunityPost: (Int) -> Void let onTapBanner: (RecommendationBannerResponse) -> Void let onTapFollowAll: (@escaping () -> Void) -> Void let onSelectFollowingTab: () -> Bool @@ -69,7 +69,7 @@ struct MainHomeView: View { onTapCreator: onTapCreator, onTapContent: onTapContent, onTapCharacter: onTapCharacter, - onTapCommunity: onTapCommunity, + onTapCommunityPost: onTapCommunityPost, onTapBanner: onTapBanner, onTapFollowAll: onTapFollowAll ) @@ -117,7 +117,7 @@ struct MainHomeView_Previews: PreviewProvider { onTapCreator: { _ in }, onTapContent: { _ in }, onTapCharacter: { _ in }, - onTapCommunity: { _ in }, + onTapCommunityPost: { _ in }, onTapBanner: { _ in }, onTapFollowAll: { action in action() }, onSelectFollowingTab: { true }, diff --git a/SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeActiveCreatorSection.swift b/SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeActiveCreatorSection.swift index 5fc9d6b6..52c0907f 100644 --- a/SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeActiveCreatorSection.swift +++ b/SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeActiveCreatorSection.swift @@ -4,18 +4,18 @@ struct MainHomeActiveCreatorSection: View { let items: [HomeActiveCreatorItem] let onTapLive: (Int) -> Void let onTapContent: (Int) -> Void - let onTapCommunity: (Int) -> Void + let onTapCommunityPost: (Int) -> Void init( items: [HomeActiveCreatorItem], onTapLive: @escaping (Int) -> Void = { _ in }, onTapContent: @escaping (Int) -> Void = { _ in }, - onTapCommunity: @escaping (Int) -> Void = { _ in } + onTapCommunityPost: @escaping (Int) -> Void = { _ in } ) { self.items = items self.onTapLive = onTapLive self.onTapContent = onTapContent - self.onTapCommunity = onTapCommunity + self.onTapCommunityPost = onTapCommunityPost } var body: some View { @@ -46,7 +46,7 @@ struct MainHomeActiveCreatorSection: View { case .audio: onTapContent(targetId) case .community: - onTapCommunity(targetId) + onTapCommunityPost(targetId) case .unknown: break } diff --git a/SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomePopularCommunitySection.swift b/SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomePopularCommunitySection.swift index d5e865af..90eb4bfa 100644 --- a/SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomePopularCommunitySection.swift +++ b/SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomePopularCommunitySection.swift @@ -2,18 +2,18 @@ import SwiftUI struct MainHomePopularCommunitySection: View { let items: [HomePopularCommunityPostItem] - let onTapCommunity: (Int) -> Void + let onTapCommunityPost: (Int) -> Void let onTapLike: (Int) -> Void let onTapPurchase: (HomePopularCommunityPostItem) -> Void init( items: [HomePopularCommunityPostItem], - onTapCommunity: @escaping (Int) -> Void = { _ in }, + onTapCommunityPost: @escaping (Int) -> Void = { _ in }, onTapLike: @escaping (Int) -> Void = { _ in }, onTapPurchase: @escaping (HomePopularCommunityPostItem) -> Void = { _ in } ) { self.items = items - self.onTapCommunity = onTapCommunity + self.onTapCommunityPost = onTapCommunityPost self.onTapLike = onTapLike self.onTapPurchase = onTapPurchase } @@ -38,11 +38,14 @@ struct MainHomePopularCommunitySection: View { isLike: item.isLiked, likeCount: item.likeCount, commentCount: item.commentCount, + onTapPost: { + onTapCommunityPost(item.postId) + }, onTapLike: { onTapLike(item.postId) }, onTapComment: { - onTapCommunity(item.creatorId) + onTapCommunityPost(item.postId) }, onTapPurchase: { onTapPurchase(item) diff --git a/SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift b/SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift index 4b8f08e0..01e34a0c 100644 --- a/SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift +++ b/SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift @@ -5,7 +5,7 @@ struct MainHomeRecommendationView: View { let onTapCreator: (Int) -> Void let onTapContent: (Int) -> Void let onTapCharacter: (Int) -> Void - let onTapCommunity: (Int) -> Void + let onTapCommunityPost: (Int) -> Void let onTapBanner: (RecommendationBannerResponse) -> Void let onTapFollowAll: (@escaping () -> Void) -> Void @@ -37,7 +37,7 @@ struct MainHomeRecommendationView: View { items: viewModel.recommendations?.recentlyActiveCreators ?? [], onTapLive: onTapLive, onTapContent: onTapContent, - onTapCommunity: onTapCommunity + onTapCommunityPost: onTapCommunityPost ) MainHomeRecentDebutCreatorSection( @@ -79,7 +79,7 @@ struct MainHomeRecommendationView: View { MainHomePopularCommunitySection( items: viewModel.recommendations?.popularCommunityPosts ?? [], - onTapCommunity: onTapCommunity, + onTapCommunityPost: onTapCommunityPost, onTapLike: { postId in viewModel.likeCommunityPost(postId: postId) }, @@ -132,7 +132,7 @@ struct MainHomeRecommendationView_Previews: PreviewProvider { onTapCreator: { _ in }, onTapContent: { _ in }, onTapCharacter: { _ in }, - onTapCommunity: { _ in }, + onTapCommunityPost: { _ in }, onTapBanner: { _ in }, onTapFollowAll: { action in action() } ) diff --git a/SodaLive/Sources/V2/Main/MainView.swift b/SodaLive/Sources/V2/Main/MainView.swift index f64526a3..e306c021 100644 --- a/SodaLive/Sources/V2/Main/MainView.swift +++ b/SodaLive/Sources/V2/Main/MainView.swift @@ -161,7 +161,7 @@ struct MainView: View { onTapCreator: handleRecommendationCreatorTap, onTapContent: handleRecommendationContentTap, onTapCharacter: handleRecommendationCreatorTap, - onTapCommunity: handleRecommendationCommunityTap, + onTapCommunityPost: handleRecommendationCommunityPostTap, onTapBanner: handleRecommendationBannerTap, onTapFollowAll: handleRecommendationFollowAllTap, onSelectFollowingTab: handleFollowingTabSelection, @@ -494,10 +494,10 @@ struct MainView: View { } } - private func handleRecommendationCommunityTap(creatorId: Int) { - guard creatorId > 0 else { return } + private func handleRecommendationCommunityPostTap(postId: Int) { + guard postId > 0 else { return } performRecommendationDetailAction { - appState.setAppStep(step: .creatorCommunityAll(creatorId: creatorId)) + appState.setAppStep(step: .creatorChannelCommunityPostDetail(postId: postId)) } } @@ -527,7 +527,9 @@ struct MainView: View { case .audio: handleRecommendationContentTap(contentId: targetId) case .community: - handleRecommendationCommunityTap(creatorId: targetId) + performRecommendationDetailAction { + appState.setAppStep(step: .creatorCommunityAll(creatorId: targetId)) + } case .unknown: return } diff --git a/docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md b/docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md index e2b0a833..ebbd189f 100644 --- a/docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md +++ b/docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md @@ -477,8 +477,76 @@ - 실행 명령: `rg "### Phase [0-9]+:|- \\[[ x]\\] \\*\\*Task [0-9]+\\.[0-9]+:" docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md` - 기대 결과: phase heading과 `Task N.N` 형식 체크박스가 검색된다. +### Phase 20: 추천 커뮤니티 게시물 상세 진입 보정 + +- [x] **Task 20.1: PRD/계획 문서에 커뮤니티 게시물 상세 이동 요구사항 추가** + - 대상 파일: + - 수정: `docs/20260602_메인_홈_추천_UI_API_연동/prd.md` + - 수정: `docs/20260602_메인_홈_추천_UI_API_연동/plan-task.md` + - 작업 내용: + - 최근 활동 크리에이터의 `activityType == COMMUNITY` 탭 액션을 커뮤니티 게시물 상세 이동으로 명시한다. + - 인기 커뮤니티 섹션의 게시물 탭 액션을 커뮤니티 게시물 상세 이동으로 명시한다. + - `targetId`는 최근 활동 `COMMUNITY`일 때 커뮤니티 게시물 `postId`로 취급한다. + - 검증 기준: + - 실행 명령: `rg "activityType == COMMUNITY|커뮤니티 게시물 상세|targetId.*postId|인기 커뮤니티" docs/20260602_메인_홈_추천_UI_API_연동` + - 기대 결과: PRD와 계획 문서에서 이번 상세 이동 요구사항이 검색된다. + +- [x] **Task 20.2: 추천 탭 커뮤니티 게시물 상세 라우팅 연결** + - 대상 파일: + - 수정: `SodaLive/Sources/V2/Main/MainView.swift` + - 수정: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift` + - 수정: `SodaLive/Sources/V2/Main/Home/Recommendation/MainHomeRecommendationView.swift` + - 수정: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomeActiveCreatorSection.swift` + - 수정: `SodaLive/Sources/V2/Main/Home/Recommendation/Components/MainHomePopularCommunitySection.swift` + - 수정: `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift` + - 작업 내용: + - 추천 탭 커뮤니티 callback을 `postId` 기준으로 명확히 연결한다. + - 최근 활동 크리에이터의 `COMMUNITY` 타입은 `targetId`를 `postId`로 전달한다. + - 인기 커뮤니티 카드 일반 탭과 댓글 아이콘 탭은 `postId`로 커뮤니티 게시물 상세에 진입한다. + - 좋아요, 구매, 오디오 재생 액션은 기존 동작을 유지한다. + - 팔로잉 탭 스케줄의 기존 `.community` 라우팅은 이번 추천 탭 범위 밖이므로 변경하지 않는다. + - 검증 기준: + - 실행 명령: `rg "onTapCommunityPost|handleRecommendationCommunityPostTap|creatorChannelCommunityPostDetail|onTapPost|onTapComment|handleFollowingScheduleTap" SodaLive/Sources/V2/Main SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift` + - 기대 결과: 추천 탭 커뮤니티 액션은 `postId` 기반 상세 이동을 사용하고, 팔로잉 스케줄 community 라우팅은 기존 분기를 유지한다. + +- [x] **Task 20.3: 정적 검증과 빌드 검증** + - 대상 파일: + - 확인: `SodaLive/Sources/V2/Main/**` + - 확인: `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift` + - 확인: `SodaLive.xcodeproj/project.pbxproj` + - 작업 내용: + - 변경 파일의 공백 오류와 프로젝트 파일 유효성을 확인한다. + - 가능한 빌드를 실행한다. + - 실행 결과를 검증 기록에 누적한다. + - 검증 기준: + - 실행 명령: `git diff --check` + - 기대 결과: 출력 없이 성공한다. + - 실행 명령: `plutil -lint SodaLive.xcodeproj/project.pbxproj` + - 기대 결과: `OK` + - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` + - 기대 결과: `BUILD SUCCEEDED` + ## 검증 기록 +### 2026-07-10 Phase 20 추천 커뮤니티 게시물 상세 진입 보정 완료 + +- 목적: 메인 홈 추천 탭의 최근 활동 `COMMUNITY`와 인기 커뮤니티 게시물 탭 액션을 커뮤니티 게시물 상세 페이지로 연결 +- 수행 내용: + - PRD에 최근 활동 `activityType == COMMUNITY`의 `targetId`를 커뮤니티 게시물 `postId`로 취급한다는 기준을 추가 + - PRD에 인기 커뮤니티 게시물 일반 탭/댓글 아이콘 탭이 커뮤니티 게시물 상세 페이지로 이동한다는 기준을 추가 + - `MainHomeView`, `MainHomeRecommendationView`, `MainHomeActiveCreatorSection`, `MainHomePopularCommunitySection`의 추천 커뮤니티 callback 이름을 `onTapCommunityPost`로 정리 + - `MainView.handleRecommendationCommunityPostTap(postId:)`를 추가해 추천 탭 커뮤니티 액션을 `AppStep.creatorChannelCommunityPostDetail(postId:onCommunityRefresh:)`로 연결 + - `CommunityPostCard`에 기본 no-op `onTapPost`를 추가하고, 인기 커뮤니티 섹션에서 일반 탭과 댓글 아이콘 탭 모두 `postId` 기반 상세 이동으로 연결 + - 팔로잉 스케줄의 기존 `.community` 라우팅은 `creatorCommunityAll(creatorId:)`로 유지하고 로그인 guard 흐름도 유지 +- 검증: + - `rg "activityType == COMMUNITY|커뮤니티 게시물 상세|targetId.*postId|인기 커뮤니티" docs/20260602_메인_홈_추천_UI_API_연동` 실행, PRD/계획 문서에 요구사항 반영 확인 + - `rg -n "onTapCommunityPost|handleRecommendationCommunityPostTap|creatorChannelCommunityPostDetail|onTapPost|onTapComment|handleFollowingScheduleTap|creatorCommunityAll" SodaLive/Sources/V2/Main SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift` 실행, 추천 탭 `postId` 상세 이동과 팔로잉 스케줄 기존 분기 확인 + - `rg -n "onTapCommunity:|handleRecommendationCommunityTap" SodaLive/Sources/V2/Main SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift` 실행, 기존 모호한 추천 커뮤니티 callback 이름 제거 확인 + - `git diff --check` 실행, 출력 없이 성공 확인 + - `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행, `OK` 확인 + - 샌드박스 내 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행, CoreSimulator/로그 접근 제한으로 실패 확인 + - 승인 후 동일 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 재실행, `BUILD SUCCEEDED` 확인 + ### 2026-06-30 Phase 17~19 최종 조립/검증/문서 갱신 완료 - 목적: V2 Main Home 추천 탭 섹션 조립 순서와 빈 섹션 숨김 조건을 최종 확인하고, 프로젝트 포함/정적 점검/빌드 검증 결과를 계획 문서에 반영 diff --git a/docs/20260602_메인_홈_추천_UI_API_연동/prd.md b/docs/20260602_메인_홈_추천_UI_API_연동/prd.md index 9ac7e2e3..24b83219 100644 --- a/docs/20260602_메인_홈_추천_UI_API_연동/prd.md +++ b/docs/20260602_메인_홈_추천_UI_API_연동/prd.md @@ -179,6 +179,8 @@ enum class RecommendedActivityType(val code: String) { - 민감 콘텐츠 보기 설정이 꺼져 있으면 `AppState.shared.setPendingContentSettingsGuideMessage(I18n.Settings.adultContentEnableGuide)`로 안내 문구를 예약하고 `AppState.shared.setAppStep(step: .contentViewSettings)`로 이동한다. - 위 조건을 통과한 경우에만 `liveDetail`, `creatorDetail`, `contentDetail`, `characterDetail` 등 실제 상세 이동을 실행한다. - 추천 탭 현재 라이브 아이템은 `roomId`가 항상 존재하는 값으로 취급하고, 아이템 탭 시 상세 화면을 거치지 않고 기존 `LiveViewModel.enterLiveRoom(roomId:)` 흐름을 호출해 방 상세 조회, 입장 전 결제 확인, 비밀방 비밀번호 dialog, `LiveRoomViewV2` 입장을 재사용한다. +- 최근 활동 크리에이터에서 `activityType == COMMUNITY`인 아이템은 `targetId`를 커뮤니티 게시물 `postId`로 취급하고, 탭 시 커뮤니티 게시물 상세 페이지로 이동한다. +- 인기 커뮤니티 섹션의 커뮤니티 게시물은 `postId` 기준으로 커뮤니티 게시물 상세 페이지로 이동한다. 좋아요, 구매, 오디오 재생 액션은 기존 개별 액션을 유지한다. 라이브 항목 주의사항: - 기존 홈의 라이브 guard는 `roomId`와 `isAdult`를 함께 받아 성인 여부를 판단한다. @@ -204,6 +206,7 @@ enum class RecommendedActivityType(val code: String) { - `구매완료` 버튼은 표시하지 않는다. - 작성자, 본문, 생성 시간, 좋아요 수, 댓글 수를 표시한다. - 생성 시간은 UTC 값을 디바이스 Timezone 기준 상대 날짜로 표시한다. +- 게시물 카드의 일반 탭 액션과 댓글 아이콘 탭 액션은 커뮤니티 게시물 상세 페이지로 이동한다. ### 7.5 사업자 정보 더보기/접기 @@ -298,6 +301,7 @@ MainHomeView - `FeedCommunityView`에 `구매완료` 버튼이 표시되지 않는다. - 사업자 정보는 마지막 섹션에서 화면 width를 채우고, 기본 3줄 말줄임표, 더보기, 전체 표시, 접기 전환이 동작한다. - 상세 진입 탭 액션은 기존 홈과 같은 로그인, 한국 사용자 본인인증, 민감 콘텐츠 보기 설정 guard를 통과한 뒤에만 실행된다. +- 최근 활동 크리에이터의 `COMMUNITY` 활동과 인기 커뮤니티 섹션의 게시물은 커뮤니티 게시물 상세 페이지로 이동한다. - `MainHomeView`는 추천/랭킹/팔로잉 탭 shell 역할만 하고, 추천 탭 섹션 조립은 `MainHomeRecommendationView`에 분리되어 있다. - 반복 UI는 기존 widget 또는 공용 컴포넌트를 재사용하고 불필요하게 중복 구현하지 않는다. - 빌드가 성공하고, 가능하면 ViewModel 디코딩/상태 전환 검증을 수행한다. @@ -307,7 +311,7 @@ MainHomeView - 각 섹션별 최대 표시 개수와 가로/세로 스크롤 정책이 Figma 기준 그대로인지, 서버 응답 전체를 모두 표시해야 하는지 확인이 필요하다. - `HomeLiveItem`에 민감/성인 라이브 여부를 판단할 `isAdult` 또는 동등한 필드가 추가되는지, 아니면 서버에서 추천 라이브를 사전 필터링하는지 확인이 필요하다. - 커뮤니티 `audioUrl`을 추천 탭 카드에서 표시/재생해야 하는지 확인이 필요하다. -- 각 카드 터치 시 상세 이동 대상이 모두 정의되어 있는지 확인이 필요하다. +- 각 카드 터치 시 상세 이동 대상이 모두 정의되어 있는지 확인이 필요하다. 단, 최근 활동 `COMMUNITY`와 인기 커뮤니티 게시물은 커뮤니티 게시물 상세 페이지로 확정한다. ## 12. Verification Plan - 문서 검증: API URL, 응답 모델, 제외 섹션, FeedCommunityView, 사업자 정보 요구가 PRD와 계획 문서에 반영되었는지 확인한다.