PadXcode-iPad/Validation/WorkingCopyGuard.swift
2026-04-12 00:46:30 -07:00

80 lines
3.1 KiB
Swift

import UIKit
import SwiftUI
import Foundation
enum WorkingCopyGuard {
enum GuardError: LocalizedError {
case notInstalled
case apiKeyMissing
case repoNameMissing(String)
var errorDescription: String? {
switch self {
case .notInstalled: return "Working Copy is not installed."
case .apiKeyMissing: return "Working Copy API key not set. Go to Settings → Working Copy."
case .repoNameMissing(let p): return "No Working Copy repo name set for '\(p)'."
}
}
var actionLabel: String {
switch self {
case .notInstalled: return "App Store"
default: return "Open Settings"
}
}
}
static func validate(projectName: String, requiresApiKey: Bool = true) throws {
guard UIApplication.shared.canOpenURL(URL(string: "working-copy://")!) else {
throw GuardError.notInstalled
}
if requiresApiKey {
let key = UserDefaults.standard.string(forKey: "workingCopyAPIKey") ?? ""
guard !key.trimmingCharacters(in: .whitespaces).isEmpty else {
throw GuardError.apiKeyMissing
}
}
let repoName = UserDefaults.standard.string(forKey: "wc_repo_for_\(projectName)") ?? ""
guard !repoName.trimmingCharacters(in: .whitespaces).isEmpty else {
throw GuardError.repoNameMissing(projectName)
}
}
}
struct WorkingCopyErrorBanner: ViewModifier {
@Binding var error: WorkingCopyGuard.GuardError?
func body(content: Content) -> some View {
content.overlay(alignment: .top) {
if let err = error {
HStack(spacing: 10) {
Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(.orange)
Text(err.localizedDescription ?? "").font(.subheadline).lineLimit(2)
Spacer()
Button(err.actionLabel) { handleAction(for: err) }.buttonStyle(.bordered).controlSize(.small)
Button { withAnimation { self.error = nil } }
label: { Image(systemName: "xmark").font(.caption.bold()) }
.buttonStyle(.plain).foregroundStyle(.secondary)
}
.padding(.horizontal, 14).padding(.vertical, 10)
.background(.regularMaterial)
.transition(.move(edge: .top).combined(with: .opacity))
}
}
.animation(.spring(response: 0.3), value: error != nil)
}
private func handleAction(for err: WorkingCopyGuard.GuardError) {
switch err {
case .notInstalled:
UIApplication.shared.open(URL(string: "https://apps.apple.com/app/working-copy-git-client/id896694807")!)
default:
NotificationCenter.default.post(name: .openSettings, object: nil)
}
self.error = nil
}
}
extension View {
func workingCopyErrorBanner(_ error: Binding<WorkingCopyGuard.GuardError?>) -> some View {
modifier(WorkingCopyErrorBanner(error: error))
}
}