diff options
Diffstat (limited to 'Map/Presentation/Preferences/GeneralPreferencesView.swift')
| -rw-r--r-- | Map/Presentation/Preferences/GeneralPreferencesView.swift | 187 |
1 files changed, 180 insertions, 7 deletions
diff --git a/Map/Presentation/Preferences/GeneralPreferencesView.swift b/Map/Presentation/Preferences/GeneralPreferencesView.swift index 9eb829e..a70d608 100644 --- a/Map/Presentation/Preferences/GeneralPreferencesView.swift +++ b/Map/Presentation/Preferences/GeneralPreferencesView.swift @@ -14,20 +14,193 @@ // along with this program. If not, see https://map.tranquil.systems. import SwiftUI +import UniformTypeIdentifiers struct GeneralPreferencesView: View { + @State private var showingResetAlert = false + @State private var showingExportDialog = false + @State private var showingImportDialog = false + @State private var showingImportError = false + @State private var importErrorMessage = "" + @State private var showingExportFailedAlert = false + @State private var exportShouldReset = false + var body: some View { - VStack { - Text("General Preferences") - .font(.largeTitle) - .padding() + VStack(alignment: .center, spacing: Dimensions.Spacing.loose) { + HStack(alignment: .firstTextBaseline, spacing: Dimensions.Spacing.regular) { + Text("preferences.general.preferences.title") + .font(.Theme.Body.emphasized) + .frame(maxWidth: 250, alignment: .trailing) + + VStack(alignment: .leading, spacing: Dimensions.Spacing.regular) { + HStack(spacing: Dimensions.Spacing.cozy) { + Button( + action: { + exportShouldReset = false + showingExportDialog = true + }, + label: { + Text("preferences.general.preferences.export") + .font(.Theme.Body.regular) + } + ) + .buttonStyle(.bordered) - Text("General settings will be available here in a future version.") - .foregroundColor(.secondary) + Button( + action: { + showingImportDialog = true + }, + label: { + Text("preferences.general.preferences.import") + .font(.Theme.Body.regular) + } + ) + .buttonStyle(.bordered) + } + + Button( + action: { + showingResetAlert = true + }, + label: { + Text("preferences.general.preferences.reset") + .font(.Theme.Body.regular) + .foregroundColor(Color.Theme.jasperRed) + } + ) + .buttonStyle(.borderless) + } + .frame(maxWidth: .infinity, alignment: .leading) + } Spacer() } - .padding() + .padding(.vertical, Dimensions.Spacing.loose) + .padding(.horizontal, Dimensions.Spacing.indulgent) + .alert("preferences.general.reset.title", isPresented: $showingResetAlert) { + Button("preferences.general.reset.cancel", role: .cancel) {} + Button("preferences.general.reset.without_export", role: .destructive) { + resetPreferences() + } + Button("preferences.general.reset.export_and_reset") { + exportShouldReset = true + showingExportDialog = true + } + } message: { + Text("preferences.general.reset.message") + } + .alert("preferences.general.import.error.title", isPresented: $showingImportError) { + Button("preferences.general.import.error.ok") {} + } message: { + Text(importErrorMessage) + } + .alert("preferences.general.export.failed.title", isPresented: $showingExportFailedAlert) { + Button("preferences.general.export.failed.ok") {} + } message: { + Text("preferences.general.export.failed.message") + } + .fileExporter( + isPresented: $showingExportDialog, + document: PreferencesDocument(), + contentType: .json, + defaultFilename: String(localized: "preferences.general.export.filename") + ) { result in + handleExportResult(result) + } + .fileImporter( + isPresented: $showingImportDialog, + allowedContentTypes: [.json], + allowsMultipleSelection: false + ) { result in + handleImport(result) + } + } + + private func resetPreferences() { + var preferences = UserPreferences() + preferences.resetToDefaults() + } + + private func handleExportResult(_ result: Result<URL, Error>) { + switch result { + case .success(_): + // Export successful + if exportShouldReset { + // Reset preferences if this was an export-and-reset operation + resetPreferences() + } + // Reset the flag + exportShouldReset = false + case .failure(let error): + if exportShouldReset { + // Export failed during export-and-reset - show alert and don't reset + showingExportFailedAlert = true + exportShouldReset = false + } else { + // Regular export failed - just log it + print("Export error: \(error)") + } + } + } + + private func handleImport(_ result: Result<[URL], Error>) { + switch result { + case .success(let urls): + guard let url = urls.first else { return } + + // Start accessing the security-scoped resource + guard url.startAccessingSecurityScopedResource() else { + importErrorMessage = String(localized: "preferences.general.import.error.file_read") + showingImportError = true + return + } + + // Ensure we stop accessing the resource when done + defer { + url.stopAccessingSecurityScopedResource() + } + + do { + let jsonData = try Data(contentsOf: url) + let jsonString = String(data: jsonData, encoding: .utf8) ?? "" + + var preferences = UserPreferences.loadFromUserDefaults() + if preferences.updateFromJSON(jsonString) { + // Import successful + } else { + importErrorMessage = String(localized: "preferences.general.import.error.invalid_format") + showingImportError = true + } + } catch { + importErrorMessage = String(localized: "preferences.general.import.error.file_read") + showingImportError = true + } + + case .failure(let error): + importErrorMessage = error.localizedDescription + showingImportError = true + } + } +} + +// Document wrapper for file export +struct PreferencesDocument: FileDocument { + static var readableContentTypes: [UTType] { [.json] } + + var preferences: UserPreferences + + init() { + self.preferences = UserPreferences.loadFromUserDefaults() + } + + init(configuration: ReadConfiguration) throws { + self.preferences = UserPreferences() + } + + func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { + let jsonString = preferences.toJSON() ?? "{}" + let data = jsonString.data(using: .utf8) ?? Data() + return FileWrapper(regularFileWithContents: data) } } |