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" 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() } } } }