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,121 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum InboxFilter: String, CaseIterable, Identifiable {
|
||||
case all = "All"
|
||||
case assigned = "Assigned"
|
||||
case created = "By me"
|
||||
case review = "Review"
|
||||
|
||||
var id: String { rawValue }
|
||||
}
|
||||
|
||||
/// A PR row enriched with its repo and aggregate status.
|
||||
struct PRRow: Identifiable, Hashable {
|
||||
let repo: Repository
|
||||
let pr: PullRequest
|
||||
let ciStatus: CIStatus
|
||||
let reviewState: ReviewState
|
||||
|
||||
var id: String { "\(repo.fullName)#\(pr.number)" }
|
||||
var fullName: String { repo.fullName }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class InboxViewModel: ObservableObject {
|
||||
@Published var rows: [PRRow] = []
|
||||
@Published var isLoading = false
|
||||
@Published var errorMessage: String?
|
||||
@Published var filter: InboxFilter = .all {
|
||||
didSet { recomputeDisplayed() }
|
||||
}
|
||||
// Raw rows before filtering, kept so switching filters is instant.
|
||||
private var allRows: [PRRow] = []
|
||||
@Published var displayedRows: [PRRow] = []
|
||||
|
||||
var isConfigured: Bool { SettingsStore.shared.isConfigured }
|
||||
|
||||
private func recomputeDisplayed() {
|
||||
switch filter {
|
||||
case .all:
|
||||
displayedRows = allRows
|
||||
case .assigned:
|
||||
displayedRows = allRows.filter {
|
||||
($0.pr.assignees ?? []).contains {
|
||||
$0.login == AppSession.currentLogin
|
||||
}
|
||||
}
|
||||
case .created:
|
||||
displayedRows = allRows.filter {
|
||||
$0.pr.user?.login == AppSession.currentLogin
|
||||
}
|
||||
case .review:
|
||||
displayedRows = allRows.filter {
|
||||
switch $0.reviewState {
|
||||
case .needsReview, .unknown: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func load() async {
|
||||
guard isConfigured, let client = SettingsStore.shared.client() else {
|
||||
errorMessage = "Configure your Gitea instance in Settings."
|
||||
return
|
||||
}
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
defer { isLoading = false }
|
||||
|
||||
do {
|
||||
let repos = try await client.repos()
|
||||
// Fetch open PRs across all repos concurrently.
|
||||
var rows: [PRRow] = []
|
||||
try await withThrowingTaskGroup(of: [PRRow].self) { group in
|
||||
for repo in repos {
|
||||
group.addTask {
|
||||
let prs = try await client.pullRequests(owner: repo.owner.login,
|
||||
repo: repo.name)
|
||||
return try await self.enrich(prs, in: repo, client: client)
|
||||
}
|
||||
}
|
||||
for try await batch in group {
|
||||
rows.append(contentsOf: batch)
|
||||
}
|
||||
}
|
||||
// Sort: most recently updated first.
|
||||
rows.sort { ($0.pr.updatedAt ?? .distantPast) > ($1.pr.updatedAt ?? .distantPast) }
|
||||
allRows = rows
|
||||
recomputeDisplayed()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
allRows = []
|
||||
recomputeDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
/// Enrich each PR with CI + review status. Runs sequentially per repo to
|
||||
/// avoid hammering the API from one inbox refresh.
|
||||
private func enrich(_ prs: [PullRequest], in repo: Repository,
|
||||
client: GiteaClient) async throws -> [PRRow] {
|
||||
var out: [PRRow] = []
|
||||
for pr in prs {
|
||||
let sha = pr.head?.sha
|
||||
var ci: CIStatus = .unknown
|
||||
var review: ReviewState = .unknown
|
||||
if let sha, !sha.isEmpty {
|
||||
let runs = (try? await client.runs(owner: repo.owner.login,
|
||||
repo: repo.name, sha: sha)) ?? []
|
||||
ci = CIStatus.from(runs: runs)
|
||||
}
|
||||
let reviews = (try? await client.reviews(owner: repo.owner.login,
|
||||
repo: repo.name,
|
||||
number: pr.number)) ?? []
|
||||
review = ReviewState.from(reviews: reviews)
|
||||
out.append(PRRow(repo: repo, pr: pr,
|
||||
ciStatus: ci, reviewState: review))
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user