feat(ios): add native settings and storage controls
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject private var model: SettingsViewModel
|
||||
@State private var showsClearDialog = false
|
||||
|
||||
init(model: SettingsViewModel? = nil) {
|
||||
_model = StateObject(wrappedValue: model ?? DefaultContainer.makeSettingsModel())
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
AITheme.canvas.ignoresSafeArea()
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 12) {
|
||||
if let errorMessage = model.errorMessage {
|
||||
AICard {
|
||||
Text(errorMessage)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(AITheme.danger)
|
||||
}
|
||||
}
|
||||
if let notice = model.notice {
|
||||
AICard {
|
||||
Text(notice)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(AITheme.success)
|
||||
}
|
||||
}
|
||||
|
||||
runtimeCard
|
||||
storageCard
|
||||
securityCard
|
||||
backgroundCard
|
||||
}
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
.navigationTitle("设置")
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .primaryAction) {
|
||||
Button {
|
||||
Task { await model.refresh() }
|
||||
} label: {
|
||||
Label("刷新", systemImage: "arrow.clockwise")
|
||||
}
|
||||
.buttonStyle(AIButtonStyle(kind: .secondary, size: .regular))
|
||||
.disabled(model.isLoading)
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
await model.refresh()
|
||||
}
|
||||
.confirmationDialog(
|
||||
"确认清除运行日志?此操作不可恢复。",
|
||||
isPresented: $showsClearDialog,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("清除运行日志", role: .destructive) {
|
||||
Task { await model.clearRuntimeLogs() }
|
||||
}
|
||||
Button("取消", role: .cancel) {}
|
||||
}
|
||||
.task {
|
||||
await model.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
private var runtimeCard: some View {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
sectionTitle("应用信息", systemImage: "info.circle")
|
||||
if let runtime = model.runtime {
|
||||
infoRow("名称", runtime.appName)
|
||||
infoRow("版本", "\(runtime.marketingVersion) (\(runtime.buildVersion))")
|
||||
infoRow("标识符", AppMetadata.bundleIdentifier)
|
||||
infoRow("设备", "\(runtime.platform) · \(runtime.deviceModel)")
|
||||
} else {
|
||||
Text("正在读取应用信息…")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var storageCard: some View {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
sectionTitle("本地数据", systemImage: "externaldrive")
|
||||
infoRow("实例配置", "App Documents/instances.json")
|
||||
infoRow("日志中心", "App Documents/log-center.json")
|
||||
infoRow("日志数量", model.storageLabel)
|
||||
Button {
|
||||
showsClearDialog = true
|
||||
} label: {
|
||||
Label("清除运行日志", systemImage: "trash")
|
||||
}
|
||||
.buttonStyle(AIButtonStyle(kind: .danger, size: .regular))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var securityCard: some View {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
sectionTitle("安全与登录", systemImage: "lock.shield")
|
||||
infoRow("实例密码", "保存在 iOS 钥匙串")
|
||||
infoRow("登录会话", "仅保存在内存")
|
||||
infoRow("访问密码", "已停用")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var backgroundCard: some View {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
sectionTitle("后台能力", systemImage: "clock.badge.exclamationmark")
|
||||
Text(model.serverCapabilitiesNotice)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
if model.serverCapabilities.isEmpty {
|
||||
AITag("无后台任务", tint: AITheme.muted)
|
||||
} else {
|
||||
ForEach(model.serverCapabilities) { capability in
|
||||
infoRow(capability.name, capability.detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sectionTitle(_ title: String, systemImage: String) -> some View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: systemImage)
|
||||
.foregroundStyle(AITheme.primary)
|
||||
Text(title)
|
||||
.font(.headline.weight(.bold))
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
private func infoRow(_ title: String, _ value: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.font(.caption)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
Text(value)
|
||||
.font(.subheadline)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user