Files
hapi/ios/Hapi/HapiApp.swift
T
weishu e1093466c9 feat(ios): support macOS through Mac Catalyst
Build the app and notification extension for Mac Catalyst with the Mac
idiom, so Macs get a 1:1 native window instead of the scaled iPad app.
The Mac build keeps the run.hapi.app bundle ID and adds Mac entitlements
(sandbox, network, microphone, user-selected files, Mac APNs).

- Always use the split on Mac with a sidebar list style, denser rows,
  14pt reading text, a hidden duplicate titlebar title, a minimum window
  size and page-sized sheets; open web links in the default browser and
  hide the unavailable live QR scanner.
- Add menu bar / hardware-keyboard commands: New Session, Settings,
  Toggle Sidebar, a Session menu (settings, files, rename, export, stop,
  previous/next session) and a guide link.
- Composer: Return sends on Mac (Shift/Option-Return for a newline, IME
  untouched, first suggestion picked); dropped files and pasted images or
  copied files become attachments on all platforms.
- CI builds Mac Catalyst and runs the navigation, split and transcript
  geometry tests there; README documents the Mac build.
2026-11-03 11:15:11 +08:00

46 lines
1.5 KiB
Swift

import HapiProtocol
import OSLog
import SwiftUI
@main
struct HapiApp: App {
/// APNs registration callbacks + notification-center delegate install
/// (P3) — SwiftUI lifecycle apps get them only through the adaptor.
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
@State private var model = AppModel()
@Environment(\.scenePhase) private var scenePhase
private let log = Logger(subsystem: "run.hapi.companion", category: "app")
var body: some Scene {
WindowGroup {
RootView()
.environment(model)
.onOpenURL { url in
handleIncomingURL(url)
}
}
.onChange(of: scenePhase, initial: true) { _, phase in
model.handleScenePhase(phase)
}
.commands {
HapiCommands()
}
}
/// `hapicompanion://bind?hub=<url>&code=<accessToken>` — parse and route
/// through `AppModel`: unpaired → pairing confirm; already paired to that
/// hub → switch with a notice; a different hub → add-hub confirm.
///
/// Never log the URL itself: the query carries the access token.
private func handleIncomingURL(_ url: URL) {
guard let link = BindLink.parse(url.absoluteString) else {
log.warning("Ignoring malformed URL (scheme: \(url.scheme ?? "none", privacy: .public))")
return
}
if !model.handleBindLink(link) {
log.warning("Bind link carried an unusable hub URL")
}
}
}