diff --git a/PediFoods.xcodeproj/project.pbxproj b/PediFoods.xcodeproj/project.pbxproj index 76b25cd..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,7 +80,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 +115,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 +128,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 +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; @@ -199,6 +200,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 +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 = ""; }; @@ -240,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 = ""; }; @@ -249,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 = ""; }; @@ -284,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 = ""; }; @@ -300,7 +303,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 +346,7 @@ 126A579C448C784867DC3AB1 /* Products */ = { isa = PBXGroup; children = ( - C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */, + C724E02EC34BB1D4352C2FF0 /* NotificationService.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 */, @@ -558,9 +561,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 +624,7 @@ ); name = NotificationServiceExtension; productName = NotificationServiceExtension; - productReference = C724E02EC34BB1D4352C2FF0 /* NotificationServiceExtension.appex */; + productReference = C724E02EC34BB1D4352C2FF0 /* NotificationService.appex */; productType = "com.apple.product-type.app-extension"; }; 66486D25E3A8A75EA8FACE44 /* PediFoodsUITests */ = { @@ -671,15 +674,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 +773,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 +781,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 */, @@ -872,6 +870,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 */, @@ -968,6 +967,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 +979,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 +995,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 +1022,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 +1081,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 +1094,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 +1106,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 +1120,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 +1133,7 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = K4E5BZMM4V; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1190,6 +1208,7 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; + DEVELOPMENT_TEAM = K4E5BZMM4V; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1208,9 +1227,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 +1242,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/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/ContentView.swift b/PediFoods/ContentView.swift index 3d77f86..90881e0 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,15 +30,15 @@ struct ContentView: View { root: $root, selectedTab: $selectedTab, tokenStore: tokenStore, - appState: $appState, - shouldPrepareLoginEntry: shouldPrepareAuthEntryAnimation, - authEntryAnimationToken: authEntryAnimationToken + 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)) } } } @@ -136,15 +134,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 +404,15 @@ 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() + withAnimation(.easeInOut(duration: 0.3)) { + root = .auth + } } @MainActor @@ -463,13 +455,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/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/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/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 6d79a9e..6413d9e 100644 --- a/PediFoods/Views/Auth/LoginView.swift +++ b/PediFoods/Views/Auth/LoginView.swift @@ -1,142 +1,81 @@ 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())) { + withAnimation(.easeInOut(duration: 0.3)) { 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/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/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) +} diff --git a/PediFoodsUITests/ProfileLoggedOutFlowTests.swift b/PediFoodsUITests/ProfileLoggedOutFlowTests.swift index 0f4f85d..5a547e3 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,52 @@ 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") + } + + /// 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") } } 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() 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: