Files
ios-gitea-prs/GiteaPR/Views/InboxView.swift
T
Hermes 0d2c4a3cb6
Build iOS App / Build .ipa and publish source (push) Successful in 36s
Match Apple Music: full-width tab bar + large title; bump to 2.5
- Bottom tab bar: replace the floating capsule (which had space below it and
  was too thin) with a full-width frosted-glass bar at the standard iOS tab
  bar height, sitting flush above the home indicator — matching Apple Music.
- Landing page title: use .large navigation title so 'Pull Requests' aligns at
  the top like Apple Music's 'Inicio', instead of the inline title.
- Bump to 2.5.
2026-08-13 07:13:48 +00:00

114 lines
4.1 KiB
Swift

import SwiftUI
struct InboxView: View {
@StateObject private var vm = InboxViewModel()
@State private var showSettings = false
// Load the inbox only on first appearance. `.task` fires on every
// appearance (including popping back from a PR detail), so without this
// guard the landing page re-runs the full network refresh on each back
// navigation. After the first load, refresh only via pull-to-refresh.
@State private var didInitialLoad = 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")
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .primaryAction) { settingsButton }
}
.navigationDestination(for: PRRow.self) { row in
PRDetailView(repo: row.repo, prNumber: row.pr.number)
}
.task {
// Load only on first appearance; subsequent appearances (e.g.
// popping back from a PR) reuse the cached list and only
// refresh when the user pulls down.
guard !didInitialLoad else { return }
didInitialLoad = true
await vm.load()
}
.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())
}
}