summaryrefslogtreecommitdiff
path: root/Shared/NeedsAttentionSnapshot.swift
blob: f386af6d2a8967bd2a02afcf8ea63703c91736e0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import Foundation
#if canImport(WidgetKit)
import WidgetKit
#endif

enum HutchAppGroup {
    static let identifier = "group.net.cleberg.Hutch"
}

enum NeedsAttentionWidgetConfiguration {
    static let kind = "NeedsAttentionWidget"
}

struct NeedsAttentionSnapshot: Codable, Sendable {
    let unreadInboxThreads: Int?
    let assignedOpenTickets: Int?
    let failedBuilds: Int?
    let updatedAt: Date

    static let unavailable = NeedsAttentionSnapshot(
        unreadInboxThreads: nil,
        assignedOpenTickets: nil,
        failedBuilds: nil,
        updatedAt: .now
    )

    var hasAnyData: Bool {
        unreadInboxThreads != nil || assignedOpenTickets != nil || failedBuilds != nil
    }

    var allCountsAreZero: Bool {
        let counts = [unreadInboxThreads, assignedOpenTickets, failedBuilds].compactMap { $0 }
        return !counts.isEmpty && counts.allSatisfy { $0 == 0 }
    }
}

enum NeedsAttentionSnapshotStore {
    private static let snapshotKey = "needsAttention.snapshot"

    static func load(defaults: UserDefaults? = sharedDefaults()) -> NeedsAttentionSnapshot? {
        guard let defaults,
              let data = defaults.data(forKey: snapshotKey) else {
            return nil
        }

        return try? JSONDecoder().decode(NeedsAttentionSnapshot.self, from: data)
    }

    static func save(_ snapshot: NeedsAttentionSnapshot, defaults: UserDefaults? = sharedDefaults()) {
        guard let defaults,
              let data = try? JSONEncoder().encode(snapshot) else {
            return
        }

        defaults.set(data, forKey: snapshotKey)
        reloadWidgetTimelines()
    }

    static func update(
        unreadInboxThreads: Int? = nil,
        assignedOpenTickets: Int? = nil,
        failedBuilds: Int? = nil,
        defaults: UserDefaults? = sharedDefaults()
    ) {
        let existing = load(defaults: defaults)
        let snapshot = NeedsAttentionSnapshot(
            unreadInboxThreads: unreadInboxThreads ?? existing?.unreadInboxThreads,
            assignedOpenTickets: assignedOpenTickets ?? existing?.assignedOpenTickets,
            failedBuilds: failedBuilds ?? existing?.failedBuilds,
            updatedAt: .now
        )
        save(snapshot, defaults: defaults)
    }

    static func adjustUnreadInboxThreads(
        by delta: Int,
        defaults: UserDefaults? = sharedDefaults()
    ) {
        guard let existing = load(defaults: defaults),
              let unreadInboxThreads = existing.unreadInboxThreads else {
            return
        }

        save(
            NeedsAttentionSnapshot(
                unreadInboxThreads: max(0, unreadInboxThreads + delta),
                assignedOpenTickets: existing.assignedOpenTickets,
                failedBuilds: existing.failedBuilds,
                updatedAt: .now
            ),
            defaults: defaults
        )
    }

    static func clear(defaults: UserDefaults? = sharedDefaults()) {
        defaults?.removeObject(forKey: snapshotKey)
        reloadWidgetTimelines()
    }

    private static func sharedDefaults() -> UserDefaults? {
        UserDefaults(suiteName: HutchAppGroup.identifier)
    }

    private static func reloadWidgetTimelines() {
        #if canImport(WidgetKit)
        WidgetCenter.shared.reloadTimelines(ofKind: NeedsAttentionWidgetConfiguration.kind)
        #endif
    }
}