aboutsummaryrefslogtreecommitdiff
path: root/Map/Presentation/Preferences
diff options
context:
space:
mode:
Diffstat (limited to 'Map/Presentation/Preferences')
-rw-r--r--Map/Presentation/Preferences/EditorPreferencesView.swift11
-rw-r--r--Map/Presentation/Preferences/GeneralPreferencesView.swift187
-rw-r--r--Map/Presentation/Preferences/MapPreferencesView.swift19
-rw-r--r--Map/Presentation/Preferences/StagesPreferencesView.swift7
-rw-r--r--Map/Presentation/Preferences/TemplatesPreferencesView.swift34
5 files changed, 206 insertions, 52 deletions
diff --git a/Map/Presentation/Preferences/EditorPreferencesView.swift b/Map/Presentation/Preferences/EditorPreferencesView.swift
index cf584ff..91b332f 100644
--- a/Map/Presentation/Preferences/EditorPreferencesView.swift
+++ b/Map/Presentation/Preferences/EditorPreferencesView.swift
@@ -17,10 +17,13 @@ import SwiftUI
struct EditorPreferencesView: View {
- @AppStorage("useSmartEditor") private var useSmartEditor = false
- @AppStorage("useCustomEditorFont") private var useCustomEditorFont = false
- @AppStorage("customEditorFontName") private var customEditorFontName = "Menlo"
- @AppStorage("editorFontSize") private var editorFontSize: Double = 14
+ @AppStorage("useSmartEditor") private var useSmartEditor = UserPreferences.Defaults.useSmartEditor
+ @AppStorage("useCustomEditorFont") private var useCustomEditorFont = UserPreferences.Defaults
+ .useCustomEditorFont
+ @AppStorage("customEditorFontName") private var customEditorFontName = UserPreferences.Defaults
+ .customEditorFontName
+ @AppStorage("editorFontSize") private var editorFontSize: Double = UserPreferences.Defaults
+ .editorFontSize
var monospacedFonts: [String] {
let fontManager = NSFontManager.shared
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)
}
}
diff --git a/Map/Presentation/Preferences/MapPreferencesView.swift b/Map/Presentation/Preferences/MapPreferencesView.swift
index f6c909c..71e9d6c 100644
--- a/Map/Presentation/Preferences/MapPreferencesView.swift
+++ b/Map/Presentation/Preferences/MapPreferencesView.swift
@@ -16,10 +16,14 @@
import SwiftUI
struct MapPreferencesView: View {
- @AppStorage("showMapBackground") private var showBackground = true
- @AppStorage("useCustomFont") private var useCustomFont = false
- @AppStorage("customFontName") private var customFontName = "Helvetica"
- @AppStorage("defaultExportFormat") private var defaultExportFormat = "png"
+ @AppStorage("showMapBackground") private var showBackground = UserPreferences.Defaults
+ .showMapBackground
+ @AppStorage("useCustomFont") private var useCustomFont = UserPreferences.Defaults.useCustomFont
+ @AppStorage("customFontName") private var customFontName = UserPreferences.Defaults.customFontName
+ @AppStorage("defaultExportFormat") private var defaultExportFormat = UserPreferences.Defaults
+ .defaultExportFormat
+ @AppStorage("useSmartLabelPositioning") private var useSmartLabelPositioning = UserPreferences
+ .Defaults.useSmartLabelPositioning
var body: some View {
VStack(alignment: .center, spacing: Dimensions.Spacing.loose) {
@@ -37,6 +41,13 @@ struct MapPreferencesView: View {
})
Toggle(
+ isOn: $useSmartLabelPositioning,
+ label: {
+ Text("preferences.map.map_style.use_smart_label_positioning")
+ .font(.Theme.Body.regular)
+ })
+
+ Toggle(
isOn: $useCustomFont,
label: {
Text("preferences.map.map_style.use_custom_font")
diff --git a/Map/Presentation/Preferences/StagesPreferencesView.swift b/Map/Presentation/Preferences/StagesPreferencesView.swift
index 6d2154c..542a7fe 100644
--- a/Map/Presentation/Preferences/StagesPreferencesView.swift
+++ b/Map/Presentation/Preferences/StagesPreferencesView.swift
@@ -16,7 +16,8 @@
import SwiftUI
struct StagesPreferencesView: View {
- @AppStorage("customStages") private var customStagesData: Data = Data()
+ @AppStorage("customStages") private var customStagesData: Data = UserPreferences.Defaults
+ .customStages
@State private var customStages: [CustomStage] = []
@State private var showingHelpPopover = false
@@ -128,10 +129,6 @@ struct StagesPreferencesView: View {
private func loadStages() {
if let decoded = try? JSONDecoder().decode([CustomStage].self, from: customStagesData) {
customStages = decoded
- } else {
- // First use - create default Cynefin stage
- customStages = [CustomStage.cynefinDefault()]
- saveStages() // Save the default to UserDefaults
}
}
diff --git a/Map/Presentation/Preferences/TemplatesPreferencesView.swift b/Map/Presentation/Preferences/TemplatesPreferencesView.swift
index 5de2428..375ed68 100644
--- a/Map/Presentation/Preferences/TemplatesPreferencesView.swift
+++ b/Map/Presentation/Preferences/TemplatesPreferencesView.swift
@@ -37,7 +37,8 @@ struct Template: Identifiable, Codable, Hashable {
}
struct TemplatesPreferencesView: View {
- @AppStorage("mapTemplates") private var templatesData: Data = Data()
+ @AppStorage("mapTemplates") private var templatesData: Data = UserPreferences.Defaults
+ .mapTemplates
@State private var selectedTemplate: Template?
@State private var showingAddSheet = false
@State private var newTemplateName = ""
@@ -183,12 +184,6 @@ struct TemplatesPreferencesView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.onAppear {
- // Initialize default templates if needed
- if templates.wrappedValue.isEmpty
- || !templates.wrappedValue.contains(where: { $0.isDefault })
- {
- createDefaultTemplates()
- }
// Set first template as selected if none is selected
if selectedTemplate == nil && !templates.wrappedValue.isEmpty {
selectedTemplate = templates.wrappedValue.first
@@ -244,31 +239,6 @@ struct TemplatesPreferencesView: View {
.padding(.horizontal, Dimensions.Spacing.indulgent)
}
- private func createDefaultTemplates() {
- let defaultTemplate = Template(
- name: String(localized: "map_template.default.title"),
- content: String(localized: "map_template.default.content"),
- isDefault: true
- )
-
- let emptyTemplate = Template(
- name: String(localized: "map_template.empty.title"),
- content: "",
- isDefault: false
- )
-
- if templates.wrappedValue.isEmpty {
- templates.wrappedValue = [defaultTemplate, emptyTemplate]
- } else {
- // Add default template if none exists
- if !templates.wrappedValue.contains(where: { $0.isDefault }) {
- var currentTemplates = templates.wrappedValue
- currentTemplates.insert(defaultTemplate, at: 0)
- templates.wrappedValue = currentTemplates
- }
- }
- }
-
private func addTemplate() {
let newTemplate = Template(
name: newTemplateName.isEmpty