crossmate

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

SettingsView.swift (4864B)


      1 import SwiftUI
      2 
      3 struct SettingsView: View {
      4     @Environment(NYTAuthService.self) private var nytAuth
      5     @Environment(PlayerPreferences.self) private var preferences
      6     @Environment(\.dismiss) private var dismiss
      7     @Environment(\.resetDatabase) private var resetDatabase
      8 
      9     @AppStorage("debugMode") private var debugMode = false
     10     @State private var showingNYTLogin = false
     11     @State private var showResetConfirmation = false
     12     @State private var externalSource: ExternalSource?
     13     @State private var easterEggTaps = 0
     14 
     15     var body: some View {
     16         @Bindable var preferences = preferences
     17         NavigationStack {
     18             Form {
     19                 Section("External Source") {
     20                     Picker("Publisher", selection: $externalSource) {
     21                         Text("None").tag(nil as ExternalSource?)
     22                         ForEach(ExternalSource.allCases) { source in
     23                             Text(source.title).tag(source as ExternalSource?)
     24                         }
     25                     }
     26                     .pickerStyle(.menu)
     27 
     28                     switch externalSource {
     29                     case nil:
     30                         EmptyView()
     31                     case .newYorkTimes:
     32                         if nytAuth.isSignedIn {
     33                             signedInView
     34                         } else {
     35                             signInView
     36                         }
     37                     }
     38                 }
     39 
     40                 if debugMode {
     41                     Section("Debugging") {
     42                         Toggle("Enable iCloud Sync", isOn: $preferences.isICloudSyncEnabled)
     43 
     44                         NavigationLink("iCloud Diagnostics") {
     45                             DiagnosticsView()
     46                         }
     47                         NavigationLink("Share Diagnostics") {
     48                             ShareDiagnosticsView()
     49                         }
     50                         NavigationLink("Record Editor") {
     51                             RecordEditorView()
     52                         }
     53 
     54                         Button("Reset Database", role: .destructive) {
     55                             showResetConfirmation = true
     56                         }
     57                         .alert(
     58                             "Delete all games?",
     59                             isPresented: $showResetConfirmation
     60                         ) {
     61                             Button("Delete All Games", role: .destructive) {
     62                                 Task { await resetDatabase?() }
     63                             }
     64                             Button("Cancel", role: .cancel) {}
     65                         } message: {
     66                             Text("This removes every game and clears the sync state on this device. Games on other devices and in iCloud are not affected.")
     67                         }
     68                     }
     69                 }
     70 
     71                 Section {
     72                     NavigationLink("About") {
     73                         AboutView()
     74                     }
     75                 } footer: {
     76                     Text("Made in Tokyo from natural ones and zeros")
     77                         .frame(maxWidth: .infinity)
     78                         .multilineTextAlignment(.center)
     79                         .padding(.top, 24)
     80                         .onTapGesture {
     81                             easterEggTaps += 1
     82                             if easterEggTaps >= 4 {
     83                                 debugMode.toggle()
     84                                 easterEggTaps = 0
     85                             }
     86                         }
     87                 }
     88             }
     89             .navigationTitle("Settings")
     90             .navigationBarTitleDisplayMode(.inline)
     91             .toolbar {
     92                 ToolbarItem(placement: .confirmationAction) {
     93                     Button("Done") { dismiss() }
     94                 }
     95             }
     96             .sheet(isPresented: $showingNYTLogin) {
     97                 NYTLoginView()
     98             }
     99         }
    100     }
    101 
    102     // MARK: - Subviews
    103 
    104     private enum ExternalSource: String, CaseIterable, Identifiable {
    105         case newYorkTimes
    106 
    107         var id: String { rawValue }
    108 
    109         var title: String {
    110             switch self {
    111             case .newYorkTimes: "New York Times"
    112             }
    113         }
    114     }
    115 
    116     @ViewBuilder
    117     private var signedInView: some View {
    118         if let email = nytAuth.signedInEmail {
    119             LabeledContent("E-mail", value: email)
    120         } else {
    121             LabeledContent("Status", value: "Signed in")
    122         }
    123         Button("Sign Out", role: .destructive) {
    124             nytAuth.signOut()
    125         }
    126     }
    127 
    128     @ViewBuilder
    129     private var signInView: some View {
    130         if let errorMessage = nytAuth.errorMessage {
    131             Text(errorMessage)
    132                 .foregroundStyle(.red)
    133                 .font(.caption)
    134         }
    135         Button("Sign In with NYT") {
    136             showingNYTLogin = true
    137         }
    138     }
    139 }