migration
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
- name: Write App Store Connect API key
|
||||
run: |
|
||||
cat > Darwin/fastlane/apikey.json <<EOF
|
||||
cat > fastlane/apikey.json <<EOF
|
||||
{
|
||||
"key_id": "${{ secrets.ASC_KEY_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 default-keychain -d user -s ~/Library/Keychains/ci-signing.keychain-db
|
||||
security find-identity -v -p codesigning
|
||||
cd Darwin
|
||||
fastlane beta
|
||||
|
||||
- name: Clean up API key
|
||||
if: always()
|
||||
run: rm -f Darwin/fastlane/apikey.json
|
||||
run: rm -f fastlane/apikey.json
|
||||
|
||||
sleep-macos-vm:
|
||||
needs: build-and-upload
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Write App Store Connect API key
|
||||
run: |
|
||||
cat > Darwin/fastlane/apikey.json <<EOF
|
||||
cat > fastlane/apikey.json <<EOF
|
||||
{
|
||||
"key_id": "${{ secrets.ASC_KEY_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
|
||||
echo "--- find-identity ---"
|
||||
security find-identity -v -p codesigning
|
||||
cd Darwin
|
||||
fastlane release
|
||||
|
||||
- name: Clean up API key
|
||||
if: always()
|
||||
run: rm -f Darwin/fastlane/apikey.json
|
||||
run: rm -f fastlane/apikey.json
|
||||
|
||||
sleep-macos-vm:
|
||||
needs: build-archive-deliver
|
||||
|
||||
@@ -30,8 +30,8 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
xcodebuild test \
|
||||
-project Darwin/PediFoods.xcodeproj \
|
||||
-scheme "PediFoods App" \
|
||||
-project PediFoods.xcodeproj \
|
||||
-scheme "PediFoods" \
|
||||
-destination "platform=iOS Simulator,name=iPhone 16,OS=latest"
|
||||
|
||||
sleep-macos-vm:
|
||||
|
||||
8
.gitignore
vendored
@@ -7,14 +7,6 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# gradle properties
|
||||
local.properties
|
||||
.gradle/
|
||||
.android/
|
||||
.kotlin/
|
||||
Android/app/keystore.jks
|
||||
Android/app/keystore.properties
|
||||
|
||||
xcodebuild*.log
|
||||
|
||||
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
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Darwin/PediFoods.xcodeproj">
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</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 SwiftUI
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
enum ImageFitMode {
|
||||
/// Scale to cover the whole box, cropping whichever axis overflows.
|
||||
@@ -22,9 +18,7 @@ struct CachedRemoteImage<Placeholder: View>: View {
|
||||
let fitMode: ImageFitMode
|
||||
let placeholder: Placeholder
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
@StateObject var loader = CachedRemoteImageLoader()
|
||||
#endif
|
||||
|
||||
init(
|
||||
imageURL: String?,
|
||||
@@ -41,52 +35,23 @@ struct CachedRemoteImage<Placeholder: View>: View {
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
Group {
|
||||
#if canImport(UIKit)
|
||||
if let uiImage = loader.uiImage {
|
||||
rendered(image: Image(uiImage: uiImage), pixelSize: uiImage.size, in: geometry.size)
|
||||
} else {
|
||||
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)
|
||||
.clipped()
|
||||
}
|
||||
.onAppear {
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
loader.load(imageURL: imageURL, ttl: ttl)
|
||||
#endif
|
||||
}
|
||||
.onChange(of: imageURL) { _, newValue in
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
loader.load(imageURL: newValue, ttl: ttl)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
@ViewBuilder
|
||||
private func rendered(image: Image, pixelSize: CGSize, in containerSize: CGSize) -> some View {
|
||||
switch fitMode {
|
||||
@@ -103,17 +68,11 @@ struct CachedRemoteImage<Placeholder: View>: View {
|
||||
.frame(width: containerSize.width, height: containerSize.height)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
@MainActor
|
||||
final class CachedRemoteImageLoader: ObservableObject {
|
||||
#if canImport(UIKit)
|
||||
@Published var uiImage: UIImage?
|
||||
#elseif canImport(AppKit)
|
||||
@Published var nsImage: NSImage?
|
||||
#endif
|
||||
|
||||
private var currentKey: String?
|
||||
private var task: Task<Void, Never>?
|
||||
@@ -130,50 +89,30 @@ final class CachedRemoteImageLoader: ObservableObject {
|
||||
|
||||
task?.cancel()
|
||||
|
||||
#if canImport(UIKit)
|
||||
uiImage = nil
|
||||
#elseif canImport(AppKit)
|
||||
nsImage = nil
|
||||
#endif
|
||||
|
||||
guard let normalized, normalized.isEmpty == false else { return }
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
let dataCacheKey = Self.dataURLCacheKey(normalized)
|
||||
if let cachedDataImage: PlatformImage = AppContentCache.shared.value(for: dataCacheKey, as: PlatformImage.self) {
|
||||
#if canImport(UIKit)
|
||||
uiImage = cachedDataImage
|
||||
#elseif canImport(AppKit)
|
||||
nsImage = cachedDataImage
|
||||
#endif
|
||||
return
|
||||
}
|
||||
|
||||
if let image = Self.imageFromDataURL(normalized) {
|
||||
AppContentCache.shared.set(image, for: dataCacheKey, ttl: ttl)
|
||||
#if canImport(UIKit)
|
||||
uiImage = image
|
||||
#elseif canImport(AppKit)
|
||||
nsImage = image
|
||||
#endif
|
||||
return
|
||||
}
|
||||
#endif
|
||||
|
||||
guard let url = URL(string: normalized) else { return }
|
||||
|
||||
task = Task { [weak self] in
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
let image = await AppImageCache.shared.image(for: url, ttl: ttl)
|
||||
guard Task.isCancelled == false else { return }
|
||||
await MainActor.run {
|
||||
#if canImport(UIKit)
|
||||
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)"
|
||||
}
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
private static func imageFromDataURL(_ source: String) -> PlatformImage? {
|
||||
let lower = source.lowercased()
|
||||
guard lower.hasPrefix("data:image"), let commaIndex = source.firstIndex(of: ",") else { return nil }
|
||||
@@ -203,14 +141,6 @@ final class CachedRemoteImageLoader: ObservableObject {
|
||||
.replacingOccurrences(of: " ", with: "")
|
||||
|
||||
guard let data = Data(base64Encoded: payload, options: [.ignoreUnknownCharacters]) else { return nil }
|
||||
#if canImport(UIKit)
|
||||
return UIImage(data: data)
|
||||
#elseif canImport(AppKit)
|
||||
return NSImage(data: data)
|
||||
#else
|
||||
return nil
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -1,6 +1,11 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum DeepLinkRouteEffect: Equatable {
|
||||
case navigateToOrder(OrderRouteContext, tab: MainTab)
|
||||
case none
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@State var root: RootFlow = .main
|
||||
@State var selectedTab: MainTab = .home
|
||||
@@ -35,7 +40,7 @@ struct ContentView: View {
|
||||
if isBootstrappingSession {
|
||||
sessionBootstrapLoadingView
|
||||
} 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
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func route(to destination: DeepLinkDestination) {
|
||||
guard root == .main else { return }
|
||||
/// Pure mapping from a deep-link destination to what `route(to:)` should
|
||||
/// do - split out so it's testable without a live `ContentView`
|
||||
/// instance (this reads no `@State`, `route(to:)` is what applies the
|
||||
/// result to `appState`/`selectedTab`).
|
||||
func routeEffect(for destination: DeepLinkDestination) -> DeepLinkRouteEffect {
|
||||
switch destination {
|
||||
case .orderTracking(let orderId, let shortId):
|
||||
appState.pendingOrderDeepLink = OrderRouteContext(
|
||||
orderId: orderId,
|
||||
shortId: shortId,
|
||||
paymentMethod: nil,
|
||||
total: nil,
|
||||
intent: .auto
|
||||
return .navigateToOrder(
|
||||
OrderRouteContext(
|
||||
orderId: orderId,
|
||||
shortId: shortId,
|
||||
paymentMethod: nil,
|
||||
total: nil,
|
||||
intent: .auto
|
||||
),
|
||||
tab: .profile
|
||||
)
|
||||
selectedTab = .profile
|
||||
case .screen(let name, let params):
|
||||
// Promo/coupon screens (participating stores/products, §6's
|
||||
// `targetScreen` convention) land here once they exist — see
|
||||
// decisions/2026-08-04-push-deeplink-routing-contract.md.
|
||||
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
|
||||
appState = AppState()
|
||||
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
|
||||
root = .auth
|
||||
authEntryAnimationToken += 1
|
||||
@@ -1,9 +1,5 @@
|
||||
import SwiftUI
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
// MARK: - Colors
|
||||
|
||||
@@ -66,13 +62,7 @@ struct AppTypography {
|
||||
}
|
||||
|
||||
private static var isBrandFontAvailable: Bool {
|
||||
#if canImport(UIKit)
|
||||
return UIFont(name: fontFamily, size: 16) != nil
|
||||
#elseif canImport(AppKit)
|
||||
return NSFont(name: fontFamily, size: 16) != nil
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
UIFont(name: fontFamily, size: 16) != nil
|
||||
}
|
||||
}
|
||||
|
||||
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" : {
|
||||
"" : {
|
||||
|
||||
},
|
||||
" " : {
|
||||
"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.",
|
||||
@@ -311,10 +307,6 @@
|
||||
"comment" : "A loading message displayed while bootstrapping the user's session.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Carrinho" : {
|
||||
"comment" : "A label for the cart section of the app.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Cartão de Crédito" : {
|
||||
|
||||
},
|
||||
@@ -453,9 +445,6 @@
|
||||
"Do que você gostou?" : {
|
||||
"comment" : "A prompt asking users to mention what they liked about a review.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Editar endereço" : {
|
||||
|
||||
},
|
||||
"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.",
|
||||
@@ -575,38 +564,9 @@
|
||||
"comment" : "The title of the checkout screen.",
|
||||
"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" : {
|
||||
"comment" : "Tab bar item title for the Home tab",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
@@ -724,13 +684,6 @@
|
||||
},
|
||||
"Meus Cartões" : {
|
||||
|
||||
},
|
||||
"Meus Endereços" : {
|
||||
"comment" : "A label displayed at the top of the view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Meus Pedidos" : {
|
||||
|
||||
},
|
||||
"Minhas Avaliações" : {
|
||||
"comment" : "A button that links to their reviews.",
|
||||
@@ -833,9 +786,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Notificações" : {
|
||||
|
||||
},
|
||||
"Notificações desativadas" : {
|
||||
|
||||
@@ -845,10 +795,6 @@
|
||||
},
|
||||
"Novo Cartão" : {
|
||||
|
||||
},
|
||||
"Novo endereço" : {
|
||||
"comment" : "A label for a form to add a new address.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Novo por aqui?" : {
|
||||
"comment" : "A text that appears at the bottom of the screen, inviting users to create an account.",
|
||||
@@ -919,19 +865,11 @@
|
||||
},
|
||||
"Pedido %@" : {
|
||||
|
||||
},
|
||||
"PediFoods" : {
|
||||
"comment" : "The name of the app.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Pedir Novamente" : {
|
||||
"comment" : "A button that allows a user to order the same items again.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Perfil" : {
|
||||
"comment" : "A label for the \"Profile\" tab in the main tab view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Pizza de varios sabores" : {
|
||||
"comment" : "A title displayed above the main content of the view.",
|
||||
"extractionState" : "stale",
|
||||
@@ -949,36 +887,6 @@
|
||||
"comment" : "The title of the privacy policy section.",
|
||||
"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?" : {
|
||||
|
||||
},
|
||||
@@ -997,10 +905,6 @@
|
||||
"comment" : "A label indicating that a particular address is the user's primary address.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Privacidade" : {
|
||||
"comment" : "The title of the privacy policy screen.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Progresso do Pedido" : {
|
||||
|
||||
},
|
||||
@@ -1008,24 +912,12 @@
|
||||
"comment" : "The text for a button that allows a user to re-evaluate an order.",
|
||||
"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" : {
|
||||
"comment" : "A button that deletes the user's profile picture.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Remover loja dos favoritos" : {
|
||||
|
||||
},
|
||||
"Resposta da loja" : {
|
||||
"comment" : "A heading for the reply from the store in an order review.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"RESTAURANT" : {
|
||||
|
||||
@@ -1156,9 +1048,6 @@
|
||||
"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",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Seu pedido está em andamento" : {
|
||||
|
||||
},
|
||||
"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.",
|
||||
@@ -1,8 +1,5 @@
|
||||
import Foundation
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
#if canImport(LCEssentials) && os(iOS)
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
@@ -45,7 +42,7 @@ enum NetworkError: Error, LocalizedError {
|
||||
}
|
||||
}
|
||||
|
||||
private struct ApiErrorDescriptor {
|
||||
struct ApiErrorDescriptor {
|
||||
let code: String?
|
||||
let message: String?
|
||||
}
|
||||
@@ -102,7 +99,7 @@ final class ApiClient: @unchecked Sendable {
|
||||
// resolving — no response, no error, nothing — the UI must still
|
||||
// get an answer so it can stop showing "nothing happened."
|
||||
try await withTimeout(seconds: 20) { [self] in
|
||||
#if canImport(LCEssentials) && os(iOS)
|
||||
#if canImport(LCEssentials)
|
||||
try await sendWithLCEssentials(request)
|
||||
#else
|
||||
try await sendWithURLSession(request)
|
||||
@@ -128,8 +125,8 @@ final class ApiClient: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
private extension ApiClient {
|
||||
#if canImport(LCEssentials) && os(iOS)
|
||||
extension ApiClient {
|
||||
#if canImport(LCEssentials)
|
||||
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 method = request.method
|
||||
@@ -402,7 +399,7 @@ private extension ApiClient {
|
||||
return false
|
||||
}
|
||||
|
||||
#if canImport(LCEssentials) && os(iOS)
|
||||
#if canImport(LCEssentials)
|
||||
func serverMessage(from error: NSError) -> String? {
|
||||
for key in [NSLocalizedFailureReasonErrorKey, "body", "responseBody", "data", "message"] {
|
||||
if let value = error.userInfo[key] as? String,
|
||||
@@ -1,11 +1,6 @@
|
||||
import Foundation
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
typealias PlatformImage = UIImage
|
||||
#elseif canImport(AppKit)
|
||||
import AppKit
|
||||
typealias PlatformImage = NSImage
|
||||
#endif
|
||||
|
||||
enum AppCacheTTL {
|
||||
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 {
|
||||
static let shared = AppImageCache()
|
||||
|
||||
@@ -132,23 +126,6 @@ final class AppImageCache: @unchecked Sendable {
|
||||
}
|
||||
|
||||
private func platformImage(from data: Data) -> PlatformImage? {
|
||||
#if canImport(UIKit)
|
||||
return UIImage(data: data)
|
||||
#elseif canImport(AppKit)
|
||||
return NSImage(data: data)
|
||||
#else
|
||||
return nil
|
||||
#endif
|
||||
UIImage(data: data)
|
||||
}
|
||||
}
|
||||
#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
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
struct FeatureControlRawFlag: Codable, Equatable {
|
||||
let enabled: Bool
|
||||
@@ -271,13 +268,11 @@ final class FeatureControlService {
|
||||
}
|
||||
|
||||
private func appVersion() -> String {
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
let version = (Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let version, version.isEmpty == false {
|
||||
return version
|
||||
}
|
||||
#endif
|
||||
return "0.0.0"
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,25 @@ import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
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
|
||||
|
||||
@MainActor
|
||||
@@ -23,17 +42,22 @@ final class LocationService: NSObject {
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
private let manager = CLLocationManager()
|
||||
private let manager: LocationManaging
|
||||
private var completion: ((LocationResult) -> Void)?
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
init(manager: LocationManaging = CLLocationManager()) {
|
||||
self.manager = manager
|
||||
super.init()
|
||||
manager.locationManagingDelegate = self
|
||||
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||
}
|
||||
#else
|
||||
override init() {
|
||||
super.init()
|
||||
#if os(iOS)
|
||||
manager.delegate = self
|
||||
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
func requestLocation(_ completion: @escaping (LocationResult) -> Void) {
|
||||
#if os(iOS)
|
||||
@@ -1,7 +1,4 @@
|
||||
import Foundation
|
||||
#if canImport(FoundationNetworking)
|
||||
import FoundationNetworking
|
||||
#endif
|
||||
|
||||
@MainActor
|
||||
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 {
|
||||
/// Without a delegate, iOS silently drops push notifications while the
|
||||
/// app is foregrounded — this is what makes them display as a banner too.
|
||||
@@ -189,9 +197,9 @@ extension PushNotificationCoordinator: UNUserNotificationCenterDelegate {
|
||||
didReceive response: UNNotificationResponse,
|
||||
withCompletionHandler completionHandler: @escaping () -> Void
|
||||
) {
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
let userInfo = UncheckedSendableBox(value: response.notification.request.content.userInfo)
|
||||
Task { @MainActor in
|
||||
PushNotificationCoordinator.shared.handleTap(userInfo: userInfo)
|
||||
PushNotificationCoordinator.shared.handleTap(userInfo: userInfo.value)
|
||||
}
|
||||
completionHandler()
|
||||
}
|
||||
@@ -80,6 +80,12 @@ private struct PersistedOrderReviewsState: Codable {
|
||||
}
|
||||
|
||||
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 addressKeyPrefix = "session.address.state.v2."
|
||||
private static let activeUserKey = "session.active.user.v1"
|
||||
@@ -94,12 +100,12 @@ enum SessionStateStore {
|
||||
/// See docs/api/push-notifications-integration-guide.md §2b — avoid
|
||||
/// re-prompting the order-tracking fallback alert on every screen visit.
|
||||
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
|
||||
}
|
||||
|
||||
static func recordPushOptInPrompted() {
|
||||
UserDefaults.standard.set(Date(), forKey: pushOptInPromptKey)
|
||||
defaults.set(Date(), forKey: pushOptInPromptKey)
|
||||
}
|
||||
|
||||
static func makeUserKey(profileId: String?, email: String?) -> String? {
|
||||
@@ -123,14 +129,14 @@ enum SessionStateStore {
|
||||
static func setActiveUserKey(_ userKey: String?) {
|
||||
let trimmed = (userKey ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if trimmed.isEmpty {
|
||||
UserDefaults.standard.removeObject(forKey: activeUserKey)
|
||||
defaults.removeObject(forKey: activeUserKey)
|
||||
} else {
|
||||
UserDefaults.standard.set(trimmed, forKey: activeUserKey)
|
||||
defaults.set(trimmed, forKey: activeUserKey)
|
||||
}
|
||||
}
|
||||
|
||||
static func loadActiveUserKey() -> String? {
|
||||
let value = UserDefaults.standard.string(forKey: activeUserKey)?
|
||||
let value = defaults.string(forKey: activeUserKey)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let value, value.isEmpty == false {
|
||||
return value
|
||||
@@ -146,7 +152,6 @@ enum SessionStateStore {
|
||||
}
|
||||
|
||||
static func loadAddress() -> AddressState? {
|
||||
let defaults = UserDefaults.standard
|
||||
let activeKey = loadActiveUserKey()
|
||||
let scopedKey = addressStorageKey(for: activeKey)
|
||||
|
||||
@@ -214,17 +219,16 @@ enum SessionStateStore {
|
||||
longitude: state.longitude
|
||||
)
|
||||
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() {
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.removeObject(forKey: addressStorageKey(for: nil))
|
||||
defaults.removeObject(forKey: legacyAddressKey)
|
||||
}
|
||||
|
||||
static func clearActiveUser() {
|
||||
UserDefaults.standard.removeObject(forKey: activeUserKey)
|
||||
defaults.removeObject(forKey: activeUserKey)
|
||||
}
|
||||
|
||||
private static func cartStorageKey(for userKey: String?) -> String {
|
||||
@@ -235,7 +239,6 @@ enum SessionStateStore {
|
||||
}
|
||||
|
||||
static func loadCart() -> CartState? {
|
||||
let defaults = UserDefaults.standard
|
||||
let key = cartStorageKey(for: nil)
|
||||
|
||||
if let data = defaults.data(forKey: key),
|
||||
@@ -297,11 +300,11 @@ enum SessionStateStore {
|
||||
total: state.total
|
||||
)
|
||||
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() {
|
||||
UserDefaults.standard.removeObject(forKey: cartStorageKey(for: nil))
|
||||
defaults.removeObject(forKey: cartStorageKey(for: nil))
|
||||
}
|
||||
|
||||
private static func trackedOrdersStorageKey(for userKey: String?) -> String {
|
||||
@@ -336,7 +339,6 @@ enum SessionStateStore {
|
||||
}
|
||||
|
||||
static func loadTrackedOrders() -> [PublicOrderResult] {
|
||||
let defaults = UserDefaults.standard
|
||||
let key = trackedOrdersStorageKey(for: nil)
|
||||
guard let data = defaults.data(forKey: key),
|
||||
let decoded = try? JSONDecoder().decode(PersistedTrackedOrdersState.self, from: data) else {
|
||||
@@ -361,11 +363,11 @@ enum SessionStateStore {
|
||||
}
|
||||
let payload = PersistedTrackedOrdersState(orders: orders)
|
||||
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() {
|
||||
UserDefaults.standard.removeObject(forKey: trackedOrdersStorageKey(for: nil))
|
||||
defaults.removeObject(forKey: trackedOrdersStorageKey(for: nil))
|
||||
}
|
||||
|
||||
static func savePendingCartOrderId(_ orderId: String) {
|
||||
@@ -374,11 +376,11 @@ enum SessionStateStore {
|
||||
clearPendingCartOrder()
|
||||
return
|
||||
}
|
||||
UserDefaults.standard.set(clean, forKey: pendingCartOrderStorageKey(for: nil))
|
||||
defaults.set(clean, forKey: pendingCartOrderStorageKey(for: nil))
|
||||
}
|
||||
|
||||
static func loadPendingCartOrderId() -> String? {
|
||||
let value = UserDefaults.standard.string(forKey: pendingCartOrderStorageKey(for: nil))?
|
||||
let value = defaults.string(forKey: pendingCartOrderStorageKey(for: nil))?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let value, value.isEmpty == false {
|
||||
return value
|
||||
@@ -387,12 +389,12 @@ enum SessionStateStore {
|
||||
}
|
||||
|
||||
static func clearPendingCartOrder() {
|
||||
UserDefaults.standard.removeObject(forKey: pendingCartOrderStorageKey(for: nil))
|
||||
defaults.removeObject(forKey: pendingCartOrderStorageKey(for: nil))
|
||||
}
|
||||
|
||||
static func loadOrderReviews() -> [OrderReviewRecord] {
|
||||
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 {
|
||||
return []
|
||||
}
|
||||
@@ -429,12 +431,12 @@ enum SessionStateStore {
|
||||
|
||||
let payload = PersistedOrderReviewsState(reviews: reviews)
|
||||
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? {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@@ -444,11 +446,11 @@ enum SessionStateStore {
|
||||
static func saveOrderReviewDraft(_ draft: OrderReviewDraftState) {
|
||||
let key = orderReviewDraftStorageKey(for: draft.orderId, userKey: nil)
|
||||
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) {
|
||||
let key = orderReviewDraftStorageKey(for: orderId, userKey: nil)
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
defaults.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||