crossmate

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

GameShareItem.swift (5723B)


      1 import Foundation
      2 import SwiftUI
      3 
      4 struct GameShareSheet: View {
      5     let gameID: UUID
      6     let title: String
      7     let shareController: ShareController
      8 
      9     @Environment(\.dismiss) private var dismiss
     10     @State private var shareURL: URL?
     11     @State private var errorMessage: String?
     12     @State private var isLoadingExistingLink = true
     13     @State private var isCreating = false
     14     @State private var didLoadExistingLink = false
     15     @State private var didCopy = false
     16 
     17     var body: some View {
     18         NavigationStack {
     19             List {
     20                 VStack(spacing: 18) {
     21                     Image(systemName: "flag.pattern.checkered.2.crossed")
     22                         .font(.system(size: 58, weight: .semibold))
     23                         .symbolRenderingMode(.hierarchical)
     24                         .foregroundStyle(Color.accentColor)
     25                         .frame(width: 88, height: 88)
     26                         .accessibilityHidden(true)
     27 
     28                     VStack(spacing: 12) {
     29                         Text("Share via iCloud")
     30                             .font(.title3.weight(.semibold))
     31                             .multilineTextAlignment(.center)
     32                             .fixedSize(horizontal: false, vertical: true)
     33 
     34                         Text("Any iCloud user with the link will be able to join your game and collaborate.")
     35                             .font(.body)
     36                             .multilineTextAlignment(.center)
     37                             .fixedSize(horizontal: false, vertical: true)
     38 
     39                         Text("Crossmate syncs games using iCloud. Please be aware that your player name is shared with other players. Players can work on the same puzzle simultaneously or at different times.")
     40                             .font(.footnote)
     41                             .foregroundStyle(.secondary)
     42                             .multilineTextAlignment(.center)
     43                             .fixedSize(horizontal: false, vertical: true)
     44                     }
     45                 }
     46                 .frame(maxWidth: .infinity)
     47                 .padding(.horizontal, 8)
     48                 .padding(.vertical, 4)
     49                 .listRowInsets(EdgeInsets())
     50                 .listRowBackground(Color.clear)
     51 
     52                 Section {
     53                     if let shareURL {
     54                         Button {
     55                             UIPasteboard.general.string = shareURL.absoluteString
     56                             didCopy = true
     57                         } label: {
     58                             Label(didCopy ? "Copied" : "Copy Link", systemImage: didCopy ? "checkmark" : "doc.on.doc")
     59                         }
     60 
     61                         ShareLink(item: shareURL) {
     62                             Label("Send Link", systemImage: "square.and.arrow.up")
     63                         }
     64                     } else if isLoadingExistingLink {
     65                         HStack {
     66                             Label("Checking Link", systemImage: "link")
     67                             Spacer()
     68                             ProgressView()
     69                         }
     70                     } else {
     71                         Button {
     72                             Task { await createLink() }
     73                         } label: {
     74                             HStack {
     75                                 Label("Create Link", systemImage: "link")
     76                                 if isCreating {
     77                                     Spacer()
     78                                     ProgressView()
     79                                 }
     80                             }
     81                         }
     82                         .disabled(isCreating || isLoadingExistingLink)
     83                     }
     84                 }
     85 
     86                 if let errorMessage {
     87                     Section("Error") {
     88                         Text(errorMessage)
     89                             .font(.caption.monospaced())
     90                             .foregroundStyle(.red)
     91                             .textSelection(.enabled)
     92                         Button {
     93                             UIPasteboard.general.string = errorMessage
     94                         } label: {
     95                             Label("Copy Error", systemImage: "doc.on.doc")
     96                         }
     97                     }
     98                 }
     99             }
    100             .navigationTitle("Share Game")
    101             .navigationBarTitleDisplayMode(.inline)
    102             .toolbar {
    103                 ToolbarItem(placement: .cancellationAction) {
    104                     Button("Done") { dismiss() }
    105                 }
    106             }
    107             .task {
    108                 await loadExistingLink()
    109             }
    110         }
    111     }
    112 
    113     private func loadExistingLink() async {
    114         guard !didLoadExistingLink else { return }
    115         didLoadExistingLink = true
    116         isLoadingExistingLink = true
    117         errorMessage = nil
    118         defer { isLoadingExistingLink = false }
    119 
    120         do {
    121             shareURL = try await shareController.existingShareLink(for: gameID)
    122         } catch {
    123             errorMessage = describe(error)
    124         }
    125     }
    126 
    127     private func createLink() async {
    128         guard !isCreating, !isLoadingExistingLink else { return }
    129         isCreating = true
    130         didCopy = false
    131         errorMessage = nil
    132         defer { isCreating = false }
    133 
    134         do {
    135             shareURL = try await shareController.createShareLink(for: gameID)
    136         } catch {
    137             errorMessage = describe(error)
    138         }
    139     }
    140 
    141     private func describe(_ error: Error) -> String {
    142         let nsError = error as NSError
    143         let userInfo = nsError.userInfo
    144             .map { "\($0.key)=\($0.value)" }
    145             .joined(separator: " | ")
    146         return "domain=\(nsError.domain) code=\(nsError.code) \(nsError.localizedDescription)\n\(userInfo)"
    147     }
    148 }