release(ios): 发布 0.2.1 并重构图标与界面细节
- 应用图标改用与 Web favicon 同源的扁平标记,金色 SIM 卡体加三格递增信号,29pt 下仍可读 - 标签统一为胶囊形状,提高深色主题下的描边权重,避免标签在暗色下消失 - 消息卡片与详情分区卡片收紧行距,单屏信息密度明显提升 - 详情头部指标精简为 4 行,数据模块改为单条读数 - 批量选择勾选框移出名称区域,工具栏控件统一为等高 34pt - 分组编辑器改为点选即生效,移除说明文字与常驻禁用按钮
This commit is contained in:
@@ -20,8 +20,8 @@ struct SettingsView: View {
|
||||
AIStatusBanner(notice, tint: AITheme.success, systemImage: "checkmark.circle")
|
||||
}
|
||||
|
||||
runtimeCard
|
||||
themeCard
|
||||
runtimeCard
|
||||
storageCard
|
||||
securityCard
|
||||
dangerZoneCard
|
||||
@@ -33,11 +33,7 @@ struct SettingsView: View {
|
||||
.navigationTitle("设置")
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .primaryAction) {
|
||||
AIToolbarButton(
|
||||
"刷新",
|
||||
systemImage: "arrow.clockwise",
|
||||
kind: .primary
|
||||
) {
|
||||
AIIconButton("刷新", systemImage: "arrow.clockwise") {
|
||||
Task { await model.refresh() }
|
||||
}
|
||||
.disabled(model.isLoading)
|
||||
@@ -67,7 +63,7 @@ struct SettingsView: View {
|
||||
AISectionTitle("应用信息", systemImage: "info.circle")
|
||||
if let runtime = model.runtime {
|
||||
AIKeyValueRow("版本", value: "\(runtime.marketingVersion) (\(runtime.buildVersion))")
|
||||
AIKeyValueRow("设备", value: runtime.deviceModel)
|
||||
AIKeyValueRow("系统", value: runtime.systemVersion)
|
||||
} else {
|
||||
Text("正在读取应用信息…")
|
||||
.font(.subheadline)
|
||||
@@ -80,19 +76,51 @@ struct SettingsView: View {
|
||||
private var themeCard: some View {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
AISectionTitle("外观", systemImage: "circle.lefthalf.filled", detail: themeStore.mode.label)
|
||||
AISegmentedControl(
|
||||
"主题模式",
|
||||
selection: Binding(
|
||||
get: { themeStore.mode },
|
||||
set: { themeStore.set($0) }
|
||||
),
|
||||
options: ThemeMode.allCases.map { (value: $0, title: $0.label) }
|
||||
)
|
||||
// 三个选项块本身已经标出当前模式,标题右侧再重复一次是冗余信息。
|
||||
AISectionTitle("主题外观", systemImage: "circle.lefthalf.filled")
|
||||
Grid(alignment: .leading, horizontalSpacing: 8, verticalSpacing: 8) {
|
||||
GridRow {
|
||||
ForEach(ThemeMode.allCases) { mode in
|
||||
themeOption(mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func themeOption(_ mode: ThemeMode) -> some View {
|
||||
let isSelected = themeStore.mode == mode
|
||||
|
||||
return Button {
|
||||
themeStore.set(mode)
|
||||
} label: {
|
||||
VStack(spacing: 7) {
|
||||
Image(systemName: mode.systemImage)
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
.foregroundStyle(isSelected ? AITheme.primary : AITheme.muted)
|
||||
.accessibilityHidden(true)
|
||||
Text(mode.label)
|
||||
.font(.caption.weight(isSelected ? .semibold : .medium))
|
||||
.foregroundStyle(isSelected ? AITheme.ink : AITheme.muted)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.8)
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 68)
|
||||
.background(isSelected ? AITheme.primary.opacity(0.10) : AITheme.control)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
||||
.strokeBorder(isSelected ? AITheme.primary.opacity(0.42) : AITheme.line)
|
||||
}
|
||||
.contentShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("主题:\(mode.label)")
|
||||
.accessibilityAddTraits(isSelected ? [.isSelected, .isButton] : .isButton)
|
||||
}
|
||||
|
||||
|
||||
private var storageCard: some View {
|
||||
AICard {
|
||||
NavigationLink {
|
||||
@@ -107,8 +135,7 @@ struct SettingsView: View {
|
||||
.foregroundStyle(AITheme.muted)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
AIKeyValueRow("数据位置", value: "仅保存在本机设备")
|
||||
AIKeyValueRow("当前占用", value: model.storageLabel)
|
||||
AIKeyValueRow("日志记录", value: model.storageLabel)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
@@ -120,14 +147,21 @@ struct SettingsView: View {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
AISectionTitle("隐私与数据", systemImage: "lock.shield")
|
||||
AIKeyValueRow("节点信息", value: "仅保存在本机设备")
|
||||
AIKeyValueRow("实例密码", value: "保存在本地数据文件中")
|
||||
AIKeyValueRow("登录会话", value: "应用关闭后自动清除")
|
||||
AIStatusBanner(
|
||||
"节点地址、实例密码和运行数据都保存在本机。应用只连接你添加的节点,登录会话关闭应用后即被清除。",
|
||||
tint: AITheme.primary,
|
||||
systemImage: "shield.checkered"
|
||||
)
|
||||
AIKeyValueRow("节点与凭据", value: "仅写入本机存储")
|
||||
AIKeyValueRow("网络请求", value: "只访问你添加的节点")
|
||||
// 卡内再套一个描边框会变成"卡中卡",这里退成一段脚注。
|
||||
HStack(alignment: .top, spacing: 7) {
|
||||
Image(systemName: "checkmark.seal")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.foregroundStyle(AITheme.success)
|
||||
.padding(.top, 2)
|
||||
.accessibilityHidden(true)
|
||||
Text("不经过中转服务器,也不上传遥测数据;节点地址、密码与日志只保存在这台 iPhone 上,删除应用即彻底清除。")
|
||||
.font(.caption)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding(.top, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,12 +170,35 @@ struct SettingsView: View {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
AISectionTitle("数据清理", systemImage: "trash.square", detail: "不可恢复")
|
||||
// 整块实心红是这页最大的色块,会把"读设置"的视线抢走;退成一行危险色入口。
|
||||
Button {
|
||||
showsClearDialog = true
|
||||
} label: {
|
||||
Label("清除运行日志", systemImage: "trash")
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "trash")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.accessibilityHidden(true)
|
||||
Text("清除运行日志")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
Spacer(minLength: 8)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.footnote.weight(.semibold))
|
||||
.opacity(0.7)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
.foregroundStyle(AITheme.danger)
|
||||
.padding(.horizontal, 12)
|
||||
.frame(minHeight: AITheme.compactControlHeight)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(AITheme.danger.opacity(0.07))
|
||||
.clipShape(RoundedRectangle(cornerRadius: AITheme.inputRadius, style: .continuous))
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: AITheme.inputRadius, style: .continuous)
|
||||
.strokeBorder(AITheme.danger.opacity(0.20))
|
||||
}
|
||||
.contentShape(RoundedRectangle(cornerRadius: AITheme.inputRadius, style: .continuous))
|
||||
}
|
||||
.buttonStyle(AIButtonStyle(kind: .danger, size: .regular, fillsWidth: true))
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user