a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Releases/ReleaseViewModels.swift

main
gitbay-ios/gitbay/Releases/ReleaseViewModels.swift history · blame · raw

112 lines · 3412 bytes

  1import Foundation
  2import Observation
  3
  4/// `release list <owner/name>`.
  5@Observable
  6@MainActor
  7final class ReleaseListViewModel {
  8
  9    private(set) var state: LoadState<[Release]> = .loading
 10    private(set) var createError: String?
 11    private(set) var working = false
 12
 13    private let client: GitbayClient
 14    let repoPath: String
 15
 16    init(client: GitbayClient, repoPath: String) {
 17        self.client = client
 18        self.repoPath = repoPath
 19    }
 20
 21    func load() async {
 22        do {
 23            let releases = try await client.readList(["release", "list", repoPath], of: Release.self)
 24            state = releases.isEmpty
 25                ? .empty("No releases. Tag a commit, push the tag, then release it.")
 26                : .loaded(releases)
 27        } catch {
 28            state = .from(error)
 29        }
 30    }
 31
 32    /// `release create <owner/name> <tag> [--title <t>] [--file -]` 
 33    /// the tag must already exist; the server's refusal says so.
 34    func create(tag: String, title: String, notes: String) async -> Bool {
 35        working = true
 36        createError = nil
 37        defer { working = false }
 38        do {
 39            var argv = ["release", "create", repoPath, tag]
 40            if !title.isEmpty { argv.append(contentsOf: ["--title", title]) }
 41            var stdin: String?
 42            if !notes.isEmpty {
 43                argv.append(contentsOf: ["--file", "-"])
 44                stdin = notes
 45            }
 46            try await client.run(argv, stdin: stdin)
 47            await load()
 48            return true
 49        } catch let error as GitbayError {
 50            createError = error.userFacingMessage
 51        } catch {
 52            createError = GitbayError.transport(error).userFacingMessage
 53        }
 54        return false
 55    }
 56}
 57
 58/// One release: `release show`, plus title/notes editing.
 59@Observable
 60@MainActor
 61final class ReleaseDetailViewModel {
 62
 63    private(set) var state: LoadState<Release> = .loading
 64    private(set) var actionError: String?
 65    private(set) var working = false
 66
 67    private let client: GitbayClient
 68    let repoPath: String
 69    let tag: String
 70
 71    init(client: GitbayClient, repoPath: String, tag: String) {
 72        self.client = client
 73        self.repoPath = repoPath
 74        self.tag = tag
 75    }
 76
 77    func load() async {
 78        do {
 79            state = .loaded(try await client.read(
 80                ["release", "show", repoPath, tag], as: Release.self))
 81        } catch {
 82            state = .from(error)
 83        }
 84    }
 85
 86    func edit(title: String, notes: String) async {
 87        working = true
 88        actionError = nil
 89        defer { working = false }
 90        do {
 91            try await client.run(
 92                ["release", "edit", repoPath, tag, "--title", title, "--file", "-"],
 93                stdin: notes
 94            )
 95            await load()
 96        } catch let error as GitbayError {
 97            actionError = error.userFacingMessage
 98        } catch {
 99            actionError = GitbayError.transport(error).userFacingMessage
100        }
101    }
102
103    /// Where a browser downloads this asset  the web's own download
104    /// route; the JSON surface has no binary-safe asset read.
105    func downloadURL(for asset: Release.Asset) -> URL {
106        client.instance.baseURL
107            .appending(path: repoPath)
108            .appending(path: "releases/download")
109            .appending(path: tag)
110            .appending(path: asset.name)
111    }
112}