eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1import LChatCore
2import SwiftUI
3import WebKit
4
5// 🤖 A space-wide app's full-pane surface on iOS (docs/native-apps.md phase
6// 4): the webview seeded with the SPACE thread + its key, wearing the app's
7// identity — bootEmbed's space flag routes it through the web's own
8// openSpaceApp path (the space thread has no chat surface). Pushed from the
9// chat list's space band.
10struct SpaceAppScreen: View {
11 let store: ThreadStore
12 let app: String
13
14 var body: some View {
15 Group {
16 if let thread = store.spaceApps.surfaceThread(
17 app: app, title: store.registry.title(app), glyph: store.registry.glyph(app)) {
18 SpaceAppSurface(instance: store.instance, thread: thread,
19 myUserId: store.myUserId,
20 instanceKeyB64: store.instanceKey(), app: app)
21 } else {
22 ContentUnavailableView("This app isn't ready yet",
23 systemImage: "square.grid.2x2",
24 description: Text("Try again in a moment."))
25 }
26 }
27 .navigationTitle(store.registry.title(app))
28 .navigationBarTitleDisplayMode(.inline)
29 }
30}
31
32// The webview plus its bridge-observing chrome: the inline app-update offer
33// (AppUpdateBar) above the pane, so a space app that changed while on screen
34// asks before reloading like every other app surface.
35private struct SpaceAppSurface: View {
36 @ObservedObject private var bridge: WebBridge
37 let instance: SavedInstance
38 let thread: ChatThread
39 let myUserId: String
40 let instanceKeyB64: String?
41 let app: String
42
43 @MainActor
44 init(instance: SavedInstance, thread: ChatThread, myUserId: String,
45 instanceKeyB64: String?, app: String) {
46 self.instance = instance
47 self.thread = thread
48 self.myUserId = myUserId
49 self.instanceKeyB64 = instanceKeyB64
50 self.app = app
51 bridge = FeedWebCache.shared.bridge(for: instance, thread: thread,
52 myUserId: myUserId,
53 instanceKeyB64: instanceKeyB64,
54 app: app, space: true)
55 }
56
57 var body: some View {
58 VStack(spacing: 0) {
59 AppUpdateBar(bridge: bridge)
60 SpaceAppWebView(instance: instance, thread: thread, myUserId: myUserId,
61 instanceKeyB64: instanceKeyB64, app: app)
62 .ignoresSafeArea(edges: .bottom)
63 }
64 }
65}
66
67private struct SpaceAppWebView: UIViewRepresentable {
68 let instance: SavedInstance
69 let thread: ChatThread
70 let myUserId: String
71 let instanceKeyB64: String?
72 let app: String
73
74 func makeUIView(context: Context) -> WKWebView {
75 FeedWebCache.shared.webView(for: instance, thread: thread, myUserId: myUserId,
76 instanceKeyB64: instanceKeyB64, app: app, space: true)
77 }
78
79 func updateUIView(_ web: WKWebView, context: Context) {}
80}
81
82// 🤖 The chat list's SPACE BAND: one chip per space-wide install with an
83// openable surface — the iOS shape of the web sidebar's space rows (the
84// UIKit-backed thread list can't grow SwiftUI rows, so the band rides a
85// safe-area inset: in the layout, never floating over rows someone is
86// reading). Its own view so it observes SpaceAppsStore and AppRegistry
87// directly.
88struct SpaceBand: View {
89 @ObservedObject var spaceApps: SpaceAppsStore
90 @ObservedObject var registry: AppRegistry
91 let onOpen: (String) -> Void
92
93 private var apps: [AppManifest] {
94 registry.knownSpace(spaceApps.installs)
95 .compactMap { registry.manifest($0.id) }
96 .filter { $0.surface ?? true }
97 }
98
99 var body: some View {
100 if !apps.isEmpty {
101 ScrollView(.horizontal, showsIndicators: false) {
102 HStack(spacing: 8) {
103 ForEach(apps) { app in
104 Button { onOpen(app.name) } label: {
105 HStack(spacing: 6) {
106 Text(app.glyph).font(.system(size: 15))
107 Text(app.title)
108 .font(.subheadline.weight(.medium))
109 .lineLimit(1)
110 }
111 .padding(.horizontal, 12)
112 .padding(.vertical, 7)
113 .floatingGlass(in: Capsule())
114 .contentShape(Capsule())
115 }
116 .buttonStyle(.plain)
117 .accessibilityLabel("\(app.title), space app")
118 .accessibilityHint("Opens the app shared with everyone in this space")
119 }
120 }
121 .padding(.horizontal, 14)
122 .padding(.vertical, 8)
123 }
124 }
125 }
126}
127