feat(indicator): 长期监测预设支持长按隐藏 + 恢复

- UserProfile 加 hiddenPresetMetrics: [String],存被隐藏的 MonitorMetric.rawValue
- IndicatorQuickSheet monitorTile 加 contextMenu 隐藏入口
- section label 右侧"已隐藏 N 个 ›"chip 触发 HiddenMonitorRestoreSheet
- 纯 UI 过滤,不动 Indicator 历史 / Trends 折线 / MetricReminder

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
link2026
2026-05-26 19:47:55 +08:00
parent 599d39af35
commit 5f8f492f0e
2 changed files with 163 additions and 2 deletions

View File

@@ -62,6 +62,9 @@ struct IndicatorQuickSheet: View {
@State private var selectedCustom: CustomMonitorMetric?
@State private var editingCustom: CustomMetricEditTarget?
// sheet
@State private var showHiddenSheet: Bool = false
private static var defaultReminderTime: Date {
Calendar.current.date(bySettingHour: 8, minute: 0, second: 0, of: .now) ?? .now
}
@@ -167,10 +170,16 @@ struct IndicatorQuickSheet: View {
private var monitorGridSection: some View {
VStack(alignment: .leading, spacing: 8) {
sectionLabel("长期监测(进趋势)")
HStack {
sectionLabel("长期监测(进趋势)")
Spacer()
if !hiddenSet.isEmpty {
hiddenCountChip
}
}
let columns = [GridItem(.flexible()), GridItem(.flexible())]
LazyVGrid(columns: columns, spacing: 8) {
ForEach(MonitorMetric.allCases) { m in
ForEach(visibleMonitorMetrics) { m in
monitorTile(m)
}
ForEach(customMetrics) { cm in
@@ -179,6 +188,12 @@ struct IndicatorQuickSheet: View {
addCustomTile
}
}
.sheet(isPresented: $showHiddenSheet) {
HiddenMonitorRestoreSheet(
hiddenMetrics: hiddenMonitorMetrics,
onRestore: { unhideMonitor($0) }
)
}
.sheet(item: $editingCustom) { target in
CustomMetricEditor(existing: target.metric) { saved in
// ,
@@ -303,6 +318,13 @@ struct IndicatorQuickSheet: View {
)
}
.buttonStyle(.plain)
.contextMenu {
Button(role: .destructive) {
hideMonitor(m)
} label: {
Label("隐藏", systemImage: "eye.slash")
}
}
}
private var labPresetSection: some View {
@@ -785,6 +807,59 @@ struct IndicatorQuickSheet: View {
return "\(fmt(r.lowerBound))\(fmt(r.upperBound))"
}
// MARK: - hidden preset
private var hiddenSet: Set<String> {
Set(profile?.hiddenPresetMetrics ?? [])
}
private var visibleMonitorMetrics: [MonitorMetric] {
MonitorMetric.allCases.filter { !hiddenSet.contains($0.rawValue) }
}
private var hiddenMonitorMetrics: [MonitorMetric] {
MonitorMetric.allCases.filter { hiddenSet.contains($0.rawValue) }
}
private var hiddenCountChip: some View {
Button {
showHiddenSheet = true
} label: {
HStack(spacing: 3) {
Text("已隐藏 \(hiddenSet.count)")
.font(.system(size: 11, weight: .medium))
Image(systemName: "chevron.right")
.font(.system(size: 9, weight: .semibold))
}
.foregroundStyle(Tj.Palette.text2)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(Capsule().fill(Tj.Palette.sand2))
}
.buttonStyle(.plain)
}
private func hideMonitor(_ m: MonitorMetric) {
let profile = UserProfileStore.loadOrCreate(in: ctx)
guard !profile.hiddenPresetMetrics.contains(m.rawValue) else { return }
profile.hiddenPresetMetrics.append(m.rawValue)
profile.updatedAt = .now
try? ctx.save()
if selectedMonitor == m {
clearMonitor()
}
}
private func unhideMonitor(_ m: MonitorMetric) {
guard let profile = profile else { return }
profile.hiddenPresetMetrics.removeAll { $0 == m.rawValue }
profile.updatedAt = .now
try? ctx.save()
if profile.hiddenPresetMetrics.isEmpty {
showHiddenSheet = false
}
}
// MARK: - apply preset
private func applyMonitor(_ m: MonitorMetric) {
@@ -1020,6 +1095,85 @@ struct CustomMetricEditTarget: Identifiable {
var id: String { metric?.seriesKey ?? "_new_" }
}
/// "" metric hiddenPresetMetrics
private struct HiddenMonitorRestoreSheet: View {
let hiddenMetrics: [MonitorMetric]
let onRestore: (MonitorMetric) -> Void
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack(spacing: 0) {
Capsule()
.fill(Tj.Palette.line)
.frame(width: 40, height: 4)
.padding(.top, 10)
.padding(.bottom, 14)
HStack {
Text("已隐藏的长期监测")
.font(.tjH2())
.foregroundStyle(Tj.Palette.text)
Spacer()
Button("完成") { dismiss() }
.font(.system(size: 14))
.foregroundStyle(Tj.Palette.ink)
}
.padding(.horizontal, 20)
.padding(.bottom, 12)
ScrollView {
VStack(spacing: 8) {
ForEach(hiddenMetrics) { m in
row(m)
}
}
.padding(.horizontal, 20)
.padding(.bottom, 24)
}
}
.background(Tj.Palette.sand)
.presentationDetents([.medium])
.presentationBackground(Tj.Palette.sand)
.presentationCornerRadius(Tj.Radius.xl)
}
private func row(_ m: MonitorMetric) -> some View {
HStack(spacing: 12) {
Image(systemName: m.icon)
.font(.system(size: 16, weight: .medium))
.foregroundStyle(Tj.Palette.ink)
.frame(width: 32, height: 32)
.background(Circle().fill(Tj.Palette.amber.opacity(0.25)))
Text(m.displayName)
.font(.system(size: 15, weight: .medium))
.foregroundStyle(Tj.Palette.text)
Spacer()
Button("显示") {
onRestore(m)
}
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(Tj.Palette.paper)
.padding(.horizontal, 14)
.padding(.vertical, 6)
.background(Capsule().fill(Tj.Palette.ink))
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
.fill(Tj.Palette.paper)
)
.overlay(
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
.strokeBorder(Tj.Palette.line, lineWidth: 1)
)
}
}
#Preview {
IndicatorQuickSheet()
.modelContainer(for: [

View File

@@ -18,6 +18,11 @@ final class UserProfile {
//
var currentMedications: [String]
// UI
// IndicatorQuickSheet MonitorMetric.rawValue
// grid, Indicator / Trends / Reminder
var hiddenPresetMetrics: [String]
var updatedAt: Date
init(birthYear: Int? = nil,
@@ -29,6 +34,7 @@ final class UserProfile {
chronicConditions: [String] = [],
familyHistory: [String] = [],
currentMedications: [String] = [],
hiddenPresetMetrics: [String] = [],
updatedAt: Date = .now) {
self.birthYear = birthYear
self.biologicalSexRaw = biologicalSexRaw
@@ -39,6 +45,7 @@ final class UserProfile {
self.chronicConditions = chronicConditions
self.familyHistory = familyHistory
self.currentMedications = currentMedications
self.hiddenPresetMetrics = hiddenPresetMetrics
self.updatedAt = updatedAt
}
}