feat(creator): 후원 탭을 연결한다

This commit is contained in:
Yu Sung
2026-07-04 22:45:07 +09:00
parent 2063603f32
commit 7c98af0c7d
4 changed files with 213 additions and 1 deletions

View File

@@ -7,6 +7,7 @@ struct CreatorChannelView: View {
@StateObject private var viewModel = CreatorChannelViewModel()
@StateObject private var channelDonationViewModel = ChannelDonationViewModel()
@StateObject private var donationViewModel = CreatorChannelDonationViewModel()
@StateObject private var liveViewModel = LiveViewModel()
@StateObject private var mypageViewModel = MyPageViewModel()
@AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token)
@@ -113,6 +114,13 @@ struct CreatorChannelView: View {
.ignoresSafeArea(.container, edges: .bottom)
}
if viewModel.selectedTab == .donation && shouldShowDonationFloatingButton && !viewModel.isLoading {
CreatorChannelDonationFloatingButton(action: showDonationDialog)
.ignoresSafeArea(.container, edges: .bottom)
.padding(.bottom, SodaSpacing.s14)
.padding(.trailing, SodaSpacing.s14)
}
if isShowAuthConfirmView {
authConfirmDialog
}
@@ -133,6 +141,7 @@ struct CreatorChannelView: View {
reloadAfterSuccess: false,
onSuccess: {
viewModel.fetchHome(creatorId: creatorId)
donationViewModel.fetchFirstPage(creatorId: creatorId)
}
)
}
@@ -207,6 +216,10 @@ struct CreatorChannelView: View {
}
}
private var shouldShowDonationFloatingButton: Bool {
isOwnCreatorChannel == false && (donationViewModel.donationCount != 0 || donationViewModel.rankings.isEmpty == false)
}
@ViewBuilder
private var selectedTabContent: some View {
if viewModel.selectedTab == .live {
@@ -228,6 +241,14 @@ struct CreatorChannelView: View {
isOwnCreatorChannel: isOwnCreatorChannel,
onTapSeries: showSeriesDetail
)
} else if viewModel.selectedTab == .donation {
CreatorChannelDonationTabView(
creatorId: creatorId,
isOwnCreatorChannel: isOwnCreatorChannel,
viewModel: donationViewModel,
onTapViewAll: showDonationRankingAll,
onTapDonate: showDonationDialog
)
} else if viewModel.selectedTab != .home {
CreatorChannelPlaceholderTabView(title: viewModel.selectedTab.title)
} else if let response = viewModel.response {
@@ -337,6 +358,10 @@ struct CreatorChannelView: View {
AppState.shared.setAppStep(step: .seriesDetail(seriesId: seriesId))
}
private func showDonationRankingAll() {
AppState.shared.setAppStep(step: .userProfileDonationAll(userId: creatorId))
}
private func showCommunity(_ creatorId: Int) {
guard creatorId > 0 else { return }
AppState.shared.setAppStep(step: .creatorCommunityAll(creatorId: creatorId))

View File

@@ -43,7 +43,6 @@ final class CreatorChannelViewModel: ObservableObject {
if let data = decoded.data, decoded.success {
self.response = data
self.selectedTab = .home
self.isApiFailedPlaceholderVisible = false
} else {
if let message = decoded.message {

View File

@@ -0,0 +1,76 @@
import SwiftUI
struct CreatorChannelDonationTabView: View {
let creatorId: Int
let isOwnCreatorChannel: Bool
@ObservedObject var viewModel: CreatorChannelDonationViewModel
let onTapViewAll: () -> Void
let onTapDonate: () -> Void
var body: some View {
content
.background(Color.black)
.onAppear {
if viewModel.hasLoaded == false {
viewModel.fetchFirstPage(creatorId: creatorId)
}
}
.sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 1)
}
private var content: some View {
VStack(spacing: 0) {
CreatorChannelDonationCountBar(donationCount: viewModel.donationCount)
if isEmptyState {
CreatorChannelDonationEmptyView(
showsDonateButton: isOwnCreatorChannel == false,
onTapDonate: onTapDonate
)
} else {
CreatorChannelDonationRankingSection(
rankings: viewModel.rankings,
onTapViewAll: onTapViewAll
)
LazyVStack(spacing: SodaSpacing.s8) {
ForEach(viewModel.donations.indices, id: \.self) { index in
CreatorChannelDonationCard(donation: viewModel.donations[index])
.padding(.horizontal, SodaSpacing.s14)
.onAppear {
viewModel.fetchNextPageIfNeeded(
creatorId: creatorId,
currentItem: viewModel.donations[index]
)
}
}
if viewModel.isLoadingNextPage {
ProgressView()
.padding(.vertical, SodaSpacing.s20)
}
}
.padding(.bottom, SodaSpacing.s20)
}
}
}
private var isEmptyState: Bool {
guard viewModel.hasLoaded else { return false }
return viewModel.rankings.isEmpty && viewModel.donationCount == 0
}
}
struct CreatorChannelDonationTabView_Previews: PreviewProvider {
static var previews: some View {
CreatorChannelDonationTabView(
creatorId: 1,
isOwnCreatorChannel: false,
viewModel: CreatorChannelDonationViewModel(),
onTapViewAll: {},
onTapDonate: {}
)
.background(Color.black)
.previewLayout(.sizeThatFits)
}
}