gitbay/Discovery/ProfileViewModel.swift
72 lines · 2174 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
41 var id: String { path }
42 var isPrivate: Bool { visibility == "private" }
43 var isArchived: Bool { archived ?? false }
44 var name: String { String(path.split(separator: "/").last ?? "") }
45 }
46
47 /// One day's contributions. Days with none are omitted, so the
48 /// calendar is filled in by the view.
49 nonisolated struct Day: Decodable, Sendable, Hashable {
50 let date: String
51 let count: Int
52 }
53 }
54
55 private(set) var state: LoadState<Profile> = .loading
56
57 private let client: GitbayClient
58 let name: String
59
60 init(client: GitbayClient, name: String) {
61 self.client = client
62 self.name = name
63 }
64
65 func load() async {
66 do {
67 state = .loaded(try await client.read(["profile", "show", name], as: Profile.self))
68 } catch {
69 state = .from(error)
70 }
71 }
72}