#if os(iOS) && canImport(ActivityKit) && canImport(AppIntents) && canImport(WidgetKit) import ActivityKit import AppIntents import SwiftUI import WidgetKit struct MailNotificationActivityAttributes: ActivityAttributes { struct ContentState: Codable, Hashable { var sender: String var initials: String var subject: String var preview: String var route: String } var threadRouteID: String } struct OpenMailLiveActivityIntent: LiveActivityIntent { static var title: LocalizedStringResource = "Open" static var openAppWhenRun = true @Parameter(title: "Route") var route: String init() { route = "socialio://mailbox/inbox" } init(route: String) { self.route = route } func perform() async throws -> some IntentResult { .result() } } struct SnoozeMailLiveActivityIntent: LiveActivityIntent { static var title: LocalizedStringResource = "Snooze" func perform() async throws -> some IntentResult { .result() } } struct MailNotificationLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: MailNotificationActivityAttributes.self) { context in VStack(alignment: .leading, spacing: 10) { header(context: context) Text(context.state.preview) .font(.caption) .foregroundStyle(.secondary) .lineLimit(2) HStack(spacing: 10) { Button(intent: OpenMailLiveActivityIntent(route: context.state.route)) { Text("Open") } .buttonStyle(.borderedProminent) Button(intent: SnoozeMailLiveActivityIntent()) { Text("Snooze") } .buttonStyle(.bordered) } } .padding(.vertical, 6) } dynamicIsland: { context in DynamicIsland { DynamicIslandExpandedRegion(.leading) { Image(systemName: "envelope.fill") .foregroundStyle(SIO.tint) } DynamicIslandExpandedRegion(.trailing) { Text(context.state.initials) .font(.headline.weight(.semibold)) } DynamicIslandExpandedRegion(.center) { VStack(alignment: .leading, spacing: 4) { header(context: context) Text(context.state.preview) .font(.caption) .foregroundStyle(.secondary) .lineLimit(2) } } DynamicIslandExpandedRegion(.bottom) { HStack(spacing: 10) { Button(intent: OpenMailLiveActivityIntent(route: context.state.route)) { Text("Open") } .buttonStyle(.borderedProminent) Button(intent: SnoozeMailLiveActivityIntent()) { Text("Snooze") } .buttonStyle(.bordered) } } } compactLeading: { Image(systemName: "envelope.fill") } compactTrailing: { Text(context.state.initials) .font(.caption2.weight(.bold)) } minimal: { Image(systemName: "envelope.fill") } } } @ViewBuilder private func header(context: ActivityViewContext) -> some View { HStack(spacing: 10) { Text(context.state.initials) .font(.caption.weight(.bold)) .foregroundStyle(SIO.tint) .frame(width: 28, height: 28) .background(SIO.tint.opacity(0.12), in: Circle()) VStack(alignment: .leading, spacing: 2) { Text(context.state.sender) .font(.subheadline.weight(.semibold)) Text(context.state.subject) .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } } } } #if !WIDGET_EXTENSION enum MailNotificationActivityController { static func startIfNeeded(with thread: MailThread) async { guard ActivityAuthorizationInfo().areActivitiesEnabled else { return } let attributes = MailNotificationActivityAttributes(threadRouteID: thread.routeID) let state = MailNotificationActivityAttributes.ContentState( sender: thread.latestMessage?.sender.name ?? thread.participants.first?.name ?? "social.io", initials: initials(from: thread.latestMessage?.sender.name ?? thread.participants.first?.name ?? "SI"), subject: thread.subject, preview: thread.previewText, route: "socialio://open?thread=\(thread.routeID)" ) if let existing = Activity.activities.first { await existing.update(ActivityContent(state: state, staleDate: nil)) return } _ = try? Activity.request( attributes: attributes, content: ActivityContent(state: state, staleDate: nil) ) } private static func initials(from name: String) -> String { String(name.split(separator: " ").prefix(2).compactMap { $0.first }).uppercased() } } #endif #endif