| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- import SwiftUI
- // MARK: - SettingsView
- /// The settings screen with storage info, debug toggles,
- /// background keep-alive guide, and app branding.
- /// Redesigned to use a minimalist wireframe business style.
- struct SettingsView: View {
- @State private var showClearCacheAlert = false
- // Mock storage values
- private let usedStorage: Double = 1.2
- private let totalStorage: Double = 5.0
- var body: some View {
- NavigationStack {
- ZStack {
- Color.spaceBlack.ignoresSafeArea()
- ScrollView {
- VStack(spacing: 20) {
- // Section: Storage
- storageSection
- .padding(.top, 12)
- // Section: Debug
- debugSection
- // Section: Keep-Alive Guide
- keepAliveSection
- // Section: About
- aboutSection
- // Footer
- footer
- .padding(.top, 12)
- .padding(.bottom, 40)
- }
- .padding(.horizontal, 16)
- }
- }
- .navigationTitle("设置")
- .navigationBarTitleDisplayMode(.inline)
- .alert("清理缓存", isPresented: $showClearCacheAlert) {
- Button("取消", role: .cancel) { }
- Button("清理", role: .destructive) {
- HapticManager.trigger(.tapFeedback)
- }
- } message: {
- Text("将清除所有本地缓存数据。此操作不可撤销。")
- }
- }
- }
- // MARK: - Storage Section
- private var storageSection: some View {
- settingsCard {
- VStack(alignment: .leading, spacing: 16) {
- sectionHeader(icon: "internaldrive", title: "存储空间")
- // Thin storage bar
- VStack(alignment: .leading, spacing: 8) {
- GeometryReader { geometry in
- ZStack(alignment: .leading) {
- // Background bar
- RoundedRectangle(cornerRadius: 3)
- .fill(Color.primary.opacity(0.06))
- .frame(height: 5)
- // Used portion
- RoundedRectangle(cornerRadius: 3)
- .fill(Color.primary)
- .frame(
- width: geometry.size.width * (usedStorage / totalStorage),
- height: 5
- )
- }
- }
- .frame(height: 5)
- HStack {
- Text(String(format: "已使用 %.1f GB", usedStorage))
- .font(.system(size: 11, weight: .regular))
- .foregroundStyle(Color.primary.opacity(0.8))
- Spacer()
- Text(String(format: "总容量 %.0f GB", totalStorage))
- .font(.system(size: 11, weight: .regular))
- .foregroundStyle(Color.secondary)
- }
- }
- // Clear cache button (minimal red outline)
- Button {
- showClearCacheAlert = true
- } label: {
- HStack(spacing: 6) {
- Image(systemName: "trash")
- .font(.system(size: 12, weight: .light))
- Text("清理缓存")
- .font(.system(size: 13, weight: .regular))
- }
- .foregroundStyle(Color.recordingRed)
- .padding(.vertical, 8)
- .frame(maxWidth: .infinity)
- .overlay(
- RoundedRectangle(cornerRadius: 6)
- .stroke(Color.recordingRed.opacity(0.3), lineWidth: 1)
- )
- }
- .padding(.top, 4)
- }
- }
- }
- // MARK: - Debug Section
- private var debugSection: some View {
- settingsCard {
- VStack(alignment: .leading, spacing: 14) {
- sectionHeader(icon: "hammer", title: "开发调试")
- // Data Storage Mode
- VStack(alignment: .leading, spacing: 4) {
- HStack {
- Text("数据存储模式")
- .font(.system(size: 13, weight: .regular))
- .foregroundStyle(Color.primary)
- Spacer()
- Text("纯本地保存")
- .font(.system(size: 13, weight: .medium))
- .foregroundStyle(Color.secondary)
- }
- Text("所有会议记录均保存在设备沙盒 SQLite 中,无网络上传")
- .font(.system(size: 10))
- .foregroundStyle(Color.secondary.opacity(0.6))
- }
- }
- }
- }
- // MARK: - Keep-Alive Section
- private var keepAliveSection: some View {
- settingsCard {
- VStack(alignment: .leading, spacing: 14) {
- sectionHeader(icon: "shield", title: "后台运行保障")
- Text("为保障全局不中断录音正常运行,请手动开启以下系统权限:")
- .font(.system(size: 12))
- .foregroundStyle(Color.secondary)
- VStack(alignment: .leading, spacing: 12) {
- keepAliveStep(
- number: 1,
- title: "开启后台音频",
- description: "Xcode 开启后台模式 -> 音频与 AirPlay",
- isChecked: true
- )
- keepAliveStep(
- number: 2,
- title: "禁用自动锁屏",
- description: "系统设置 -> 显示与亮度 -> 自动锁定 -> 永不",
- isChecked: true
- )
- keepAliveStep(
- number: 3,
- title: "允许始终访问定位",
- description: "系统设置 -> 隐私与安全性 -> 定位服务 -> 现场记录 -> 始终允许",
- isChecked: false
- )
- }
- .padding(.top, 4)
- }
- }
- }
- private func keepAliveStep(number: Int, title: String, description: String, isChecked: Bool) -> some View {
- HStack(alignment: .top, spacing: 10) {
- ZStack {
- Circle()
- .fill(Color.primary.opacity(isChecked ? 0.05 : 0.01))
- .frame(width: 24, height: 24)
- .businessBorder(cornerRadius: 12)
- if isChecked {
- Image(systemName: "checkmark")
- .font(.system(size: 10, weight: .semibold))
- .foregroundStyle(Color.primary)
- } else {
- Text("\(number)")
- .font(.system(size: 10, weight: .medium, design: .monospaced))
- .foregroundStyle(Color.secondary)
- }
- }
- VStack(alignment: .leading, spacing: 2) {
- Text(title)
- .font(.system(size: 13, weight: .medium))
- .foregroundStyle(Color.primary)
- Text(description)
- .font(.system(size: 11))
- .foregroundStyle(Color.secondary.opacity(0.8))
- }
- }
- }
- // MARK: - About Section
- private var aboutSection: some View {
- settingsCard {
- VStack(spacing: 14) {
- sectionHeader(icon: "info.circle", title: "关于")
- VStack(spacing: 10) {
- infoRow(label: "应用名称", value: "现场记录 (Celestia Trace)")
- infoRow(label: "版本信息", value: "0.1.0 (本地持久化单机版)")
- infoRow(label: "底层框架", value: "SwiftUI + SwiftData")
- infoRow(label: "系统支持", value: "iOS 17.0+ (模拟器)")
- }
- .padding(.top, 4)
- }
- }
- }
- private func infoRow(label: String, value: String) -> some View {
- HStack {
- Text(label)
- .font(.system(size: 12))
- .foregroundStyle(Color.secondary)
- Spacer()
- Text(value)
- .font(.system(size: 12, weight: .regular, design: .monospaced))
- .foregroundStyle(Color.primary)
- }
- }
- // MARK: - Footer
- private var footer: some View {
- VStack(spacing: 4) {
- Text("星痕现场记录")
- .font(.system(size: 10, weight: .bold, design: .monospaced))
- .foregroundStyle(Color.secondary.opacity(0.4))
- .tracking(2)
- Text("极简专业现场记录工作台")
- .font(.system(size: 9))
- .foregroundStyle(Color.secondary.opacity(0.3))
- }
- }
- // MARK: - Reusable Components
- private func settingsCard<Content: View>(@ViewBuilder content: () -> Content) -> some View {
- VStack(alignment: .leading, spacing: 0) {
- content()
- }
- .padding(16)
- .background(Color.cardBackground.opacity(0.15))
- .businessBorder(cornerRadius: 10)
- }
- private func sectionHeader(icon: String, title: String) -> some View {
- HStack(spacing: 8) {
- Image(systemName: icon)
- .font(.system(size: 12, weight: .light))
- .foregroundStyle(Color.secondary)
- Text(title)
- .font(.system(size: 14, weight: .semibold))
- .foregroundStyle(Color.primary)
- }
- }
- }
- // MARK: - Preview
- #Preview {
- SettingsView()
- }
|