diff --git a/SodaLive.xcodeproj/project.pbxproj b/SodaLive.xcodeproj/project.pbxproj index 32b7caf5..4c99e9be 100644 --- a/SodaLive.xcodeproj/project.pbxproj +++ b/SodaLive.xcodeproj/project.pbxproj @@ -258,6 +258,9 @@ 401C10322FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10022FEF300000A5BBA3 /* MainHomeFollowingApi.swift */; }; 401C10332FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10032FEF300000A5BBA3 /* MainHomeFollowingRepository.swift */; }; 401C10342FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10042FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift */; }; + 401C10412FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10442FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift */; }; + 401C10422FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10442FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift */; }; + 401C10432FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10442FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift */; }; 40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */; }; 40235DEE2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */; }; 40235DF02E37983D00B09A87 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEF2E37983D00B09A87 /* PersistenceController.swift */; }; @@ -1796,6 +1799,7 @@ 401C10022FEF300000A5BBA3 /* MainHomeFollowingApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingApi.swift; sourceTree = ""; }; 401C10032FEF300000A5BBA3 /* MainHomeFollowingRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingRepository.swift; sourceTree = ""; }; 401C10042FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingViewModel.swift; sourceTree = ""; }; + 401C10442FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingCreatorSection.swift; sourceTree = ""; }; 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecentContent+CoreDataClass.swift"; sourceTree = ""; }; 40235DEF2E37983D00B09A87 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; 40235DF22E37987300B09A87 /* RecentContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentContentService.swift; sourceTree = ""; }; @@ -2797,12 +2801,21 @@ children = ( 401C10052FEF300000A5BBA3 /* Models */, 401C10062FEF300000A5BBA3 /* Repository */, + 401C10402FEF500000A5BBA3 /* Components */, 401C10042FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift */, 401A3FB72FEEAFA000A5BBA3 /* MainHomeFollowingView.swift */, ); path = Following; sourceTree = ""; }; + 401C10402FEF500000A5BBA3 /* Components */ = { + isa = PBXGroup; + children = ( + 401C10442FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift */, + ); + path = Components; + sourceTree = ""; + }; 401A3FC02FEEB00100A5BBA3 /* Components */ = { isa = PBXGroup; children = ( @@ -5467,6 +5480,7 @@ 401C10122FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */, 401C10132FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */, 401C10142FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */, + 401C10412FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */, 409143302A85652F00EEBFB1 /* CanChargeView.swift in Sources */, 40975CA32B572298004E2F1B /* LiveRoomOverlayStrokeTextButton.swift in Sources */, 40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */, @@ -6231,6 +6245,7 @@ 401C10222FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */, 401C10232FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */, 401C10242FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */, + 401C10422FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */, B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */, CA65AD0D677B4A8E9CAC0E15 /* MainTab.swift in Sources */, A0643C829BE7377DC09112CB /* MainTabBarButton.swift in Sources */, @@ -7025,6 +7040,7 @@ 401C10322FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */, 401C10332FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */, 401C10342FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */, + 401C10432FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */, ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */, 33BA3AB731849284E62AE04B /* MainTab.swift in Sources */, B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */, diff --git a/SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift b/SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift new file mode 100644 index 00000000..e67160d9 --- /dev/null +++ b/SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift @@ -0,0 +1,68 @@ +import SwiftUI + +struct MainHomeFollowingCreatorSection: View { + let followingCreators: [FollowingCreatorResponse] + let onTapCreator: (Int) -> Void + let onTapAll: () -> Void + + init( + followingCreators: [FollowingCreatorResponse], + onTapCreator: @escaping (Int) -> Void = { _ in }, + onTapAll: @escaping () -> Void = {} + ) { + self.followingCreators = followingCreators + self.onTapCreator = onTapCreator + self.onTapAll = onTapAll + } + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(alignment: .top, spacing: SodaSpacing.s12) { + ForEach(followingCreators) { creator in + CreatorProfileItem( + imageUrl: creator.creatorProfileImageUrl, + name: creator.creatorNickname, + imageSize: CGSize(width: 72, height: 72), + spacing: SodaSpacing.s8 + ) { + onTapCreator(creator.creatorId) + } + .frame(width: 72) + } + + Button(action: onTapAll) { + Text(I18n.HomeFollowing.allButtonTitle) + .appFont(.body4) + .foregroundColor(Color.soda400) + .padding(.horizontal, 16) + .frame(height: allButtonHeight) + } + .buttonStyle(.plain) + } + .padding(.horizontal, SodaSpacing.s20) + } + } + + private var allButtonHeight: CGFloat { 72 + SodaSpacing.s8 + 20 } +} + +struct MainHomeFollowingCreatorSection_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: SodaSpacing.s20) { + MainHomeFollowingCreatorSection( + followingCreators: [ + FollowingCreatorResponse( + creatorId: 1, + creatorNickname: "크리에이터", + creatorProfileImageUrl: "https://picsum.photos/200/200" + ) + ] + ) + + MainHomeFollowingCreatorSection(followingCreators: []) + } + .padding(.vertical, SodaSpacing.s20) + .background(Color.black) + .previewLayout(.sizeThatFits) + } +} diff --git a/SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift b/SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift index cf956eef..92669154 100644 --- a/SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift +++ b/SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift @@ -1,8 +1,21 @@ import SwiftUI struct MainHomeFollowingView: View { + let onTapFollowingAll: () -> Void + + init(onTapFollowingAll: @escaping () -> Void = {}) { + self.onTapFollowingAll = onTapFollowingAll + } + var body: some View { - MainPlaceholderTabView(title: "팔로잉") + VStack(spacing: 0) { + MainHomeFollowingCreatorSection( + followingCreators: [], + onTapAll: onTapFollowingAll + ) + + MainPlaceholderTabView(title: "팔로잉") + } } } diff --git a/SodaLive/Sources/V2/Main/Home/MainHomeView.swift b/SodaLive/Sources/V2/Main/Home/MainHomeView.swift index 663c860a..efb995e2 100644 --- a/SodaLive/Sources/V2/Main/Home/MainHomeView.swift +++ b/SodaLive/Sources/V2/Main/Home/MainHomeView.swift @@ -12,6 +12,7 @@ struct MainHomeView: View { let onTapBanner: (RecommendationBannerResponse) -> Void let onTapFollowAll: (@escaping () -> Void) -> Void let onSelectFollowingTab: () -> Bool + let onTapFollowingAll: () -> Void @State private var selectedTab: MainHomeTab = .recommendation @@ -71,7 +72,7 @@ struct MainHomeView: View { case .ranking: MainHomeRankingView(onTapCreator: onTapCreator) case .following: - MainHomeFollowingView() + MainHomeFollowingView(onTapFollowingAll: onTapFollowingAll) } } } @@ -106,7 +107,8 @@ struct MainHomeView_Previews: PreviewProvider { onTapCommunity: { _ in }, onTapBanner: { _ in }, onTapFollowAll: { action in action() }, - onSelectFollowingTab: { true } + onSelectFollowingTab: { true }, + onTapFollowingAll: {} ) } } diff --git a/SodaLive/Sources/V2/Main/MainView.swift b/SodaLive/Sources/V2/Main/MainView.swift index 9bd0de1c..8364a2ed 100644 --- a/SodaLive/Sources/V2/Main/MainView.swift +++ b/SodaLive/Sources/V2/Main/MainView.swift @@ -164,7 +164,8 @@ struct MainView: View { onTapCommunity: handleRecommendationCommunityTap, onTapBanner: handleRecommendationBannerTap, onTapFollowAll: handleRecommendationFollowAllTap, - onSelectFollowingTab: handleFollowingTabSelection + onSelectFollowingTab: handleFollowingTabSelection, + onTapFollowingAll: handleFollowingAllTap ) case .content: MainPlaceholderTabView(title: MainTab.content.title) @@ -485,6 +486,10 @@ struct MainView: View { performRecommendationDetailAction(action: action) } + private func handleFollowingAllTap() { + appState.setAppStep(step: .followingList) + } + private func handleFollowingTabSelection() -> Bool { let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { diff --git a/docs/20260630_메인_홈_팔로잉_탭/plan-task.md b/docs/20260630_메인_홈_팔로잉_탭/plan-task.md index ffd7ed9c..9a185c7c 100644 --- a/docs/20260630_메인_홈_팔로잉_탭/plan-task.md +++ b/docs/20260630_메인_홈_팔로잉_탭/plan-task.md @@ -174,7 +174,7 @@ ### Phase 3: 팔로잉 크리에이터 섹션과 전체 버튼 -- [ ] **Task 3.1: 팔로잉 크리에이터 섹션 생성** +- [x] **Task 3.1: 팔로잉 크리에이터 섹션 생성** - 대상 파일: - 생성: `SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift` - 확인: `SodaLive/Sources/V2/Component/Creator/CreatorProfileItem.swift` @@ -193,7 +193,7 @@ - 실행 명령: `rg "struct MainHomeFollowingCreatorSection|ScrollView\\(\\.horizontal|followingCreators|onTapCreator|onTapAll|allButtonTitle|Color.soda400|padding\\(\\.horizontal, 16\\)" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift` - 기대 결과: 가로 creator 목록과 마지막 `전체` 버튼 요구사항이 확인된다. -- [ ] **Task 3.2: 전체 팔로잉 목록 진입 연결** +- [x] **Task 3.2: 전체 팔로잉 목록 진입 연결** - 대상 파일: - 수정: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift` - 수정: `SodaLive/Sources/V2/Main/MainView.swift` @@ -472,3 +472,7 @@ - 2026-06-30: `rg "struct HomeFollowingTabResponse|struct ChatRoomListItemResponse|enum CreatorActivityType|struct FollowingNewsResponse|struct FollowingCreatorRankingNewsResponse|struct FollowingContentNewsResponse|struct FollowingContentRankingNewsResponse|struct FollowingCommunityPostNewsResponse|enum FollowingNewsType|unknown\(" SodaLive/Sources/V2/Main/Home/Following/Models/HomeFollowingTabResponse.swift`로 Phase 2 응답 모델과 unknown enum 처리를 확인했다. - 2026-06-30: `rg "enum MainHomeFollowingApi|getFollowing|/api/v2/home/following|Authorization|requestPlain|TargetType|final class MainHomeFollowingRepository|MoyaProvider|AnyPublisher" SodaLive/Sources/V2/Main/Home/Following/Repository`로 API/Repository 패턴을 확인했다. - 2026-06-30: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`를 실행해 빌드 성공을 확인했다. 기존 프로젝트의 duplicate build file/run script warning은 남아 있으나 Phase 1-2 컴파일 오류는 없었다. +- 2026-06-30: Phase 3 구현을 진행해 `MainHomeFollowingCreatorSection`을 추가하고, `MainHomeView -> MainHomeFollowingView -> MainView.handleFollowingAllTap()` 경로로 `전체` 버튼을 `.followingList` 라우팅에 연결했다. +- 2026-06-30: `rg "struct MainHomeFollowingCreatorSection|ScrollView\(\.horizontal|followingCreators|onTapCreator|onTapAll|allButtonTitle|Color\.soda400|padding\(\.horizontal, 16\)" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift`로 팔로잉 크리에이터 섹션 요구사항을 확인했다. +- 2026-06-30: `rg "onTapFollowingAll|followingList|FollowCreatorView" SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/V2/Main/MainView.swift SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift`로 `전체` 버튼 라우팅 연결을 확인했다. +- 2026-06-30: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`를 재실행해 Phase 3 신규 Swift 파일 target 등록과 컴파일 성공을 확인했다.