wangdl a09853ffa9 fix(ios): 全局去掉图标彩色背景块,图标直接显色
- ZXSettingRow / ZXSettingToggleRow 移除 background+clipShape
- ProfileView 通知/设置图标去掉毛玻璃背景
- 全局清理 .background(xxxBG(0.xx)).clipShape() 图案

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:33:59 +08:00

201 lines
10 KiB
Swift

import SwiftUI
struct SettingsView: View {
@EnvironmentObject var authManager: AuthManager
@StateObject private var profileVM = ProfileViewModel()
@State private var language = "zh-CN"
@State private var appearance = "system"
@State private var defaultFocusMinutes = 25
@State private var notificationEnabled = true
@State private var reviewReminder = true
@State private var reminderTime = "20:00"
@State private var intervalDays = "1"
@State private var iCloudSync = false
@State private var autoBackup = false
@State private var showLogoutAlert = false
var body: some View {
ZStack {
Color.zxBg0.ignoresSafeArea()
ScrollView {
VStack(spacing: 16) {
sectionHeader("外观与语言")
VStack(spacing: 0) {
ZXSettingRow(title: "外观", value: appearanceLabel, icon: appearance == "dark" ? "icon-moon" : "icon-sun", color: Color.zxPurple, isCustom: true)
.contentShape(Rectangle())
.onTapGesture { cycleAppearance() }
ZXSettingDivider()
ZXSettingRow(title: "语言", value: language == "zh-CN" ? "简体中文" : "English", icon: "globe", color: Color.zxTeal)
}
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
sectionHeader("学习设置")
VStack(spacing: 0) {
NavigationLink(value: Route.goalSetting) {
ZXSettingRow(title: "学习目标", value: "备考考试", icon: "target", color: Color.zxOrange)
}.foregroundColor(.primary)
ZXSettingDivider()
NavigationLink(value: Route.methodPreference) {
ZXSettingRow(title: "学习方法偏好", value: "间隔回忆 · 费曼技巧", icon: "brain.head.profile", color: Color.zxPurple)
}.foregroundColor(.primary)
}
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
sectionHeader("复习提醒")
VStack(spacing: 0) {
ZXSettingToggleRow(title: "开启复习提醒", icon: "bell.badge.fill", color: Color.zxOrange, isOn: $reviewReminder)
if reviewReminder {
ZXSettingDivider()
ZXSettingPickerRow(title: "提醒时间", value: $reminderTime, options: ["08:00", "12:00", "18:00", "20:00", "21:00"])
ZXSettingDivider()
ZXSettingPickerRow(title: "间隔天数", value: $intervalDays, options: ["1", "2", "3", "5", "7"])
}
}
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
sectionHeader("数据")
VStack(spacing: 0) {
ZXSettingToggleRow(title: "iCloud 同步", icon: "icloud.fill", color: Color.zxTeal, isOn: $iCloudSync)
ZXSettingDivider()
ZXSettingToggleRow(title: "自动备份", icon: "arrow.triangle.2.circlepath", color: Color.zxAccent, isOn: $autoBackup)
}
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
VStack(spacing: 0) {
NavigationLink(value: Route.feedbackForm) {
ZXSettingRow(title: "帮助与反馈", value: "", icon: "questionmark.circle.fill", color: Color.zxAccent)
}.foregroundColor(.primary)
ZXSettingDivider()
ZXSettingRow(title: "隐私政策", value: "", icon: "hand.raised.fill", color: Color.zxYellow)
ZXSettingDivider()
ZXSettingRow(title: "用户协议", value: "", icon: "doc.text.fill", color: Color.zxGreen)
}
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
VStack(spacing: 0) {
Button {
showLogoutAlert = true
} label: {
HStack(spacing: 12) {
Image(systemName: "rectangle.portrait.and.arrow.right").font(.system(size: 16)).foregroundColor(.red).frame(width: 32, height: 32)
Text("退出登录").font(.system(size: 14, weight: .semibold)).foregroundColor(.red)
Spacer()
}.padding(.horizontal, 16).padding(.vertical, 14)
}
}
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
.alert("退出登录", isPresented: $showLogoutAlert) {
Button("取消", role: .cancel) {}
Button("退出", role: .destructive) {
Task { await authManager.signOut() }
}
} message: {
Text("退出后需要重新登录")
}
sectionHeader("关于知习")
HStack(spacing: 4) {
Text("知习 v1.0").font(.system(size: 12)).foregroundColor(Color.zxF03)
}.padding(.bottom, 100)
}.padding(.horizontal, 20).padding(.top, 8)
}
.scrollIndicators(.hidden)
}
.task { await profileVM.loadProfile() }
.onChange(of: profileVM.preferences) { _, p in
guard let p else { return }
appearance = p.appearance ?? "system"
language = p.language ?? "zh-CN"
defaultFocusMinutes = p.defaultFocusMinutes ?? 25
notificationEnabled = p.notificationEnabled ?? true
reviewReminder = notificationEnabled
}
.navigationBarTitleDisplayMode(.inline).animatedTabBarHide().toolbarBackground(.hidden, for: .navigationBar)
}
private func sectionHeader(_ text: String) -> some View {
Text(text).font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035).tracking(0.5).padding(.top, 4)
}
private var appearanceLabel: String {
switch appearance { case "system": return "跟随系统"; case "dark": return "深色模式"; default: return "浅色模式" }
}
private func cycleAppearance() {
switch appearance {
case "system": appearance = "dark"
case "dark": appearance = "light"
default: appearance = "system"
}
Task {
await profileVM.updatePreferences(UpdatePreferencesRequest(
preferredMethods: nil, defaultFocusMinutes: defaultFocusMinutes,
aiSuggestionLevel: nil, language: language, appearance: appearance,
notificationEnabled: notificationEnabled
))
}
}
private func saveNotificationSettings() {
Task {
await profileVM.updatePreferences(UpdatePreferencesRequest(
preferredMethods: nil, defaultFocusMinutes: defaultFocusMinutes,
aiSuggestionLevel: nil, language: language, appearance: appearance,
notificationEnabled: notificationEnabled
))
}
}
}
// MARK: - Setting row components
struct ZXSettingRow: View {
let title: String; let value: String; let icon: String; let color: Color; var isCustom: Bool = false
var body: some View {
HStack(spacing: 12) {
if isCustom { Image(icon).resizable().scaledToFit().frame(width: 20, height: 20).foregroundColor(color) }
else { Image(systemName: icon).font(.system(size: 18)).foregroundColor(color) }
Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0)
Spacer()
if !value.isEmpty { Text(value).font(.system(size: 13)).foregroundColor(Color.zxF03) }
Image(systemName: "chevron.right").font(.system(size: 12)).foregroundColor(Color.zxF03)
}.padding(.horizontal, 16).padding(.vertical, 14)
}
}
struct ZXSettingToggleRow: View {
let title: String; let icon: String; let color: Color; @Binding var isOn: Bool
var body: some View {
HStack(spacing: 12) {
Image(systemName: icon).font(.system(size: 18)).foregroundColor(color)
Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0)
Spacer()
Toggle("", isOn: $isOn).labelsHidden().tint(Color.zxPurple)
}.padding(.horizontal, 16).padding(.vertical, 14)
}
}
struct ZXSettingPickerRow: View {
let title: String; @Binding var value: String; let options: [String]
var body: some View {
HStack(spacing: 12) {
Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0).opacity(0.6)
Spacer()
Picker(title, selection: $value) {
ForEach(options, id: \.self) { o in Text(o).tag(o) }
}.pickerStyle(.segmented).frame(width: 200).tint(Color.zxPurple)
}.padding(.horizontal, 16).padding(.vertical, 10)
}
}
struct ZXSettingDivider: View {
var body: some View { Rectangle().fill(Color.zxBorder008).frame(height: 1).padding(.leading, 60) }
}