diff --git a/.github/workflows/auto-fix-lint-format-commit.yml b/.github/workflows/auto-fix-lint-format-commit.yml deleted file mode 100644 index f6b8269ea2..0000000000 --- a/.github/workflows/auto-fix-lint-format-commit.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Auto Fix Lint and Format - -on: - pull_request_target: - types: [opened, synchronize] - -jobs: - auto-fix: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.head_ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: "pnpm" - - - name: Install dependencies - run: pnpm install - - - name: Run linter and fix issues - run: | - export NODE_OPTIONS="--max_old_space_size=16384" - pnpm run lint:fix - - - name: Run formatter - run: pnpm run format - - - name: Check for changes - id: check_changes - run: | - git diff --exit-code || echo "has_changes=true" >> $GITHUB_ENV - - - name: Commit and push changes - if: steps.check_changes.outputs.has_changes == 'true' || env.has_changes == 'true' - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: "chore: auto-fix linting and formatting issues" - commit_options: "--no-verify" - file_pattern: "." - - - name: Add PR comment - if: steps.check_changes.outputs.has_changes == 'true' || env.has_changes == 'true' - uses: actions/github-script@v7 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'Linting and formatting issues were automatically fixed. Please review the changes.' - }); diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts index 7e38d2b807..5c87b11d0a 100644 --- a/apps/mobile/app.config.ts +++ b/apps/mobile/app.config.ts @@ -147,6 +147,7 @@ export default ({ config }: ConfigContext): ExpoConfig => { }, ], "expo-background-task", + "expo-share-intent", ], } diff --git a/apps/mobile/ios/Folo.xcodeproj/project.pbxproj b/apps/mobile/ios/Folo.xcodeproj/project.pbxproj index 9dcc5a222d..c8fb12e2ed 100644 --- a/apps/mobile/ios/Folo.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/Folo.xcodeproj/project.pbxproj @@ -7,33 +7,55 @@ objects = { /* Begin PBXBuildFile section */ - 08804409FAE316B2F4286290 /* Pods_Folo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2C9A37CAFE9FFED2FCC141E3 /* Pods_Folo.framework */; }; - 1274C3CE4776411EBE7CEAE8 /* rn-web in Resources */ = {isa = PBXBuildFile; fileRef = B69598E8ACBE445EA3C3DCEE /* rn-web */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 21D47042A21541638A8813FF /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 771F66ABFAD64B3D89ABAB8A /* GoogleService-Info.plist */; }; + 320C9B6283A558FACAB1F077 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FEFB77576C2248B994FBDC2F /* PrivacyInfo.xcprivacy */; }; 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; - 948BDAD26FE9D66B8735BBD0 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CEF90D1FA529DB2CC2AE941 /* ExpoModulesProvider.swift */; }; - A6D4133120E003E1031C2B48 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = BB62D7D685AF319E0A030204 /* PrivacyInfo.xcprivacy */; }; + 49E6152678E64D0F94E41CB2 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 37D265EE3D784D71BB2A1D40 /* MainInterface.storyboard */; }; + 68D26842EA194BFDB08A6A4C /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2F2C62A5E0443479CE1BC1B /* ShareViewController.swift */; }; + 8DB5D03574E32D6AEF460BF4 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE6DFA75757F61EFB9FE4C4D /* ExpoModulesProvider.swift */; }; + A64C4D1F7DFC4F8EB4DB1F20 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = FC82B5696EE74D37ABBC11A0 /* GoogleService-Info.plist */; }; + AE539BC6827CFB582DA95D14 /* Pods_Folo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A92EA1D99570EEE67CF3C16 /* Pods_Folo.framework */; }; BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; + BC17B94C251D4DAB8EFCE14B /* rn-web in Resources */ = {isa = PBXBuildFile; fileRef = 41DD004D89664106AF26882E /* rn-web */; }; + D1D29D5EDCB1438EA5B52E69 /* ShareExtensionPreprocessor.js in Resources */ = {isa = PBXBuildFile; fileRef = 9F635367F99642D0B8439EE3 /* ShareExtensionPreprocessor.js */; }; + E67F80FC976E4D099819B4C8 /* ShareExtension.appex in Copy Files */ = {isa = PBXBuildFile; fileRef = 8E3AE256327246A8BC759523 /* ShareExtension.appex */; }; F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + F6883DCF3F4749F7B35E85BE /* Copy Files */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + E67F80FC976E4D099819B4C8 /* ShareExtension.appex in Copy Files */, + ); + name = "Copy Files"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 13B07F961A680F5B00A75B9A /* Folo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Folo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Folo/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Folo/Info.plist; sourceTree = ""; }; - 1CEF90D1FA529DB2CC2AE941 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Folo/ExpoModulesProvider.swift"; sourceTree = ""; }; - 2C9A37CAFE9FFED2FCC141E3 /* Pods_Folo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Folo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5D1548572A564A709411B1D2 /* Pods-Folo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Folo.release.xcconfig"; path = "Target Support Files/Pods-Folo/Pods-Folo.release.xcconfig"; sourceTree = ""; }; - 771F66ABFAD64B3D89ABAB8A /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Folo/GoogleService-Info.plist"; sourceTree = ""; }; - 84F18C4CB344992039541AAC /* Pods-Folo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Folo.debug.xcconfig"; path = "Target Support Files/Pods-Folo/Pods-Folo.debug.xcconfig"; sourceTree = ""; }; + 2DCF96DC58493B0192531ED6 /* Pods-Folo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Folo.release.xcconfig"; path = "Target Support Files/Pods-Folo/Pods-Folo.release.xcconfig"; sourceTree = ""; }; + 37D265EE3D784D71BB2A1D40 /* MainInterface.storyboard */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MainInterface.storyboard; path = ShareExtension/MainInterface.storyboard; sourceTree = ""; }; + 41DD004D89664106AF26882E /* rn-web */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "rn-web"; path = "../../../out/rn-web"; sourceTree = ""; }; + 5A92EA1D99570EEE67CF3C16 /* Pods_Folo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Folo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8E3AE256327246A8BC759523 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; fileEncoding = 9; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F635367F99642D0B8439EE3 /* ShareExtensionPreprocessor.js */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = ShareExtensionPreprocessor.js; path = ShareExtension/ShareExtensionPreprocessor.js; sourceTree = ""; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Folo/SplashScreen.storyboard; sourceTree = ""; }; - B69598E8ACBE445EA3C3DCEE /* rn-web */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "rn-web"; path = "../../../out/rn-web"; sourceTree = ""; }; + AE6DFA75757F61EFB9FE4C4D /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Folo/ExpoModulesProvider.swift"; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; - BB62D7D685AF319E0A030204 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Folo/PrivacyInfo.xcprivacy; sourceTree = ""; }; + C2F2C62A5E0443479CE1BC1B /* ShareViewController.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = ShareViewController.swift; path = ShareExtension/ShareViewController.swift; sourceTree = ""; }; + EBA10F66956B3F2B469ECF20 /* Pods-Folo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Folo.debug.xcconfig"; path = "Target Support Files/Pods-Folo/Pods-Folo.debug.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Folo/AppDelegate.swift; sourceTree = ""; }; F11748442D0722820044C1D9 /* Folo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Folo-Bridging-Header.h"; path = "Folo/Folo-Bridging-Header.h"; sourceTree = ""; }; + FC82B5696EE74D37ABBC11A0 /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Folo/GoogleService-Info.plist"; sourceTree = ""; }; + FEFB77576C2248B994FBDC2F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = PrivacyInfo.xcprivacy; path = ShareExtension/PrivacyInfo.xcprivacy; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -41,7 +63,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 08804409FAE316B2F4286290 /* Pods_Folo.framework in Frameworks */, + AE539BC6827CFB582DA95D14 /* Pods_Folo.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -57,16 +79,7 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB61A68108700A75B9A /* Info.plist */, AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, - 771F66ABFAD64B3D89ABAB8A /* GoogleService-Info.plist */, - BB62D7D685AF319E0A030204 /* PrivacyInfo.xcprivacy */, - ); - name = Folo; - sourceTree = ""; - }; - 2329C4DD95ED4964546416B9 /* Folo */ = { - isa = PBXGroup; - children = ( - 1CEF90D1FA529DB2CC2AE941 /* ExpoModulesProvider.swift */, + FC82B5696EE74D37ABBC11A0 /* GoogleService-Info.plist */, ); name = Folo; sourceTree = ""; @@ -75,34 +88,34 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 2C9A37CAFE9FFED2FCC141E3 /* Pods_Folo.framework */, + 5A92EA1D99570EEE67CF3C16 /* Pods_Folo.framework */, ); name = Frameworks; sourceTree = ""; }; - 486BB19B76483417F230E142 /* Pods */ = { + 3A5943D2DC04222133711A9C /* ExpoModulesProviders */ = { isa = PBXGroup; children = ( - 84F18C4CB344992039541AAC /* Pods-Folo.debug.xcconfig */, - 5D1548572A564A709411B1D2 /* Pods-Folo.release.xcconfig */, + 67E2623C6E6025274DB28B03 /* Folo */, ); - path = Pods; + name = ExpoModulesProviders; sourceTree = ""; }; - 5A694C761377C5AE81D5B04E /* ExpoModulesProviders */ = { + 426DBD4BB0F2A55C930D86FD /* Pods */ = { isa = PBXGroup; children = ( - 2329C4DD95ED4964546416B9 /* Folo */, + EBA10F66956B3F2B469ECF20 /* Pods-Folo.debug.xcconfig */, + 2DCF96DC58493B0192531ED6 /* Pods-Folo.release.xcconfig */, ); - name = ExpoModulesProviders; + path = Pods; sourceTree = ""; }; - 62F56456C9AC4A5EA82DF7AB /* Assets */ = { + 67E2623C6E6025274DB28B03 /* Folo */ = { isa = PBXGroup; children = ( - B69598E8ACBE445EA3C3DCEE /* rn-web */, + AE6DFA75757F61EFB9FE4C4D /* ExpoModulesProvider.swift */, ); - name = Assets; + name = Folo; sourceTree = ""; }; 832341AE1AAA6A7D00B99B32 /* Libraries */ = { @@ -119,9 +132,10 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, - 62F56456C9AC4A5EA82DF7AB /* Assets */, - 486BB19B76483417F230E142 /* Pods */, - 5A694C761377C5AE81D5B04E /* ExpoModulesProviders */, + D003C5535DFD4F2DA4FB9DBF /* Assets */, + 426DBD4BB0F2A55C930D86FD /* Pods */, + 3A5943D2DC04222133711A9C /* ExpoModulesProviders */, + A6884C6F2DD38F3D00FEF9C4 /* Recovered References */, ); indentWidth = 2; sourceTree = ""; @@ -132,10 +146,22 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* Folo.app */, + 8E3AE256327246A8BC759523 /* ShareExtension.appex */, ); name = Products; sourceTree = ""; }; + A6884C6F2DD38F3D00FEF9C4 /* Recovered References */ = { + isa = PBXGroup; + children = ( + FEFB77576C2248B994FBDC2F /* PrivacyInfo.xcprivacy */, + C2F2C62A5E0443479CE1BC1B /* ShareViewController.swift */, + 9F635367F99642D0B8439EE3 /* ShareExtensionPreprocessor.js */, + 37D265EE3D784D71BB2A1D40 /* MainInterface.storyboard */, + ); + name = "Recovered References"; + sourceTree = ""; + }; BB2F792B24A3F905000567C9 /* Supporting */ = { isa = PBXGroup; children = ( @@ -145,6 +171,14 @@ path = Folo/Supporting; sourceTree = ""; }; + D003C5535DFD4F2DA4FB9DBF /* Assets */ = { + isa = PBXGroup; + children = ( + 41DD004D89664106AF26882E /* rn-web */, + ); + name = Assets; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -153,15 +187,16 @@ buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Folo" */; buildPhases = ( 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, - E2A317A7E9D9C06F986E7E62 /* [Expo] Configure project */, + 5E890B1668811587A29AD449 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, - 6E4ADCBDA5B760296BAE78FA /* [CP] Embed Pods Frameworks */, - 8F2D99FDEA6998C9D98BD7E0 /* [CP-User] [RNFB] Core Configuration */, - 8DF9409D54556D15DD94C0B5 /* [CP-User] [RNFB] Crashlytics Configuration */, + F6883DCF3F4749F7B35E85BE /* Copy Files */, + 9F255C46DB3464731515199A /* [CP] Embed Pods Frameworks */, + 500EDCF2DA15CE389233A756 /* [CP-User] [RNFB] Core Configuration */, + B21A3B61F927817CE097558E /* [CP-User] [RNFB] Crashlytics Configuration */, ); buildRules = ( ); @@ -172,6 +207,22 @@ productReference = 13B07F961A680F5B00A75B9A /* Folo.app */; productType = "com.apple.product-type.application"; }; + DC5BFD054E2447199A9DF0F6 /* ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 71A60495F1CF455A86204BC3 /* Build configuration list for PBXNativeTarget "ShareExtension" */; + buildPhases = ( + 79ED032316E44498843E8100 /* Sources */, + 21A077D08F00435088248C78 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ShareExtension; + productName = ShareExtension; + productReference = 8E3AE256327246A8BC759523 /* ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -181,7 +232,13 @@ LastUpgradeCheck = 1130; TargetAttributes = { 13B07F861A680F5B00A75B9A = { + DevelopmentTeam = 492J8Q67PF; LastSwiftMigration = 1250; + ProvisioningStyle = Automatic; + }; + DC5BFD054E2447199A9DF0F6 = { + DevelopmentTeam = 492J8Q67PF; + ProvisioningStyle = Automatic; }; }; }; @@ -199,6 +256,7 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* Folo */, + DC5BFD054E2447199A9DF0F6 /* ShareExtension */, ); }; /* End PBXProject section */ @@ -211,9 +269,18 @@ BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, - 21D47042A21541638A8813FF /* GoogleService-Info.plist in Resources */, - 1274C3CE4776411EBE7CEAE8 /* rn-web in Resources */, - A6D4133120E003E1031C2B48 /* PrivacyInfo.xcprivacy in Resources */, + A64C4D1F7DFC4F8EB4DB1F20 /* GoogleService-Info.plist in Resources */, + BC17B94C251D4DAB8EFCE14B /* rn-web in Resources */, + 320C9B6283A558FACAB1F077 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 21A077D08F00435088248C78 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D1D29D5EDCB1438EA5B52E69 /* ShareExtensionPreprocessor.js in Resources */, + 49E6152678E64D0F94E41CB2 /* MainInterface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -257,23 +324,37 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 6E4ADCBDA5B760296BAE78FA /* [CP] Embed Pods Frameworks */ = { + 500EDCF2DA15CE389233A756 /* [CP-User] [RNFB] Core Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Folo/Pods-Folo-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + ); + name = "[CP-User] [RNFB] Core Configuration"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + }; + 5E890B1668811587A29AD449 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Folo/Pods-Folo-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Folo/expo-configure-project.sh\"\n"; }; 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; @@ -363,51 +444,37 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Folo/Pods-Folo-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 8DF9409D54556D15DD94C0B5 /* [CP-User] [RNFB] Crashlytics Configuration */ = { + 9F255C46DB3464731515199A /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", - "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", - ); - name = "[CP-User] [RNFB] Crashlytics Configuration"; - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n"; - }; - 8F2D99FDEA6998C9D98BD7E0 /* [CP-User] [RNFB] Core Configuration */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( + "${PODS_ROOT}/Target Support Files/Pods-Folo/Pods-Folo-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - inputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); - name = "[CP-User] [RNFB] Core Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Folo/Pods-Folo-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - E2A317A7E9D9C06F986E7E62 /* [Expo] Configure project */ = { + B21A3B61F927817CE097558E /* [CP-User] [RNFB] Crashlytics Configuration */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}", + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); - outputPaths = ( - ); + name = "[CP-User] [RNFB] Crashlytics Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Folo/expo-configure-project.sh\"\n"; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -417,7 +484,15 @@ buildActionMask = 2147483647; files = ( F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */, - 948BDAD26FE9D66B8735BBD0 /* ExpoModulesProvider.swift in Sources */, + 8DB5D03574E32D6AEF460BF4 /* ExpoModulesProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 79ED032316E44498843E8100 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 68D26842EA194BFDB08A6A4C /* ShareViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -426,11 +501,13 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 84F18C4CB344992039541AAC /* Pods-Folo.debug.xcconfig */; + baseConfigurationReference = EBA10F66956B3F2B469ECF20 /* Pods-Folo.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Folo/Folo.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 492J8Q67PF; ENABLE_BITCODE = NO; @@ -463,11 +540,13 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5D1548572A564A709411B1D2 /* Pods-Folo.release.xcconfig */; + baseConfigurationReference = 2DCF96DC58493B0192531ED6 /* Pods-Folo.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Folo/Folo.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 492J8Q67PF; INFOPLIST_FILE = Folo/Info.plist; @@ -492,6 +571,33 @@ }; name = Release; }; + 6C898F778ACB447E96031765 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 492J8Q67PF; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "ShareExtension/ShareExtension-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 0.1.8; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = "is.follow.share-extension"; + PRODUCT_NAME = ShareExtension; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 83CBBA201A601CBA00E9B192 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -638,6 +744,37 @@ }; name = Release; }; + EBD219503C0E44A9941F2620 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 492J8Q67PF; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "ShareExtension/ShareExtension-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 0.1.8; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = "is.follow.share-extension"; + PRODUCT_NAME = ShareExtension; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -650,6 +787,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 71A60495F1CF455A86204BC3 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EBD219503C0E44A9941F2620 /* Debug */, + 6C898F778ACB447E96031765 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Folo" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/apps/mobile/ios/Folo/Folo.entitlements b/apps/mobile/ios/Folo/Folo.entitlements index 3f2ea618bc..a320a316be 100644 --- a/apps/mobile/ios/Folo/Folo.entitlements +++ b/apps/mobile/ios/Folo/Folo.entitlements @@ -8,5 +8,9 @@ Default + com.apple.security.application-groups + + group.is.follow + \ No newline at end of file diff --git a/apps/mobile/ios/Folo/Info.plist b/apps/mobile/ios/Folo/Info.plist index 8e687f617a..6a291b830a 100644 --- a/apps/mobile/ios/Folo/Info.plist +++ b/apps/mobile/ios/Folo/Info.plist @@ -2,6 +2,8 @@ + AppGroupIdentifier + group.is.follow BGTaskSchedulerPermittedIdentifiers com.expo.modules.backgroundtask.processing diff --git a/apps/mobile/ios/ShareExtension/MainInterface.storyboard b/apps/mobile/ios/ShareExtension/MainInterface.storyboard new file mode 100644 index 0000000000..5a695aa730 --- /dev/null +++ b/apps/mobile/ios/ShareExtension/MainInterface.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/mobile/ios/ShareExtension/PrivacyInfo.xcprivacy b/apps/mobile/ios/ShareExtension/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..703a17fe9e --- /dev/null +++ b/apps/mobile/ios/ShareExtension/PrivacyInfo.xcprivacy @@ -0,0 +1,21 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + \ No newline at end of file diff --git a/apps/mobile/ios/ShareExtension/ShareExtension-Info.plist b/apps/mobile/ios/ShareExtension/ShareExtension-Info.plist new file mode 100644 index 0000000000..fbfae79cfe --- /dev/null +++ b/apps/mobile/ios/ShareExtension/ShareExtension-Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleName + $(PRODUCT_NAME) + CFBundleDisplayName + Folo - Share Extension + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + NSExtensionActivationSupportsWebPageWithMaxCount + 1 + + NSExtensionJavaScriptPreprocessingFile + ShareExtensionPreprocessor + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + AppGroupIdentifier + group.is.follow + + \ No newline at end of file diff --git a/apps/mobile/ios/ShareExtension/ShareExtension.entitlements b/apps/mobile/ios/ShareExtension/ShareExtension.entitlements new file mode 100644 index 0000000000..22ea98405b --- /dev/null +++ b/apps/mobile/ios/ShareExtension/ShareExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.is.follow + + + \ No newline at end of file diff --git a/apps/mobile/ios/ShareExtension/ShareExtensionPreprocessor.js b/apps/mobile/ios/ShareExtension/ShareExtensionPreprocessor.js new file mode 100644 index 0000000000..c2b886b729 --- /dev/null +++ b/apps/mobile/ios/ShareExtension/ShareExtensionPreprocessor.js @@ -0,0 +1,28 @@ +class ShareExtensionPreprocessor { + run({ completionFunction }) { + // Extract meta tags and image sources from the document + const metas = { + title: document.title, + }; + + // Get all meta elements + const metaElements = document.querySelectorAll("meta"); + for (const meta of metaElements) { + const name = meta.getAttribute("name") || meta.getAttribute("property"); + const content = meta.getAttribute("content"); + + if (name && content) { + metas[name] = content; + } + } + + + + // Call the completion function with the extracted data + completionFunction({ + baseURI: document.baseURI, + meta: JSON.stringify(metas), + }); + } +} +var ExtensionPreprocessingJS = new ShareExtensionPreprocessor(); diff --git a/apps/mobile/ios/ShareExtension/ShareViewController.swift b/apps/mobile/ios/ShareExtension/ShareViewController.swift new file mode 100644 index 0000000000..721c332d49 --- /dev/null +++ b/apps/mobile/ios/ShareExtension/ShareViewController.swift @@ -0,0 +1,660 @@ +/*! + * Native module created for Expo Share Intent (https://github.com/achorein/expo-share-intent) + * author: achorein (https://github.com/achorein) + * inspired by : + * - https://ajith-ab.github.io/react-native-receive-sharing-intent/docs/ios#create-share-extension + */ +import MobileCoreServices +import Photos +import Social +import UIKit + +class ShareViewController: UIViewController { + let hostAppGroupIdentifier = "group.is.follow" + let shareProtocol = "follow" + let sharedKey = "followShareKey" + var sharedMedia: [SharedMediaFile] = [] + var sharedWebUrl: [WebUrl] = [] + var sharedText: [String] = [] + let imageContentType: String = UTType.image.identifier + let videoContentType: String = UTType.movie.identifier + let textContentType: String = UTType.text.identifier + let urlContentType: String = UTType.url.identifier + let propertyListType: String = UTType.propertyList.identifier + let fileURLType: String = UTType.fileURL.identifier + let pdfContentType: String = UTType.pdf.identifier + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + Task { + guard let extensionContext = self.extensionContext, + let content = extensionContext.inputItems.first as? NSExtensionItem, + let attachments = content.attachments + else { + dismissWithError(message: "No content found") + return + } + for (index, attachment) in (attachments).enumerated() { + if attachment.hasItemConformingToTypeIdentifier(imageContentType) { + await handleImages(content: content, attachment: attachment, index: index) + } else if attachment.hasItemConformingToTypeIdentifier(videoContentType) { + await handleVideos(content: content, attachment: attachment, index: index) + } else if attachment.hasItemConformingToTypeIdentifier(fileURLType) { + await handleFiles(content: content, attachment: attachment, index: index) + } else if attachment.hasItemConformingToTypeIdentifier(pdfContentType) { + await handlePdf(content: content, attachment: attachment, index: index) + } else if attachment.hasItemConformingToTypeIdentifier(propertyListType) { + await handlePrepocessing(content: content, attachment: attachment, index: index) + } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) { + await handleUrl(content: content, attachment: attachment, index: index) + } else if attachment.hasItemConformingToTypeIdentifier(textContentType) { + await handleText(content: content, attachment: attachment, index: index) + } else { + NSLog("[ERROR] content type not handle !\(String(describing: content))") + dismissWithError(message: "content type not handle \(String(describing: content)))") + } + } + } + } + + private func handleText(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async { + Task.detached { + if let item = try! await attachment.loadItem(forTypeIdentifier: self.textContentType) + as? String + { + Task { @MainActor in + + self.sharedText.append(item) + // If this is the last item, save sharedText in userDefaults and redirect to host app + if index == (content.attachments?.count)! - 1 { + let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier) + userDefaults?.set(self.sharedText, forKey: self.sharedKey) + userDefaults?.synchronize() + self.redirectToHostApp(type: .text) + } + + } + } else { + NSLog("[ERROR] Cannot load text content !\(String(describing: content))") + await self.dismissWithError( + message: "Cannot load text content \(String(describing: content))") + } + } + } + + private func handleUrl(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async { + Task.detached { + if let item = try! await attachment.loadItem(forTypeIdentifier: self.urlContentType) as? URL { + Task { @MainActor in + + self.sharedWebUrl.append(WebUrl(url: item.absoluteString, meta: "")) + // If this is the last item, save sharedText in userDefaults and redirect to host app + if index == (content.attachments?.count)! - 1 { + let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier) + userDefaults?.set(self.toData(data: self.sharedWebUrl), forKey: self.sharedKey) + userDefaults?.synchronize() + self.redirectToHostApp(type: .weburl) + } + + } + } else { + NSLog("[ERROR] Cannot load url content !\(String(describing: content))") + await self.dismissWithError( + message: "Cannot load url content \(String(describing: content))") + } + } + } + + private func handlePrepocessing(content: NSExtensionItem, attachment: NSItemProvider, index: Int) + async + { + Task.detached { + if let item = try! await attachment.loadItem( + forTypeIdentifier: self.propertyListType, options: nil) + as? NSDictionary + { + Task { @MainActor in + + if let results = item[NSExtensionJavaScriptPreprocessingResultsKey] + as? NSDictionary + { + NSLog( + "[DEBUG] NSExtensionJavaScriptPreprocessingResultsKey \(String(describing: results))" + ) + self.sharedWebUrl.append( + WebUrl(url: results["baseURI"] as! String, meta: results["meta"] as! String)) + // If this is the last item, save sharedText in userDefaults and redirect to host app + if index == (content.attachments?.count)! - 1 { + let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier) + userDefaults?.set(self.toData(data: self.sharedWebUrl), forKey: self.sharedKey) + userDefaults?.synchronize() + self.redirectToHostApp(type: .weburl) + } + } else { + NSLog("[ERROR] Cannot load preprocessing results !\(String(describing: content))") + self.dismissWithError( + message: "Cannot load preprocessing results \(String(describing: content))") + } + + } + } else { + NSLog("[ERROR] Cannot load preprocessing content !\(String(describing: content))") + await self.dismissWithError( + message: "Cannot load preprocessing content \(String(describing: content))") + } + } + } + + private func handleImages(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async + { + Task.detached { + if let item = try? await attachment.loadItem(forTypeIdentifier: self.imageContentType) { + Task { @MainActor in + + var url: URL? = nil + if let dataURL = item as? URL { + url = dataURL + } else if let imageData = item as? UIImage { + url = self.saveScreenshot(imageData) + } + + var pixelWidth: Int? = nil + var pixelHeight: Int? = nil + if let imageSource = CGImageSourceCreateWithURL(url! as CFURL, nil) { + if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) + as Dictionary? + { + pixelWidth = imageProperties[kCGImagePropertyPixelWidth] as? Int + pixelHeight = imageProperties[kCGImagePropertyPixelHeight] as? Int + // Check orientation and flip size if required + if let orientationNumber = imageProperties[kCGImagePropertyOrientation] as! CFNumber? + { + var orientation: Int = 0 + CFNumberGetValue(orientationNumber, .intType, &orientation) + if orientation > 4 { + let temp: Int? = pixelWidth + pixelWidth = pixelHeight + pixelHeight = temp + } + } + } + } + + // Always copy + let fileName = self.getFileName(from: url!, type: .image) + let fileExtension = self.getExtension(from: url!, type: .image) + let fileSize = self.getFileSize(from: url!) + let mimeType = url!.mimeType(ext: fileExtension) + let newName = "\(UUID().uuidString).\(fileExtension)" + let newPath = FileManager.default + .containerURL( + forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier)! + .appendingPathComponent(newName) + let copied = self.copyFile(at: url!, to: newPath) + if copied { + self.sharedMedia.append( + SharedMediaFile( + path: newPath.absoluteString, thumbnail: nil, fileName: fileName, + fileSize: fileSize, width: pixelWidth, height: pixelHeight, duration: nil, + mimeType: mimeType, type: .image)) + } + + // If this is the last item, save imagesData in userDefaults and redirect to host app + if index == (content.attachments?.count)! - 1 { + let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier) + userDefaults?.set(self.toData(data: self.sharedMedia), forKey: self.sharedKey) + userDefaults?.synchronize() + self.redirectToHostApp(type: .media) + } + + } + } else { + NSLog("[ERROR] Cannot load image content !\(String(describing: content))") + await self.dismissWithError( + message: "Cannot load image content \(String(describing: content))") + } + } + } + + private func documentDirectoryPath() -> URL? { + let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return path.first + } + + private func saveScreenshot(_ image: UIImage) -> URL? { + var screenshotURL: URL? = nil + if let screenshotData = image.pngData(), + let screenshotPath = documentDirectoryPath()?.appendingPathComponent("screenshot.png") + { + try? screenshotData.write(to: screenshotPath) + screenshotURL = screenshotPath + } + return screenshotURL + } + + private func handleVideos(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async + { + Task.detached { + if let url = try? await attachment.loadItem(forTypeIdentifier: self.videoContentType) as? URL + { + Task { @MainActor in + + // Always copy + let fileName = self.getFileName(from: url, type: .video) + let fileExtension = self.getExtension(from: url, type: .video) + let fileSize = self.getFileSize(from: url) + let mimeType = url.mimeType(ext: fileExtension) + let newName = "\(UUID().uuidString).\(fileExtension)" + let newPath = FileManager.default + .containerURL( + forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier)! + .appendingPathComponent(newName) + let copied = self.copyFile(at: url, to: newPath) + if copied { + guard + let sharedFile = self.getSharedMediaFile( + forVideo: newPath, fileName: fileName, fileSize: fileSize, mimeType: mimeType) + else { + return + } + self.sharedMedia.append(sharedFile) + } + + // If this is the last item, save imagesData in userDefaults and redirect to host app + if index == (content.attachments?.count)! - 1 { + let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier) + userDefaults?.set(self.toData(data: self.sharedMedia), forKey: self.sharedKey) + userDefaults?.synchronize() + self.redirectToHostApp(type: .media) + } + + } + } else { + NSLog("[ERROR] Cannot load video content !\(String(describing: content))") + await self.dismissWithError( + message: "Cannot load video content \(String(describing: content))") + } + } + } + + private func handlePdf(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async { + Task.detached { + if let url = try? await attachment.loadItem(forTypeIdentifier: self.pdfContentType) as? URL { + Task { @MainActor in + + await self.handleFileURL(content: content, url: url, index: index) + + } + } else { + NSLog("[ERROR] Cannot load pdf content !\(String(describing: content))") + await self.dismissWithError( + message: "Cannot load pdf content \(String(describing: content))") + } + } + } + + private func handleFiles(content: NSExtensionItem, attachment: NSItemProvider, index: Int) async { + Task.detached { + if let url = try? await attachment.loadItem(forTypeIdentifier: self.fileURLType) as? URL { + Task { @MainActor in + + await self.handleFileURL(content: content, url: url, index: index) + + } + } else { + NSLog("[ERROR] Cannot load file content !\(String(describing: content))") + await self.dismissWithError( + message: "Cannot load file content \(String(describing: content))") + } + } + } + + private func handleFileURL(content: NSExtensionItem, url: URL, index: Int) async { + // Always copy + let fileName = self.getFileName(from: url, type: .file) + let fileExtension = self.getExtension(from: url, type: .file) + let fileSize = self.getFileSize(from: url) + let mimeType = url.mimeType(ext: fileExtension) + let newName = "\(UUID().uuidString).\(fileExtension)" + let newPath = FileManager.default + .containerURL( + forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier)! + .appendingPathComponent(newName) + let copied = self.copyFile(at: url, to: newPath) + if copied { + self.sharedMedia.append( + SharedMediaFile( + path: newPath.absoluteString, thumbnail: nil, fileName: fileName, + fileSize: fileSize, width: nil, height: nil, duration: nil, mimeType: mimeType, + type: .file)) + } + + if index == (content.attachments?.count)! - 1 { + let userDefaults = UserDefaults(suiteName: self.hostAppGroupIdentifier) + userDefaults?.set(self.toData(data: self.sharedMedia), forKey: self.sharedKey) + userDefaults?.synchronize() + self.redirectToHostApp(type: .file) + } + } + + private func dismissWithError(message: String? = nil) { + DispatchQueue.main.async { + NSLog("[ERROR] Error loading application ! \(message!)") + let alert = UIAlertController( + title: "Error", message: "Error loading application: \(message!)", preferredStyle: .alert) + + let action = UIAlertAction(title: "OK", style: .cancel) { _ in + self.dismiss(animated: true, completion: nil) + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + } + + alert.addAction(action) + self.present(alert, animated: true, completion: nil) + } + } + + private func redirectToHostApp(type: RedirectType) { + let url = URL(string: "\(shareProtocol)://dataUrl=\(sharedKey)#\(type)")! + var responder = self as UIResponder? + + while responder != nil { + if let application = responder as? UIApplication { + if application.canOpenURL(url) { + application.open(url) + } else { + NSLog("redirectToHostApp canOpenURL KO: \(shareProtocol)") + self.dismissWithError( + message: "Application not found, invalid url scheme \(shareProtocol)") + return + } + } + responder = responder!.next + } + extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + } + + enum RedirectType { + case media + case text + case weburl + case file + } + + func getExtension(from url: URL, type: SharedMediaType) -> String { + let parts = url.lastPathComponent.components(separatedBy: ".") + var ex: String? = nil + if parts.count > 1 { + ex = parts.last + } + if ex == nil { + switch type { + case .image: + ex = "PNG" + case .video: + ex = "MP4" + case .file: + ex = "TXT" + } + } + return ex ?? "Unknown" + } + + func getFileName(from url: URL, type: SharedMediaType) -> String { + var name = url.lastPathComponent + if name == "" { + name = UUID().uuidString + "." + getExtension(from: url, type: type) + } + return name + } + + func getFileSize(from url: URL) -> Int? { + do { + let resources = try url.resourceValues(forKeys: [.fileSizeKey]) + return resources.fileSize + } catch { + NSLog("Error: \(error)") + return nil + } + } + + func copyFile(at srcURL: URL, to dstURL: URL) -> Bool { + do { + if FileManager.default.fileExists(atPath: dstURL.path) { + try FileManager.default.removeItem(at: dstURL) + } + try FileManager.default.copyItem(at: srcURL, to: dstURL) + } catch (let error) { + NSLog("Cannot copy item at \(srcURL) to \(dstURL): \(error)") + return false + } + return true + } + + private func getSharedMediaFile(forVideo: URL, fileName: String, fileSize: Int?, mimeType: String) + -> SharedMediaFile? + { + let asset = AVAsset(url: forVideo) + let thumbnailPath = getThumbnailPath(for: forVideo) + let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded() + var trackWidth: Int? = nil + var trackHeight: Int? = nil + + // get video info + let track = asset.tracks(withMediaType: AVMediaType.video).first ?? nil + if track != nil { + let size = track!.naturalSize.applying(track!.preferredTransform) + trackWidth = abs(Int(size.width)) + trackHeight = abs(Int(size.height)) + } + + if FileManager.default.fileExists(atPath: thumbnailPath.path) { + return SharedMediaFile( + path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, fileName: fileName, + fileSize: fileSize, width: trackWidth, height: trackHeight, duration: duration, + mimeType: mimeType, type: .video) + } + + var saved = false + let assetImgGenerate = AVAssetImageGenerator(asset: asset) + assetImgGenerate.appliesPreferredTrackTransform = true + assetImgGenerate.maximumSize = CGSize(width: 360, height: 360) + do { + let img = try assetImgGenerate.copyCGImage( + at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil) + try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath) + saved = true + } catch { + saved = false + } + + return saved + ? SharedMediaFile( + path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, fileName: fileName, + fileSize: fileSize, width: trackWidth, height: trackHeight, duration: duration, + mimeType: mimeType, type: .video) : nil + } + + private func getThumbnailPath(for url: URL) -> URL { + let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences( + of: "==", with: "") + let path = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: self.hostAppGroupIdentifier)! + .appendingPathComponent("\(fileName).jpg") + return path + } + + class WebUrl: Codable { + var url: String + var meta: String + + init(url: String, meta: String) { + self.url = url + self.meta = meta + } + } + + class SharedMediaFile: Codable { + var path: String // can be image, video or url path + var thumbnail: String? // video thumbnail + var fileName: String // uuid + extension + var fileSize: Int? + var width: Int? // for image + var height: Int? // for image + var duration: Double? // video duration in milliseconds + var mimeType: String + var type: SharedMediaType + + init( + path: String, thumbnail: String?, fileName: String, fileSize: Int?, width: Int?, height: Int?, + duration: Double?, mimeType: String, type: SharedMediaType + ) { + self.path = path + self.thumbnail = thumbnail + self.fileName = fileName + self.fileSize = fileSize + self.width = width + self.height = height + self.duration = duration + self.mimeType = mimeType + self.type = type + } + } + + enum SharedMediaType: Int, Codable { + case image + case video + case file + } + + func toData(data: [WebUrl]) -> Data? { + let encodedData = try? JSONEncoder().encode(data) + return encodedData + } + func toData(data: [SharedMediaFile]) -> Data? { + let encodedData = try? JSONEncoder().encode(data) + return encodedData + } +} + +internal let mimeTypes = [ + "html": "text/html", + "htm": "text/html", + "shtml": "text/html", + "css": "text/css", + "xml": "text/xml", + "gif": "image/gif", + "jpeg": "image/jpeg", + "jpg": "image/jpeg", + "js": "application/javascript", + "atom": "application/atom+xml", + "rss": "application/rss+xml", + "mml": "text/mathml", + "txt": "text/plain", + "jad": "text/vnd.sun.j2me.app-descriptor", + "wml": "text/vnd.wap.wml", + "htc": "text/x-component", + "png": "image/png", + "tif": "image/tiff", + "tiff": "image/tiff", + "wbmp": "image/vnd.wap.wbmp", + "ico": "image/x-icon", + "jng": "image/x-jng", + "bmp": "image/x-ms-bmp", + "svg": "image/svg+xml", + "svgz": "image/svg+xml", + "webp": "image/webp", + "woff": "application/font-woff", + "jar": "application/java-archive", + "war": "application/java-archive", + "ear": "application/java-archive", + "json": "application/json", + "hqx": "application/mac-binhex40", + "doc": "application/msword", + "pdf": "application/pdf", + "ps": "application/postscript", + "eps": "application/postscript", + "ai": "application/postscript", + "rtf": "application/rtf", + "m3u8": "application/vnd.apple.mpegurl", + "xls": "application/vnd.ms-excel", + "eot": "application/vnd.ms-fontobject", + "ppt": "application/vnd.ms-powerpoint", + "wmlc": "application/vnd.wap.wmlc", + "kml": "application/vnd.google-earth.kml+xml", + "kmz": "application/vnd.google-earth.kmz", + "7z": "application/x-7z-compressed", + "cco": "application/x-cocoa", + "jardiff": "application/x-java-archive-diff", + "jnlp": "application/x-java-jnlp-file", + "run": "application/x-makeself", + "pl": "application/x-perl", + "pm": "application/x-perl", + "prc": "application/x-pilot", + "pdb": "application/x-pilot", + "rar": "application/x-rar-compressed", + "rpm": "application/x-redhat-package-manager", + "sea": "application/x-sea", + "swf": "application/x-shockwave-flash", + "sit": "application/x-stuffit", + "tcl": "application/x-tcl", + "tk": "application/x-tcl", + "der": "application/x-x509-ca-cert", + "pem": "application/x-x509-ca-cert", + "crt": "application/x-x509-ca-cert", + "xpi": "application/x-xpinstall", + "xhtml": "application/xhtml+xml", + "xspf": "application/xspf+xml", + "zip": "application/zip", + "epub": "application/epub+zip", + "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "mid": "audio/midi", + "midi": "audio/midi", + "kar": "audio/midi", + "mp3": "audio/mpeg", + "ogg": "audio/ogg", + "m4a": "audio/x-m4a", + "ra": "audio/x-realaudio", + "3gpp": "video/3gpp", + "3gp": "video/3gpp", + "ts": "video/mp2t", + "mp4": "video/mp4", + "mpeg": "video/mpeg", + "mpg": "video/mpeg", + "mov": "video/quicktime", + "webm": "video/webm", + "flv": "video/x-flv", + "m4v": "video/x-m4v", + "mng": "video/x-mng", + "asx": "video/x-ms-asf", + "asf": "video/x-ms-asf", + "wmv": "video/x-ms-wmv", + "avi": "video/x-msvideo", +] + +extension URL { + func mimeType(ext: String?) -> String { + if #available(iOSApplicationExtension 14.0, *) { + if let pathExt = ext, + let mimeType = UTType(filenameExtension: pathExt)?.preferredMIMEType + { + return mimeType + } else { + return "application/octet-stream" + } + } else { + return mimeTypes[ext?.lowercased() ?? ""] ?? "application/octet-stream" + } + } +} + +extension Array { + subscript(safe index: UInt) -> Element? { + return Int(index) < count ? self[Int(index)] : nil + } +} diff --git a/apps/mobile/package.json b/apps/mobile/package.json index f402a96efc..08da41885c 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -73,6 +73,7 @@ "expo-media-library": "17.1.6", "expo-notifications": "0.31.1", "expo-secure-store": "14.2.3", + "expo-share-intent": "4.0.0", "expo-sharing": "13.1.5", "expo-splash-screen": "0.30.7", "expo-sqlite": "15.2.9", diff --git a/apps/mobile/src/hooks/useIntentHandler.ts b/apps/mobile/src/hooks/useIntentHandler.ts index e435213b83..9119dc8e9f 100644 --- a/apps/mobile/src/hooks/useIntentHandler.ts +++ b/apps/mobile/src/hooks/useIntentHandler.ts @@ -1,8 +1,10 @@ import * as Linking from "expo-linking" +import { useShareIntent } from "expo-share-intent" import { useEffect } from "react" import { useNavigation } from "../lib/navigation/hooks" import { FollowScreen } from "../screens/(modal)/FollowScreen" +import { DiscoverTabScreen } from "../screens/(stack)/(tabs)/discover" // This needs to stay outside of react to persist between account switches let previousIntentUrl = "" @@ -15,6 +17,16 @@ export function useIntentHandler() { const navigation = useNavigation() + const { hasShareIntent, shareIntent } = useShareIntent() + + useEffect(() => { + if (hasShareIntent && shareIntent.webUrl) { + navigation.presentControllerView(DiscoverTabScreen, { + searchValue: shareIntent.webUrl, + }) + } + }, [hasShareIntent, navigation, shareIntent.webUrl]) + useEffect(() => { if (incomingUrl && incomingUrl !== previousIntentUrl) { previousIntentUrl = incomingUrl diff --git a/apps/mobile/src/modules/discover/ctx.tsx b/apps/mobile/src/modules/discover/ctx.tsx index d4914c59cf..169f81aaf3 100644 --- a/apps/mobile/src/modules/discover/ctx.tsx +++ b/apps/mobile/src/modules/discover/ctx.tsx @@ -12,6 +12,7 @@ interface SearchPageContextType { searchValueAtom: PrimitiveAtom searchTypeAtom: PrimitiveAtom + fromIntent?: boolean } export const SearchPageContext = createContext(null!) @@ -35,15 +36,22 @@ export const useSetSearchBarHeight = () => { return use(setSearchBarHeightContext) } -export const SearchPageProvider = ({ children }: { children: React.ReactNode }) => { +export const SearchPageProvider = ({ + children, + searchValue, +}: { + children: React.ReactNode + searchValue?: string +}) => { const [atomRefs] = useState((): SearchPageContextType => { - const searchFocusedAtom = atom(false) - const searchValueAtom = atom("") + const searchFocusedAtom = atom(!!searchValue) + const searchValueAtom = atom(searchValue ?? "") const searchTypeAtom = atom(SearchType.Feed) return { searchFocusedAtom, searchValueAtom, searchTypeAtom, + fromIntent: !!searchValue, } }) return {children} diff --git a/apps/mobile/src/modules/discover/search.tsx b/apps/mobile/src/modules/discover/search.tsx index 5f2a2a6c34..922776df72 100644 --- a/apps/mobile/src/modules/discover/search.tsx +++ b/apps/mobile/src/modules/discover/search.tsx @@ -15,7 +15,7 @@ import { BlurEffect } from "@/src/components/common/BlurEffect" import { getDefaultHeaderHeight } from "@/src/components/layouts/utils" import { SetNavigationHeaderHeightContext } from "@/src/components/layouts/views/NavigationHeaderContext" import { Search2CuteReIcon } from "@/src/icons/search_2_cute_re" -import { useScreenIsInSheetModal } from "@/src/lib/navigation/hooks" +import { useNavigation, useScreenIsInSheetModal } from "@/src/lib/navigation/hooks" import { ScreenItemContext } from "@/src/lib/navigation/ScreenItemContext" import { accentColor, useColor } from "@/src/theme/colors" @@ -41,7 +41,7 @@ export const DiscoverHeader = () => { const headerHeight = getDefaultHeaderHeight(frame, sheetModal, insets.top) const scrollContainerAnimatedX = useSearchPageScrollContainerAnimatedX() - const { searchFocusedAtom } = useSearchPageContext() + const { searchFocusedAtom, fromIntent } = useSearchPageContext() const isFocused = useAtomValue(searchFocusedAtom) const setHeaderHeight = use(SetNavigationHeaderHeightContext) @@ -56,7 +56,7 @@ export const DiscoverHeader = () => { > - + {isFocused && } @@ -65,6 +65,7 @@ export const DiscoverHeader = () => { } const SearchInput = () => { + const navigation = useNavigation() const { t } = useTranslation("common") const { searchFocusedAtom, searchValueAtom } = useSearchPageContext() const [isFocused, setIsFocused] = useAtom(searchFocusedAtom) @@ -77,11 +78,11 @@ const SearchInput = () => { const skeletonTranslateX = useSharedValue(0) const placeholderOpacity = useSharedValue(0) const marginRight = useSharedValue(0) - const cancelButtonTranslateX = useSharedValue(20) const [tempSearchValue, setTempSearchValue] = useState(searchValue) - const focusOrHasValue = isFocused || searchValue || tempSearchValue + const focusOrHasValue = isFocused || !!searchValue || !!tempSearchValue + const cancelButtonTranslateX = useSharedValue(focusOrHasValue ? 0 : 20) useEffect(() => { if (focusOrHasValue) { @@ -196,9 +197,13 @@ const SearchInput = () => { { - setIsFocused(false) - setSearchValue("") - setTempSearchValue("") + if (navigation.canGoBack()) { + navigation.dismiss() + } else { + setIsFocused(false) + setSearchValue("") + setTempSearchValue("") + } }} className="absolute -right-20 w-20 pl-4" style={cancelButtonAnimatedStyle} diff --git a/apps/mobile/src/screens/(stack)/(tabs)/discover.tsx b/apps/mobile/src/screens/(stack)/(tabs)/discover.tsx index a389a47c46..fc95919505 100644 --- a/apps/mobile/src/screens/(stack)/(tabs)/discover.tsx +++ b/apps/mobile/src/screens/(stack)/(tabs)/discover.tsx @@ -11,10 +11,10 @@ import { } from "@/src/modules/discover/ctx" import { DiscoverHeader } from "@/src/modules/discover/search" -export default function Discover() { +export default function Discover({ searchValue }: { searchValue?: string }) { return ( - + diff --git a/package.json b/package.json index 5ab3b99026..d92ac5321b 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,8 @@ "react-native-sheet-transitions": "patches/react-native-sheet-transitions.patch", "react-native-screens": "patches/react-native-screens.patch", "expo-image": "patches/expo-image.patch", - "react-native-ios-utilities": "patches/react-native-ios-utilities.patch" + "react-native-ios-utilities": "patches/react-native-ios-utilities.patch", + "xcode": "patches/xcode.patch" }, "overrides": { "@electron/node-gyp": "10.2.0-electron.2", diff --git a/patches/xcode.patch b/patches/xcode.patch new file mode 100644 index 0000000000..176f38787b --- /dev/null +++ b/patches/xcode.patch @@ -0,0 +1,31 @@ +diff --git a/lib/pbxProject.js b/lib/pbxProject.js +index 068548ab89dfd2d39f90d46d881c17dc86f90bf4..8f5ae84a130cc1c801bb91f03d4d9a75ad46caf5 100644 +--- a/lib/pbxProject.js ++++ b/lib/pbxProject.js +@@ -1148,7 +1148,7 @@ pbxProject.prototype.updateBuildProperty = function(prop, value, build, targetNa + } + } + } +- ++ + var configs = this.pbxXCBuildConfigurationSection(); + for (var configName in configs) { + if (!COMMENT_KEY.test(configName)) { +@@ -1679,7 +1679,7 @@ function correctForFrameworksPath(file, project) { + function correctForPath(file, project, group) { + var r_group_dir = new RegExp('^' + group + '[\\\\/]'); + +- if (project.pbxGroupByName(group).path) ++ if (project.pbxGroupByName(group)&&project.pbxGroupByName(group).path) + file.path = file.path.replace(r_group_dir, ''); + + return file; +@@ -2091,7 +2091,7 @@ pbxProject.prototype.getBuildProperty = function(prop, build, targetName) { + } + } + } +- ++ + var configs = this.pbxXCBuildConfigurationSection(); + for (var configName in configs) { + if (!COMMENT_KEY.test(configName)) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77a8001dc1..1165ca6257 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,6 +70,9 @@ patchedDependencies: workbox-precaching: hash: 51e57c78af317e292b43fca4b72f57b247b7b1a1faa8b03c8881ddb798c7c52f path: patches/workbox-precaching.patch + xcode: + hash: aa445e8363cb2a506d0f122fba5b7d36d4878779b864dc04fcc2358418be8465 + path: patches/xcode.patch importers: @@ -909,6 +912,9 @@ importers: expo-secure-store: specifier: 14.2.3 version: 14.2.3(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5)) + expo-share-intent: + specifier: 4.0.0 + version: 4.0.0(50f5cb1998537fd12e3c719869c9ba3a) expo-sharing: specifier: 13.1.5 version: 13.1.5(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5)) @@ -3501,6 +3507,9 @@ packages: '@expo/config-plugins@10.0.1': resolution: {integrity: sha512-JM8fmG2TVzuF02LrkywZKfCBfA8yVvOeK+3X/JnWUQqWCdUpLNf1RmCdnZ67VBeFLBD74ro+Gd1bJR1dW5aMcw==} + '@expo/config-plugins@10.0.2': + resolution: {integrity: sha512-TzUn3pPdpwCS0yYaSlZOClgDmCX8N4I2lfgitX5oStqmvpPtB+vqtdyqsVM02fQ2tlJIAqwBW+NHaHqqy8Jv7g==} + '@expo/config-plugins@9.0.12': resolution: {integrity: sha512-/Ko/NM+GzvJyRkq8PITm8ms0KY5v0wmN1OQFYRMkcJqOi3PjlhndW+G6bHpJI9mkQXBaUnHwAiGLqIC3+MQ5Wg==} @@ -9383,6 +9392,15 @@ packages: peerDependencies: expo: '*' + expo-share-intent@4.0.0: + resolution: {integrity: sha512-edgHV+9ghtinhsNERm5Gv6tIyvMA36FSvRVzNCJm9vom4TBJOemj6nBM4ucvae+HqD5ipY9KUNMbCusg+5i4kA==} + peerDependencies: + expo: ^53 + expo-constants: '>=17.1.5' + expo-linking: '>=7.0.2' + react: 19.0.0 + react-native: '*' + expo-sharing@13.1.5: resolution: {integrity: sha512-X/5sAEiWXL2kdoGE3NO5KmbfcmaCWuWVZXHu8OQef7Yig4ZgHFkGD11HKJ5KqDrDg+SRZe4ISd6MxE7vGUgm4w==} peerDependencies: @@ -17656,7 +17674,26 @@ snapshots: semver: 7.7.1 slash: 3.0.0 slugify: 1.6.6 - xcode: 3.0.1 + xcode: 3.0.1(patch_hash=aa445e8363cb2a506d0f122fba5b7d36d4878779b864dc04fcc2358418be8465) + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + + '@expo/config-plugins@10.0.2': + dependencies: + '@expo/config-types': 53.0.3 + '@expo/json-file': 9.1.4 + '@expo/plist': 0.3.4 + '@expo/sdk-runtime-versions': 1.0.0 + chalk: 4.1.2 + debug: 4.4.0(supports-color@8.1.1) + getenv: 1.0.0 + glob: 10.4.5 + resolve-from: 5.0.0 + semver: 7.7.1 + slash: 3.0.0 + slugify: 1.6.6 + xcode: 3.0.1(patch_hash=aa445e8363cb2a506d0f122fba5b7d36d4878779b864dc04fcc2358418be8465) xml2js: 0.6.0 transitivePeerDependencies: - supports-color @@ -17675,7 +17712,7 @@ snapshots: semver: 7.7.1 slash: 3.0.0 slugify: 1.6.6 - xcode: 3.0.1 + xcode: 3.0.1(patch_hash=aa445e8363cb2a506d0f122fba5b7d36d4878779b864dc04fcc2358418be8465) xml2js: 0.6.0 transitivePeerDependencies: - supports-color @@ -17694,7 +17731,7 @@ snapshots: semver: 7.7.1 slash: 3.0.0 slugify: 1.6.6 - xcode: 3.0.1 + xcode: 3.0.1(patch_hash=aa445e8363cb2a506d0f122fba5b7d36d4878779b864dc04fcc2358418be8465) xml2js: 0.6.0 transitivePeerDependencies: - supports-color @@ -25182,6 +25219,17 @@ snapshots: dependencies: expo: 53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5) + expo-share-intent@4.0.0(50f5cb1998537fd12e3c719869c9ba3a): + dependencies: + '@expo/config-plugins': 10.0.2 + expo: 53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5) + expo-constants: 17.1.4(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)) + expo-linking: 7.1.4(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0) + react: 19.0.0 + react-native: 0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5) + transitivePeerDependencies: + - supports-color + expo-sharing@13.1.5(expo@53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5)): dependencies: expo: 53.0.4(@babel/core@7.26.10)(@expo/metro-runtime@5.0.4(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0))(react-native@0.79.1(@babel/core@7.26.10)(@types/react@19.1.3)(bufferutil@4.0.9)(react@19.0.0)(utf-8-validate@6.0.5))(react@19.0.0)(utf-8-validate@6.0.5) @@ -32219,7 +32267,7 @@ snapshots: dependencies: path-extra: 1.0.3 - xcode@3.0.1: + xcode@3.0.1(patch_hash=aa445e8363cb2a506d0f122fba5b7d36d4878779b864dc04fcc2358418be8465): dependencies: simple-plist: 1.3.1 uuid: 7.0.3