From 316caf6687e839f1c1e2b1fb843a51b58c49049f Mon Sep 17 00:00:00 2001 From: Johannes Klein Date: Tue, 14 Oct 2025 01:16:29 +0200 Subject: [PATCH] 922 share sheet changes (#3129) * Allow 500 photos in image picker * Allow 500 photos from share extension * Basic React share sheet setup Following the setup here: https://github.com/inaturalist/react-native-share-menu/blob/f5805e92082fd79f251cefbe86759201589f6e9a/SHARE_EXTENSION_VIEW.md * Basic ShareSheet React component * Update ShareSheet.tsx * Update ShareSheet.tsx --- index.share.js | 5 ++ .../Base.lproj/MainInterface.storyboard | 2 +- .../Info.plist | 28 +++++- ...actNative-ShareExtension-Bridging-Header.h | 8 +- .../project.pbxproj | 23 +++++ src/components/PhotoImporter/PhotoLibrary.js | 2 +- src/components/ShareSheet/ShareSheet.tsx | 86 +++++++++++++++++++ 7 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 index.share.js create mode 100644 src/components/ShareSheet/ShareSheet.tsx diff --git a/index.share.js b/index.share.js new file mode 100644 index 000000000..cdfc72cb6 --- /dev/null +++ b/index.share.js @@ -0,0 +1,5 @@ +import { AppRegistry } from "react-native"; + +import ShareSheet from "./src/components/ShareSheet/ShareSheet"; + +AppRegistry.registerComponent( "ShareMenuModuleComponent", () => ShareSheet ); diff --git a/ios/iNaturalistReactNative-ShareExtension/Base.lproj/MainInterface.storyboard b/ios/iNaturalistReactNative-ShareExtension/Base.lproj/MainInterface.storyboard index 286a50894..d8e9f4ef7 100644 --- a/ios/iNaturalistReactNative-ShareExtension/Base.lproj/MainInterface.storyboard +++ b/ios/iNaturalistReactNative-ShareExtension/Base.lproj/MainInterface.storyboard @@ -9,7 +9,7 @@ - + diff --git a/ios/iNaturalistReactNative-ShareExtension/Info.plist b/ios/iNaturalistReactNative-ShareExtension/Info.plist index ac27bc41e..806d08d0f 100644 --- a/ios/iNaturalistReactNative-ShareExtension/Info.plist +++ b/ios/iNaturalistReactNative-ShareExtension/Info.plist @@ -13,7 +13,7 @@ NSExtensionActivationRule NSExtensionActivationSupportsImageWithMaxCount - 20 + 500 NSExtensionMainStoryboard @@ -21,5 +21,31 @@ NSExtensionPointIdentifier com.apple.share-services + ReactShareViewBackgroundColor + + Red + 1 + Green + 1 + Blue + 1 + Alpha + 1 + Transparent + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + diff --git a/ios/iNaturalistReactNative-ShareExtension/iNaturalistReactNative-ShareExtension-Bridging-Header.h b/ios/iNaturalistReactNative-ShareExtension/iNaturalistReactNative-ShareExtension-Bridging-Header.h index 1b2cb5d6d..bf52ff6e1 100644 --- a/ios/iNaturalistReactNative-ShareExtension/iNaturalistReactNative-ShareExtension-Bridging-Header.h +++ b/ios/iNaturalistReactNative-ShareExtension/iNaturalistReactNative-ShareExtension-Bridging-Header.h @@ -1,4 +1,4 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - +#import +#import +#import +#import diff --git a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj index dc80882ec..ee012b81c 100644 --- a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj +++ b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 8F1AC6772BC1B610002F994B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 8F1AC6762BC1B610002F994B /* PrivacyInfo.xcprivacy */; }; 8F1AC6792BC1B610002F994B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 8F1AC6762BC1B610002F994B /* PrivacyInfo.xcprivacy */; }; 8F346E4A2CF6912700CED7B4 /* geomodel.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = 8F346E492CF6912700CED7B4 /* geomodel.mlmodel */; }; + 8FA933AD2E99522900179553 /* ReactShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA933AC2E99522900179553 /* ReactShareViewController.swift */; }; A5C00A8934ED4A48A1495179 /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0972395C34134C71A54A2A5D /* INatIcon.ttf */; }; AE4DC81B3A87484CB3FD6750 /* Lato-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4B0AEEF6CA584BCF9880EB35 /* Lato-Regular.ttf */; }; E5DFC1C6FBFA45739CE91C69 /* Lato-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 69DF855D92EA4ADFB73B47F1 /* Lato-MediumItalic.ttf */; }; @@ -92,6 +93,7 @@ 8C2D97D72EED451C887998A8 /* Lato-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Lato-BoldItalic.ttf"; path = "../assets/fonts/Lato-BoldItalic.ttf"; sourceTree = ""; }; 8F1AC6762BC1B610002F994B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 8F346E492CF6912700CED7B4 /* geomodel.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = geomodel.mlmodel; sourceTree = ""; }; + 8FA933AC2E99522900179553 /* ReactShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ReactShareViewController.swift; path = "../node_modules/react-native-share-menu/ios/ReactShareViewController.swift"; sourceTree = SOURCE_ROOT; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; F15C1390617A309CE0A194B2 /* Pods_iNaturalistReactNative_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iNaturalistReactNative_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -202,6 +204,7 @@ 8B65ED2C29F575C10054CCEF /* iNaturalistReactNative-ShareExtension */ = { isa = PBXGroup; children = ( + 8FA933AC2E99522900179553 /* ReactShareViewController.swift */, 8B65ED3C29F576D00054CCEF /* iNaturalistReactNative-ShareExtension.entitlements */, 8B65ED3A29F575FE0054CCEF /* ShareViewController.swift */, 8B65ED2F29F575C10054CCEF /* MainInterface.storyboard */, @@ -260,6 +263,7 @@ 8B65ED2829F575C10054CCEF /* Frameworks */, 8B65ED2929F575C10054CCEF /* Resources */, CBBE96E94BCFC337E2A3EDB6 /* [CP] Copy Pods Resources */, + 8F0FF30C2E9AF08F005DCE05 /* Bundle React Native code and images */, ); buildRules = ( ); @@ -397,6 +401,24 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iNaturalistReactNative/Pods-iNaturalistReactNative-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 8F0FF30C2E9AF08F005DCE05 /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export ENTRY_FILE=index.share.js\n\nset -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; + }; 987B044B8ED1BE214203D222 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -472,6 +494,7 @@ buildActionMask = 2147483647; files = ( 8B65ED3B29F575FE0054CCEF /* ShareViewController.swift in Sources */, + 8FA933AD2E99522900179553 /* ReactShareViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/components/PhotoImporter/PhotoLibrary.js b/src/components/PhotoImporter/PhotoLibrary.js index 44300d711..459cfcff6 100644 --- a/src/components/PhotoImporter/PhotoLibrary.js +++ b/src/components/PhotoImporter/PhotoLibrary.js @@ -25,7 +25,7 @@ import { useLayoutPrefs } from "sharedHooks"; import useExitObservationsFlow from "sharedHooks/useExitObservationFlow"; import useStore from "stores/useStore"; -const MAX_PHOTOS_ALLOWED = 20; +const MAX_PHOTOS_ALLOWED = 500; const FROM_AICAMERA_MAX_PHOTOS_ALLOWED = 1; const PhotoLibrary = ( ): Node => { diff --git a/src/components/ShareSheet/ShareSheet.tsx b/src/components/ShareSheet/ShareSheet.tsx new file mode 100644 index 000000000..aefb008e0 --- /dev/null +++ b/src/components/ShareSheet/ShareSheet.tsx @@ -0,0 +1,86 @@ +// Fellow developers: This is the component that is rendered when the share extension is opened. +// It uses a separate React instance and entry point that is registered in index.share.js +// It is not set up to use anything from the main app, like nativewind or styled components. +import React, { useEffect, useState } from "react"; +import { + Pressable, StyleSheet, Text, View +} from "react-native"; +import { ShareMenuReactView } from "react-native-share-menu"; + +interface ButtonProps { + onPress: () => void; + title: string; + style?: object; +} + +const Button = ( { onPress, title, style }: ButtonProps ) => ( + + {/* eslint-disable-next-line no-use-before-define */} + {title} + +); + +const ShareSheet = () => { + const [sharedData, setSharedData] = useState( [] ); + + useEffect( () => { + // @ts-expect-error data has any type here, but the actual type should come from the library + ShareMenuReactView.data().then( ( { data } ) => { + setSharedData( data ); + } ); + }, [] ); + + const { + container, text, buttonGroup, destructive + // eslint-disable-next-line no-use-before-define + } = styles; + + return ( + + + {`Share ${sharedData.length} photos with iNaturalist?`} + + +