Files
FamilyMealPlanner/MealMood/Views/Onboarding/WeekReadyStepView.swift
T
alexandrev-tibco 79423e3511 tests: target MealMoodUITests que recorre el onboarding completo
El bug de los pasos 5 y 6 era invisible a los tests unitarios: el contrato de
datos estaba bien, lo que fallaba era que la vista se desmontaba antes de pintar
el aha moment y el paywall. Hace falta recorrer la UI de verdad.

Anade el target MealMoodUITests (solo simulador, sin firma) y un test que hace
el onboarding entero en instalacion limpia y comprueba que Week Ready y el
paywall aparecen antes de la Home.

Los CTA del onboarding llevan ahora accessibilityIdentifier para que el test no
dependa de los textos, que estan en 6 idiomas.

Validado en los dos sentidos: pasa con el fix, y revirtiendo temporalmente
markFinished: false falla en la asercion de Week Ready. Suite completa: 27 tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-05 23:06:04 +02:00

118 lines
4.4 KiB
Swift

import SwiftUI
/// Onboarding "aha moment" step: shows the freshly auto-filled first week and
/// asks for the weekly reminder with context (instead of a cold OS prompt on Home).
struct WeekReadyStepView: View {
let filledCount: Int
var onContinue: () -> Void
@State private var isRequestingPermission = false
@State private var reminderGranted = false
var body: some View {
VStack(spacing: 24) {
VStack(spacing: 16) {
ZStack {
Circle()
.fill(Color.mealMoodCoral.opacity(0.12))
.frame(width: 88, height: 88)
Image(systemName: filledCount > 0 ? "checkmark.circle.fill" : "calendar.badge.checkmark")
.font(.system(size: 44, weight: .semibold))
.foregroundColor(.mealMoodCoral)
}
.padding(.top, 24)
Text("onboarding_week_ready_title")
.font(.mealMoodH1)
.foregroundColor(.mealMoodTextPrimary)
.multilineTextAlignment(.center)
.accessibilityIdentifier("onboarding_week_ready_title")
Text(subtitleText)
.font(.mealMoodBody)
.foregroundColor(.mealMoodTextSecondary)
.multilineTextAlignment(.center)
}
.padding(.horizontal, 24)
VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 12) {
Image(systemName: "bell.badge.fill")
.font(.system(size: 22))
.foregroundColor(.mealMoodCoral)
Text("onboarding_reminder_pitch")
.font(.mealMoodSmall)
.foregroundColor(.mealMoodTextPrimary)
.fixedSize(horizontal: false, vertical: true)
}
}
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.mealMoodSurface)
.cornerRadius(14)
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(Color(hex: "#F0F0F0"), lineWidth: 1)
)
.padding(.horizontal, 24)
Spacer(minLength: 0)
VStack(spacing: 12) {
PrimaryButton(
title: reminderGranted
? String(localized: "onboarding_continue")
: String(localized: "onboarding_reminder_allow"),
action: {
if reminderGranted {
onContinue()
} else {
enableReminders()
}
}
)
.disabled(isRequestingPermission)
if !reminderGranted {
Button {
onContinue()
} label: {
Text("onboarding_reminder_skip")
.font(.mealMoodSmall)
.foregroundColor(.mealMoodTextSecondary)
}
.buttonStyle(.plain)
.accessibilityIdentifier("onboarding_cta_week_ready_skip")
}
}
.padding(.horizontal, 24)
.padding(.bottom, 40)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
}
private var subtitleText: String {
if filledCount > 0 {
return String(format: String(localized: "onboarding_week_ready_subtitle"), filledCount)
}
return String(localized: "onboarding_week_ready_subtitle_empty")
}
private func enableReminders() {
isRequestingPermission = true
Task {
let granted = await NotificationService.shared.requestPermission()
NotificationService.shared.remindersEnabled = true
AnalyticsService.logNotificationsToggled(enabled: granted)
isRequestingPermission = false
if granted {
reminderGranted = true
HapticManager.shared.notification(type: .success)
// Small beat so the user sees the button flip before moving on.
try? await Task.sleep(nanoseconds: 600_000_000)
}
onContinue()
}
}
}