feat(home): 팔로잉 크리에이터 섹션을 추가한다

This commit is contained in:
Yu Sung
2026-07-01 00:04:17 +09:00
parent ae09776c9b
commit fb7d0c799f
6 changed files with 114 additions and 6 deletions

View File

@@ -258,6 +258,9 @@
401C10322FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10022FEF300000A5BBA3 /* MainHomeFollowingApi.swift */; }; 401C10322FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10022FEF300000A5BBA3 /* MainHomeFollowingApi.swift */; };
401C10332FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10032FEF300000A5BBA3 /* MainHomeFollowingRepository.swift */; }; 401C10332FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10032FEF300000A5BBA3 /* MainHomeFollowingRepository.swift */; };
401C10342FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10042FEF300000A5BBA3 /* MainHomeFollowingViewModel.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 */; }; 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 */; }; 40235DEE2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */; };
40235DF02E37983D00B09A87 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40235DEF2E37983D00B09A87 /* PersistenceController.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 = "<group>"; }; 401C10022FEF300000A5BBA3 /* MainHomeFollowingApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingApi.swift; sourceTree = "<group>"; };
401C10032FEF300000A5BBA3 /* MainHomeFollowingRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingRepository.swift; sourceTree = "<group>"; }; 401C10032FEF300000A5BBA3 /* MainHomeFollowingRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingRepository.swift; sourceTree = "<group>"; };
401C10042FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingViewModel.swift; sourceTree = "<group>"; }; 401C10042FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingViewModel.swift; sourceTree = "<group>"; };
401C10442FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingCreatorSection.swift; sourceTree = "<group>"; };
40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecentContent+CoreDataClass.swift"; sourceTree = "<group>"; }; 40235DEC2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RecentContent+CoreDataClass.swift"; sourceTree = "<group>"; };
40235DEF2E37983D00B09A87 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; }; 40235DEF2E37983D00B09A87 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = "<group>"; };
40235DF22E37987300B09A87 /* RecentContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentContentService.swift; sourceTree = "<group>"; }; 40235DF22E37987300B09A87 /* RecentContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentContentService.swift; sourceTree = "<group>"; };
@@ -2797,12 +2801,21 @@
children = ( children = (
401C10052FEF300000A5BBA3 /* Models */, 401C10052FEF300000A5BBA3 /* Models */,
401C10062FEF300000A5BBA3 /* Repository */, 401C10062FEF300000A5BBA3 /* Repository */,
401C10402FEF500000A5BBA3 /* Components */,
401C10042FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift */, 401C10042FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift */,
401A3FB72FEEAFA000A5BBA3 /* MainHomeFollowingView.swift */, 401A3FB72FEEAFA000A5BBA3 /* MainHomeFollowingView.swift */,
); );
path = Following; path = Following;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
401C10402FEF500000A5BBA3 /* Components */ = {
isa = PBXGroup;
children = (
401C10442FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift */,
);
path = Components;
sourceTree = "<group>";
};
401A3FC02FEEB00100A5BBA3 /* Components */ = { 401A3FC02FEEB00100A5BBA3 /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -5467,6 +5480,7 @@
401C10122FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */, 401C10122FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */,
401C10132FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */, 401C10132FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */,
401C10142FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */, 401C10142FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */,
401C10412FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */,
409143302A85652F00EEBFB1 /* CanChargeView.swift in Sources */, 409143302A85652F00EEBFB1 /* CanChargeView.swift in Sources */,
40975CA32B572298004E2F1B /* LiveRoomOverlayStrokeTextButton.swift in Sources */, 40975CA32B572298004E2F1B /* LiveRoomOverlayStrokeTextButton.swift in Sources */,
40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */, 40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */,
@@ -6231,6 +6245,7 @@
401C10222FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */, 401C10222FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */,
401C10232FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */, 401C10232FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */,
401C10242FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */, 401C10242FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */,
401C10422FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */,
B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */, B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */,
CA65AD0D677B4A8E9CAC0E15 /* MainTab.swift in Sources */, CA65AD0D677B4A8E9CAC0E15 /* MainTab.swift in Sources */,
A0643C829BE7377DC09112CB /* MainTabBarButton.swift in Sources */, A0643C829BE7377DC09112CB /* MainTabBarButton.swift in Sources */,
@@ -7025,6 +7040,7 @@
401C10322FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */, 401C10322FEF300000A5BBA3 /* MainHomeFollowingApi.swift in Sources */,
401C10332FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */, 401C10332FEF300000A5BBA3 /* MainHomeFollowingRepository.swift in Sources */,
401C10342FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */, 401C10342FEF300000A5BBA3 /* MainHomeFollowingViewModel.swift in Sources */,
401C10432FEF500000A5BBA3 /* MainHomeFollowingCreatorSection.swift in Sources */,
ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */, ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */,
33BA3AB731849284E62AE04B /* MainTab.swift in Sources */, 33BA3AB731849284E62AE04B /* MainTab.swift in Sources */,
B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */, B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */,

View File

@@ -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)
}
}

