import SwiftUI struct BrowseScreen: View { @EnvironmentObject private var app: AppModel let kind: String // "MOVIE" or "SERIES" let title: String @State private var home: HomePayload? @State private var shelfPrefs: [ShelfPref] = [] @State private var mode: BrowseMode = .planks @State private var items: [MediaItem] = [] @State private var total = 0 @State private var error: String? @State private var loading = true @State private var loadingMore = false private enum BrowseMode: Hashable { case planks case catalog(String) } private var kindShelves: [ShelfRow] { guard let home else { return [] } return home.shelves.filter { shelf in let k = (shelf.kind ?? "").uppercased() if kind == "MOVIE" { return k == "MOVIE" || k.contains("MOVIE") } return k == "SERIES" || k.contains("SERIES") } } private var chips: [(label: String, mode: BrowseMode)] { var list: [(String, BrowseMode)] = [("Planken", .planks)] if kind == "MOVIE" { list += [("Alles", .catalog("movies")), ("Recent", .catalog("recent-movies")), ("4K", .catalog("4k-movies"))] } else { list += [("Alles", .catalog("series")), ("Recent", .catalog("recent-series"))] } for pref in shelfPrefs where pref.visible { let k = (pref.kind ?? "").uppercased() let match = kind == "MOVIE" ? (k == "MOVIE" || k.contains("MOVIE")) : (k == "SERIES" || k.contains("SERIES")) if match { let slug = pref.slug?.isEmpty == false ? pref.slug! : pref.shelfId list.append((pref.name, .catalog(slug))) } } return list } private let cols = [GridItem(.adaptive(minimum: 104), spacing: 12)] var body: some View { VStack(spacing: 0) { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(Array(chips.enumerated()), id: \.offset) { _, chip in Button { mode = chip.mode Task { await activate(chip.mode) } } label: { Text(chip.label) .font(McFonts.caption) .foregroundStyle(mode == chip.mode ? .white : McColors.textDim) .padding(.horizontal, 14) .padding(.vertical, 8) .background(mode == chip.mode ? McColors.accent : McColors.surface, in: Capsule()) } .buttonStyle(.plain) } } .padding(.horizontal, 16) .padding(.vertical, 10) } content } .background(McColors.bg.ignoresSafeArea()) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { BrandHeader(subtitle: title.uppercased()) } } .toolbarBackground(McColors.bg, for: .navigationBar) .toolbarColorScheme(.dark, for: .navigationBar) .task { await bootstrap() } .refreshable { await bootstrap() } } @ViewBuilder private var content: some View { if loading && home == nil && items.isEmpty { ProgressView().tint(.white).frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error, home == nil && items.isEmpty { VStack(spacing: 12) { Text(error).foregroundStyle(McColors.danger) Button("Opnieuw") { Task { await bootstrap() } }.buttonStyle(McPrimaryButtonStyle()) } } else { switch mode { case .planks: ScrollView { VStack(alignment: .leading, spacing: 22) { if kindShelves.isEmpty { ContentUnavailableView("Geen planken", systemImage: "rectangle.stack", description: Text("Nog geen \(title.lowercased())-planken zichtbaar.")) .frame(maxWidth: .infinity) .padding(.top, 40) } else { ForEach(kindShelves) { shelf in if !shelf.items.isEmpty { ShelfSection(title: shelf.title, items: shelf.items) } } } } .padding(.bottom, 24) } case .catalog: ScrollView { LazyVGrid(columns: cols, spacing: 14) { ForEach(items) { item in NavigationLink(value: item) { PosterCard(item: item, width: 104) } .buttonStyle(.plain) .onAppear { if item.id == items.last?.id { Task { await loadMore() } } } } } .padding(.horizontal, 16) .padding(.bottom, 24) if loadingMore { ProgressView().tint(.white).padding() } } .navigationDestination(for: MediaItem.self) { MediaRouter(item: $0) } } } } private func bootstrap() async { loading = true error = nil do { async let homeTask = app.api.home() async let prefsTask = app.api.getShelfPrefs() home = try await homeTask shelfPrefs = (try? await prefsTask)?.sorted(by: { $0.sortOrder < $1.sortOrder }) ?? [] if case .catalog(let id) = mode { await loadCatalog(id, reset: true) } loading = false } catch { self.error = error.localizedDescription loading = false } } private func activate(_ mode: BrowseMode) async { switch mode { case .planks: break case .catalog(let id): await loadCatalog(id, reset: true) } } private func loadCatalog(_ id: String, reset: Bool) async { if reset { items = [] loading = true } do { let page = try await app.api.browse(catalogId: id, skip: 0, take: 60) total = page.total ?? page.items.count items = page.items loading = false } catch { self.error = error.localizedDescription loading = false } } private func loadMore() async { guard case .catalog(let id) = mode, !loadingMore, items.count < total else { return } loadingMore = true do { let page = try await app.api.browse(catalogId: id, skip: items.count, take: 60) items.append(contentsOf: page.items) loadingMore = false } catch { loadingMore = false } } }