import SwiftUI /// Week page — Calendar-app style: day number in a circle on the left (today /// filled in coral), meals with tinted icons on the right. /// /// Swipes horizontally between every week the phone sent, so the weeks already /// planned ahead are reachable from the wrist. struct WeekView: View { @EnvironmentObject private var store: WatchWeekStore @State private var selection: Date? /// Weeks from the snapshot, or the single week of a pre-2.1.2 payload. private var weeks: [WatchWeekPayload.Week] { if let weeks = store.payload?.weeks, !weeks.isEmpty { return weeks } guard let payload = store.payload else { return [] } return [WatchWeekPayload.Week( weekStartDate: payload.weekStartDate ?? payload.updatedAt.startOfWeekOnWatch(), weekTitle: payload.weekTitle, days: payload.days )] } var body: some View { Group { if weeks.isEmpty { EmptySyncView() } else { TabView(selection: $selection) { ForEach(weeks) { week in weekPage(week) .tag(Optional(week.weekStartDate)) } } .tabViewStyle(.verticalPage(transitionStyle: .blur)) .onAppear { // Land on the current week, not on the oldest one. if selection == nil { selection = weeks.first(where: { $0.isCurrentWeek() })?.weekStartDate ?? weeks.first?.weekStartDate } } } } } private func weekPage(_ week: WatchWeekPayload.Week) -> some View { ScrollView { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 4) { Text(week.weekTitle) .font(.system(.footnote, design: .rounded, weight: .bold)) .foregroundStyle(WatchTheme.coral) if !week.isCurrentWeek() { Image(systemName: "chevron.up.chevron.down") .font(.system(size: 8, weight: .bold)) .foregroundStyle(.secondary) } } .padding(.horizontal, 2) ForEach(week.days, id: \.dayOfWeek) { day in // Only the current week can contain today. DayRow(day: day, isToday: week.isCurrentWeek() && day.isCurrentDay()) } } } } } private struct DayRow: View { let day: WatchWeekPayload.Day /// Resolved by the payload when the view renders, not by the snapshot's own /// frozen `isToday`. let isToday: Bool /// "Lun 8" → ("Lun", "8") private var parts: (name: String, number: String) { let pieces = day.title.split(separator: " ", maxSplits: 1) guard pieces.count == 2 else { return (day.title, "") } return (String(pieces[0]), String(pieces[1])) } var body: some View { HStack(alignment: .top, spacing: 8) { VStack(spacing: 0) { Text(parts.name.uppercased()) .font(.system(size: 9, weight: .bold, design: .rounded)) .foregroundStyle(isToday ? WatchTheme.coral : .secondary) Text(parts.number) .font(.system(size: 15, weight: .bold, design: .rounded)) .foregroundStyle(isToday ? .white : .primary) .frame(width: 26, height: 26) .background( Circle().fill(isToday ? WatchTheme.coral : .clear) ) } .frame(width: 32) VStack(alignment: .leading, spacing: 3) { ForEach(Array(day.meals.enumerated()), id: \.offset) { _, meal in HStack(spacing: 4) { Image(systemName: WatchWeekPayload.icon(for: meal.type)) .font(.system(size: 9, weight: .semibold)) .foregroundStyle(WatchTheme.accent(for: meal.type)) .frame(width: 12) Text(meal.name ?? "—") .font(.system(size: 13, design: .rounded)) .lineLimit(1) } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, 5) .padding(.horizontal, 7) .background( isToday ? WatchTheme.coral.opacity(0.16) : Color.white.opacity(0.07), in: RoundedRectangle(cornerRadius: 9) ) } } }