127 lines
5.6 KiB
Swift
127 lines
5.6 KiB
Swift
import SwiftUI
|
|
|
|
enum MessageCommand {
|
|
case open, reply, forward, delete, restoreToInbox, read, unread
|
|
}
|
|
|
|
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
|
|
if let start = source.firstIndex(of: "<"), let end = source[start...].firstIndex(of: ">") {
|
|
recipient = String(source[source.index(after: start)..<end])
|
|
} else { recipient = source }
|
|
return ComposeDraft(accountID: accountID, recipient: forwarding ? "" : recipient,
|
|
subject: (forwarding ? "Fwd: " : "Re: ") + message.subject,
|
|
body: "\n\n" + (forwarding ? "Weitergeleitete Nachricht" : "Am \(message.date.formatted()) schrieb \(message.sender):") +
|
|
"\nVon: \(message.sender)\nAn: \(message.recipient)\nBetreff: \(message.subject)\n\n" +
|
|
message.body.components(separatedBy: "\n").map { forwarding ? $0 : "> " + $0 }.joined(separator: "\n"),
|
|
attachments: forwarding ? message.attachments : [], isReply: !forwarding)
|
|
}
|
|
}
|
|
|
|
struct MessageActionButtons: View {
|
|
let action: (MessageCommand) -> Void
|
|
var body: some View {
|
|
Button("Löschen…", systemImage: "trash") { action(.delete) }
|
|
Button("Antworten…", systemImage: "arrowshape.turn.up.left") { action(.reply) }
|
|
Button("Weiterleiten", systemImage: "arrowshape.turn.up.right") { action(.forward) }
|
|
}
|
|
}
|
|
|
|
struct MessageDetailWindow: View {
|
|
let store: MailStore
|
|
let messageID: String?
|
|
@Environment(\.openWindow) private var openWindow
|
|
@State private var confirmingDelete = false
|
|
@AppStorage("mailDeleteConfirmation") private var deleteConfirmation: MailDeleteConfirmation = .always
|
|
@AppStorage("markReadImmediately") private var markReadImmediately = false
|
|
@AppStorage("markReadDelaySeconds") private var markReadDelaySeconds = 1
|
|
|
|
private var message: MailMessage? {
|
|
_ = store.archiveRevision
|
|
return messageID.flatMap { store.message(id: $0) }
|
|
}
|
|
var body: some View {
|
|
MessageReader(message: message)
|
|
.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 {
|
|
case .reply, .forward:
|
|
let draft = ComposeDraft.from(message, accountID: store.accountID(for: message.id), forwarding: command == .forward)
|
|
openWindow(id: "compose", value: store.prepareCompose(draft))
|
|
case .delete:
|
|
if deleteConfirmation.isRequired(count: 1) { confirmingDelete = true }
|
|
else { Task { await store.deleteMessages(ids: [message.id]) } }
|
|
default: break
|
|
}
|
|
}.disabled(message == nil)
|
|
}
|
|
.alert("Nachricht löschen?", isPresented: $confirmingDelete) {
|
|
Button("Abbrechen", role: .cancel) {}
|
|
Button("Löschen", role: .destructive) {
|
|
if let messageID { Task { await store.deleteMessages(ids: [messageID]) } }
|
|
}
|
|
} message: {
|
|
Text(store.selectionContainsTrash([messageID ?? ""])
|
|
? "Die Nachricht wird auch auf dem Server endgültig gelöscht."
|
|
: "Die Nachricht wird auch auf dem Server in den Papierkorb verschoben.")
|
|
}
|
|
.task(id: AutoReadRequest(ids: Set([messageID].compactMap { $0 }), immediately: markReadImmediately, seconds: markReadDelaySeconds)) {
|
|
guard let message, !message.isRead else { return }
|
|
do {
|
|
try await AutoReadRequest(ids: [message.id], immediately: markReadImmediately, seconds: markReadDelaySeconds).wait()
|
|
await store.setRead(ids: [message.id], read: true)
|
|
} catch { /* The message window closed before the read delay elapsed. */ }
|
|
}
|
|
.alert("MikeMail", isPresented: Binding(get: { store.error != nil }, set: { if !$0 { store.error = nil } })) {
|
|
Button("OK") { store.error = nil }
|
|
} message: { Text(store.error ?? "") }
|
|
}
|
|
}
|
|
|
|
struct UnreadBadge: View {
|
|
let count: Int
|
|
var body: some View {
|
|
if count > 0 {
|
|
Text(count.formatted()).font(.caption.bold()).monospacedDigit()
|
|
.padding(.horizontal, 6).padding(.vertical, 2)
|
|
.background(.quaternary, in: Capsule())
|
|
.accessibilityLabel("\(count) ungelesen")
|
|
}
|
|
}
|
|
}
|