gitbay/Discovery/FeedEvent.swift

5411753023a5e2958f17b523903abce380941107
gitbay-ios/gitbay/Discovery/FeedEvent.swift history · blame · raw

84 lines · 3019 bytes

 1import Foundation
 2
 3/// One `feed` event. `data` varies by kind; unknown kinds and fields
 4/// must render, not sink the feed.
 5nonisolated struct FeedEvent: Decodable, Sendable, Hashable, Identifiable {
 6    let id: Int64
 7    let repo: String
 8    let actor: String?
 9    let kind: String
10    let data: Payload?
11    let createdAt: Date
12
13    enum CodingKeys: String, CodingKey {
14        case id, repo, actor, kind, data
15        case createdAt = "created_at"
16    }
17
18    nonisolated struct Payload: Decodable, Sendable, Hashable {
19        let number: Int64?
20        let sha: String?
21        let tag: String?
22        let title: String?
23    }
24
25    /// "opened !4", "build #56 succeeded" — the verb phrase after the actor.
26    var phrase: String {
27        let n = data?.number.map(String.init) ?? ""
28        switch kind {
29        case "mr.created": return "opened !\(n)"
30        case "mr.merged": return "merged !\(n)"
31        case "mr.closed": return "closed !\(n)"
32        case "issue.created": return "opened #\(n)"
33        case "issue.closed": return "closed #\(n)"
34        case "issue.open": return "reopened #\(n)"
35        case "issue.commented": return "commented on #\(n)"
36        case "build.success": return "build #\(n) succeeded"
37        case "build.failure": return "build #\(n) failed"
38        case "release.created": return "released \(data?.tag ?? "")"
39        case "push": return "pushed"
40        default: return kind
41        }
42    }
43
44    var icon: (name: String, isPositive: Bool?) {
45        switch kind {
46        case "mr.created": ("arrow.triangle.merge", nil)
47        case "mr.merged": ("arrow.triangle.merge", true)
48        case "mr.closed": ("xmark.circle", false)
49        case "issue.created", "issue.open": ("smallcircle.filled.circle", nil)
50        case "issue.closed": ("checkmark.circle", true)
51        case "issue.commented": ("bubble.left", nil)
52        case "build.success": ("checkmark.circle.fill", true)
53        case "build.failure": ("xmark.circle.fill", false)
54        case "release.created": ("shippingbox", nil)
55        case "push": ("arrow.up.circle", nil)
56        default: ("circle", nil)
57        }
58    }
59
60    /// Where tapping the row goes, when the event names something the app
61    /// has a screen for.
62    var destination: FeedDestination? {
63        if kind == "release.created", let tag = data?.tag {
64            return .release(repo: repo, tag: tag)
65        }
66        guard let number = data?.number else {
67            return .repo(repo)
68        }
69        switch kind.split(separator: ".").first {
70        case "mr": return .mr(repo: repo, number: number)
71        case "issue": return .issue(repo: repo, number: number)
72        case "build": return .build(repo: repo, number: number)
73        default: return .repo(repo)
74        }
75    }
76}
77
78nonisolated enum FeedDestination: Hashable {
79    case repo(String)
80    case mr(repo: String, number: Int64)
81    case issue(repo: String, number: Int64)
82    case build(repo: String, number: Int64)
83    case release(repo: String, tag: String)
84}