Merge pull request 'fix/auth/payment-login-lockout' (#44) from fix/auth/payment-login-lockout into main
Reviewed-on: Loverde-Company-LTDA/Pedi-Foods-Skip#44
This commit is contained in:
@@ -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 = "<group>"; };
|
||||
2F4ED1B7B71C905115380DC0 /* FeatureControlService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureControlService.swift; sourceTree = "<group>"; };
|
||||
30024AC97E79912E1358DDFB /* FiltersModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersModalView.swift; sourceTree = "<group>"; };
|
||||
3056B162576522D94702AE6E /* AppBackButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBackButtonIcon.swift; sourceTree = "<group>"; };
|
||||
30B3B6E69309F3EDF28E81CC /* LegalViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalViews.swift; sourceTree = "<group>"; };
|
||||
31916C1A4005396ADA5FF3BC /* StoreCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreCard.swift; sourceTree = "<group>"; };
|
||||
35400BEE3D09C61B2058579F /* AppContentCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentCacheTests.swift; sourceTree = "<group>"; };
|
||||
@@ -227,7 +229,7 @@
|
||||
69A9FB3DE679C3D4C60E7E42 /* ApiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiConfig.swift; sourceTree = "<group>"; };
|
||||
6BF467DF1447525F04B242B0 /* ReviewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewsView.swift; sourceTree = "<group>"; };
|
||||
6CAA9B253B1A6C8815A0E69D /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
6FC454261845C1EDF970552A /* CheckoutTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutTypes.swift; sourceTree = "<group>"; };
|
||||
7034E89267D9F5D86BF1E424 /* CartCheckoutFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartCheckoutFlowTests.swift; sourceTree = "<group>"; };
|
||||
@@ -240,7 +242,7 @@
|
||||
76B24E69A3F6A3EB86449870 /* AddAddressFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAddressFormView.swift; sourceTree = "<group>"; };
|
||||
794249487F718BCFBF176E8D /* ApiModelsDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiModelsDecodingTests.swift; sourceTree = "<group>"; };
|
||||
79D2854B8062D96742CFDD99 /* HomeViewComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewComponents.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
7C13751F450A42A2BDB0783D /* ProfileLoggedOutFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileLoggedOutFlowTests.swift; sourceTree = "<group>"; };
|
||||
7CD06F9A7AF9365D1FBE35D0 /* AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressComponents.swift; sourceTree = "<group>"; };
|
||||
@@ -249,7 +251,8 @@
|
||||
89B662E8B7E1B8F1CF7E657E /* KeychainStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStore.swift; sourceTree = "<group>"; };
|
||||
8A3FB85A2A2E2952B9A2BAB0 /* DeepLinkDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkDestination.swift; sourceTree = "<group>"; };
|
||||
8AA914FB8B32B106911FD67B /* LegalDocumentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalDocumentTests.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
8DD2B73D89E6D33643EF58F5 /* DeepLinkRouteEffectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkRouteEffectTests.swift; sourceTree = "<group>"; };
|
||||
8E3A0A2E253ABFCA35D02F28 /* TextFieldCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldCompat.swift; sourceTree = "<group>"; };
|
||||
@@ -284,7 +287,7 @@
|
||||
C44662BAA20AD862BE1DBC40 /* PublicLocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicLocationService.swift; sourceTree = "<group>"; };
|
||||
C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnackbarOverlay.swift; sourceTree = "<group>"; };
|
||||
C6B08901A67C8D90A3B50882 /* ApiCustomerPayloadModelsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiCustomerPayloadModelsTests.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
CAB9F980426D39E93E091051 /* LocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = "<group>"; };
|
||||
CD5EE81AE4347B4086684F8B /* CartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartView.swift; sourceTree = "<group>"; };
|
||||
@@ -300,7 +303,6 @@
|
||||
EB95D6804877D2ED244811D0 /* Inputs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inputs.swift; sourceTree = "<group>"; };
|
||||
ECBFCE20C54A79DEA8F4512B /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
|
||||
EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRemoteImage.swift; sourceTree = "<group>"; };
|
||||
52EEF6262A813BB8274266C8 /* AppBackButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBackButtonIcon.swift; sourceTree = "<group>"; };
|
||||
F0B079DFAB606DCA87A536C9 /* LaunchSplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchSplashView.swift; sourceTree = "<group>"; };
|
||||
F18F768F7A2A47111A85FE88 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||
F2B2C5BBDFF4473C472F45B4 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
|
||||
@@ -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;
|
||||
|
||||
@@ -53,6 +53,10 @@ struct FeaturedStoreCard: View {
|
||||
Text("(\(store.reviews))")
|
||||
.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)
|
||||
@@ -60,6 +64,7 @@ struct FeaturedStoreCard: View {
|
||||
.font(.caption)
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(store.statusLabel?.isEmpty == false ? (store.statusLabel ?? "Fechado") : "Fechado")
|
||||
.font(AppTypography.heading3)
|
||||
|
||||
@@ -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
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
root = .auth
|
||||
authEntryAnimationToken += 1
|
||||
scheduleDisableAuthEntryPreparation()
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@@ -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" : {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<PublicStoreDetail> = try await client.send(req)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
ZStack(alignment: .top) {
|
||||
(colorScheme == .dark ? Color.black : AppColors.backgroundLight)
|
||||
.ignoresSafeArea()
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
VStack(spacing: 18) {
|
||||
Spacer().frame(height: logoTopInset)
|
||||
private var content: some View {
|
||||
VStack(spacing: 20) {
|
||||
Spacer()
|
||||
|
||||
logoImage
|
||||
.scaledToFit()
|
||||
.frame(height: 180)
|
||||
.frame(height: 160)
|
||||
|
||||
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)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 28)
|
||||
.opacity(textVisible ? 1.0 : 0.0)
|
||||
.offset(y: textVisible ? 0 : 24)
|
||||
|
||||
Spacer().frame(height: 40)
|
||||
Spacer()
|
||||
|
||||
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") {
|
||||
VStack(spacing: 12) {
|
||||
PrimaryButton(title: "Criar conta", image: Image(systemName: "arrow.right")) {
|
||||
navigate(.registration)
|
||||
}
|
||||
.tint(AppColors.tertiary)
|
||||
|
||||
SecondaryButton(title: "Entrar") {
|
||||
navigate(.loginEmail)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 28)
|
||||
|
||||
legalLinks
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(AppColors.backgroundLight.ignoresSafeArea())
|
||||
}
|
||||
|
||||
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)
|
||||
.foregroundStyle(AppColors.primary)
|
||||
}
|
||||
.font(AppTypography.body)
|
||||
.opacity(textVisible ? 1.0 : 0.0)
|
||||
.offset(y: textVisible ? 0 : 24)
|
||||
|
||||
Spacer().frame(height: 12)
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.onAppear {
|
||||
if shouldPrepareEntryAnimation {
|
||||
applyHiddenStateWithoutAnimation()
|
||||
} else {
|
||||
showFinalStateWithoutAnimation()
|
||||
}
|
||||
}
|
||||
.task(id: authEntryAnimationToken) {
|
||||
await runEntryAnimationIfNeeded(for: authEntryAnimationToken)
|
||||
}
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
#Preview {
|
||||
LoginView(root: .constant(.auth), navigate: { _ in })
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -109,8 +109,13 @@ extension StoreDetailView {
|
||||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
// 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 {
|
||||
|
||||
@@ -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 "--"
|
||||
(storeDistance ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
return raw
|
||||
|
||||
var hasStoreDistance: Bool {
|
||||
distanceValueLabel.isEmpty == false
|
||||
}
|
||||
|
||||
var isStoreOpen: Bool {
|
||||
|
||||
@@ -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) == "")
|
||||
}
|
||||
|
||||
49
PediFoodsTests/PublicStoreDistanceTests.swift
Normal file
49
PediFoodsTests/PublicStoreDistanceTests.swift
Normal file
@@ -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<T: Decodable>(_ 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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
47
project.yml
47
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:
|
||||
|
||||
Reference in New Issue
Block a user