import SwiftUI import SwiftData import Combine /// 主页「今日提醒」卡:汇总今天会触发的自由提醒(CustomReminder)+ 指标提醒(MetricReminder), /// 按时间升序展示;已过点的行淡化(只表示「时间已过」,不代表已完成——本期不追踪打卡)。 /// 今天没有任何提醒 → 整卡隐藏(返回 EmptyView,与「持续中症状」卡同款)。 /// 卡内只读;点右上「全部 ›」打开提醒中心(RemindersListView)管理。 struct TodayRemindersCard: View { @Query(sort: \CustomReminder.updatedAt, order: .reverse) private var customReminders: [CustomReminder] @Query(sort: \MetricReminder.updatedAt, order: .reverse) private var metricReminders: [MetricReminder] @State private var showingCenter = false /// 每分钟自走一次,用于刷新「今天」判定与「已过点」淡化(与 OngoingSymptomsCard 同款)。 @State private var tick: Date = .now private let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() /// 今天会触发的提醒,自由提醒 + 指标提醒合并成统一行模型,按时间升序。 private var items: [TodayItem] { let cal = Calendar.current var arr: [TodayItem] = [] for r in customReminders where r.occurs(on: tick, calendar: cal) { arr.append(TodayItem(id: "c-\(r.id.uuidString)", hour: r.hour, minute: r.minute, title: r.title)) } for r in metricReminders where r.occurs(on: tick, calendar: cal) { arr.append(TodayItem(id: "m-\(r.metricId)", hour: r.hour, minute: r.minute, title: r.displayName)) } return arr.sorted { ($0.hour, $0.minute) < ($1.hour, $1.minute) } } var body: some View { let rows = items if rows.isEmpty { EmptyView() } else { VStack(alignment: .leading, spacing: 10) { header(count: rows.count) VStack(spacing: 8) { ForEach(rows) { row($0) } } } .padding(.bottom, 18) .onReceive(timer) { now in tick = now } .sheet(isPresented: $showingCenter) { // 列表页依赖外层 NavigationStack 提供标题栏;sheet 形态补「完成」按钮。 NavigationStack { RemindersListView(presentedAsSheet: true) } } } } private func header(count: Int) -> some View { HStack(spacing: 8) { Circle() .fill(Tj.Palette.amber) .frame(width: 7, height: 7) Text("今日提醒") .font(.tjH2()) .foregroundStyle(Tj.Palette.text) Text("\(count) 项") .font(.system(size: 12)) .foregroundStyle(Tj.Palette.text3) Spacer() Button { showingCenter = true } label: { Text("全部 ›") .font(.system(size: 12)) .foregroundStyle(Tj.Palette.text3) } .buttonStyle(.plain) } } private func row(_ item: TodayItem) -> some View { let isPast = item.isPast(now: tick) return HStack(spacing: 12) { Text(item.timeLabel) .font(.system(size: 14, weight: .semibold).monospacedDigit()) .foregroundStyle(isPast ? Tj.Palette.text3 : Tj.Palette.ink) .frame(width: 46, alignment: .leading) Image(systemName: "bell.fill") .font(.system(size: 12)) .foregroundStyle(isPast ? Tj.Palette.text3 : Tj.Palette.amber) Text(item.title) .font(.system(size: 15, weight: .medium)) .foregroundStyle(isPast ? Tj.Palette.text3 : Tj.Palette.text) .lineLimit(1) Spacer(minLength: 0) } .padding(.horizontal, 14) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) .fill(Tj.Palette.paper) ) .shadow(color: Color(red: 0.196, green: 0.157, blue: 0.098).opacity(0.04), radius: 2, x: 0, y: 1) } } /// 「今日提醒」行的统一展示模型(自由提醒与指标提醒共用)。 private struct TodayItem: Identifiable { let id: String let hour: Int let minute: Int let title: String var timeLabel: String { String(format: "%02d:%02d", hour, minute) } /// 该提醒的时分是否早于此刻(同一天内「已过点」)。 func isPast(now: Date) -> Bool { let c = Calendar.current.dateComponents([.hour, .minute], from: now) let nowMinutes = (c.hour ?? 0) * 60 + (c.minute ?? 0) return hour * 60 + minute < nowMinutes } }