watch: texto de la complicacion proporcional y complicacion de 3 dias

- La rectangular de hoy escala el texto segun comidas: 1 comida -> 17pt
  (hasta 3 lineas), 2 -> 15pt, 3 -> 12pt; antes 12pt fijo y con dos
  comidas quedaba enano
- Nueva complicacion "MealMood - 3 dias": hoy + los dos siguientes en
  una linea por dia (abreviatura en coral si es hoy + platos)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
This commit is contained in:
alexandrev-tibco
2026-09-09 09:47:38 +02:00
parent 653aa75264
commit 0067a807ea
3 changed files with 116 additions and 7 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>2.1.0</string>
<key>CFBundleVersion</key>
<string>81</string>
<string>82</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
+114 -5
View File
@@ -8,6 +8,7 @@ import SwiftUI
struct MealMoodWatchWidgetBundle: WidgetBundle {
var body: some Widget {
MealMoodWatchWidget()
MealMoodWatchDaysWidget()
}
}
@@ -83,16 +84,21 @@ struct TodayComplicationView: View {
default:
VStack(alignment: .leading, spacing: 2) {
if let day = entry.day {
ForEach(Array(day.meals.prefix(3).enumerated()), id: \.offset) { _, meal in
// The rectangular slot fits ~3 lines: with fewer meals the
// text scales up so the space isn't wasted.
let meals = Array(day.meals.prefix(3))
let nameSize: CGFloat = meals.count <= 1 ? 17 : (meals.count == 2 ? 15 : 12)
ForEach(Array(meals.enumerated()), id: \.offset) { _, meal in
HStack(spacing: 4) {
Image(systemName: WatchWeekPayload.icon(for: meal.type))
.font(.system(size: 9, weight: .bold))
.font(.system(size: nameSize - 3, weight: .bold))
.foregroundStyle(accent(for: meal.type))
.widgetAccentable()
.frame(width: 11)
.frame(width: nameSize)
Text(meal.name ?? "")
.font(.system(size: 12, weight: .semibold, design: .rounded))
.lineLimit(1)
.font(.system(size: nameSize, weight: .semibold, design: .rounded))
.lineLimit(meals.count <= 1 ? 3 : (meals.count == 2 ? 2 : 1))
.minimumScaleFactor(0.75)
}
}
} else {
@@ -108,6 +114,10 @@ struct TodayComplicationView: View {
}
private func accent(for mealType: String) -> Color {
Self.accentColor(for: mealType)
}
static func accentColor(for mealType: String) -> Color {
switch mealType {
case "breakfast": return Color(red: 0.95, green: 0.68, blue: 0.25)
case "lunch": return Color(red: 0.98, green: 0.55, blue: 0.30)
@@ -125,3 +135,102 @@ struct TodayComplicationView: View {
return meals.first(where: { $0.name != nil }) ?? meals.first
}
}
// MARK: - Next days complication
struct DaysEntry: TimelineEntry {
let date: Date
let days: [WatchWeekPayload.Day]
}
struct DaysProvider: TimelineProvider {
func placeholder(in context: Context) -> DaysEntry {
DaysEntry(date: Date(), days: sampleDays)
}
func getSnapshot(in context: Context, completion: @escaping (DaysEntry) -> Void) {
let days = upcomingDays
completion(DaysEntry(date: Date(), days: days.isEmpty ? sampleDays : days))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<DaysEntry>) -> Void) {
let entry = DaysEntry(date: Date(), days: upcomingDays)
let nextMidnight = Calendar.current.startOfDay(for: Date()).addingTimeInterval(86_400 + 300)
completion(Timeline(entries: [entry], policy: .after(nextMidnight)))
}
/// Today and the next two planned days (falls back to the week's first
/// days when today is outside the planned range, e.g. weekends off).
private var upcomingDays: [WatchWeekPayload.Day] {
guard let payload = WatchWeekPayload.stored() else { return [] }
let todayOffset = (Calendar.current.component(.weekday, from: Date()) + 5) % 7
let fromToday = payload.days.filter { $0.dayOfWeek >= todayOffset }
let source = fromToday.isEmpty ? payload.days : fromToday
return Array(source.prefix(3))
}
private var sampleDays: [WatchWeekPayload.Day] {
[
.init(dayOfWeek: 0, title: "Lun 8", isToday: true, meals: [
.init(type: "lunch", label: "Comida", name: "Ensalada"),
.init(type: "dinner", label: "Cena", name: "Salmón")]),
.init(dayOfWeek: 1, title: "Mar 9", isToday: false, meals: [
.init(type: "dinner", label: "Cena", name: "Lentejas")]),
.init(dayOfWeek: 2, title: "Mié 10", isToday: false, meals: [
.init(type: "dinner", label: "Cena", name: "Pizza casera")])
]
}
}
struct MealMoodWatchDaysWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "MealMoodWatchDays", provider: DaysProvider()) { entry in
DaysComplicationView(entry: entry)
.containerBackground(.clear, for: .widget)
}
.configurationDisplayName("MealMood — 3 días")
.supportedFamilies([.accessoryRectangular])
}
}
struct DaysComplicationView: View {
let entry: DaysEntry
var body: some View {
VStack(alignment: .leading, spacing: 1) {
if entry.days.isEmpty {
Text(verbatim: "MealMood")
.font(.system(size: 12, weight: .semibold, design: .rounded))
Image(systemName: "iphone.and.arrow.forward")
.font(.system(size: 10))
.foregroundStyle(.secondary)
} else {
ForEach(entry.days, id: \.dayOfWeek) { day in
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(dayAbbrev(day))
.font(.system(size: 11, weight: .heavy, design: .rounded))
.foregroundStyle(day.isToday
? Color(red: 1.0, green: 0.45, blue: 0.35)
: .secondary)
.widgetAccentable()
.frame(width: 28, alignment: .leading)
Text(mealsLine(day))
.font(.system(size: 12, weight: .medium, design: .rounded))
.lineLimit(1)
.minimumScaleFactor(0.7)
}
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
private func dayAbbrev(_ day: WatchWeekPayload.Day) -> String {
String(day.title.split(separator: " ").first ?? "").uppercased()
}
private func mealsLine(_ day: WatchWeekPayload.Day) -> String {
let names = day.meals.compactMap(\.name)
return names.isEmpty ? "" : names.joined(separator: " · ")
}
}
+1 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>2.1.0</string>
<key>CFBundleVersion</key>
<string>81</string>
<string>82</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>