crossmate

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

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 }