cocottetech/@platform/codebase/@features/ai-copilot/ios-fe/Sources/App.swift
Natalie a26a845e16 feat(@projects/@cocottetech): add auto-refresh lifecycle to cockpit model
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-08 03:30:19 -07:00

151 lines
5.6 KiB
Swift

import SwiftUI
import CocotteCockpitKit
import CocottePlatformModels
import CocottePlatformAPIClient
// Stream 2 the iOS cockpit. TabView shell (navigation lives here, not the Kit):
// Drops · Assets · Fleet · Activity · Insights, reusing the shared model + views.
@main
struct CocotteCockpitiOSApp: App {
var body: some Scene {
WindowGroup { RootView() }
}
}
struct RootView: View {
@State private var model = RootView.makeModel()
@State private var theme: Theme = .dark
/// Build the data source. With `--api-base <host-root>` + `--user-id <uuid>`
/// (and optional `--token <jwt>`, `--org-id <uuid>`) the cockpit talks to the live
/// platform.api via the shared client; without them, the mock dataset.
/// `--api-base` is the host root (e.g. `http://black:3060`) the client adds `/api/v1`.
private static func makeModel() -> CockpitModel {
let args = ProcessInfo.processInfo.arguments
func value(_ flag: String) -> String? {
guard let i = args.firstIndex(of: flag), i + 1 < args.count else { return nil }
return args[i + 1]
}
guard let base = value("--api-base"), let url = URL(string: base),
let userIdStr = value("--user-id"), let userId = UUID(uuidString: userIdStr) else {
return CockpitModel()
}
let scope = TenantScope(userId: userId, orgId: value("--org-id").flatMap { UUID(uuidString: $0) })
let auth = InMemoryAuthProvider(token: value("--token"))
return CockpitModel(api: LiveCockpitAPI(baseURL: url, auth: auth, scope: scope))
}
@State private var selection: Int = {
let args = ProcessInfo.processInfo.arguments
if let i = args.firstIndex(of: "--tab"), i + 1 < args.count, let n = Int(args[i + 1]) { return n }
return 0
}()
private var tokens: Tokens { Tokens.make(theme, .regular) }
var body: some View {
TabView(selection: $selection) {
DropsTab(model: model, theme: $theme)
.tabItem { Label("Drops", systemImage: "tray.full") }.tag(0)
AssetsTab(model: model)
.tabItem { Label("Assets", systemImage: "photo.on.rectangle") }.tag(1)
FleetTab(model: model)
.tabItem { Label("Fleet", systemImage: "person.3") }.tag(2)
ActivityTab(model: model)
.tabItem { Label("Activity", systemImage: "bolt.horizontal") }.tag(3)
InsightsTab(model: model)
.tabItem { Label("Insights", systemImage: "chart.bar") }.tag(4)
}
.environment(\.tokens, tokens)
.preferredColorScheme(theme == .dark ? .dark : .light)
.tint(tokens.accent)
.task { await model.autoRefresh() }
}
}
private struct DropsTab: View {
var model: CockpitModel
@Binding var theme: Theme
@State private var path: [UUID] = []
@State private var composing = ProcessInfo.processInfo.arguments.contains("--composer")
var body: some View {
NavigationStack(path: $path) {
ContentDropsView(model: model, onSelectDrop: { path.append($0.id) })
.navigationTitle("CocotteAI · social")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button { theme = (theme == .dark ? .light : .dark) } label: {
Image(systemName: "circle.lefthalf.filled")
}
}
ToolbarItem(placement: .topBarTrailing) {
Button { composing = true } label: { Image(systemName: "plus") }
}
}
.navigationDestination(for: UUID.self) { id in
if let drop = model.drop(id) {
DropDetailView(drop: drop)
.navigationTitle(drop.displayTitle)
.navigationBarTitleDisplayMode(.inline)
}
}
.sheet(isPresented: $composing) {
ComposerView(model: model, onClose: { composing = false })
}
}
}
}
private struct AssetsTab: View {
var model: CockpitModel
var body: some View {
NavigationStack {
AssetLibraryView(model: model)
.navigationTitle("Assets")
.navigationBarTitleDisplayMode(.inline)
}
}
}
private struct FleetTab: View {
var model: CockpitModel
@State private var path: [UUID] = []
var body: some View {
NavigationStack(path: $path) {
FleetListView(model: model, onSelect: { path.append($0.id) })
.navigationTitle("Fleet · content")
.navigationBarTitleDisplayMode(.inline)
.navigationDestination(for: UUID.self) { id in
if let s = model.specialist(id) {
SpecialistDetailView(specialist: s)
.navigationTitle(s.displayName)
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
}
private struct ActivityTab: View {
var model: CockpitModel
var body: some View {
NavigationStack {
ActivityView(model: model)
.navigationTitle("Activity")
.navigationBarTitleDisplayMode(.inline)
}
}
}
private struct InsightsTab: View {
var model: CockpitModel
var body: some View {
NavigationStack {
AnalyticsView(model: model)
.navigationTitle("Insights")
.navigationBarTitleDisplayMode(.inline)
}
}
}