Files
ios-gitea-prs/GiteaPR/Detail/Sheets.swift
T
Hermes 2301b8a861
Build iOS App / Build .ipa and publish source (push) Successful in 23s
Merge success animation + auto-return to landing page; bump to 3.0
On a successful merge, show a brief full-screen green checkmark animation
('Merged'), then automatically pop the PR detail view back to the landing
page. MergeSheet now calls an onSuccess callback; PRDetailView shows the
overlay and dismisses after ~1.2s. Bump to 3.0.
2026-08-13 16:35:06 +00:00

142 lines
5.2 KiB
Swift

import SwiftUI
/// Composes a comment. `mode` picks whether it posts a plain comment or a
/// review with a state (Approve / Request changes / Comment).
struct CommentSheet: View {
@ObservedObject var vm: PRDetailViewModel
@Environment(\.dismiss) private var dismiss
@State private var text = ""
var mode: CommentMode = .comment
enum CommentMode { case comment, approve, requestChanges }
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 12) {
Text(title).font(.headline)
TextEditor(text: $text)
.frame(minHeight: 150)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color(.separator)))
Text("\(text.count) characters")
.font(.caption2)
.foregroundColor(.secondary)
Spacer()
}
.padding()
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("Submit") { submit() }
.disabled(text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
}
}
}
private var title: String {
switch mode {
case .approve: return "Approve"
case .requestChanges: return "Request changes"
case .comment: return "Comment"
}
}
private func submit() {
let body = text.trimmingCharacters(in: .whitespacesAndNewlines)
switch mode {
case .approve:
Task { await vm.submitReview(state: "approved", comment: body) }
case .requestChanges:
Task { await vm.submitReview(state: "changes", comment: body) }
case .comment:
Task { await vm.postComment(body) }
}
dismiss()
}
}
struct MergeSheet: View {
@ObservedObject var vm: PRDetailViewModel
@Environment(\.dismiss) private var dismiss
@State private var method = "merge"
/// Called when the merge succeeds, so the parent can show the success
/// animation and pop back to the landing page.
var onSuccess: () -> Void = {}
private let methods = [("merge", "Create merge commit"),
("rebase", "Rebase and merge"),
("squash", "Squash and merge")]
var body: some View {
NavigationStack {
Form {
Section("Merge method") {
Picker("Method", selection: $method) {
ForEach(methods, id: \.0) { m in
Text(m.1).tag(m.0)
}
}
.pickerStyle(.inline)
}
Section {
HStack {
Text("CI")
Spacer()
switch vm.ciStatus {
case .success: Label("Passing", systemImage: "checkmark.circle.fill")
.foregroundColor(.green)
case .failure: Label("Failing", systemImage: "xmark.circle.fill")
.foregroundColor(.red)
case .running: Label("Running", systemImage: "hourglass")
.foregroundColor(.orange)
case .cancelled, .unknown: Label("Unknown", systemImage: "questionmark.circle")
.foregroundColor(.secondary)
}
}
if vm.ciStatus == .failure {
if SettingsStore.shared.mergeGateMode == .block {
Label("Merging is blocked while CI is red (Settings).",
systemImage: "lock")
.font(.caption)
.foregroundColor(.secondary)
} else {
Label("CI is red — you can still merge (Settings allows it).",
systemImage: "exclamationmark.triangle")
.font(.caption)
.foregroundColor(.orange)
}
}
}
}
.navigationTitle("Merge")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
}
ToolbarItem(placement: .confirmationAction) {
Button("Merge") { performMerge() }
.disabled(SettingsStore.shared.mergeGateMode == .block
&& vm.ciStatus == .failure)
}
}
}
}
private func performMerge() {
Task {
let success = await vm.merge(method: method)
if success {
dismiss()
onSuccess()
}
}
}
}