krz/hutch

an ios client for sourcehut

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

v3.1.7: 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}