krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.8.2: Hutch/Views/Home/RecentActivityStore.swift · raw
1import Foundation
2
3enum RecentActivityKind: String, Codable, Sendable {
4 case repository
5 case ticket
6 case build
7}
8
9struct RecentActivityEntry: Codable, Hashable, Identifiable, Sendable {
10 let kind: RecentActivityKind
11 let title: String
12 let viewedAt: Date
13 let repositoryOwner: String?
14 let repositoryName: String?
15 let repositoryService: SRHTService?
16 let ticketOwnerUsername: String?
17 let ticketTrackerName: String?
18 let ticketId: Int?
19 let buildJobId: Int?
20
21 var id: String {
22 switch kind {
23 case .repository:
24 let service = repositoryService?.rawValue ?? SRHTService.git.rawValue
25 return "repo:\(service):\(repositoryOwner ?? "")/\(repositoryName ?? "")"
26 case .ticket:
27 return "ticket:\(ticketOwnerUsername ?? "")/\(ticketTrackerName ?? "")#\(ticketId ?? 0)"
28 case .build:
29 return "build:\(buildJobId ?? 0)"
30 }
31 }
32
33 var detailText: String {
34 switch kind {
35 case .repository:
36 let serviceName = repositoryService?.displayName ?? "Repository"
37 return "\(serviceName) • Viewed \(viewedAt.relativeDescription)"
38 case .ticket:
39 return "Ticket • Viewed \(viewedAt.relativeDescription)"
40 case .build:
41 return "Build • Viewed \(viewedAt.relativeDescription)"
42 }
43 }
44}
45
46enum RecentActivityStore {
47 private static let maximumEntries = 5
48
49 static func load(defaults: UserDefaults) -> [RecentActivityEntry] {
50 guard let data = defaults.data(forKey: AppStorageKeys.recentActivity) else {
51 return []
52 }
53
54 do {
55 return try JSONDecoder().decode([RecentActivityEntry].self, from: data)
56 } catch {
57 defaults.removeObject(forKey: AppStorageKeys.recentActivity)
58 return []
59 }
60 }
61
62 static func recordRepository(
63 _ repository: RepositorySummary,
64 defaults: UserDefaults,
65 now: Date = .now
66 ) {
67 record(
68 RecentActivityEntry(
69 kind: .repository,
70 title: "\(repository.owner.canonicalName)/\(repository.name)",
71 viewedAt: now,
72 repositoryOwner: repository.owner.canonicalName.srhtUsername,
73 repositoryName: repository.name,
74 repositoryService: repository.service,
75 ticketOwnerUsername: nil,
76 ticketTrackerName: nil,
77 ticketId: nil,
78 buildJobId: nil
79 ),
80 defaults: defaults
81 )
82 }
83
84 static func recordTicket(
85 ownerUsername: String,
86 trackerName: String,
87 ticketId: Int,
88 title: String,
89 defaults: UserDefaults,
90 now: Date = .now
91 ) {
92 record(
93 RecentActivityEntry(
94 kind: .ticket,
95 title: "#\(ticketId) \(title)",
96 viewedAt: now,
97 repositoryOwner: nil,
98 repositoryName: nil,
99 repositoryService: nil,
100 ticketOwnerUsername: ownerUsername,
101 ticketTrackerName: trackerName,
102 ticketId: ticketId,
103 buildJobId: nil
104 ),
105 defaults: defaults
106 )
107 }
108
109 static func recordBuild(
110 jobId: Int,
111 title: String,
112 defaults: UserDefaults,
113 now: Date = .now
114 ) {
115 record(
116 RecentActivityEntry(
117 kind: .build,
118 title: title,
119 viewedAt: now,
120 repositoryOwner: nil,
121 repositoryName: nil,
122 repositoryService: nil,
123 ticketOwnerUsername: nil,
124 ticketTrackerName: nil,
125 ticketId: nil,
126 buildJobId: jobId
127 ),
128 defaults: defaults
129 )
130 }
131
132 private static func record(_ entry: RecentActivityEntry, defaults: UserDefaults) {
133 var entries = load(defaults: defaults)
134 entries.removeAll { $0.id == entry.id }
135 entries.insert(entry, at: 0)
136
137 if entries.count > maximumEntries {
138 entries = Array(entries.prefix(maximumEntries))
139 }
140
141 save(entries, defaults: defaults)
142 }
143
144 private static func save(_ entries: [RecentActivityEntry], defaults: UserDefaults) {
145 guard let data = try? JSONEncoder().encode(entries) else { return }
146 defaults.set(data, forKey: AppStorageKeys.recentActivity)
147 }
148}