import SwiftUI struct CreatorChannelAudioTabView: View { let creatorId: Int let isOwnCreatorChannel: Bool let onTapContent: (Int) -> Void @StateObject private var viewModel = CreatorChannelAudioViewModel() @State private var isSortPopupPresented = false 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) { if isEmptyState { emptyStateView } else { ZStack(alignment: .topTrailing) { VStack(spacing: 0) { CapsuleTabBar( items: viewModel.displayThemes, selectedItem: Binding( get: { viewModel.selectedTheme }, set: { viewModel.selectTheme($0, creatorId: creatorId) } ), title: { $0.themeName } ) CreatorChannelSortBar( selectedSort: viewModel.selectedSort, totalCount: viewModel.response?.audioContentCount, onTapSort: { isSortPopupPresented.toggle() } ) LazyVStack(spacing: 0) { if shouldShowOwnershipRate, let response = viewModel.response { CreatorChannelAudioOwnershipRateView( purchasedAudioContentRate: response.purchasedAudioContentRate, purchasedAudioContentCount: response.purchasedAudioContentCount, paidAudioContentCount: response.paidAudioContentCount ) .padding(.bottom, SodaSpacing.s8) } ForEach(viewModel.audioContents) { audioContent in AudioContentListRow( item: AudioContentListRowItem( audioContent: audioContent, subtitle: audioContent.duration ), style: .purchaseState ) { onTapContent(audioContent.audioContentId) } .onAppear { viewModel.fetchNextPageIfNeeded(creatorId: creatorId, currentItem: audioContent) } } if viewModel.isLoadingNextPage { ProgressView() .padding(.vertical, SodaSpacing.s20) } } .padding(.bottom, isOwnCreatorChannel ? 70 : SodaSpacing.s20) } if isSortPopupPresented { Color.black .opacity(0.001) .frame(maxWidth: .infinity, maxHeight: .infinity) .onTapGesture { isSortPopupPresented = false } CreatorChannelSortContextPopup(selectedSort: viewModel.selectedSort) { sort in viewModel.selectSort(sort, creatorId: creatorId) isSortPopupPresented = false } .padding(.top, 104) .padding(.trailing, SodaSpacing.s14) .zIndex(1) } } .frame(maxWidth: .infinity, alignment: .topTrailing) .onChange(of: viewModel.selectedSort) { _ in isSortPopupPresented = false } .onChange(of: viewModel.selectedTheme) { _ in isSortPopupPresented = false } .onDisappear { isSortPopupPresented = false } } } } private var shouldShowOwnershipRate: Bool { guard isOwnCreatorChannel == false else { return false } return viewModel.isAllThemeSelected } private var isEmptyState: Bool { guard viewModel.hasLoaded, let response = viewModel.response else { return false } return response.audioContentCount == 0 } private var emptyStateView: some View { Text(I18n.CreatorChannelAudio.emptyMessage) .appFont(.body3) .foregroundColor(Color.gray500) .multilineTextAlignment(.center) .frame(maxWidth: .infinity) .padding(.vertical, 48) } } struct CreatorChannelAudioTabView_Previews: PreviewProvider { static var previews: some View { CreatorChannelAudioTabView( creatorId: 1, isOwnCreatorChannel: false, onTapContent: { _ in } ) .background(Color.black) .previewLayout(.sizeThatFits) } }