feat(home): 팔로잉 탭 화면을 조립한다
This commit is contained in:
@@ -279,6 +279,9 @@
|
||||
401C105A2FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10592FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift */; };
|
||||
401C105B2FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10592FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift */; };
|
||||
401C105C2FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C10592FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift */; };
|
||||
401C105E2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C105D2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift */; };
|
||||
401C105F2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C105D2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift */; };
|
||||
401C10602FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 401C105D2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.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 */; };
|
||||
@@ -1824,6 +1827,7 @@
|
||||
401C10542FEFC00000A5BBA3 /* MainHomeFollowingRankingNewsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingRankingNewsCard.swift; sourceTree = "<group>"; };
|
||||
401C10582FEFD00000A5BBA3 /* MainHomeFollowingNewsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingNewsSection.swift; sourceTree = "<group>"; };
|
||||
401C10592FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingContentNewsCard.swift; sourceTree = "<group>"; };
|
||||
401C105D2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainHomeFollowingEmptyStateView.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>"; };
|
||||
40235DF22E37987300B09A87 /* RecentContentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentContentService.swift; sourceTree = "<group>"; };
|
||||
@@ -2842,6 +2846,7 @@
|
||||
401C10542FEFC00000A5BBA3 /* MainHomeFollowingRankingNewsCard.swift */,
|
||||
401C10592FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift */,
|
||||
401C10582FEFD00000A5BBA3 /* MainHomeFollowingNewsSection.swift */,
|
||||
401C105D2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@@ -5517,6 +5522,7 @@
|
||||
401C10512FEFC00000A5BBA3 /* MainHomeFollowingRankingNewsCard.swift in Sources */,
|
||||
401C105A2FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift in Sources */,
|
||||
401C10552FEFD00000A5BBA3 /* MainHomeFollowingNewsSection.swift in Sources */,
|
||||
401C105E2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift in Sources */,
|
||||
409143302A85652F00EEBFB1 /* CanChargeView.swift in Sources */,
|
||||
40975CA32B572298004E2F1B /* LiveRoomOverlayStrokeTextButton.swift in Sources */,
|
||||
40235DED2E3797EB00B09A87 /* RecentContent+CoreDataClass.swift in Sources */,
|
||||
@@ -6288,6 +6294,7 @@
|
||||
401C10522FEFC00000A5BBA3 /* MainHomeFollowingRankingNewsCard.swift in Sources */,
|
||||
401C105B2FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift in Sources */,
|
||||
401C10562FEFD00000A5BBA3 /* MainHomeFollowingNewsSection.swift in Sources */,
|
||||
401C105F2FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift in Sources */,
|
||||
B3AF650899B8FC84E0C0CDA0 /* MainPlaceholderTabView.swift in Sources */,
|
||||
CA65AD0D677B4A8E9CAC0E15 /* MainTab.swift in Sources */,
|
||||
A0643C829BE7377DC09112CB /* MainTabBarButton.swift in Sources */,
|
||||
@@ -7089,6 +7096,7 @@
|
||||
401C10532FEFC00000A5BBA3 /* MainHomeFollowingRankingNewsCard.swift in Sources */,
|
||||
401C105C2FEFE00000A5BBA3 /* MainHomeFollowingContentNewsCard.swift in Sources */,
|
||||
401C10572FEFD00000A5BBA3 /* MainHomeFollowingNewsSection.swift in Sources */,
|
||||
401C10602FEFF00000A5BBA3 /* MainHomeFollowingEmptyStateView.swift in Sources */,
|
||||
ACD71056E97E13E48941ABBB /* MainPlaceholderTabView.swift in Sources */,
|
||||
33BA3AB731849284E62AE04B /* MainTab.swift in Sources */,
|
||||
B11681AC02BF51C6E2FA6FF3 /* MainTabBarButton.swift in Sources */,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import SwiftUI
|
||||
|
||||
struct MainHomeFollowingEmptyStateView: View {
|
||||
let message: String
|
||||
let loginAction: (() -> Void)?
|
||||
|
||||
init(message: String, loginAction: (() -> Void)? = nil) {
|
||||
self.message = message
|
||||
self.loginAction = loginAction
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: SodaSpacing.s16) {
|
||||
Spacer()
|
||||
|
||||
Text(message)
|
||||
.appFont(.body5)
|
||||
.foregroundColor(Color.gray500)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let loginAction {
|
||||
Button(action: loginAction) {
|
||||
Text(I18n.HomeFollowing.loginButtonTitle)
|
||||
.appFont(size: 14, weight: .bold)
|
||||
.foregroundColor(.black)
|
||||
.padding(.horizontal, SodaSpacing.s16)
|
||||
.frame(height: 40)
|
||||
.background(Color.soda400)
|
||||
.clipShape(RoundedRectangle(cornerRadius: SodaRadius.r14, style: .continuous))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.black)
|
||||
}
|
||||
}
|
||||
|
||||
struct MainHomeFollowingEmptyStateView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MainHomeFollowingEmptyStateView(message: I18n.HomeFollowing.emptyStateMessage)
|
||||
.frame(width: 390, height: 320)
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,12 @@ struct MainHomeFollowingNewsSection: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if !recentNews.isEmpty {
|
||||
if !renderableNews.isEmpty {
|
||||
VStack(alignment: .leading, spacing: SodaSpacing.s14) {
|
||||
SectionTitle(title: I18n.HomeFollowing.recentNewsTitle)
|
||||
|
||||
VStack(alignment: .leading, spacing: SodaSpacing.s8) {
|
||||
ForEach(recentNews) { news in
|
||||
ForEach(renderableNews) { news in
|
||||
newsItemView(news)
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,25 @@ struct MainHomeFollowingNewsSection: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var renderableNews: [FollowingNewsResponse] {
|
||||
recentNews.filter { news in
|
||||
switch news.type {
|
||||
case .creatorRanking:
|
||||
return news.creatorRanking != nil
|
||||
case .contentRanking:
|
||||
return news.contentRanking != nil
|
||||
case .communityPost:
|
||||
return news.communityPost != nil
|
||||
case .audioContent:
|
||||
return news.audioContent != nil
|
||||
case .photoContent:
|
||||
return news.photoContent != nil
|
||||
case .unknown:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func newsItemView(_ news: FollowingNewsResponse) -> some View {
|
||||
switch news.type {
|
||||
|
||||
@@ -3,6 +3,8 @@ import SwiftUI
|
||||
struct MainHomeFollowingView: View {
|
||||
let onTapLive: (Int) -> Void
|
||||
let onTapCreator: (Int) -> Void
|
||||
let onTapContent: (Int) -> Void
|
||||
let onTapLogin: () -> Void
|
||||
let onTapFollowingAll: () -> Void
|
||||
let onTapChatTab: () -> Void
|
||||
let onTapChatRoom: (Int) -> Void
|
||||
@@ -13,6 +15,8 @@ struct MainHomeFollowingView: View {
|
||||
init(
|
||||
onTapLive: @escaping (Int) -> Void = { _ in },
|
||||
onTapCreator: @escaping (Int) -> Void = { _ in },
|
||||
onTapContent: @escaping (Int) -> Void = { _ in },
|
||||
onTapLogin: @escaping () -> Void = {},
|
||||
onTapFollowingAll: @escaping () -> Void = {},
|
||||
onTapChatTab: @escaping () -> Void = {},
|
||||
onTapChatRoom: @escaping (Int) -> Void = { _ in },
|
||||
@@ -20,6 +24,8 @@ struct MainHomeFollowingView: View {
|
||||
) {
|
||||
self.onTapLive = onTapLive
|
||||
self.onTapCreator = onTapCreator
|
||||
self.onTapContent = onTapContent
|
||||
self.onTapLogin = onTapLogin
|
||||
self.onTapFollowingAll = onTapFollowingAll
|
||||
self.onTapChatTab = onTapChatTab
|
||||
self.onTapChatRoom = onTapChatRoom
|
||||
@@ -27,41 +33,78 @@ struct MainHomeFollowingView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
MainHomeFollowingCreatorSection(
|
||||
followingCreators: viewModel.response?.followingCreators ?? [],
|
||||
onTapCreator: onTapCreator,
|
||||
onTapAll: onTapFollowingAll
|
||||
)
|
||||
|
||||
MainHomeFollowingLiveSection(
|
||||
onAirLives: viewModel.response?.onAirLives ?? [],
|
||||
onTapLive: onTapLive
|
||||
)
|
||||
|
||||
if let recentChats = viewModel.response?.recentChats, !recentChats.isEmpty {
|
||||
MainHomeFollowingChatSection(
|
||||
recentChats: recentChats,
|
||||
onTapTitle: onTapChatTab,
|
||||
onTapChatRoom: onTapChatRoom
|
||||
)
|
||||
}
|
||||
|
||||
if let monthlySchedules = viewModel.response?.monthlySchedules, !monthlySchedules.isEmpty {
|
||||
MainHomeFollowingScheduleSection(
|
||||
monthlySchedules: monthlySchedules,
|
||||
onTapSchedule: onTapSchedule
|
||||
)
|
||||
}
|
||||
|
||||
MainPlaceholderTabView(title: I18n.HomeFollowing.tabTitle)
|
||||
}
|
||||
content
|
||||
.onAppear {
|
||||
if viewModel.hasLoaded == false {
|
||||
viewModel.fetchFollowing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
.tint(.white)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.black)
|
||||
} else if viewModel.isLoginRequired {
|
||||
MainHomeFollowingEmptyStateView(
|
||||
message: I18n.HomeFollowing.loginRequiredMessage,
|
||||
loginAction: onTapLogin
|
||||
)
|
||||
} else if !viewModel.message.isEmpty {
|
||||
MainHomeFollowingEmptyStateView(message: viewModel.message)
|
||||
} else if let response = viewModel.response {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
MainHomeFollowingCreatorSection(
|
||||
followingCreators: response.followingCreators,
|
||||
onTapCreator: onTapCreator,
|
||||
onTapAll: onTapFollowingAll
|
||||
)
|
||||
|
||||
if !response.onAirLives.isEmpty {
|
||||
MainHomeFollowingLiveSection(
|
||||
onAirLives: response.onAirLives,
|
||||
onTapLive: onTapLive
|
||||
)
|
||||
.padding(.top, 28)
|
||||
}
|
||||
|
||||
if !response.recentChats.isEmpty {
|
||||
MainHomeFollowingChatSection(
|
||||
recentChats: response.recentChats,
|
||||
onTapTitle: onTapChatTab,
|
||||
onTapChatRoom: onTapChatRoom
|
||||
)
|
||||
.padding(.top, 48)
|
||||
}
|
||||
|
||||
if !response.monthlySchedules.isEmpty {
|
||||
MainHomeFollowingScheduleSection(
|
||||
monthlySchedules: response.monthlySchedules,
|
||||
onTapSchedule: onTapSchedule
|
||||
)
|
||||
.padding(.top, 48)
|
||||
}
|
||||
|
||||
if !response.recentNews.isEmpty {
|
||||
MainHomeFollowingNewsSection(
|
||||
recentNews: response.recentNews,
|
||||
onTapCreator: onTapCreator,
|
||||
onTapContent: onTapContent
|
||||
)
|
||||
.padding(.top, 48)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, SodaSpacing.s20)
|
||||
}
|
||||
.background(Color.black)
|
||||
} else {
|
||||
MainHomeFollowingEmptyStateView(message: I18n.HomeFollowing.emptyStateMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MainHomeFollowingView_Previews: PreviewProvider {
|
||||
|
||||
@@ -12,6 +12,7 @@ struct MainHomeView: View {
|
||||
let onTapBanner: (RecommendationBannerResponse) -> Void
|
||||
let onTapFollowAll: (@escaping () -> Void) -> Void
|
||||
let onSelectFollowingTab: () -> Bool
|
||||
let onTapFollowingLogin: () -> Void
|
||||
let onTapFollowingAll: () -> Void
|
||||
let onTapChatTab: () -> Void
|
||||
let onTapChatRoom: (Int) -> Void
|
||||
@@ -78,6 +79,8 @@ struct MainHomeView: View {
|
||||
MainHomeFollowingView(
|
||||
onTapLive: onTapLive,
|
||||
onTapCreator: onTapCreator,
|
||||
onTapContent: onTapContent,
|
||||
onTapLogin: onTapFollowingLogin,
|
||||
onTapFollowingAll: onTapFollowingAll,
|
||||
onTapChatTab: onTapChatTab,
|
||||
onTapChatRoom: onTapChatRoom,
|
||||
@@ -118,6 +121,7 @@ struct MainHomeView_Previews: PreviewProvider {
|
||||
onTapBanner: { _ in },
|
||||
onTapFollowAll: { action in action() },
|
||||
onSelectFollowingTab: { true },
|
||||
onTapFollowingLogin: {},
|
||||
onTapFollowingAll: {},
|
||||
onTapChatTab: {},
|
||||
onTapChatRoom: { _ in },
|
||||
|
||||
@@ -165,6 +165,7 @@ struct MainView: View {
|
||||
onTapBanner: handleRecommendationBannerTap,
|
||||
onTapFollowAll: handleRecommendationFollowAllTap,
|
||||
onSelectFollowingTab: handleFollowingTabSelection,
|
||||
onTapFollowingLogin: handleFollowingLoginTap,
|
||||
onTapFollowingAll: handleFollowingAllTap,
|
||||
onTapChatTab: handleFollowingChatTabTap,
|
||||
onTapChatRoom: handleFollowingChatRoomTap,
|
||||
@@ -527,6 +528,10 @@ struct MainView: View {
|
||||
return true
|
||||
}
|
||||
|
||||
private func handleFollowingLoginTap() {
|
||||
AppState.shared.setAppStep(step: .login)
|
||||
}
|
||||
|
||||
private func handleRecommendationBannerTap(_ item: RecommendationBannerResponse) {
|
||||
if let eventItem = item.eventItem {
|
||||
performRecommendationDetailAction {
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
|
||||
### Phase 11: 화면 조립, empty/error 상태, 상세 진입 callback 연결
|
||||
|
||||
- [ ] **Task 11.1: 팔로잉 탭 화면 조립**
|
||||
- [x] **Task 11.1: 팔로잉 탭 화면 조립**
|
||||
- 대상 파일:
|
||||
- 수정: `SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift`
|
||||
- 작업 내용:
|
||||
@@ -392,7 +392,7 @@
|
||||
- 실행 명령: `rg "MainHomeFollowingViewModel|fetchFollowing|MainHomeFollowingCreatorSection|MainHomeFollowingLiveSection|MainHomeFollowingChatSection|MainHomeFollowingScheduleSection|MainHomeFollowingNewsSection|MainHomeFollowingEmptyStateView|emptyStateMessage|loadFailedMessage|MainPlaceholderTabView" SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift`
|
||||
- 기대 결과: `MainPlaceholderTabView`는 검색되지 않고, 팔로잉 화면 상태 분기와 섹션 조립이 확인된다.
|
||||
|
||||
- [ ] **Task 11.2: empty/login required view 생성**
|
||||
- [x] **Task 11.2: empty/login required view 생성**
|
||||
- 대상 파일:
|
||||
- 생성: `SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingEmptyStateView.swift`
|
||||
- 작업 내용:
|
||||
@@ -403,7 +403,7 @@
|
||||
- 실행 명령: `rg "struct MainHomeFollowingEmptyStateView|message|loginButtonTitle|Button|Color.black|frame\\(maxWidth: \\.infinity" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingEmptyStateView.swift`
|
||||
- 기대 결과: empty/API 실패/로그인 필요 상태 공용 안내 view가 확인된다.
|
||||
|
||||
- [ ] **Task 11.3: 최근 소식 섹션 조립**
|
||||
- [x] **Task 11.3: 최근 소식 섹션 조립**
|
||||
- 대상 파일:
|
||||
- 생성 또는 수정: `SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingNewsSection.swift`
|
||||
- 작업 내용:
|
||||
@@ -419,7 +419,7 @@
|
||||
- 실행 명령: `rg "struct MainHomeFollowingNewsSection|recentNewsTitle|visibleFromAtUtc|creatorRanking|contentRanking|communityPost|audioContent|photoContent|unknown|MainHomeFollowingRankingNewsCard|MainHomeFollowingContentNewsCard|CommunityPostCard" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingNewsSection.swift`
|
||||
- 기대 결과: 최근 소식 타입별 payload 분기와 unknown 숨김 정책이 확인된다.
|
||||
|
||||
- [ ] **Task 11.4: 상세 진입 callback 연결**
|
||||
- [x] **Task 11.4: 상세 진입 callback 연결**
|
||||
- 대상 파일:
|
||||
- 수정: `SodaLive/Sources/V2/Main/Home/MainHomeView.swift`
|
||||
- 수정: `SodaLive/Sources/V2/Main/MainView.swift`
|
||||
@@ -554,3 +554,17 @@
|
||||
- 2026-07-01: `rg "localhost:3845|imgRectangle|http://localhost" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingContentNewsCard.swift` 결과가 없어 Figma localhost asset 미사용을 확인했다.
|
||||
- 2026-07-01: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test -derivedDataPath "/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/SodaLiveDerivedDataPhase10Test"`는 `Scheme SodaLive is not currently configured for the test action.`으로 실패해 테스트 액션 미구성 상태를 확인했다.
|
||||
- 2026-07-01: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug -derivedDataPath "/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/SodaLiveDerivedDataPhase10" build`를 실행해 Phase 10 신규 Swift 파일 컴파일과 빌드 성공을 확인했다. 기존 duplicate build file/run script/AppIntents/dependency scan warning은 남아 있으나 컴파일 오류는 없었다.
|
||||
- 2026-07-01: Phase 11 구현 전 `rg "struct MainHomeFollowingEmptyStateView" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingEmptyStateView.swift`는 파일 없음으로 실패했고, `rg "MainPlaceholderTabView" SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift`는 placeholder 잔존을 확인했으며, `rg "MainHomeFollowingNewsSection\(" SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift`는 결과가 없어 최근 소식 미조립 상태를 확인했다.
|
||||
- 2026-07-01: Phase 11 구현을 진행해 `MainHomeFollowingEmptyStateView`를 추가하고, `MainHomeFollowingView`에서 `isLoading`, `isLoginRequired`, `message`, `response` 상태를 분기하도록 조립했다. content 상태에서는 팔로잉 크리에이터, On Air, 최근 대화, 이달의 스케줄, 최근 소식 순서로 렌더링하고, `MainPlaceholderTabView`를 제거했다.
|
||||
- 2026-07-01: `MainHomeFollowingView`에 `onTapContent` callback을 추가하고 `MainHomeView`에서 기존 `onTapContent`를 전달해 최근 소식의 콘텐츠 랭킹/오디오/화보 tap이 기존 `MainView.handleRecommendationContentTap(contentId:)` 경로를 재사용하도록 연결했다. `COMMUNITY_POST`는 기존 Task 9.2 제약에 따라 상세 진입을 연결하지 않았다.
|
||||
- 2026-07-01: `SodaLive.xcodeproj/project.pbxproj`에 `MainHomeFollowingEmptyStateView.swift` 파일 참조와 Sources build file을 추가해 Xcode navigator와 target compile sources에서 확인 가능하도록 등록했다.
|
||||
- 2026-07-01: `rg "struct MainHomeFollowingEmptyStateView|message|loginButtonTitle|Button|Color.black|frame\(maxWidth: \.infinity|SodaRadius\.r14" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingEmptyStateView.swift`로 empty/login required view 요구사항을 확인했다.
|
||||
- 2026-07-01: `rg "MainHomeFollowingViewModel|fetchFollowing|ProgressView|MainHomeFollowingCreatorSection|MainHomeFollowingLiveSection|MainHomeFollowingChatSection|MainHomeFollowingScheduleSection|MainHomeFollowingNewsSection|MainHomeFollowingEmptyStateView|emptyStateMessage|loadFailedMessage|MainPlaceholderTabView" SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift` 및 `if rg "MainPlaceholderTabView" SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift; then exit 1; else true; fi`로 상태 분기/섹션 조립과 placeholder 제거를 확인했다.
|
||||
- 2026-07-01: `rg "onTapContent|MainHomeFollowingView\(|handleRecommendationContentTap|onTapFollowingAll|onTapChatRoom|onTapFollowingSchedule" SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/V2/Main/MainView.swift`로 팔로잉 탭 상세 진입 callback이 기존 route에 연결됨을 확인했다.
|
||||
- 2026-07-01: 최초 Phase 11 빌드는 `MainHomeFollowingEmptyStateView.swift`의 존재하지 않는 `SodaRadius.r20` 사용으로 실패했다. `SodaLive/Sources/UI/Theme/Radius.swift`에서 `r4`, `r8`, `r14`만 존재함을 확인하고 `SodaRadius.r14`로 수정했다.
|
||||
- 2026-07-01: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug -derivedDataPath "/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/SodaLiveDerivedDataPhase11" build`를 재실행해 Phase 11 빌드 성공을 확인했다. 기존 duplicate build file/run script/AppIntents/dependency scan warning은 남아 있으나 컴파일 오류는 없었다.
|
||||
- 2026-07-01: Phase 11 리뷰에서 `isLoginRequired` 상태의 로그인 버튼 액션 미연결과 최근 소식 payload mismatch 시 제목만 남을 수 있는 문제를 지적받았다. `MainHomeFollowingView -> MainHomeView -> MainView.handleFollowingLoginTap()` callback을 추가해 로그인 필요 상태의 `MainHomeFollowingEmptyStateView`에 `loginAction`을 전달하고 `.login` route로 연결했다.
|
||||
- 2026-07-01: `MainHomeFollowingNewsSection`에 `renderableNews`를 추가해 `.unknown` 및 payload가 없는 news item을 섹션 렌더링 전에 제외했다. `if !renderableNews.isEmpty`와 `ForEach(renderableNews)`를 사용해 실제 카드가 없는 경우 최근 소식 제목만 표시되지 않도록 수정했다.
|
||||
- 2026-07-01: `rg "onTapLogin|onTapFollowingLogin|handleFollowingLoginTap|loginAction: onTapLogin|setAppStep\(step: \.login\)" SodaLive/Sources/V2/Main/Home/Following/MainHomeFollowingView.swift SodaLive/Sources/V2/Main/Home/MainHomeView.swift SodaLive/Sources/V2/Main/MainView.swift`로 로그인 필요 상태의 버튼 action과 `.login` route 연결을 확인했다.
|
||||
- 2026-07-01: `rg "renderableNews|if !renderableNews\.isEmpty|ForEach\(renderableNews\)|case \.unknown|return false" SodaLive/Sources/V2/Main/Home/Following/Components/MainHomeFollowingNewsSection.swift`로 최근 소식 섹션의 렌더 가능 item 필터링과 unknown 숨김을 확인했다.
|
||||
- 2026-07-01: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug -derivedDataPath "/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/SodaLiveDerivedDataPhase11" build`를 재실행해 Phase 11 리뷰 수정 후 빌드 성공을 확인했다. 기존 duplicate build file/run script/Crashlytics dSYM/AppIntents/dependency scan warning은 남아 있으나 컴파일 오류는 없었다.
|
||||
|
||||
Reference in New Issue
Block a user