feat(ios): refine native interface and app icon
Unify bottom actions, selection controls, navigation structure, spacing, and theme colors across iOS pages. Replace the app icon with a higher-contrast native-style asset and include its source SVG.
This commit is contained in:
@@ -2,106 +2,113 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct MultiSimAdminApp: App {
|
||||
@State private var tab: AppTab
|
||||
@State private var tab: AppTab
|
||||
|
||||
init() {
|
||||
let arguments = ProcessInfo.processInfo.arguments
|
||||
if arguments.contains("--tab-messages") {
|
||||
_tab = State(initialValue: .messages)
|
||||
} else if arguments.contains("--tab-notifications") {
|
||||
_tab = State(initialValue: .notifications)
|
||||
} else if arguments.contains("--tab-logs") {
|
||||
_tab = State(initialValue: .logs)
|
||||
} else if arguments.contains("--tab-settings") {
|
||||
_tab = State(initialValue: .settings)
|
||||
} else {
|
||||
_tab = State(initialValue: .fleet)
|
||||
}
|
||||
init() {
|
||||
let arguments = ProcessInfo.processInfo.arguments
|
||||
if arguments.contains("--tab-messages") {
|
||||
_tab = State(initialValue: .messages)
|
||||
} else if arguments.contains("--tab-notifications") {
|
||||
_tab = State(initialValue: .notifications)
|
||||
} else if arguments.contains("--tab-logs") {
|
||||
_tab = State(initialValue: .logs)
|
||||
} else if arguments.contains("--tab-settings") {
|
||||
_tab = State(initialValue: .settings)
|
||||
} else {
|
||||
_tab = State(initialValue: .fleet)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
VStack(spacing: 0) {
|
||||
switch tab {
|
||||
case .fleet:
|
||||
NavigationStack { FleetView().aiInlineTitle() }
|
||||
case .messages:
|
||||
NavigationStack { MessagesView().aiInlineTitle() }
|
||||
case .notifications:
|
||||
NavigationStack { NotificationsView().aiInlineTitle() }
|
||||
case .logs:
|
||||
NavigationStack { LogsView().aiInlineTitle() }
|
||||
case .settings:
|
||||
NavigationStack { SettingsView().aiInlineTitle() }
|
||||
}
|
||||
AITabBar(selection: $tab)
|
||||
}
|
||||
.tint(AITheme.primary)
|
||||
#if os(iOS)
|
||||
.toolbarBackground(AITheme.canvas, for: .navigationBar)
|
||||
.toolbarBackground(.visible, for: .navigationBar)
|
||||
#endif
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
VStack(spacing: 0) {
|
||||
switch tab {
|
||||
case .fleet:
|
||||
NavigationStack { FleetView().aiInlineTitle() }
|
||||
case .messages:
|
||||
NavigationStack { MessagesView().aiInlineTitle() }
|
||||
case .notifications:
|
||||
NavigationStack { NotificationsView().aiInlineTitle() }
|
||||
case .logs:
|
||||
NavigationStack { LogsView().aiInlineTitle() }
|
||||
case .settings:
|
||||
NavigationStack { SettingsView().aiInlineTitle() }
|
||||
}
|
||||
AITabBar(selection: $tab)
|
||||
}
|
||||
.tint(AITheme.primary)
|
||||
#if os(iOS)
|
||||
.toolbarBackground(.ultraThinMaterial, for: .navigationBar)
|
||||
.toolbarBackground(.visible, for: .navigationBar)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
private extension View {
|
||||
func aiInlineTitle() -> some View {
|
||||
navigationBarTitleDisplayMode(.inline)
|
||||
extension View {
|
||||
fileprivate func aiInlineTitle() -> some View {
|
||||
navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
private extension View {
|
||||
func aiInlineTitle() -> some View { self }
|
||||
}
|
||||
extension View {
|
||||
fileprivate func aiInlineTitle() -> some View { self }
|
||||
}
|
||||
#endif
|
||||
|
||||
enum AppTab: Hashable {
|
||||
case fleet
|
||||
case messages
|
||||
case notifications
|
||||
case logs
|
||||
case settings
|
||||
case fleet
|
||||
case messages
|
||||
case notifications
|
||||
case logs
|
||||
case settings
|
||||
}
|
||||
|
||||
struct AITabBar: View {
|
||||
@Binding var selection: AppTab
|
||||
@Binding var selection: AppTab
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
tab(.fleet, "节点", "server.rack")
|
||||
tab(.messages, "消息", "message")
|
||||
tab(.notifications, "通知", "bell")
|
||||
tab(.logs, "日志", "doc.text")
|
||||
tab(.settings, "设置", "gearshape")
|
||||
}
|
||||
.frame(height: 58)
|
||||
.background(AITheme.surface)
|
||||
.overlay(alignment: .top) {
|
||||
Rectangle()
|
||||
.fill(AITheme.line)
|
||||
.frame(height: 1)
|
||||
}
|
||||
.background(AITheme.surface.ignoresSafeArea(edges: .bottom))
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
tab(.fleet, "节点", "server.rack")
|
||||
tab(.messages, "消息", "message")
|
||||
tab(.notifications, "通知", "bell")
|
||||
tab(.logs, "日志", "doc.text")
|
||||
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] : [])
|
||||
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] : [])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user