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.
123 lines
4.3 KiB
Swift
123 lines
4.3 KiB
Swift
import SwiftUI
|
|
|
|
struct InboxView: View {
|
|
@StateObject private var vm = InboxViewModel()
|
|
@State private var showSettings = false
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Group {
|
|
if !vm.isConfigured {
|
|
ContentUnavailableView(
|
|
"Not configured",
|
|
systemImage: "key",
|
|
description: Text("Add your Gitea instance and token in Settings."))
|
|
.toolbar {
|
|
ToolbarItem(placement: .primaryAction) { settingsButton }
|
|
}
|
|
} else if vm.displayedRows.isEmpty && !vm.isLoading {
|
|
ContentUnavailableView(
|
|
"No open PRs",
|
|
systemImage: "tray",
|
|
description: Text(vm.filter == .all
|
|
? "Nothing to review."
|
|
: "No PRs match this filter."))
|
|
} else {
|
|
List {
|
|
ForEach(vm.displayedRows) { row in
|
|
NavigationLink(value: row) {
|
|
PRRowView(row: row)
|
|
}
|
|
}
|
|
}
|
|
.refreshable { await vm.load() }
|
|
.listStyle(.plain)
|
|
}
|
|
}
|
|
.navigationTitle("Pull Requests")
|
|
.toolbar {
|
|
ToolbarItem(placement: .primaryAction) { settingsButton }
|
|
}
|
|
.toolbar {
|
|
ToolbarItemGroup(placement: .bottomBar) {
|
|
Picker("Filter", selection: $vm.filter) {
|
|
ForEach(InboxFilter.allCases) { f in
|
|
Text(f.rawValue).tag(f)
|
|
}
|
|
}
|
|
.pickerStyle(.segmented)
|
|
.disabled(vm.displayedRows.isEmpty && vm.isLoading)
|
|
}
|
|
}
|
|
.navigationDestination(for: PRRow.self) { row in
|
|
PRDetailView(repo: row.repo, prNumber: row.pr.number)
|
|
}
|
|
.task { await vm.load() }
|
|
.overlay {
|
|
if vm.isLoading {
|
|
ProgressView()
|
|
}
|
|
}
|
|
.alert("Error", isPresented: .init(
|
|
get: { vm.errorMessage != nil },
|
|
set: { if !$0 { vm.errorMessage = nil } })) {
|
|
Button("OK", role: .cancel) {}
|
|
} message: {
|
|
Text(vm.errorMessage ?? "")
|
|
}
|
|
.sheet(isPresented: $showSettings) {
|
|
SettingsView()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var settingsButton: some View {
|
|
Button { showSettings = true } label: {
|
|
Image(systemName: "gearshape")
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PRRowView: View {
|
|
let row: PRRow
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: 12) {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
HStack(spacing: 6) {
|
|
Text("#\(row.pr.number)")
|
|
.font(.subheadline.weight(.bold))
|
|
.foregroundColor(.secondary)
|
|
CICircle(status: row.ciStatus)
|
|
if row.reviewState != .unknown {
|
|
ReviewBadge(state: row.reviewState)
|
|
}
|
|
if row.pr.draft == true {
|
|
Text("Draft")
|
|
.font(.caption2)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
Text(row.pr.title)
|
|
.font(.body.weight(.medium))
|
|
.lineLimit(2)
|
|
Text(row.fullName)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
Spacer()
|
|
if let updated = row.pr.updatedAt {
|
|
Text(relative(updated))
|
|
.font(.caption2)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
.padding(.vertical, 2)
|
|
}
|
|
|
|
private func relative(_ date: Date) -> String {
|
|
let f = RelativeDateTimeFormatter()
|
|
f.unitsStyle = .abbreviated
|
|
return f.localizedString(for: date, relativeTo: Date())
|
|
}
|
|
}
|