krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v2.9.0: Shared/NeedsAttentionSnapshot.swift · raw

  1import Foundation
  2#if canImport(WidgetKit)
  3import WidgetKit
  4#endif
  5
  6enum HutchAppGroup {
  7    static let identifier = "group.net.cleberg.Hutch"
  8}
  9
 10enum NeedsAttentionWidgetConfiguration {
 11    static let kind = "NeedsAttentionWidget"
 12}
 13
 14struct NeedsAttentionSnapshot: Codable, Sendable {
 15    let unreadInboxThreads: Int?
 16    let assignedOpenTickets: Int?
 17    let failedBuilds: Int?
 18    let updatedAt: Date
 19
 20    static let unavailable = NeedsAttentionSnapshot(
 21        unreadInboxThreads: nil,
 22        assignedOpenTickets: nil,
 23        failedBuilds: nil,
 24        updatedAt: .now
 25    )
 26
 27    var hasAnyData: Bool {
 28        unreadInboxThreads != nil || assignedOpenTickets != nil || failedBuilds != nil
 29    }
 30
 31    var allCountsAreZero: Bool {
 32        let counts = [unreadInboxThreads, assignedOpenTickets, failedBuilds].compactMap { $0 }
 33        return !counts.isEmpty && counts.allSatisfy { $0 == 0 }
 34    }
 35}
 36
 37enum NeedsAttentionSnapshotStore {
 38    private static let snapshotKey = "needsAttention.snapshot"
 39
 40    static func load(defaults: UserDefaults? = sharedDefaults()) -> NeedsAttentionSnapshot? {
 41        guard let defaults,
 42              let data = defaults.data(forKey: snapshotKey) else {
 43            return nil
 44        }
 45
 46        return try? JSONDecoder().decode(NeedsAttentionSnapshot.self, from: data)
 47    }
 48
 49    static func save(_ snapshot: NeedsAttentionSnapshot, defaults: UserDefaults? = sharedDefaults()) {
 50        guard let defaults,
 51              let data = try? JSONEncoder().encode(snapshot) else {
 52            return
 53        }
 54
 55        defaults.set(data, forKey: snapshotKey)
 56        reloadWidgetTimelines()
 57    }
 58
 59    static func update(
 60        unreadInboxThreads: Int? = nil,
 61        assignedOpenTickets: Int? = nil,
 62        failedBuilds: Int? = nil,
 63        defaults: UserDefaults? = sharedDefaults()
 64    ) {
 65        let existing = load(defaults: defaults)
 66        let snapshot = NeedsAttentionSnapshot(
 67            unreadInboxThreads: unreadInboxThreads ?? existing?.unreadInboxThreads,
 68            assignedOpenTickets: assignedOpenTickets ?? existing?.assignedOpenTickets,
 69            failedBuilds: failedBuilds ?? existing?.failedBuilds,
 70            updatedAt: .now
 71        )
 72        save(snapshot, defaults: defaults)
 73    }
 74
 75    static func adjustUnreadInboxThreads(
 76        by delta: Int,
 77        defaults: UserDefaults? = sharedDefaults()
 78    ) {
 79        guard let existing = load(defaults: defaults),
 80              let unreadInboxThreads = existing.unreadInboxThreads else {
 81            return
 82        }
 83
 84        save(
 85            NeedsAttentionSnapshot(
 86                unreadInboxThreads: max(0, unreadInboxThreads + delta),
 87                assignedOpenTickets: existing.assignedOpenTickets,
 88                failedBuilds: existing.failedBuilds,
 89                updatedAt: .now
 90            ),
 91            defaults: defaults
 92        )
 93    }
 94
 95    static func clear(defaults: UserDefaults? = sharedDefaults()) {
 96        defaults?.removeObject(forKey: snapshotKey)
 97        reloadWidgetTimelines()
 98    }
 99
100    private static func sharedDefaults() -> UserDefaults? {
101        UserDefaults(suiteName: HutchAppGroup.identifier)
102    }
103
104    private static func reloadWidgetTimelines() {
105        #if canImport(WidgetKit)
106        WidgetCenter.shared.reloadTimelines(ofKind: NeedsAttentionWidgetConfiguration.kind)
107        #endif
108    }
109}