aboutsummaryrefslogtreecommitdiff
path: root/Map/Presentation/Preferences/GeneralPreferencesView.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Map/Presentation/Preferences/GeneralPreferencesView.swift')
-rw-r--r--Map/Presentation/Preferences/GeneralPreferencesView.swift187
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)
}
}