feat(home): 팔로잉 탭 화면을 조립한다

This commit is contained in:
Yu Sung
2026-07-01 17:36:07 +09:00
parent 5b679389a6
commit f93e65372b
7 changed files with 175 additions and 35 deletions

View File

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

View File

@@ -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 {

View File

@@ -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 {