import SwiftUI struct MainContentRankingView: View { let onTapContent: (Int) -> Void @StateObject private var viewModel = MainContentRankingViewModel() init(onTapContent: @escaping (Int) -> Void = { _ in }) { self.onTapContent = onTapContent } var body: some View { ZStack { Color.black .ignoresSafeArea() VStack(spacing: 0) { MainContentRankingTypeTabBar( items: AudioRankingType.allCases, selectedItem: selectedTypeBinding, title: { $0.title } ) if viewModel.isLoading && !viewModel.hasLoaded { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .frame(maxWidth: .infinity, maxHeight: .infinity) } else if viewModel.shouldShowEmptyState { MainContentAudioRankingEmptyStateView(message: viewModel.emptyStateMessage) .frame(maxWidth: .infinity, maxHeight: .infinity) } else { rankingContent } } } .onAppear { if !viewModel.hasLoaded { viewModel.fetchRankings() } } .sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 2) } private var rankingContent: some View { ScrollView(showsIndicators: false) { VStack(alignment: .leading, spacing: SodaSpacing.s8) { if let topItem { MainContentAudioRankingTopCard( item: topItem, showRankChange: viewModel.showRankChange, onTapContent: onTapContent ) } if !secondToSeventhRankItems.isEmpty { LazyVGrid(columns: twoColumns, alignment: .center, spacing: SodaSpacing.s8) { ForEach(secondToSeventhRankItems) { item in MainContentAudioRankingThumbnailCard( item: item, showRankChange: viewModel.showRankChange, columnsPerRow: 2, onTapContent: onTapContent ) } } } if !eighthToTenthRankItems.isEmpty { LazyVGrid(columns: threeColumns, alignment: .center, spacing: SodaSpacing.s8) { ForEach(eighthToTenthRankItems) { item in MainContentAudioRankingThumbnailCard( item: item, showRankChange: viewModel.showRankChange, columnsPerRow: 3, onTapContent: onTapContent ) } } } if !rowRankItems.isEmpty { VStack(spacing: SodaSpacing.s8) { ForEach(rowRankItems) { item in MainContentAudioRankingRow( item: item, showRankChange: viewModel.showRankChange, onTapContent: onTapContent ) } } } } .padding(.horizontal, SodaSpacing.s8) .padding(.vertical, SodaSpacing.s20) } } private var twoColumns: [GridItem] { [GridItem(.flexible()), GridItem(.flexible())] } private var threeColumns: [GridItem] { [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())] } private var selectedTypeBinding: Binding { Binding( get: { viewModel.selectedType }, set: { viewModel.selectType($0) } ) } private var topItem: AudioRankingItemResponse? { viewModel.items.first { $0.rank == 1 } } private var secondToSeventhRankItems: [AudioRankingItemResponse] { viewModel.items.filter { 2...7 ~= $0.rank } } private var eighthToTenthRankItems: [AudioRankingItemResponse] { viewModel.items.filter { 8...10 ~= $0.rank } } private var rowRankItems: [AudioRankingItemResponse] { viewModel.items.filter { 11...20 ~= $0.rank } } } private struct MainContentRankingTypeTabBar: View { let items: [AudioRankingType] @Binding var selectedItem: AudioRankingType let title: (AudioRankingType) -> String var body: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(alignment: .center, spacing: SodaSpacing.s8) { ForEach(items, id: \.self) { item in MainContentRankingTypeTabBarItem( title: title(item), isSelected: selectedItem == item, action: { selectedItem = item } ) } } .padding(.horizontal, SodaSpacing.s14) } .frame(maxWidth: .infinity) .frame(height: 52, alignment: .center) .background(Color.black) } } private struct MainContentRankingTypeTabBarItem: View { let title: String let isSelected: Bool let action: () -> Void var body: some View { Button { action() } label: { Text(title) .appFont(size: 14, weight: .medium) .foregroundColor(isSelected ? Color.black : Color.white) .lineLimit(1) .padding(.horizontal, SodaSpacing.s12) .padding(.vertical, SodaSpacing.s8) .frame(height: 34) .background(isSelected ? Color.white : Color.black) .clipShape(Capsule()) .overlay( Capsule() .stroke(isSelected ? Color.white : Color.gray700, lineWidth: 1) ) } .buttonStyle(.plain) } }