BusinessCard/BusinessCardClip/BusinessCardClipApp.swift

116 lines
4.3 KiB
Swift

import SwiftUI
import AppIntents
import Bedrock
@main
struct BusinessCardClipApp: App {
@State private var recordName: String?
@State private var launchErrorMessage: String?
#if DEBUG
@State private var debugState: ClipDebugState?
#endif
var body: some Scene {
WindowGroup {
Group {
#if DEBUG
if let debugState {
ClipDebugHarnessView(initialState: debugState)
} else if let recordName {
ClipRootView(recordName: recordName)
} else if let launchErrorMessage {
ClipErrorView(message: launchErrorMessage) {
self.launchErrorMessage = nil
}
} else {
ClipLoadingView()
}
#else
if let recordName {
ClipRootView(recordName: recordName)
} else if let launchErrorMessage {
ClipErrorView(message: launchErrorMessage) {
self.launchErrorMessage = nil
}
} else {
ClipLoadingView()
}
#endif
}
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
Design.debugLog("Clip: onContinueUserActivity fired, webpageURL=\(activity.webpageURL?.absoluteString ?? "nil")")
handleUserActivity(activity)
}
.onOpenURL { url in
Design.debugLog("Clip: onOpenURL fired, url=\(url.absoluteString)")
handleURL(url)
}
.task {
Design.debugLog("Clip: .task running - checking launch sources")
#if DEBUG
if let dbg = parseDebugStateArgument() {
Design.debugLog("Clip: debug state from args: \(dbg.rawValue)")
debugState = dbg
return
}
#endif
if let envURL = ProcessInfo.processInfo.environment["_XCAppClipURL"],
let url = URL(string: envURL) {
Design.debugLog("Clip: _XCAppClipURL from env: \(envURL)")
handleURL(url)
} else {
Design.debugLog("Clip: no _XCAppClipURL in env, no debug args. recordName=\(recordName ?? "nil")")
}
}
}
}
private func handleUserActivity(_ activity: NSUserActivity) {
Design.debugLog("Clip: handleUserActivity, webpageURL=\(activity.webpageURL?.absoluteString ?? "nil")")
guard let url = activity.webpageURL else {
Design.debugLog("Clip: handleUserActivity - no webpageURL, skipping")
return
}
handleURL(url)
}
private func handleURL(_ url: URL) {
Design.debugLog("Clip: handleURL \(url.absoluteString)")
guard let id = extractRecordName(from: url) else {
Design.debugLog("Clip: extractRecordName failed for \(url.absoluteString)")
launchErrorMessage = ClipError.invalidRecord.localizedDescription
return
}
Design.debugLog("Clip: extracted recordName=\(id), setting recordName")
launchErrorMessage = nil
recordName = id
}
private func extractRecordName(from url: URL) -> String? {
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let id = components.queryItems?
.first(where: { $0.name == ClipDesign.URL.recordQueryName })?
.value,
!id.isEmpty {
return id
}
// Fallback for path-based links (e.g. /clip/{recordName}).
let candidate = url.lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines)
guard !candidate.isEmpty, candidate != "/" else { return nil }
return candidate
}
#if DEBUG
private func parseDebugStateArgument() -> ClipDebugState? {
let prefix = "--clip-debug="
guard let argument = ProcessInfo.processInfo.arguments.first(where: { $0.hasPrefix(prefix) }) else {
return nil
}
let value = String(argument.dropFirst(prefix.count))
return ClipDebugState(rawValue: value)
}
#endif
}