- 删除分组实体:分组视图/编辑器/仓储与 groupId 全部移除,升级首启清理 instance-groups.json 与 fleet.groupMode - 新增批量标签表:三态覆盖度(未贴 / N/M / 全部),整批加减与新建标签 - 配色重构,全应用零蓝色:石墨墨色承担主操作与选中态,颜色只留给状态语义; 浅色改中性冷灰,深色改低明度石墨三层 - 选中态改实心主色 + onPrimary,替换不可读的低透明度色雾(主题块、图例胶囊) - 应用图标重做为扁平石墨底 + 黄铜 SIM 面板,参数化光栅化脚本与参考稿 - 运行概览:检测进度并入副标题,读数栅格固定八格两行并保留不可用占位 - 实例详情:仪表改两列发丝线栅格,缺数据留轨道,修掉头部高度回弹; 补齐设备名称/存储总量/可用存储/使用率四个字段名 - 版本号显示去掉括号,设置页只显示 0.2.2 - 文档:新增 docs/ios-native-0.2.2.md,同步 ios-native.md 与两处 README
149 lines
3.9 KiB
Swift
149 lines
3.9 KiB
Swift
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.origin.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] : [])
|
|
}
|
|
}
|