watch: semanas navegables y los dias que no se planifican
Dos cosas que faltaban, las dos visibles en domingo con el fin de semana desactivado: El envio al reloj solo llevaba la semana en curso, asi que no habia forma de ver las semanas ya planificadas por delante. Ahora viajan cuatro (la anterior, la actual y las dos siguientes) y la vista de semana se desliza entre ellas, arrancando en la actual. Y un dia fuera del plan dejaba al reloj sin nada que enseñar: el domingo no existe en el payload si no planificas fines de semana, asi que la complicacion salia vacia y la app decia "abre MealMood en el iPhone", como si fuera un problema de sincronizacion. Ahora se distingue: si hoy no se planifica se dice, y se enseña el proximo dia que si tiene comidas — que es lo util en el reloj. De paso, las semanas se comparan con el calendario alineado a lunes. Con el del sistema, en las regiones donde la semana empieza en domingo, un domingo caia en otra semana que su propio lunes y un envio recien hecho parecia caducado. Refs #36, #37 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
This commit is contained in:
@@ -5,52 +5,84 @@ import SwiftUI
|
||||
struct TodayView: View {
|
||||
@EnvironmentObject private var store: WatchWeekStore
|
||||
|
||||
/// Resolved as the view renders. The snapshot's own `isToday` is frozen at
|
||||
/// the moment the iPhone built it, which is how a Wednesday plan ended up
|
||||
/// showing on a Saturday.
|
||||
private var today: WatchWeekPayload.Day? {
|
||||
store.payload?.currentDay()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let today {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(today.title)
|
||||
.font(.system(.title3, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(WatchTheme.coral)
|
||||
Spacer()
|
||||
Image(systemName: "fork.knife")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.horizontal, 2)
|
||||
|
||||
if today.isEmpty {
|
||||
EmptyDayView(text: store.payload?.emptyDayText)
|
||||
} else {
|
||||
ForEach(Array(today.meals.enumerated()), id: \.offset) { _, meal in
|
||||
MealCard(meal: meal, emptyText: store.payload?.emptyMealText)
|
||||
if let payload = store.payload {
|
||||
switch payload.todayState() {
|
||||
case .planned(let today):
|
||||
dayHeader(today.title)
|
||||
if today.isEmpty {
|
||||
EmptyDayView(text: payload.emptyDayText)
|
||||
} else {
|
||||
ForEach(Array(today.meals.enumerated()), id: \.offset) { _, meal in
|
||||
MealCard(meal: meal, emptyText: payload.emptyMealText)
|
||||
}
|
||||
}
|
||||
|
||||
case .notPlannedToday(let next):
|
||||
// A weekend with weekends turned off isn't a sync
|
||||
// problem — say so, and point at what does come next.
|
||||
NotPlannedTodayView(text: payload.notPlannedTodayText)
|
||||
if let next {
|
||||
if let label = payload.nextUpLabel {
|
||||
Text(label.uppercased())
|
||||
.font(.system(size: 10, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
dayHeader(next.title)
|
||||
ForEach(Array(next.meals.enumerated()), id: \.offset) { _, meal in
|
||||
MealCard(meal: meal, emptyText: payload.emptyMealText)
|
||||
}
|
||||
}
|
||||
|
||||
case .outOfDate:
|
||||
StaleView(text: payload.staleText)
|
||||
}
|
||||
} else if let payload = store.payload {
|
||||
// There is a snapshot, but it doesn't cover today: better to
|
||||
// say so than to show another day's meals as if they were
|
||||
// today's.
|
||||
StaleView(text: payload.staleText)
|
||||
|
||||
// When the watch is holding an old snapshot, this is what
|
||||
// makes it obvious instead of guessing why meals look wrong.
|
||||
LastUpdatedFooter(payload: payload)
|
||||
} else {
|
||||
EmptySyncView()
|
||||
}
|
||||
|
||||
// When the watch is holding an old snapshot, this is what makes
|
||||
// it obvious instead of guessing why the meals look wrong.
|
||||
if let payload = store.payload {
|
||||
LastUpdatedFooter(payload: payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dayHeader(_ title: String) -> some View {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(title)
|
||||
.font(.system(.title3, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(WatchTheme.coral)
|
||||
Spacer()
|
||||
Image(systemName: "fork.knife")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.horizontal, 2)
|
||||
}
|
||||
}
|
||||
|
||||
/// Today is deliberately outside the plan (weekends off, typically).
|
||||
struct NotPlannedTodayView: View {
|
||||
var text: String?
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "calendar.badge.minus")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(WatchTheme.coral)
|
||||
if let text {
|
||||
Text(text)
|
||||
.font(.system(size: 12, weight: .semibold, design: .rounded))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
|
||||
private struct LastUpdatedFooter: View {
|
||||
|
||||
@@ -2,28 +2,66 @@ 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 let payload = store.payload {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(payload.weekTitle)
|
||||
.font(.system(.footnote, design: .rounded, weight: .bold))
|
||||
.foregroundStyle(WatchTheme.coral)
|
||||
.padding(.horizontal, 2)
|
||||
|
||||
ForEach(payload.days, id: \.dayOfWeek) { day in
|
||||
// Nothing is "today" in a snapshot that no longer
|
||||
// covers today.
|
||||
DayRow(day: day, isToday: payload.currentDay()?.dayOfWeek == day.dayOfWeek)
|
||||
}
|
||||
if weeks.isEmpty {
|
||||
EmptySyncView()
|
||||
} else {
|
||||
TabView(selection: $selection) {
|
||||
ForEach(weeks) { week in
|
||||
weekPage(week)
|
||||
.tag(Optional(week.weekStartDate))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EmptySyncView()
|
||||
.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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user