BundledBrowseView.swift (2710B)
1 import SwiftUI 2 3 struct BundledBrowseView: View { 4 let onSelected: (String) -> Void 5 6 @AppStorage("debugMode") private var debugMode = false 7 8 private var bundles: [PuzzleCatalog.PuzzleBundle] { 9 PuzzleCatalog.bundles(includeDebug: debugMode) 10 } 11 12 var body: some View { 13 List(bundles) { bundle in 14 NavigationLink { 15 PuzzleListView(puzzles: bundle.puzzles, onSelected: onSelected) 16 .navigationTitle(bundle.name) 17 .navigationBarTitleDisplayMode(.inline) 18 } label: { 19 VStack(alignment: .leading, spacing: 2) { 20 Text(bundle.name) 21 Text("^[\(bundle.puzzles.count) puzzle](inflect: true)") 22 .font(.subheadline) 23 .foregroundStyle(.secondary) 24 } 25 } 26 .accessibilityLabel("\(bundle.name), \(bundle.puzzles.count) \(bundle.puzzles.count == 1 ? "puzzle" : "puzzles")") 27 } 28 } 29 } 30 31 /// A list of individual puzzles, each showing a grid preview alongside its 32 /// title and publisher. 33 private struct PuzzleListView: View { 34 let puzzles: [PuzzleCatalog.Entry] 35 let onSelected: (String) -> Void 36 37 var body: some View { 38 List(puzzles) { entry in 39 Button { 40 if let source = try? entry.loadSource() { 41 onSelected(source) 42 } 43 } label: { 44 HStack(spacing: 12) { 45 GridThumbnailView( 46 width: entry.gridWidth, 47 height: entry.gridHeight, 48 cells: entry.blockCells.map { $0 ? .block : .empty } 49 ) 50 VStack(alignment: .leading, spacing: 2) { 51 Text(entry.title) 52 if let publisher = entry.publisher, !publisher.isEmpty { 53 Text(publisher) 54 .font(.subheadline) 55 .foregroundStyle(.secondary) 56 } 57 } 58 } 59 } 60 .buttonStyle(.plain) 61 .accessibilityElement(children: .ignore) 62 .accessibilityLabel(accessibilityLabel(for: entry)) 63 .accessibilityHint("Creates this puzzle") 64 } 65 } 66 67 private func accessibilityLabel(for entry: PuzzleCatalog.Entry) -> String { 68 var parts = [entry.title] 69 if let publisher = entry.publisher, !publisher.isEmpty { 70 parts.append(publisher) 71 } 72 parts.append("\(entry.gridWidth) by \(entry.gridHeight)") 73 return parts.joined(separator: ", ") 74 } 75 }