crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

NewGameSheet.swift (4160B)


      1 import SwiftUI
      2 
      3 struct NewGameSheet: View {
      4     let store: GameStore
      5     var onCreated: (UUID) -> Void = { _ in }
      6 
      7     @Environment(\.dismiss) private var dismiss
      8     @Environment(NYTAuthService.self) private var nytAuth
      9     @AppStorage("lastPuzzleSource") private var storedSourceRaw = PuzzleSource.bundles.rawValue
     10     @AppStorage("debugMode") private var debugMode = false
     11     @State private var selection: PuzzleSource = .bundles
     12     @State private var duplicateSource: String?
     13     @State private var createError: String?
     14 
     15     private var availableSources: [PuzzleSource] {
     16         var sources: [PuzzleSource] = [.bundles]
     17         if debugMode {
     18             sources.append(.debug)
     19         }
     20         sources.append(.imported)
     21         if nytAuth.canAttemptNYTFetch {
     22             sources.append(.nyt)
     23         }
     24         return sources
     25     }
     26 
     27     var body: some View {
     28         NavigationStack {
     29             VStack(spacing: 0) {
     30                 Picker("Source", selection: $selection) {
     31                     ForEach(availableSources) { source in
     32                         Text(source.title).tag(source)
     33                     }
     34                 }
     35                 .pickerStyle(.segmented)
     36                 .padding()
     37 
     38                 Group {
     39                     switch selection {
     40                     case .bundles:
     41                         BundledBrowseView(onSelected: handleSelected)
     42                     case .debug:
     43                         DebugBrowseView(onSelected: handleSelected)
     44                     case .imported:
     45                         ImportedBrowseView(onSelected: handleSelected)
     46                     case .nyt:
     47                         NYTBrowseView(
     48                             onSelected: handleSelected,
     49                             excludedDates: store.nytPuzzleDatesInLibrary()
     50                         )
     51                     }
     52                 }
     53             }
     54             .navigationTitle("New Puzzle")
     55             .navigationBarTitleDisplayMode(.inline)
     56             .toolbar {
     57                 ToolbarItem(placement: .cancellationAction) {
     58                     Button {
     59                         dismiss()
     60                     } label: {
     61                         Image(systemName: "xmark")
     62                     }
     63                     .accessibilityLabel("Cancel")
     64                 }
     65             }
     66         }
     67         .onAppear {
     68             // Fall back to .bundles if the stored raw value is missing or no
     69             // longer maps to a case (e.g. the renamed "bundled" -> "bundles").
     70             let stored = PuzzleSource(rawValue: storedSourceRaw) ?? .bundles
     71             selection = availableSources.contains(stored) ? stored : .bundles
     72         }
     73         .onChange(of: selection) { _, newValue in
     74             storedSourceRaw = newValue.rawValue
     75         }
     76         .alert(
     77             "Puzzle Already in Library",
     78             isPresented: .init(
     79                 get: { duplicateSource != nil },
     80                 set: { if !$0 { duplicateSource = nil } }
     81             ),
     82             presenting: duplicateSource
     83         ) { source in
     84             Button("Create Copy") {
     85                 create(from: source)
     86             }
     87             Button("Cancel", role: .cancel) {}
     88         } message: { _ in
     89             Text("You already have a game for this puzzle. Cancel and resume it from the library, or create a new copy.")
     90         }
     91         .alert(
     92             "Couldn't Create Game",
     93             isPresented: .init(
     94                 get: { createError != nil },
     95                 set: { if !$0 { createError = nil } }
     96             ),
     97             presenting: createError
     98         ) { _ in
     99             Button("OK", role: .cancel) {}
    100         } message: { message in
    101             Text(message)
    102         }
    103     }
    104 
    105     private func handleSelected(_ source: String) {
    106         if store.findGameID(matching: source) != nil {
    107             duplicateSource = source
    108         } else {
    109             create(from: source)
    110         }
    111     }
    112 
    113     private func create(from source: String) {
    114         do {
    115             let gameID = try store.createGame(from: source)
    116             dismiss()
    117             onCreated(gameID)
    118         } catch {
    119             createError = error.localizedDescription
    120         }
    121     }
    122 }