Models: - Convert Conversation and ChatMessage to SwiftData @Model classes with a one-to-many relationship and cascade delete. - Rename ChatMessage.id to clientID and drop Conversation.id in favor of jid (the string id would clash with PersistentIdentifier). - Keep MessageReaction as a Codable value stored in an array attribute. Persistence: - Add ArchiveStore, a per-account ModelContainer/ModelContext that replaces the JSON ChatArchive with load/save/erase. - Add ArchiveMetadataRecord for the durable MAM checkpoints, cursor, locally deleted message IDs and roster contact JIDs. - Add LegacyArchiveImporter to import the old JSON snapshot once and delete the file afterwards, preserving existing history. AppModel: - Replace ChatArchive with ArchiveStore and persist via context.save(); insert new models and delete the replaced object on upsert merges so SwiftData never keeps an orphaned duplicate. Tests: - Replace ChatArchiveTests with ArchiveStoreTests (store round-trip and legacy import) and drop the now-obsolete Codable round-trip assertions.
36 lines
1.1 KiB
Swift
36 lines
1.1 KiB
Swift
import Foundation
|
|
|
|
#if os(iOS)
|
|
import UIKit
|
|
typealias ChatMediaPlatformImage = UIImage
|
|
#elseif os(macOS)
|
|
import AppKit
|
|
typealias ChatMediaPlatformImage = NSImage
|
|
#endif
|
|
|
|
/// SwiftUI can evaluate every visible bubble repeatedly during scrolling.
|
|
/// Keep decoded thumbnails in a bounded native-image cache so those passes do
|
|
/// not repeatedly decompress JPEG/HEIC data on the main actor.
|
|
@MainActor
|
|
enum ChatMediaImageCache {
|
|
private static let cache: NSCache<NSString, ChatMediaPlatformImage> = {
|
|
let cache = NSCache<NSString, ChatMediaPlatformImage>()
|
|
cache.countLimit = 160
|
|
cache.totalCostLimit = 48 * 1_024 * 1_024
|
|
return cache
|
|
}()
|
|
|
|
static func image(
|
|
for message: ChatMessage,
|
|
data: Data
|
|
) -> ChatMediaPlatformImage? {
|
|
let cacheKey = "\(message.conversationID)\u{1F}\(message.clientID)" as NSString
|
|
if let cached = cache.object(forKey: cacheKey) {
|
|
return cached
|
|
}
|
|
guard let image = ChatMediaPlatformImage(data: data) else { return nil }
|
|
cache.setObject(image, forKey: cacheKey, cost: data.count)
|
|
return image
|
|
}
|
|
}
|