migration
@@ -35,7 +35,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Write App Store Connect API key
|
- name: Write App Store Connect API key
|
||||||
run: |
|
run: |
|
||||||
cat > Darwin/fastlane/apikey.json <<EOF
|
cat > fastlane/apikey.json <<EOF
|
||||||
{
|
{
|
||||||
"key_id": "${{ secrets.ASC_KEY_ID }}",
|
"key_id": "${{ secrets.ASC_KEY_ID }}",
|
||||||
"issuer_id": "${{ secrets.ASC_ISSUER_ID }}",
|
"issuer_id": "${{ secrets.ASC_ISSUER_ID }}",
|
||||||
@@ -52,12 +52,11 @@ jobs:
|
|||||||
security list-keychains -d user -s ~/Library/Keychains/ci-signing.keychain-db ~/Library/Keychains/login.keychain-db
|
security list-keychains -d user -s ~/Library/Keychains/ci-signing.keychain-db ~/Library/Keychains/login.keychain-db
|
||||||
security default-keychain -d user -s ~/Library/Keychains/ci-signing.keychain-db
|
security default-keychain -d user -s ~/Library/Keychains/ci-signing.keychain-db
|
||||||
security find-identity -v -p codesigning
|
security find-identity -v -p codesigning
|
||||||
cd Darwin
|
|
||||||
fastlane beta
|
fastlane beta
|
||||||
|
|
||||||
- name: Clean up API key
|
- name: Clean up API key
|
||||||
if: always()
|
if: always()
|
||||||
run: rm -f Darwin/fastlane/apikey.json
|
run: rm -f fastlane/apikey.json
|
||||||
|
|
||||||
sleep-macos-vm:
|
sleep-macos-vm:
|
||||||
needs: build-and-upload
|
needs: build-and-upload
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Write App Store Connect API key
|
- name: Write App Store Connect API key
|
||||||
run: |
|
run: |
|
||||||
cat > Darwin/fastlane/apikey.json <<EOF
|
cat > fastlane/apikey.json <<EOF
|
||||||
{
|
{
|
||||||
"key_id": "${{ secrets.ASC_KEY_ID }}",
|
"key_id": "${{ secrets.ASC_KEY_ID }}",
|
||||||
"issuer_id": "${{ secrets.ASC_ISSUER_ID }}",
|
"issuer_id": "${{ secrets.ASC_ISSUER_ID }}",
|
||||||
@@ -70,12 +70,11 @@ jobs:
|
|||||||
security dump-keychain "$HOME/Library/Keychains/ci-signing.keychain-db" | grep -c "class.*0x80001000" || true
|
security dump-keychain "$HOME/Library/Keychains/ci-signing.keychain-db" | grep -c "class.*0x80001000" || true
|
||||||
echo "--- find-identity ---"
|
echo "--- find-identity ---"
|
||||||
security find-identity -v -p codesigning
|
security find-identity -v -p codesigning
|
||||||
cd Darwin
|
|
||||||
fastlane release
|
fastlane release
|
||||||
|
|
||||||
- name: Clean up API key
|
- name: Clean up API key
|
||||||
if: always()
|
if: always()
|
||||||
run: rm -f Darwin/fastlane/apikey.json
|
run: rm -f fastlane/apikey.json
|
||||||
|
|
||||||
sleep-macos-vm:
|
sleep-macos-vm:
|
||||||
needs: build-archive-deliver
|
needs: build-archive-deliver
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ jobs:
|
|||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: |
|
run: |
|
||||||
xcodebuild test \
|
xcodebuild test \
|
||||||
-project Darwin/PediFoods.xcodeproj \
|
-project PediFoods.xcodeproj \
|
||||||
-scheme "PediFoods App" \
|
-scheme "PediFoods" \
|
||||||
-destination "platform=iOS Simulator,name=iPhone 16,OS=latest"
|
-destination "platform=iOS Simulator,name=iPhone 16,OS=latest"
|
||||||
|
|
||||||
sleep-macos-vm:
|
sleep-macos-vm:
|
||||||
|
|||||||
8
.gitignore
vendored
@@ -7,14 +7,6 @@
|
|||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# gradle properties
|
|
||||||
local.properties
|
|
||||||
.gradle/
|
|
||||||
.android/
|
|
||||||
.kotlin/
|
|
||||||
Android/app/keystore.jks
|
|
||||||
Android/app/keystore.properties
|
|
||||||
|
|
||||||
xcodebuild*.log
|
xcodebuild*.log
|
||||||
|
|
||||||
default.profraw
|
default.profraw
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
#include "../Skip.env"
|
|
||||||
|
|
||||||
// Set the action that will be executed as part of the Xcode Run Script phase
|
|
||||||
// Setting to "launch" will build and run the app in the first open Android emulator or device
|
|
||||||
// Setting to "build" will just run gradle build, but will not launch the app
|
|
||||||
// Setting to "none" will completely disable the build and launch of the Android app
|
|
||||||
SKIP_ACTION = launch
|
|
||||||
//SKIP_ACTION = build
|
|
||||||
//SKIP_ACTION = none
|
|
||||||
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
|
|
||||||
|
|
||||||
INFOPLIST_FILE = Info.plist
|
|
||||||
GENERATE_INFOPLIST_FILE = YES
|
|
||||||
|
|
||||||
// The user-visible name of the app (localizable)
|
|
||||||
//INFOPLIST_KEY_CFBundleDisplayName = App Name
|
|
||||||
//INFOPLIST_KEY_LSApplicationCategoryType = public.app-category.utilities
|
|
||||||
//INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "This app uses your location to …"
|
|
||||||
|
|
||||||
// iOS-specific Info.plist property keys
|
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*] = YES
|
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*] = YES
|
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*] = NO
|
|
||||||
INFOPLIST_KEY_UILaunchStoryboardName[sdk=iphone*] = LaunchScreen
|
|
||||||
INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations[sdk=iphone*] = UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown
|
|
||||||
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0
|
|
||||||
SUPPORTS_MACCATALYST = NO
|
|
||||||
|
|
||||||
// iPhone + iPad
|
|
||||||
TARGETED_DEVICE_FAMILY = 1,2
|
|
||||||
|
|
||||||
// iPhone only
|
|
||||||
// TARGETED_DEVICE_FAMILY = 1
|
|
||||||
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES
|
|
||||||
|
|
||||||
// the name of the product module; this can be anything, but cannot conflict with any Swift module names
|
|
||||||
PRODUCT_MODULE_NAME = $(PRODUCT_NAME:c99extidentifier)App
|
|
||||||
|
|
||||||
// On-device testing may need to override the bundle ID
|
|
||||||
// PRODUCT_BUNDLE_IDENTIFIER[config=Debug][sdk=iphoneos*] = cool.beans.BundleIdentifer
|
|
||||||
|
|
||||||
SDKROOT = auto
|
|
||||||
SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
|
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES
|
|
||||||
|
|
||||||
SWIFT_VERSION = 6
|
|
||||||
|
|
||||||
// Development team ID for on-device testing
|
|
||||||
CODE_SIGNING_REQUIRED = NO
|
|
||||||
CODE_SIGN_STYLE = Automatic
|
|
||||||
CODE_SIGN_ENTITLEMENTS = Entitlements.plist
|
|
||||||
//CODE_SIGNING_IDENTITY = -
|
|
||||||
//DEVELOPMENT_TEAM =
|
|
||||||
@@ -1,484 +0,0 @@
|
|||||||
// !$*UTF8*$!
|
|
||||||
{
|
|
||||||
archiveVersion = 1;
|
|
||||||
classes = {
|
|
||||||
};
|
|
||||||
objectVersion = 56;
|
|
||||||
objects = {
|
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
|
||||||
002E82AE37F71856D2AD0524 /* NotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 588B094973FB7448596F3D09 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeaderOnCopy, ); }; };
|
|
||||||
1457598D2491B8729EEBB739 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E34FD2046E40CE1E302DB11 /* NotificationService.swift */; };
|
|
||||||
491F27822DA55B72004926EE /* PediFoods in Frameworks */ = {isa = PBXBuildFile; productRef = 491F27812DA55B72004926EE /* PediFoods */; };
|
|
||||||
491F27832DA55B72004926EE /* PediFoods in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 491F27812DA55B72004926EE /* PediFoods */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
|
||||||
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */; };
|
|
||||||
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F90C2B2A52156200F06D93 /* Main.swift */; };
|
|
||||||
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49F90C2F2A52156300F06D93 /* Assets.xcassets */; };
|
|
||||||
837DB5EB5FA4527A71F02C70 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78EA2CD32C41A0B833E06B89 /* Foundation.framework */; };
|
|
||||||
B13CABF22F36437300469FD6 /* LCEssentials in Frameworks */ = {isa = PBXBuildFile; productRef = B13CABF12F36437300469FD6 /* LCEssentials */; };
|
|
||||||
B185A1FD2FB67A12001C7771 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B185A1FC2FB67A12001C7771 /* LaunchScreen.storyboard */; };
|
|
||||||
/* End PBXBuildFile section */
|
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
|
||||||
E60A9FE80EBD27A663AF17E0 /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = 49F90C202A52156200F06D93 /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = 4FB6BE053007051C3D26B06E;
|
|
||||||
remoteInfo = NotificationServiceExtension;
|
|
||||||
};
|
|
||||||
/* End PBXContainerItemProxy section */
|
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
|
||||||
499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */ = {
|
|
||||||
isa = PBXCopyFilesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
dstPath = "";
|
|
||||||
dstSubfolderSpec = 10;
|
|
||||||
files = (
|
|
||||||
491F27832DA55B72004926EE /* PediFoods in Embed Frameworks */,
|
|
||||||
);
|
|
||||||
name = "Embed Frameworks";
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
C28D30C677D0B233FE310699 /* Embed Foundation Extensions */ = {
|
|
||||||
isa = PBXCopyFilesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
dstPath = "";
|
|
||||||
dstSubfolderSpec = 13;
|
|
||||||
files = (
|
|
||||||
002E82AE37F71856D2AD0524 /* NotificationServiceExtension.appex in Embed Foundation Extensions */,
|
|
||||||
);
|
|
||||||
name = "Embed Foundation Extensions";
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
|
||||||
4900101C2BACEA710000DE33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
493609562A6B7EAE00C401E2 /* PediFoods */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = PediFoods; path = ..; sourceTree = "<group>"; };
|
|
||||||
496BDBEB2B89A47800C09264 /* PediFoods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PediFoods.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = ../Sources/PediFoods/Resources/Localizable.xcstrings; sourceTree = "<group>"; };
|
|
||||||
496EB72F2A6AE4DE00C1253A /* Skip.env */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Skip.env; path = ../Skip.env; sourceTree = "<group>"; };
|
|
||||||
496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = PediFoods.xcconfig; sourceTree = "<group>"; };
|
|
||||||
496EB72F2A6AE4DE00C1253C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
|
||||||
49F90C2B2A52156200F06D93 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = Sources/Main.swift; sourceTree = SOURCE_ROOT; };
|
|
||||||
49F90C2F2A52156300F06D93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
|
||||||
49F90C312A52156300F06D93 /* Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.plist; sourceTree = "<group>"; };
|
|
||||||
4E34FD2046E40CE1E302DB11 /* NotificationService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
|
||||||
588B094973FB7448596F3D09 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
78EA2CD32C41A0B833E06B89 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
|
||||||
B185A1FC2FB67A12001C7771 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sources/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; };
|
|
||||||
ED678A4BFF090312B354648E /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
|
||||||
499CD43C2AC5B799001AE8D8 /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
B13CABF22F36437300469FD6 /* LCEssentials in Frameworks */,
|
|
||||||
491F27822DA55B72004926EE /* PediFoods in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
CAFA309ED97AD448069C6F95 /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
837DB5EB5FA4527A71F02C70 /* Foundation.framework in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXFrameworksBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
|
||||||
39B295835451899F1689190A /* NotificationServiceExtension */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
4E34FD2046E40CE1E302DB11 /* NotificationService.swift */,
|
|
||||||
ED678A4BFF090312B354648E /* Info.plist */,
|
|
||||||
);
|
|
||||||
name = NotificationServiceExtension;
|
|
||||||
path = NotificationServiceExtension;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
496BDBEC2B89A47800C09264 /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
496BDBEB2B89A47800C09264 /* PediFoods.app */,
|
|
||||||
588B094973FB7448596F3D09 /* NotificationServiceExtension.appex */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
49F90C1F2A52156200F06D93 = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
496EB72F2A6AE4DE00C1253C /* README.md */,
|
|
||||||
496EB72F2A6AE4DE00C1253A /* Skip.env */,
|
|
||||||
496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */,
|
|
||||||
496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */,
|
|
||||||
493609562A6B7EAE00C401E2 /* PediFoods */,
|
|
||||||
49F90C2A2A52156200F06D93 /* App */,
|
|
||||||
496BDBEC2B89A47800C09264 /* Products */,
|
|
||||||
39B295835451899F1689190A /* NotificationServiceExtension */,
|
|
||||||
8F798D6B741A681CBA9CC824 /* Frameworks */,
|
|
||||||
);
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
49F90C2A2A52156200F06D93 /* App */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
49F90C2B2A52156200F06D93 /* Main.swift */,
|
|
||||||
B185A1FC2FB67A12001C7771 /* LaunchScreen.storyboard */,
|
|
||||||
49F90C2F2A52156300F06D93 /* Assets.xcassets */,
|
|
||||||
49F90C312A52156300F06D93 /* Entitlements.plist */,
|
|
||||||
4900101C2BACEA710000DE33 /* Info.plist */,
|
|
||||||
);
|
|
||||||
name = App;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
8F798D6B741A681CBA9CC824 /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
C127D57D66FAAE472B202C61 /* iOS */,
|
|
||||||
);
|
|
||||||
name = Frameworks;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
C127D57D66FAAE472B202C61 /* iOS */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
78EA2CD32C41A0B833E06B89 /* Foundation.framework */,
|
|
||||||
);
|
|
||||||
name = iOS;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
|
||||||
499CD4382AC5B799001AE8D8 /* PediFoods App */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = 499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "PediFoods App" */;
|
|
||||||
buildPhases = (
|
|
||||||
499CD43A2AC5B799001AE8D8 /* Sources */,
|
|
||||||
499CD43C2AC5B799001AE8D8 /* Frameworks */,
|
|
||||||
499CD43E2AC5B799001AE8D8 /* Resources */,
|
|
||||||
499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */,
|
|
||||||
C28D30C677D0B233FE310699 /* Embed Foundation Extensions */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
A3838B46B6FB8B79EE9B0F9F /* PBXTargetDependency */,
|
|
||||||
);
|
|
||||||
name = "PediFoods App";
|
|
||||||
packageProductDependencies = (
|
|
||||||
491F27812DA55B72004926EE /* PediFoods */,
|
|
||||||
B13CABF12F36437300469FD6 /* LCEssentials */,
|
|
||||||
);
|
|
||||||
productName = App;
|
|
||||||
productReference = 496BDBEB2B89A47800C09264 /* PediFoods.app */;
|
|
||||||
productType = "com.apple.product-type.application";
|
|
||||||
};
|
|
||||||
4FB6BE053007051C3D26B06E /* NotificationServiceExtension */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = A20C22A5541093DB9CFD38F4 /* Build configuration list for PBXNativeTarget "NotificationServiceExtension" */;
|
|
||||||
buildPhases = (
|
|
||||||
29CCE086EDBB10F49FF72DDA /* Sources */,
|
|
||||||
CAFA309ED97AD448069C6F95 /* Frameworks */,
|
|
||||||
7A8A5D1C5EC61B8ACACF33CF /* Resources */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
);
|
|
||||||
name = NotificationServiceExtension;
|
|
||||||
productName = NotificationServiceExtension;
|
|
||||||
productReference = 588B094973FB7448596F3D09 /* NotificationServiceExtension.appex */;
|
|
||||||
productType = "com.apple.product-type.app-extension";
|
|
||||||
};
|
|
||||||
/* End PBXNativeTarget section */
|
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
|
||||||
49F90C202A52156200F06D93 /* Project object */ = {
|
|
||||||
isa = PBXProject;
|
|
||||||
attributes = {
|
|
||||||
BuildIndependentTargetsInParallel = 1;
|
|
||||||
LastSwiftUpdateCheck = 1430;
|
|
||||||
LastUpgradeCheck = 1630;
|
|
||||||
TargetAttributes = {
|
|
||||||
499CD4382AC5B799001AE8D8 = {
|
|
||||||
SystemCapabilities = {
|
|
||||||
com.apple.Push = {
|
|
||||||
enabled = 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildConfigurationList = 49F90C232A52156200F06D93 /* Build configuration list for PBXProject "PediFoods" */;
|
|
||||||
compatibilityVersion = "Xcode 14.0";
|
|
||||||
developmentRegion = en;
|
|
||||||
hasScannedForEncodings = 0;
|
|
||||||
knownRegions = (
|
|
||||||
en,
|
|
||||||
Base,
|
|
||||||
es,
|
|
||||||
ja,
|
|
||||||
"zh-Hans",
|
|
||||||
);
|
|
||||||
mainGroup = 49F90C1F2A52156200F06D93;
|
|
||||||
packageReferences = (
|
|
||||||
B13CABF02F36437300469FD6 /* XCRemoteSwiftPackageReference "LCEssentials" */,
|
|
||||||
);
|
|
||||||
productRefGroup = 496BDBEC2B89A47800C09264 /* Products */;
|
|
||||||
projectDirPath = "";
|
|
||||||
projectRoot = "";
|
|
||||||
targets = (
|
|
||||||
499CD4382AC5B799001AE8D8 /* PediFoods App */,
|
|
||||||
4FB6BE053007051C3D26B06E /* NotificationServiceExtension */,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXProject section */
|
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
|
||||||
499CD43E2AC5B799001AE8D8 /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
B185A1FD2FB67A12001C7771 /* LaunchScreen.storyboard in Resources */,
|
|
||||||
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */,
|
|
||||||
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
7A8A5D1C5EC61B8ACACF33CF /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXResourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
|
||||||
29CCE086EDBB10F49FF72DDA /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
1457598D2491B8729EEBB739 /* NotificationService.swift in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
499CD43A2AC5B799001AE8D8 /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXSourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
|
||||||
A3838B46B6FB8B79EE9B0F9F /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
name = NotificationServiceExtension;
|
|
||||||
target = 4FB6BE053007051C3D26B06E /* NotificationServiceExtension */;
|
|
||||||
targetProxy = E60A9FE80EBD27A663AF17E0 /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
/* End PBXTargetDependency section */
|
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
|
||||||
499CD4422AC5B799001AE8D8 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
CODE_SIGN_ENTITLEMENTS = Entitlements.plist;
|
|
||||||
CODE_SIGN_STYLE = Manual;
|
|
||||||
DEVELOPMENT_TEAM = "";
|
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods";
|
|
||||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
|
||||||
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods uses your location to show nearby stores and estimate delivery times.";
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "PediFoods Dev Prov Prof";
|
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
|
||||||
SUPPORTS_MACCATALYST = NO;
|
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
499CD4432AC5B799001AE8D8 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
CODE_SIGN_ENTITLEMENTS = Entitlements.plist;
|
|
||||||
CODE_SIGN_IDENTITY = "Apple Distribution";
|
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
|
||||||
CODE_SIGN_STYLE = Manual;
|
|
||||||
DEVELOPMENT_TEAM = K4E5BZMM4V;
|
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods";
|
|
||||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
|
||||||
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "PediFoods uses your location to show nearby stores and estimate delivery times.";
|
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
|
|
||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "com.br.pedifoods.app AppStore";
|
|
||||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "PediFoods Dist XC Prov Prof";
|
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
|
||||||
SUPPORTS_MACCATALYST = NO;
|
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
49F90C4B2A52156300F06D93 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_TESTABILITY = YES;
|
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
49F90C4C2A52156300F06D93 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
602E0FA08B93FC360B6AF5D1 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_TEAM = K4E5BZMM4V;
|
|
||||||
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/Frameworks",
|
|
||||||
"@executable_path/../../Frameworks",
|
|
||||||
);
|
|
||||||
MARKETING_VERSION = 0.0.1;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app.NotificationService;
|
|
||||||
PRODUCT_NAME = NotificationService;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SKIP_INSTALL = YES;
|
|
||||||
SWIFT_VERSION = 6;
|
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
F90A5B614A60B25B97F3541D /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
|
||||||
DEVELOPMENT_TEAM = K4E5BZMM4V;
|
|
||||||
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/Frameworks",
|
|
||||||
"@executable_path/../../Frameworks",
|
|
||||||
);
|
|
||||||
MARKETING_VERSION = 0.0.1;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.br.pedifoods.app.NotificationService;
|
|
||||||
PRODUCT_NAME = NotificationService;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SKIP_INSTALL = YES;
|
|
||||||
SWIFT_VERSION = 6;
|
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
|
||||||
VALIDATE_PRODUCT = YES;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
|
||||||
499CD4412AC5B799001AE8D8 /* Build configuration list for PBXNativeTarget "PediFoods App" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
499CD4422AC5B799001AE8D8 /* Debug */,
|
|
||||||
499CD4432AC5B799001AE8D8 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
49F90C232A52156200F06D93 /* Build configuration list for PBXProject "PediFoods" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
49F90C4B2A52156300F06D93 /* Debug */,
|
|
||||||
49F90C4C2A52156300F06D93 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
A20C22A5541093DB9CFD38F4 /* Build configuration list for PBXNativeTarget "NotificationServiceExtension" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
F90A5B614A60B25B97F3541D /* Release */,
|
|
||||||
602E0FA08B93FC360B6AF5D1 /* Debug */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
|
||||||
B13CABF02F36437300469FD6 /* XCRemoteSwiftPackageReference "LCEssentials" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials.git";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 1.0.11;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
|
||||||
491F27812DA55B72004926EE /* PediFoods */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = PediFoods;
|
|
||||||
};
|
|
||||||
B13CABF12F36437300469FD6 /* LCEssentials */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = B13CABF02F36437300469FD6 /* XCRemoteSwiftPackageReference "LCEssentials" */;
|
|
||||||
productName = LCEssentials;
|
|
||||||
};
|
|
||||||
/* End XCSwiftPackageProductDependency section */
|
|
||||||
};
|
|
||||||
rootObject = 49F90C202A52156200F06D93 /* Project object */;
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1630"
|
|
||||||
version = "1.7">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES"
|
|
||||||
buildArchitectures = "Automatic">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
|
||||||
BuildableName = "PediFoods.app"
|
|
||||||
BlueprintName = "PediFoods App"
|
|
||||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
shouldAutocreateTestPlan = "YES">
|
|
||||||
</TestAction>
|
|
||||||
<LaunchAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
launchStyle = "0"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
|
||||||
debugServiceExtension = "internal"
|
|
||||||
allowLocationSimulation = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
|
||||||
BuildableName = "PediFoods.app"
|
|
||||||
BlueprintName = "PediFoods App"
|
|
||||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "499CD4382AC5B799001AE8D8"
|
|
||||||
BuildableName = "PediFoods.app"
|
|
||||||
BlueprintName = "PediFoods App"
|
|
||||||
ReferencedContainer = "container:PediFoods.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
import PediFoods
|
|
||||||
|
|
||||||
private typealias AppRootView = PediFoodsRootView
|
|
||||||
private typealias AppDelegate = PediFoodsAppDelegate
|
|
||||||
|
|
||||||
/// The entry point to the app simply loads the App implementation from SPM module.
|
|
||||||
@main struct AppMain: App {
|
|
||||||
@AppDelegateAdaptor(AppMainDelegate.self) var appDelegate
|
|
||||||
@Environment(\.scenePhase) private var scenePhase
|
|
||||||
|
|
||||||
var body: some Scene {
|
|
||||||
WindowGroup {
|
|
||||||
AppRootView()
|
|
||||||
}
|
|
||||||
.onChange(of: scenePhase) { oldPhase, newPhase in
|
|
||||||
switch newPhase {
|
|
||||||
case .active:
|
|
||||||
AppDelegate.shared.onResume()
|
|
||||||
case .inactive:
|
|
||||||
AppDelegate.shared.onPause()
|
|
||||||
case .background:
|
|
||||||
AppDelegate.shared.onStop()
|
|
||||||
@unknown default:
|
|
||||||
print("unknown app phase: \(newPhase)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if canImport(UIKit)
|
|
||||||
typealias AppDelegateAdaptor = UIApplicationDelegateAdaptor
|
|
||||||
typealias AppMainDelegateBase = UIApplicationDelegate
|
|
||||||
typealias AppType = UIApplication
|
|
||||||
#elseif canImport(AppKit)
|
|
||||||
typealias AppDelegateAdaptor = NSApplicationDelegateAdaptor
|
|
||||||
typealias AppMainDelegateBase = NSApplicationDelegate
|
|
||||||
typealias AppType = NSApplication
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@MainActor final class AppMainDelegate: NSObject, AppMainDelegateBase {
|
|
||||||
let application = AppType.shared
|
|
||||||
|
|
||||||
#if canImport(UIKit)
|
|
||||||
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
|
||||||
AppDelegate.shared.onInit()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
|
||||||
AppDelegate.shared.onLaunch()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillTerminate(_ application: UIApplication) {
|
|
||||||
AppDelegate.shared.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
|
|
||||||
AppDelegate.shared.onLowMemory()
|
|
||||||
}
|
|
||||||
|
|
||||||
// support for SkipNotify.fetchNotificationToken()
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
||||||
NotificationCenter.default.post(name: NSNotification.Name("didRegisterForRemoteNotificationsWithDeviceToken"), object: application, userInfo: ["deviceToken": deviceToken])
|
|
||||||
}
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) {
|
|
||||||
NotificationCenter.default.post(name: NSNotification.Name("didFailToRegisterForRemoteNotificationsWithError"), object: application, userInfo: ["error": error])
|
|
||||||
}
|
|
||||||
#elseif canImport(AppKit)
|
|
||||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
|
||||||
AppDelegate.shared.onInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
||||||
AppDelegate.shared.onLaunch()
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillTerminate(_ application: Notification) {
|
|
||||||
AppDelegate.shared.onDestroy()
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
86
LICENSE.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|

|
||||||
|
|
||||||
|
Copyright (C) Loverde Company - All Rights Reserved
|
||||||
|
----------
|
||||||
|
|
||||||
|
# ⚠️ INFORMAÇÕES CONFIDENCIAIS E PROPRIEDADE INTELECTUAL ⚠️
|
||||||
|
|
||||||
|
## 🚨 AVISO LEGAL IMPORTANTE
|
||||||
|
|
||||||
|
**ATENÇÃO: Este repositório contém informações CONFIDENCIAIS e PROPRIETÁRIAS.**
|
||||||
|
|
||||||
|
### ⛔ PROIBIÇÕES ABSOLUTAS
|
||||||
|
|
||||||
|
- **NÃO** compartilhe este código com terceiros
|
||||||
|
- **NÃO** faça fork, clone ou cópia sem autorização expressa
|
||||||
|
- **NÃO** utilize qualquer parte deste código em outros projetos
|
||||||
|
- **NÃO** distribua, venda ou comercialize este software
|
||||||
|
- **NÃO** reverse engineer ou descompile o código
|
||||||
|
- **NÃO** acesse sem permissão explícita do proprietário
|
||||||
|
|
||||||
|
### 🔒 DIREITOS DE PROPRIEDADE INTELECTUAL
|
||||||
|
|
||||||
|
Este software é propriedade exclusiva de **Daniel Arantes Loverde** e está protegido por:
|
||||||
|
|
||||||
|
- **Lei de Propriedade Intelectual (Lei 9.610/98)**
|
||||||
|
- **Lei de Software (Lei 9.609/98)**
|
||||||
|
- **Lei Geral de Proteção de Dados (LGPD - Lei 13.709/18)**
|
||||||
|
- **Direitos Autorais e Patentes**
|
||||||
|
|
||||||
|
### ⚖️ CONSEQUÊNCIAS LEGAIS
|
||||||
|
|
||||||
|
O acesso não autorizado, uso indevido, cópia ou distribuição deste código constitui:
|
||||||
|
|
||||||
|
- **CRIME** de violação de propriedade intelectual
|
||||||
|
- **CRIME** de roubo de propriedade intelectual
|
||||||
|
- **CRIME** de violação de direitos autorais
|
||||||
|
- **CRIME** de acesso não autorizado a sistema informatizado
|
||||||
|
|
||||||
|
**Penas previstas: 2 a 5 anos de reclusão + multa**
|
||||||
|
|
||||||
|
### 🛡️ MEDIDAS DE SEGURANÇA
|
||||||
|
|
||||||
|
- **Monitoramento**: Todo acesso é registrado e monitorado
|
||||||
|
- **Rastreamento**: IPs e atividades são rastreados
|
||||||
|
- **Logs**: Sistema completo de auditoria
|
||||||
|
- **Criptografia**: Dados protegidos com AES-256
|
||||||
|
- **Autenticação**: Acesso restrito com JWT + OTP
|
||||||
|
|
||||||
|
### 📧 CONTATO AUTORIZADO
|
||||||
|
|
||||||
|
**ÚNICO CONTATO VÁLIDO:**
|
||||||
|
- **Email**: daniel@loverde.com.br
|
||||||
|
- **Autorização**: Apenas via email oficial
|
||||||
|
|
||||||
|
### 🔐 CLASSIFICAÇÃO
|
||||||
|
|
||||||
|
**NÍVEL DE CONFIDENCIALIDADE: MÁXIMO**
|
||||||
|
- **Restrito**: Apenas para o proprietário
|
||||||
|
- **Não Divulgar**: Informações proprietárias
|
||||||
|
- **Não Compartilhar**: Código fonte protegido
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ DISCLAIMER FINAL
|
||||||
|
|
||||||
|
**Ao acessar este repositório, você concorda que:**
|
||||||
|
|
||||||
|
1. **Reconhece** que este é um software proprietário
|
||||||
|
2. **Compromete-se** a não violar os direitos de propriedade intelectual
|
||||||
|
3. **Aceita** as consequências legais por qualquer violação
|
||||||
|
4. **Entende** que o acesso é monitorado e rastreado
|
||||||
|
5. **Concorda** em respeitar todas as leis aplicáveis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Este documento é parte integrante do software e deve ser mantido confidencial.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This source code is protected under international copyright law. All rights
|
||||||
|
reserved and protected by the copyright holders.
|
||||||
|
This file is confidential and only available to authorized individuals with the
|
||||||
|
permission of the copyright holders. If you encounter this file and do not have
|
||||||
|
permission, please contact the copyright holders and delete this file.
|
||||||
|
|
||||||
|
Autor: Daniel Arantes Loverde
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
e# Plano - Tela de Perfil
|
|
||||||
|
|
||||||
Legenda:
|
|
||||||
- 🟢 Concluído
|
|
||||||
- 🟡 Em validação
|
|
||||||
- 🔴 Pendente
|
|
||||||
|
|
||||||
Última atualização: 2026-02-13
|
|
||||||
|
|
||||||
## Entregas
|
|
||||||
|
|
||||||
- 🟢 Redesign do conteúdo da tela de perfil no estilo do modelo (header + cards de menu)
|
|
||||||
- 🟢 Header ajustada para encostar no topo e nas laterais
|
|
||||||
- 🟢 Redução de escala dos itens da lista (ícones, fonte, altura e paddings)
|
|
||||||
- 🟢 Faixa de proteção no rodapé para não deixar conteúdo por trás da tabbar
|
|
||||||
- 🟢 Confirmação com alert padrão ao tocar em "Sair da Conta"
|
|
||||||
- 🟢 Fluxo de editar endereço reutilizando a tela de novo endereço
|
|
||||||
- 🟢 Swipe para excluir endereço com compatibilidade Android (gesto custom)
|
|
||||||
- 🟢 Polimento visual do swipe/delete (sem vazamento de fundo vermelho)
|
|
||||||
|
|
||||||
## Pendências
|
|
||||||
|
|
||||||
- 🟡 Validação visual final no device (iOS e Android) com screenshots finais
|
|
||||||
- 🔴 Definir destinos reais dos itens ainda placeholder:
|
|
||||||
- Meus Cartões
|
|
||||||
- Minhas Avaliações
|
|
||||||
- Cupons de Desconto
|
|
||||||
- Configurações/Ajuda (confirmar nomenclatura final)
|
|
||||||
- 🔴 Corrigir build global do projeto (dependência `LCEssentials` ausente em `StoreDetailView.swift`)
|
|
||||||
|
|
||||||
Autor: Daniel Arantes Loverde
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# Análise Técnica — Arquitetura Skip (PediFoods)
|
|
||||||
|
|
||||||
## Entendimento confirmado
|
|
||||||
|
|
||||||
O projeto usa **Skip com fonte principal em Swift/SwiftUI** e geração/transpilação para Android.
|
|
||||||
|
|
||||||
Diretriz operacional validada:
|
|
||||||
|
|
||||||
1. **iOS é a plataforma prioritária** de desenvolvimento e validação.
|
|
||||||
2. **Android entra depois**, com adaptações isoladas por compilação condicional.
|
|
||||||
3. Toda alteração deve preservar comportamento de iOS e evitar regressão cross-platform.
|
|
||||||
|
|
||||||
## Como o projeto está estruturado
|
|
||||||
|
|
||||||
1. Código compartilhado principal: `Sources/PediFoods/*` (Swift/SwiftUI).
|
|
||||||
2. Entry point iOS: `Darwin/Sources/Main.swift`.
|
|
||||||
3. Entry point Android: `Android/app/src/main/kotlin/Main.kt`.
|
|
||||||
4. Configuração Skip: `Sources/PediFoods/Skip/skip.yml` (`mode: native`).
|
|
||||||
5. Build/transpilação Android via plugin Skip (`skipstone`) no target Swift Package.
|
|
||||||
|
|
||||||
## Evidências no código (padrão já em uso)
|
|
||||||
|
|
||||||
1. Uso extensivo de condicionais:
|
|
||||||
- `#if os(iOS)`
|
|
||||||
- `#if os(Android)`
|
|
||||||
- `#if canImport(UIKit)`
|
|
||||||
2. Dependência iOS-only no `Package.swift`:
|
|
||||||
- `LCEssentials` com `condition: .when(platforms: [.iOS])`
|
|
||||||
3. Exemplo claro de adaptação segura:
|
|
||||||
- `ContentView.swift`: `SnackbarCenter` com implementação distinta para Android e iOS.
|
|
||||||
|
|
||||||
## Regra prática para próximas tarefas
|
|
||||||
|
|
||||||
1. Implementar primeiro para iOS.
|
|
||||||
2. Validar iOS (build/execução/comportamento).
|
|
||||||
3. Só depois adaptar Android, sempre em bloco condicional separado.
|
|
||||||
4. Nunca alterar caminho iOS por causa de ajuste Android.
|
|
||||||
5. Em mudanças de UI de inicialização (ex.: splash), tratar iOS e Android em fluxos independentes.
|
|
||||||
|
|
||||||
## Nota específica para Splash
|
|
||||||
|
|
||||||
Estado atual identificado:
|
|
||||||
|
|
||||||
1. iOS com Launch Screen gerado automaticamente (`INFOPLIST_KEY_UILaunchScreen_Generation = YES`).
|
|
||||||
2. Android sem implementação custom de splash dedicada no `res/values` + tema de launch.
|
|
||||||
|
|
||||||
Implicação:
|
|
||||||
|
|
||||||
1. Mudança de splash para iOS pode ser feita sem tocar Android.
|
|
||||||
2. Quando Android for tratado, deve entrar em camada própria (tema/recursos Android), sem impactar o fluxo iOS.
|
|
||||||
|
|
||||||
Autor: Daniel Arantes Loverde
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// swift-tools-version: 6.1
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "pedi-foods",
|
|
||||||
defaultLocalization: "en",
|
|
||||||
platforms: [.iOS(.v17), .macOS(.v14)],
|
|
||||||
products: [
|
|
||||||
.library(name: "PediFoods", type: .dynamic, targets: ["PediFoods"]),
|
|
||||||
],
|
|
||||||
dependencies: [
|
|
||||||
.package(url: "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials.git", from: "1.0.5")
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
.target(name: "PediFoods", dependencies: [
|
|
||||||
.product(name: "LCEssentials", package: "lcessentials")
|
|
||||||
], resources: [.process("Resources")]),
|
|
||||||
.testTarget(name: "PediFoodsTests", dependencies: ["PediFoods"]),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
1290
PediFoods.xcodeproj/project.pbxproj
Normal file
@@ -2,6 +2,6 @@
|
|||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Darwin/PediFoods.xcodeproj">
|
location = "self:">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
</Workspace>
|
</Workspace>
|
||||||
143
PediFoods.xcodeproj/xcshareddata/xcschemes/PediFoods.xcscheme
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1430"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
runPostActionsOnFailure = "NO">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "B3B5CFF77A8ED019D1299ABB"
|
||||||
|
BuildableName = "PediFoods.app"
|
||||||
|
BlueprintName = "PediFoods"
|
||||||
|
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "NO"
|
||||||
|
buildForProfiling = "NO"
|
||||||
|
buildForArchiving = "NO"
|
||||||
|
buildForAnalyzing = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "3027BAE353B414EC70E2A2AC"
|
||||||
|
BuildableName = "PediFoodsTests.xctest"
|
||||||
|
BlueprintName = "PediFoodsTests"
|
||||||
|
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "NO"
|
||||||
|
buildForProfiling = "NO"
|
||||||
|
buildForArchiving = "NO"
|
||||||
|
buildForAnalyzing = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "66486D25E3A8A75EA8FACE44"
|
||||||
|
BuildableName = "PediFoodsUITests.xctest"
|
||||||
|
BlueprintName = "PediFoodsUITests"
|
||||||
|
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
onlyGenerateCoverageForSpecifiedTargets = "NO">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "B3B5CFF77A8ED019D1299ABB"
|
||||||
|
BuildableName = "PediFoods.app"
|
||||||
|
BlueprintName = "PediFoods"
|
||||||
|
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "3027BAE353B414EC70E2A2AC"
|
||||||
|
BuildableName = "PediFoodsTests.xctest"
|
||||||
|
BlueprintName = "PediFoodsTests"
|
||||||
|
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "66486D25E3A8A75EA8FACE44"
|
||||||
|
BuildableName = "PediFoodsUITests.xctest"
|
||||||
|
BlueprintName = "PediFoodsUITests"
|
||||||
|
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
<CommandLineArguments>
|
||||||
|
</CommandLineArguments>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "B3B5CFF77A8ED019D1299ABB"
|
||||||
|
BuildableName = "PediFoods.app"
|
||||||
|
BlueprintName = "PediFoods"
|
||||||
|
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<CommandLineArguments>
|
||||||
|
</CommandLineArguments>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "B3B5CFF77A8ED019D1299ABB"
|
||||||
|
BuildableName = "PediFoods.app"
|
||||||
|
BlueprintName = "PediFoods"
|
||||||
|
ReferencedContainer = "container:PediFoods.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<CommandLineArguments>
|
||||||
|
</CommandLineArguments>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 835 B After Width: | Height: | Size: 835 B |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 667 KiB After Width: | Height: | Size: 667 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 406 KiB After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 924 KiB After Width: | Height: | Size: 924 KiB |
|
Before Width: | Height: | Size: 951 KiB After Width: | Height: | Size: 951 KiB |
|
Before Width: | Height: | Size: 940 KiB After Width: | Height: | Size: 940 KiB |
|
Before Width: | Height: | Size: 926 KiB After Width: | Height: | Size: 926 KiB |
|
Before Width: | Height: | Size: 799 KiB After Width: | Height: | Size: 799 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1,10 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
#if canImport(UIKit)
|
|
||||||
import UIKit
|
import UIKit
|
||||||
#elseif canImport(AppKit)
|
|
||||||
import AppKit
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum ImageFitMode {
|
enum ImageFitMode {
|
||||||
/// Scale to cover the whole box, cropping whichever axis overflows.
|
/// Scale to cover the whole box, cropping whichever axis overflows.
|
||||||
@@ -22,9 +18,7 @@ struct CachedRemoteImage<Placeholder: View>: View {
|
|||||||
let fitMode: ImageFitMode
|
let fitMode: ImageFitMode
|
||||||
let placeholder: Placeholder
|
let placeholder: Placeholder
|
||||||
|
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
@StateObject var loader = CachedRemoteImageLoader()
|
@StateObject var loader = CachedRemoteImageLoader()
|
||||||
#endif
|
|
||||||
|
|
||||||
init(
|
init(
|
||||||
imageURL: String?,
|
imageURL: String?,
|
||||||
@@ -41,52 +35,23 @@ struct CachedRemoteImage<Placeholder: View>: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
Group {
|
Group {
|
||||||
#if canImport(UIKit)
|
|
||||||
if let uiImage = loader.uiImage {
|
if let uiImage = loader.uiImage {
|
||||||
rendered(image: Image(uiImage: uiImage), pixelSize: uiImage.size, in: geometry.size)
|
rendered(image: Image(uiImage: uiImage), pixelSize: uiImage.size, in: geometry.size)
|
||||||
} else {
|
} else {
|
||||||
placeholder
|
placeholder
|
||||||
}
|
}
|
||||||
#elseif canImport(AppKit)
|
|
||||||
if let nsImage = loader.nsImage {
|
|
||||||
rendered(image: Image(nsImage: nsImage), pixelSize: nsImage.size, in: geometry.size)
|
|
||||||
} else {
|
|
||||||
placeholder
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if let imageURL,
|
|
||||||
let url = URL(string: imageURL) {
|
|
||||||
AsyncImage(url: url) { phase in
|
|
||||||
switch phase {
|
|
||||||
case .success(let image):
|
|
||||||
image
|
|
||||||
.resizable()
|
|
||||||
.scaledToFill()
|
|
||||||
default:
|
|
||||||
placeholder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
placeholder
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||||
.clipped()
|
.clipped()
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
loader.load(imageURL: imageURL, ttl: ttl)
|
loader.load(imageURL: imageURL, ttl: ttl)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
.onChange(of: imageURL) { _, newValue in
|
.onChange(of: imageURL) { _, newValue in
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
loader.load(imageURL: newValue, ttl: ttl)
|
loader.load(imageURL: newValue, ttl: ttl)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func rendered(image: Image, pixelSize: CGSize, in containerSize: CGSize) -> some View {
|
private func rendered(image: Image, pixelSize: CGSize, in containerSize: CGSize) -> some View {
|
||||||
switch fitMode {
|
switch fitMode {
|
||||||
@@ -103,17 +68,11 @@ struct CachedRemoteImage<Placeholder: View>: View {
|
|||||||
.frame(width: containerSize.width, height: containerSize.height)
|
.frame(width: containerSize.width, height: containerSize.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class CachedRemoteImageLoader: ObservableObject {
|
final class CachedRemoteImageLoader: ObservableObject {
|
||||||
#if canImport(UIKit)
|
|
||||||
@Published var uiImage: UIImage?
|
@Published var uiImage: UIImage?
|
||||||
#elseif canImport(AppKit)
|
|
||||||
@Published var nsImage: NSImage?
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private var currentKey: String?
|
private var currentKey: String?
|
||||||
private var task: Task<Void, Never>?
|
private var task: Task<Void, Never>?
|
||||||
@@ -130,50 +89,30 @@ final class CachedRemoteImageLoader: ObservableObject {
|
|||||||
|
|
||||||
task?.cancel()
|
task?.cancel()
|
||||||
|
|
||||||
#if canImport(UIKit)
|
|
||||||
uiImage = nil
|
uiImage = nil
|
||||||
#elseif canImport(AppKit)
|
|
||||||
nsImage = nil
|
|
||||||
#endif
|
|
||||||
|
|
||||||
guard let normalized, normalized.isEmpty == false else { return }
|
guard let normalized, normalized.isEmpty == false else { return }
|
||||||
|
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
let dataCacheKey = Self.dataURLCacheKey(normalized)
|
let dataCacheKey = Self.dataURLCacheKey(normalized)
|
||||||
if let cachedDataImage: PlatformImage = AppContentCache.shared.value(for: dataCacheKey, as: PlatformImage.self) {
|
if let cachedDataImage: PlatformImage = AppContentCache.shared.value(for: dataCacheKey, as: PlatformImage.self) {
|
||||||
#if canImport(UIKit)
|
|
||||||
uiImage = cachedDataImage
|
uiImage = cachedDataImage
|
||||||
#elseif canImport(AppKit)
|
|
||||||
nsImage = cachedDataImage
|
|
||||||
#endif
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let image = Self.imageFromDataURL(normalized) {
|
if let image = Self.imageFromDataURL(normalized) {
|
||||||
AppContentCache.shared.set(image, for: dataCacheKey, ttl: ttl)
|
AppContentCache.shared.set(image, for: dataCacheKey, ttl: ttl)
|
||||||
#if canImport(UIKit)
|
|
||||||
uiImage = image
|
uiImage = image
|
||||||
#elseif canImport(AppKit)
|
|
||||||
nsImage = image
|
|
||||||
#endif
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
guard let url = URL(string: normalized) else { return }
|
guard let url = URL(string: normalized) else { return }
|
||||||
|
|
||||||
task = Task { [weak self] in
|
task = Task { [weak self] in
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
let image = await AppImageCache.shared.image(for: url, ttl: ttl)
|
let image = await AppImageCache.shared.image(for: url, ttl: ttl)
|
||||||
guard Task.isCancelled == false else { return }
|
guard Task.isCancelled == false else { return }
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
#if canImport(UIKit)
|
|
||||||
self?.uiImage = image
|
self?.uiImage = image
|
||||||
#elseif canImport(AppKit)
|
|
||||||
self?.nsImage = image
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +126,6 @@ final class CachedRemoteImageLoader: ObservableObject {
|
|||||||
return "data-image:\(source.count):\(head):\(tail)"
|
return "data-image:\(source.count):\(head):\(tail)"
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
private static func imageFromDataURL(_ source: String) -> PlatformImage? {
|
private static func imageFromDataURL(_ source: String) -> PlatformImage? {
|
||||||
let lower = source.lowercased()
|
let lower = source.lowercased()
|
||||||
guard lower.hasPrefix("data:image"), let commaIndex = source.firstIndex(of: ",") else { return nil }
|
guard lower.hasPrefix("data:image"), let commaIndex = source.firstIndex(of: ",") else { return nil }
|
||||||
@@ -203,14 +141,6 @@ final class CachedRemoteImageLoader: ObservableObject {
|
|||||||
.replacingOccurrences(of: " ", with: "")
|
.replacingOccurrences(of: " ", with: "")
|
||||||
|
|
||||||
guard let data = Data(base64Encoded: payload, options: [.ignoreUnknownCharacters]) else { return nil }
|
guard let data = Data(base64Encoded: payload, options: [.ignoreUnknownCharacters]) else { return nil }
|
||||||
#if canImport(UIKit)
|
|
||||||
return UIImage(data: data)
|
return UIImage(data: data)
|
||||||
#elseif canImport(AppKit)
|
|
||||||
return NSImage(data: data)
|
|
||||||
#else
|
|
||||||
return nil
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
enum DeepLinkRouteEffect: Equatable {
|
||||||
|
case navigateToOrder(OrderRouteContext, tab: MainTab)
|
||||||
|
case none
|
||||||
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@State var root: RootFlow = .main
|
@State var root: RootFlow = .main
|
||||||
@State var selectedTab: MainTab = .home
|
@State var selectedTab: MainTab = .home
|
||||||
@@ -35,7 +40,7 @@ struct ContentView: View {
|
|||||||
if isBootstrappingSession {
|
if isBootstrappingSession {
|
||||||
sessionBootstrapLoadingView
|
sessionBootstrapLoadingView
|
||||||
} else {
|
} else {
|
||||||
MainTabView(selectedTab: $selectedTab, root: $root, tokenStore: tokenStore, appState: $appState)
|
MainTabView(selectedTab: $selectedTab, tokenStore: tokenStore, appState: $appState, enterAuth: enterAuthFlow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,24 +274,41 @@ struct ContentView: View {
|
|||||||
self.pushDeepLinkObserver = nil
|
self.pushDeepLinkObserver = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
/// Pure mapping from a deep-link destination to what `route(to:)` should
|
||||||
private func route(to destination: DeepLinkDestination) {
|
/// do - split out so it's testable without a live `ContentView`
|
||||||
guard root == .main else { return }
|
/// instance (this reads no `@State`, `route(to:)` is what applies the
|
||||||
|
/// result to `appState`/`selectedTab`).
|
||||||
|
func routeEffect(for destination: DeepLinkDestination) -> DeepLinkRouteEffect {
|
||||||
switch destination {
|
switch destination {
|
||||||
case .orderTracking(let orderId, let shortId):
|
case .orderTracking(let orderId, let shortId):
|
||||||
appState.pendingOrderDeepLink = OrderRouteContext(
|
return .navigateToOrder(
|
||||||
|
OrderRouteContext(
|
||||||
orderId: orderId,
|
orderId: orderId,
|
||||||
shortId: shortId,
|
shortId: shortId,
|
||||||
paymentMethod: nil,
|
paymentMethod: nil,
|
||||||
total: nil,
|
total: nil,
|
||||||
intent: .auto
|
intent: .auto
|
||||||
|
),
|
||||||
|
tab: .profile
|
||||||
)
|
)
|
||||||
selectedTab = .profile
|
|
||||||
case .screen(let name, let params):
|
case .screen(let name, let params):
|
||||||
// Promo/coupon screens (participating stores/products, §6's
|
// Promo/coupon screens (participating stores/products, §6's
|
||||||
// `targetScreen` convention) land here once they exist — see
|
// `targetScreen` convention) land here once they exist — see
|
||||||
// decisions/2026-08-04-push-deeplink-routing-contract.md.
|
// decisions/2026-08-04-push-deeplink-routing-contract.md.
|
||||||
logger.debug("Unhandled deep-link screen: \(name, privacy: .public) params=\(params, privacy: .public)")
|
logger.debug("Unhandled deep-link screen: \(name, privacy: .public) params=\(params, privacy: .public)")
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func route(to destination: DeepLinkDestination) {
|
||||||
|
guard root == .main else { return }
|
||||||
|
switch routeEffect(for: destination) {
|
||||||
|
case .navigateToOrder(let context, let tab):
|
||||||
|
appState.pendingOrderDeepLink = context
|
||||||
|
selectedTab = tab
|
||||||
|
case .none:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,6 +407,16 @@ struct ContentView: View {
|
|||||||
isBootstrappingSession = false
|
isBootstrappingSession = false
|
||||||
appState = AppState()
|
appState = AppState()
|
||||||
selectedTab = .home
|
selectedTab = .home
|
||||||
|
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.
|
||||||
|
@MainActor
|
||||||
|
private func enterAuthFlow() {
|
||||||
shouldPrepareAuthEntryAnimation = true
|
shouldPrepareAuthEntryAnimation = true
|
||||||
root = .auth
|
root = .auth
|
||||||
authEntryAnimationToken += 1
|
authEntryAnimationToken += 1
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
#if canImport(UIKit)
|
|
||||||
import UIKit
|
import UIKit
|
||||||
#elseif canImport(AppKit)
|
|
||||||
import AppKit
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// MARK: - Colors
|
// MARK: - Colors
|
||||||
|
|
||||||
@@ -66,13 +62,7 @@ struct AppTypography {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static var isBrandFontAvailable: Bool {
|
private static var isBrandFontAvailable: Bool {
|
||||||
#if canImport(UIKit)
|
UIFont(name: fontFamily, size: 16) != nil
|
||||||
return UIFont(name: fontFamily, size: 16) != nil
|
|
||||||
#elseif canImport(AppKit)
|
|
||||||
return NSFont(name: fontFamily, size: 16) != nil
|
|
||||||
#else
|
|
||||||
return false
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
63
PediFoods/PediFoodsApp.swift
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import OSLog
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
let logger: Logger = Logger(subsystem: "com.br.pedifoods.app", category: "PediFoods")
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct PediFoodsApp: App {
|
||||||
|
@UIApplicationDelegateAdaptor(PediFoodsAppDelegate.self) private var appDelegate
|
||||||
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
.onChange(of: scenePhase) { oldPhase, newPhase in
|
||||||
|
switch newPhase {
|
||||||
|
case .active:
|
||||||
|
logger.debug("onResume")
|
||||||
|
NotificationCenter.default.post(name: .appDidResume, object: nil)
|
||||||
|
case .inactive:
|
||||||
|
logger.debug("onPause")
|
||||||
|
case .background:
|
||||||
|
logger.debug("onStop")
|
||||||
|
@unknown default:
|
||||||
|
logger.debug("unknown scene phase: \(String(describing: newPhase))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PediFoodsAppDelegate: NSObject, UIApplicationDelegate {
|
||||||
|
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||||
|
logger.debug("onInit")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||||
|
logger.debug("onLaunch")
|
||||||
|
Task { @MainActor in
|
||||||
|
PushNotificationCoordinator.shared.startObservingDeviceToken()
|
||||||
|
PushNotificationCoordinator.shared.becomeNotificationCenterDelegate()
|
||||||
|
await PushNotificationCoordinator.shared.refreshRegistrationIfAuthorized()
|
||||||
|
await PushNotificationCoordinator.shared.syncCustomerAttributes()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationWillTerminate(_ application: UIApplication) {
|
||||||
|
logger.debug("onDestroy")
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationDidReceiveMemoryWarning(_ application: UIApplication) {
|
||||||
|
logger.debug("onLowMemory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name("didRegisterForRemoteNotificationsWithDeviceToken"), object: application, userInfo: ["deviceToken": deviceToken])
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) {
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name("didFailToRegisterForRemoteNotificationsWithError"), object: application, userInfo: ["error": error])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,6 @@
|
|||||||
"strings" : {
|
"strings" : {
|
||||||
"" : {
|
"" : {
|
||||||
|
|
||||||
},
|
|
||||||
" " : {
|
|
||||||
"comment" : "A placeholder text used to maintain vertical spacing in the UI.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
},
|
||||||
"- %@" : {
|
"- %@" : {
|
||||||
"comment" : "A text element displaying a negative monetary value, indicating a discount.",
|
"comment" : "A text element displaying a negative monetary value, indicating a discount.",
|
||||||
@@ -311,10 +307,6 @@
|
|||||||
"comment" : "A loading message displayed while bootstrapping the user's session.",
|
"comment" : "A loading message displayed while bootstrapping the user's session.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Carrinho" : {
|
|
||||||
"comment" : "A label for the cart section of the app.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Cartão de Crédito" : {
|
"Cartão de Crédito" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -453,9 +445,6 @@
|
|||||||
"Do que você gostou?" : {
|
"Do que você gostou?" : {
|
||||||
"comment" : "A prompt asking users to mention what they liked about a review.",
|
"comment" : "A prompt asking users to mention what they liked about a review.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
|
||||||
"Editar endereço" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Em uma escala de 0 a 10, qual é a chance de você indicar o Pedi Foods para um amigo?" : {
|
"Em uma escala de 0 a 10, qual é a chance de você indicar o Pedi Foods para um amigo?" : {
|
||||||
"comment" : "A description under the NPS scale question in the order review view.",
|
"comment" : "A description under the NPS scale question in the order review view.",
|
||||||
@@ -575,38 +564,9 @@
|
|||||||
"comment" : "The title of the checkout screen.",
|
"comment" : "The title of the checkout screen.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Hello [%@](https://skip.tools)!" : {
|
|
||||||
"comment" : "Welcome tab contents",
|
|
||||||
"extractionState" : "stale",
|
|
||||||
"localizations" : {
|
|
||||||
"es" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "¡Hola [%@](https://skip.tools)!"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fr" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Bonjour [%@](https://skip.tools)!"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ja" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "こんにちは [%@](https://skip.tools)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zh-Hans" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "你好 [%@](https://skip.tools)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Home" : {
|
"Home" : {
|
||||||
"comment" : "Tab bar item title for the Home tab",
|
"comment" : "Tab bar item title for the Home tab",
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"es" : {
|
"es" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@@ -724,13 +684,6 @@
|
|||||||
},
|
},
|
||||||
"Meus Cartões" : {
|
"Meus Cartões" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Meus Endereços" : {
|
|
||||||
"comment" : "A label displayed at the top of the view.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Meus Pedidos" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Minhas Avaliações" : {
|
"Minhas Avaliações" : {
|
||||||
"comment" : "A button that links to their reviews.",
|
"comment" : "A button that links to their reviews.",
|
||||||
@@ -833,9 +786,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Notificações" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Notificações desativadas" : {
|
"Notificações desativadas" : {
|
||||||
|
|
||||||
@@ -845,10 +795,6 @@
|
|||||||
},
|
},
|
||||||
"Novo Cartão" : {
|
"Novo Cartão" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Novo endereço" : {
|
|
||||||
"comment" : "A label for a form to add a new address.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
},
|
||||||
"Novo por aqui?" : {
|
"Novo por aqui?" : {
|
||||||
"comment" : "A text that appears at the bottom of the screen, inviting users to create an account.",
|
"comment" : "A text that appears at the bottom of the screen, inviting users to create an account.",
|
||||||
@@ -919,19 +865,11 @@
|
|||||||
},
|
},
|
||||||
"Pedido %@" : {
|
"Pedido %@" : {
|
||||||
|
|
||||||
},
|
|
||||||
"PediFoods" : {
|
|
||||||
"comment" : "The name of the app.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
},
|
||||||
"Pedir Novamente" : {
|
"Pedir Novamente" : {
|
||||||
"comment" : "A button that allows a user to order the same items again.",
|
"comment" : "A button that allows a user to order the same items again.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Perfil" : {
|
|
||||||
"comment" : "A label for the \"Profile\" tab in the main tab view.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Pizza de varios sabores" : {
|
"Pizza de varios sabores" : {
|
||||||
"comment" : "A title displayed above the main content of the view.",
|
"comment" : "A title displayed above the main content of the view.",
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
@@ -949,36 +887,6 @@
|
|||||||
"comment" : "The title of the privacy policy section.",
|
"comment" : "The title of the privacy policy section.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Powered by [Skip](https://skip.tools)" : {
|
|
||||||
"comment" : "Link markdown text for the Powered by… label",
|
|
||||||
"extractionState" : "stale",
|
|
||||||
"localizations" : {
|
|
||||||
"es" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Impulsado por [Skip](https://skip.tools)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fr" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Fonctionnalités offertes par [Skip](https://skip.tools)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ja" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "[Skip](https://skip.tools) を使って動かしています"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zh-Hans" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "由[Skip](https://skip.tools)提供支持"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Precisa de ajuda com esse pedido?" : {
|
"Precisa de ajuda com esse pedido?" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -997,10 +905,6 @@
|
|||||||
"comment" : "A label indicating that a particular address is the user's primary address.",
|
"comment" : "A label indicating that a particular address is the user's primary address.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Privacidade" : {
|
|
||||||
"comment" : "The title of the privacy policy screen.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Progresso do Pedido" : {
|
"Progresso do Pedido" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -1008,24 +912,12 @@
|
|||||||
"comment" : "The text for a button that allows a user to re-evaluate an order.",
|
"comment" : "The text for a button that allows a user to re-evaluate an order.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Receber Código" : {
|
|
||||||
"comment" : "A button label that says \"Receive Code\".",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Reenviar código" : {
|
|
||||||
"comment" : "A button that allows a user to request a new OTP code.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
|
||||||
"Remover" : {
|
"Remover" : {
|
||||||
"comment" : "A button that deletes the user's profile picture.",
|
"comment" : "A button that deletes the user's profile picture.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Remover loja dos favoritos" : {
|
"Remover loja dos favoritos" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Resposta da loja" : {
|
|
||||||
"comment" : "A heading for the reply from the store in an order review.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
},
|
||||||
"RESTAURANT" : {
|
"RESTAURANT" : {
|
||||||
|
|
||||||
@@ -1156,9 +1048,6 @@
|
|||||||
"Seu endereço não é atendido por esta loja.\nDeseja trocar mesmo assim?" : {
|
"Seu endereço não é atendido por esta loja.\nDeseja trocar mesmo assim?" : {
|
||||||
"comment" : "An alert that appears when a user tries to place an order from an address that is not served by the store. The alert offers the user the option to either keep the current address or to switch to a different one",
|
"comment" : "An alert that appears when a user tries to place an order from an address that is not served by the store. The alert offers the user the option to either keep the current address or to switch to a different one",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
|
||||||
"Seu pedido está em andamento" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Sim" : {
|
"Sim" : {
|
||||||
"comment" : "The text for a button that confirms an action. In this case, it confirms the user's choice to continue with the order despite the address not being served by the store.",
|
"comment" : "The text for a button that confirms an action. In this case, it confirms the user's choice to continue with the order despite the address not being served by the store.",
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
#if canImport(FoundationNetworking)
|
#if canImport(LCEssentials)
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
#if canImport(LCEssentials) && os(iOS)
|
|
||||||
import LCEssentials
|
import LCEssentials
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -45,7 +42,7 @@ enum NetworkError: Error, LocalizedError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ApiErrorDescriptor {
|
struct ApiErrorDescriptor {
|
||||||
let code: String?
|
let code: String?
|
||||||
let message: String?
|
let message: String?
|
||||||
}
|
}
|
||||||
@@ -102,7 +99,7 @@ final class ApiClient: @unchecked Sendable {
|
|||||||
// resolving — no response, no error, nothing — the UI must still
|
// resolving — no response, no error, nothing — the UI must still
|
||||||
// get an answer so it can stop showing "nothing happened."
|
// get an answer so it can stop showing "nothing happened."
|
||||||
try await withTimeout(seconds: 20) { [self] in
|
try await withTimeout(seconds: 20) { [self] in
|
||||||
#if canImport(LCEssentials) && os(iOS)
|
#if canImport(LCEssentials)
|
||||||
try await sendWithLCEssentials(request)
|
try await sendWithLCEssentials(request)
|
||||||
#else
|
#else
|
||||||
try await sendWithURLSession(request)
|
try await sendWithURLSession(request)
|
||||||
@@ -128,8 +125,8 @@ final class ApiClient: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ApiClient {
|
extension ApiClient {
|
||||||
#if canImport(LCEssentials) && os(iOS)
|
#if canImport(LCEssentials)
|
||||||
func sendWithLCEssentials<T: Decodable>(_ request: ApiRequest) async throws -> T {
|
func sendWithLCEssentials<T: Decodable>(_ request: ApiRequest) async throws -> T {
|
||||||
let urlString = try buildURL(path: request.path, query: request.queryItems, baseURL: request.baseURLOverride ?? ApiConfig.baseURL).absoluteString
|
let urlString = try buildURL(path: request.path, query: request.queryItems, baseURL: request.baseURLOverride ?? ApiConfig.baseURL).absoluteString
|
||||||
let method = request.method
|
let method = request.method
|
||||||
@@ -402,7 +399,7 @@ private extension ApiClient {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(LCEssentials) && os(iOS)
|
#if canImport(LCEssentials)
|
||||||
func serverMessage(from error: NSError) -> String? {
|
func serverMessage(from error: NSError) -> String? {
|
||||||
for key in [NSLocalizedFailureReasonErrorKey, "body", "responseBody", "data", "message"] {
|
for key in [NSLocalizedFailureReasonErrorKey, "body", "responseBody", "data", "message"] {
|
||||||
if let value = error.userInfo[key] as? String,
|
if let value = error.userInfo[key] as? String,
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
#if canImport(UIKit)
|
|
||||||
import UIKit
|
import UIKit
|
||||||
typealias PlatformImage = UIImage
|
typealias PlatformImage = UIImage
|
||||||
#elseif canImport(AppKit)
|
|
||||||
import AppKit
|
|
||||||
typealias PlatformImage = NSImage
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum AppCacheTTL {
|
enum AppCacheTTL {
|
||||||
static let twoHours: TimeInterval = 2 * 60 * 60
|
static let twoHours: TimeInterval = 2 * 60 * 60
|
||||||
@@ -61,7 +56,6 @@ final class AppContentCache: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
final class AppImageCache: @unchecked Sendable {
|
final class AppImageCache: @unchecked Sendable {
|
||||||
static let shared = AppImageCache()
|
static let shared = AppImageCache()
|
||||||
|
|
||||||
@@ -132,23 +126,6 @@ final class AppImageCache: @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func platformImage(from data: Data) -> PlatformImage? {
|
private func platformImage(from data: Data) -> PlatformImage? {
|
||||||
#if canImport(UIKit)
|
UIImage(data: data)
|
||||||
return UIImage(data: data)
|
|
||||||
#elseif canImport(AppKit)
|
|
||||||
return NSImage(data: data)
|
|
||||||
#else
|
|
||||||
return nil
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !(canImport(UIKit) || canImport(AppKit))
|
|
||||||
final class AppImageCache: @unchecked Sendable {
|
|
||||||
static let shared = AppImageCache()
|
|
||||||
|
|
||||||
private init() {}
|
|
||||||
|
|
||||||
func invalidateAll() {}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
#if canImport(FoundationNetworking)
|
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct FeatureControlRawFlag: Codable, Equatable {
|
struct FeatureControlRawFlag: Codable, Equatable {
|
||||||
let enabled: Bool
|
let enabled: Bool
|
||||||
@@ -271,13 +268,11 @@ final class FeatureControlService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func appVersion() -> String {
|
private func appVersion() -> String {
|
||||||
#if canImport(UIKit) || canImport(AppKit)
|
|
||||||
let version = (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String)?
|
let version = (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String)?
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if let version, version.isEmpty == false {
|
if let version, version.isEmpty == false {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return "0.0.0"
|
return "0.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,25 @@ import Foundation
|
|||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
|
|
||||||
|
/// The subset of `CLLocationManager` `LocationService` needs — lets tests
|
||||||
|
/// substitute a fake instead of touching real hardware/OS permission state.
|
||||||
|
@MainActor
|
||||||
|
protocol LocationManaging: AnyObject {
|
||||||
|
var locationManagingDelegate: CLLocationManagerDelegate? { get set }
|
||||||
|
var desiredAccuracy: CLLocationAccuracy { get set }
|
||||||
|
var authorizationStatus: CLAuthorizationStatus { get }
|
||||||
|
var location: CLLocation? { get }
|
||||||
|
func requestWhenInUseAuthorization()
|
||||||
|
func requestLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CLLocationManager: LocationManaging {
|
||||||
|
var locationManagingDelegate: CLLocationManagerDelegate? {
|
||||||
|
get { delegate }
|
||||||
|
set { delegate = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@@ -23,17 +42,22 @@ final class LocationService: NSObject {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
private let manager = CLLocationManager()
|
private let manager: LocationManaging
|
||||||
private var completion: ((LocationResult) -> Void)?
|
private var completion: ((LocationResult) -> Void)?
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
init(manager: LocationManaging = CLLocationManager()) {
|
||||||
|
self.manager = manager
|
||||||
|
super.init()
|
||||||
|
manager.locationManagingDelegate = self
|
||||||
|
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||||
|
}
|
||||||
|
#else
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
#if os(iOS)
|
|
||||||
manager.delegate = self
|
|
||||||
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
func requestLocation(_ completion: @escaping (LocationResult) -> Void) {
|
func requestLocation(_ completion: @escaping (LocationResult) -> Void) {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
#if canImport(FoundationNetworking)
|
|
||||||
import FoundationNetworking
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class OrderRealtimeTracker {
|
final class OrderRealtimeTracker {
|
||||||
@@ -173,6 +173,14 @@ final class PushNotificationCoordinator: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `UNNotification.userInfo` is `[AnyHashable: Any]`, which the compiler
|
||||||
|
/// can't prove `Sendable` — but it's an immutable payload handed to us
|
||||||
|
/// once by the OS, so crossing the actor boundary with it is safe in
|
||||||
|
/// practice.
|
||||||
|
private struct UncheckedSendableBox<Value>: @unchecked Sendable {
|
||||||
|
let value: Value
|
||||||
|
}
|
||||||
|
|
||||||
extension PushNotificationCoordinator: UNUserNotificationCenterDelegate {
|
extension PushNotificationCoordinator: UNUserNotificationCenterDelegate {
|
||||||
/// Without a delegate, iOS silently drops push notifications while the
|
/// Without a delegate, iOS silently drops push notifications while the
|
||||||
/// app is foregrounded — this is what makes them display as a banner too.
|
/// app is foregrounded — this is what makes them display as a banner too.
|
||||||
@@ -189,9 +197,9 @@ extension PushNotificationCoordinator: UNUserNotificationCenterDelegate {
|
|||||||
didReceive response: UNNotificationResponse,
|
didReceive response: UNNotificationResponse,
|
||||||
withCompletionHandler completionHandler: @escaping () -> Void
|
withCompletionHandler completionHandler: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
let userInfo = response.notification.request.content.userInfo
|
let userInfo = UncheckedSendableBox(value: response.notification.request.content.userInfo)
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
PushNotificationCoordinator.shared.handleTap(userInfo: userInfo)
|
PushNotificationCoordinator.shared.handleTap(userInfo: userInfo.value)
|
||||||
}
|
}
|
||||||
completionHandler()
|
completionHandler()
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,12 @@ private struct PersistedOrderReviewsState: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum SessionStateStore {
|
enum SessionStateStore {
|
||||||
|
/// Swappable for tests (isolated `UserDefaults(suiteName:)`), defaults
|
||||||
|
/// to the real app defaults in production. `UserDefaults` is thread-safe
|
||||||
|
/// on its own; `nonisolated(unsafe)` only opts out of Swift 6's static-
|
||||||
|
/// mutable-state check for the var itself, tests set it once up front.
|
||||||
|
nonisolated(unsafe) static var defaults: UserDefaults = .standard
|
||||||
|
|
||||||
private static let legacyAddressKey = "session.address.state.v1"
|
private static let legacyAddressKey = "session.address.state.v1"
|
||||||
private static let addressKeyPrefix = "session.address.state.v2."
|
private static let addressKeyPrefix = "session.address.state.v2."
|
||||||
private static let activeUserKey = "session.active.user.v1"
|
private static let activeUserKey = "session.active.user.v1"
|
||||||
@@ -94,12 +100,12 @@ enum SessionStateStore {
|
|||||||
/// See docs/api/push-notifications-integration-guide.md §2b — avoid
|
/// See docs/api/push-notifications-integration-guide.md §2b — avoid
|
||||||
/// re-prompting the order-tracking fallback alert on every screen visit.
|
/// re-prompting the order-tracking fallback alert on every screen visit.
|
||||||
static func shouldPromptPushOptIn() -> Bool {
|
static func shouldPromptPushOptIn() -> Bool {
|
||||||
guard let last = UserDefaults.standard.object(forKey: pushOptInPromptKey) as? Date else { return true }
|
guard let last = defaults.object(forKey: pushOptInPromptKey) as? Date else { return true }
|
||||||
return Date().timeIntervalSince(last) > pushOptInCooldown
|
return Date().timeIntervalSince(last) > pushOptInCooldown
|
||||||
}
|
}
|
||||||
|
|
||||||
static func recordPushOptInPrompted() {
|
static func recordPushOptInPrompted() {
|
||||||
UserDefaults.standard.set(Date(), forKey: pushOptInPromptKey)
|
defaults.set(Date(), forKey: pushOptInPromptKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func makeUserKey(profileId: String?, email: String?) -> String? {
|
static func makeUserKey(profileId: String?, email: String?) -> String? {
|
||||||
@@ -123,14 +129,14 @@ enum SessionStateStore {
|
|||||||
static func setActiveUserKey(_ userKey: String?) {
|
static func setActiveUserKey(_ userKey: String?) {
|
||||||
let trimmed = (userKey ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = (userKey ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if trimmed.isEmpty {
|
if trimmed.isEmpty {
|
||||||
UserDefaults.standard.removeObject(forKey: activeUserKey)
|
defaults.removeObject(forKey: activeUserKey)
|
||||||
} else {
|
} else {
|
||||||
UserDefaults.standard.set(trimmed, forKey: activeUserKey)
|
defaults.set(trimmed, forKey: activeUserKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadActiveUserKey() -> String? {
|
static func loadActiveUserKey() -> String? {
|
||||||
let value = UserDefaults.standard.string(forKey: activeUserKey)?
|
let value = defaults.string(forKey: activeUserKey)?
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if let value, value.isEmpty == false {
|
if let value, value.isEmpty == false {
|
||||||
return value
|
return value
|
||||||
@@ -146,7 +152,6 @@ enum SessionStateStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func loadAddress() -> AddressState? {
|
static func loadAddress() -> AddressState? {
|
||||||
let defaults = UserDefaults.standard
|
|
||||||
let activeKey = loadActiveUserKey()
|
let activeKey = loadActiveUserKey()
|
||||||
let scopedKey = addressStorageKey(for: activeKey)
|
let scopedKey = addressStorageKey(for: activeKey)
|
||||||
|
|
||||||
@@ -214,17 +219,16 @@ enum SessionStateStore {
|
|||||||
longitude: state.longitude
|
longitude: state.longitude
|
||||||
)
|
)
|
||||||
guard let data = try? JSONEncoder().encode(payload) else { return }
|
guard let data = try? JSONEncoder().encode(payload) else { return }
|
||||||
UserDefaults.standard.set(data, forKey: addressStorageKey(for: nil))
|
defaults.set(data, forKey: addressStorageKey(for: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func clearAddress() {
|
static func clearAddress() {
|
||||||
let defaults = UserDefaults.standard
|
|
||||||
defaults.removeObject(forKey: addressStorageKey(for: nil))
|
defaults.removeObject(forKey: addressStorageKey(for: nil))
|
||||||
defaults.removeObject(forKey: legacyAddressKey)
|
defaults.removeObject(forKey: legacyAddressKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func clearActiveUser() {
|
static func clearActiveUser() {
|
||||||
UserDefaults.standard.removeObject(forKey: activeUserKey)
|
defaults.removeObject(forKey: activeUserKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func cartStorageKey(for userKey: String?) -> String {
|
private static func cartStorageKey(for userKey: String?) -> String {
|
||||||
@@ -235,7 +239,6 @@ enum SessionStateStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func loadCart() -> CartState? {
|
static func loadCart() -> CartState? {
|
||||||
let defaults = UserDefaults.standard
|
|
||||||
let key = cartStorageKey(for: nil)
|
let key = cartStorageKey(for: nil)
|
||||||
|
|
||||||
if let data = defaults.data(forKey: key),
|
if let data = defaults.data(forKey: key),
|
||||||
@@ -297,11 +300,11 @@ enum SessionStateStore {
|
|||||||
total: state.total
|
total: state.total
|
||||||
)
|
)
|
||||||
guard let data = try? JSONEncoder().encode(payload) else { return }
|
guard let data = try? JSONEncoder().encode(payload) else { return }
|
||||||
UserDefaults.standard.set(data, forKey: cartStorageKey(for: nil))
|
defaults.set(data, forKey: cartStorageKey(for: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func clearCart() {
|
static func clearCart() {
|
||||||
UserDefaults.standard.removeObject(forKey: cartStorageKey(for: nil))
|
defaults.removeObject(forKey: cartStorageKey(for: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func trackedOrdersStorageKey(for userKey: String?) -> String {
|
private static func trackedOrdersStorageKey(for userKey: String?) -> String {
|
||||||
@@ -336,7 +339,6 @@ enum SessionStateStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func loadTrackedOrders() -> [PublicOrderResult] {
|
static func loadTrackedOrders() -> [PublicOrderResult] {
|
||||||
let defaults = UserDefaults.standard
|
|
||||||
let key = trackedOrdersStorageKey(for: nil)
|
let key = trackedOrdersStorageKey(for: nil)
|
||||||
guard let data = defaults.data(forKey: key),
|
guard let data = defaults.data(forKey: key),
|
||||||
let decoded = try? JSONDecoder().decode(PersistedTrackedOrdersState.self, from: data) else {
|
let decoded = try? JSONDecoder().decode(PersistedTrackedOrdersState.self, from: data) else {
|
||||||
@@ -361,11 +363,11 @@ enum SessionStateStore {
|
|||||||
}
|
}
|
||||||
let payload = PersistedTrackedOrdersState(orders: orders)
|
let payload = PersistedTrackedOrdersState(orders: orders)
|
||||||
guard let data = try? JSONEncoder().encode(payload) else { return }
|
guard let data = try? JSONEncoder().encode(payload) else { return }
|
||||||
UserDefaults.standard.set(data, forKey: trackedOrdersStorageKey(for: nil))
|
defaults.set(data, forKey: trackedOrdersStorageKey(for: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func clearTrackedOrders() {
|
static func clearTrackedOrders() {
|
||||||
UserDefaults.standard.removeObject(forKey: trackedOrdersStorageKey(for: nil))
|
defaults.removeObject(forKey: trackedOrdersStorageKey(for: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func savePendingCartOrderId(_ orderId: String) {
|
static func savePendingCartOrderId(_ orderId: String) {
|
||||||
@@ -374,11 +376,11 @@ enum SessionStateStore {
|
|||||||
clearPendingCartOrder()
|
clearPendingCartOrder()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
UserDefaults.standard.set(clean, forKey: pendingCartOrderStorageKey(for: nil))
|
defaults.set(clean, forKey: pendingCartOrderStorageKey(for: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadPendingCartOrderId() -> String? {
|
static func loadPendingCartOrderId() -> String? {
|
||||||
let value = UserDefaults.standard.string(forKey: pendingCartOrderStorageKey(for: nil))?
|
let value = defaults.string(forKey: pendingCartOrderStorageKey(for: nil))?
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if let value, value.isEmpty == false {
|
if let value, value.isEmpty == false {
|
||||||
return value
|
return value
|
||||||
@@ -387,12 +389,12 @@ enum SessionStateStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func clearPendingCartOrder() {
|
static func clearPendingCartOrder() {
|
||||||
UserDefaults.standard.removeObject(forKey: pendingCartOrderStorageKey(for: nil))
|
defaults.removeObject(forKey: pendingCartOrderStorageKey(for: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadOrderReviews() -> [OrderReviewRecord] {
|
static func loadOrderReviews() -> [OrderReviewRecord] {
|
||||||
let key = orderReviewsStorageKey(for: nil)
|
let key = orderReviewsStorageKey(for: nil)
|
||||||
guard let data = UserDefaults.standard.data(forKey: key),
|
guard let data = defaults.data(forKey: key),
|
||||||
let decoded = try? JSONDecoder().decode(PersistedOrderReviewsState.self, from: data) else {
|
let decoded = try? JSONDecoder().decode(PersistedOrderReviewsState.self, from: data) else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -429,12 +431,12 @@ enum SessionStateStore {
|
|||||||
|
|
||||||
let payload = PersistedOrderReviewsState(reviews: reviews)
|
let payload = PersistedOrderReviewsState(reviews: reviews)
|
||||||
guard let data = try? JSONEncoder().encode(payload) else { return }
|
guard let data = try? JSONEncoder().encode(payload) else { return }
|
||||||
UserDefaults.standard.set(data, forKey: orderReviewsStorageKey(for: nil))
|
defaults.set(data, forKey: orderReviewsStorageKey(for: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func loadOrderReviewDraft(orderId: String) -> OrderReviewDraftState? {
|
static func loadOrderReviewDraft(orderId: String) -> OrderReviewDraftState? {
|
||||||
let key = orderReviewDraftStorageKey(for: orderId, userKey: nil)
|
let key = orderReviewDraftStorageKey(for: orderId, userKey: nil)
|
||||||
guard let data = UserDefaults.standard.data(forKey: key),
|
guard let data = defaults.data(forKey: key),
|
||||||
let draft = try? JSONDecoder().decode(OrderReviewDraftState.self, from: data) else {
|
let draft = try? JSONDecoder().decode(OrderReviewDraftState.self, from: data) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -444,11 +446,11 @@ enum SessionStateStore {
|
|||||||
static func saveOrderReviewDraft(_ draft: OrderReviewDraftState) {
|
static func saveOrderReviewDraft(_ draft: OrderReviewDraftState) {
|
||||||
let key = orderReviewDraftStorageKey(for: draft.orderId, userKey: nil)
|
let key = orderReviewDraftStorageKey(for: draft.orderId, userKey: nil)
|
||||||
guard let data = try? JSONEncoder().encode(draft) else { return }
|
guard let data = try? JSONEncoder().encode(draft) else { return }
|
||||||
UserDefaults.standard.set(data, forKey: key)
|
defaults.set(data, forKey: key)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func clearOrderReviewDraft(orderId: String) {
|
static func clearOrderReviewDraft(orderId: String) {
|
||||||
let key = orderReviewDraftStorageKey(for: orderId, userKey: nil)
|
let key = orderReviewDraftStorageKey(for: orderId, userKey: nil)
|
||||||
UserDefaults.standard.removeObject(forKey: key)
|
defaults.removeObject(forKey: key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||