feat(ios): simplify navigation and ship 0.2.0

Move diagnostics into settings, remove the app-local notification module, unify the global theme switcher, refine privacy copy and the instance detail experience, and rebuild the app icon.
This commit is contained in:
chick
2026-09-12 17:10:02 +08:00
parent 20632be86a
commit e01a105645
25 changed files with 318 additions and 1393 deletions
@@ -38,40 +38,95 @@ struct InstanceDetailView: View {
}
private var header: some View {
AICard {
VStack(alignment: .leading, spacing: 12) {
AISectionTitle(
"设备详情",
systemImage: "iphone.gen3",
actionTitle: revealsSensitive ? "隐藏敏感值" : "显示敏感值",
actionSystemImage: revealsSensitive ? "eye.slash" : "eye"
) {
revealsSensitive.toggle()
AICard(padding: 18) {
VStack(alignment: .leading, spacing: 16) {
HStack(alignment: .top, spacing: 14) {
Image(systemName: "cpu")
.font(.system(size: 21, weight: .semibold))
.foregroundStyle(AITheme.primary)
.frame(width: 46, height: 46)
.background(AITheme.primary.opacity(0.11))
.clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 5) {
Text(model.instance.name)
.font(.title3.weight(.bold))
.kerning(-0.2)
.foregroundStyle(AITheme.ink)
.lineLimit(2)
Text(model.instance.origin.absoluteString)
.font(.caption)
.foregroundStyle(AITheme.muted)
.lineLimit(2)
.truncationMode(.middle)
}
}
Text(model.instance.origin.absoluteString)
.font(.subheadline)
.foregroundStyle(AITheme.muted)
.lineLimit(2)
.truncationMode(.middle)
HStack(spacing: 10) {
AITag(revealsSensitive ? "敏感值已显示" : "敏感值已隐藏", tint: AITheme.primary)
if let availableCount {
AITag("\(availableCount)/6 模块", tint: AITheme.success)
}
}
Button {
withAnimation(.easeOut(duration: 0.18)) {
revealsSensitive.toggle()
}
} label: {
Label(
revealsSensitive ? "隐藏敏感值" : "显示敏感值",
systemImage: revealsSensitive ? "eye.slash" : "eye"
)
}
.buttonStyle(AIButtonStyle(kind: .secondary, size: .compact, fixedWidth: 112))
if model.isLoading {
Text("正在读取设备详情…")
.font(.subheadline)
.foregroundStyle(AITheme.muted)
AILoadingCard("正在读取设备详情")
}
}
}
}
private var availableCount: Int? {
guard let snapshot = model.snapshot else { return nil }
return snapshot.sections.filter { !$0.values.isEmpty }.count
}
@ViewBuilder
private func sectionCard(_ section: DeviceSection) -> some View {
AICard {
VStack(alignment: .leading, spacing: 12) {
AISectionTitle(section.title, systemImage: "list.bullet.rectangle")
if section.values.isEmpty {
Text("此模块当前不可用或未返回数据。")
.font(.subheadline)
if section.values.isEmpty {
AICard(padding: 14) {
HStack(spacing: 10) {
Image(systemName: "wifi.slash")
.font(.subheadline.weight(.semibold))
.foregroundStyle(AITheme.muted)
} else {
ForEach(sortedValues(section.values), id: \.key) { item in
.frame(width: 32, height: 32)
.background(AITheme.control)
.clipShape(RoundedRectangle(cornerRadius: 11, style: .continuous))
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 2) {
Text(section.title)
.font(.subheadline.weight(.semibold))
.foregroundStyle(AITheme.ink)
Text("模块未返回数据")
.font(.caption)
.foregroundStyle(AITheme.muted)
}
}
}
} else {
AICard(padding: 18) {
VStack(alignment: .leading, spacing: 16) {
AISectionTitle(section.title, systemImage: sectionIcon(section.id))
Divider()
.overlay(AITheme.line)
ForEach(Array(sortedValues(section.values).enumerated()), id: \.element.key) { index, item in
if index > 0 {
Divider()
.overlay(AITheme.line.opacity(0.6))
}
AIKeyValueRow(fieldLabel(item.key), value: display(item.key, item.value))
}
}
@@ -79,6 +134,18 @@ struct InstanceDetailView: View {
}
}
private func sectionIcon(_ id: String) -> String {
switch id {
case "device": return "iphone.gen3"
case "sim": return "simcard"
case "network": return "antenna.radiowaves.left.and.right"
case "stats": return "gauge.with.needle"
case "cpu": return "cpu"
case "connectivity": return "dot.radiowaves.left.and.right"
default: return "list.bullet.rectangle"
}
}
private let fieldLabels: [String: String] = [
"model": "型号",
"manufacturer": "制造商",