全面重写 iOS 所有页面样式:渐变背景 + 玻璃卡片(.thinMaterial)+ 圆角阴影,匹配 Android 版视觉效果

这个提交包含在:
JGZ_YES
2026-08-12 13:50:20 +08:00
父节点 1dc00c9645
当前提交 e85b0dbde5
修改 7 个文件,包含 520 行新增349 行删除
+14 -5
查看文件
@@ -4,6 +4,14 @@ struct AgreementView: View {
@EnvironmentObject var store: Store @EnvironmentObject var store: Store
var body: some View { var body: some View {
ZStack {
LinearGradient(
colors: [Color.white, Color(red: 0.84, green: 0.84, blue: 0.87)],
startPoint: .top,
endPoint: .bottom
)
.ignoresSafeArea()
VStack(spacing: 0) { VStack(spacing: 0) {
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
@@ -13,19 +21,19 @@ struct AgreementView: View {
Text("欢迎使用「睡了吗」。\n\n本协议说明应用功能与数据使用方式,请仔细阅读。") Text("欢迎使用「睡了吗」。\n\n本协议说明应用功能与数据使用方式,请仔细阅读。")
.font(.body).foregroundColor(.secondary) .font(.body).foregroundColor(.secondary)
section("一、应用功能") { agreementSection("一、应用功能") {
Text("1. 睡眠监督:在您设定的就寝时段,提醒您按时入睡。\n(注:iOS 系统限制,本应用无法直接屏蔽其他应用,仅提供通知提醒。)\n2. 作息记录:记录您的入睡与起床时间,生成睡眠统计。\n3. 控制码功能(见第二条)。") Text("1. 睡眠监督:在您设定的就寝时段,提醒您按时入睡。\n(注:iOS 系统限制,本应用无法直接屏蔽其他应用,仅提供通知提醒。)\n2. 作息记录:记录您的入睡与起床时间,生成睡眠统计。\n3. 控制码功能(见第二条)。")
} }
section("二、控制码功能") { agreementSection("二、控制码功能") {
Text("本应用会为您的手机生成一个独一无二的控制码。\n\n• 控制码绑定本机,卸载重装后才会变更。\n• 您可以将控制码分享给朋友。朋友输入您的控制码后可以远程设置您的就寝/起床时间、提醒您入睡/起床。\n• 您可以随时在设置中停止分享。\n• 请仅将控制码分享给您信任的人。") Text("本应用会为您的手机生成一个独一无二的控制码。\n\n• 控制码绑定本机,卸载重装后才会变更。\n• 您可以将控制码分享给朋友。朋友输入您的控制码后可以远程设置您的就寝/起床时间、提醒您入睡/起床。\n• 您可以随时在设置中停止分享。\n• 请仅将控制码分享给您信任的人。")
} }
section("三、数据与隐私") { agreementSection("三、数据与隐私") {
Text("• 您的作息设置、睡眠记录会同步到服务器,以便朋友通过控制码查看和远程操作。\n• 服务器仅存储与功能相关的必要数据,不会出售或分享给第三方。\n• 您可以在应用设置中随时停止监督服务,断开与服务器同步。") Text("• 您的作息设置、睡眠记录会同步到服务器,以便朋友通过控制码查看和远程操作。\n• 服务器仅存储与功能相关的必要数据,不会出售或分享给第三方。\n• 您可以在应用设置中随时停止监督服务,断开与服务器同步。")
} }
section("四、所需权限") { agreementSection("四、所需权限") {
Text("• 通知:发送就寝提醒与朋友远程消息\n• 网络访问:与服务器同步作息数据、接收朋友远程指令") Text("• 通知:发送就寝提醒与朋友远程消息\n• 网络访问:与服务器同步作息数据、接收朋友远程指令")
} }
@@ -68,9 +76,10 @@ struct AgreementView: View {
.background(.regularMaterial) .background(.regularMaterial)
} }
} }
}
@ViewBuilder @ViewBuilder
private func section(_ title: String, @ViewBuilder content: () -> some View) -> some View { private func agreementSection(_ title: String, @ViewBuilder content: () -> some View) -> some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text(title).font(.headline) Text(title).font(.headline)
content().font(.subheadline).foregroundColor(.secondary) content().font(.subheadline).foregroundColor(.secondary)
+11 -1
查看文件
@@ -3,9 +3,17 @@ import SwiftUI
struct ContentView: View { struct ContentView: View {
@EnvironmentObject var store: Store @EnvironmentObject var store: Store
@State private var selectedTab = 0 @State private var selectedTab = 0
@State private var settingsRefresh = UUID()
var body: some View { var body: some View {
ZStack {
//
LinearGradient(
colors: [Color.white, Color(red: 0.84, green: 0.84, blue: 0.87)],
startPoint: .top,
endPoint: .bottom
)
.ignoresSafeArea()
TabView(selection: $selectedTab) { TabView(selection: $selectedTab) {
DashboardView() DashboardView()
.tabItem { .tabItem {
@@ -29,5 +37,7 @@ struct ContentView: View {
.tag(2) .tag(2)
} }
.tint(.blue) .tint(.blue)
.toolbarBackground(.ultraThinMaterial, for: .tabBar)
}
} }
} }
+35 -20
查看文件
@@ -6,10 +6,8 @@ struct DashboardView: View {
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View { var body: some View {
NavigationStack {
ScrollView { ScrollView {
VStack(spacing: 20) { VStack(spacing: 16) {
// + // +
VStack(spacing: 4) { VStack(spacing: 4) {
Text(timeString) Text(timeString)
@@ -23,7 +21,7 @@ struct DashboardView: View {
Text(greeting) Text(greeting)
.font(.title2) .font(.title2)
// //
VStack(spacing: 8) { VStack(spacing: 8) {
Text(statusTitle) Text(statusTitle)
.font(.headline) .font(.headline)
@@ -33,23 +31,21 @@ struct DashboardView: View {
} }
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background(.regularMaterial) .glassCard()
.clipShape(RoundedRectangle(cornerRadius: 16))
// // /
HStack(spacing: 16) { HStack(spacing: 12) {
statusCard(title: "就寝时间", value: TimeUtil.fmt(store.bedtimeMinutes)) infoCard(title: "就寝时间", value: TimeUtil.fmt(store.bedtimeMinutes), icon: "moon.fill")
statusCard(title: "起床时间", value: TimeUtil.fmt(store.waketimeMinutes)) infoCard(title: "起床时间", value: TimeUtil.fmt(store.waketimeMinutes), icon: "sunrise.fill")
} }
// //
HStack { HStack {
Image(systemName: store.supervisionEnabled ? "shield.checkered" : "shield.slash") Image(systemName: store.supervisionEnabled ? "shield.checkered" : "shield.slash")
.foregroundColor(store.supervisionEnabled ? .green : .red) .foregroundColor(store.supervisionEnabled ? .green : .secondary)
Text(store.supervisionEnabled ? "监督运行中" : "监督已关闭") Text(store.supervisionEnabled ? "监督运行中" : "监督已关闭")
.font(.subheadline) .font(.subheadline)
} }
.padding(.horizontal)
// / // /
if store.isSleeping { if store.isSleeping {
@@ -74,12 +70,17 @@ struct DashboardView: View {
.tint(.indigo) .tint(.indigo)
} }
} }
.padding() .padding(.horizontal, 16)
.padding(.bottom, 16)
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("睡了吗")
.font(.headline)
}
} }
.navigationTitle("睡了吗")
.onReceive(timer) { t in now = t } .onReceive(timer) { t in now = t }
} }
}
private var timeString: String { private var timeString: String {
let f = DateFormatter(); f.dateFormat = "HH:mm"; return f.string(from: now) let f = DateFormatter(); f.dateFormat = "HH:mm"; return f.string(from: now)
@@ -121,14 +122,28 @@ struct DashboardView: View {
} }
@ViewBuilder @ViewBuilder
private func statusCard(title: String, value: String) -> some View { private func infoCard(title: String, value: String, icon: String) -> some View {
VStack(spacing: 4) { VStack(spacing: 6) {
Text(title).font(.caption).foregroundColor(.secondary) Image(systemName: icon)
.font(.title3)
.foregroundColor(.blue)
Text(value).font(.title3).bold() Text(value).font(.title3).bold()
Text(title).font(.caption).foregroundColor(.secondary)
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding() .padding()
.background(.regularMaterial) .glassCard()
.clipShape(RoundedRectangle(cornerRadius: 12)) }
}
// MARK: - Modifier
extension View {
func glassCard(radius: CGFloat = 16) -> some View {
self
.background(
RoundedRectangle(cornerRadius: radius)
.fill(.thinMaterial)
.shadow(color: .black.opacity(0.06), radius: 8, x: 0, y: 2)
)
} }
} }
+57 -22
查看文件
@@ -8,8 +8,10 @@ struct FriendControlView: View {
@State private var errorMsg: String? @State private var errorMsg: String?
var body: some View { var body: some View {
Form { ScrollView {
Section { VStack(spacing: 16) {
//
VStack(spacing: 12) {
HStack { HStack {
TextField("输入朋友的控制码", text: $friendCode) TextField("输入朋友的控制码", text: $friendCode)
.disableAutocorrection(true) .disableAutocorrection(true)
@@ -19,49 +21,82 @@ struct FriendControlView: View {
.controlSize(.small) .controlSize(.small)
.disabled(friendCode.isEmpty || isLoading) .disabled(friendCode.isEmpty || isLoading)
} }
if isLoading {
ProgressView()
} }
if let info = friendInfo { if let info = friendInfo {
Section("朋友信息") { Text(info)
Text(info).font(.headline) .font(.headline)
} .foregroundColor(.green)
} }
if let error = errorMsg { if let error = errorMsg {
Section { Text(error).foregroundColor(.red) } Text(error)
.font(.subheadline)
.foregroundColor(.red)
} }
}
.padding()
.glassCard()
//
if friendInfo != nil { if friendInfo != nil {
Section("操作") { VStack(spacing: 8) {
Button { triggerFriend("sleep") } label: { Button {
Label("催朋友睡觉", systemImage: "moon.zzz.fill") sendCommand("sleep")
} label: {
Label("提醒入睡", systemImage: "moon.zzz.fill")
.frame(maxWidth: .infinity)
} }
Button { triggerFriend("wake") } label: { .buttonStyle(.bordered)
Label("叫朋友起床", systemImage: "sunrise.fill") .tint(.indigo)
Button {
sendCommand("wake")
} label: {
Label("提醒起床", systemImage: "sunrise.fill")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
.tint(.orange)
} }
} }
} }
.padding(16)
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("控制朋友").font(.headline)
}
} }
.navigationTitle("控制朋友")
.overlay { if isLoading { ProgressView() } }
} }
private func lookupFriend() { private func lookupFriend() {
isLoading = true; errorMsg = nil; friendInfo = nil isLoading = true
errorMsg = nil
friendInfo = nil
Task { Task {
if let res = await ApiClient.friendInfo(code: friendCode) { let result = await ApiClient.query(code: friendCode)
friendInfo = (res["nickname"] as? String) ?? friendCode
} else {
errorMsg = "未找到该用户"
}
isLoading = false isLoading = false
switch result {
case .success(let info):
friendInfo = info
case .failure(let e):
errorMsg = "查询失败:\(e.localizedDescription)"
}
} }
} }
private func triggerFriend(_ action: String) { private func sendCommand(_ cmd: String) {
Task { Task {
let res = await ApiClient.friendTrigger(friendCode: friendCode, action: action) let result = await ApiClient.command(code: friendCode, cmd: cmd)
if !res.ok { errorMsg = res.error ?? "操作失败" } await MainActor.run {
if case .failure(let e) = result {
errorMsg = "指令失败:\(e.localizedDescription)"
}
}
} }
} }
} }
+90 -45
查看文件
@@ -3,16 +3,17 @@ import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@EnvironmentObject var store: Store @EnvironmentObject var store: Store
@State private var showAgreement = false @State private var showAgreement = false
@State private var showStyleSettings = false
@State private var showTimePicker = false @State private var showTimePicker = false
@State private var isBedPicker = false @State private var isBedPicker = false
@State private var pickerDate = Date() @State private var pickerDate = Date()
var body: some View { var body: some View {
NavigationStack { NavigationStack {
Form { ScrollView {
// MARK: - VStack(spacing: 12) {
Section { //
cardSection(header: "控制码") {
VStack(spacing: 12) {
HStack { HStack {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("我的控制码").font(.caption).foregroundColor(.secondary) Text("我的控制码").font(.caption).foregroundColor(.secondary)
@@ -23,81 +24,87 @@ struct SettingsView: View {
if !store.controlCode.isEmpty { if !store.controlCode.isEmpty {
ShareLink(item: shareText) { ShareLink(item: shareText) {
Image(systemName: "square.and.arrow.up") Image(systemName: "square.and.arrow.up")
.font(.title3)
} }
} }
} }
Divider()
Toggle("启用控制码功能", isOn: $store.controlCodeEnabled) Toggle("启用控制码功能", isOn: $store.controlCodeEnabled)
.onChange(of: store.controlCodeEnabled) { _ in store.save() } .onChange(of: store.controlCodeEnabled) { _ in store.save() }
} header: { Text("控制码") }
// MARK: -
Section {
HStack {
Text("目标就寝时间")
Spacer()
Text(TimeUtil.fmt(store.bedtimeMinutes)).foregroundColor(.blue)
} }
.contentShape(Rectangle())
.onTapGesture { showTimePickerSheet(isBed: true) }
HStack {
Text("目标起床时间")
Spacer()
Text(TimeUtil.fmt(store.waketimeMinutes)).foregroundColor(.blue)
} }
.contentShape(Rectangle())
.onTapGesture { showTimePickerSheet(isBed: false) }
} header: { Text("作息") }
// MARK: - //
Section { cardSection(header: "作息") {
VStack(spacing: 0) {
timeRow(title: "目标就寝时间", minutes: store.bedtimeMinutes) {
showTimePickerSheet(isBed: true)
}
Divider().padding(.leading, 16)
timeRow(title: "目标起床时间", minutes: store.waketimeMinutes) {
showTimePickerSheet(isBed: false)
}
}
}
//
cardSection(header: "提醒", footer: "睡觉时间前发送通知提醒你准备休息") {
Toggle("睡前 30 分钟提醒", isOn: $store.bedtimeReminderEnabled) Toggle("睡前 30 分钟提醒", isOn: $store.bedtimeReminderEnabled)
.onChange(of: store.bedtimeReminderEnabled) { _ in .onChange(of: store.bedtimeReminderEnabled) { _ in
store.save() store.save()
BedtimeReminder.schedule(store: store) BedtimeReminder.schedule(store: store)
} }
} header: { Text("提醒") } footer: {
Text("睡觉时间前发送通知提醒你准备休息")
} }
// MARK: - //
Section { cardSection(header: "监督", footer: "就寝时段提醒你放下手机") {
Toggle("监督模式", isOn: $store.supervisionEnabled) Toggle("监督模式", isOn: $store.supervisionEnabled)
.onChange(of: store.supervisionEnabled) { _ in store.save() } .onChange(of: store.supervisionEnabled) { _ in store.save() }
} header: { Text("监督") } footer: {
Text("就寝时段提醒你放下手机")
} }
// MARK: - //
Section { cardSection(header: "后台运行", footer: "关闭后应用切到后台将停止服务") {
Toggle("允许后台运行", isOn: $store.backgroundRunning) Toggle("允许后台运行", isOn: $store.backgroundRunning)
.onChange(of: store.backgroundRunning) { _ in store.save() } .onChange(of: store.backgroundRunning) { _ in store.save() }
} header: { Text("后台运行") } footer: {
Text("关闭后应用切到后台将停止服务")
} }
// MARK: - //
Section { cardSection(header: "外观") {
NavigationLink { NavigationLink {
StyleSettingsView() StyleSettingsView()
} label: { } label: {
HStack {
Text("样式") Text("样式")
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundColor(.secondary)
}
}
.foregroundColor(.primary)
} }
} header: { Text("外观") }
// MARK: - //
Section { cardSection(header: "朋友") {
NavigationLink("控制朋友") { NavigationLink("控制朋友") {
FriendControlView() FriendControlView()
} }
} header: { Text("朋友") } .foregroundColor(.primary)
}
// MARK: - //
Section { cardSection {
Button("用户协议与隐私说明") { showAgreement = true } Button("用户协议与隐私说明") { showAgreement = true }
} }
} }
.navigationTitle("设置") .padding(.horizontal, 16)
.padding(.vertical, 12)
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("设置").font(.headline)
}
}
.sheet(isPresented: $showAgreement) { .sheet(isPresented: $showAgreement) {
AgreementView() AgreementView()
} }
@@ -108,7 +115,7 @@ struct SettingsView: View {
} }
} }
// MARK: - SwiftUI // MARK: -
private var timePickerSheet: some View { private var timePickerSheet: some View {
VStack(spacing: 16) { VStack(spacing: 16) {
HStack { HStack {
@@ -135,7 +142,6 @@ struct SettingsView: View {
.padding(.horizontal) .padding(.horizontal)
DatePicker("", selection: $pickerDate, displayedComponents: .hourAndMinute) DatePicker("", selection: $pickerDate, displayedComponents: .hourAndMinute)
.datePickerStyle(.wheel)
.labelsHidden() .labelsHidden()
} }
.padding(.top, 12) .padding(.top, 12)
@@ -161,4 +167,43 @@ struct SettingsView: View {
\(webLink) \(webLink)
""" """
} }
// MARK: -
@ViewBuilder
private func timeRow(title: String, minutes: Int, action: @escaping () -> Void) -> some View {
Button(action: action) {
HStack {
Text(title)
Spacer()
Text(TimeUtil.fmt(minutes))
.foregroundColor(.blue)
}
.padding(.vertical, 12)
.padding(.horizontal, 16)
.foregroundColor(.primary)
}
}
@ViewBuilder
private func cardSection(header: String? = nil, footer: String? = nil, @ViewBuilder content: () -> some View) -> some View {
VStack(alignment: .leading, spacing: 4) {
if let h = header {
Text(h)
.font(.caption)
.foregroundColor(.secondary)
.padding(.bottom, 2)
}
content()
.padding(.horizontal, 16)
.padding(.vertical, 8)
.glassCard(radius: 14)
if let f = footer {
Text(f)
.font(.caption2)
.foregroundColor(.secondary)
.padding(.horizontal, 4)
}
}
}
} }
+42 -22
查看文件
@@ -6,32 +6,42 @@ struct StatsView: View {
private var records: [SleepRecord] { store.recentRecords(7) } private var records: [SleepRecord] { store.recentRecords(7) }
var body: some View { var body: some View {
NavigationStack {
ScrollView { ScrollView {
VStack(spacing: 20) { VStack(spacing: 16) {
// KPI // KPI
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) { LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 12) {
kpiCard(title: "平均睡眠", value: avgHours, unit: "小时", color: .blue) StatKpiCard(title: "平均睡眠", value: avgHours, unit: "小时", color: .blue)
kpiCard(title: "连续达标", value: "\(streakDays)", unit: "", color: .green) StatKpiCard(title: "连续达标", value: "\(streakDays)", unit: "", color: .green)
kpiCard(title: "准时入睡", value: "\(onTimeCount)/\(max(records.count, 1))", unit: "", color: .orange) StatKpiCard(title: "准时入睡", value: "\(onTimeCount)/\(max(records.count, 1))", unit: "", color: .orange)
kpiCard(title: "达标率", value: String(format: "%.0f%%", goalRate * 100), unit: "", color: .purple) StatKpiCard(title: "达标率", value: String(format: "%.0f%%", goalRate * 100), unit: "", color: .purple)
} }
// //
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("最近 7 晚").font(.headline).padding(.horizontal) HStack {
Text("最近 7 晚")
.font(.headline)
Spacer()
}
if records.isEmpty { if records.isEmpty {
Text("暂无睡眠记录").foregroundColor(.secondary).padding() Text("暂无睡眠记录")
.foregroundColor(.secondary)
.padding()
.frame(maxWidth: .infinity)
.glassCard()
} else { } else {
ForEach(records) { rec in ForEach(records) { rec in
recordRow(rec) RecordRow(rec: rec)
} }
} }
} }
} }
.padding() .padding(16)
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("统计").font(.headline)
} }
.navigationTitle("统计")
} }
} }
@@ -56,36 +66,46 @@ struct StatsView: View {
private var onTimeCount: Int { private var onTimeCount: Int {
records.filter { $0.bedMinutes <= store.bedtimeMinutes + 30 }.count records.filter { $0.bedMinutes <= store.bedtimeMinutes + 30 }.count
} }
}
@ViewBuilder struct StatKpiCard: View {
private func kpiCard(title: String, value: String, unit: String, color: Color) -> some View { let title: String
let value: String
let unit: String
let color: Color
var body: some View {
VStack(spacing: 4) { VStack(spacing: 4) {
Text(title).font(.caption).foregroundColor(.secondary) Text(title).font(.caption).foregroundColor(.secondary)
HStack(alignment: .lastTextBaseline, spacing: 2) { HStack(alignment: .lastTextBaseline, spacing: 2) {
Text(value).font(.title2).bold().foregroundColor(color) Text(value).font(.title2).bold().foregroundColor(color)
if !unit.isEmpty { Text(unit).font(.caption).foregroundColor(.secondary) } if !unit.isEmpty {
Text(unit).font(.caption).foregroundColor(.secondary)
}
} }
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding() .padding()
.background(.regularMaterial) .glassCard(radius: 14)
.clipShape(RoundedRectangle(cornerRadius: 12)) }
} }
@ViewBuilder struct RecordRow: View {
private func recordRow(_ rec: SleepRecord) -> some View { let rec: SleepRecord
var body: some View {
HStack { HStack {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 2) {
Text(rec.nightDate).font(.subheadline).bold() Text(rec.nightDate).font(.subheadline).bold()
Text("\(TimeUtil.fmt(rec.bedMinutes))\(TimeUtil.fmt(rec.wakeMinutes))") Text("\(TimeUtil.fmt(rec.bedMinutes))\(TimeUtil.fmt(rec.wakeMinutes))")
.font(.caption).foregroundColor(.secondary) .font(.caption).foregroundColor(.secondary)
} }
Spacer() Spacer()
Text(String(format: "%.1f 小时", rec.sleepHours)) Text(String(format: "%.1f 小时", rec.sleepHours))
.font(.headline).foregroundColor(rec.sleepHours >= 7 ? .green : .orange) .font(.headline)
.foregroundColor(rec.sleepHours >= 7 ? .green : .orange)
} }
.padding() .padding()
.background(.regularMaterial) .glassCard(radius: 14)
.clipShape(RoundedRectangle(cornerRadius: 12))
} }
} }
+46 -9
查看文件
@@ -4,22 +4,29 @@ struct StyleSettingsView: View {
@EnvironmentObject var store: Store @EnvironmentObject var store: Store
var body: some View { var body: some View {
Form { ScrollView {
Section { VStack(spacing: 12) {
//
styleCard {
Toggle("暗色模式", isOn: $store.darkMode) Toggle("暗色模式", isOn: $store.darkMode)
.onChange(of: store.darkMode) { _ in store.save() } .onChange(of: store.darkMode) { _ in store.save() }
} } header: { Text("主题") }
Section { //
styleCard {
VStack(spacing: 8) {
Picker("导航栏样式", selection: $store.navStyle) { Picker("导航栏样式", selection: $store.navStyle) {
Text("默认").tag(0) Text("默认").tag(0)
Text("无边框").tag(1) Text("无边框").tag(1)
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)
.onChange(of: store.navStyle) { _ in store.save() } .onChange(of: store.navStyle) { _ in store.save() }
}
} header: { Text("导航栏") } } header: { Text("导航栏") }
Section { //
styleCard {
VStack(spacing: 12) {
Picker("字体风格", selection: $store.fontStyle) { Picker("字体风格", selection: $store.fontStyle) {
Text("默认").tag(0) Text("默认").tag(0)
Text("圆体").tag(1) Text("圆体").tag(1)
@@ -28,36 +35,66 @@ struct StyleSettingsView: View {
.pickerStyle(.segmented) .pickerStyle(.segmented)
.onChange(of: store.fontStyle) { _ in store.save() } .onChange(of: store.fontStyle) { _ in store.save() }
VStack { VStack(spacing: 4) {
HStack { HStack {
Text("字体大小: \(String(format: "%.0f%%", store.fontScale * 100))") Text("字体大小: \(String(format: "%.0f%%", store.fontScale * 100))")
.font(.subheadline)
Spacer() Spacer()
Button("重置") { store.fontScale = 1.0; store.save() } Button("重置") { store.fontScale = 1.0; store.save() }
.buttonStyle(.borderless) .buttonStyle(.borderless)
.font(.caption)
} }
Slider(value: $store.fontScale, in: 0.85...1.30, step: 0.05) Slider(value: $store.fontScale, in: 0.85...1.30, step: 0.05)
.onChange(of: store.fontScale) { _ in store.save() } .onChange(of: store.fontScale) { _ in store.save() }
} }
}
} header: { Text("字体") } } header: { Text("字体") }
Section { //
styleCard {
Picker("卡片圆角", selection: $store.cardRadius) { Picker("卡片圆角", selection: $store.cardRadius) {
Text("小 (4)").tag(4) Text("小 (4)").tag(4)
Text("中 (18)").tag(18) Text("中 (18)").tag(18)
Text("大 (28)").tag(28) Text("大 (28)").tag(28)
} }
.pickerStyle(.segmented)
.onChange(of: store.cardRadius) { _ in store.save() } .onChange(of: store.cardRadius) { _ in store.save() }
} header: { Text("卡片") } } header: { Text("卡片") }
Section { //
styleCard {
Picker("模糊强度", selection: $store.blurStrength) { Picker("模糊强度", selection: $store.blurStrength) {
Text("").tag(0) Text("").tag(0)
Text("轻微").tag(1) Text("轻微").tag(1)
Text("强烈").tag(2) Text("强烈").tag(2)
} }
.pickerStyle(.segmented)
.onChange(of: store.blurStrength) { _ in store.save() } .onChange(of: store.blurStrength) { _ in store.save() }
} header: { Text("模糊") } } header: { Text("模糊") }
} }
.navigationTitle("样式设置") .padding(.horizontal, 16)
.padding(.vertical, 12)
}
.toolbar {
ToolbarItem(placement: .principal) {
Text("样式设置").font(.headline)
}
}
}
@ViewBuilder
private func styleCard<Content: View, Header: View>(
@ViewBuilder content: () -> Content,
header: () -> Header
) -> some View {
VStack(alignment: .leading, spacing: 4) {
header()
.font(.caption)
.foregroundColor(.secondary)
content()
.padding(.horizontal, 16)
.padding(.vertical, 8)
.glassCard(radius: 14)
}
} }
} }