diff options
Diffstat (limited to 'Hutch/Views/Home/RecentActivityStore.swift')
| -rw-r--r-- | Hutch/Views/Home/RecentActivityStore.swift | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/Hutch/Views/Home/RecentActivityStore.swift b/Hutch/Views/Home/RecentActivityStore.swift new file mode 100644 index 0000000..464a2c1 --- /dev/null +++ b/Hutch/Views/Home/RecentActivityStore.swift @@ -0,0 +1,148 @@ +import Foundation + +enum RecentActivityKind: String, Codable, Sendable { + case repository + case ticket + case build +} + +struct RecentActivityEntry: Codable, Hashable, Identifiable, Sendable { + let kind: RecentActivityKind + let title: String + let viewedAt: Date + let repositoryOwner: String? + let repositoryName: String? + let repositoryService: SRHTService? + let ticketOwnerUsername: String? + let ticketTrackerName: String? + let ticketId: Int? + let buildJobId: Int? + + var id: String { + switch kind { + case .repository: + let service = repositoryService?.rawValue ?? SRHTService.git.rawValue + return "repo:\(service):\(repositoryOwner ?? "")/\(repositoryName ?? "")" + case .ticket: + return "ticket:\(ticketOwnerUsername ?? "")/\(ticketTrackerName ?? "")#\(ticketId ?? 0)" + case .build: + return "build:\(buildJobId ?? 0)" + } + } + + var detailText: String { + switch kind { + case .repository: + let serviceName = repositoryService?.displayName ?? "Repository" + return "\(serviceName) • Viewed \(viewedAt.relativeDescription)" + case .ticket: + return "Ticket • Viewed \(viewedAt.relativeDescription)" + case .build: + return "Build • Viewed \(viewedAt.relativeDescription)" + } + } +} + +enum RecentActivityStore { + private static let maximumEntries = 5 + + static func load(defaults: UserDefaults) -> [RecentActivityEntry] { + guard let data = defaults.data(forKey: AppStorageKeys.recentActivity) else { + return [] + } + + do { + return try JSONDecoder().decode([RecentActivityEntry].self, from: data) + } catch { + defaults.removeObject(forKey: AppStorageKeys.recentActivity) + return [] + } + } + + static func recordRepository( + _ repository: RepositorySummary, + defaults: UserDefaults, + now: Date = .now + ) { + record( + RecentActivityEntry( + kind: .repository, + title: "\(repository.owner.canonicalName)/\(repository.name)", + viewedAt: now, + repositoryOwner: repository.owner.canonicalName.srhtUsername, + repositoryName: repository.name, + repositoryService: repository.service, + ticketOwnerUsername: nil, + ticketTrackerName: nil, + ticketId: nil, + buildJobId: nil + ), + defaults: defaults + ) + } + + static func recordTicket( + ownerUsername: String, + trackerName: String, + ticketId: Int, + title: String, + defaults: UserDefaults, + now: Date = .now + ) { + record( + RecentActivityEntry( + kind: .ticket, + title: "#\(ticketId) \(title)", + viewedAt: now, + repositoryOwner: nil, + repositoryName: nil, + repositoryService: nil, + ticketOwnerUsername: ownerUsername, + ticketTrackerName: trackerName, + ticketId: ticketId, + buildJobId: nil + ), + defaults: defaults + ) + } + + static func recordBuild( + jobId: Int, + title: String, + defaults: UserDefaults, + now: Date = .now + ) { + record( + RecentActivityEntry( + kind: .build, + title: title, + viewedAt: now, + repositoryOwner: nil, + repositoryName: nil, + repositoryService: nil, + ticketOwnerUsername: nil, + ticketTrackerName: nil, + ticketId: nil, + buildJobId: jobId + ), + defaults: defaults + ) + } + + private static func record(_ entry: RecentActivityEntry, defaults: UserDefaults) { + var entries = load(defaults: defaults) + entries.removeAll { $0.id == entry.id } + entries.insert(entry, at: 0) + + if entries.count > maximumEntries { + entries = Array(entries.prefix(maximumEntries)) + } + + save(entries, defaults: defaults) + } + + private static func save(_ entries: [RecentActivityEntry], defaults: UserDefaults) { + guard let data = try? JSONEncoder().encode(entries) else { return } + defaults.set(data, forKey: AppStorageKeys.recentActivity) + } +} |
