export: revision UX de estilos, 2 estilos nuevos y copia como texto

- Header elegante: logo fijado a la izquierda y titulo centrado de verdad
  (ZStack), fecha a la derecha
- School transpuesto: dias como columnas y comidas como filas — antes con
  1-2 comidas quedaba media pagina A4 apaisada en blanco
- Default: las celdas del grid se expanden para llenar la pagina (mismo
  sintoma de hueco que el school)
- Estilo nuevo "Story" (9:16, 2160x3840) para stories de IG/WhatsApp y
  "Minimal (imprimir)" en A4 con lineas finas sin fondos (ahorro tinta)
- Picker del preview pasa de segmentado a chips desplazables (5 estilos)
- WeekPlanTextExporter + boton "Copiar como texto (WhatsApp)" en el
  preview: menu semanal con *negritas* y emojis listo para pegar en
  WhatsApp/Telegram (evento analytics format=text)
- Harness de render en tests: genera PNG de los 5 estilos con datos de
  ejemplo en /tmp/mealmood-export-previews para revisar el diseno
- Nombres de estilos en espanol corregidos (estaban en ingles)

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-01 16:04:07 +02:00
parent dc1a76275b
commit 715dd4ec5e
10 changed files with 455 additions and 65 deletions
+71
View File
@@ -1,6 +1,77 @@
import XCTest
import SwiftUI
import SwiftData
@testable import MealMood
/// Not a real assertion suite: renders every export style with sample data to
/// /tmp/mealmood-export-previews/ so design changes can be reviewed as images.
final class ExportStyleRenderTests: XCTestCase {
@MainActor
func testRenderAllExportStyles() throws {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(
for: AppSettings.self, Dish.self, Tag.self, WeekPlan.self, MealSlot.self,
configurations: config
)
let context = container.mainContext
let settings = AppSettings()
settings.activeMealTypes = [.lunch, .dinner]
settings.includeWeekends = true
settings.language = "es"
context.insert(settings)
let names = [
"Tortilla de patatas", "Salmón al horno", "Ensalada César",
"Lentejas con chorizo", "Pizza casera", "Pollo asado con patatas",
"Crema de calabaza", "Espaguetis boloñesa", "Merluza en salsa verde",
"Arroz tres delicias", "Fajitas de pollo", "Gazpacho", "Croquetas caseras", "Sopa de fideos"
]
let dishes = names.map { Dish(name: $0) }
dishes.forEach { context.insert($0) }
let monday = Calendar(identifier: .gregorian).date(from: DateComponents(year: 2026, month: 8, day: 31))!
let plan = WeekPlan(weekStartDate: monday)
context.insert(plan)
var dishIndex = 0
for day in 0...6 {
for meal in [MealType.lunch, .dinner] {
let slot = MealSlot(dayOfWeek: day, mealType: meal.rawValue)
if day == 5 && meal == .dinner {
slot.isEatingOut = true
} else {
slot.dishId = dishes[dishIndex % dishes.count].id
if day == 2 && meal == .dinner {
slot.secondaryDishId = dishes[(dishIndex + 5) % dishes.count].id
}
dishIndex += 1
}
slot.weekPlan = plan
plan.slotList.append(slot)
context.insert(slot)
}
}
try context.save()
let outputDir = URL(fileURLWithPath: "/tmp/mealmood-export-previews")
try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true)
for style in WeekExportStyle.allCases {
let renderer = ImageRenderer(content: WeekPlanShareView(
plan: plan, settings: settings, dishes: dishes, tags: [],
exportStyleOverride: style
))
let size = WeekPlanShareView.canvasSize(for: style)
renderer.proposedSize = ProposedViewSize(width: size.width, height: size.height)
renderer.scale = 1
let image = try XCTUnwrap(renderer.uiImage, "render failed for \(style.rawValue)")
let data = try XCTUnwrap(image.pngData())
try data.write(to: outputDir.appendingPathComponent("export-\(style.rawValue).png"))
}
}
}
final class DateHelpersTests: XCTestCase {
func testStartOfWeekReturnsMonday() {