import Foundation import Observation /// `profile show ` — the whole profile in one read: description, /// links, org membership (or an org's members), the repositories the /// account may see, and a year of activity. @Observable @MainActor final class ProfileViewModel { nonisolated struct Profile: Decodable, Sendable, Hashable { let name: String let kind: String // user | org let description: String? let website: String? let orgs: [Member]? let members: [Member]? let repos: [Repo] let activity: [Day]? let activityTotal: Int enum CodingKeys: String, CodingKey { case name, kind, description, website, orgs, members, repos, activity case activityTotal = "activity_total" } var isOrg: Bool { kind == "org" } nonisolated struct Member: Decodable, Sendable, Hashable, Identifiable { let name: String let role: String? var id: String { name } } nonisolated struct Repo: Decodable, Sendable, Hashable, Identifiable { let path: String let visibility: String let description: String? let archived: Bool? var id: String { path } var isPrivate: Bool { visibility == "private" } var isArchived: Bool { archived ?? false } var name: String { String(path.split(separator: "/").last ?? "") } } /// One day's contributions. Days with none are omitted, so the /// calendar is filled in by the view. nonisolated struct Day: Decodable, Sendable, Hashable { let date: String let count: Int } } private(set) var state: LoadState = .loading private let client: GitbayClient let name: String init(client: GitbayClient, name: String) { self.client = client self.name = name } func load() async { do { state = .loaded(try await client.read(["profile", "show", name], as: Profile.self)) } catch { state = .from(error) } } }