a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Discovery/ProfileViewModel.swift

main
gitbay-ios/gitbay/Discovery/ProfileViewModel.swift history · blame · raw

91 lines · 3067 bytes

 1import Foundation
 2import Observation
 3
 4/// `profile show <name>`  the whole profile in one read: description,
 5/// links, org membership (or an org's members), the repositories the
 6/// account may see, and a year of activity.
 7@Observable
 8@MainActor
 9final class ProfileViewModel {
10
11    nonisolated struct Profile: Decodable, Sendable, Hashable {
12        let name: String
13        let kind: String // user | org
14        let description: String?
15        let website: String?
16        let orgs: [Member]?
17        let members: [Member]?
18        let repos: [Repo]
19        let activity: [Day]?
20        let activityTotal: Int
21
22        enum CodingKeys: String, CodingKey {
23            case name, kind, description, website, orgs, members, repos, activity
24            case activityTotal = "activity_total"
25        }
26
27        var isOrg: Bool { kind == "org" }
28
29        nonisolated struct Member: Decodable, Sendable, Hashable, Identifiable {
30            let name: String
31            let role: String?
32            var id: String { name }
33        }
34
35        nonisolated struct Repo: Decodable, Sendable, Hashable, Identifiable {
36            let path: String
37            let visibility: String
38            let description: String?
39            let archived: Bool?
40            let topics: [String]?
41            let license: String?
42            let defaultBranch: String?
43            let updated: String?
44
45            enum CodingKeys: String, CodingKey {
46                case path, visibility, description, archived, topics, license, updated
47                case defaultBranch = "default_branch"
48            }
49
50            var id: String { path }
51            var isPrivate: Bool { visibility == "private" }
52            var isArchived: Bool { archived ?? false }
53            var name: String { String(path.split(separator: "/").last ?? "") }
54
55            /// "main · MIT · updated 2026-08-30", the line the web's repo
56            /// rows carry. Parts the server did not report are left out.
57            var meta: String {
58                var parts: [String] = []
59                if let defaultBranch, !defaultBranch.isEmpty { parts.append(defaultBranch) }
60                if let license, !license.isEmpty { parts.append(license) }
61                if let updated, !updated.isEmpty { parts.append("updated \(updated)") }
62                return parts.joined(separator: " · ")
63            }
64        }
65
66        /// One day's contributions. Days with none are omitted, so the
67        /// calendar is filled in by the view.
68        nonisolated struct Day: Decodable, Sendable, Hashable {
69            let date: String
70            let count: Int
71        }
72    }
73
74    private(set) var state: LoadState<Profile> = .loading
75
76    private let client: GitbayClient
77    let name: String
78
79    init(client: GitbayClient, name: String) {
80        self.client = client
81        self.name = name
82    }
83
84    func load() async {
85        do {
86            state = .loaded(try await client.read(["profile", "show", name], as: Profile.self))
87        } catch {
88            state = .from(error)
89        }
90    }
91}