import Foundation /// Inbox für Share-Extension. Die Extension persistiert hier, die /// Haupt-App liest beim Start und zeigt einen Banner mit /// "→ Als Karte speichern". Shared App-Group-Container. struct PendingShare: Codable, Identifiable, Hashable, Sendable { let id: String let text: String let sourceURL: String? let capturedAt: Date init(text: String, sourceURL: String? = nil, capturedAt: Date = .now) { id = "\(capturedAt.timeIntervalSince1970)-\(UUID().uuidString.prefix(8))" self.text = text self.sourceURL = sourceURL self.capturedAt = capturedAt } } enum PendingShareStore { static let appGroupID = "group.ev.mana.cards" static let filename = "pending-shares.json" static var url: URL? { FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: appGroupID)? .appendingPathComponent(filename) } /// FIFO-Liste aller noch nicht konsumierten Shares. static func readAll() -> [PendingShare] { guard let url, let data = try? Data(contentsOf: url) else { return [] } let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 return (try? decoder.decode([PendingShare].self, from: data)) ?? [] } /// Append-only-Write. Bei Concurrent-Writes aus Extension + Haupt-App /// kann ein Eintrag verloren gehen — akzeptabel, weil Extension nur /// schreibt wenn User aktiv "Teilen" tippt. static func append(_ share: PendingShare) { guard let url else { return } var all = readAll() all.append(share) write(all) } /// Entfernt einen Eintrag (wenn die Haupt-App ihn als Karte gespeichert hat). static func remove(id: String) { let all = readAll().filter { $0.id != id } write(all) } private static func write(_ shares: [PendingShare]) { guard let url else { return } let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 guard let data = try? encoder.encode(shares) else { return } try? data.write(to: url, options: .atomic) } }