View File

@@ -1,8 +1,21 @@
import SwiftUI import SwiftUI
struct MainHomeFollowingView: View { struct MainHomeFollowingView: View {
let onTapFollowingAll: () -> Void
init(onTapFollowingAll: @escaping () -> Void = {}) {
self.onTapFollowingAll = onTapFollowingAll
}
var body: some View { var body: some View {
MainPlaceholderTabView(title: "팔로잉") VStack(spacing: 0) {
MainHomeFollowingCreatorSection(
followingCreators: [],
onTapAll: onTapFollowingAll
)
MainPlaceholderTabView(title: "팔로잉")
}
} }
} }

View File

@@ -12,6 +12,7 @@ struct MainHomeView: View {
let onTapBanner: (RecommendationBannerResponse) -> Void let onTapBanner: (RecommendationBannerResponse) -> Void
let onTapFollowAll: (@escaping () -> Void) -> Void let onTapFollowAll: (@escaping () -> Void) -> Void
let onSelectFollowingTab: () -> Bool let onSelectFollowingTab: () -> Bool
let onTapFollowingAll: () -> Void
@State private var selectedTab: MainHomeTab = .recommendation @State private var selectedTab: MainHomeTab = .recommendation
@@ -71,7 +72,7 @@ struct MainHomeView: View {
case .ranking: case .ranking:
MainHomeRankingView(onTapCreator: onTapCreator) MainHomeRankingView(onTapCreator: onTapCreator)
case .following: case .following:
MainHomeFollowingView() MainHomeFollowingView(onTapFollowingAll: onTapFollowingAll)
} }
} }
} }
@@ -106,7 +107,8 @@ struct MainHomeView_Previews: PreviewProvider {
onTapCommunity: { _ in }, onTapCommunity: { _ in },
onTapBanner: { _ in }, onTapBanner: { _ in },
onTapFollowAll: { action in action() }, onTapFollowAll: { action in action() },
onSelectFollowingTab: { true } onSelectFollowingTab: { true },
onTapFollowingAll: {}
) )
} }
} }

View File

@@ -164,7 +164,8 @@ struct MainView: View {
onTapCommunity: handleRecommendationCommunityTap, onTapCommunity: handleRecommendationCommunityTap,
onTapBanner: handleRecommendationBannerTap, onTapBanner: handleRecommendationBannerTap,
onTapFollowAll: handleRecommendationFollowAllTap, onTapFollowAll: handleRecommendationFollowAllTap,
onSelectFollowingTab: handleFollowingTabSelection onSelectFollowingTab: handleFollowingTabSelection,
onTapFollowingAll: handleFollowingAllTap
) )
case .content: case .content:
MainPlaceholderTabView(title: MainTab.content.title) MainPlaceholderTabView(title: MainTab.content.title)
@@ -485,6 +486,10 @@ struct MainView: View {
performRecommendationDetailAction(action: action) performRecommendationDetailAction(action: action)
} }
private func handleFollowingAllTap() {
appState.setAppStep(step: .followingList)
}
private func handleFollowingTabSelection() -> Bool { private func handleFollowingTabSelection() -> Bool {
let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines) let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { guard !trimmed.isEmpty else {

View File

@@ -174,7 +174,7 @@
### Phase 3: 팔로잉 크리에이터 섹션과 전체 버튼 ### Phase 3: 팔로잉 크리에이터 섹션과 전체 버튼
- [ ] **Task 3.1: 팔로잉 크리에이터 섹션 생성** - [x] **Task 3.1: 팔로잉 크리에이터 섹션 생성**
- 대상 파일: - 대상 파일:
- 생성: `SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift` - 생성: `SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift`
- 확인: `SodaLive/Sources/V2/Component/Creator/CreatorProfileItem.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` - 실행 명령: `rg "struct MainHomeFollowingCreatorSection|ScrollView\\(\\.horizontal|followingCreators|onTapCreator|onTapAll|allButtonTitle|Color.soda400|padding\\(\\.horizontal, 16\\)" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingCreatorSection.swift`
- 기대 결과: 가로 creator 목록과 마지막 `전체` 버튼 요구사항이 확인된다. - 기대 결과: 가로 creator 목록과 마지막 `전체` 버튼 요구사항이 확인된다.
- [ ] **Task 3.2: 전체 팔로잉 목록 진입 연결** - [x] **Task 3.2: 전체 팔로잉 목록 진입 연결**
- 대상 파일: - 대상 파일:
- 수정: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift` - 수정: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift`
- 수정: `SodaLive/Sources/V2/Main/MainView.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 "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<MainHomeFollowingApi>|AnyPublisher<Response, MoyaError>" SodaLive/Sources/V2/Main/Home/Following/Repository`로 API/Repository 패턴을 확인했다. - 2026-06-30: `rg "enum MainHomeFollowingApi|getFollowing|/api/v2/home/following|Authorization|requestPlain|TargetType|final class MainHomeFollowingRepository|MoyaProvider<MainHomeFollowingApi>|AnyPublisher<Response, MoyaError>" 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: `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 등록과 컴파일 성공을 확인했다.