|
|
@@ -1,323 +1,310 @@
|
|
|
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.
|
|
|
+
|
|
|
+/// A compact settings hub. Detailed controls live behind a single function list.
|
|
|
struct SettingsView: View {
|
|
|
- @State private var showClearCacheAlert = false
|
|
|
+ @ObservedObject private var authManager: AuthManager = .shared
|
|
|
@State private var usedStorageBytes: Int64 = 0
|
|
|
@State private var availableStorageBytes: Int64 = 0
|
|
|
+ @AppStorage(DeveloperLogStore.enabledKey) private var isDeveloperModeEnabled = false
|
|
|
|
|
|
var body: some View {
|
|
|
NavigationStack {
|
|
|
- ZStack {
|
|
|
- Color.spaceBlack.ignoresSafeArea()
|
|
|
-
|
|
|
- ScrollView {
|
|
|
- VStack(spacing: 20) {
|
|
|
- // Section: Storage
|
|
|
- storageSection
|
|
|
- .padding(.top, 12)
|
|
|
+ List {
|
|
|
+ NavigationLink {
|
|
|
+ ProfileView()
|
|
|
+ } label: {
|
|
|
+ settingsRow(
|
|
|
+ icon: "person.crop.circle",
|
|
|
+ title: "账号",
|
|
|
+ detail: accountSummary
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- // Section: Debug
|
|
|
- debugSection
|
|
|
+ NavigationLink {
|
|
|
+ RecordingDevicesSettingsView()
|
|
|
+ } label: {
|
|
|
+ settingsRow(
|
|
|
+ icon: "mic",
|
|
|
+ title: "我的录音设备"
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- // Section: Keep-Alive Guide
|
|
|
- keepAliveSection
|
|
|
+ NavigationLink {
|
|
|
+ StorageSettingsView(
|
|
|
+ usedStorageBytes: $usedStorageBytes,
|
|
|
+ availableStorageBytes: $availableStorageBytes,
|
|
|
+ reloadStorageMetrics: reloadStorageMetrics
|
|
|
+ )
|
|
|
+ } label: {
|
|
|
+ settingsRow(
|
|
|
+ icon: "internaldrive",
|
|
|
+ title: "存储空间",
|
|
|
+ detail: formattedBytes(usedStorageBytes)
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- // Section: About
|
|
|
- aboutSection
|
|
|
+ NavigationLink {
|
|
|
+ AboutSettingsView()
|
|
|
+ } label: {
|
|
|
+ settingsRow(
|
|
|
+ icon: "info.circle",
|
|
|
+ title: "关于",
|
|
|
+ detail: appVersion
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- // Footer
|
|
|
- footer
|
|
|
- .padding(.top, 12)
|
|
|
- .padding(.bottom, 40)
|
|
|
+ Section {
|
|
|
+ Toggle(isOn: $isDeveloperModeEnabled) {
|
|
|
+ settingsRow(
|
|
|
+ icon: "hammer",
|
|
|
+ title: "开发者模式"
|
|
|
+ )
|
|
|
}
|
|
|
- .padding(.horizontal, 16)
|
|
|
+ .tint(.celestiaCyan)
|
|
|
}
|
|
|
}
|
|
|
+ .scrollContentBackground(.hidden)
|
|
|
+ .background(Color.spaceBlack)
|
|
|
+ .listStyle(.insetGrouped)
|
|
|
.navigationTitle("设置")
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
|
- .alert("清理缓存", isPresented: $showClearCacheAlert) {
|
|
|
- Button("取消", role: .cancel) { }
|
|
|
- Button("清理", role: .destructive) {
|
|
|
- clearTemporaryCache()
|
|
|
- HapticManager.trigger(.tapFeedback)
|
|
|
- }
|
|
|
- } message: {
|
|
|
- Text("只清除系统缓存目录,不会删除录音、照片或会话记录。")
|
|
|
+ .task {
|
|
|
+ reloadStorageMetrics()
|
|
|
}
|
|
|
- .task { reloadStorageMetrics() }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 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 * storageUsageRatio,
|
|
|
- height: 5
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- .frame(height: 5)
|
|
|
-
|
|
|
- HStack {
|
|
|
- Text("App 数据 \(formattedBytes(usedStorageBytes))")
|
|
|
- .font(.system(size: 11, weight: .regular))
|
|
|
- .foregroundStyle(Color.primary.opacity(0.8))
|
|
|
+ private var accountSummary: String {
|
|
|
+ authManager.currentUser?.username ?? "未登录"
|
|
|
+ }
|
|
|
|
|
|
- Spacer()
|
|
|
+ private var appVersion: String {
|
|
|
+ Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0.0"
|
|
|
+ }
|
|
|
|
|
|
- Text("设备可用 \(formattedBytes(availableStorageBytes))")
|
|
|
- .font(.system(size: 11, weight: .regular))
|
|
|
- .foregroundStyle(Color.secondary)
|
|
|
- }
|
|
|
- }
|
|
|
+ private func settingsRow(icon: String, title: String, detail: String? = nil) -> some View {
|
|
|
+ HStack(spacing: 14) {
|
|
|
+ Image(systemName: icon)
|
|
|
+ .font(.system(size: 17, weight: .regular))
|
|
|
+ .foregroundStyle(Color.celestiaCyan)
|
|
|
+ .frame(width: 24)
|
|
|
|
|
|
- // 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)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ Text(title)
|
|
|
+ .foregroundStyle(Color.primary)
|
|
|
|
|
|
- // MARK: - Debug Section
|
|
|
-
|
|
|
- private var debugSection: some View {
|
|
|
- settingsCard {
|
|
|
- VStack(alignment: .leading, spacing: 14) {
|
|
|
- sectionHeader(icon: "externaldrive.connected.to.line.below", 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)
|
|
|
- }
|
|
|
+ Spacer()
|
|
|
|
|
|
- Text("记录先保存在本机;登录后由用户主动同步到服务器")
|
|
|
- .font(.system(size: 10))
|
|
|
- .foregroundStyle(Color.secondary.opacity(0.6))
|
|
|
- }
|
|
|
+ if let detail {
|
|
|
+ Text(detail)
|
|
|
+ .font(.system(size: 13))
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
+ .lineLimit(1)
|
|
|
}
|
|
|
}
|
|
|
+ .padding(.vertical, 5)
|
|
|
}
|
|
|
|
|
|
- // 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)
|
|
|
+ private func formattedBytes(_ bytes: Int64) -> String {
|
|
|
+ ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
|
|
|
+ }
|
|
|
|
|
|
- VStack(alignment: .leading, spacing: 12) {
|
|
|
- keepAliveStep(
|
|
|
- number: 1,
|
|
|
- title: "开启后台音频",
|
|
|
- description: "Xcode 开启后台模式 -> 音频与 AirPlay",
|
|
|
- isChecked: true
|
|
|
- )
|
|
|
+ private func reloadStorageMetrics() {
|
|
|
+ let fileManager = FileManager.default
|
|
|
+ let roots = [
|
|
|
+ fileManager.urls(for: .documentDirectory, in: .userDomainMask).first,
|
|
|
+ fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
|
|
|
+ ].compactMap { $0 }
|
|
|
|
|
|
- keepAliveStep(
|
|
|
- number: 2,
|
|
|
- title: "禁用自动锁屏",
|
|
|
- description: "系统设置 -> 显示与亮度 -> 自动锁定 -> 永不",
|
|
|
- isChecked: true
|
|
|
- )
|
|
|
+ usedStorageBytes = roots.reduce(0) { total, root in
|
|
|
+ let keys: Set<URLResourceKey> = [.isRegularFileKey, .fileSizeKey]
|
|
|
+ guard let enumerator = fileManager.enumerator(
|
|
|
+ at: root,
|
|
|
+ includingPropertiesForKeys: Array(keys)
|
|
|
+ ) else {
|
|
|
+ return total
|
|
|
+ }
|
|
|
|
|
|
- keepAliveStep(
|
|
|
- number: 3,
|
|
|
- title: "允许始终访问定位",
|
|
|
- description: "系统设置 -> 隐私与安全性 -> 定位服务 -> 现场记录 -> 始终允许",
|
|
|
- isChecked: false
|
|
|
- )
|
|
|
+ var subtotal: Int64 = 0
|
|
|
+ for case let url as URL in enumerator {
|
|
|
+ guard let values = try? url.resourceValues(forKeys: keys),
|
|
|
+ values.isRegularFile == true else {
|
|
|
+ continue
|
|
|
}
|
|
|
- .padding(.top, 4)
|
|
|
+ subtotal += Int64(values.fileSize ?? 0)
|
|
|
}
|
|
|
+ return total + subtotal
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- 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)
|
|
|
- }
|
|
|
- }
|
|
|
+ let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
|
|
|
+ availableStorageBytes = Int64(
|
|
|
+ (try? documents?.resourceValues(
|
|
|
+ forKeys: [.volumeAvailableCapacityForImportantUsageKey]
|
|
|
+ ).volumeAvailableCapacityForImportantUsage) ?? 0
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- VStack(alignment: .leading, spacing: 2) {
|
|
|
- Text(title)
|
|
|
- .font(.system(size: 13, weight: .medium))
|
|
|
- .foregroundStyle(Color.primary)
|
|
|
+// MARK: - Storage
|
|
|
|
|
|
- Text(description)
|
|
|
- .font(.system(size: 11))
|
|
|
- .foregroundStyle(Color.secondary.opacity(0.8))
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+private struct StorageSettingsView: View {
|
|
|
+ @Binding var usedStorageBytes: Int64
|
|
|
+ @Binding var availableStorageBytes: Int64
|
|
|
+ let reloadStorageMetrics: () -> Void
|
|
|
|
|
|
- // MARK: - About Section
|
|
|
+ @State private var showClearCacheAlert = false
|
|
|
|
|
|
- private var aboutSection: some View {
|
|
|
- settingsCard {
|
|
|
- VStack(spacing: 14) {
|
|
|
- sectionHeader(icon: "info.circle", title: "关于")
|
|
|
+ var body: some View {
|
|
|
+ List {
|
|
|
+ Section {
|
|
|
+ metricRow(label: "App 数据", value: formattedBytes(usedStorageBytes))
|
|
|
+ metricRow(label: "设备可用", value: formattedBytes(availableStorageBytes))
|
|
|
+ }
|
|
|
|
|
|
- VStack(spacing: 10) {
|
|
|
- infoRow(label: "应用名称", value: "星痕 CelestiaTrace")
|
|
|
- infoRow(label: "版本信息", value: "1.0.0")
|
|
|
- infoRow(label: "底层框架", value: "SwiftUI + SwiftData")
|
|
|
- infoRow(label: "系统支持", value: "iOS 17.0+")
|
|
|
+ Section {
|
|
|
+ Button("清理缓存", role: .destructive) {
|
|
|
+ showClearCacheAlert = true
|
|
|
}
|
|
|
- .padding(.top, 4)
|
|
|
+ } footer: {
|
|
|
+ Text("只清除系统缓存目录,不会删除录音、照片或会话记录。")
|
|
|
}
|
|
|
}
|
|
|
+ .scrollContentBackground(.hidden)
|
|
|
+ .background(Color.spaceBlack)
|
|
|
+ .navigationTitle("存储空间")
|
|
|
+ .navigationBarTitleDisplayMode(.inline)
|
|
|
+ .alert("清理缓存", isPresented: $showClearCacheAlert) {
|
|
|
+ Button("取消", role: .cancel) {}
|
|
|
+ Button("清理", role: .destructive) {
|
|
|
+ clearTemporaryCache()
|
|
|
+ HapticManager.trigger(.tapFeedback)
|
|
|
+ }
|
|
|
+ } message: {
|
|
|
+ Text("只清除系统缓存目录,不会删除录音、照片或会话记录。")
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- private func infoRow(label: String, value: String) -> some View {
|
|
|
+ private func metricRow(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)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // MARK: - Footer
|
|
|
+ private func formattedBytes(_ bytes: Int64) -> String {
|
|
|
+ ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
|
|
|
+ }
|
|
|
|
|
|
- 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)
|
|
|
+ private func clearTemporaryCache() {
|
|
|
+ let fileManager = FileManager.default
|
|
|
+ guard let cacheURL = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first,
|
|
|
+ let children = try? fileManager.contentsOfDirectory(
|
|
|
+ at: cacheURL,
|
|
|
+ includingPropertiesForKeys: nil
|
|
|
+ ) else {
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- Text("极简专业现场记录工作台")
|
|
|
- .font(.system(size: 9))
|
|
|
- .foregroundStyle(Color.secondary.opacity(0.3))
|
|
|
+ for child in children {
|
|
|
+ try? fileManager.removeItem(at: child)
|
|
|
}
|
|
|
+ reloadStorageMetrics()
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - Recording Devices
|
|
|
|
|
|
- // MARK: - Reusable Components
|
|
|
+private struct RecordingDevicesSettingsView: View {
|
|
|
+ @ObservedObject private var authManager: AuthManager = .shared
|
|
|
+ @ObservedObject private var bleManager: BLEManager = .shared
|
|
|
+ @State private var showAuthModal = false
|
|
|
+ @State private var showScanDeviceModal = false
|
|
|
|
|
|
- private func settingsCard<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
|
|
- VStack(alignment: .leading, spacing: 0) {
|
|
|
- content()
|
|
|
+ var body: some View {
|
|
|
+ ScrollView {
|
|
|
+ DeviceListView(
|
|
|
+ bleManager: bleManager,
|
|
|
+ authManager: authManager,
|
|
|
+ onAddDeviceClicked: handleAddDevice
|
|
|
+ )
|
|
|
+ .padding(16)
|
|
|
+ }
|
|
|
+ .background(Color.spaceBlack)
|
|
|
+ .navigationTitle("我的录音设备")
|
|
|
+ .navigationBarTitleDisplayMode(.inline)
|
|
|
+ .sheet(isPresented: $showAuthModal) {
|
|
|
+ AuthModalView(authManager: authManager)
|
|
|
+ }
|
|
|
+ .sheet(isPresented: $showScanDeviceModal) {
|
|
|
+ DeviceScanView(bleManager: bleManager, authManager: authManager)
|
|
|
}
|
|
|
- .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)
|
|
|
+ private func handleAddDevice() {
|
|
|
+ if authManager.isLoggedIn {
|
|
|
+ showScanDeviceModal = true
|
|
|
+ } else {
|
|
|
+ showAuthModal = true
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+// MARK: - About
|
|
|
+
|
|
|
+private struct AboutSettingsView: View {
|
|
|
+ var body: some View {
|
|
|
+ List {
|
|
|
+ Section {
|
|
|
+ infoRow(label: "应用名称", value: "星痕 CelestiaTrace")
|
|
|
+ infoRow(label: "版本信息", value: appVersion)
|
|
|
+ infoRow(label: "系统支持", value: "iOS 17.0+")
|
|
|
+ }
|
|
|
|
|
|
- private var storageUsageRatio: Double {
|
|
|
- let total = usedStorageBytes + availableStorageBytes
|
|
|
- guard total > 0 else { return 0 }
|
|
|
- return min(1, Double(usedStorageBytes) / Double(total))
|
|
|
+ Section {
|
|
|
+ guidanceRow(
|
|
|
+ title: "自动锁定",
|
|
|
+ detail: "系统设置 → 显示与亮度 → 自动锁定"
|
|
|
+ )
|
|
|
+ guidanceRow(
|
|
|
+ title: "定位权限",
|
|
|
+ detail: "系统设置 → 隐私与安全性 → 定位服务"
|
|
|
+ )
|
|
|
+ } header: {
|
|
|
+ Text("后台运行保障")
|
|
|
+ } footer: {
|
|
|
+ Text("长时间现场记录前,请确认系统权限与电量充足。")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .scrollContentBackground(.hidden)
|
|
|
+ .background(Color.spaceBlack)
|
|
|
+ .navigationTitle("关于")
|
|
|
+ .navigationBarTitleDisplayMode(.inline)
|
|
|
}
|
|
|
|
|
|
- private func formattedBytes(_ bytes: Int64) -> String {
|
|
|
- ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
|
|
|
+ private var appVersion: String {
|
|
|
+ Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0.0"
|
|
|
}
|
|
|
|
|
|
- private func reloadStorageMetrics() {
|
|
|
- let fileManager = FileManager.default
|
|
|
- let roots = [
|
|
|
- fileManager.urls(for: .documentDirectory, in: .userDomainMask).first,
|
|
|
- fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
|
|
|
- ].compactMap { $0 }
|
|
|
- usedStorageBytes = roots.reduce(0) { total, root in
|
|
|
- let keys: Set<URLResourceKey> = [.isRegularFileKey, .fileSizeKey]
|
|
|
- guard let enumerator = fileManager.enumerator(at: root, includingPropertiesForKeys: Array(keys)) else { return total }
|
|
|
- var subtotal: Int64 = 0
|
|
|
- for case let url as URL in enumerator {
|
|
|
- guard let values = try? url.resourceValues(forKeys: keys), values.isRegularFile == true else { continue }
|
|
|
- subtotal += Int64(values.fileSize ?? 0)
|
|
|
- }
|
|
|
- return total + subtotal
|
|
|
+ private func infoRow(label: String, value: String) -> some View {
|
|
|
+ HStack {
|
|
|
+ Text(label)
|
|
|
+ Spacer()
|
|
|
+ Text(value)
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
}
|
|
|
- let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
|
|
|
- availableStorageBytes = Int64((try? documents?.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey]).volumeAvailableCapacityForImportantUsage) ?? 0)
|
|
|
}
|
|
|
|
|
|
- private func clearTemporaryCache() {
|
|
|
- let fileManager = FileManager.default
|
|
|
- guard let cacheURL = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first,
|
|
|
- let children = try? fileManager.contentsOfDirectory(at: cacheURL, includingPropertiesForKeys: nil) else { return }
|
|
|
- for child in children {
|
|
|
- try? fileManager.removeItem(at: child)
|
|
|
+ private func guidanceRow(title: String, detail: String) -> some View {
|
|
|
+ VStack(alignment: .leading, spacing: 5) {
|
|
|
+ Text(title)
|
|
|
+ Text(detail)
|
|
|
+ .font(.system(size: 12))
|
|
|
+ .foregroundStyle(Color.secondary)
|
|
|
}
|
|
|
- reloadStorageMetrics()
|
|
|
+ .padding(.vertical, 3)
|
|
|
}
|
|
|
}
|
|
|
|