From 57103c637a8254c6701e75aa11071118baace066 Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Thu, 27 Aug 2026 10:11:04 -0300 Subject: [PATCH 1/5] [auth-payment-login-lockout] Make auth intro a clean screen with a back button App Review rejected the 2026-08 build: tapping checkout as a guest switched root to .auth, which replaced the whole UI with LoginView as the NavigationStack root - no nav bar, no back, no dismiss. User was trapped. - LoginView: rebuilt as a static screen in LCENavigationView with a back button that sets root = .main, a 'Criar conta' and an 'Entrar' button, and 'Termos de Uso' / 'Politica de Privacidade' links. Added #Preview. - Forced .preferredColorScheme(.light) and fixed DS colors (the app has no dark theme, so system dark mode was inverting the nav bar / title). - Removed the entry-reveal animation machinery (heroVisible/textVisible/ buttonVisible/token/prepare flags across LoginView, AuthFlowView, ContentView) - dead since guest browsing shipped and the root cause of the 2026-08-06 'content stuck hidden' bug. - enterAuthFlow() is now just root = .auth. - UITestSupport.ensureLoggedIn taps 'Entrar' (new label). - ProfileLoggedOutFlowTests: new test covering the choices and the way back out of .auth. - Localizable.xcstrings: catalog caught up to the new/removed strings. --- PediFoods/ContentView.swift | 29 +-- PediFoods/Resources/Localizable.xcstrings | 10 +- PediFoods/Views/Auth/AuthFlowView.swift | 11 +- PediFoods/Views/Auth/LoginView.swift | 169 ++++++------------ .../ProfileLoggedOutFlowTests.swift | 30 ++-- PediFoodsUITests/UITestSupport.swift | 2 +- 6 files changed, 81 insertions(+), 170 deletions(-) diff --git a/PediFoods/ContentView.swift b/PediFoods/ContentView.swift index 3d77f86..254f04c 100644 --- a/PediFoods/ContentView.swift +++ b/PediFoods/ContentView.swift @@ -15,8 +15,6 @@ struct ContentView: View { @State var hasPerformedInitialLaunchBootstrap = false @State var showLaunchSplash = true @State var shouldPulseLaunchSplash = true - @State var shouldPrepareAuthEntryAnimation = DefaultTokenStore().jwt == nil - @State var authEntryAnimationToken = 0 @State private var sessionExpiredObserver: NSObjectProtocol? @State var cartResetObserver: Any? @State var appResumeObserver: Any? @@ -32,9 +30,7 @@ struct ContentView: View { root: $root, selectedTab: $selectedTab, tokenStore: tokenStore, - appState: $appState, - shouldPrepareLoginEntry: shouldPrepareAuthEntryAnimation, - authEntryAnimationToken: authEntryAnimationToken + appState: $appState ) case .main: if isBootstrappingSession { @@ -136,15 +132,11 @@ struct ContentView: View { } if root == .auth { - shouldPrepareAuthEntryAnimation = true shouldPulseLaunchSplash = false try? await Task.sleep(nanoseconds: 180_000_000) withAnimation(.easeInOut(duration: 0.34)) { showLaunchSplash = false } - try? await Task.sleep(nanoseconds: 360_000_000) - authEntryAnimationToken += 1 - scheduleDisableAuthEntryPreparation() return } @@ -410,17 +402,13 @@ struct ContentView: View { enterAuthFlow() } - /// The only correct way to transition `root` to `.auth`: pairs the - /// switch with the token bump `LoginView` needs to run its entry - /// reveal animation. A bare `root = .auth` leaves the login screen's - /// hero/text/button stuck hidden - see - /// decisions/2026-08-06-login-entry-animation-bug.md. + /// The single funnel for entering the auth flow. `LoginView` is a + /// static screen with its own `LCENavigationView` back button (sets + /// `root = .main`), so no animation priming is needed here - see + /// decisions/2026-08-27-payment-auth-lockout-clean-intro-screen.md. @MainActor private func enterAuthFlow() { - shouldPrepareAuthEntryAnimation = true root = .auth - authEntryAnimationToken += 1 - scheduleDisableAuthEntryPreparation() } @MainActor @@ -463,13 +451,6 @@ struct ContentView: View { ) } - private func scheduleDisableAuthEntryPreparation() { - Task { @MainActor in - try? await Task.sleep(nanoseconds: 900_000_000) - shouldPrepareAuthEntryAnimation = false - } - } - private func hasConfiguredAddress() -> Bool { if appState.address.selectedId != nil { return true diff --git a/PediFoods/Resources/Localizable.xcstrings b/PediFoods/Resources/Localizable.xcstrings index 8452e20..7099a48 100644 --- a/PediFoods/Resources/Localizable.xcstrings +++ b/PediFoods/Resources/Localizable.xcstrings @@ -88,6 +88,9 @@ }, "Abrir Ajustes" : { + }, + "Acesse sua conta" : { + }, "Acompanhamento em tempo real" : { @@ -346,10 +349,6 @@ "comment" : "A prompt asking users to share their opinion about the delivery.", "isCommentAutoGenerated" : true }, - "Criar conta" : { - "comment" : "A link that navigates to the registration screen.", - "isCommentAutoGenerated" : true - }, "Crie sua conta" : { "comment" : "A link that directs users to create an account.", "isCommentAutoGenerated" : true @@ -748,9 +747,6 @@ "Não recebeu o código?" : { "comment" : "A question displayed below the button to re-send the OTP.", "isCommentAutoGenerated" : true - }, - "Não tem conta ainda?" : { - }, "Nenhum cartão cadastrado" : { diff --git a/PediFoods/Views/Auth/AuthFlowView.swift b/PediFoods/Views/Auth/AuthFlowView.swift index 1dc0aa9..9edd55f 100644 --- a/PediFoods/Views/Auth/AuthFlowView.swift +++ b/PediFoods/Views/Auth/AuthFlowView.swift @@ -11,21 +11,12 @@ struct AuthFlowView: View { @Binding var selectedTab: MainTab let tokenStore: TokenStore @Binding var appState: AppState - var shouldPrepareLoginEntry: Bool = false - var authEntryAnimationToken: Int = 0 @State var path: [Route] = [] var body: some View { NavigationStack(path: $path) { - LoginView( - root: $root, - selectedTab: $selectedTab, - tokenStore: tokenStore, - appState: $appState, - shouldPrepareEntryAnimation: shouldPrepareLoginEntry, - authEntryAnimationToken: authEntryAnimationToken - ) { route in + LoginView(root: $root) { route in path.append(route) } .navigationDestination(for: Route.self) { route in diff --git a/PediFoods/Views/Auth/LoginView.swift b/PediFoods/Views/Auth/LoginView.swift index 6d79a9e..dfa36a3 100644 --- a/PediFoods/Views/Auth/LoginView.swift +++ b/PediFoods/Views/Auth/LoginView.swift @@ -1,142 +1,79 @@ import SwiftUI +#if canImport(LCEssentials) +import LCEssentials +#endif struct LoginView: View { @Binding var root: RootFlow - @Binding var selectedTab: MainTab - let tokenStore: TokenStore - @Binding var appState: AppState - @Environment(\.colorScheme) var colorScheme - var shouldPrepareEntryAnimation: Bool = false - var authEntryAnimationToken: Int = 0 let navigate: (Route) -> Void - @State var heroVisible = true - @State var textVisible = true - @State var buttonVisible = true - @State var lastAnimatedToken = 0 @ViewBuilder private var logoImage: some View { SwiftUI.Image("pedifoods") .resizable() } - @ViewBuilder private var pinHeroImage: some View { - SwiftUI.Image("pin_image_app") - .resizable() - } - var body: some View { - GeometryReader { geo in - let heroHeight = max(360, geo.size.height * 0.44) - let logoTopInset = max(0, (geo.size.height - 180) / 2) + LCENavigationView { + content + } + .setLeftButton(image: AnyView(AppBackButtonIcon())) { root = .main } + .setTitle(text: Text("Acesse sua conta").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary)) + .setNavigationBarBackgroundColor(AppColors.backgroundLight) + .buttonStyle(.plain) + // The app ships no dark theme; force light so system dark mode + // can't invert the nav bar / title against the fixed DS colors. + .preferredColorScheme(.light) + } - ZStack(alignment: .top) { - (colorScheme == .dark ? Color.black : AppColors.backgroundLight) - .ignoresSafeArea() + private var content: some View { + VStack(spacing: 20) { + Spacer() - pinHeroImage - .scaledToFill() - .frame(height: heroHeight + 80) - .offset(y: heroVisible ? -60 : -(heroHeight + 220)) - .mask( - LinearGradient( - colors: [.black, .black, .black.opacity(0.0)], - startPoint: .top, - endPoint: .bottom - ) - ) + logoImage + .scaledToFit() + .frame(height: 160) - VStack(spacing: 18) { - Spacer().frame(height: logoTopInset) + Text("Sua vontade, no seu tempo.\nTudo o que você precisa, em um toque.") + .font(AppTypography.heading25) + .foregroundStyle(AppColors.textPrimary) + .multilineTextAlignment(.center) + .padding(.horizontal, 28) - logoImage - .scaledToFit() - .frame(height: 180) + Spacer() - Text("Sua vontade, no seu tempo.\nTudo o que você precisa, em um toque.") - .font(AppTypography.heading25) - .foregroundStyle(colorScheme == .dark ? AppColors.textInverse : AppColors.textPrimary) - .multilineTextAlignment(.leading) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 28) - .opacity(textVisible ? 1.0 : 0.0) - .offset(y: textVisible ? 0 : 24) + VStack(spacing: 12) { + PrimaryButton(title: "Criar conta", image: Image(systemName: "arrow.right")) { + navigate(.registration) + } + .tint(AppColors.tertiary) - Spacer().frame(height: 40) - - Button { - navigate(.loginEmail) - } label: { - PrimaryButtonLabel(title: "ENTRAR") - } - .padding(.horizontal, 28) - .tint(AppColors.tertiary) - .offset(y: buttonVisible ? 0 : 140) - .opacity(buttonVisible ? 1.0 : 0.0) - .buttonStyle(.plain) - - HStack(spacing: 6) { - Text("Não tem conta ainda?") - .foregroundStyle(colorScheme == .dark ? AppColors.textInverse.opacity(0.9) : AppColors.textPrimary) - Button("Criar conta") { - navigate(.registration) - } - .buttonStyle(.plain) - .foregroundStyle(AppColors.primary) - } - .font(AppTypography.body) - .opacity(textVisible ? 1.0 : 0.0) - .offset(y: textVisible ? 0 : 24) - - Spacer().frame(height: 12) + SecondaryButton(title: "Entrar") { + navigate(.loginEmail) } } - .ignoresSafeArea() - .onAppear { - if shouldPrepareEntryAnimation { - applyHiddenStateWithoutAnimation() - } else { - showFinalStateWithoutAnimation() - } - } - .task(id: authEntryAnimationToken) { - await runEntryAnimationIfNeeded(for: authEntryAnimationToken) - } + .padding(.horizontal, 28) + + legalLinks + .padding(.top, 4) + .padding(.bottom, 24) } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(AppColors.backgroundLight.ignoresSafeArea()) } - @MainActor - private func applyHiddenStateWithoutAnimation() { - heroVisible = false - textVisible = false - buttonVisible = false - } - - @MainActor - private func showFinalStateWithoutAnimation() { - heroVisible = true - textVisible = true - buttonVisible = true - } - - @MainActor - private func runEntryAnimationIfNeeded(for token: Int) async { - guard token > 0 else { return } - guard token != lastAnimatedToken else { return } - lastAnimatedToken = token - - applyHiddenStateWithoutAnimation() - try? await Task.sleep(nanoseconds: 40_000_000) - - withAnimation(.spring(response: 0.64, dampingFraction: 0.9)) { - heroVisible = true - } - try? await Task.sleep(nanoseconds: 160_000_000) - withAnimation(.easeOut(duration: 0.42)) { - textVisible = true - } - try? await Task.sleep(nanoseconds: 150_000_000) - withAnimation(.spring(response: 0.52, dampingFraction: 0.86)) { - buttonVisible = true + private var legalLinks: some View { + HStack(spacing: 6) { + Button("Termos de Uso") { navigate(.terms) } + .buttonStyle(.plain) + Text("·") + Button("Política de Privacidade") { navigate(.policy) } + .buttonStyle(.plain) } + .font(.system(size: 14, weight: .medium)) + .foregroundStyle(AppColors.textMuted) } } + +#Preview { + LoginView(root: .constant(.auth), navigate: { _ in }) +} diff --git a/PediFoodsUITests/ProfileLoggedOutFlowTests.swift b/PediFoodsUITests/ProfileLoggedOutFlowTests.swift index 0f4f85d..2445888 100644 --- a/PediFoodsUITests/ProfileLoggedOutFlowTests.swift +++ b/PediFoodsUITests/ProfileLoggedOutFlowTests.swift @@ -1,10 +1,7 @@ import XCTest -/// Covers the Profile tab's logged-out state (`ProfileLoggedOutView`) - -/// confirmed reachable. Actually logging in is blocked by a separate -/// navigation issue in LoginView's entry-reveal animation, documented in -/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md - not -/// covered here. +/// Covers the Profile tab's logged-out state (`ProfileLoggedOutView`) and +/// the auth intro screen (`LoginView`) it opens. final class ProfileLoggedOutFlowTests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false @@ -20,18 +17,27 @@ final class ProfileLoggedOutFlowTests: XCTestCase { XCTAssertTrue(app.buttons["Entrar ou Cadastrar"].isHittable) } - func testTappingLoginButtonDoesNotCrash() throws { + /// The auth intro screen must offer both entry points plus the legal + /// links, and — the reason App Review rejected the build in 2026-08 — + /// a working way back out (its `LCENavigationView` back button, which + /// sets `root = .main`). + func testAuthIntroScreenShowsChoicesAndCanGoBack() throws { let app = XCUIApplication() app.launch() XCTAssertTrue(app.reachLoggedOutProfile()) app.buttons["Entrar ou Cadastrar"].tap() - sleep(2) - // Known limitation (see decisions doc): the login screen's entry - // animation doesn't reliably reveal when navigating here from an - // already-running guest session, so this only asserts the app is - // still alive and responsive, not that login itself completes. - XCTAssertEqual(app.state, .runningForeground) + XCTAssertTrue(app.buttons["Criar conta"].waitForExistence(timeout: 5), "Auth intro missing 'Criar conta'") + XCTAssertTrue(app.buttons["Entrar"].exists, "Auth intro missing 'Entrar'") + XCTAssertTrue(app.buttons["Termos de Uso"].exists, "Auth intro missing 'Termos de Uso' link") + XCTAssertTrue(app.buttons["Política de Privacidade"].exists, "Auth intro missing 'Política de Privacidade' link") + + let backButton = app.buttons.matching(NSPredicate(format: "label == %@", "Back")).firstMatch + XCTAssertTrue(backButton.exists, "Auth intro has no back button — user is trapped") + backButton.tap() + + // 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") } } diff --git a/PediFoodsUITests/UITestSupport.swift b/PediFoodsUITests/UITestSupport.swift index 52ff997..b4db582 100644 --- a/PediFoodsUITests/UITestSupport.swift +++ b/PediFoodsUITests/UITestSupport.swift @@ -147,7 +147,7 @@ extension XCUIApplication { buttons["Entrar ou Cadastrar"].tap() - let entrarButton = buttons["ENTRAR"] + let entrarButton = buttons["Entrar"] guard entrarButton.waitForExistence(timeout: 5) else { return false } entrarButton.tap() From 21250ec2b9b26a3034e2d09c3fbdcb9fcff5650b Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Thu, 27 Aug 2026 10:38:28 -0300 Subject: [PATCH 2/5] [signing] Restore LC provisioning profiles and sync project.yml I reverted project.pbxproj earlier to drop Xcode's objectVersion churn and wiped the manual signing config with it. Restored. - project.pbxproj: back to the LC Prov profile set (Dev/Dist for the app, Dev/Dist Push for NotificationService), CODE_SIGN_STYLE = Manual on all targets - byte-identical to fix/signing/notification-extension-distribution. - project.yml: was still pointing at the old 'PediFoods Dev/Dist Prov Prof' names and CODE_SIGN_STYLE Automatic for the extension. Synced to the same LC Prov set so 'xcodegen generate' reproduces the signing instead of reverting it. Verified: regenerating from this project.yml yields the same CODE_SIGN_* / PROVISIONING_PROFILE_SPECIFIER settings. --- PediFoods.xcodeproj/project.pbxproj | 158 ++++++++++++++++------------ project.yml | 47 +++++++-- 2 files changed, 128 insertions(+), 77 deletions(-) diff --git a/PediFoods.xcodeproj/project.pbxproj b/PediFoods.xcodeproj/project.pbxproj index 76b25cd..c8ecfd9 100644 --- a/PediFoods.xcodeproj/project.pbxproj +++ b/PediFoods.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -79,7 +79,7 @@ 7CC171B83BC4CDEC38B25F8C /* SnackbarCenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CE116C585A7529CF309B3D /* SnackbarCenterTests.swift */; }; 7D7BF04765D6CA6AC4F11DDD /* StoreCatalogNormalizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EC38EEE1E7105BC21E354C /* StoreCatalogNormalizerTests.swift */; }; 80B476CCE2B90BEE2FA4CB37 /* PediFoodsUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E3E6C1834786CA3621B7BB /* PediFoodsUITestsLaunchTests.swift */; }; - 811217F88AA350A70CC22479 /* NotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 811217F88AA350A70CC22479 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 85EA52253BABA327EC07CFF0 /* AuthFormattersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 561E00C486D60EC2DF720EAF /* AuthFormattersTests.swift */; }; 8D6622AE9325D02B830BD115 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6CAA9B253B1A6C8815A0E69D /* LaunchScreen.storyboard */; }; 90ADA376585F5438F3A1A1D6 /* HomeView+Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1517B141263438E466452037 /* HomeView+Favorites.swift */; }; @@ -114,7 +114,6 @@ CCE23FD7706B34BDE544E05A /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC381D02A37312BE360024F /* Buttons.swift */; }; CD96980F3D7B603126FDE6AC /* URLProtocolStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51798A414469C911D44F0F6 /* URLProtocolStub.swift */; }; CEA08090E08B5E39FD0DD5D6 /* CachedRemoteImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */; }; - 38452FD60CBDE248531DA484 /* AppBackButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EEF6262A813BB8274266C8 /* AppBackButtonIcon.swift */; }; D2FB3C624B3C27E59BCA997A /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA34EE194C71755B3C82F30 /* MainTabView.swift */; }; D3EC6E0A68B809F7A3370B1B /* ProductDetailSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553A05B9EAD0B9D0D9F1B369 /* ProductDetailSheet.swift */; }; D48148B155BA2F76C41C0787 /* HomeFiltersStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FBD7D809ACE1840165A9DCC /* HomeFiltersStateTests.swift */; }; @@ -128,6 +127,7 @@ E4D492E4B651A8FCC5016B43 /* PizzaFlavorAddonsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ECD3AA9543133BE623668D /* PizzaFlavorAddonsSheet.swift */; }; E7817C56D8F083A231E11E4E /* LocationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB9F980426D39E93E091051 /* LocationService.swift */; }; EBBF7EFFDC9286D4F64544F4 /* PublicLocationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85F176EA2B204CC95072D28 /* PublicLocationModels.swift */; }; + EC9F35551075A78EF7869784 /* AppBackButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3056B162576522D94702AE6E /* AppBackButtonIcon.swift */; }; ECFEB3B3779C4F267EC148A4 /* SnackbarCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226D1B7C049B405436DD36B4 /* SnackbarCenter.swift */; }; F03F08F65DFAD9166884EAD4 /* StoreCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31916C1A4005396ADA5FF3BC /* StoreCard.swift */; }; F280C0AC9902B13E2224C790 /* OrdersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF413C3924669E761BF455B8 /* OrdersView.swift */; }; @@ -168,7 +168,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( - 811217F88AA350A70CC22479 /* NotificationServiceExtension.appex in Embed Foundation Extensions */, + 811217F88AA350A70CC22479 /* NotificationService.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -199,6 +199,7 @@ 2EA34EE194C71755B3C82F30 /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 2F4ED1B7B71C905115380DC0 /* FeatureControlService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureControlService.swift; sourceTree = ""; }; 30024AC97E79912E1358DDFB /* FiltersModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersModalView.swift; sourceTree = ""; }; + 3056B162576522D94702AE6E /* AppBackButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBackButtonIcon.swift; sourceTree = ""; }; 30B3B6E69309F3EDF28E81CC /* LegalViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalViews.swift; sourceTree = ""; }; 31916C1A4005396ADA5FF3BC /* StoreCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreCard.swift; sourceTree = ""; }; 35400BEE3D09C61B2058579F /* AppContentCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentCacheTests.swift; sourceTree = ""; }; @@ -227,7 +228,7 @@ 69A9FB3DE679C3D4C60E7E42 /* ApiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiConfig.swift; sourceTree = ""; }; 6BF467DF1447525F04B242B0 /* ReviewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewsView.swift; sourceTree = ""; }; 6CAA9B253B1A6C8815A0E69D /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = PediFoodsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PediFoodsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6EC381D02A37312BE360024F /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = ""; }; 6FC454261845C1EDF970552A /* CheckoutTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutTypes.swift; sourceTree = ""; }; 7034E89267D9F5D86BF1E424 /* CartCheckoutFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartCheckoutFlowTests.swift; sourceTree = ""; }; @@ -240,7 +241,7 @@ 76B24E69A3F6A3EB86449870 /* AddAddressFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAddressFormView.swift; sourceTree = ""; }; 794249487F718BCFBF176E8D /* ApiModelsDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiModelsDecodingTests.swift; sourceTree = ""; }; 79D2854B8062D96742CFDD99 /* HomeViewComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewComponents.swift; sourceTree = ""; }; - 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7A9F14B1BDD7747C546DFAF0 /* GuestSessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestSessionService.swift; sourceTree = ""; }; 7C13751F450A42A2BDB0783D /* ProfileLoggedOutFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileLoggedOutFlowTests.swift; sourceTree = ""; }; 7CD06F9A7AF9365D1FBE35D0 /* AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressComponents.swift; sourceTree = ""; }; @@ -249,7 +250,7 @@ 89B662E8B7E1B8F1CF7E657E /* KeychainStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStore.swift; sourceTree = ""; }; 8A3FB85A2A2E2952B9A2BAB0 /* DeepLinkDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkDestination.swift; sourceTree = ""; }; 8AA914FB8B32B106911FD67B /* LegalDocumentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalDocumentTests.swift; sourceTree = ""; }; - 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = PediFoodsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PediFoodsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8D2C6A0E85E133F947293181 /* OrderDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsView.swift; sourceTree = ""; }; 8DD2B73D89E6D33643EF58F5 /* DeepLinkRouteEffectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkRouteEffectTests.swift; sourceTree = ""; }; 8E3A0A2E253ABFCA35D02F28 /* TextFieldCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldCompat.swift; sourceTree = ""; }; @@ -284,7 +285,7 @@ C44662BAA20AD862BE1DBC40 /* PublicLocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicLocationService.swift; sourceTree = ""; }; C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnackbarOverlay.swift; sourceTree = ""; }; C6B08901A67C8D90A3B50882 /* ApiCustomerPayloadModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCustomerPayloadModelsTests.swift; sourceTree = ""; }; - C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; CA44F5384D21B7F718F95522 /* StoreDetailSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreDetailSupport.swift; sourceTree = ""; }; CAB9F980426D39E93E091051 /* LocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = ""; }; CD5EE81AE4347B4086684F8B /* CartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartView.swift; sourceTree = ""; }; @@ -300,7 +301,6 @@ EB95D6804877D2ED244811D0 /* Inputs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inputs.swift; sourceTree = ""; }; ECBFCE20C54A79DEA8F4512B /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRemoteImage.swift; sourceTree = ""; }; - 52EEF6262A813BB8274266C8 /* AppBackButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBackButtonIcon.swift; sourceTree = ""; }; F0B079DFAB606DCA87A536C9 /* LaunchSplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchSplashView.swift; sourceTree = ""; }; F18F768F7A2A47111A85FE88 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; F2B2C5BBDFF4473C472F45B4 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; @@ -344,7 +344,7 @@ 126A579C448C784867DC3AB1 /* Products */ = { isa = PBXGroup; children = ( - C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */, + C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */, 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */, 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */, 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */, @@ -558,9 +558,9 @@ D7552D3AF029ADAC7DE5A76B /* Components */ = { isa = PBXGroup; children = ( + 3056B162576522D94702AE6E /* AppBackButtonIcon.swift */, 6EC381D02A37312BE360024F /* Buttons.swift */, EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */, - 52EEF6262A813BB8274266C8 /* AppBackButtonIcon.swift */, EB95D6804877D2ED244811D0 /* Inputs.swift */, C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */, 31916C1A4005396ADA5FF3BC /* StoreCard.swift */, @@ -621,7 +621,7 @@ ); name = NotificationServiceExtension; productName = NotificationServiceExtension; - productReference = C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */; + productReference = C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */; productType = "com.apple.product-type.app-extension"; }; 66486D25E3A8A75EA8FACE44 /* PediFoodsUITests */ = { @@ -671,15 +671,10 @@ BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; TargetAttributes = { - 33EB2407F0F758EB98A769D3 = { - DevelopmentTeam = K4E5BZMM4V; - ProvisioningStyle = Automatic; - }; 66486D25E3A8A75EA8FACE44 = { TestTargetID = B3B5CFF77A8ED019D1299ABB; }; B3B5CFF77A8ED019D1299ABB = { - ProvisioningStyle = Manual; SystemCapabilities = { com.apple.Push = { enabled = YES; @@ -775,6 +770,7 @@ 42FD703A96353119FAB2F711 /* ApiPizzaModels.swift in Sources */, 691351CFE207B9F34361AEB6 /* ApiReviewModels.swift in Sources */, 279A489F01434E0523EC1BAA /* ApiService.swift in Sources */, + EC9F35551075A78EF7869784 /* AppBackButtonIcon.swift in Sources */, B239BDCEB9F8934F1C71258C /* AppCache.swift in Sources */, 9EDDCECCAEEB8FA4909DA9D4 /* AppCoordinator.swift in Sources */, 9F09502CF9331F0C94BFA5F0 /* AppState.swift in Sources */, @@ -782,7 +778,6 @@ 3689239282AADF06783CBBEA /* AuthFormatters.swift in Sources */, CCE23FD7706B34BDE544E05A /* Buttons.swift in Sources */, CEA08090E08B5E39FD0DD5D6 /* CachedRemoteImage.swift in Sources */, - 38452FD60CBDE248531DA484 /* AppBackButtonIcon.swift in Sources */, DE499D6150C15EB046AF7963 /* CartView.swift in Sources */, 70EA850C067AB6C6B87539E2 /* CheckoutTypes.swift in Sources */, 41BE548D929D3814A736FA12 /* CheckoutView+Logic.swift in Sources */, @@ -968,6 +963,11 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -975,6 +975,7 @@ "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.PediFoodsTests; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; SWIFT_VERSION = 6; TARGETED_DEVICE_FAMILY = "1,2"; @@ -990,54 +991,10 @@ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = PediFoods/PediFoods.entitlements; - CODE_SIGN_IDENTITY = "Apple Distribution"; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = K4E5BZMM4V; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = PediFoods/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; - INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods usa sua localização para mostrar lojas perto de você"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = NO; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; - INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 0.0.1; - PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app; - PRODUCT_NAME = PediFoods; - PROVISIONING_PROFILE_SPECIFIER = "com.br.pedifoods.app AppStore"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "PediFoods Dist XC Prov Prof"; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; - SUPPORTS_MACCATALYST = NO; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 6; - TARGETED_DEVICE_FAMILY = 1; - }; - name = Release; - }; - 4424276379B7A6A98892CFBC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - APP_ATTEST_ENVIRONMENT = development; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = PediFoods/PediFoods.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2026.0.25.0.0.1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; ENABLE_PREVIEWS = YES; @@ -1061,7 +1018,52 @@ PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app; PRODUCT_NAME = PediFoods; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "PediFoods Dev Prov Prof"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "LC Prov PediFoods Dist Profile"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; + 4424276379B7A6A98892CFBC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APP_ATTEST_ENVIRONMENT = development; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = PediFoods/PediFoods.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 2026.0.25.0.0.1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PediFoods/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods usa sua localização para mostrar lojas perto de você"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = NO; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app; + PRODUCT_NAME = PediFoods; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "LC Prov PediFoods Dev Profile"; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -1075,6 +1077,12 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; + "DEVELOPMENT_TEAM[sdk=macosx*]" = K4E5BZMM4V; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1082,6 +1090,7 @@ "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.PediFoodsTests; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; SWIFT_VERSION = 6; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1093,9 +1102,11 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = K4E5BZMM4V; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1105,6 +1116,8 @@ MARKETING_VERSION = 0.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app.NotificationService; PRODUCT_NAME = NotificationService; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "LC Prov PediFoods Dev Push Profile"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_VERSION = 6; @@ -1116,6 +1129,7 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = K4E5BZMM4V; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1190,6 +1204,7 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = K4E5BZMM4V; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1208,9 +1223,12 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = K4E5BZMM4V; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; INFOPLIST_FILE = NotificationServiceExtension/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1220,6 +1238,8 @@ MARKETING_VERSION = 0.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app.NotificationService; PRODUCT_NAME = NotificationService; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "LC Prov PediFoods Dist Push Profile"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_VERSION = 6; diff --git a/project.yml b/project.yml index 7aecccb..63e3eb5 100644 --- a/project.yml +++ b/project.yml @@ -52,17 +52,19 @@ targets: INFOPLIST_KEY_UIStatusBarStyle: UIStatusBarStyleDefault configs: Debug: + CODE_SIGN_IDENTITY: "Apple Development" + "CODE_SIGN_IDENTITY[sdk=iphoneos*]": "iPhone Developer" DEVELOPMENT_TEAM: "" "DEVELOPMENT_TEAM[sdk=iphoneos*]": K4E5BZMM4V PROVISIONING_PROFILE_SPECIFIER: "" - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]": "PediFoods Dev Prov Prof" + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]": "LC Prov PediFoods Dev Profile" Release: - DEVELOPMENT_TEAM: K4E5BZMM4V - "DEVELOPMENT_TEAM[sdk=iphoneos*]": K4E5BZMM4V - CODE_SIGN_IDENTITY: "Apple Distribution" + CODE_SIGN_IDENTITY: "Apple Development" "CODE_SIGN_IDENTITY[sdk=iphoneos*]": "iPhone Distribution" - PROVISIONING_PROFILE_SPECIFIER: "com.br.pedifoods.app AppStore" - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]": "PediFoods Dist XC Prov Prof" + DEVELOPMENT_TEAM: "" + "DEVELOPMENT_TEAM[sdk=iphoneos*]": K4E5BZMM4V + PROVISIONING_PROFILE_SPECIFIER: "" + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]": "LC Prov PediFoods Dist Profile" attributes: SystemCapabilities: com.apple.Push: @@ -80,12 +82,25 @@ targets: MARKETING_VERSION: 0.0.1 CURRENT_PROJECT_VERSION: 1 INFOPLIST_FILE: NotificationServiceExtension/Info.plist - CODE_SIGN_STYLE: Automatic - DEVELOPMENT_TEAM: K4E5BZMM4V + CODE_SIGN_STYLE: Manual SWIFT_VERSION: "6" TARGETED_DEVICE_FAMILY: "1" SKIP_INSTALL: YES CLANG_ENABLE_OBJC_WEAK: NO + configs: + Debug: + "CODE_SIGN_IDENTITY[sdk=iphoneos*]": "iPhone Developer" + DEVELOPMENT_TEAM: "" + "DEVELOPMENT_TEAM[sdk=iphoneos*]": K4E5BZMM4V + PROVISIONING_PROFILE_SPECIFIER: "" + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]": "LC Prov PediFoods Dev Push Profile" + Release: + CODE_SIGN_IDENTITY: "Apple Development" + "CODE_SIGN_IDENTITY[sdk=iphoneos*]": "iPhone Distribution" + DEVELOPMENT_TEAM: "" + "DEVELOPMENT_TEAM[sdk=iphoneos*]": K4E5BZMM4V + PROVISIONING_PROFILE_SPECIFIER: "" + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]": "LC Prov PediFoods Dist Push Profile" PediFoodsTests: type: bundle.unit-test @@ -98,6 +113,21 @@ targets: base: SWIFT_VERSION: "6" GENERATE_INFOPLIST_FILE: YES + CODE_SIGN_STYLE: Manual + configs: + Debug: + "CODE_SIGN_IDENTITY[sdk=iphoneos*]": "iPhone Developer" + "CODE_SIGN_IDENTITY[sdk=macosx*]": "Apple Development" + DEVELOPMENT_TEAM: "" + "DEVELOPMENT_TEAM[sdk=iphoneos*]": K4E5BZMM4V + "DEVELOPMENT_TEAM[sdk=macosx*]": K4E5BZMM4V + PROVISIONING_PROFILE_SPECIFIER: "" + Release: + "CODE_SIGN_IDENTITY[sdk=iphoneos*]": "Apple Distribution" + "CODE_SIGN_IDENTITY[sdk=macosx*]": "-" + DEVELOPMENT_TEAM: "" + "DEVELOPMENT_TEAM[sdk=iphoneos*]": K4E5BZMM4V + PROVISIONING_PROFILE_SPECIFIER: "" PediFoodsUITests: type: bundle.ui-testing @@ -110,6 +140,7 @@ targets: base: SWIFT_VERSION: "6" GENERATE_INFOPLIST_FILE: YES + DEVELOPMENT_TEAM: K4E5BZMM4V schemes: PediFoods: From 0f0672f18dd8e4c4cda4d5d131de93df658036a2 Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Thu, 27 Aug 2026 10:59:06 -0300 Subject: [PATCH 3/5] [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") + } } From a0e4375914cfe8a901a5aa0b41ffc113dcedc105 Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Thu, 27 Aug 2026 16:33:01 -0300 Subject: [PATCH 4/5] [public-store-distance] Consume the public distance contract, stop labelling it as unavailable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guest browsing showed 'Distância indisponível' on every store card and a '--' tile on store detail, because the public locator response carries no distance and StoreSummary.init(publicItem:) hardcoded nil. Backend contract (docs/plans/public-store-distance-consumer.md): distance is a number, never null, 0 means unavailable. Implemented app-side ahead of the backend - every change is forward-compatible, and the part that removes the broken label works with no backend at all. - PublicStoreListItem / PublicStoreDetail: + distance (optional, so today's responses without the field still decode). - StoreSummary.init(publicItem:): normalize the 0 sentinel to nil, so one representation of 'unknown' reaches the label and the max-distance filter. - fetchStoreDetail: send state/city from GuestLocationStore by default - the server needs them to resolve the city centroid. - formatDistance: empty string for nil/0/negative. It previously returned 'Distância indisponível' for nil, and - found by the new test - '0 m' for 0 and '-1000 m' for a negative. - StoreCard: drop the distance segment and its '·' separator together, otherwise the row ended in a dangling separator. - StoreDetailView: drop the DISTÂNCIA tile and its divider instead of '--'. Also fixes the same label in the authenticated flow when the user declined location and has no address coordinates. Tests: PublicStoreDistanceTests (decode with/without the field, passthrough, 0 normalization) + the empty case in HomeViewFilteringTests. --- PediFoods.xcodeproj/project.pbxproj | 43 ++++++++++------ PediFoods/Components/StoreCard.swift | 17 ++++--- PediFoods/Services/PublicLocationModels.swift | 9 ++++ .../Services/PublicLocationService.swift | 20 +++++++- PediFoods/Views/Main/HomeView+Data.swift | 7 ++- PediFoods/Views/Main/HomeView+Filtering.swift | 7 ++- .../Main/StoreDetailView+Components.swift | 9 +++- .../Views/Main/StoreDetailView+Logic.swift | 12 +++-- PediFoodsTests/HomeViewFilteringTests.swift | 14 +++++- PediFoodsTests/PublicStoreDistanceTests.swift | 49 +++++++++++++++++++ 10 files changed, 154 insertions(+), 33 deletions(-) create mode 100644 PediFoodsTests/PublicStoreDistanceTests.swift diff --git a/PediFoods.xcodeproj/project.pbxproj b/PediFoods.xcodeproj/project.pbxproj index c8ecfd9..60e9941 100644 --- a/PediFoods.xcodeproj/project.pbxproj +++ b/PediFoods.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -79,9 +79,10 @@ 7CC171B83BC4CDEC38B25F8C /* SnackbarCenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CE116C585A7529CF309B3D /* SnackbarCenterTests.swift */; }; 7D7BF04765D6CA6AC4F11DDD /* StoreCatalogNormalizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EC38EEE1E7105BC21E354C /* StoreCatalogNormalizerTests.swift */; }; 80B476CCE2B90BEE2FA4CB37 /* PediFoodsUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E3E6C1834786CA3621B7BB /* PediFoodsUITestsLaunchTests.swift */; }; - 811217F88AA350A70CC22479 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 811217F88AA350A70CC22479 /* NotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 85EA52253BABA327EC07CFF0 /* AuthFormattersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 561E00C486D60EC2DF720EAF /* AuthFormattersTests.swift */; }; 8D6622AE9325D02B830BD115 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6CAA9B253B1A6C8815A0E69D /* LaunchScreen.storyboard */; }; + 901A6A5A00C18FC59F764B85 /* PublicStoreDistanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E55893DD92CBFB24F212454 /* PublicStoreDistanceTests.swift */; }; 90ADA376585F5438F3A1A1D6 /* HomeView+Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1517B141263438E466452037 /* HomeView+Favorites.swift */; }; 918BCBC1EAD7EE134DB9742D /* CheckoutTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FF15CEE5B137074E6B2489 /* CheckoutTypesTests.swift */; }; 926E0C4DD3C82485D4262A62 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B2F3558C58CD1E71F0F3A0 /* ContentView.swift */; }; @@ -168,7 +169,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( - 811217F88AA350A70CC22479 /* NotificationService.appex in Embed Foundation Extensions */, + 811217F88AA350A70CC22479 /* NotificationServiceExtension.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -210,6 +211,7 @@ 4C415019CC1F314EE7318864 /* ApiCardModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCardModelsTests.swift; sourceTree = ""; }; 4DC5416AEBA77C43CF55ABA8 /* PublicLocationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicLocationPickerView.swift; sourceTree = ""; }; 4E0CCEF22D356F9EC723999D /* HomeView+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeView+Data.swift"; sourceTree = ""; }; + 4E55893DD92CBFB24F212454 /* PublicStoreDistanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicStoreDistanceTests.swift; sourceTree = ""; }; 507E868458359BE0E4FF25F1 /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = ""; }; 52BFF93F21509F1AD4F6676B /* PediFoodsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PediFoodsApp.swift; sourceTree = ""; }; 54795EDED987426386226358 /* GuestLocationStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestLocationStoreTests.swift; sourceTree = ""; }; @@ -228,7 +230,7 @@ 69A9FB3DE679C3D4C60E7E42 /* ApiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiConfig.swift; sourceTree = ""; }; 6BF467DF1447525F04B242B0 /* ReviewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewsView.swift; sourceTree = ""; }; 6CAA9B253B1A6C8815A0E69D /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PediFoodsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = PediFoodsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6EC381D02A37312BE360024F /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = ""; }; 6FC454261845C1EDF970552A /* CheckoutTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutTypes.swift; sourceTree = ""; }; 7034E89267D9F5D86BF1E424 /* CartCheckoutFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartCheckoutFlowTests.swift; sourceTree = ""; }; @@ -241,7 +243,7 @@ 76B24E69A3F6A3EB86449870 /* AddAddressFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAddressFormView.swift; sourceTree = ""; }; 794249487F718BCFBF176E8D /* ApiModelsDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiModelsDecodingTests.swift; sourceTree = ""; }; 79D2854B8062D96742CFDD99 /* HomeViewComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewComponents.swift; sourceTree = ""; }; - 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7A9F14B1BDD7747C546DFAF0 /* GuestSessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestSessionService.swift; sourceTree = ""; }; 7C13751F450A42A2BDB0783D /* ProfileLoggedOutFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileLoggedOutFlowTests.swift; sourceTree = ""; }; 7CD06F9A7AF9365D1FBE35D0 /* AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressComponents.swift; sourceTree = ""; }; @@ -250,7 +252,7 @@ 89B662E8B7E1B8F1CF7E657E /* KeychainStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStore.swift; sourceTree = ""; }; 8A3FB85A2A2E2952B9A2BAB0 /* DeepLinkDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkDestination.swift; sourceTree = ""; }; 8AA914FB8B32B106911FD67B /* LegalDocumentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalDocumentTests.swift; sourceTree = ""; }; - 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PediFoodsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = PediFoodsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8D2C6A0E85E133F947293181 /* OrderDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsView.swift; sourceTree = ""; }; 8DD2B73D89E6D33643EF58F5 /* DeepLinkRouteEffectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkRouteEffectTests.swift; sourceTree = ""; }; 8E3A0A2E253ABFCA35D02F28 /* TextFieldCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldCompat.swift; sourceTree = ""; }; @@ -285,7 +287,7 @@ C44662BAA20AD862BE1DBC40 /* PublicLocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicLocationService.swift; sourceTree = ""; }; C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnackbarOverlay.swift; sourceTree = ""; }; C6B08901A67C8D90A3B50882 /* ApiCustomerPayloadModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCustomerPayloadModelsTests.swift; sourceTree = ""; }; - C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; CA44F5384D21B7F718F95522 /* StoreDetailSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreDetailSupport.swift; sourceTree = ""; }; CAB9F980426D39E93E091051 /* LocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = ""; }; CD5EE81AE4347B4086684F8B /* CartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartView.swift; sourceTree = ""; }; @@ -344,7 +346,7 @@ 126A579C448C784867DC3AB1 /* Products */ = { isa = PBXGroup; children = ( - C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */, + C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */, 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */, 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */, 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */, @@ -446,6 +448,7 @@ 23B6B814027518969EB04341 /* HomeViewFilteringTests.swift */, 5A82497C63D507E82FE02693 /* ImageSourceResolverTests.swift */, 8AA914FB8B32B106911FD67B /* LegalDocumentTests.swift */, + 4E55893DD92CBFB24F212454 /* PublicStoreDistanceTests.swift */, 747AD255B6D881E011FDF907 /* SessionStateStoreTests.swift */, 160571AB2F8D4DF98C0BE16F /* SmokeTests.swift */, B2CE116C585A7529CF309B3D /* SnackbarCenterTests.swift */, @@ -621,7 +624,7 @@ ); name = NotificationServiceExtension; productName = NotificationServiceExtension; - productReference = C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */; + productReference = C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */; productType = "com.apple.product-type.app-extension"; }; 66486D25E3A8A75EA8FACE44 /* PediFoodsUITests */ = { @@ -671,10 +674,21 @@ BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; TargetAttributes = { + 3027BAE353B414EC70E2A2AC = { + DevelopmentTeam = ""; + ProvisioningStyle = Manual; + }; + 33EB2407F0F758EB98A769D3 = { + DevelopmentTeam = ""; + ProvisioningStyle = Manual; + }; 66486D25E3A8A75EA8FACE44 = { + DevelopmentTeam = K4E5BZMM4V; TestTargetID = B3B5CFF77A8ED019D1299ABB; }; B3B5CFF77A8ED019D1299ABB = { + DevelopmentTeam = ""; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.Push = { enabled = YES; @@ -867,6 +881,7 @@ 63AB40542AABCC3CB2390B87 /* HomeViewFilteringTests.swift in Sources */, 22E3BF78B8C2B22D14EABE8B /* ImageSourceResolverTests.swift in Sources */, 2FCB3403A764AD74AC3BD47C /* LegalDocumentTests.swift in Sources */, + 901A6A5A00C18FC59F764B85 /* PublicStoreDistanceTests.swift in Sources */, F760557D906B860D0EA277DE /* SessionStateStoreTests.swift in Sources */, B24B898508C8A8EF4191EC89 /* SmokeTests.swift in Sources */, 7CC171B83BC4CDEC38B25F8C /* SnackbarCenterTests.swift in Sources */, @@ -986,7 +1001,6 @@ 388EFAF707AD295240B9E951 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - APP_ATTEST_ENVIRONMENT = production; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -994,7 +1008,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026.0.25.0.0.1; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; ENABLE_PREVIEWS = YES; @@ -1002,7 +1016,7 @@ INFOPLIST_FILE = PediFoods/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods usa sua localização para mostrar lojas perto de você"; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods uses your location to show nearby stores and estimate delivery times."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = NO; @@ -1031,7 +1045,6 @@ 4424276379B7A6A98892CFBC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - APP_ATTEST_ENVIRONMENT = development; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1039,7 +1052,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2026.0.25.0.0.1; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; ENABLE_PREVIEWS = YES; @@ -1047,7 +1060,7 @@ INFOPLIST_FILE = PediFoods/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods usa sua localização para mostrar lojas perto de você"; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods uses your location to show nearby stores and estimate delivery times."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = NO; diff --git a/PediFoods/Components/StoreCard.swift b/PediFoods/Components/StoreCard.swift index df66098..5c24c9c 100644 --- a/PediFoods/Components/StoreCard.swift +++ b/PediFoods/Components/StoreCard.swift @@ -53,12 +53,17 @@ struct FeaturedStoreCard: View { Text("(\(store.reviews))") .font(.caption) .foregroundStyle(AppColors.textMuted) - Text("·") - .font(.caption) - .foregroundStyle(AppColors.textMuted) - Text(store.distance) - .font(.caption) - .foregroundStyle(AppColors.textMuted) + // Empty distance means "unavailable" - drop the separator + // with it, otherwise the row ends in a dangling "·". + // See docs/plans/public-store-distance-consumer.md. + if store.distance.isEmpty == false { + Text("·") + .font(.caption) + .foregroundStyle(AppColors.textMuted) + Text(store.distance) + .font(.caption) + .foregroundStyle(AppColors.textMuted) + } } } else { Text(store.statusLabel?.isEmpty == false ? (store.statusLabel ?? "Fechado") : "Fechado") diff --git a/PediFoods/Services/PublicLocationModels.swift b/PediFoods/Services/PublicLocationModels.swift index 516c2a2..f563acb 100644 --- a/PediFoods/Services/PublicLocationModels.swift +++ b/PediFoods/Services/PublicLocationModels.swift @@ -28,6 +28,12 @@ struct PublicStoreListItem: Decodable, Sendable, Identifiable { let deliveryTime: String? let deliveryFee: Double? let minOrder: Double? + /// Kilometres from the picked city's centroid to the store - always + /// approximate, since the origin is the city rather than the visitor. + /// The server sends `0` for "unavailable" and never null; optional here + /// only so the app keeps decoding responses from before the field + /// shipped. See docs/plans/public-store-distance-consumer.md. + let distance: Double? } struct PublicStoreDetail: Decodable, Sendable { @@ -56,6 +62,9 @@ struct PublicStoreDetail: Decodable, Sendable { let deliveryPrice: Double? let minOrder: Double? let acceptPix: Bool? + /// Same contract as `PublicStoreListItem.distance`, but only populated + /// when the detail request carries `state`/`city`. + let distance: Double? } // Maps the public (anonymous) store-detail projection onto the same models diff --git a/PediFoods/Services/PublicLocationService.swift b/PediFoods/Services/PublicLocationService.swift index af0eade..e5da6c5 100644 --- a/PediFoods/Services/PublicLocationService.swift +++ b/PediFoods/Services/PublicLocationService.swift @@ -34,13 +34,31 @@ final class PublicLocationService: @unchecked Sendable { } /// No guest token — this route is fully public/unauthenticated per the doc. - func fetchStoreDetail(identifier: String) async throws -> PublicStoreDetail { + /// + /// `state`/`city` are optional on the server and are what let it resolve + /// the city centroid the store `distance` is measured from; without them + /// the response carries `distance: 0` (see + /// docs/plans/public-store-distance-consumer.md). Defaults to whatever the + /// visitor picked in "ENTREGAR EM:". + func fetchStoreDetail( + identifier: String, + state: String? = GuestLocationStore.shared.selectedState, + city: String? = GuestLocationStore.shared.selectedCity + ) async throws -> PublicStoreDetail { let encodedIdentifier = identifier.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? identifier + var query: [URLQueryItem] = [] + if let state, state.isEmpty == false { + query.append(URLQueryItem(name: "state", value: state)) + } + if let city, city.isEmpty == false { + query.append(URLQueryItem(name: "city", value: city)) + } let req = ApiRequest( path: "/api/public/store/\(encodedIdentifier)", method: "GET", module: .none, requiresAuth: false, + queryItems: query, baseURLOverride: ApiConfig.pediFoodsBFFURL ) let envelope: ApiEnvelope = try await client.send(req) diff --git a/PediFoods/Views/Main/HomeView+Data.swift b/PediFoods/Views/Main/HomeView+Data.swift index 2c1510b..8405acf 100644 --- a/PediFoods/Views/Main/HomeView+Data.swift +++ b/PediFoods/Views/Main/HomeView+Data.swift @@ -177,7 +177,7 @@ extension HomeView { extension StoreSummary { /// Maps the public-locator DTO onto the same model HomeView already - /// renders — distance/positiveReviews don't exist in that response. + /// renders — positiveReviews doesn't exist in that response. init(publicItem: PublicStoreListItem) { self.id = publicItem.id self.name = publicItem.name ?? "Loja" @@ -189,7 +189,10 @@ extension StoreSummary { self.positiveReviews = nil self.deliveryTime = publicItem.deliveryTime self.deliveryFee = publicItem.deliveryFee - self.distance = nil + // The contract uses `0` for "no distance available"; normalize it to + // nil here so every downstream consumer (card label, max-distance + // filter) sees one representation of "unknown". + self.distance = (publicItem.distance ?? 0) > 0 ? publicItem.distance : nil self.isOpen = publicItem.isOpen self.statusLabel = publicItem.statusLabel } diff --git a/PediFoods/Views/Main/HomeView+Filtering.swift b/PediFoods/Views/Main/HomeView+Filtering.swift index 7950f4b..3cab9f8 100644 --- a/PediFoods/Views/Main/HomeView+Filtering.swift +++ b/PediFoods/Views/Main/HomeView+Filtering.swift @@ -82,8 +82,13 @@ extension HomeView { resolveStoreMediaURL(appState.profile.profilePicture) } + /// Empty string means "no distance to show" - the caller omits the whole + /// segment rather than labelling it. Per the API contract + /// (docs/plans/public-store-distance-consumer.md) the server never sends + /// null and uses `0` for "unavailable", so both that and a missing value + /// collapse to the same empty result. func formatDistance(_ distance: Double?) -> String { - guard let distance else { return "Distância indisponível" } + guard let distance, distance > 0 else { return "" } if distance >= 1 { return String(format: "%.1f km", distance) } diff --git a/PediFoods/Views/Main/StoreDetailView+Components.swift b/PediFoods/Views/Main/StoreDetailView+Components.swift index ade35f9..379aa93 100644 --- a/PediFoods/Views/Main/StoreDetailView+Components.swift +++ b/PediFoods/Views/Main/StoreDetailView+Components.swift @@ -109,8 +109,13 @@ extension StoreDetailView { } HStack(spacing: 0) { - statItem(title: "DISTÂNCIA", value: distanceValueLabel) - Divider().frame(height: 34) + // No distance available: drop the tile and its divider + // instead of showing a placeholder - see + // docs/plans/public-store-distance-consumer.md. + if hasStoreDistance { + statItem(title: "DISTÂNCIA", value: distanceValueLabel) + Divider().frame(height: 34) + } if let deliveryTime = info?.deliveryTime { statItem(title: "TEMPO MIN.", value: deliveryTime+" min.") } else { diff --git a/PediFoods/Views/Main/StoreDetailView+Logic.swift b/PediFoods/Views/Main/StoreDetailView+Logic.swift index a45bedb..1e3daef 100644 --- a/PediFoods/Views/Main/StoreDetailView+Logic.swift +++ b/PediFoods/Views/Main/StoreDetailView+Logic.swift @@ -57,12 +57,14 @@ extension StoreDetailView { return "R$ --" } + /// Already formatted upstream by `HomeView.formatDistance`, which returns + /// an empty string when there's no distance to show. var distanceValueLabel: String { - let raw = (storeDistance ?? "").trimmingCharacters(in: .whitespacesAndNewlines) - if raw.isEmpty { - return "--" - } - return raw + (storeDistance ?? "").trimmingCharacters(in: .whitespacesAndNewlines) + } + + var hasStoreDistance: Bool { + distanceValueLabel.isEmpty == false } var isStoreOpen: Bool { diff --git a/PediFoodsTests/HomeViewFilteringTests.swift b/PediFoodsTests/HomeViewFilteringTests.swift index 170f36d..3a38e82 100644 --- a/PediFoodsTests/HomeViewFilteringTests.swift +++ b/PediFoodsTests/HomeViewFilteringTests.swift @@ -63,5 +63,17 @@ func formatDistanceSwitchesUnitsAtOneKm() { #expect(view.formatDistance(0.5) == "500 m") #expect(view.formatDistance(1.0) == "1.0 km") #expect(view.formatDistance(2.3) == "2.3 km") - #expect(view.formatDistance(nil) == "Distância indisponível") +} + +/// Contract (docs/plans/public-store-distance-consumer.md): the API never +/// sends null - `0` means "no distance available". Both that and a missing +/// value render as an empty string so the caller omits the segment +/// entirely, instead of showing "Distância indisponível" or "0 m". +@Test("formatDistance renders nothing when the distance is missing or zero") +@MainActor +func formatDistanceIsEmptyWhenUnavailable() { + let view = makeHomeView() + #expect(view.formatDistance(nil) == "") + #expect(view.formatDistance(0) == "") + #expect(view.formatDistance(-1) == "") } diff --git a/PediFoodsTests/PublicStoreDistanceTests.swift b/PediFoodsTests/PublicStoreDistanceTests.swift new file mode 100644 index 0000000..f0f0da8 --- /dev/null +++ b/PediFoodsTests/PublicStoreDistanceTests.swift @@ -0,0 +1,49 @@ +import Foundation +import Testing +@testable import PediFoods + +/// Covers the public-locator distance contract documented in +/// docs/plans/public-store-distance-consumer.md: the server sends +/// `distance` as a number (never null) where `0` means "unavailable", and +/// the app collapses that to `nil` so a single representation of "unknown" +/// reaches the card label and the max-distance filter. +private func decode(_ type: T.Type, _ json: String) throws -> T { + try JSONDecoder().decode(T.self, from: Data(json.utf8)) +} + +@Test("PublicStoreListItem decodes the distance field") +func publicStoreListItemDecodesDistance() throws { + let item = try decode(PublicStoreListItem.self, #"{"id":"1","distance":3.4}"#) + #expect(item.distance == 3.4) +} + +/// The field ships on the backend after the app - a response without it +/// must still decode rather than throwing. +@Test("PublicStoreListItem still decodes a response with no distance field") +func publicStoreListItemDecodesWithoutDistance() throws { + let item = try decode(PublicStoreListItem.self, #"{"id":"1"}"#) + #expect(item.distance == nil) +} + +@Test("PublicStoreDetail decodes the distance field") +func publicStoreDetailDecodesDistance() throws { + let detail = try decode(PublicStoreDetail.self, #"{"id":"1","distance":1.2}"#) + #expect(detail.distance == 1.2) + let without = try decode(PublicStoreDetail.self, #"{"id":"1"}"#) + #expect(without.distance == nil) +} + +@Test("StoreSummary carries a real public distance through") +func storeSummaryKeepsPublicDistance() throws { + let item = try decode(PublicStoreListItem.self, #"{"id":"1","name":"A","distance":2.5}"#) + #expect(StoreSummary(publicItem: item).distance == 2.5) +} + +@Test("StoreSummary maps the unavailable sentinel and a missing distance to nil") +func storeSummaryNormalizesUnavailableDistance() throws { + let zero = try decode(PublicStoreListItem.self, #"{"id":"1","name":"A","distance":0}"#) + #expect(StoreSummary(publicItem: zero).distance == nil) + + let absent = try decode(PublicStoreListItem.self, #"{"id":"1","name":"A"}"#) + #expect(StoreSummary(publicItem: absent).distance == nil) +} From 1275fbee7a61f5d202fa8b96ceef2d184b65de2d Mon Sep 17 00:00:00 2001 From: Daniel Arantes Loverde Date: Thu, 27 Aug 2026 16:59:54 -0300 Subject: [PATCH 5/5] [signing] Restore project.pbxproj settings wiped by xcodegen Running 'xcodegen generate' to register a new test file reverted every setting that lives in project.pbxproj but is missing or stale in project.yml. Restored the file from its known-good state and re-added only the four PublicStoreDistanceTests.swift references by hand. What xcodegen had destroyed: - TargetAttributes gained DevelopmentTeam = "" on the app, the extension and the unit tests, which blanks the Team in Xcode's Signing UI so the provisioning profile no longer resolves. - APP_ATTEST_ENVIRONMENT = production removed entirely. - NSLocationWhenInUseUsageDescription replaced with the English string from project.yml, dropping the pt-BR one shipped for App Review. - CURRENT_PROJECT_VERSION reset from 2026.0.25.0.0.1 to 1. Verified the file now differs from the known-good state by exactly the four new test-file lines, and that all four items above are back. --- PediFoods.xcodeproj/project.pbxproj | 43 ++++++++++++----------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/PediFoods.xcodeproj/project.pbxproj b/PediFoods.xcodeproj/project.pbxproj index 60e9941..7b48fa3 100644 --- a/PediFoods.xcodeproj/project.pbxproj +++ b/PediFoods.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -36,6 +36,7 @@ 2B202626C34FC9F5C87C0388 /* ApiOrderModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4625B70E3E8BEEB9D0B2042 /* ApiOrderModels.swift */; }; 2F56755488E951C453AC2126 /* LCEssentials in Frameworks */ = {isa = PBXBuildFile; productRef = B8D5E8821C95A331C3A19DA1 /* LCEssentials */; }; 2FCB3403A764AD74AC3BD47C /* LegalDocumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA914FB8B32B106911FD67B /* LegalDocumentTests.swift */; }; + 901A6A5A00C18FC59F764B85 /* PublicStoreDistanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E55893DD92CBFB24F212454 /* PublicStoreDistanceTests.swift */; }; 33AB0B87867400F3D0C65FA0 /* ApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBC4B9B43C015C4DD411359 /* ApiClient.swift */; }; 3689239282AADF06783CBBEA /* AuthFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B3C375FC18002A884A44F1 /* AuthFormatters.swift */; }; 3BD69E880EBC797B87EE547B /* StoreDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DB74B676A58B367F026090 /* StoreDetailView.swift */; }; @@ -79,10 +80,9 @@ 7CC171B83BC4CDEC38B25F8C /* SnackbarCenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CE116C585A7529CF309B3D /* SnackbarCenterTests.swift */; }; 7D7BF04765D6CA6AC4F11DDD /* StoreCatalogNormalizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EC38EEE1E7105BC21E354C /* StoreCatalogNormalizerTests.swift */; }; 80B476CCE2B90BEE2FA4CB37 /* PediFoodsUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56E3E6C1834786CA3621B7BB /* PediFoodsUITestsLaunchTests.swift */; }; - 811217F88AA350A70CC22479 /* NotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 811217F88AA350A70CC22479 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 85EA52253BABA327EC07CFF0 /* AuthFormattersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 561E00C486D60EC2DF720EAF /* AuthFormattersTests.swift */; }; 8D6622AE9325D02B830BD115 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6CAA9B253B1A6C8815A0E69D /* LaunchScreen.storyboard */; }; - 901A6A5A00C18FC59F764B85 /* PublicStoreDistanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E55893DD92CBFB24F212454 /* PublicStoreDistanceTests.swift */; }; 90ADA376585F5438F3A1A1D6 /* HomeView+Favorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1517B141263438E466452037 /* HomeView+Favorites.swift */; }; 918BCBC1EAD7EE134DB9742D /* CheckoutTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8FF15CEE5B137074E6B2489 /* CheckoutTypesTests.swift */; }; 926E0C4DD3C82485D4262A62 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B2F3558C58CD1E71F0F3A0 /* ContentView.swift */; }; @@ -169,7 +169,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( - 811217F88AA350A70CC22479 /* NotificationServiceExtension.appex in Embed Foundation Extensions */, + 811217F88AA350A70CC22479 /* NotificationService.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -211,7 +211,6 @@ 4C415019CC1F314EE7318864 /* ApiCardModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCardModelsTests.swift; sourceTree = ""; }; 4DC5416AEBA77C43CF55ABA8 /* PublicLocationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicLocationPickerView.swift; sourceTree = ""; }; 4E0CCEF22D356F9EC723999D /* HomeView+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeView+Data.swift"; sourceTree = ""; }; - 4E55893DD92CBFB24F212454 /* PublicStoreDistanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicStoreDistanceTests.swift; sourceTree = ""; }; 507E868458359BE0E4FF25F1 /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = ""; }; 52BFF93F21509F1AD4F6676B /* PediFoodsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PediFoodsApp.swift; sourceTree = ""; }; 54795EDED987426386226358 /* GuestLocationStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestLocationStoreTests.swift; sourceTree = ""; }; @@ -230,7 +229,7 @@ 69A9FB3DE679C3D4C60E7E42 /* ApiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiConfig.swift; sourceTree = ""; }; 6BF467DF1447525F04B242B0 /* ReviewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewsView.swift; sourceTree = ""; }; 6CAA9B253B1A6C8815A0E69D /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = PediFoodsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PediFoodsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6EC381D02A37312BE360024F /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = ""; }; 6FC454261845C1EDF970552A /* CheckoutTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutTypes.swift; sourceTree = ""; }; 7034E89267D9F5D86BF1E424 /* CartCheckoutFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartCheckoutFlowTests.swift; sourceTree = ""; }; @@ -243,7 +242,7 @@ 76B24E69A3F6A3EB86449870 /* AddAddressFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAddressFormView.swift; sourceTree = ""; }; 794249487F718BCFBF176E8D /* ApiModelsDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiModelsDecodingTests.swift; sourceTree = ""; }; 79D2854B8062D96742CFDD99 /* HomeViewComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewComponents.swift; sourceTree = ""; }; - 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7A9F14B1BDD7747C546DFAF0 /* GuestSessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestSessionService.swift; sourceTree = ""; }; 7C13751F450A42A2BDB0783D /* ProfileLoggedOutFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileLoggedOutFlowTests.swift; sourceTree = ""; }; 7CD06F9A7AF9365D1FBE35D0 /* AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressComponents.swift; sourceTree = ""; }; @@ -252,7 +251,8 @@ 89B662E8B7E1B8F1CF7E657E /* KeychainStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStore.swift; sourceTree = ""; }; 8A3FB85A2A2E2952B9A2BAB0 /* DeepLinkDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkDestination.swift; sourceTree = ""; }; 8AA914FB8B32B106911FD67B /* LegalDocumentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalDocumentTests.swift; sourceTree = ""; }; - 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = PediFoodsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4E55893DD92CBFB24F212454 /* PublicStoreDistanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicStoreDistanceTests.swift; sourceTree = ""; }; + 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PediFoodsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8D2C6A0E85E133F947293181 /* OrderDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDetailsView.swift; sourceTree = ""; }; 8DD2B73D89E6D33643EF58F5 /* DeepLinkRouteEffectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkRouteEffectTests.swift; sourceTree = ""; }; 8E3A0A2E253ABFCA35D02F28 /* TextFieldCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldCompat.swift; sourceTree = ""; }; @@ -287,7 +287,7 @@ C44662BAA20AD862BE1DBC40 /* PublicLocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicLocationService.swift; sourceTree = ""; }; C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnackbarOverlay.swift; sourceTree = ""; }; C6B08901A67C8D90A3B50882 /* ApiCustomerPayloadModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCustomerPayloadModelsTests.swift; sourceTree = ""; }; - C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; CA44F5384D21B7F718F95522 /* StoreDetailSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreDetailSupport.swift; sourceTree = ""; }; CAB9F980426D39E93E091051 /* LocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = ""; }; CD5EE81AE4347B4086684F8B /* CartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartView.swift; sourceTree = ""; }; @@ -346,7 +346,7 @@ 126A579C448C784867DC3AB1 /* Products */ = { isa = PBXGroup; children = ( - C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */, + C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */, 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */, 6DD1D8390E57439A347F9E07 /* PediFoodsTests.xctest */, 8AF987AB319512C123DFD558 /* PediFoodsUITests.xctest */, @@ -624,7 +624,7 @@ ); name = NotificationServiceExtension; productName = NotificationServiceExtension; - productReference = C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */; + productReference = C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */; productType = "com.apple.product-type.app-extension"; }; 66486D25E3A8A75EA8FACE44 /* PediFoodsUITests */ = { @@ -674,21 +674,10 @@ BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; TargetAttributes = { - 3027BAE353B414EC70E2A2AC = { - DevelopmentTeam = ""; - ProvisioningStyle = Manual; - }; - 33EB2407F0F758EB98A769D3 = { - DevelopmentTeam = ""; - ProvisioningStyle = Manual; - }; 66486D25E3A8A75EA8FACE44 = { - DevelopmentTeam = K4E5BZMM4V; TestTargetID = B3B5CFF77A8ED019D1299ABB; }; B3B5CFF77A8ED019D1299ABB = { - DevelopmentTeam = ""; - ProvisioningStyle = Manual; SystemCapabilities = { com.apple.Push = { enabled = YES; @@ -1001,6 +990,7 @@ 388EFAF707AD295240B9E951 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APP_ATTEST_ENVIRONMENT = production; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1008,7 +998,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2026.0.25.0.0.1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; ENABLE_PREVIEWS = YES; @@ -1016,7 +1006,7 @@ INFOPLIST_FILE = PediFoods/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods uses your location to show nearby stores and estimate delivery times."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods usa sua localização para mostrar lojas perto de você"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = NO; @@ -1045,6 +1035,7 @@ 4424276379B7A6A98892CFBC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APP_ATTEST_ENVIRONMENT = development; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -1052,7 +1043,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2026.0.25.0.0.1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V; ENABLE_PREVIEWS = YES; @@ -1060,7 +1051,7 @@ INFOPLIST_FILE = PediFoods/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods uses your location to show nearby stores and estimate delivery times."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods usa sua localização para mostrar lojas perto de você"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = NO;