import SwiftUI import SwiftData struct ContentView: View { @Environment(\.modelContext) private var modelContext @State private var viewModel = GameViewModel() @State private var themeManager = ThemeManager() @State private var cardFaceStyle: CardFaceStyle = .classic @State private var cardBackDesign: CardBackDesign = .blue @State private var showingRules = false @State private var showingNewGame = false @State private var showingSettings = false @State private var showingStats = false @State private var hasLoaded = false var body: some View { NavigationStack { GameBoardView( viewModel: viewModel, theme: themeManager.currentTheme, cardFaceStyle: cardFaceStyle, cardBackDesign: cardBackDesign ) .toolbar { #if os(iOS) ToolbarItemGroup(placement: .bottomBar) { iOSToolbar } #else ToolbarItemGroup(placement: .primaryAction) { macToolbar } #endif } .navigationTitle(viewModel.variant.displayName) #if os(iOS) .navigationBarTitleDisplayMode(.inline) .toolbarBackground(.visible, for: .bottomBar) .toolbarBackground(.ultraThinMaterial, for: .bottomBar) .toolbarColorScheme(.dark, for: .bottomBar) #endif .tint(.white) .sheet(isPresented: $showingRules) { RulesView(variant: viewModel.variant) } .sheet(isPresented: $showingNewGame) { NewGameSheet( variant: viewModel.variant, difficulty: viewModel.difficulty ) { newVariant, newDifficulty in showingNewGame = false viewModel.difficulty = newDifficulty viewModel.changeVariant(to: newVariant) } } .sheet(isPresented: $showingSettings) { SettingsView( theme: Binding( get: { themeManager.currentTheme }, set: { themeManager.applyTheme($0) } ), cardFaceStyle: $cardFaceStyle, cardBackDesign: $cardBackDesign, soundEnabled: Binding( get: { viewModel.isSoundEnabled }, set: { viewModel.isSoundEnabled = $0 } ) ) } .sheet(isPresented: $showingStats) { StatisticsView() } } .onAppear { guard !hasLoaded else { return } hasLoaded = true let pm = PersistenceManager(modelContext: modelContext) viewModel.persistenceManager = pm let prefs = pm.loadPreferences() if let theme = GameTheme.allThemes.first(where: { $0.id == prefs.themeId }) { themeManager.applyTheme(theme) } if let style = CardFaceStyle(rawValue: prefs.cardFaceStyle) { cardFaceStyle = style } if let back = CardBackDesign(rawValue: prefs.cardBackDesign) { cardBackDesign = back } viewModel.isSoundEnabled = prefs.soundEnabled if let savedGame = pm.loadMostRecentGame() { viewModel.resumeGame(from: savedGame) } else { if let savedVariant = GameVariant(rawValue: prefs.lastVariant) { viewModel.changeVariant(to: savedVariant) } else { viewModel.newGame() } } } .onChange(of: cardFaceStyle) { savePreferences() } .onChange(of: cardBackDesign) { savePreferences() } .onChange(of: themeManager.currentTheme) { savePreferences() } .onChange(of: viewModel.isSoundEnabled) { savePreferences() } #if os(iOS) .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in viewModel.saveGameNow() savePreferences() } #endif #if os(macOS) .frame(minWidth: 800, minHeight: 600) #endif } private func savePreferences() { viewModel.persistenceManager?.savePreferences( themeId: themeManager.currentTheme.id, cardFaceStyle: cardFaceStyle, cardBackDesign: cardBackDesign, soundEnabled: viewModel.isSoundEnabled, lastVariant: viewModel.variant, lastDifficulty: viewModel.difficulty ) } // MARK: - iOS Toolbar (bottom bar — 5 items max, with labels) @ViewBuilder private var iOSToolbar: some View { Button { showingNewGame = true } label: { VStack(spacing: 2) { Image(systemName: "plus.circle.fill") .font(.title3) Text("New") .font(.caption2) } } .keyboardShortcut("n", modifiers: .command) Spacer() Button { viewModel.undo() } label: { VStack(spacing: 2) { Image(systemName: "arrow.uturn.backward.circle.fill") .font(.title3) Text("Undo") .font(.caption2) } } .keyboardShortcut("z", modifiers: .command) .disabled(!viewModel.canUndo) Spacer() Button { viewModel.requestHint() } label: { VStack(spacing: 2) { Image(systemName: "lightbulb.fill") .font(.title3) Text("Hint") .font(.caption2) } } .keyboardShortcut("h", modifiers: []) Spacer() // "More" menu — consolidates secondary actions Menu { // Game variants Menu("Game Variant") { ForEach(GameVariant.allCases) { variant in Button { viewModel.changeVariant(to: variant) } label: { HStack { Text(variant.displayName) if variant == viewModel.variant { Image(systemName: "checkmark") } } } } } // Difficulty Menu("Difficulty") { ForEach(Difficulty.allCases) { diff in Button { viewModel.difficulty = diff viewModel.newGame() } label: { HStack { Text(diff.displayName) if diff == viewModel.difficulty { Image(systemName: "checkmark") } } } } } Divider() Button { viewModel.autoComplete() } label: { Label("Auto-Complete", systemImage: "wand.and.stars") } Button { viewModel.isSoundEnabled.toggle() } label: { Label(viewModel.isSoundEnabled ? "Sound On" : "Sound Off", systemImage: viewModel.isSoundEnabled ? "speaker.wave.2" : "speaker.slash") } Divider() Button { showingRules = true } label: { Label("Rules", systemImage: "book") } Button { showingStats = true } label: { Label("Statistics", systemImage: "chart.bar") } Button { showingSettings = true } label: { Label("Settings", systemImage: "gearshape") } } label: { VStack(spacing: 2) { Image(systemName: "ellipsis.circle.fill") .font(.title3) Text("More") .font(.caption2) } } } // MARK: - macOS Toolbar (all items fit, no labels needed) @ViewBuilder private var macToolbar: some View { Menu { ForEach(GameVariant.allCases) { variant in Button(variant.displayName) { viewModel.changeVariant(to: variant) } } Divider() ForEach(Difficulty.allCases) { diff in Button { viewModel.difficulty = diff viewModel.newGame() } label: { HStack { Text(diff.displayName) if diff == viewModel.difficulty { Image(systemName: "checkmark") } } } } } label: { Label("Game", systemImage: "suit.spade.fill") } Button { showingNewGame = true } label: { Label("New Game", systemImage: "plus.circle") } .keyboardShortcut("n", modifiers: .command) Button { viewModel.undo() } label: { Label("Undo", systemImage: "arrow.uturn.backward") } .keyboardShortcut("z", modifiers: .command) .disabled(!viewModel.canUndo) Button { viewModel.requestHint() } label: { Label("Hint", systemImage: "lightbulb") } .keyboardShortcut("h", modifiers: []) Button { viewModel.autoComplete() } label: { Label("Auto", systemImage: "wand.and.stars") } Button { showingRules = true } label: { Label("Rules", systemImage: "book") } Button { showingStats = true } label: { Label("Stats", systemImage: "chart.bar") } Button { showingSettings = true } label: { Label("Settings", systemImage: "gearshape") } Button { viewModel.isSoundEnabled.toggle() } label: { Label("Sound", systemImage: viewModel.isSoundEnabled ? "speaker.wave.2" : "speaker.slash") } } }