import Foundation /// One `feed` event. `data` varies by kind; unknown kinds and fields /// must render, not sink the feed. nonisolated struct FeedEvent: Decodable, Sendable, Hashable, Identifiable { let id: Int64 let repo: String let actor: String? let kind: String let data: Payload? let createdAt: Date enum CodingKeys: String, CodingKey { case id, repo, actor, kind, data case createdAt = "created_at" } nonisolated struct Payload: Decodable, Sendable, Hashable { let number: Int64? let sha: String? let tag: String? let title: String? } /// "opened !4", "build #56 succeeded" — the verb phrase after the actor. var phrase: String { let n = data?.number.map(String.init) ?? "" switch kind { case "mr.created": return "opened !\(n)" case "mr.merged": return "merged !\(n)" case "mr.closed": return "closed !\(n)" case "issue.created": return "opened #\(n)" case "issue.closed": return "closed #\(n)" case "issue.open": return "reopened #\(n)" case "issue.commented": return "commented on #\(n)" case "build.success": return "build #\(n) succeeded" case "build.failure": return "build #\(n) failed" case "release.created": return "released \(data?.tag ?? "")" case "push": return "pushed" default: return kind } } var icon: (name: String, isPositive: Bool?) { switch kind { case "mr.created": ("arrow.triangle.merge", nil) case "mr.merged": ("arrow.triangle.merge", true) case "mr.closed": ("xmark.circle", false) case "issue.created", "issue.open": ("smallcircle.filled.circle", nil) case "issue.closed": ("checkmark.circle", true) case "issue.commented": ("bubble.left", nil) case "build.success": ("checkmark.circle.fill", true) case "build.failure": ("xmark.circle.fill", false) case "release.created": ("shippingbox", nil) case "push": ("arrow.up.circle", nil) default: ("circle", nil) } } /// Where tapping the row goes, when the event names something the app /// has a screen for. var destination: FeedDestination? { if kind == "release.created", let tag = data?.tag { return .release(repo: repo, tag: tag) } guard let number = data?.number else { return .repo(repo) } switch kind.split(separator: ".").first { case "mr": return .mr(repo: repo, number: number) case "issue": return .issue(repo: repo, number: number) case "build": return .build(repo: repo, number: number) default: return .repo(repo) } } } nonisolated enum FeedDestination: Hashable { case repo(String) case mr(repo: String, number: Int64) case issue(repo: String, number: Int64) case build(repo: String, number: Int64) case release(repo: String, tag: String) }