From bbfbd976ed0828bfcf57159e066c90fe5f465699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Dr=2E=20med=2E=20K=C3=BCbler?= Date: Sun, 8 Nov 2026 22:00:47 +0100 Subject: [PATCH] Alpha 5 --- MailNotifications.swift | 70 ++++++ MikeMail.xcodeproj/project.xcproj | 10 + .../UserInterfaceState.xcuserstate | Bin 132546 -> 145770 bytes MikeMail/AccountOrderDrop.swift | 12 +- MikeMail/AccountsView.swift | 4 + MikeMail/ContentView.swift | 227 +++++++++++++++--- MikeMail/Localizable.xcstrings | 90 +++++++ MikeMail/MIME.swift | 16 +- MikeMail/MailBodyEditor.swift | 74 +++++- MikeMail/MailIdleMonitor.swift | 1 - MikeMail/MailModels.swift | 45 +++- MikeMail/MailPrinter.swift | 94 ++++++++ MikeMail/MailSettings.swift | 45 ++-- MikeMail/MailStore.swift | 104 ++++++-- MikeMail/MessageActions.swift | 22 ++ MikeMail/MikeMailApp.swift | 3 + MikeMail/RecipientField.swift | 8 +- MikeMail/WindowCloseGuard.swift | 80 ++++++ MikeMailTests/MailIdleTests.swift | 2 - 19 files changed, 804 insertions(+), 103 deletions(-) create mode 100644 MailNotifications.swift create mode 100644 MikeMail/MailPrinter.swift create mode 100644 MikeMail/WindowCloseGuard.swift diff --git a/MailNotifications.swift b/MailNotifications.swift new file mode 100644 index 0000000..c7ca10c --- /dev/null +++ b/MailNotifications.swift @@ -0,0 +1,70 @@ +import AppKit +import UserNotifications + +final class MailNotificationDelegate: NSObject, UNUserNotificationCenterDelegate { + static let shared = MailNotificationDelegate() + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + completionHandler([.banner, .list, .sound]) + } +} + +struct NewMailNotification: Sendable { + let sender: String + let subject: String +} + +@MainActor +enum MailNotifications { + static let enabledKey = "newMailNotifications" + + static func configure() { + UNUserNotificationCenter.current().delegate = MailNotificationDelegate.shared + } + + static func requestAuthorization() async -> Bool { + let center = UNUserNotificationCenter.current() + let settings = await center.notificationSettings() + switch settings.authorizationStatus { + case .authorized, .provisional: + return true + case .notDetermined: + return (try? await center.requestAuthorization(options: [.alert, .sound])) == true + default: + return false + } + } + + static func postNewMail(_ messages: [NewMailNotification], badgeCount: Int) async { + guard !messages.isEmpty, UserDefaults.standard.bool(forKey: enabledKey) else { return } + guard await requestAuthorization() else { return } + + for message in messages { + let content = UNMutableNotificationContent() + content.title = String(localized: "Neue Mail") + content.subtitle = message.sender.isEmpty + ? String(localized: "Unbekannter Absender") + : message.sender + content.body = message.subject.isEmpty + ? String(localized: "(Ohne Betreff)") + : message.subject + content.sound = .default + content.badge = NSNumber(value: badgeCount) + + let request = UNNotificationRequest( + identifier: UUID().uuidString, + content: content, + trigger: nil + ) + try? await UNUserNotificationCenter.current().add(request) + } + } + + static func updateBadge(_ count: Int) { + NSApp.dockTile.badgeLabel = count > 0 ? String(count) : nil + } +} diff --git a/MikeMail.xcodeproj/project.xcproj b/MikeMail.xcodeproj/project.xcproj index 12ab648..85d784e 100644 --- a/MikeMail.xcodeproj/project.xcproj +++ b/MikeMail.xcodeproj/project.xcproj @@ -15,6 +15,7 @@ { "kind": "folder", "path": "MikeMail", "target-membership": [ "MikeMail" ] }, { "kind": "folder", "path": "MikeMailTests", "target-membership": [ "MikeMailTests" ] }, { "kind": "folder", "path": "MikeMailUITests", "target-membership": [ "MikeMailUITests" ] }, + { "path": "MailNotifications.swift", "target-membership": [ "MikeMail/compile-sources" ] }, { "kind": "group", "name": "Products", @@ -45,8 +46,17 @@ "DEVELOPMENT_TEAM": "GQR972L9NJ", "ENABLE_APP_SANDBOX": "YES", "ENABLE_HARDENED_RUNTIME": "YES", + "ENABLE_INCOMING_NETWORK_CONNECTIONS": "NO", "ENABLE_OUTGOING_NETWORK_CONNECTIONS": "YES", "ENABLE_PREVIEWS": "YES", + "ENABLE_RESOURCE_ACCESS_AUDIO_INPUT": "NO", + "ENABLE_RESOURCE_ACCESS_BLUETOOTH": "NO", + "ENABLE_RESOURCE_ACCESS_CALENDARS": "NO", + "ENABLE_RESOURCE_ACCESS_CAMERA": "NO", + "ENABLE_RESOURCE_ACCESS_CONTACTS": "NO", + "ENABLE_RESOURCE_ACCESS_LOCATION": "NO", + "ENABLE_RESOURCE_ACCESS_PRINTING": "YES", + "ENABLE_RESOURCE_ACCESS_USB": "NO", "ENABLE_USER_SELECTED_FILES": "readwrite", "GENERATE_INFOPLIST_FILE": "YES", "INFOPLIST_KEY_NSHumanReadableCopyright": "", diff --git a/MikeMail.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/UserInterfaceState.xcuserstate b/MikeMail.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/UserInterfaceState.xcuserstate index 3c669f17311cb39fcff285d38020a652ad882851..6038d755f036e2b46766dd6cce26c216a333aac4 100644 GIT binary patch literal 145770 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" + } + + let ccRow = message.cc.isEmpty + ? "" + : "CC:\(escaped(message.cc))" + let html = """ + + + + + + + + \(ccRow) + +
Betreff:\(escaped(message.subject))
Von:\(escaped(message.sender))
An:\(escaped(message.recipient))
Datum:\(escaped(message.date.formatted(date: .long, time: .shortened)))
+
+
\(body)
+ + """ + + return try? NSAttributedString( + data: Data(html.utf8), + options: [ + .documentType: NSAttributedString.DocumentType.html, + .characterEncoding: String.Encoding.utf8.rawValue + ], + documentAttributes: nil + ) + } + + private static func escaped(_ text: String) -> String { + text + .replacingOccurrences(of: "&", with: "&") + .replacingOccurrences(of: "<", with: "<") + .replacingOccurrences(of: ">", with: ">") + .replacingOccurrences(of: "\"", with: """) + } +} diff --git a/MikeMail/MailSettings.swift b/MikeMail/MailSettings.swift index dba78e0..1e1b70a 100644 --- a/MikeMail/MailSettings.swift +++ b/MikeMail/MailSettings.swift @@ -38,10 +38,10 @@ struct MailSettings: View { @AppStorage("blockRemoteContent") private var blockRemoteContent = true private let fontNames = NSFontManager.shared.availableFontFamilies.sorted() @AppStorage("mailDeleteConfirmation") private var deleteConfirmation: MailDeleteConfirmation = .always - @AppStorage("idleEnabled") private var idleEnabled = true - @AppStorage("pollMinutes") private var pollMinutes = 5 @AppStorage("offerTestMail") private var offerTestMail = true @AppStorage("checkOnLaunch") private var checkOnLaunch = true + @AppStorage(AttachmentMentionDetector.settingKey) private var warnMissingAttachment = true + @AppStorage(MailNotifications.enabledKey) private var newMailNotifications = false @AppStorage("showUnifiedInbox") private var showInbox = true @AppStorage("showUnifiedTrash") private var showTrash = true @@ -59,17 +59,17 @@ struct MailSettings: View { } Tab("Abruf", systemImage: "arrow.clockwise") { RetrievalSettingsTab( - idleEnabled: $idleEnabled, - pollMinutes: $pollMinutes, offerTestMail: $offerTestMail, - checkOnLaunch: $checkOnLaunch + checkOnLaunch: $checkOnLaunch, + newMailNotifications: $newMailNotifications ) } Tab("Verhalten", systemImage: "slider.horizontal.3") { BehaviorSettingsTab( markReadImmediately: $markReadImmediately, markReadDelaySeconds: $markReadDelaySeconds, - deleteConfirmation: $deleteConfirmation + deleteConfirmation: $deleteConfirmation, + warnMissingAttachment: $warnMissingAttachment ) } Tab("Sicherung", systemImage: "externaldrive") { @@ -184,27 +184,28 @@ private struct DisplaySettingsTab: View { } private struct RetrievalSettingsTab: View { - @Binding var idleEnabled: Bool - @Binding var pollMinutes: Int @Binding var offerTestMail: Bool @Binding var checkOnLaunch: Bool + @Binding var newMailNotifications: 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) } + Section("Mitteilungen") { + Toggle("Bei neuen Mails benachrichtigen", isOn: $newMailNotifications) + .onChange(of: newMailNotifications) { _, enabled in + guard enabled else { return } + Task { + if await !MailNotifications.requestAuthorization() { + newMailNotifications = false + } + } + } + Text("Die Anzahl ungelesener Mails wird immer am App-Symbol angezeigt.") + .foregroundStyle(.secondary) + } Section("Neue Konten") { Toggle("Nach neuem Konto Testmail anbieten", isOn: $offerTestMail) } @@ -218,6 +219,7 @@ private struct BehaviorSettingsTab: View { @Binding var markReadImmediately: Bool @Binding var markReadDelaySeconds: Int @Binding var deleteConfirmation: MailDeleteConfirmation + @Binding var warnMissingAttachment: Bool var body: some View { Form { @@ -230,6 +232,11 @@ private struct BehaviorSettingsTab: View { Stepper("Nach \(markReadDelaySeconds) Sekunden", value: $markReadDelaySeconds, in: 1...3600) } } + Section("Verfassen") { + Toggle("Vor möglicherweise vergessenem Anhang warnen", isOn: $warnMissingAttachment) + Text("Prüft Betreff und Nachrichtentext vor dem Senden auf Hinweise wie „im Anhang“, „anbei“ oder „beigefügt“.") + .foregroundStyle(.secondary) + } Section("Löschen") { Picker("Beim Löschen von Mails nachfragen", selection: $deleteConfirmation) { Text("Immer").tag(MailDeleteConfirmation.always) diff --git a/MikeMail/MailStore.swift b/MikeMail/MailStore.swift index 6d48588..654c96d 100644 --- a/MikeMail/MailStore.swift +++ b/MikeMail/MailStore.swift @@ -21,7 +21,6 @@ final class MailStore { private var selectedFolderID: String? private var didStart = false private let idleMonitor = MailIdleMonitor() - private var idleEnabled = false private var pendingIdleAccounts: Set = [] private var idleRefreshGeneration = UUID() private var idleRefreshTask: Task? @@ -88,7 +87,7 @@ final class MailStore { validatedAccounts[account.id] = nil validatedPasswords[account.id] = nil status = "Konto lokal gespeichert." - configureIdle(enabled: idleEnabled) + configureIdle() idleMonitor.restart(accountID: account.id) } @@ -100,7 +99,7 @@ final class MailStore { remainingFolders[id] = nil try archive.saveConfiguration(ArchiveConfiguration(accounts: remaining, folders: remainingFolders, recipients: recipientHistory)) accounts = remaining - configureIdle(enabled: idleEnabled) + configureIdle() folders = remainingFolders messages = [] totalMessages = 0 @@ -131,11 +130,19 @@ final class MailStore { items.flatMap { [$0] + allFolders($0.children ?? []) } } + var totalUnreadCount: Int { + accounts + .flatMap { allFolders(folders[$0.id] ?? []) } + .filter(\.isInbox) + .reduce(0) { $0 + (unreadCounts[$1.id] ?? 0) } + } + func refresh(folderID: String?, accountID: UUID? = nil, inboxOnly: Bool = false) async { guard !isBusy else { return } isBusy = true defer { isBusy = false } var failures: [String] = [] + var newInboxMessages: [NewMailNotification] = [] do { let archive = try storage() for account in accounts where accountID == nil || account.id == accountID { @@ -186,6 +193,13 @@ final class MailStore { try Task.checkCancellation() status = "\(folder.name): \(index + 1) von \(missing.count) neuen Mails speichern …" let raw = try await client.rawMessage(uid: uid) + if inboxOnly && folder.isInbox { + let headers = MIME.split(raw).0 + newInboxMessages.append(NewMailNotification( + sender: MailDisplay.sender(MIME.decodeHeader(headers["from"] ?? "")), + subject: MIME.decodeHeader(headers["subject"] ?? "(Ohne Betreff)") + )) + } try archive.store(raw: raw, folderID: folder.id, validity: inventory.validity, uid: String(uid), flags: inventory.flags[uid] ?? [], visible: false) } @@ -201,6 +215,10 @@ final class MailStore { unreadCounts = try archive.unreadCounts() archiveRevision += 1 if let selectedFolderID { try display(folderID: selectedFolderID) } + await MailNotifications.postNewMail( + newInboxMessages, + badgeCount: totalUnreadCount + ) } catch { failures.append(error.localizedDescription) } if failures.isEmpty { status = "Abgleich abgeschlossen. Alle abgerufenen Mails sind offline verfügbar." @@ -244,7 +262,7 @@ final class MailStore { let copies = Self.addresses(cc) let blindCopies = Self.addresses(bcc) let envelope = Array(Set(addresses + copies + blindCopies)).sorted() - let data = try MIME.compose(from: account.email, to: addresses, cc: copies, bcc: blindCopies, subject: subject, body: body, attachments: attachments, htmlBody: htmlBody) + let data = try MIME.compose(from: account.email, senderName: account.displayName, to: addresses, cc: copies, bcc: blindCopies, subject: subject, body: body, attachments: attachments, htmlBody: htmlBody) let localFolder = MailFolder(accountID: account.id, path: "__local_outgoing__", name: "Lokale Versandkopien") let localID = UUID().uuidString // Persist BEFORE SMTP, so a crash or uncertain delivery cannot lose the composed message. @@ -266,6 +284,58 @@ final class MailStore { } } + func saveDraft(accountID: UUID?, recipients: String, cc: String, bcc: String, subject: String, + body: String, attachments: [MailAttachment], htmlBody: String?) async throws { + guard let account = accounts.first(where: { $0.id == accountID }) else { + throw MailError.message("Bitte ein Absenderkonto auswählen.") + } + guard !sendingAccountIDs.contains(account.id) else { + throw MailError.message("Für dieses Konto läuft bereits ein Vorgang.") + } + sendingAccountIDs.insert(account.id) + defer { sendingAccountIDs.remove(account.id) } + + let client = try IMAPClient(account: account) + defer { client.close() } + try await client.login(account: account, password: PasswordStore.password(for: account.id)) + + var discovered = try await client.folders(accountID: account.id) + var draftsFolder = allFolders(discovered).first { $0.specialUse?.contains("\\drafts") == true } + ?? allFolders(discovered).first { + ["drafts", "entwürfe", "entwuerfe"].contains($0.name.lowercased()) + } + if draftsFolder == nil { + try await client.createFolder(path: "Drafts") + discovered = try await client.folders(accountID: account.id) + draftsFolder = allFolders(discovered).first { $0.specialUse?.contains("\\drafts") == true } + ?? allFolders(discovered).first { $0.path == "Drafts" } + } + guard let draftsFolder else { + throw MailError.message("Der Entwürfe-Ordner konnte nicht gefunden oder erstellt werden.") + } + + let data = try MIME.compose( + from: account.email, + senderName: account.displayName, + to: Self.addresses(recipients), + cc: Self.addresses(cc), + bcc: Self.addresses(bcc), + subject: subject, + body: body, + attachments: attachments, + htmlBody: htmlBody, + allowEmptyRecipients: true, + includeBccHeader: true + ) + try await client.append(data, path: draftsFolder.path, flags: ["\\Draft"]) + + folders[account.id] = discovered + try persist() + status = "Entwurf gespeichert." + while isBusy { try await Task.sleep(for: .milliseconds(200)) } + await refresh(folderID: selectedFolderID, accountID: account.id) + } + func start(folderID: String?) async { await load(folderID: folderID) guard !didStart else { return } @@ -536,25 +606,9 @@ final class MailStore { } } - func poll(intervalMinutes: Int) async { - guard intervalMinutes > 0 else { return } - while !Task.isCancelled { - do { try await Task.sleep(for: .seconds(max(1, intervalMinutes) * 60)) } - catch { return } - await refresh(folderID: selectedFolderID) - } - } - - func configureIdle(enabled: Bool) { - idleEnabled = enabled - if !enabled { - pendingIdleAccounts.removeAll() - idleRefreshGeneration = UUID() - idleRefreshTask?.cancel() - idleRefreshTask = nil - idleStatuses = [:] - } else { idleStatuses = idleStatuses.filter { id, _ in accounts.contains { $0.id == id } } } - idleMonitor.configure(accounts: accounts, enabled: enabled) { [weak self] id in + func configureIdle() { + idleStatuses = idleStatuses.filter { id, _ in accounts.contains { $0.id == id } } + idleMonitor.configure(accounts: accounts, enabled: true) { [weak self] id in self?.scheduleIdleRefresh(accountID: id) } report: { [weak self] id, status in self?.idleStatuses[id] = status @@ -564,7 +618,7 @@ final class MailStore { } private func scheduleIdleRefresh(accountID: UUID) { - guard idleEnabled, accounts.contains(where: { $0.id == accountID }) else { return } + guard accounts.contains(where: { $0.id == accountID }) else { return } pendingIdleAccounts.insert(accountID) guard idleRefreshTask == nil else { return } let generation = UUID() @@ -574,7 +628,7 @@ final class MailStore { defer { if self.idleRefreshGeneration == generation { self.idleRefreshTask = nil } } // Coalesce bursts and retain notifications arriving during another synchronization. try? await Task.sleep(for: .milliseconds(400)) - while !Task.isCancelled, self.idleEnabled, !self.pendingIdleAccounts.isEmpty { + while !Task.isCancelled, !self.pendingIdleAccounts.isEmpty { if self.isBusy { do { try await Task.sleep(for: .milliseconds(200)) } catch { return } continue diff --git a/MikeMail/MessageActions.swift b/MikeMail/MessageActions.swift index 1af3f44..abfe5f0 100644 --- a/MikeMail/MessageActions.swift +++ b/MikeMail/MessageActions.swift @@ -8,11 +8,27 @@ struct ComposeDraft: Identifiable { let id = UUID() var accountID: UUID? var recipient = "" + var cc = "" + var bcc = "" var subject = "" var body = "" + var htmlBody: String? var attachments: [MailAttachment] = [] var isReply = false + static func saved(_ message: MailMessage, accountID: UUID?) -> ComposeDraft { + ComposeDraft( + accountID: accountID, + recipient: message.recipient, + cc: message.cc, + bcc: message.bcc, + subject: message.subject, + body: message.body, + htmlBody: message.htmlBody, + attachments: message.attachments + ) + } + static func from(_ message: MailMessage, accountID: UUID?, forwarding: Bool) -> ComposeDraft { let source = message.replyTo.isEmpty ? message.sender : message.replyTo let recipient: String @@ -55,6 +71,12 @@ struct MessageDetailWindow: View { .frame(minWidth: 600, minHeight: 450) .navigationTitle(message?.subject ?? "Nachricht") .toolbar { + Button("Drucken …", systemImage: "printer") { + guard let message else { return } + MailPrinter.print(message) + } + .disabled(message == nil) + MessageActionButtons { command in guard let message else { return } switch command { diff --git a/MikeMail/MikeMailApp.swift b/MikeMail/MikeMailApp.swift index a7a6f6b..c81c657 100644 --- a/MikeMail/MikeMailApp.swift +++ b/MikeMail/MikeMailApp.swift @@ -4,6 +4,9 @@ import SwiftUI struct MikeMailApp: App { @State private var store: MailStore = { SettingsBackupManager.applyPendingRestoreIfNeeded() + UserDefaults.standard.removeObject(forKey: "idleEnabled") + UserDefaults.standard.removeObject(forKey: "pollMinutes") + MailNotifications.configure() #if DEBUG if ProcessInfo.processInfo.arguments.contains("--ui-fixture") { return MailStore.uiFixture() } #endif diff --git a/MikeMail/RecipientField.swift b/MikeMail/RecipientField.swift index db552f9..48a46b8 100644 --- a/MikeMail/RecipientField.swift +++ b/MikeMail/RecipientField.swift @@ -5,7 +5,7 @@ import ContactsUI enum RecipientCompletion { static func matches(in history: [String], input: String) -> [String] { - let parts = input.components(separatedBy: ",") + let parts = input.components(separatedBy: CharacterSet(charactersIn: ",;")) let query = (parts.last ?? "").trimmingCharacters(in: .whitespacesAndNewlines) guard !query.isEmpty else { return [] } let selected = Set(parts.dropLast().map { $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() }) @@ -16,11 +16,11 @@ enum RecipientCompletion { } static func inserting(_ address: String, into input: String) -> String { - var parts = input.components(separatedBy: ",").dropLast().map { + var parts = input.components(separatedBy: CharacterSet(charactersIn: ",;")).dropLast().map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }.filter { !$0.isEmpty } if !parts.contains(where: { $0.caseInsensitiveCompare(address) == .orderedSame }) { parts.append(address) } - return parts.joined(separator: ", ") + ", " + return parts.joined(separator: "; ") + "; " } } @@ -82,7 +82,7 @@ struct RecipientField: View { recipient = RecipientCompletion.inserting(address, into: recipient) focused = true }.frame(width: 30, height: 26) - TextField("An (mehrere mit Komma trennen)", text: $recipient) + TextField("An (mehrere mit Semikolon trennen)", text: $recipient) .accessibilityIdentifier("recipientField") .focused($focused) .onKeyPress(.downArrow) { diff --git a/MikeMail/WindowCloseGuard.swift b/MikeMail/WindowCloseGuard.swift new file mode 100644 index 0000000..e8ec083 --- /dev/null +++ b/MikeMail/WindowCloseGuard.swift @@ -0,0 +1,80 @@ +import SwiftUI +import AppKit + +/// Intercepts the native close button while preserving SwiftUI's existing window delegate. +struct WindowCloseGuard: NSViewRepresentable { + let shouldClose: () -> Bool + let onCloseAttempt: () -> Void + + func makeCoordinator() -> Coordinator { + Coordinator(shouldClose: shouldClose, onCloseAttempt: onCloseAttempt) + } + + func makeNSView(context: Context) -> NSView { + let view = NSView() + DispatchQueue.main.async { + context.coordinator.attach(to: view.window) + } + return view + } + + func updateNSView(_ view: NSView, context: Context) { + context.coordinator.shouldClose = shouldClose + context.coordinator.onCloseAttempt = onCloseAttempt + DispatchQueue.main.async { + context.coordinator.attach(to: view.window) + } + } + + static func dismantleNSView(_ view: NSView, coordinator: Coordinator) { + coordinator.detach() + } + + final class Coordinator: NSObject, NSWindowDelegate { + var shouldClose: () -> Bool + var onCloseAttempt: () -> Void + weak var window: NSWindow? + weak var previousDelegate: NSWindowDelegate? + + init(shouldClose: @escaping () -> Bool, onCloseAttempt: @escaping () -> Void) { + self.shouldClose = shouldClose + self.onCloseAttempt = onCloseAttempt + } + + func attach(to window: NSWindow?) { + guard let window else { return } + if self.window === window, window.delegate === self { return } + detach() + self.window = window + previousDelegate = window.delegate + window.delegate = self + } + + func detach() { + if window?.delegate === self { + window?.delegate = previousDelegate + } + window = nil + previousDelegate = nil + } + + func windowShouldClose(_ sender: NSWindow) -> Bool { + guard shouldClose() else { + onCloseAttempt() + return false + } + return previousDelegate?.windowShouldClose?(sender) ?? true + } + + override func responds(to selector: Selector!) -> Bool { + super.responds(to: selector) || previousDelegate?.responds(to: selector) == true + } + + override func forwardingTarget(for selector: Selector!) -> Any? { + if previousDelegate?.responds(to: selector) == true { + return previousDelegate + } + return super.forwardingTarget(for: selector) + } + } +} diff --git a/MikeMailTests/MailIdleTests.swift b/MikeMailTests/MailIdleTests.swift index 735e2d3..07e543e 100644 --- a/MikeMailTests/MailIdleTests.swift +++ b/MikeMailTests/MailIdleTests.swift @@ -33,7 +33,5 @@ struct MailIdleTests { #expect(store.authenticationAccount == nil) store.requestPassword(accountID: UUID()) #expect(store.authenticationAccount == nil) - store.configureIdle(enabled: false) - #expect(store.idleStatuses.isEmpty) } }

_es_ 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 literal 132546 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 0 { stack.addArrangedSubview(NSTextField(labelWithString: String(count))) } } diff --git a/MikeMail/AccountsView.swift b/MikeMail/AccountsView.swift index d1c09c8..57f21c8 100644 --- a/MikeMail/AccountsView.swift +++ b/MikeMail/AccountsView.swift @@ -211,6 +211,10 @@ struct AccountSettingsForm: View { var body: some View { Form { TextField("Name", text: $account.name) + TextField("Anzeigename", text: Binding( + get: { account.displayName ?? "" }, + set: { account.displayName = $0 } + )) TextField("E-Mail-Adresse", text: $account.email).accessibilityIdentifier("accountEmail") TextField("Benutzername", text: $account.username) TextField("Server", text: $account.host) diff --git a/MikeMail/ContentView.swift b/MikeMail/ContentView.swift index 662d1ad..438af2d 100644 --- a/MikeMail/ContentView.swift +++ b/MikeMail/ContentView.swift @@ -41,8 +41,6 @@ struct ContentView: View { @State private var selectedMessages: Set = [] @State private var accountPresentation = AccountPresentation() @Environment(\.openWindow) private var openWindow - @AppStorage("idleEnabled") private var idleEnabled = true - @AppStorage("pollMinutes") private var pollMinutes = 5 @AppStorage("mailDeleteConfirmation") private var deleteConfirmation: MailDeleteConfirmation = .always @AppStorage("markReadImmediately") private var markReadImmediately = false @AppStorage("markReadDelaySeconds") private var markReadDelaySeconds = 1 @@ -107,12 +105,10 @@ struct ContentView: View { .safeAreaInset(edge: .bottom) { HStack { if store.isBusy { ProgressView().controlSize(.small) } - if idleEnabled { - Image(systemName: "bolt.horizontal.circle") - .foregroundStyle(store.idleStatuses.values.contains("Sofortempfang aktiv") ? .green : .secondary) - .help(store.accounts.map { "\($0.email): \(store.idleStatuses[$0.id] ?? "Verbindung wird vorbereitet")" }.joined(separator: "\n")) - .accessibilityLabel("Sofortempfang") - } + Image(systemName: "bolt.horizontal.circle") + .foregroundStyle(store.idleStatuses.values.contains("Sofortempfang aktiv") ? .green : .secondary) + .help(store.accounts.map { "\($0.email): \(store.idleStatuses[$0.id] ?? "Verbindung wird vorbereitet")" }.joined(separator: "\n")) + .accessibilityLabel("Sofortempfang") Text(store.status).font(.caption).foregroundStyle(.secondary) Spacer() Text("\(store.messages.count) Nachrichten").font(.caption).foregroundStyle(.secondary) @@ -142,7 +138,6 @@ struct ContentView: View { .keyboardShortcut("n", modifiers: .command).disabled(store.accounts.isEmpty) Button("Löschen", systemImage: "trash") { deleteSelection() } .disabled(selectedMessages.isEmpty) - SettingsLink { Label("Einstellungen", systemImage: "gearshape") } } } .focusedSceneValue(\.accountPresentation, accountPresentation) @@ -155,10 +150,12 @@ struct ContentView: View { })) { account in PasswordRecoveryView(store: store, account: account) } - .task(id: IdleConfiguration(enabled: idleEnabled, accounts: store.accounts)) { - store.configureIdle(enabled: idleEnabled) + .task(id: IdleConfiguration(accounts: store.accounts)) { + store.configureIdle() + } + .task(id: store.totalUnreadCount) { + MailNotifications.updateBadge(store.totalUnreadCount) } - .task(id: pollMinutes) { await store.poll(intervalMinutes: pollMinutes) } .task(id: AutoReadRequest(ids: selectedMessages, immediately: markReadImmediately, seconds: markReadDelaySeconds)) { guard selectedMessages.count == 1, let id = selectedMessages.first, let message = store.message(id: id), !message.isRead else { return } @@ -212,7 +209,13 @@ struct ContentView: View { guard !ids.isEmpty else { return } switch command { case .open: - if ids.count == 1, let id = ids.first { openWindow(id: "message", value: id) } + if ids.count == 1, let id = ids.first, let message = store.message(id: id) { + if selectedFolder.flatMap({ store.folder(id: $0) })?.isDrafts == true { + openComposer(.saved(message, accountID: store.accountID(for: id))) + } else { + openWindow(id: "message", value: id) + } + } case .reply, .forward: if ids.count == 1, let id = ids.first, let message = store.message(id: id) { openComposer(.from(message, accountID: store.accountID(for: id), forwarding: command == .forward)) @@ -465,6 +468,33 @@ struct MessageReader: View { } } +enum AttachmentMentionDetector { + static let settingKey = "warnMissingAttachment" + + private static let phrases = [ + "im anhang", + "in der anlage", + "als anhang", + "siehe anhang", + "siehe anlage", + "anbei", + "beigefügt", + "beigefuegt", + "angehängt", + "angehaengt", + "attachment", + "attached", + "enclosed" + ] + + static func mentionsAttachment(in text: String) -> Bool { + let normalized = text.folding(options: [.caseInsensitive, .diacriticInsensitive], locale: .current) + return phrases.contains { phrase in + normalized.contains(phrase.folding(options: [.caseInsensitive, .diacriticInsensitive], locale: .current)) + } + } +} + struct ComposeView: View { let store: MailStore var draft: ComposeDraft? = nil @@ -477,6 +507,12 @@ struct ComposeView: View { @State private var subject = "" @State private var editor = MailEditorModel() @State private var htmlEnabled = true + @State private var confirmingPlainText = false + @State private var confirmingMissingAttachment = false + @State private var confirmingClose = false + @State private var closeApproved = false + @State private var showAdditionalRecipients = false + @AppStorage(AttachmentMentionDetector.settingKey) private var warnMissingAttachment = true @AppStorage("bodyFontName") private var bodyFontName = "Helvetica" @AppStorage("bodyFontSize") private var bodyFontSize = 14.0 @State private var attachments: [MailAttachment] = [] @@ -491,21 +527,49 @@ struct ComposeView: View { Text("Neue Nachricht").font(.title2).bold() Form { Picker("Von", selection: $accountID) { - ForEach(store.accounts) { account in Text(account.email).tag(Optional(account.id)) } + ForEach(store.accounts) { account in + Text(account.senderDisplay).tag(Optional(account.id)) + } } RecipientField(recipient: $recipient, history: store.recipientHistory) - TextField("CC", text: $cc).accessibilityIdentifier("composeCC") - TextField("BCC", text: $bcc).accessibilityIdentifier("composeBCC") + Toggle("CC/BCC aktivieren", isOn: $showAdditionalRecipients) + .toggleStyle(.checkbox) + if showAdditionalRecipients { + TextField("CC (mehrere mit Semikolon trennen)", text: $cc) + .accessibilityIdentifier("composeCC") + TextField("BCC (mehrere mit Semikolon trennen)", text: $bcc) + .accessibilityIdentifier("composeBCC") + } TextField("Betreff", text: $subject) + .padding(.top, 8) } - Toggle("HTML-Nachricht", isOn: $htmlEnabled) + Toggle("HTML-Nachricht", isOn: Binding( + get: { htmlEnabled }, + set: { enabled in + if enabled { + htmlEnabled = true + } else if editor.hasRichTextFormatting { + confirmingPlainText = true + } else { + htmlEnabled = false + } + } + )) if htmlEnabled { MailFormattingBar(editor: editor) } - MailBodyEditor(model: editor, initialText: draft?.body ?? "", + MailBodyEditor(model: editor, initialText: draft?.body ?? "", initialHTML: draft?.htmlBody, fontName: bodyFontName, fontSize: bodyFontSize, focusOnOpen: draft?.isReply == true, richText: htmlEnabled) .frame(minHeight: 180, maxHeight: .infinity).border(.quaternary) VStack(alignment: .leading, spacing: 8) { - Label(attachments.count == 1 ? "Anhang – Dateien hier ablegen" : "Anhänge – Dateien hier ablegen", systemImage: "paperclip").font(.headline) + Label(attachments.count == 1 ? "Anhang – Dateien hier ablegen" : "Anhänge – Dateien hier ablegen", systemImage: "paperclip") + .font(.body) + .fontWeight(.regular) + .foregroundStyle(Color.blue.opacity(0.8)) + .frame( + maxWidth: .infinity, + minHeight: attachments.isEmpty ? 68 : nil, + alignment: .center + ) ScrollView { ForEach(attachments) { attachment in HStack { @@ -516,12 +580,18 @@ struct ComposeView: View { .labelStyle(.iconOnly) } } - }.frame(height: attachments.isEmpty ? 16 : 80) + }.frame(height: attachments.isEmpty ? 0 : 80) } .padding(12) .frame(maxWidth: .infinity, alignment: .leading) - .background(Color.accentColor.opacity(dropTargeted ? 0.18 : 0.06), in: RoundedRectangle(cornerRadius: 10)) - .overlay(RoundedRectangle(cornerRadius: 10).strokeBorder(dropTargeted ? Color.accentColor : Color.secondary.opacity(0.3))) + .background(Color.blue.opacity(dropTargeted ? 0.12 : 0.04), in: RoundedRectangle(cornerRadius: 10)) + .overlay { + RoundedRectangle(cornerRadius: 10) + .strokeBorder( + dropTargeted ? Color.blue.opacity(0.9) : Color.blue.opacity(0.35), + style: StrokeStyle(lineWidth: 1.5, dash: [7, 5]) + ) + } .dropDestination(for: URL.self) { urls, _ in guard !sending, urls.allSatisfy(\.isFileURL) else { return false } addAttachments(urls) @@ -529,21 +599,17 @@ struct ComposeView: View { } isTargeted: { dropTargeted = $0 } if let error { Text(error).foregroundStyle(.red).textSelection(.enabled) } HStack { - Button("Abbrechen") { dismiss() }.keyboardShortcut(.cancelAction) + Button("Abbrechen") { requestClose() }.keyboardShortcut(.cancelAction) Button("Anhang hinzufügen", systemImage: "paperclip") { importing = true } Spacer() if sending { ProgressView().controlSize(.small) } Button("Senden", systemImage: "paperplane") { - sending = true - error = nil - Task { - do { - try await store.send(accountID: accountID, recipients: recipient, cc: cc, bcc: bcc, - subject: subject, body: editor.plainText, attachments: attachments, - htmlBody: htmlEnabled ? try editor.html() : nil) - dismiss() - } catch { self.error = error.localizedDescription } - sending = false + let text = subject + "\n" + editor.plainText + if warnMissingAttachment && attachments.isEmpty + && AttachmentMentionDetector.mentionsAttachment(in: text) { + confirmingMissingAttachment = true + } else { + sendMessage() } } .buttonStyle(.borderedProminent) @@ -551,6 +617,13 @@ struct ComposeView: View { } } .padding(24) + .background { + WindowCloseGuard( + shouldClose: { closeApproved || (!hasComposedContent && !sending) }, + onCloseAttempt: { if !sending { requestClose() } } + ) + .frame(width: 0, height: 0) + } .frame(minWidth: 640, idealWidth: 760, maxWidth: .infinity, minHeight: 600, idealHeight: 780, maxHeight: .infinity) .disabled(sending) .interactiveDismissDisabled(sending) @@ -559,13 +632,101 @@ struct ComposeView: View { initialized = true accountID = draft?.accountID ?? defaultAccountID ?? store.accounts.first?.id recipient = draft?.recipient ?? "" + cc = draft?.cc ?? "" + bcc = draft?.bcc ?? "" subject = draft?.subject ?? "" + htmlEnabled = draft?.htmlBody != nil || draft == nil + showAdditionalRecipients = !cc.isEmpty || !bcc.isEmpty attachments = draft?.attachments ?? [] } .fileImporter(isPresented: $importing, allowedContentTypes: [.data], allowsMultipleSelection: true) { result in do { addAttachments(try result.get()) } catch { self.error = error.localizedDescription } } + .alert("HTML-Formatierung entfernen?", isPresented: $confirmingPlainText) { + Button("Abbrechen", role: .cancel) {} + Button("Formatierungen entfernen", role: .destructive) { + editor.removeRichTextFormatting() + htmlEnabled = false + } + } message: { + Text("Beim Deaktivieren der HTML-Nachricht gehen alle Textformatierungen verloren.") + } + .alert("Nachricht schließen?", isPresented: $confirmingClose) { + Button("Zurück zur Mail", role: .cancel) {} + Button("Als Entwurf speichern") { saveDraftAndClose() } + Button("Löschen", role: .destructive) { + closeApproved = true + DispatchQueue.main.async { dismiss() } + } + } message: { + Text("Möchtest du die verfasste Nachricht löschen oder als Entwurf speichern?") + } + .alert("Anhang vergessen?", isPresented: $confirmingMissingAttachment) { + Button("Abbrechen", role: .cancel) {} + Button("Trotzdem senden") { sendMessage() } + } message: { + Text("Der Nachrichtentext deutet auf einen Anhang hin, es wurde aber keine Datei angefügt.") + } + } + + private var hasComposedContent: Bool { + !recipient.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + || !cc.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + || !bcc.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + || !subject.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + || !editor.plainText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + || !attachments.isEmpty + } + + private func requestClose() { + if hasComposedContent { + confirmingClose = true + } else { + closeApproved = true + DispatchQueue.main.async { dismiss() } + } + } + + private func saveDraftAndClose() { + sending = true + error = nil + Task { + do { + try await store.saveDraft( + accountID: accountID, + recipients: recipient, + cc: cc, + bcc: bcc, + subject: subject, + body: editor.plainText, + attachments: attachments, + htmlBody: htmlEnabled ? try editor.html() : nil + ) + closeApproved = true + DispatchQueue.main.async { dismiss() } + } catch { + self.error = error.localizedDescription + } + sending = false + } + } + + private func sendMessage() { + sending = true + error = nil + Task { + do { + try await store.send(accountID: accountID, recipients: recipient, cc: cc, bcc: bcc, + subject: subject, body: editor.plainText, attachments: attachments, + htmlBody: htmlEnabled ? try editor.html() : nil) + closeApproved = true + DispatchQueue.main.async { dismiss() } + } catch { + self.error = error.localizedDescription + } + sending = false + } } private func addAttachments(_ urls: [URL]) { diff --git a/MikeMail/Localizable.xcstrings b/MikeMail/Localizable.xcstrings index 9016636..1a2063b 100644 --- a/MikeMail/Localizable.xcstrings +++ b/MikeMail/Localizable.xcstrings @@ -23,6 +23,9 @@ } } } + }, + "(Ohne Betreff)" : { + }, "%@" : { "localizations" : { @@ -224,6 +227,7 @@ } }, "Alle %lld Minuten" : { + "extractionState" : "stale", "localizations" : { "de" : { "variations" : { @@ -314,6 +318,9 @@ } } } + }, + "Als Entwurf speichern" : { + }, "Als gelesen markieren" : { "localizations" : { @@ -336,6 +343,7 @@ } }, "An (mehrere mit Komma trennen)" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -344,6 +352,9 @@ } } } + }, + "An (mehrere mit Semikolon trennen)" : { + }, "An: %@" : { "localizations" : { @@ -384,6 +395,9 @@ } } } + }, + "Anhang vergessen?" : { + }, "Anhänge – Dateien hier ablegen" : { "localizations" : { @@ -404,8 +418,12 @@ } } } + }, + "Anzeigename" : { + }, "Auf neue Mails prüfen" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -426,6 +444,7 @@ } }, "BCC" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -434,6 +453,15 @@ } } } + }, + "BCC (mehrere mit Semikolon trennen)" : { + + }, + "Bei neuen Mails benachrichtigen" : { + + }, + "Beim Deaktivieren der HTML-Nachricht gehen alle Textformatierungen verloren." : { + }, "Beim Löschen von Mails nachfragen" : { "localizations" : { @@ -479,6 +507,7 @@ } }, "CC" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -487,6 +516,9 @@ } } } + }, + "CC (mehrere mit Semikolon trennen)" : { + }, "CC: %@" : { "localizations" : { @@ -497,6 +529,9 @@ } } } + }, + "CC/BCC aktivieren" : { + }, "Darstellung" : { @@ -556,6 +591,9 @@ } } } + }, + "Der Nachrichtentext deutet auf einen Anhang hin, es wurde aber keine Datei angefügt." : { + }, "Der Ordner wird auf dem Mailserver erstellt." : { "localizations" : { @@ -569,6 +607,9 @@ }, "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 Anzahl ungelesener Mails wird immer am App-Symbol angezeigt." : { + }, "Die ausgewählten Nachrichten werden auch auf dem Server in den Papierkorb verschoben." : { "localizations" : { @@ -619,6 +660,9 @@ } } } + }, + "Drucken …" : { + }, "Durchgestrichen" : { "localizations" : { @@ -671,6 +715,7 @@ } }, "Einstellungen" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -751,6 +796,9 @@ } } } + }, + "Formatierungen entfernen" : { + }, "Füge ein Konto hinzu oder wähle links ein Konto aus." : { "localizations" : { @@ -791,6 +839,9 @@ } } } + }, + "HTML-Formatierung entfernen?" : { + }, "HTML-Nachricht" : { "localizations" : { @@ -1011,6 +1062,12 @@ } } } + }, + "Mitteilungen" : { + + }, + "Möchtest du die verfasste Nachricht löschen oder als Entwurf speichern?" : { + }, "Nach %lld Sekunden" : { "localizations" : { @@ -1113,6 +1170,9 @@ } } } + }, + "Nachricht schließen?" : { + }, "Nachrichten durchsuchen" : { "localizations" : { @@ -1156,8 +1216,12 @@ }, "Neue Konten" : { + }, + "Neue Mail" : { + }, "Neue Mails werden bei laufender App sofort gemeldet. Das Prüfintervall dient zusätzlich als Absicherung." : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1238,6 +1302,7 @@ } }, "Nur manuell" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1369,6 +1434,15 @@ } } } + }, + "Prüft Betreff und Nachrichtentext vor dem Senden auf Hinweise wie „im Anhang“, „anbei“ oder „beigefügt“." : { + + }, + "Rechtschreib- und Grammatikprüfung" : { + + }, + "Rechtschreibprüfung" : { + }, "Regulärer Ausdruck" : { "localizations" : { @@ -1487,6 +1561,7 @@ } }, "Sofortempfang mit IMAP IDLE" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -1545,6 +1620,12 @@ } } } + }, + "Trotzdem senden" : { + + }, + "Unbekannter Absender" : { + }, "Unterstrichen" : { "localizations" : { @@ -1565,6 +1646,9 @@ } } } + }, + "Verfassen" : { + }, "Verhalten" : { @@ -1588,6 +1672,9 @@ } } } + }, + "Vor möglicherweise vergessenem Anhang warnen" : { + }, "Vorschau Mailtext" : { "localizations" : { @@ -1629,6 +1716,9 @@ } } } + }, + "Zurück zur Mail" : { + } }, "version" : "1.3" diff --git a/MikeMail/MIME.swift b/MikeMail/MIME.swift index e992d4d..559fa61 100644 --- a/MikeMail/MIME.swift +++ b/MikeMail/MIME.swift @@ -94,8 +94,8 @@ enum MIME { return MailMessage(id: id, sender: decodeHeader(fields["from"] ?? ""), recipient: decodeHeader(fields["to"] ?? ""), subject: decodeHeader(fields["subject"] ?? "(Ohne Betreff)"), date: date ?? .distantPast, body: bodies.isEmpty ? plainText(htmlBodies.joined(separator: "\n")) : bodies.joined(separator: "\n"), - attachments: attachments, cc: decodeHeader(fields["cc"] ?? ""), replyTo: decodeHeader(fields["reply-to"] ?? ""), - htmlBody: htmlBodies.isEmpty ? nil : htmlBodies.joined(separator: "\n")) + attachments: attachments, cc: decodeHeader(fields["cc"] ?? ""), bcc: decodeHeader(fields["bcc"] ?? ""), + replyTo: decodeHeader(fields["reply-to"] ?? ""), htmlBody: htmlBodies.isEmpty ? nil : htmlBodies.joined(separator: "\n")) } private static func parsePart(_ data: Data, depth: Int, bodies: inout [String], htmlBodies: inout [String], attachments: inout [MailAttachment]) { @@ -151,8 +151,10 @@ enum MIME { value.range(of: "^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$", options: .regularExpression) != nil } - static func compose(from: String, to: [String], cc: [String] = [], bcc: [String] = [], subject: String, body: String, attachments: [MailAttachment], htmlBody: String? = nil) throws -> Data { - guard validAddress(from), !(to + cc + bcc).isEmpty, (to + cc + bcc).allSatisfy(validAddress), + static func compose(from: String, senderName: String? = nil, to: [String], cc: [String] = [], bcc: [String] = [], subject: String, body: String, attachments: [MailAttachment], htmlBody: String? = nil, allowEmptyRecipients: Bool = false, includeBccHeader: Bool = false) throws -> Data { + let trimmedSenderName = senderName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + guard validAddress(from), (allowEmptyRecipients || !(to + cc + bcc).isEmpty), (to + cc + bcc).allSatisfy(validAddress), + !trimmedSenderName.unicodeScalars.contains(where: { CharacterSet.newlines.contains($0) }), !subject.unicodeScalars.contains(where: { CharacterSet.newlines.contains($0) }) else { throw MailError.message("Bitte gültige E-Mail-Adressen und einen einzeiligen Betreff eingeben.") } @@ -163,8 +165,12 @@ enum MIME { func base64(_ data: Data) -> String { data.base64EncodedString(options: [.lineLength76Characters, .endLineWithCarriageReturn, .endLineWithLineFeed]) } - var result = "From: \(from)\r\nTo: \(to.joined(separator: ", "))\r\n" + let fromHeader = trimmedSenderName.isEmpty + ? from + : "=?UTF-8?B?\(Data(trimmedSenderName.utf8).base64EncodedString())?= <\(from)>" + var result = "From: \(fromHeader)\r\nTo: \(to.joined(separator: ", "))\r\n" if !cc.isEmpty { result += "Cc: \(cc.joined(separator: ", "))\r\n" } + if includeBccHeader && !bcc.isEmpty { result += "Bcc: \(bcc.joined(separator: ", "))\r\n" } result += "Subject: =?UTF-8?B?\(Data(subject.utf8).base64EncodedString())?=\r\n" result += "Date: \(dateFormatter.string(from: Date()))\r\nMessage-ID: <\(UUID().uuidString)@mikemail.local>\r\n" result += "MIME-Version: 1.0\r\nContent-Type: multipart/mixed; boundary=\"\(boundary)\"\r\n\r\n" diff --git a/MikeMail/MailBodyEditor.swift b/MikeMail/MailBodyEditor.swift index d390465..722d0b9 100644 --- a/MikeMail/MailBodyEditor.swift +++ b/MikeMail/MailBodyEditor.swift @@ -12,6 +12,7 @@ final class MailEditorModel { var italic = false var underline = false var strikethrough = false + var spellCheckingEnabled = true var plainText: String { textView?.string ?? "" } @@ -87,6 +88,52 @@ final class MailEditorModel { return result } } + + func setSpellCheckingEnabled(_ enabled: Bool) { + spellCheckingEnabled = enabled + textView?.isContinuousSpellCheckingEnabled = enabled + textView?.isGrammarCheckingEnabled = enabled + } + + var hasRichTextFormatting: Bool { + guard let view = textView else { return false } + let text = view.attributedString() + guard text.length > 0 else { return false } + let baseFont = view.font ?? NSFont.systemFont(ofSize: 14) + var found = false + text.enumerateAttributes(in: NSRange(location: 0, length: text.length)) { attributes, _, stop in + let font = attributes[.font] as? NSFont ?? baseFont + let traits = NSFontManager.shared.traits(of: font) + let hasFontFormatting = font.familyName != baseFont.familyName + || font.pointSize != baseFont.pointSize + || traits.contains(.boldFontMask) + || traits.contains(.italicFontMask) + let hasDecoration = (attributes[.underlineStyle] as? Int ?? 0) != 0 + || (attributes[.strikethroughStyle] as? Int ?? 0) != 0 + let hasColor = (attributes[.foregroundColor] as? NSColor)?.isEqual(NSColor.textColor) == false + || attributes[.backgroundColor] != nil + if hasFontFormatting || hasDecoration || hasColor || attributes[.link] != nil { + found = true + stop.pointee = true + } + } + return found + } + + func removeRichTextFormatting() { + guard let view = textView, let storage = view.textStorage else { return } + let range = NSRange(location: 0, length: storage.length) + storage.setAttributes([ + .font: view.font ?? NSFont.systemFont(ofSize: 14), + .foregroundColor: NSColor.textColor + ], range: range) + view.typingAttributes = [ + .font: view.font ?? NSFont.systemFont(ofSize: 14), + .foregroundColor: NSColor.textColor + ] + view.didChangeText() + refreshSelection() + } } struct MailFormattingBar: View { @@ -118,6 +165,14 @@ struct MailFormattingBar: View { Button { editor.setAttribute(.strikethroughStyle, value: editor.strikethrough ? 0 : NSUnderlineStyle.single.rawValue) } label: { Image(systemName: "strikethrough").foregroundStyle(editor.strikethrough ? Color.accentColor : .primary) }.help("Durchgestrichen") + Divider() + .frame(height: 20) + Toggle("Rechtschreibprüfung", isOn: Binding( + get: { editor.spellCheckingEnabled }, + set: { editor.setSpellCheckingEnabled($0) } + )) + .toggleStyle(.switch) + .help("Rechtschreib- und Grammatikprüfung") Spacer(minLength: 0) } .padding(8).background(.quaternary, in: RoundedRectangle(cornerRadius: 6)) @@ -127,6 +182,7 @@ struct MailFormattingBar: View { struct MailBodyEditor: NSViewRepresentable { let model: MailEditorModel let initialText: String + let initialHTML: String? let fontName: String let fontSize: Double let focusOnOpen: Bool @@ -141,9 +197,23 @@ struct MailBodyEditor: NSViewRepresentable { view.importsGraphics = false view.allowsUndo = true view.isAutomaticQuoteSubstitutionEnabled = false + view.isContinuousSpellCheckingEnabled = model.spellCheckingEnabled + view.isGrammarCheckingEnabled = model.spellCheckingEnabled view.textContainerInset = NSSize(width: 8, height: 8) view.font = NSFont(name: fontName, size: fontSize) ?? .systemFont(ofSize: fontSize) - view.string = initialText + if let initialHTML, + let attributedText = try? NSAttributedString( + data: Data(initialHTML.utf8), + options: [ + .documentType: NSAttributedString.DocumentType.html, + .characterEncoding: String.Encoding.utf8.rawValue + ], + documentAttributes: nil + ) { + view.textStorage?.setAttributedString(attributedText) + } else { + view.string = initialText + } view.setSelectedRange(NSRange(location: 0, length: 0)) view.setAccessibilityIdentifier("composeBody") view.delegate = context.coordinator @@ -158,6 +228,8 @@ struct MailBodyEditor: NSViewRepresentable { func updateNSView(_ scroll: NSScrollView, context: Context) { guard let view = scroll.documentView as? NSTextView else { return } view.isRichText = richText + view.isContinuousSpellCheckingEnabled = model.spellCheckingEnabled + view.isGrammarCheckingEnabled = model.spellCheckingEnabled } final class Coordinator: NSObject, NSTextViewDelegate { diff --git a/MikeMail/MailIdleMonitor.swift b/MikeMail/MailIdleMonitor.swift index 980a4ab..01a3586 100644 --- a/MikeMail/MailIdleMonitor.swift +++ b/MikeMail/MailIdleMonitor.swift @@ -102,6 +102,5 @@ final class MailIdleMonitor { } struct IdleConfiguration: Equatable { - let enabled: Bool let accounts: [MailAccount] } diff --git a/MikeMail/MailModels.swift b/MikeMail/MailModels.swift index e7abb5d..9d19a05 100644 --- a/MikeMail/MailModels.swift +++ b/MikeMail/MailModels.swift @@ -4,12 +4,18 @@ import Security struct MailAccount: Identifiable, Codable, Hashable, Sendable { var id = UUID() var name = "" + var displayName: String? var email = "" var username = "" var host = "" var smtpPort: UInt16 = 587 var imapPort: UInt16 = 993 + var senderDisplay: String { + let value = displayName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return value.isEmpty ? email : "\(value) <\(email)>" + } + static let testAccount = MailAccount(name: "Testkonto", email: "test@kuebler-sulzbach.de", username: "test@kuebler-sulzbach.de", host: "w0099985.kasserver.com") } @@ -29,14 +35,40 @@ struct MailFolder: Identifiable, Hashable, Codable, Sendable { specialUse?.contains("\\trash") == true || ["trash", "papierkorb", "deleted messages", "deleted items"].contains(name.lowercased()) } + var isDrafts: Bool { + specialUse?.contains("\\drafts") == true + || ["drafts", "entwürfe", "entwuerfe"].contains(name.lowercased()) + } + var icon: String { - switch path.lowercased() { - case "inbox": "tray" - case let value where value.contains("sent"): "paperplane" - case let value where value.contains("trash"): "trash" - case let value where value.contains("draft"): "doc" - default: "folder" + if isInbox { return "tray" } + if isTrash { return "trash" } + + let uses = Set((specialUse ?? []).map { $0.lowercased() }) + let value = (path + " " + name).lowercased() + + if uses.contains("\\sent") || value.contains("sent") || value.contains("gesendet") { + return "paperplane" } + if uses.contains("\\drafts") || value.contains("draft") || value.contains("entwurf") { + return "doc.text" + } + if uses.contains("\\junk") || value.contains("junk") || value.contains("spam") { + return "exclamationmark.octagon" + } + if uses.contains("\\archive") || value.contains("archive") || value.contains("archiv") { + return "archivebox" + } + if uses.contains("\\all") || value.contains("all mail") || value.contains("alle nachrichten") { + return "tray.full" + } + if uses.contains("\\flagged") || value.contains("flagged") || value.contains("markiert") { + return "flag" + } + if uses.contains("\\important") || value.contains("important") || value.contains("wichtig") { + return "star" + } + return "folder" } } @@ -58,6 +90,7 @@ struct MailMessage: Identifiable, Sendable { let attachments: [MailAttachment] var isRead = false var cc = "" + var bcc = "" var replyTo = "" var htmlBody: String? = nil var displaySender: String { MailDisplay.sender(sender) } diff --git a/MikeMail/MailPrinter.swift b/MikeMail/MailPrinter.swift new file mode 100644 index 0000000..5094f6b --- /dev/null +++ b/MikeMail/MailPrinter.swift @@ -0,0 +1,94 @@ +import AppKit + +@MainActor +enum MailPrinter { + static func print(_ message: MailMessage) { + guard let attributedMessage = makeAttributedMessage(message) else { + NSSound.beep() + return + } + + let printInfo = (NSPrintInfo.shared.copy() as? NSPrintInfo) ?? NSPrintInfo.shared + printInfo.horizontalPagination = .fit + printInfo.verticalPagination = .automatic + + let printableWidth = max( + printInfo.paperSize.width - printInfo.leftMargin - printInfo.rightMargin, + 400 + ) + let printableHeight = max( + printInfo.paperSize.height - printInfo.topMargin - printInfo.bottomMargin, + 400 + ) + let textView = NSTextView(frame: NSRect(x: 0, y: 0, width: printableWidth, height: 1)) + textView.isEditable = false + textView.isSelectable = false + textView.isRichText = true + textView.drawsBackground = false + textView.isHorizontallyResizable = false + textView.isVerticallyResizable = true + textView.textContainer?.widthTracksTextView = true + textView.textContainer?.containerSize = NSSize( + width: printableWidth, + height: .greatestFiniteMagnitude + ) + textView.textStorage?.setAttributedString(attributedMessage) + if let textContainer = textView.textContainer { + textView.layoutManager?.ensureLayout(for: textContainer) + let contentHeight = textView.layoutManager?.usedRect(for: textContainer).height ?? 1 + let requiredHeight = max(contentHeight + 24, printableHeight) + let pageCount = ceil(requiredHeight / printableHeight) + textView.frame.size.height = pageCount * printableHeight + } + + let operation = NSPrintOperation(view: textView, printInfo: printInfo) + operation.showsPrintPanel = true + operation.showsProgressPanel = true + operation.run() + } + + private static func makeAttributedMessage(_ message: MailMessage) -> NSAttributedString? { + let body: String + if let htmlBody = message.htmlBody, !htmlBody.isEmpty { + body = htmlBody + } else { + body = "