Local work on the social.io handoff before merging the claude worktree branch. Includes the full per-spec Sources/Core/Design module (8 files), watchOS target under WatchApp/, Live Activity + widget extension, entitlements, scheme, and asset catalog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
174 lines
4.2 KiB
Swift
174 lines
4.2 KiB
Swift
import Foundation
|
|
|
|
enum Mailbox: String, CaseIterable, Identifiable, Codable {
|
|
case inbox
|
|
case starred
|
|
case snoozed
|
|
case screener
|
|
case sent
|
|
case drafts
|
|
case archive
|
|
case trash
|
|
|
|
var id: String { rawValue }
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .inbox: "Inbox"
|
|
case .starred: "Starred"
|
|
case .snoozed: "Snoozed"
|
|
case .screener: "The Screener"
|
|
case .sent: "Sent"
|
|
case .drafts: "Drafts"
|
|
case .archive: "Archive"
|
|
case .trash: "Trash"
|
|
}
|
|
}
|
|
|
|
var systemImage: String {
|
|
switch self {
|
|
case .inbox: "tray.full"
|
|
case .starred: "star"
|
|
case .snoozed: "clock.badge"
|
|
case .screener: "line.3.horizontal.decrease.circle"
|
|
case .sent: "paperplane"
|
|
case .drafts: "doc.text"
|
|
case .archive: "archivebox"
|
|
case .trash: "trash"
|
|
}
|
|
}
|
|
}
|
|
|
|
struct MailPerson: Identifiable, Hashable, Codable {
|
|
let id: UUID
|
|
let name: String
|
|
let email: String
|
|
|
|
init(id: UUID = UUID(), name: String, email: String) {
|
|
self.id = id
|
|
self.name = name
|
|
self.email = email
|
|
}
|
|
}
|
|
|
|
struct MailMessage: Identifiable, Hashable, Codable {
|
|
let id: UUID
|
|
let routeID: String
|
|
let sender: MailPerson
|
|
let recipients: [MailPerson]
|
|
let sentAt: Date
|
|
let body: String
|
|
let isDraft: Bool
|
|
let attachments: [MailAttachment]
|
|
|
|
init(
|
|
id: UUID = UUID(),
|
|
routeID: String = UUID().uuidString.lowercased(),
|
|
sender: MailPerson,
|
|
recipients: [MailPerson],
|
|
sentAt: Date,
|
|
body: String,
|
|
isDraft: Bool = false,
|
|
attachments: [MailAttachment] = []
|
|
) {
|
|
self.id = id
|
|
self.routeID = routeID
|
|
self.sender = sender
|
|
self.recipients = recipients
|
|
self.sentAt = sentAt
|
|
self.body = body
|
|
self.isDraft = isDraft
|
|
self.attachments = attachments
|
|
}
|
|
}
|
|
|
|
struct MailAttachment: Identifiable, Hashable, Codable {
|
|
let id: UUID
|
|
let name: String
|
|
let size: String
|
|
|
|
init(id: UUID = UUID(), name: String, size: String) {
|
|
self.id = id
|
|
self.name = name
|
|
self.size = size
|
|
}
|
|
}
|
|
|
|
struct MailThread: Identifiable, Hashable, Codable {
|
|
let id: UUID
|
|
let routeID: String
|
|
var mailbox: Mailbox
|
|
var subject: String
|
|
var participants: [MailPerson]
|
|
var messages: [MailMessage]
|
|
var isUnread: Bool
|
|
var isStarred: Bool
|
|
var tags: [String]
|
|
var lane: Lane
|
|
var summary: [String]?
|
|
var isScreeningCandidate: Bool
|
|
|
|
init(
|
|
id: UUID = UUID(),
|
|
routeID: String = UUID().uuidString.lowercased(),
|
|
mailbox: Mailbox,
|
|
subject: String,
|
|
participants: [MailPerson],
|
|
messages: [MailMessage],
|
|
isUnread: Bool,
|
|
isStarred: Bool,
|
|
tags: [String] = [],
|
|
lane: Lane = .people,
|
|
summary: [String]? = nil,
|
|
isScreeningCandidate: Bool = false
|
|
) {
|
|
self.id = id
|
|
self.routeID = routeID
|
|
self.mailbox = mailbox
|
|
self.subject = subject
|
|
self.participants = participants
|
|
self.messages = messages.sorted { $0.sentAt < $1.sentAt }
|
|
self.isUnread = isUnread
|
|
self.isStarred = isStarred
|
|
self.tags = tags
|
|
self.lane = lane
|
|
self.summary = summary
|
|
self.isScreeningCandidate = isScreeningCandidate
|
|
}
|
|
|
|
var latestMessage: MailMessage? {
|
|
messages.max(by: { $0.sentAt < $1.sentAt })
|
|
}
|
|
|
|
var previewText: String {
|
|
latestMessage?.body.replacingOccurrences(of: "\n", with: " ") ?? ""
|
|
}
|
|
|
|
var lastUpdated: Date {
|
|
latestMessage?.sentAt ?? .distantPast
|
|
}
|
|
|
|
var messageCount: Int {
|
|
messages.count
|
|
}
|
|
|
|
var hasAttachments: Bool {
|
|
messages.contains { !$0.attachments.isEmpty }
|
|
}
|
|
}
|
|
|
|
struct ComposeDraft: Equatable, Codable {
|
|
var to = ""
|
|
var cc = ""
|
|
var from = ""
|
|
var subject = ""
|
|
var body = ""
|
|
|
|
var isEmpty: Bool {
|
|
to.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
|
|
cc.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
|
|
subject.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
|
|
body.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
}
|
|
}
|