64 lines
2.1 KiB
Swift
64 lines
2.1 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
import Observation
|
|
|
|
/// Local restoration metadata only. Each app owns one store across its windows.
|
|
@MainActor @Observable
|
|
public final class RecentlyClosedStore<Entry: Codable & Identifiable> where Entry.ID == UUID {
|
|
public private(set) var entries: [Entry]
|
|
public private(set) var storageError: String?
|
|
private var persistentIDs: Set<UUID>
|
|
private let defaults: UserDefaults?
|
|
private let key: String
|
|
private var canSave = true
|
|
|
|
public init(defaults: UserDefaults?, key: String) {
|
|
self.defaults = defaults
|
|
self.key = key
|
|
do {
|
|
let entries = try defaults?.data(forKey: key).map { try JSONDecoder().decode([Entry].self, from: $0) } ?? []
|
|
self.entries = Array(entries.prefix(15))
|
|
persistentIDs = Set(entries.prefix(15).map(\.id))
|
|
} catch {
|
|
entries = []
|
|
persistentIDs = []
|
|
storageError = error.localizedDescription
|
|
canSave = false
|
|
}
|
|
}
|
|
|
|
public func record(_ entry: Entry, persist: Bool = true) {
|
|
entries.removeAll { $0.id == entry.id }
|
|
entries.insert(entry, at: 0)
|
|
entries = Array(entries.prefix(15))
|
|
if persist { persistentIDs.insert(entry.id) } else { persistentIDs.remove(entry.id) }
|
|
persistentIDs.formIntersection(entries.map(\.id))
|
|
save()
|
|
}
|
|
|
|
public func update(_ entry: Entry) {
|
|
guard let index = entries.firstIndex(where: { $0.id == entry.id }) else { return }
|
|
entries[index] = entry
|
|
save()
|
|
}
|
|
|
|
public func remove(_ id: UUID) {
|
|
entries.removeAll { $0.id == id }
|
|
persistentIDs.remove(id)
|
|
save()
|
|
}
|
|
|
|
private func save() {
|
|
guard canSave else { return }
|
|
do {
|
|
let data = try JSONEncoder().encode(entries.filter { persistentIDs.contains($0.id) })
|
|
defaults?.set(data, forKey: key)
|
|
storageError = nil
|
|
} catch {
|
|
storageError = error.localizedDescription
|
|
}
|
|
}
|
|
}
|