import ManaCore import SwiftUI /// Explore-Tab: Featured + Trending Sections, plus Browse-Link. struct ExploreView: View { @Binding var deepLinkSlug: String? @Environment(AuthClient.self) private var auth @State private var store: MarketplaceStore? @State private var path: [MarketplaceRoute] = [] init(deepLinkSlug: Binding = .constant(nil)) { _deepLinkSlug = deepLinkSlug } var body: some View { NavigationStack(path: $path) { ZStack { CardsTheme.background.ignoresSafeArea() content } .navigationTitle("Entdecken") .navigationDestination(for: MarketplaceRoute.self) { route in switch route { case .browse: BrowseView() case let .publicDeck(slug): PublicDeckView(slug: slug) } } .navigationDestination(for: String.self) { deckId in DeckDetailView(deckId: deckId) } .refreshable { await store?.loadExplore() } .task { if store == nil { store = MarketplaceStore(auth: auth) } await store?.loadExplore() } .onChange(of: deepLinkSlug) { _, newSlug in guard let slug = newSlug else { return } path = [.publicDeck(slug: slug)] deepLinkSlug = nil } } } @ViewBuilder private var content: some View { if let store { if store.isLoadingExplore, store.featured.isEmpty, store.trending.isEmpty { ProgressView() .tint(CardsTheme.primary) } else if let message = store.errorMessage, store.featured.isEmpty { ContentUnavailableView( "Marketplace nicht erreichbar", systemImage: "wifi.exclamationmark", description: Text(message) ) .foregroundStyle(CardsTheme.foreground) } else { ScrollView { VStack(alignment: .leading, spacing: 24) { if !store.featured.isEmpty { section(title: "Vorgestellt", icon: "star.fill", items: store.featured) } if !store.trending.isEmpty { section(title: "Im Trend", icon: "flame.fill", items: store.trending) } NavigationLink(value: MarketplaceRoute.browse) { HStack { Label("Alle Decks durchsuchen", systemImage: "magnifyingglass") Spacer() Image(systemName: "chevron.right") .font(.footnote) } .padding() .background(CardsTheme.surface, in: RoundedRectangle(cornerRadius: 10)) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(CardsTheme.border, lineWidth: 1) ) .foregroundStyle(CardsTheme.foreground) } .buttonStyle(.plain) .padding(.horizontal, 20) } .padding(.vertical, 12) } } } } private func section(title: String, icon: String, items: [PublicDeckEntry]) -> some View { VStack(alignment: .leading, spacing: 12) { HStack(spacing: 6) { Image(systemName: icon) .foregroundStyle(CardsTheme.primary) Text(title) .font(.title3.weight(.semibold)) .foregroundStyle(CardsTheme.foreground) Text("\(items.count)") .font(.subheadline) .foregroundStyle(CardsTheme.mutedForeground) } .padding(.horizontal, 20) ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .top, spacing: 16) { ForEach(items) { item in NavigationLink(value: MarketplaceRoute.publicDeck(slug: item.slug)) { PublicDeckCard(entry: item) .frame(width: 240) .scrollTransition(.animated) { content, phase in content .scaleEffect(phase.isIdentity ? 1 : 0.92) .opacity(phase.isIdentity ? 1 : 0.7) } } .buttonStyle(.plain) } } .padding(.horizontal, 20) .padding(.bottom, 12) .scrollTargetLayout() } .scrollTargetBehavior(.viewAligned) } } } /// Routen-Enum für die Marketplace-NavigationStack. enum MarketplaceRoute: Hashable { case browse case publicDeck(slug: String) } /// Tile für Marketplace-Decks im Explore-Tab. Nutzt `DeckCoverTile` /// als Basis (selber Look + Größe wie `DeckStackTile` auf der Decks- /// Seite). Footer: Karten-Count, Star-Count, Credits, Owner-Badge. struct PublicDeckCard: View { let entry: PublicDeckEntry var body: some View { DeckCoverTile( title: entry.title, description: entry.description, category: parsedCategory, seed: entry.slug, colorAccentHex: nil, isFeatured: entry.isFeatured ) { footerContent } } private var parsedCategory: DeckCategory? { guard let category = entry.category else { return nil } return DeckCategory(rawValue: category) } private var footerContent: some View { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 8) { Label("\(entry.cardCount)", systemImage: "rectangle.stack") .font(.caption2) .foregroundStyle(CardsTheme.mutedForeground) Label("\(entry.starCount)", systemImage: "star.fill") .font(.caption2) .foregroundStyle(CardsTheme.warning) if entry.isPaid { Label("\(entry.priceCredits)", systemImage: "creditcard") .font(.caption2.weight(.semibold)) .foregroundStyle(CardsTheme.primary) } Spacer() } HStack(spacing: 4) { Text(entry.owner.displayName) .font(.caption2) .foregroundStyle(CardsTheme.mutedForeground) .lineLimit(1) if entry.owner.verifiedMana { Image(systemName: "checkmark.seal.fill") .font(.caption2) .foregroundStyle(CardsTheme.primary) } } } } }