From 0f0672f18dd8e4c4cda4d5d131de93df658036a2 Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Thu, 27 Aug 2026 10:59:06 -0300 Subject: [PATCH] [auth-payment-login-lockout] Unify auth back button and animate the flow entry - RegistrationView / LoginEmailView / OtpView: pushed with no nav bar, so they showed the oversized iOS 26 system glass back button. Wrapped each in LCENavigationView with the standard AppBackButtonIcon, matching LoginView and every other screen. Dropped the now-dead colorScheme dark branches and forced .preferredColorScheme(.light) (app has no dark theme). - ContentView: root .auth <-> .main switched with no transition. Added .move transitions on both branches and wrapped enterAuthFlow() / LoginView's back action in withAnimation, so opening auth from Profile's 'Entrar ou Cadastrar' now slides in and back slides out. - ProfileLoggedOutFlowTests: new test asserting the pushed auth screens carry the LCENavigationView back button. --- PediFoods/ContentView.swift | 6 ++++- PediFoods/Views/Auth/LoginEmailView.swift | 20 ++++++++++++--- PediFoods/Views/Auth/LoginView.swift | 4 ++- PediFoods/Views/Auth/OtpView.swift | 16 ++++++++++-- PediFoods/Views/Auth/RegistrationView.swift | 25 +++++++++++++------ .../ProfileLoggedOutFlowTests.swift | 25 +++++++++++++++++++ 6 files changed, 81 insertions(+), 15 deletions(-) diff --git a/PediFoods/ContentView.swift b/PediFoods/ContentView.swift index 254f04c..90881e0 100644 --- a/PediFoods/ContentView.swift +++ b/PediFoods/ContentView.swift @@ -32,11 +32,13 @@ struct ContentView: View { tokenStore: tokenStore, appState: $appState ) + .transition(.move(edge: .trailing)) case .main: if isBootstrappingSession { sessionBootstrapLoadingView } else { MainTabView(selectedTab: $selectedTab, tokenStore: tokenStore, appState: $appState, enterAuth: enterAuthFlow) + .transition(.move(edge: .leading)) } } } @@ -408,7 +410,9 @@ struct ContentView: View { /// decisions/2026-08-27-payment-auth-lockout-clean-intro-screen.md. @MainActor private func enterAuthFlow() { - root = .auth + withAnimation(.easeInOut(duration: 0.3)) { + root = .auth + } } @MainActor diff --git a/PediFoods/Views/Auth/LoginEmailView.swift b/PediFoods/Views/Auth/LoginEmailView.swift index a85de32..163ef38 100644 --- a/PediFoods/Views/Auth/LoginEmailView.swift +++ b/PediFoods/Views/Auth/LoginEmailView.swift @@ -1,4 +1,7 @@ import SwiftUI +#if canImport(LCEssentials) +import LCEssentials +#endif struct LoginEmailView: View { @Binding var root: RootFlow @@ -11,7 +14,6 @@ struct LoginEmailView: View { @State var isLoading = false @State var errorMessage: String? @Environment(\.dismiss) var dismiss - @Environment(\.colorScheme) var colorScheme @ViewBuilder private var logoImage: some View { SwiftUI.Image("pedifoods") @@ -19,8 +21,18 @@ struct LoginEmailView: View { } var body: some View { + LCENavigationView { + content + } + .setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() } + .setNavigationBarBackgroundColor(AppColors.backgroundLight) + .buttonStyle(.plain) + .preferredColorScheme(.light) + } + + private var content: some View { ZStack { - (colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea() + AppColors.backgroundLight.ignoresSafeArea() VStack(spacing: 0) { logoImage @@ -29,7 +41,7 @@ struct LoginEmailView: View { Text("Boas-vindas!") .font(AppTypography.heading1) - .foregroundStyle(colorScheme == .dark ? Color.white : AppColors.textPrimary) + .foregroundStyle(AppColors.textPrimary) .padding(Edge.Set.top, 8) .padding(.bottom, 16) @@ -66,7 +78,7 @@ struct LoginEmailView: View { HStack(spacing: 6) { Text("Novo por aqui?") - .foregroundStyle(colorScheme == .dark ? Color.white.opacity(0.8) : Color.gray) + .foregroundStyle(Color.gray) Text("Crie sua conta") .foregroundStyle(AppColors.primary) .onTapGesture { diff --git a/PediFoods/Views/Auth/LoginView.swift b/PediFoods/Views/Auth/LoginView.swift index dfa36a3..6413d9e 100644 --- a/PediFoods/Views/Auth/LoginView.swift +++ b/PediFoods/Views/Auth/LoginView.swift @@ -16,7 +16,9 @@ struct LoginView: View { LCENavigationView { content } - .setLeftButton(image: AnyView(AppBackButtonIcon())) { root = .main } + .setLeftButton(image: AnyView(AppBackButtonIcon())) { + withAnimation(.easeInOut(duration: 0.3)) { root = .main } + } .setTitle(text: Text("Acesse sua conta").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary)) .setNavigationBarBackgroundColor(AppColors.backgroundLight) .buttonStyle(.plain) diff --git a/PediFoods/Views/Auth/OtpView.swift b/PediFoods/Views/Auth/OtpView.swift index bcf93ce..4816b37 100644 --- a/PediFoods/Views/Auth/OtpView.swift +++ b/PediFoods/Views/Auth/OtpView.swift @@ -1,5 +1,8 @@ import SwiftUI import UIKit +#if canImport(LCEssentials) +import LCEssentials +#endif struct OtpView: View { private let resendDelaySeconds = 45 @@ -21,13 +24,23 @@ struct OtpView: View { @Environment(\.dismiss) var dismiss var body: some View { + LCENavigationView { + content + } + .setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() } + .setNavigationBarBackgroundColor(AppColors.backgroundLight) + .buttonStyle(.plain) + .preferredColorScheme(.light) + } + + private var content: some View { ScrollView { VStack(spacing: 0) { SwiftUI.Image("pedifoods") .resizable() .scaledToFit() .frame(width: 74, height: 74) - .padding(.top, 140) + .padding(.top, 48) Text("Verificação") .font(AppTypography.heading1) @@ -122,7 +135,6 @@ struct OtpView: View { } .scrollDismissesKeyboard(.interactively) .background(AppColors.backgroundLight) - .ignoresSafeArea() .onAppear { isOtpFocused = true startResendCooldown() diff --git a/PediFoods/Views/Auth/RegistrationView.swift b/PediFoods/Views/Auth/RegistrationView.swift index a03f8bb..cb2441d 100644 --- a/PediFoods/Views/Auth/RegistrationView.swift +++ b/PediFoods/Views/Auth/RegistrationView.swift @@ -1,4 +1,7 @@ import SwiftUI +#if canImport(LCEssentials) +import LCEssentials +#endif struct RegistrationView: View { @Binding var root: RootFlow @@ -12,7 +15,6 @@ struct RegistrationView: View { @State var isLoading = false @State var errorMessage: String? @Environment(\.dismiss) var dismiss - @Environment(\.colorScheme) var colorScheme let navigate: (Route) -> Void private var isFormValid: Bool { !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !phone.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && acceptedTerms } @@ -23,8 +25,18 @@ struct RegistrationView: View { } var body: some View { + LCENavigationView { + content + } + .setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() } + .setNavigationBarBackgroundColor(AppColors.backgroundLight) + .buttonStyle(.plain) + .preferredColorScheme(.light) + } + + private var content: some View { ZStack { - (colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea() + AppColors.backgroundLight.ignoresSafeArea() ScrollView { VStack(spacing: 0) { @@ -34,11 +46,11 @@ struct RegistrationView: View { Text("Crie sua conta") .font(AppTypography.heading1) - .foregroundStyle(colorScheme == .dark ? Color.white : AppColors.textPrimary) + .foregroundStyle(AppColors.textPrimary) Text("Preencha os dados abaixo para começar.") .font(AppTypography.body) - .foregroundStyle(colorScheme == .dark ? Color.white : AppColors.textPrimary) + .foregroundStyle(AppColors.textPrimary) .padding(.top, 8) .padding(.bottom, 20) @@ -64,7 +76,7 @@ struct RegistrationView: View { Group { Text("Ao se cadastrar, você concorda com nossos [Termos de Uso](app://terms) e [Política de Privacidade](app://policy)") - .foregroundStyle(colorScheme == .dark ? Color.white : AppColors.textPrimary) + .foregroundStyle(AppColors.textPrimary) .tint(AppColors.primary) .environment(\.openURL, OpenURLAction { url in guard url.scheme == "app" else { return .handled } @@ -98,7 +110,7 @@ struct RegistrationView: View { HStack(spacing: 6) { Text("Já tem uma conta?") - .foregroundStyle(colorScheme == .dark ? Color.white.opacity(0.8) : AppColors.textPrimary) + .foregroundStyle(AppColors.textPrimary) NavigationLink("Entrar") { LoginEmailView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore, appState: $appState, navigate: navigate) } @@ -110,7 +122,6 @@ struct RegistrationView: View { Spacer().frame(height: 12) } } - .padding(.top, -40) } } diff --git a/PediFoodsUITests/ProfileLoggedOutFlowTests.swift b/PediFoodsUITests/ProfileLoggedOutFlowTests.swift index 2445888..5a547e3 100644 --- a/PediFoodsUITests/ProfileLoggedOutFlowTests.swift +++ b/PediFoodsUITests/ProfileLoggedOutFlowTests.swift @@ -40,4 +40,29 @@ final class ProfileLoggedOutFlowTests: XCTestCase { // Back out of `.auth` lands on the main tab bar again. XCTAssertTrue(app.buttons["Home"].waitForExistence(timeout: 5), "Back button did not return to the main flow") } + + /// The pushed auth screens (Registration, LoginEmail) must carry the + /// app's standard `AppBackButtonIcon` via `LCENavigationView`, not the + /// oversized iOS 26 system glass back button. + func testPushedAuthScreensUseAppBackButton() throws { + let app = XCUIApplication() + app.launch() + XCTAssertTrue(app.reachLoggedOutProfile()) + app.buttons["Entrar ou Cadastrar"].tap() + + XCTAssertTrue(app.buttons["Criar conta"].waitForExistence(timeout: 5)) + app.buttons["Criar conta"].tap() + XCTAssertTrue(app.staticTexts["Crie sua conta"].waitForExistence(timeout: 5)) + let regBack = app.buttons.matching(NSPredicate(format: "label == %@", "Back")).firstMatch + XCTAssertTrue(regBack.exists, "Registration screen has no LCENavigationView back button") + regBack.tap() + + XCTAssertTrue(app.buttons["Entrar"].waitForExistence(timeout: 5)) + app.buttons["Entrar"].tap() + XCTAssertTrue(app.staticTexts["Boas-vindas!"].waitForExistence(timeout: 5)) + let loginBack = app.buttons.matching(NSPredicate(format: "label == %@", "Back")).firstMatch + XCTAssertTrue(loginBack.exists, "Login e-mail screen has no LCENavigationView back button") + loginBack.tap() + XCTAssertTrue(app.buttons["Criar conta"].waitForExistence(timeout: 5), "Back did not return to the auth intro") + } }