import SwiftUI @main struct MultiSimAdminApp: App { @StateObject private var themeStore = ThemeStore.shared @State private var tab: AppTab init() { let arguments = ProcessInfo.processInfo.arguments var initial: AppTab = .fleet if arguments.contains("--tab-messages") { initial = .messages } else if arguments.contains("--tab-settings") { initial = .settings } #if DEBUG if arguments.contains("--tab-detail") { initial = .debugDetail } if arguments.contains("--tab-logs") { initial = .debugLogs } #endif _tab = State(initialValue: initial) } var body: some Scene { WindowGroup { Group { switch tab { case .fleet: NavigationStack { FleetView().aiInlineTitle() } case .messages: NavigationStack { MessagesView().aiInlineTitle() } case .settings: NavigationStack { SettingsView().aiInlineTitle() } #if DEBUG case .debugDetail: NavigationStack { DebugDetailView().aiInlineTitle() } case .debugLogs: NavigationStack { LogsView().aiInlineTitle() } #endif } } .safeAreaInset(edge: .bottom, spacing: 0) { AITabBar(selection: $tab) } .tint(AITheme.primary) .preferredColorScheme(themeStore.mode.colorScheme) #if os(iOS) .toolbarBackground(.ultraThinMaterial, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) #endif } } } #if os(iOS) extension View { fileprivate func aiInlineTitle() -> some View { navigationBarTitleDisplayMode(.inline) } } #else extension View { fileprivate func aiInlineTitle() -> some View { self } } #endif enum AppTab: Hashable { case fleet case messages case settings #if DEBUG case debugDetail case debugLogs #endif } #if DEBUG /// 本地截图验证用的入口,Release 构建不包含。 struct DebugDetailView: View { @State private var target: Instance? var body: some View { Group { if let instance = target { InstanceDetailView(instance: instance) } else { ProgressView() .frame(maxWidth: .infinity, maxHeight: .infinity) } } .task { let repository = FileInstanceRepository(location: DefaultContainer.documentFileURL("instances.json")) guard let listed = try? await repository.list(InstanceQuery()), listed.isEmpty == false else { return } // 截图验证优先看可达节点(3000),否则详情页全是空态,看不出真实排版。 target = listed.first { $0.endpoint.directOrigin?.port == 3000 } ?? listed[0] } } } #endif struct AITabBar: View { @Binding var selection: AppTab var body: some View { HStack(spacing: 0) { tab(.fleet, "节点", "server.rack") tab(.messages, "消息", "message") tab(.settings, "设置", "gearshape") } .frame(height: 62) .background { ZStack { Rectangle() .fill(.ultraThinMaterial) Rectangle() .fill(AITheme.chrome) } .ignoresSafeArea(edges: .bottom) } .overlay(alignment: .top) { Rectangle() .fill(AITheme.line) .frame(height: 1) } } private func tab(_ value: AppTab, _ title: String, _ systemImage: String) -> some View { let isSelected = selection == value return Button { selection = value } label: { VStack(spacing: 4) { Image(systemName: systemImage) .font(.system(size: 18, weight: .semibold)) Text(title) .font(.caption2.weight(.semibold)) } .foregroundStyle(isSelected ? AITheme.primary : AITheme.muted) .frame(maxWidth: .infinity, maxHeight: .infinity) } .buttonStyle(.plain) .accessibilityLabel(title) .accessibilityAddTraits(isSelected ? [.isSelected] : []) } }