From 01c3c2052712e656d54d676f23089ee3c17739cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Dr=2E=20med=2E=20K=C3=BCbler?= Date: Sun, 8 Nov 2026 19:20:53 +0100 Subject: [PATCH] Alpha 4 Update --- MikeMail.xcodeproj/project.xcproj | 7 +- .../UserInterfaceState.xcuserstate | Bin 0 -> 132546 bytes MikeMail/AccountsView.swift | 33 +- MikeMail/ContentView.swift | 63 +- MikeMail/Localizable.xcstrings | 1635 +++++++++++++++++ MikeMail/MailModels.swift | 2 +- MikeMail/MailSettings.swift | 261 ++- MikeMail/MailStore.swift | 6 +- MikeMail/MikeMail-InfoPlist.xcstrings | 42 + MikeMail/MikeMailApp.swift | 4 +- MikeMail/PasswordRecoveryView.swift | 21 +- MikeMail/SettingsBackup.swift | 125 ++ 12 files changed, 2147 insertions(+), 52 deletions(-) create mode 100644 MikeMail.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 MikeMail/Localizable.xcstrings create mode 100644 MikeMail/MikeMail-InfoPlist.xcstrings create mode 100644 MikeMail/SettingsBackup.swift diff --git a/MikeMail.xcodeproj/project.xcproj b/MikeMail.xcodeproj/project.xcproj index 8c97254..12ab648 100644 --- a/MikeMail.xcodeproj/project.xcproj +++ b/MikeMail.xcodeproj/project.xcproj @@ -8,6 +8,7 @@ "development": "en", "supported": [ "Base", + "de", ], }, "files": [ @@ -46,13 +47,14 @@ "ENABLE_HARDENED_RUNTIME": "YES", "ENABLE_OUTGOING_NETWORK_CONNECTIONS": "YES", "ENABLE_PREVIEWS": "YES", - "ENABLE_USER_SELECTED_FILES": "readonly", + "ENABLE_USER_SELECTED_FILES": "readwrite", "GENERATE_INFOPLIST_FILE": "YES", "INFOPLIST_KEY_NSHumanReadableCopyright": "", "LD_RUNPATH_SEARCH_PATHS": [ "$(inherited)", "@executable_path/../Frameworks", ], + "MACOSX_DEPLOYMENT_TARGET": "27.0", "MARKETING_VERSION": "1.0", "OTHER_LDFLAGS": [ "$(inherited)", @@ -89,7 +91,7 @@ "CURRENT_PROJECT_VERSION": "1", "DEVELOPMENT_TEAM": "GQR972L9NJ", "GENERATE_INFOPLIST_FILE": "YES", - "MACOSX_DEPLOYMENT_TARGET": "27.2", + "MACOSX_DEPLOYMENT_TARGET": "27.0", "MARKETING_VERSION": "1.0", "PRODUCT_BUNDLE_IDENTIFIER": "Dr.-med.-Michael-Ku-bler.MikeMailTests", "PRODUCT_NAME": "$(TARGET_NAME)", @@ -119,6 +121,7 @@ "CURRENT_PROJECT_VERSION": "1", "DEVELOPMENT_TEAM": "GQR972L9NJ", "GENERATE_INFOPLIST_FILE": "YES", + "MACOSX_DEPLOYMENT_TARGET": "27.0", "MARKETING_VERSION": "1.0", "PRODUCT_BUNDLE_IDENTIFIER": "Dr.-med.-Michael-Ku-bler.MikeMailUITests", "PRODUCT_NAME": "$(TARGET_NAME)", diff --git a/MikeMail.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/UserInterfaceState.xcuserstate b/MikeMail.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..3c669f17311cb39fcff285d38020a652ad882851 GIT binary patch literal 132546 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 = [] @AppStorage("offerTestMail") private var offerTestMail = true var body: some View { @@ -116,6 +117,7 @@ struct AccountsView: View { selection = store.accounts.first?.id } .onChange(of: selection) { _, _ in error = nil; resetResult = nil } + .onDisappear { refreshChangedAccounts() } .confirmationDialog("Alle lokalen Mails dieses Kontos löschen?", isPresented: $confirmingReset, titleVisibility: .visible) { Button("Lokale Mails endgültig löschen", role: .destructive) { guard let account = resetAccount else { return } @@ -153,11 +155,25 @@ struct AccountsView: View { await store.refresh(folderID: nil, accountID: id) if offerTestMail { testMailAccount = account; askingTestMail = true } } + } else { + accountsNeedingRefresh.insert(id) } error = nil } catch { self.error = error.localizedDescription } } + private func refreshChangedAccounts() { + let accountIDs = accountsNeedingRefresh + accountsNeedingRefresh.removeAll() + guard !accountIDs.isEmpty else { return } + Task { + for id in accountIDs where store.accounts.contains(where: { $0.id == id }) { + while store.isBusy { try? await Task.sleep(for: .milliseconds(150)) } + await store.refresh(folderID: nil, accountID: id) + } + } + } + private func testConnection() { guard let id = selection, let account = drafts[id] else { return } let password = passwords[id] ?? "" @@ -190,6 +206,7 @@ struct AccountsView: View { struct AccountSettingsForm: View { @Binding var account: MailAccount @Binding var password: String + @State private var showsPassword = false var body: some View { Form { @@ -199,7 +216,21 @@ struct AccountSettingsForm: View { TextField("Server", text: $account.host) TextField("IMAP-Port (TLS)", value: $account.imapPort, format: .number.grouping(.never)) TextField("SMTP-Port (587 STARTTLS / 465 TLS)", value: $account.smtpPort, format: .number.grouping(.never)) - SecureField("Passwort", text: $password) + LabeledContent("Passwort") { + HStack(spacing: 8) { + if showsPassword { + TextField("Passwort", text: $password) + } else { + SecureField("Passwort", text: $password) + } + Button(showsPassword ? "Passwort verbergen" : "Passwort anzeigen", + systemImage: showsPassword ? "eye.slash" : "eye") { + showsPassword.toggle() + } + .labelStyle(.iconOnly) + .buttonStyle(.borderless) + } + } } } } diff --git a/MikeMail/ContentView.swift b/MikeMail/ContentView.swift index 6b8c82e..662d1ad 100644 --- a/MikeMail/ContentView.swift +++ b/MikeMail/ContentView.swift @@ -1,7 +1,30 @@ import SwiftUI +import Observation import AppKit import UniformTypeIdentifiers +@MainActor @Observable +final class AccountPresentation { + var isPresented = false +} + +extension FocusedValues { + @Entry var accountPresentation: AccountPresentation? +} + +struct AccountCommands: Commands { + @FocusedValue(\.accountPresentation) private var accountPresentation + + var body: some Commands { + CommandGroup(after: .appSettings) { + Button("Konten verwalten …") { + accountPresentation?.isPresented = true + } + .disabled(accountPresentation == nil) + } + } +} + struct ContentView: View { let store: MailStore @AppStorage("selectedFolder") private var savedFolder = "" @@ -16,7 +39,7 @@ struct ContentView: View { @AppStorage("searchRegex") private var searchRegex = false @State private var selectedFolder: String? @State private var selectedMessages: Set = [] - @State private var showingAccounts = false + @State private var accountPresentation = AccountPresentation() @Environment(\.openWindow) private var openWindow @AppStorage("idleEnabled") private var idleEnabled = true @AppStorage("pollMinutes") private var pollMinutes = 5 @@ -119,11 +142,14 @@ struct ContentView: View { .keyboardShortcut("n", modifiers: .command).disabled(store.accounts.isEmpty) Button("Löschen", systemImage: "trash") { deleteSelection() } .disabled(selectedMessages.isEmpty) - Button("Konten", systemImage: "person.crop.circle") { showingAccounts = true } SettingsLink { Label("Einstellungen", systemImage: "gearshape") } } } - .sheet(isPresented: $showingAccounts) { AccountsView(store: store) } + .focusedSceneValue(\.accountPresentation, accountPresentation) + .sheet(isPresented: Binding( + get: { accountPresentation.isPresented }, + set: { accountPresentation.isPresented = $0 } + )) { AccountsView(store: store) } .sheet(item: Binding(get: { store.authenticationAccount }, set: { value in if value == nil { store.dismissPasswordRequest() } })) { account in @@ -307,24 +333,37 @@ struct MessageTable: View { @AppStorage("mailSortColumn") private var savedColumn = "date" @AppStorage("mailSortAscending") private var savedAscending = false @State private var sortOrder = [KeyPathComparator(\MailMessage.date, order: .reverse)] + @SceneStorage("MessageTableColumns") + private var columnCustomization: TableColumnCustomization var body: some View { - Table(of: MailMessage.self, selection: $selection, sortOrder: $sortOrder) { - TableColumn("Anhang", value: \.attachmentCount) { message in - if !message.attachments.isEmpty { - Image(systemName: "paperclip").accessibilityLabel(message.attachments.count == 1 ? "Ein Anhang" : "\(message.attachments.count) Anhänge") - } - }.width(55) + Table(of: MailMessage.self, selection: $selection, sortOrder: $sortOrder, + columnCustomization: $columnCustomization) { TableColumn("Absender", value: \.displaySender) { message in Text(message.displaySender).fontWeight(message.isRead ? .regular : .bold) - }.width(min: 110, ideal: 160) + } + .width(min: 110, ideal: 160) + .customizationID("sender") + TableColumn("", value: \.attachmentCount) { message in + if !message.attachments.isEmpty { + Image(systemName: "paperclip") + .frame(maxWidth: .infinity, alignment: .center) + .accessibilityLabel(message.attachments.count == 1 ? "Ein Anhang" : "\(message.attachments.count) Anhänge") + } + } + .width(28) + .customizationID("attachments") TableColumn("Datum", value: \.date) { message in if message.date == .distantPast { Text("—") } else { Text(MailDisplay.date(message.date)).monospacedDigit().fontWeight(message.isRead ? .regular : .bold) } - }.width(min: 120, ideal: 150) + } + .width(min: 120, ideal: 150) + .customizationID("date") TableColumn("Betreff", value: \.subject) { message in Text(message.subject).fontWeight(message.isRead ? .regular : .bold) - }.width(min: 160, ideal: 280) + } + .width(min: 160, ideal: 280) + .customizationID("subject") } rows: { ForEach(messages.sorted(using: sortOrder)) { message in TableRow(message).draggable(MailDrag.encode(selection.contains(message.id) ? selection : [message.id])) diff --git a/MikeMail/Localizable.xcstrings b/MikeMail/Localizable.xcstrings new file mode 100644 index 0000000..9016636 --- /dev/null +++ b/MikeMail/Localizable.xcstrings @@ -0,0 +1,1635 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "" : { + + }, + "—" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "—" + } + } + } + }, + "„%@“ und alle Unterordner samt Nachrichten werden auf dem Server endgültig gelöscht." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "„%@“ und alle Unterordner samt Nachrichten werden auf dem Server endgültig gelöscht." + } + } + } + }, + "%@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%@" + } + } + } + }, + "%lld Anhänge" : { + "localizations" : { + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Anhang" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Anhänge" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Anhänge" + } + } + } + } + }, + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Anhang" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Anhänge" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Anhänge" + } + } + } + } + } + } + }, + "%lld Nachrichten" : { + "localizations" : { + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachricht" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachrichten" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachrichten" + } + } + } + } + }, + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachricht" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachrichten" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachrichten" + } + } + } + } + } + } + }, + "%lld Nachrichten löschen?" : { + "localizations" : { + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachricht löschen?" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachrichten löschen?" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachrichten löschen?" + } + } + } + } + }, + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachricht löschen?" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachrichten löschen?" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld Nachrichten löschen?" + } + } + } + } + } + } + }, + "%lld ungelesen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "%lld ungelesen" + } + } + } + }, + "Abbrechen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Abbrechen" + } + } + } + }, + "Abruf" : { + + }, + "Absender" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Absender" + } + } + } + }, + "Alle %lld Minuten" : { + "localizations" : { + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Jede %lld Minute" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Alle %lld Minuten" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Alle %lld Minuten" + } + } + } + } + }, + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Jede %lld Minute" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Alle %lld Minuten" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Alle %lld Minuten" + } + } + } + } + } + } + }, + "Alle Anhänge speichern unter…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Alle Anhänge speichern unter…" + } + } + } + }, + "Alle Konten" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Alle Konten" + } + } + } + }, + "Alle lokalen Mails dieses Kontos löschen?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Alle lokalen Mails dieses Kontos löschen?" + } + } + } + }, + "Alle Nachrichten in diesem Papierkorb werden auch auf dem Server endgültig gelöscht. Beim gemeinsamen Papierkorb betrifft dies alle Konten." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Alle Nachrichten in diesem Papierkorb werden auch auf dem Server endgültig gelöscht. Beim gemeinsamen Papierkorb betrifft dies alle Konten." + } + } + } + }, + "Als gelesen markieren" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Als gelesen markieren" + } + } + } + }, + "Als ungelesen markieren" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Als ungelesen markieren" + } + } + } + }, + "An (mehrere mit Komma trennen)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "An (mehrere mit Komma trennen)" + } + } + } + }, + "An: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "An: %@" + } + } + } + }, + "Anhang" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Anhang" + } + } + } + }, + "Anhang – Dateien hier ablegen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Anhang – Dateien hier ablegen" + } + } + } + }, + "Anhang hinzufügen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Anhang hinzufügen" + } + } + } + }, + "Anhänge – Dateien hier ablegen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Anhänge – Dateien hier ablegen" + } + } + } + }, + "Antworten…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Antworten …" + } + } + } + }, + "Auf neue Mails prüfen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Auf neue Mails prüfen" + } + } + } + }, + "Ausgewählte Nachrichten im Papierkorb werden auch auf dem Server endgültig gelöscht. Andere ausgewählte Nachrichten werden in den Papierkorb verschoben." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Ausgewählte Nachrichten im Papierkorb werden auch auf dem Server endgültig gelöscht. Andere ausgewählte Nachrichten werden in den Papierkorb verschoben." + } + } + } + }, + "BCC" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "BCC" + } + } + } + }, + "Beim Löschen von Mails nachfragen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Beim Löschen von Mails nachfragen" + } + } + } + }, + "Beim nächsten Start wiederherstellen" : { + + }, + "Beim Start automatisch auf neue Mails prüfen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Beim Start automatisch auf neue Mails prüfen" + } + } + } + }, + "Benutzername" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Benutzername" + } + } + } + }, + "Betreff" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Betreff" + } + } + } + }, + "CC" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "CC" + } + } + } + }, + "CC: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "CC: %@" + } + } + } + }, + "Darstellung" : { + + }, + "Das Backup enthält App-Einstellungen, Konten sowie gespeicherte Fenster-, Split- und Tabellenlayouts. Mails und Anhänge werden nicht gesichert." : { + + }, + "Das Backup enthält die Passwörter im Klartext. Bewahre die Datei an einem geschützten Ort auf." : { + + }, + "Das Passwort wird erst nach erfolgreicher Anmeldung im Schlüsselbund gespeichert." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Das Passwort wird erst nach erfolgreicher Anmeldung im Schlüsselbund gespeichert." + } + } + } + }, + "Datei speichern unter…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Datei speichern unter …" + } + } + } + }, + "Datum" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Datum" + } + } + } + }, + "Der Inhalt und die Anhänge erscheinen hier." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Der Inhalt und die Anhänge erscheinen hier." + } + } + } + }, + "Der Mailserver hat die Anmeldung für %@ abgelehnt." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Der Mailserver hat die Anmeldung für %@ abgelehnt." + } + } + } + }, + "Der Ordner wird auf dem Mailserver erstellt." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Der Ordner wird auf dem Mailserver erstellt." + } + } + } + }, + "Die aktuellen Einstellungen und Konten werden beim nächsten Start ersetzt. Mails und Anhänge bleiben unverändert. MikeMail wird jetzt automatisch beendet; öffne die App anschließend erneut." : { + + }, + "Die ausgewählten Nachrichten werden auch auf dem Server in den Papierkorb verschoben." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Die ausgewählten Nachrichten werden auch auf dem Server in den Papierkorb verschoben." + } + } + } + }, + "Die lokalen Kontoeinstellungen und das gespeicherte Passwort werden entfernt. Mails auf dem Server bleiben erhalten." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Die lokalen Kontoeinstellungen und das gespeicherte Passwort werden entfernt. Mails auf dem Server bleiben erhalten." + } + } + } + }, + "Die Nachricht wird auch auf dem Server endgültig gelöscht." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Die Nachricht wird auch auf dem Server endgültig gelöscht." + } + } + } + }, + "Die Nachricht wird auch auf dem Server in den Papierkorb verschoben." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Die Nachricht wird auch auf dem Server in den Papierkorb verschoben." + } + } + } + }, + "Doppelklick zum Öffnen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Doppelklick zum Öffnen" + } + } + } + }, + "Durchgestrichen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Durchgestrichen" + } + } + } + }, + "E-Mail-Adresse" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "E-Mail-Adresse" + } + } + } + }, + "Ein Anhang" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Ein Anhang" + } + } + } + }, + "Ein leeres Passwortfeld behält das gespeicherte Passwort bei. Änderungen mit „Speichern“ übernehmen." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Ein leeres Passwortfeld behält das gespeicherte Passwort bei. Änderungen mit „Speichern“ übernehmen." + } + } + } + }, + "Eine Testnachricht an %@ senden?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Eine Testnachricht an %@ senden?" + } + } + } + }, + "Einstellungen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Einstellungen" + } + } + } + }, + "Einstellungen sichern …" : { + + }, + "Einstellungen und Konten" : { + + }, + "Einstellungen wiederherstellen …" : { + + }, + "Einstellungen wiederherstellen?" : { + + }, + "Entfernen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Entfernen" + } + } + } + }, + "Erstellen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Erstellen" + } + } + } + }, + "Externe Inhalte blockiert" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Externe Inhalte blockiert" + } + } + } + }, + "Externe Inhalte erlaubt" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Externe Inhalte erlaubt" + } + } + } + }, + "Externe Inhalte in HTML-Mails blockieren" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Externe Inhalte in HTML-Mails blockieren" + } + } + } + }, + "Fett" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Fett" + } + } + } + }, + "Füge ein Konto hinzu oder wähle links ein Konto aus." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Füge ein Konto hinzu oder wähle links ein Konto aus." + } + } + } + }, + "Gemeinsamen Papierkorb anzeigen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Gemeinsamen Papierkorb anzeigen" + } + } + } + }, + "Gemeinsamen Posteingang anzeigen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Gemeinsamen Posteingang anzeigen" + } + } + } + }, + "Gilt beim Öffnen einer Mail. Inhalte können für eine einzelne Mail freigegeben werden. Eigene HTML-Schriftformate bleiben erhalten." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Gilt beim Öffnen einer Mail. Inhalte können für eine einzelne Mail freigegeben werden. Eigene HTML-Schriftformate bleiben erhalten." + } + } + } + }, + "HTML-Nachricht" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "HTML-Nachricht" + } + } + } + }, + "IMAP-Port (TLS)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "IMAP-Port (TLS)" + } + } + } + }, + "Immer" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Immer" + } + } + } + }, + "In neuem Fenster öffnen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "In neuem Fenster öffnen" + } + } + } + }, + "Inhalt" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Inhalt" + } + } + } + }, + "Inhalte laden" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Inhalte laden" + } + } + } + }, + "Kein Konto ausgewählt" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Kein Konto ausgewählt" + } + } + } + }, + "Keine Nachrichten" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Keine Nachrichten" + } + } + } + }, + "Konten" : { + "extractionState" : "stale", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Konten" + } + } + } + }, + "Konten verwalten …" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Konten verwalten …" + } + } + } + }, + "Konteneinstellungen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Konteneinstellungen" + } + } + } + }, + "Konto aus MikeMail entfernen?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Konto aus MikeMail entfernen?" + } + } + } + }, + "Konto löschen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Konto löschen" + } + } + } + }, + "Konto: %@\n\nAlle lokal gespeicherten Mails und Anhänge dieses Kontos werden entfernt, auch archivierte Nachrichten und lokale Versandkopien. Nur lokal vorhandene Mails können nicht vom Server wiederhergestellt werden.\n\nMails auf dem Server, Kontoeinstellungen und Passwort bleiben unverändert. Beim nächsten „Abrufen“ werden alle vorhandenen Servermails neu heruntergeladen." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Konto: %@\n\nAlle lokal gespeicherten Mails und Anhänge dieses Kontos werden entfernt, auch archivierte Nachrichten und lokale Versandkopien. Nur lokal vorhandene Mails können nicht vom Server wiederhergestellt werden.\n\nMails auf dem Server, Kontoeinstellungen und Passwort bleiben unverändert. Beim nächsten „Abrufen“ werden alle vorhandenen Servermails neu heruntergeladen." + } + } + } + }, + "Kursiv" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Kursiv" + } + } + } + }, + "Lesestatus" : { + + }, + "Lokale Mails endgültig löschen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Lokale Mails endgültig löschen" + } + } + } + }, + "Lokale Mails löschen …" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Lokale Mails löschen  …" + } + } + } + }, + "Löschen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Löschen" + } + } + } + }, + "Löschen…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Löschen …" + } + } + } + }, + "Mailabruf" : { + + }, + "Maildarstellung" : { + + }, + "Mails abrufen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Mails abrufen" + } + } + } + }, + "MikeMail" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "MikeMail" + } + } + } + }, + "Nach %lld Sekunden" : { + "localizations" : { + "de" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nach %lld Sekunde" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nach %lld Sekunden" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nach %lld Sekunden" + } + } + } + } + }, + "en" : { + "variations" : { + "plural" : { + "one" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nach %lld Sekunde" + } + }, + "other" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nach %lld Sekunden" + } + }, + "zero" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nach %lld Sekunden" + } + } + } + } + } + } + }, + "Nach einer Wartezeit" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nach einer Wartezeit" + } + } + } + }, + "Nach neuem Konto Testmail anbieten" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nach neuem Konto Testmail anbieten" + } + } + } + }, + "Nachricht" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nachricht" + } + } + } + }, + "Nachricht auswählen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nachricht auswählen" + } + } + } + }, + "Nachricht löschen?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nachricht löschen?" + } + } + } + }, + "Nachrichten durchsuchen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nachrichten durchsuchen" + } + } + } + }, + "Nachrichten löschen…" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nachrichten löschen …" + } + } + } + }, + "Name" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Name" + } + } + } + }, + "Nein" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nein" + } + } + } + }, + "Neue Konten" : { + + }, + "Neue Mails werden bei laufender App sofort gemeldet. Das Prüfintervall dient zusätzlich als Absicherung." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Neue Mails werden bei laufender App sofort gemeldet. Das Prüfintervall dient zusätzlich als Absicherung." + } + } + } + }, + "Neue Nachricht" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Neue Nachricht" + } + } + } + }, + "Neuen Ordner erstellen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Neuen Ordner erstellen" + } + } + } + }, + "Neues Konto" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Neues Konto" + } + } + } + }, + "Neues Konto hinzufügen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Neues Konto hinzufügen" + } + } + } + }, + "Neues Passwort" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Neues Passwort" + } + } + } + }, + "Nie" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nie" + } + } + } + }, + "Nur bei mehreren Nachrichten" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nur bei mehreren Nachrichten" + } + } + } + }, + "Nur manuell" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Nur manuell" + } + } + } + }, + "Öffnen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Öffnen" + } + } + } + }, + "OK" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "OK" + } + } + } + }, + "Ordner löschen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Ordner löschen" + } + } + } + }, + "Ordner löschen?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Ordner löschen?" + } + } + } + }, + "Ordnername" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Ordnername" + } + } + } + }, + "Papierkorb leeren" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Papierkorb leeren" + } + } + } + }, + "Papierkorb leeren?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Papierkorb leeren?" + } + } + } + }, + "Passwort" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Passwort" + } + } + } + }, + "Passwort anzeigen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Passwort anzeigen" + } + } + } + }, + "Passwort erforderlich" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Passwort erforderlich" + } + } + } + }, + "Passwort verbergen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Passwort verbergen" + } + } + } + }, + "Passwörter in das Backup aufnehmen" : { + + }, + "Prüfen und speichern" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Prüfen und speichern" + } + } + } + }, + "Regulärer Ausdruck" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Regulärer Ausdruck" + } + } + } + }, + "Schließen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Schließen" + } + } + } + }, + "Schriftart" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Schriftart" + } + } + } + }, + "Schriftart für Mailtexte" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Schriftart für Mailtexte" + } + } + } + }, + "Schriftgröße" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Schriftgröße" + } + } + } + }, + "Schriftgröße: %lld pt" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Schriftgröße: %lld pt" + } + } + } + }, + "Seitenleiste" : { + + }, + "Senden" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Senden" + } + } + } + }, + "Server" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Server" + } + } + } + }, + "Sicherung" : { + + }, + "SMTP-Port (587 STARTTLS / 465 TLS)" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "SMTP-Port (587 STARTTLS / 465 TLS)" + } + } + } + }, + "Sofort bei Auswahl" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Sofort bei Auswahl" + } + } + } + }, + "Sofortempfang" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Sofortempfang" + } + } + } + }, + "Sofortempfang mit IMAP IDLE" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Sofortempfang mit IMAP IDLE" + } + } + } + }, + "Speichern" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Speichern" + } + } + } + }, + "Suchen in:" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Suchen in:" + } + } + } + }, + "Testmail senden" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Testmail senden" + } + } + } + }, + "Testmail senden?" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Testmail senden?" + } + } + } + }, + "Textfarbe" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Textfarbe" + } + } + } + }, + "Unterstrichen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Unterstrichen" + } + } + } + }, + "Verbindung testen" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Verbindung testen" + } + } + } + }, + "Verhalten" : { + + }, + "Von" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Von" + } + } + } + }, + "Von: %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Von: %@" + } + } + } + }, + "Vorschau Mailtext" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Vorschau Mailtext" + } + } + } + }, + "Wähle einen Ordner oder ändere die Suche." : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Wähle einen Ordner oder ändere die Suche." + } + } + } + }, + "Wartezeit in Sekunden" : { + "extractionState" : "stale", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Wartezeit in Sekunden" + } + } + } + }, + "Weiterleiten" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "Weiterleiten" + } + } + } + } + }, + "version" : "1.3" +} \ No newline at end of file diff --git a/MikeMail/MailModels.swift b/MikeMail/MailModels.swift index 5dafdec..e7abb5d 100644 --- a/MikeMail/MailModels.swift +++ b/MikeMail/MailModels.swift @@ -108,7 +108,7 @@ enum PasswordStore { let status = SecItemCopyMatching(entry as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data, let value = String(data: data, encoding: .utf8) else { - throw MailError.message("Bitte unter „Konten“ das Passwort für dieses Konto speichern.") + throw MailError.authentication("Für dieses Konto fehlt ein gültiges Passwort im Schlüsselbund.") } return value } diff --git a/MikeMail/MailSettings.swift b/MikeMail/MailSettings.swift index 8236e91..dba78e0 100644 --- a/MikeMail/MailSettings.swift +++ b/MikeMail/MailSettings.swift @@ -1,5 +1,6 @@ import SwiftUI import AppKit +import UniformTypeIdentifiers enum MailDeleteConfirmation: String { case always, multiple, never @@ -25,6 +26,11 @@ struct AutoReadRequest: Equatable { } struct MailSettings: View { + let store: MailStore + @State private var includePasswordsInBackup = false + @State private var restoreData: Data? + @State private var confirmingRestore = false + @State private var backupStatus = UserDefaults.standard.string(forKey: SettingsBackupManager.restoreResultKey) @AppStorage("markReadImmediately") private var markReadImmediately = false @AppStorage("markReadDelaySeconds") private var markReadDelaySeconds = 1 @AppStorage("bodyFontName") private var bodyFontName = "Helvetica" @@ -39,44 +45,237 @@ struct MailSettings: View { @AppStorage("showUnifiedInbox") private var showInbox = true @AppStorage("showUnifiedTrash") private var showTrash = true + var body: some View { + TabView { + Tab("Darstellung", systemImage: "textformat") { + DisplaySettingsTab( + bodyFontName: $bodyFontName, + bodyFontSize: $bodyFontSize, + blockRemoteContent: $blockRemoteContent, + showInbox: $showInbox, + showTrash: $showTrash, + fontNames: fontNames + ) + } + Tab("Abruf", systemImage: "arrow.clockwise") { + RetrievalSettingsTab( + idleEnabled: $idleEnabled, + pollMinutes: $pollMinutes, + offerTestMail: $offerTestMail, + checkOnLaunch: $checkOnLaunch + ) + } + Tab("Verhalten", systemImage: "slider.horizontal.3") { + BehaviorSettingsTab( + markReadImmediately: $markReadImmediately, + markReadDelaySeconds: $markReadDelaySeconds, + deleteConfirmation: $deleteConfirmation + ) + } + Tab("Sicherung", systemImage: "externaldrive") { + BackupSettingsTab( + includePasswords: $includePasswordsInBackup, + status: backupStatus, + save: saveSettingsBackup, + restore: selectSettingsBackup + ) + } + } + .padding(24) + .frame(width: 940) + .frame(minHeight: 620) + .onAppear { + if backupStatus != nil { + UserDefaults.standard.removeObject(forKey: SettingsBackupManager.restoreResultKey) + } + } + .alert("Einstellungen wiederherstellen?", isPresented: $confirmingRestore) { + Button("Abbrechen", role: .cancel) { restoreData = nil } + Button("Beim nächsten Start wiederherstellen", role: .destructive) { + stageSettingsRestore() + } + } message: { + Text("Die aktuellen Einstellungen und Konten werden beim nächsten Start ersetzt. Mails und Anhänge bleiben unverändert. MikeMail wird jetzt automatisch beendet; öffne die App anschließend erneut.") + } + } + + private func saveSettingsBackup() { + do { + let backup = try SettingsBackupManager.create( + accounts: store.accounts, + includePasswords: includePasswordsInBackup + ) + let panel = NSSavePanel() + panel.allowedContentTypes = [.json] + panel.nameFieldStringValue = "MikeMail-Einstellungen.json" + panel.title = "Einstellungen sichern" + guard panel.runModal() == .OK, let url = panel.url else { return } + try backup.data.write(to: url, options: .atomic) + backupStatus = backup.missingPasswords == 0 + ? "Einstellungen und \(store.accounts.count) Konten wurden gesichert." + : "Backup gespeichert. Für \(backup.missingPasswords) Konten war kein Passwort im Schlüsselbund vorhanden." + } catch { + backupStatus = error.localizedDescription + } + } + + private func selectSettingsBackup() { + let panel = NSOpenPanel() + panel.allowedContentTypes = [.json] + panel.allowsMultipleSelection = false + panel.canChooseDirectories = false + panel.title = "Einstellungen wiederherstellen" + guard panel.runModal() == .OK, let url = panel.url else { return } + do { + let data = try Data(contentsOf: url) + _ = try SettingsBackupManager.validate(data) + restoreData = data + confirmingRestore = true + } catch { + backupStatus = error.localizedDescription + } + } + + private func stageSettingsRestore() { + guard let restoreData else { return } + do { + try SettingsBackupManager.stageRestore(restoreData) + backupStatus = "Backup geprüft. MikeMail wird beendet und stellt die Einstellungen beim nächsten Start wieder her." + NSApp.terminate(nil) + } catch { + backupStatus = error.localizedDescription + } + self.restoreData = nil + } +} + +private struct DisplaySettingsTab: View { + @Binding var bodyFontName: String + @Binding var bodyFontSize: Double + @Binding var blockRemoteContent: Bool + @Binding var showInbox: Bool + @Binding var showTrash: Bool + let fontNames: [String] + var body: some View { Form { - Picker("Schriftart für Mailtexte", selection: $bodyFontName) { - ForEach(fontNames, id: \.self) { Text($0).tag($0) } + Section("Maildarstellung") { + Picker("Schriftart für Mailtexte", selection: $bodyFontName) { + ForEach(fontNames, id: \.self) { Text($0).tag($0) } + } + Stepper("Schriftgröße: \(Int(bodyFontSize)) pt", value: $bodyFontSize, in: 8...48) + Text("Vorschau Mailtext") + .font(.custom(bodyFontName, size: bodyFontSize)) + .padding(.top, 8) + Toggle("Externe Inhalte in HTML-Mails blockieren", isOn: $blockRemoteContent) + Text("Gilt beim Öffnen einer Mail. Inhalte können für eine einzelne Mail freigegeben werden. Eigene HTML-Schriftformate bleiben erhalten.") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) } - Stepper("Schriftgröße: \(Int(bodyFontSize)) pt", value: $bodyFontSize, in: 8...48) - Text("Vorschau Mailtext").font(.custom(bodyFontName, size: bodyFontSize)) - Toggle("Externe Inhalte in HTML-Mails blockieren", isOn: $blockRemoteContent) - Text("Gilt beim Öffnen einer Mail. Inhalte können für eine einzelne Mail freigegeben werden. Eigene HTML-Schriftformate bleiben erhalten.") - .font(.caption).foregroundStyle(.secondary) - Divider() - Toggle("Sofortempfang mit IMAP IDLE", isOn: $idleEnabled) - Text("Neue Mails werden bei laufender App sofort gemeldet. Das Prüfintervall dient zusätzlich als Absicherung.").font(.caption).foregroundStyle(.secondary) - Picker("Auf neue Mails prüfen", selection: $pollMinutes) { - Text("Nur manuell").tag(0) - ForEach([1, 2, 5, 10, 15, 30, 60], id: \.self) { Text("Alle \($0) Minuten").tag($0) } + Section("Seitenleiste") { + Toggle("Gemeinsamen Posteingang anzeigen", isOn: $showInbox) + Toggle("Gemeinsamen Papierkorb anzeigen", isOn: $showTrash) } - Toggle("Nach neuem Konto Testmail anbieten", isOn: $offerTestMail) - Toggle("Beim Start automatisch auf neue Mails prüfen", isOn: $checkOnLaunch) - Toggle("Gemeinsamen Posteingang anzeigen", isOn: $showInbox) - Toggle("Gemeinsamen Papierkorb anzeigen", isOn: $showTrash) - Picker("Als gelesen markieren", selection: $markReadImmediately) { - Text("Sofort bei Auswahl").tag(true) - Text("Nach einer Wartezeit").tag(false) - } - if !markReadImmediately { - Stepper("Nach \(markReadDelaySeconds) Sekunden", value: $markReadDelaySeconds, in: 1...3600) - TextField("Wartezeit in Sekunden", value: $markReadDelaySeconds, format: .number) - .onChange(of: markReadDelaySeconds) { _, value in - markReadDelaySeconds = min(3600, max(1, value)) + } + .formStyle(.grouped) + .padding(24) + } +} + +private struct RetrievalSettingsTab: View { + @Binding var idleEnabled: Bool + @Binding var pollMinutes: Int + @Binding var offerTestMail: Bool + @Binding var checkOnLaunch: Bool + + var body: some View { + Form { + Section("Mailabruf") { + Toggle("Sofortempfang mit IMAP IDLE", isOn: $idleEnabled) + Text("Neue Mails werden bei laufender App sofort gemeldet. Das Prüfintervall dient zusätzlich als Absicherung.") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + Picker("Auf neue Mails prüfen", selection: $pollMinutes) { + Text("Nur manuell").tag(0) + ForEach([1, 2, 5, 10, 15, 30, 60], id: \.self) { + Text("Alle \($0) Minuten").tag($0) } + } + Toggle("Beim Start automatisch auf neue Mails prüfen", isOn: $checkOnLaunch) } - Picker("Beim Löschen von Mails nachfragen", selection: $deleteConfirmation) { - Text("Immer").tag(MailDeleteConfirmation.always) - Text("Nur bei mehreren Nachrichten").tag(MailDeleteConfirmation.multiple) - Text("Nie").tag(MailDeleteConfirmation.never) + Section("Neue Konten") { + Toggle("Nach neuem Konto Testmail anbieten", isOn: $offerTestMail) } - }.padding(24).frame(width: 460) + } + .formStyle(.grouped) + .padding(24) + } +} + +private struct BehaviorSettingsTab: View { + @Binding var markReadImmediately: Bool + @Binding var markReadDelaySeconds: Int + @Binding var deleteConfirmation: MailDeleteConfirmation + + var body: some View { + Form { + Section("Lesestatus") { + Picker("Als gelesen markieren", selection: $markReadImmediately) { + Text("Sofort bei Auswahl").tag(true) + Text("Nach einer Wartezeit").tag(false) + } + if !markReadImmediately { + Stepper("Nach \(markReadDelaySeconds) Sekunden", value: $markReadDelaySeconds, in: 1...3600) + } + } + Section("Löschen") { + Picker("Beim Löschen von Mails nachfragen", selection: $deleteConfirmation) { + Text("Immer").tag(MailDeleteConfirmation.always) + Text("Nur bei mehreren Nachrichten").tag(MailDeleteConfirmation.multiple) + Text("Nie").tag(MailDeleteConfirmation.never) + } + } + } + .formStyle(.grouped) + .padding(24) + } +} + +private struct BackupSettingsTab: View { + @Binding var includePasswords: Bool + let status: String? + let save: () -> Void + let restore: () -> Void + + var body: some View { + Form { + Section("Einstellungen und Konten") { + Text("Das Backup enthält App-Einstellungen, Konten sowie gespeicherte Fenster-, Split- und Tabellenlayouts. Mails und Anhänge werden nicht gesichert.") + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + Toggle("Passwörter in das Backup aufnehmen", isOn: $includePasswords) + if includePasswords { + Text("Das Backup enthält die Passwörter im Klartext. Bewahre die Datei an einem geschützten Ort auf.") + .font(.caption) + .foregroundStyle(.orange) + .fixedSize(horizontal: false, vertical: true) + } + HStack { + Button("Einstellungen sichern …", systemImage: "square.and.arrow.up", action: save) + Button("Einstellungen wiederherstellen …", systemImage: "square.and.arrow.down", action: restore) + } + if let status { + Text(status) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } + } + .formStyle(.grouped) + .padding(24) } } diff --git a/MikeMail/MailStore.swift b/MikeMail/MailStore.swift index 8254475..6d48588 100644 --- a/MikeMail/MailStore.swift +++ b/MikeMail/MailStore.swift @@ -313,7 +313,11 @@ final class MailStore { do { try await client.login(account: account, password: PasswordStore.password(for: id)) return client - } catch { client.close(); throw error } + } catch { + client.close() + if case MailError.authentication = error { requestPassword(accountID: id) } + throw error + } } /// Message mutations commit their confirmed changes locally; folder operations rediscover the tree. diff --git a/MikeMail/MikeMail-InfoPlist.xcstrings b/MikeMail/MikeMail-InfoPlist.xcstrings new file mode 100644 index 0000000..e6cfb43 --- /dev/null +++ b/MikeMail/MikeMail-InfoPlist.xcstrings @@ -0,0 +1,42 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "CFBundleName" : { + "comment" : "Bundle name", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "MikeMail" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "MikeMail" + } + } + } + }, + "NSHumanReadableCopyright" : { + "comment" : "Copyright (human-readable)", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "machine_translated", + "value" : "" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + } + } + }, + "version" : "1.3" +} \ No newline at end of file diff --git a/MikeMail/MikeMailApp.swift b/MikeMail/MikeMailApp.swift index 65205b0..a7a6f6b 100644 --- a/MikeMail/MikeMailApp.swift +++ b/MikeMail/MikeMailApp.swift @@ -3,6 +3,7 @@ import SwiftUI @main struct MikeMailApp: App { @State private var store: MailStore = { + SettingsBackupManager.applyPendingRestoreIfNeeded() #if DEBUG if ProcessInfo.processInfo.arguments.contains("--ui-fixture") { return MailStore.uiFixture() } #endif @@ -14,6 +15,7 @@ struct MikeMailApp: App { ContentView(store: store) } .defaultSize(width: 1200, height: 800) + .commands { AccountCommands() } WindowGroup("Nachricht", id: "message", for: String.self) { $id in MessageDetailWindow(store: store, messageID: id) }.defaultSize(width: 800, height: 650) @@ -24,6 +26,6 @@ struct MikeMailApp: App { } .defaultSize(width: 760, height: 780) .windowResizability(.contentMinSize) - Settings { MailSettings() } + Settings { MailSettings(store: store) } } } diff --git a/MikeMail/PasswordRecoveryView.swift b/MikeMail/PasswordRecoveryView.swift index 2be5d86..9d11102 100644 --- a/MikeMail/PasswordRecoveryView.swift +++ b/MikeMail/PasswordRecoveryView.swift @@ -5,6 +5,7 @@ struct PasswordRecoveryView: View { let account: MailAccount @State private var password = "" @State private var checking = false + @State private var showsPassword = false @State private var error: String? @FocusState private var passwordFocused: Bool @@ -12,9 +13,23 @@ struct PasswordRecoveryView: View { VStack(alignment: .leading, spacing: 16) { Text("Passwort erforderlich").font(.title2).bold() Text("Der Mailserver hat die Anmeldung für \(account.email) abgelehnt.") - SecureField("Neues Passwort", text: $password) - .textContentType(.password) - .focused($passwordFocused) + HStack(spacing: 8) { + if showsPassword { + TextField("Neues Passwort", text: $password) + .textContentType(.password) + .focused($passwordFocused) + } else { + SecureField("Neues Passwort", text: $password) + .textContentType(.password) + .focused($passwordFocused) + } + Button(showsPassword ? "Passwort verbergen" : "Passwort anzeigen", + systemImage: showsPassword ? "eye.slash" : "eye") { + showsPassword.toggle() + } + .labelStyle(.iconOnly) + .buttonStyle(.borderless) + } Text("Das Passwort wird erst nach erfolgreicher Anmeldung im Schlüsselbund gespeichert.") .font(.caption).foregroundStyle(.secondary) if let error { Text(error).foregroundStyle(.red) } diff --git a/MikeMail/SettingsBackup.swift b/MikeMail/SettingsBackup.swift new file mode 100644 index 0000000..1124050 --- /dev/null +++ b/MikeMail/SettingsBackup.swift @@ -0,0 +1,125 @@ +import Foundation + +struct SettingsBackup: Codable { + let formatVersion: Int + let createdAt: Date + let preferences: Data + let accounts: [MailAccount] + let passwords: [UUID: String]? +} + +enum SettingsBackupManager { + private static let formatVersion = 1 + private static let pendingRestoreKey = "MikeMail.PendingSettingsRestore" + static let restoreResultKey = "MikeMail.SettingsRestoreResult" + + static func create(accounts: [MailAccount], includePasswords: Bool) throws -> (data: Data, missingPasswords: Int) { + guard let bundleIdentifier = Bundle.main.bundleIdentifier else { + throw MailError.message("Die App-Einstellungen konnten nicht ermittelt werden.") + } + + var preferences = UserDefaults.standard.persistentDomain(forName: bundleIdentifier) ?? [:] + preferences[pendingRestoreKey] = nil + preferences[restoreResultKey] = nil + let preferencesData = try PropertyListSerialization.data( + fromPropertyList: preferences, + format: .binary, + options: 0 + ) + + var passwords: [UUID: String]? + var missingPasswords = 0 + if includePasswords { + var storedPasswords: [UUID: String] = [:] + for account in accounts { + do { + storedPasswords[account.id] = try PasswordStore.password(for: account.id) + } catch { + missingPasswords += 1 + } + } + passwords = storedPasswords + } + + let backup = SettingsBackup( + formatVersion: formatVersion, + createdAt: Date(), + preferences: preferencesData, + accounts: accounts, + passwords: passwords + ) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + encoder.dateEncodingStrategy = .iso8601 + return (try encoder.encode(backup), missingPasswords) + } + + static func validate(_ data: Data) throws -> SettingsBackup { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + let backup: SettingsBackup + do { + backup = try decoder.decode(SettingsBackup.self, from: data) + } catch { + throw MailError.message("Die ausgewählte Datei ist kein gültiges MikeMail-Einstellungsbackup.") + } + guard backup.formatVersion == formatVersion else { + throw MailError.message("Diese Backup-Version wird nicht unterstützt.") + } + guard (try PropertyListSerialization.propertyList(from: backup.preferences, options: [], format: nil)) is [String: Any] else { + throw MailError.message("Das Backup enthält keine gültigen App-Einstellungen.") + } + return backup + } + + static func stageRestore(_ data: Data) throws { + _ = try validate(data) + UserDefaults.standard.set(data, forKey: pendingRestoreKey) + } + + static func applyPendingRestoreIfNeeded() { + let defaults = UserDefaults.standard + guard let data = defaults.data(forKey: pendingRestoreKey) else { return } + + do { + let backup = try validate(data) + guard let bundleIdentifier = Bundle.main.bundleIdentifier, + var preferences = try PropertyListSerialization.propertyList( + from: backup.preferences, + options: [], + format: nil + ) as? [String: Any] else { + throw MailError.message("Die App-Einstellungen im Backup sind ungültig.") + } + + preferences[pendingRestoreKey] = nil + preferences[restoreResultKey] = nil + defaults.setPersistentDomain(preferences, forName: bundleIdentifier) + + let archive = try MailArchive() + let current = try archive.configuration() + let restoredAccountIDs = Set(backup.accounts.map(\.id)) + let retainedFolders = (current?.folders ?? [:]).filter { restoredAccountIDs.contains($0.key) } + try archive.saveConfiguration(ArchiveConfiguration( + accounts: backup.accounts, + folders: retainedFolders, + recipients: current?.recipients ?? [] + )) + + for (accountID, password) in backup.passwords ?? [:] { + try PasswordStore.save(password, for: accountID) + } + + defaults.set( + "Einstellungen und \(backup.accounts.count) Konten wurden wiederhergestellt.", + forKey: restoreResultKey + ) + } catch { + defaults.removeObject(forKey: pendingRestoreKey) + defaults.set( + "Wiederherstellung fehlgeschlagen: \(error.localizedDescription)", + forKey: restoreResultKey + ) + } + } +}