Add an iPhone client sharing the macOS app's SwiftUI code

This commit is contained in:
Ian Gulliver
2026-10-21 22:00:54 -07:00
parent 57d660c57f
commit 9e81e9107d
46 changed files with 1625 additions and 287 deletions
+47 -14
View File
@@ -1,4 +1,3 @@
import AppKit
import ByreAPI
import SwiftUI
@@ -175,12 +174,21 @@ final class ChatModel {
self?.builder.appendDelta(ev)
}
})
#if os(macOS)
activeObserver = NotificationCenter.default.addObserver(of: NSApplication.self, for: .didBecomeActive) { [weak self] _ in
self?.markShown()
}
keyObserver = NotificationCenter.default.addObserver(of: NSWindow.self, for: .didBecomeKey) { [weak self] _ in
self?.markShown()
}
#else
activeObserver = NotificationCenter.default.addObserver(of: UIApplication.self, for: .didBecomeActive) { [weak self] _ in
self?.markShown()
}
offs.append(store.paneFocus.shown.add { [weak self] _ in
self?.markShown()
})
#endif
}
func stop() {
@@ -225,7 +233,7 @@ final class ChatModel {
}
private func markShown() {
guard let taskId, store.paneFocus.windowKey else {
guard let taskId, store.paneFocus.taskVisible else {
return
}
for seq in visibleSummaries {
@@ -460,7 +468,7 @@ final class ChatModel {
var imgs: [PendingImage] = []
for id in p.imageIds {
let data = try await ImageCache.fetch(.blob(id), store.api)
guard let image = NSImage(data: data) else {
guard let image = PlatformImage(data: data) else {
throw ApiError(status: 0, message: "not an image")
}
imgs.append(PendingImage(data: data, image: image))
@@ -903,34 +911,59 @@ struct StatusButton: View {
}
}
private struct Scanner: NSViewRepresentable {
@MainActor
private struct Scanner {
let color: Color
func update(_ v: ScannerView, _ environment: EnvironmentValues) {
let c = color.resolve(in: environment).cgColor
let clear = c.copy(alpha: 0)!
CATransaction.begin()
CATransaction.setDisableActions(true)
v.glow.colors = [clear, c, clear]
CATransaction.commit()
}
}
#if os(macOS)
extension Scanner: NSViewRepresentable {
func makeNSView(context: Context) -> ScannerView {
ScannerView()
}
func updateNSView(_ v: ScannerView, context: Context) {
let c = NSColor(color)
CATransaction.begin()
CATransaction.setDisableActions(true)
v.glow.colors = [c.withAlphaComponent(0).cgColor, c.cgColor, c.withAlphaComponent(0).cgColor]
CATransaction.commit()
update(v, context.environment)
}
}
#else
extension Scanner: UIViewRepresentable {
func makeUIView(context: Context) -> ScannerView {
ScannerView()
}
private final class ScannerView: NSView {
func updateUIView(_ v: ScannerView, context: Context) {
update(v, context.environment)
}
}
#endif
private final class ScannerView: PlatformView {
let glow = CAGradientLayer()
override init(frame: NSRect) {
override init(frame: CGRect) {
super.init(frame: frame)
#if os(macOS)
wantsLayer = true
let host = layer!
#else
let host = layer
#endif
let dots = CALayer()
dots.frame = CGRect(x: 0, y: 0, width: 84, height: 2)
for x in stride(from: 0.0, through: 80, by: 5) {
let dot = CALayer()
dot.frame = CGRect(x: x, y: 0, width: 2, height: 2)
dot.backgroundColor = NSColor.black.cgColor
dot.backgroundColor = CGColor(gray: 0, alpha: 1)
dots.addSublayer(dot)
}
glow.bounds = CGRect(x: 0, y: 0, width: 30, height: 2)
@@ -941,7 +974,7 @@ private final class ScannerView: NSView {
strip.frame = dots.frame
strip.mask = dots
strip.addSublayer(glow)
layer?.addSublayer(strip)
host.addSublayer(strip)
let sweep = CABasicAnimation(keyPath: "position.x")
sweep.fromValue = 15
sweep.toValue = 69
@@ -987,7 +1020,7 @@ private struct PendingView: View {
if !pending.images.isEmpty {
FlowLayout(spacing: 6) {
ForEach(pending.images) { img in
Image(nsImage: img.image)
Image(platformImage: img.image)
.resizable()
.scaledToFit()
.frame(maxWidth: 240, maxHeight: 180)