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 selectedCustom: CustomMonitorMetric?
|
||||||
@State private var editingCustom: CustomMetricEditTarget?
|
@State private var editingCustom: CustomMetricEditTarget?
|
||||||
|
|
||||||
|
// 隐藏管理 sheet 触发态
|
||||||
|
@State private var showHiddenSheet: Bool = false
|
||||||
|
|
||||||
private static var defaultReminderTime: Date {
|
private static var defaultReminderTime: Date {
|
||||||
Calendar.current.date(bySettingHour: 8, minute: 0, second: 0, of: .now) ?? .now
|
Calendar.current.date(bySettingHour: 8, minute: 0, second: 0, of: .now) ?? .now
|
||||||
}
|
}
|
||||||
@@ -167,10 +170,16 @@ struct IndicatorQuickSheet: View {
|
|||||||
|
|
||||||
private var monitorGridSection: some View {
|
private var monitorGridSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
HStack {
|
||||||
sectionLabel("长期监测(进趋势)")
|
sectionLabel("长期监测(进趋势)")
|
||||||
|
Spacer()
|
||||||
|
if !hiddenSet.isEmpty {
|
||||||
|
hiddenCountChip
|
||||||
|
}
|
||||||
|
}
|
||||||
let columns = [GridItem(.flexible()), GridItem(.flexible())]
|
let columns = [GridItem(.flexible()), GridItem(.flexible())]
|
||||||
LazyVGrid(columns: columns, spacing: 8) {
|
LazyVGrid(columns: columns, spacing: 8) {
|
||||||
ForEach(MonitorMetric.allCases) { m in
|
ForEach(visibleMonitorMetrics) { m in
|
||||||
monitorTile(m)
|
monitorTile(m)
|
||||||
}
|
}
|
||||||
ForEach(customMetrics) { cm in
|
ForEach(customMetrics) { cm in
|
||||||
@@ -179,6 +188,12 @@ struct IndicatorQuickSheet: View {
|
|||||||
addCustomTile
|
addCustomTile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showHiddenSheet) {
|
||||||
|
HiddenMonitorRestoreSheet(
|
||||||
|
hiddenMetrics: hiddenMonitorMetrics,
|
||||||
|
onRestore: { unhideMonitor($0) }
|
||||||
|
)
|
||||||
|
}
|
||||||
.sheet(item: $editingCustom) { target in
|
.sheet(item: $editingCustom) { target in
|
||||||
CustomMetricEditor(existing: target.metric) { saved in
|
CustomMetricEditor(existing: target.metric) { saved in
|
||||||
// 新建后自动选中,删除后清空选择
|
// 新建后自动选中,删除后清空选择
|
||||||
@@ -303,6 +318,13 @@ struct IndicatorQuickSheet: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
.contextMenu {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
hideMonitor(m)
|
||||||
|
} label: {
|
||||||
|
Label("隐藏", systemImage: "eye.slash")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var labPresetSection: some View {
|
private var labPresetSection: some View {
|
||||||
@@ -785,6 +807,59 @@ struct IndicatorQuickSheet: View {
|
|||||||
return "\(fmt(r.lowerBound))–\(fmt(r.upperBound))"
|
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
|
// MARK: - apply preset
|
||||||
|
|
||||||
private func applyMonitor(_ m: MonitorMetric) {
|
private func applyMonitor(_ m: MonitorMetric) {
|
||||||
@@ -1020,6 +1095,85 @@ struct CustomMetricEditTarget: Identifiable {
|
|||||||
var id: String { metric?.seriesKey ?? "_new_" }
|
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 {
|
#Preview {
|
||||||
IndicatorQuickSheet()
|
IndicatorQuickSheet()
|
||||||
.modelContainer(for: [
|
.modelContainer(for: [
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ final class UserProfile {
|
|||||||
// —— 当前用药 ——
|
// —— 当前用药 ——
|
||||||
var currentMedications: [String]
|
var currentMedications: [String]
|
||||||
|
|
||||||
|
// —— UI 偏好 ——
|
||||||
|
// 用户在 IndicatorQuickSheet 长按隐藏的 MonitorMetric.rawValue。
|
||||||
|
// 只影响录入 grid,不影响 Indicator 历史 / Trends / Reminder。
|
||||||
|
var hiddenPresetMetrics: [String]
|
||||||
|
|
||||||
var updatedAt: Date
|
var updatedAt: Date
|
||||||
|
|
||||||
init(birthYear: Int? = nil,
|
init(birthYear: Int? = nil,
|
||||||
@@ -29,6 +34,7 @@ final class UserProfile {
|
|||||||
chronicConditions: [String] = [],
|
chronicConditions: [String] = [],
|
||||||
familyHistory: [String] = [],
|
familyHistory: [String] = [],
|
||||||
currentMedications: [String] = [],
|
currentMedications: [String] = [],
|
||||||
|
hiddenPresetMetrics: [String] = [],
|
||||||
updatedAt: Date = .now) {
|
updatedAt: Date = .now) {
|
||||||
self.birthYear = birthYear
|
self.birthYear = birthYear
|
||||||
self.biologicalSexRaw = biologicalSexRaw
|
self.biologicalSexRaw = biologicalSexRaw
|
||||||
@@ -39,6 +45,7 @@ final class UserProfile {
|
|||||||
self.chronicConditions = chronicConditions
|
self.chronicConditions = chronicConditions
|
||||||
self.familyHistory = familyHistory
|
self.familyHistory = familyHistory
|
||||||
self.currentMedications = currentMedications
|
self.currentMedications = currentMedications
|
||||||
|
self.hiddenPresetMetrics = hiddenPresetMetrics
|
||||||
self.updatedAt = updatedAt
|
self.updatedAt = updatedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user