a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Repos/RepoDetailViewModel.swift

main
gitbay-ios/gitbay/Repos/RepoDetailViewModel.swift history · blame · raw

90 lines · 2982 bytes

 1import Foundation
 2import Observation
 3
 4/// The repo screen: header from `repo show`, README found in the root
 5/// tree and fetched with `repo cat`.
 6@Observable
 7@MainActor
 8final class RepoDetailViewModel {
 9
10    private(set) var state: LoadState<RepoDetail> = .loading
11    /// README markdown, when the root tree has one. Absent is normal.
12    private(set) var readme: String?
13    private(set) var readmeName: String?
14    /// Whether this repo is on the account's dashboard. nil until known 
15    /// pin state only exists in the dashboard aggregate.
16    private(set) var isPinned: Bool?
17    private(set) var actionError: String?
18    private(set) var working = false
19
20    private let client: GitbayClient
21    private let path: String
22
23    init(client: GitbayClient, path: String) {
24        self.client = client
25        self.path = path
26    }
27
28    func load() async {
29        do {
30            let detail = try await client.read(["repo", "show", path], as: RepoDetail.self)
31            state = .loaded(detail)
32        } catch {
33            state = .from(error)
34            return
35        }
36        await loadPinned()
37        await loadReadme()
38    }
39
40    private func loadPinned() async {
41        guard let dashboard = try? await client.read(["dashboard"], as: DashboardData.self) else {
42            return
43        }
44        isPinned = dashboard.pinned.contains { $0.path == path }
45    }
46
47    // MARK: - Management actions
48
49    func setPinned(_ pinned: Bool) async {
50        await perform(["repo", pinned ? "pin" : "unpin", path])
51        await loadPinned()
52    }
53
54    func setArchived(_ archived: Bool) async {
55        await perform(["repo", archived ? "archive" : "unarchive", path])
56    }
57
58    private func perform(_ argv: [String]) async {
59        working = true
60        actionError = nil
61        defer { working = false }
62        do {
63            try await client.run(argv)
64            if let detail = try? await client.read(["repo", "show", path], as: RepoDetail.self) {
65                state = .loaded(detail)
66            }
67        } catch let error as GitbayError {
68            actionError = error.userFacingMessage
69        } catch {
70            actionError = GitbayError.transport(error).userFacingMessage
71        }
72    }
73
74    private func loadReadme() async {
75        // The README is whatever the root tree calls one, matched the way
76        // the web UI matches: README, README.md, readme.org, and so on.
77        guard let listing = try? await client.read(
78            ["repo", "tree", path], as: TreeListing.self
79        ) else { return }
80        let candidate = listing.entries.first {
81            !$0.isDirectory && $0.name.lowercased().hasPrefix("readme")
82        }
83        guard let candidate else { return }
84        guard let file = try? await client.read(
85            ["repo", "cat", path, candidate.name], as: FileContent.self
86        ), !file.binary, let content = file.content else { return }
87        readmeName = candidate.name
88        readme = content
89    }
90}