GiteaPR: native iOS app to manage Gitea pull requests
Build iOS App / Build .ipa and publish source (push) Failing after 35s
Build iOS App / Build .ipa and publish source (push) Failing after 35s
Multi-repo PR inbox, review (approve/request changes/comment), merge with gating toggle, read-only diff viewer, and Gitea Actions check status with one-tap re-run. Ships unsigned via the iMac runner to the SideStore source.
This commit is contained in:
commit
1025634978
27 files changed
+2322
No files matched your search
@@ -0,0 +1,151 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
final class PRDetailViewModel: ObservableObject {
|
||||
let repo: Repository
|
||||
let prNumber: Int
|
||||
|
||||
@Published var pr: PullRequest?
|
||||
@Published var comments: [IssueComment] = []
|
||||
@Published var reviews: [PullReview] = []
|
||||
@Published var files: [ChangedFile] = []
|
||||
@Published var commits: [PRCommit] = []
|
||||
@Published var runs: [ActionRun] = []
|
||||
@Published var jobs: [Int64: [ActionJob]] = [:]
|
||||
|
||||
@Published var isLoading = false
|
||||
@Published var isActionsLoading = false
|
||||
@Published var errorMessage: String?
|
||||
@Published var busyMessage: String?
|
||||
|
||||
private var owner: String { repo.owner.login }
|
||||
|
||||
init(repo: Repository, prNumber: Int) {
|
||||
self.repo = repo
|
||||
self.prNumber = prNumber
|
||||
}
|
||||
|
||||
var ciStatus: CIStatus { CIStatus.from(runs: runs) }
|
||||
var reviewState: ReviewState { ReviewState.from(reviews: reviews) }
|
||||
|
||||
var hasFailedRun: Bool {
|
||||
runs.contains {
|
||||
let c = ($0.conclusion ?? "").lowercased()
|
||||
return c == "failure" || c == "cancelled"
|
||||
}
|
||||
}
|
||||
|
||||
func load() async {
|
||||
guard let client = SettingsStore.shared.client() else { return }
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
defer { isLoading = false }
|
||||
do {
|
||||
async let prT = client.pullRequest(owner: owner, repo: repo.name, number: prNumber)
|
||||
async let commentsT = client.comments(owner: owner, repo: repo.name, number: prNumber)
|
||||
async let reviewsT = client.reviews(owner: owner, repo: repo.name, number: prNumber)
|
||||
async let filesT = client.changedFiles(owner: owner, repo: repo.name, number: prNumber)
|
||||
async let commitsT = client.commits(owner: owner, repo: repo.name, number: prNumber)
|
||||
pr = try await prT
|
||||
comments = try await commentsT
|
||||
reviews = try await reviewsT
|
||||
files = try await filesT
|
||||
commits = try await commitsT
|
||||
// CI runs depend on the head SHA from the PR.
|
||||
if let sha = pr?.head?.sha, !sha.isEmpty {
|
||||
await loadRuns(client: client, sha: sha)
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
private func loadRuns(client: GiteaClient, sha: String) async {
|
||||
isActionsLoading = true
|
||||
defer { isActionsLoading = false }
|
||||
do {
|
||||
runs = try await client.runs(owner: owner, repo: repo.name, sha: sha)
|
||||
// Load jobs for each run concurrently.
|
||||
for run in runs {
|
||||
let jobList = (try? await client.jobs(owner: owner, repo: repo.name,
|
||||
runID: run.id)) ?? []
|
||||
jobs[run.id] = jobList
|
||||
}
|
||||
} catch {
|
||||
// CI is best-effort; don't fail the whole detail view.
|
||||
}
|
||||
}
|
||||
|
||||
func rerun(runID: Int64) async {
|
||||
guard let client = SettingsStore.shared.client() else { return }
|
||||
busyMessage = "Re-running job…"
|
||||
defer { busyMessage = nil }
|
||||
do {
|
||||
try await client.rerun(owner: owner, repo: repo.name, runID: runID)
|
||||
if let sha = pr?.head?.sha {
|
||||
await loadRuns(client: client, sha: sha)
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
func postComment(_ body: String) async {
|
||||
guard let client = SettingsStore.shared.client() else { return }
|
||||
busyMessage = "Posting…"
|
||||
defer { busyMessage = nil }
|
||||
do {
|
||||
let c = try await client.postComment(owner: owner, repo: repo.name,
|
||||
number: prNumber, body: body)
|
||||
comments.append(c)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
func submitReview(state: String, comment: String?) async {
|
||||
guard let client = SettingsStore.shared.client() else { return }
|
||||
busyMessage = "Submitting…"
|
||||
defer { busyMessage = nil }
|
||||
do {
|
||||
let r = try await client.submitReview(owner: owner, repo: repo.name,
|
||||
number: prNumber, state: state,
|
||||
comment: comment)
|
||||
reviews.append(r)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
func merge(method: String) async -> Bool {
|
||||
guard let client = SettingsStore.shared.client() else { return false }
|
||||
busyMessage = "Merging…"
|
||||
defer { busyMessage = nil }
|
||||
do {
|
||||
try await client.merge(owner: owner, repo: repo.name,
|
||||
number: prNumber, method: method)
|
||||
// Refresh the PR state to reflect the merge.
|
||||
pr = try? await client.pullRequest(owner: owner, repo: repo.name,
|
||||
number: prNumber)
|
||||
return true
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func setState(_ state: String) async {
|
||||
guard let client = SettingsStore.shared.client() else { return }
|
||||
busyMessage = "Updating…"
|
||||
defer { busyMessage = nil }
|
||||
do {
|
||||
try await client.setState(owner: owner, repo: repo.name,
|
||||
number: prNumber, state: state)
|
||||
pr = try? await client.pullRequest(owner: owner, repo: repo.name,
|
||||
number: prNumber)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user