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.
80 lines
2.8 KiB
Swift
80 lines
2.8 KiB
Swift
import SwiftUI
|
|
|
|
/// Read-only unified diff viewer for a single changed file.
|
|
///
|
|
/// Parses a Gitea unified diff patch into line hunks so added/deleted lines can
|
|
/// be colour-coded. Gitea only returns `patch` for small diffs; large files
|
|
/// fall back to a raw text view.
|
|
struct DiffView: View {
|
|
let file: ChangedFile
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let patch = file.patch, !patch.isEmpty {
|
|
ScrollView([.horizontal, .vertical]) {
|
|
LazyVStack(alignment: .leading, spacing: 0) {
|
|
ForEach(parse(patch)) { line in
|
|
diffLine(line)
|
|
}
|
|
}
|
|
.font(.caption.monospaced())
|
|
}
|
|
} else {
|
|
ContentUnavailableView(
|
|
"Diff too large to preview",
|
|
systemImage: "doc.text",
|
|
description: Text("Gitea didn't return a patch for this file."))
|
|
}
|
|
}
|
|
.navigationTitle(file.filename)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
private func diffLine(_ line: DiffLine) -> some View {
|
|
Text(line.text)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(.horizontal, 8)
|
|
.padding(.vertical, 1)
|
|
.background(background(for: line.type))
|
|
.foregroundColor(foreground(for: line.type))
|
|
}
|
|
|
|
private func background(for type: DiffLineType) -> Color {
|
|
switch type {
|
|
case .addition: return Color.green.opacity(0.15)
|
|
case .deletion: return Color.red.opacity(0.15)
|
|
case .hunk, .meta: return Color(.secondarySystemBackground)
|
|
case .context: return .clear
|
|
}
|
|
}
|
|
private func foreground(for type: DiffLineType) -> Color {
|
|
switch type {
|
|
case .addition: return .green
|
|
case .deletion: return .red
|
|
case .hunk: return .secondary
|
|
case .meta, .context: return .primary
|
|
}
|
|
}
|
|
|
|
private func parse(_ patch: String) -> [DiffLine] {
|
|
patch.components(separatedBy: "\n").map { line in
|
|
if line.hasPrefix("@@") { return DiffLine(text: line, type: .hunk) }
|
|
if line.hasPrefix("diff ") || line.hasPrefix("index ")
|
|
|| line.hasPrefix("--- ") || line.hasPrefix("+++ ") {
|
|
return DiffLine(text: line, type: .meta)
|
|
}
|
|
if line.hasPrefix("+") { return DiffLine(text: line, type: .addition) }
|
|
if line.hasPrefix("-") { return DiffLine(text: line, type: .deletion) }
|
|
return DiffLine(text: line, type: .context)
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum DiffLineType { case context, addition, deletion, hunk, meta }
|
|
|
|
private struct DiffLine: Identifiable {
|
|
let text: String
|
|
let type: DiffLineType
|
|
var id: UUID { UUID() }
|
|
}
|