crossmate

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

ImportedBrowseView.swift (3822B)


      1 import SwiftUI
      2 import UniformTypeIdentifiers
      3 
      4 struct ImportedBrowseView: View {
      5     let onSelected: (String) -> Void
      6 
      7     @Environment(DriveMonitor.self) private var monitor
      8     @State private var errorMessage: String?
      9     @State private var showingImporter = false
     10 
     11     var body: some View {
     12         Group {
     13             if !monitor.containerAvailable {
     14                 ContentUnavailableView {
     15                     Label("iCloud Drive Unavailable", systemImage: "icloud.slash")
     16                 } description: {
     17                     Text("Sign in to iCloud and enable iCloud Drive to import puzzles.")
     18                 }
     19             } else if let root = monitor.root, !root.children.isEmpty {
     20                 List {
     21                     ForEach(root.children) { item in
     22                         DriveItemRow(item: item, onOpen: open)
     23                     }
     24                 }
     25             } else {
     26                 ContentUnavailableView {
     27                     Label("No Imported Puzzles", systemImage: "folder")
     28                 } description: {
     29                     Text("Add .xd or .puz files to the Crossmate folder in Files, or tap the import button to bring one in.")
     30                 }
     31             }
     32         }
     33         .toolbar {
     34             ToolbarItem(placement: .primaryAction) {
     35                 Button {
     36                     showingImporter = true
     37                 } label: {
     38                     Label("Import", systemImage: "square.and.arrow.down")
     39                 }
     40                 .disabled(!monitor.containerAvailable)
     41             }
     42         }
     43         .fileImporter(
     44             isPresented: $showingImporter,
     45             allowedContentTypes: [.xdPuzzle, .acrossLitePuzzle],
     46             allowsMultipleSelection: false
     47         ) { result in
     48             handleImport(result)
     49         }
     50         .alert(
     51             "Couldn't Open Puzzle",
     52             isPresented: .init(
     53                 get: { errorMessage != nil },
     54                 set: { if !$0 { errorMessage = nil } }
     55             ),
     56             presenting: errorMessage
     57         ) { _ in
     58             Button("OK", role: .cancel) {}
     59         } message: { message in
     60             Text(message)
     61         }
     62     }
     63 
     64     private func handleImport(_ result: Result<[URL], Error>) {
     65         do {
     66             let urls = try result.get()
     67             guard let url = urls.first else { return }
     68             try monitor.importFile(from: url)
     69         } catch {
     70             errorMessage = error.localizedDescription
     71         }
     72     }
     73 
     74     private func open(_ item: DriveItem) {
     75         if !item.isDownloaded {
     76             monitor.startDownloading(item)
     77             errorMessage = "This puzzle is still downloading from iCloud. Try again in a moment."
     78             return
     79         }
     80         do {
     81             let source = try monitor.readSource(at: item.url)
     82             onSelected(source)
     83         } catch {
     84             errorMessage = error.localizedDescription
     85         }
     86     }
     87 }
     88 
     89 private struct DriveItemRow: View {
     90     let item: DriveItem
     91     let onOpen: (DriveItem) -> Void
     92 
     93     var body: some View {
     94         if item.isDirectory {
     95             DisclosureGroup {
     96                 ForEach(item.children) { child in
     97                     DriveItemRow(item: child, onOpen: onOpen)
     98                 }
     99             } label: {
    100                 Label(item.name, systemImage: "folder")
    101             }
    102         } else {
    103             Button {
    104                 onOpen(item)
    105             } label: {
    106                 HStack {
    107                     Label(item.url.lastPathComponent, systemImage: "doc.text")
    108                         .foregroundStyle(.primary)
    109                     Spacer()
    110                     if !item.isDownloaded {
    111                         Image(systemName: "icloud.and.arrow.down")
    112                             .foregroundStyle(.secondary)
    113                     }
    114                 }
    115             }
    116         }
    117     }
    118 }