Build iOS App / Build .ipa and publish source (push) Successful in 23s
The inbox was slow because it enriched every PR with CI runs + reviews (2 extra API calls per PR, sequential). Drop that from the list — CI/review status now shows only in the PR detail screen. Add a disk cache so the landing screen renders instantly from the last load, then refreshes in the background and keeps the cached list on network failure. Bump to 2.1.
109 lines
3.7 KiB
Swift
109 lines
3.7 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("Nothing to review."))
|
|
} 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 }
|
|
}
|
|
.navigationDestination(for: PRRow.self) { row in
|
|
PRDetailView(repo: row.repo, prNumber: row.pr.number)
|
|
}
|
|
.task { await vm.load() }
|
|
.overlay {
|
|
// Only show a spinner when there's nothing cached to display
|
|
// yet; with a cache the list renders instantly and refreshes
|
|
// in the background.
|
|
if vm.isLoading && vm.displayedRows.isEmpty {
|
|
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)
|
|
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())
|
|
}
|
|
}
|