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:
@@ -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) {
|
||||
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: [
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user