gitbay/Repos/RepoActionsViewModel.swift

v1.0.6
gitbay-ios/gitbay/Repos/RepoActionsViewModel.swift history · blame · raw

110 lines · 3303 bytes

  1import Foundation
  2import Observation
  3
  4/// Fork, watch, mute and bookmark — repository-level actions performed
  5/// against a `RepoDetail` already loaded elsewhere:
  6///
  7/// - Watch/mute/bookmark state now comes back on `repo show`
  8///   (`RepoDetail.watch`/`.bookmarked`), so this view model only sends
  9///   the write commands; the caller rereads `repo show` to see the result.
 10/// - `fork_of` is also on `repo fork`'s own response, which is why `fork`
 11///   hands its result back to the caller directly.
 12@Observable
 13@MainActor
 14final class RepoActionsViewModel {
 15
 16    /// `repo fork`'s result — the only place `fork_of` is reported.
 17    nonisolated struct ForkResult: Decodable, Sendable, Hashable {
 18        let path: String
 19        let forkOf: String
 20
 21        enum CodingKeys: String, CodingKey {
 22            case path
 23            case forkOf = "fork_of"
 24        }
 25    }
 26
 27    private(set) var actionError: String?
 28    private(set) var notice: String?
 29    private(set) var working = false
 30
 31    private let client: GitbayClient
 32    let repoPath: String
 33
 34    init(client: GitbayClient, repoPath: String) {
 35        self.client = client
 36        self.repoPath = repoPath
 37    }
 38
 39    // MARK: - Bookmarks
 40
 41    func setBookmarked(_ bookmarked: Bool) async {
 42        await perform(["repo", bookmarked ? "bookmark" : "unbookmark", repoPath])
 43    }
 44
 45    // MARK: - Watch / mute
 46
 47    func watch() async {
 48        await perform(["repo", "watch", repoPath])
 49        if actionError == nil {
 50            notice = "You are now watching this repository."
 51        }
 52    }
 53
 54    func mute() async {
 55        await perform(["repo", "mute", repoPath])
 56        if actionError == nil {
 57            notice = "Notifications for this repository are muted."
 58        }
 59    }
 60
 61    func unwatch() async {
 62        await perform(["repo", "unwatch", repoPath])
 63        if actionError == nil {
 64            notice = "Notifications for this repository are back to the default."
 65        }
 66    }
 67
 68    // MARK: - Fork
 69
 70    func fork(named name: String?) async -> ForkResult? {
 71        var argv = ["repo", "fork", repoPath]
 72        if let name {
 73            let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
 74            if !trimmed.isEmpty {
 75                argv += ["--name", trimmed]
 76            }
 77        }
 78        working = true
 79        actionError = nil
 80        notice = nil
 81        defer { working = false }
 82        do {
 83            guard let result = try await client.run(argv, as: ForkResult.self) else {
 84                actionError = "The fork succeeded, but the server reported nothing back."
 85                return nil
 86            }
 87            return result
 88        } catch let error as GitbayError {
 89            actionError = error.userFacingMessage
 90            return nil
 91        } catch {
 92            actionError = GitbayError.transport(error).userFacingMessage
 93            return nil
 94        }
 95    }
 96
 97    private func perform(_ argv: [String]) async {
 98        working = true
 99        actionError = nil
100        notice = nil
101        defer { working = false }
102        do {
103            try await client.run(argv)
104        } catch let error as GitbayError {
105            actionError = error.userFacingMessage
106        } catch {
107            actionError = GitbayError.transport(error).userFacingMessage
108        }
109    }
110}