diff --git a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj index d36f2fb1b..89fea6da8 100644 --- a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj +++ b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj @@ -11,13 +11,42 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 46E31F54C547C341F605BB66 /* libPods-Joplin.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A5E1CD825FABD6C4E704EA54 /* libPods-Joplin.a */; }; + 5E556FC75AECECB13464A724 /* libPods-ShareExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FAC957496DFD2368FFE3C360 /* libPods-ShareExtension.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + AE152142260F770400217DCB /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AE152141260F770400217DCB /* ShareViewController.m */; }; + AE82E4AF2599FA3A0013551B /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AE82E4AD2599FA3A0013551B /* MainInterface.storyboard */; }; + AE82E4B32599FA3A0013551B /* ShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = AE82E4A82599FA3A0013551B /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + AE82E4B12599FA3A0013551B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AE82E4A72599FA3A0013551B; + remoteInfo = ShareExtension; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + AE82E4B42599FA3A0013551B /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + AE82E4B32599FA3A0013551B /* ShareExtension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* JoplinTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JoplinTests.m; sourceTree = ""; }; + 0473A2D469A3555053E69327 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; }; 09056573D4C040FBD5FEB93A /* Pods-Joplin-JoplinTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Joplin-JoplinTests.debug.xcconfig"; path = "Target Support Files/Pods-Joplin-JoplinTests/Pods-Joplin-JoplinTests.debug.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Joplin.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Joplin.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Joplin/AppDelegate.h; sourceTree = ""; }; @@ -32,13 +61,22 @@ 2DA44D9A347489A29B995F73 /* Pods-Joplin-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Joplin-tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-Joplin-tvOSTests/Pods-Joplin-tvOSTests.debug.xcconfig"; sourceTree = ""; }; 37DBC181C4AD99CBE0D07EEB /* Pods-Joplin-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Joplin-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-Joplin-tvOSTests/Pods-Joplin-tvOSTests.release.xcconfig"; sourceTree = ""; }; 505CB61090817F4453631957 /* Pods-Joplin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Joplin.debug.xcconfig"; path = "Target Support Files/Pods-Joplin/Pods-Joplin.debug.xcconfig"; sourceTree = ""; }; + 5DE39012F71F18423C665C57 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Joplin/LaunchScreen.storyboard; sourceTree = ""; }; A3FEB746EE7F1B0FF28528E1 /* Pods-Joplin-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Joplin-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-Joplin-tvOS/Pods-Joplin-tvOS.debug.xcconfig"; sourceTree = ""; }; A5E1CD825FABD6C4E704EA54 /* libPods-Joplin.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Joplin.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + AE152140260F770400217DCB /* ShareViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ShareViewController.h; path = Source/ShareExtension/ShareViewController.h; sourceTree = ""; }; + AE152141260F770400217DCB /* ShareViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ShareViewController.m; path = Source/ShareExtension/ShareViewController.m; sourceTree = ""; }; + AE7945DB259C9A2500051BE2 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = ""; }; + AE7945E6259C9AEE00051BE2 /* Joplin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Joplin.entitlements; path = Joplin/Joplin.entitlements; sourceTree = ""; }; + AE82E4A82599FA3A0013551B /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + AE82E4AE2599FA3A0013551B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + AE82E4B02599FA3A0013551B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B61798F36B3BC123BF8EA4D9 /* libPods-Joplin-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Joplin-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; }; F69B873C692CE22F1C4C9264 /* libPods-Joplin-JoplinTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Joplin-JoplinTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + FAC957496DFD2368FFE3C360 /* libPods-ShareExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ShareExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -50,6 +88,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AE82E4A52599FA3A0013551B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E556FC75AECECB13464A724 /* libPods-ShareExtension.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -73,6 +119,7 @@ 13B07FAE1A68108700A75B9A /* Joplin */ = { isa = PBXGroup; children = ( + AE7945E6259C9AEE00051BE2 /* Joplin.entitlements */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, @@ -93,6 +140,7 @@ 258F823D616BE3D6A52BC900 /* libPods-Joplin-tvOSTests.a */, A5E1CD825FABD6C4E704EA54 /* libPods-Joplin.a */, F69B873C692CE22F1C4C9264 /* libPods-Joplin-JoplinTests.a */, + FAC957496DFD2368FFE3C360 /* libPods-ShareExtension.a */, ); name = Frameworks; sourceTree = ""; @@ -110,6 +158,7 @@ 13B07FAE1A68108700A75B9A /* Joplin */, 832341AE1AAA6A7D00B99B32 /* Libraries */, 00E356EF1AD99517003FC87E /* JoplinTests */, + AE82E4A92599FA3A0013551B /* ShareExtension */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, 9CDB1D9DB6483D893504BFCB /* Pods */, @@ -123,6 +172,7 @@ isa = PBXGroup; children = ( 13B07F961A680F5B00A75B9A /* Joplin.app */, + AE82E4A82599FA3A0013551B /* ShareExtension.appex */, ); name = Products; sourceTree = ""; @@ -138,10 +188,24 @@ 245A6EBAE2E874DB706B16DB /* Pods-Joplin-tvOS.release.xcconfig */, 2DA44D9A347489A29B995F73 /* Pods-Joplin-tvOSTests.debug.xcconfig */, 37DBC181C4AD99CBE0D07EEB /* Pods-Joplin-tvOSTests.release.xcconfig */, + 5DE39012F71F18423C665C57 /* Pods-ShareExtension.debug.xcconfig */, + 0473A2D469A3555053E69327 /* Pods-ShareExtension.release.xcconfig */, ); path = Pods; sourceTree = ""; }; + AE82E4A92599FA3A0013551B /* ShareExtension */ = { + isa = PBXGroup; + children = ( + AE152140260F770400217DCB /* ShareViewController.h */, + AE152141260F770400217DCB /* ShareViewController.m */, + AE7945DB259C9A2500051BE2 /* ShareExtension.entitlements */, + AE82E4AD2599FA3A0013551B /* MainInterface.storyboard */, + AE82E4B02599FA3A0013551B /* Info.plist */, + ); + path = ShareExtension; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -156,16 +220,36 @@ 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, CBC8354E4CF5CF4E15F2FCDE /* [CP] Copy Pods Resources */, + AE82E4B42599FA3A0013551B /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + AE82E4B22599FA3A0013551B /* PBXTargetDependency */, ); name = Joplin; productName = Joplin; productReference = 13B07F961A680F5B00A75B9A /* Joplin.app */; productType = "com.apple.product-type.application"; }; + AE82E4A72599FA3A0013551B /* ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = AE82E4B72599FA3A0013551B /* Build configuration list for PBXNativeTarget "ShareExtension" */; + buildPhases = ( + 027E2AA6B101F8CFCA582EC1 /* [CP] Check Pods Manifest.lock */, + AE82E4A42599FA3A0013551B /* Sources */, + AE82E4A52599FA3A0013551B /* Frameworks */, + AE82E4A62599FA3A0013551B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ShareExtension; + productName = ShareExtension; + productReference = AE82E4A82599FA3A0013551B /* ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -179,6 +263,10 @@ DevelopmentTeam = A9BXAFS6CT; LastSwiftMigration = 1120; }; + AE82E4A72599FA3A0013551B = { + CreatedOnToolsVersion = 12.0.1; + DevelopmentTeam = A9BXAFS6CT; + }; }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "Joplin" */; @@ -195,6 +283,7 @@ projectRoot = ""; targets = ( 13B07F861A680F5B00A75B9A /* Joplin */, + AE82E4A72599FA3A0013551B /* ShareExtension */, ); }; /* End PBXProject section */ @@ -209,6 +298,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AE82E4A62599FA3A0013551B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AE82E4AF2599FA3A0013551B /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -226,6 +323,28 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=/usr/local/opt/node@12/bin/node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; + 027E2AA6B101F8CFCA582EC1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ShareExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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; + }; 335ACF4DE85695BEBB18D8A3 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -329,8 +448,35 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AE82E4A42599FA3A0013551B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AE152142260F770400217DCB /* ShareViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + AE82E4B22599FA3A0013551B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AE82E4A72599FA3A0013551B /* ShareExtension */; + targetProxy = AE82E4B12599FA3A0013551B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + AE82E4AD2599FA3A0013551B /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + AE82E4AE2599FA3A0013551B /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; @@ -338,6 +484,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements; CURRENT_PROJECT_VERSION = 63; DEVELOPMENT_TEAM = A9BXAFS6CT; ENABLE_BITCODE = NO; @@ -365,6 +512,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements; CURRENT_PROJECT_VERSION = 63; DEVELOPMENT_TEAM = A9BXAFS6CT; INFOPLIST_FILE = Joplin/Info.plist; @@ -497,6 +645,62 @@ }; name = Release; }; + AE82E4B52599FA3A0013551B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5DE39012F71F18423C665C57 /* Pods-ShareExtension.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = A9BXAFS6CT; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AE82E4B62599FA3A0013551B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0473A2D469A3555053E69327 /* Pods-ShareExtension.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = A9BXAFS6CT; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = ShareExtension/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -518,6 +722,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + AE82E4B72599FA3A0013551B /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AE82E4B52599FA3A0013551B /* Debug */, + AE82E4B62599FA3A0013551B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; diff --git a/packages/app-mobile/ios/Joplin.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme b/packages/app-mobile/ios/Joplin.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme new file mode 100644 index 000000000..e477768f6 --- /dev/null +++ b/packages/app-mobile/ios/Joplin.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/app-mobile/ios/Joplin/AppDelegate.m b/packages/app-mobile/ios/Joplin/AppDelegate.m index ea5f1f0d2..2b3b86ec1 100644 --- a/packages/app-mobile/ios/Joplin/AppDelegate.m +++ b/packages/app-mobile/ios/Joplin/AppDelegate.m @@ -3,6 +3,7 @@ #import #import #import +#import #import #import #import "RNQuickActionManager.h" @@ -103,4 +104,8 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response #endif } +- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { + return [RCTLinkingManager application:app openURL:url options:options]; +} + @end diff --git a/packages/app-mobile/ios/Joplin/Info.plist b/packages/app-mobile/ios/Joplin/Info.plist index d88b47af1..1fb02cbfa 100644 --- a/packages/app-mobile/ios/Joplin/Info.plist +++ b/packages/app-mobile/ios/Joplin/Info.plist @@ -20,6 +20,19 @@ $(MARKETING_VERSION) CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + net.cozic.joplin + CFBundleURLSchemes + + joplin + + + CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/packages/app-mobile/ios/Joplin/Joplin.entitlements b/packages/app-mobile/ios/Joplin/Joplin.entitlements new file mode 100644 index 000000000..9d5fd8676 --- /dev/null +++ b/packages/app-mobile/ios/Joplin/Joplin.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.net.cozic.joplin + + + diff --git a/packages/app-mobile/ios/Podfile b/packages/app-mobile/ios/Podfile index e00f497a9..91955abc0 100644 --- a/packages/app-mobile/ios/Podfile +++ b/packages/app-mobile/ios/Podfile @@ -11,6 +11,7 @@ target 'Joplin' do use_react_native!(:path => config["reactNativePath"]) pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons' + pod 'JoplinRNShareExtension', :path => 'ShareExtension' # Enables Flipper. # @@ -21,3 +22,7 @@ target 'Joplin' do flipper_post_install(installer) end end + +target 'ShareExtension' do + pod 'JoplinCommonShareExtension', :path => 'ShareExtension' +end diff --git a/packages/app-mobile/ios/Podfile.lock b/packages/app-mobile/ios/Podfile.lock index 9ac944544..970a12ec5 100644 --- a/packages/app-mobile/ios/Podfile.lock +++ b/packages/app-mobile/ios/Podfile.lock @@ -67,6 +67,10 @@ PODS: - DoubleConversion - glog - glog (0.3.5) + - JoplinCommonShareExtension (1.0.0) + - JoplinRNShareExtension (1.0.0): + - JoplinCommonShareExtension + - React (= 0.63.3) - OpenSSL-Universal (1.0.2.20): - OpenSSL-Universal/Static (= 1.0.2.20) - OpenSSL-Universal/Static (1.0.2.20) @@ -373,6 +377,8 @@ DEPENDENCIES: - FlipperKit/SKIOSNetworkPlugin (~> 0.54.0) - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - JoplinCommonShareExtension (from `ShareExtension`) + - JoplinRNShareExtension (from `ShareExtension`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - React (from `../node_modules/react-native/`) @@ -444,6 +450,10 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec" glog: :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + JoplinCommonShareExtension: + :path: ShareExtension + JoplinRNShareExtension: + :path: ShareExtension RCTRequired: :path: "../node_modules/react-native/Libraries/RCTRequired" RCTTypeSafety: @@ -545,6 +555,8 @@ SPEC CHECKSUMS: FlipperKit: ab353d41aea8aae2ea6daaf813e67496642f3d7d Folly: b73c3869541e86821df3c387eb0af5f65addfab4 glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3 + JoplinCommonShareExtension: 270b4f8eb4e22828eeda433a04ed689fc1fd09b5 + JoplinRNShareExtension: 89e042edc89e290bc88955fe3ac7f1aaea97cbc8 OpenSSL-Universal: ff34003318d5e1163e9529b08470708e389ffcdd RCTRequired: 48884c74035a0b5b76dbb7a998bd93bcfc5f2047 RCTTypeSafety: edf4b618033c2f1c5b7bc3d90d8e085ed95ba2ab @@ -590,6 +602,6 @@ SPEC CHECKSUMS: Yoga: 7d13633d129fd179e01b8953d38d47be90db185a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: a0d1ca4e385ef46f9103f02206ebf612107dd508 +PODFILE CHECKSUM: 6f558aa823c0fd07348253cc01996e4ee3de75c4 COCOAPODS: 1.10.1 diff --git a/packages/app-mobile/ios/ShareExtension/Base.lproj/MainInterface.storyboard b/packages/app-mobile/ios/ShareExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 000000000..10af3690c --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/app-mobile/ios/ShareExtension/Info.plist b/packages/app-mobile/ios/ShareExtension/Info.plist new file mode 100644 index 000000000..ae967d4c9 --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ShareExtension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationDictionaryVersion + 2 + NSExtensionActivationSupportsImageWithMaxCount + 10 + NSExtensionActivationSupportsMovieWithMaxCount + 10 + NSExtensionActivationSupportsText + 1 + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/packages/app-mobile/ios/ShareExtension/JoplinCommonShareExtension.podspec b/packages/app-mobile/ios/ShareExtension/JoplinCommonShareExtension.podspec new file mode 100644 index 000000000..9b3ede8e6 --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/JoplinCommonShareExtension.podspec @@ -0,0 +1,12 @@ +Pod::Spec.new do |spec| + spec.name = "JoplinCommonShareExtension" + spec.version = "1.0.0" + spec.summary = "Common Share Extension code for Joplin." + spec.description = "Common Share Extension for Joplin" + spec.homepage = "https://github.com/laurent22/joplin" + spec.license = { :type => "MIT" } + spec.author = { "Duncan Cunningham" => "duncanc4@gmail.com" } + spec.platform = :ios, "9.0" + spec.source = { :path => "." } + spec.source_files = "Source/Common/**/*.{h,m}" +end diff --git a/packages/app-mobile/ios/ShareExtension/JoplinRNShareExtension.podspec b/packages/app-mobile/ios/ShareExtension/JoplinRNShareExtension.podspec new file mode 100644 index 000000000..f39e61caa --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/JoplinRNShareExtension.podspec @@ -0,0 +1,14 @@ +Pod::Spec.new do |spec| + spec.name = "JoplinRNShareExtension" + spec.version = "1.0.0" + spec.summary = "React Native module for Joplin to access the data from the share extension." + spec.description = "React Native Share Extension module for Joplin" + spec.homepage = "https://github.com/laurent22/joplin" + spec.license = { :type => "MIT" } + spec.author = { "Duncan Cunningham" => "duncanc4@gmail.com" } + spec.platform = :ios, "9.0" + spec.source = { :path => "." } + spec.source_files = "Source/RNShareExtension/**/*.{h,m}" + spec.dependency "React", "0.63.3" + spec.dependency "JoplinCommonShareExtension" +end diff --git a/packages/app-mobile/ios/ShareExtension/README.md b/packages/app-mobile/ios/ShareExtension/README.md new file mode 100644 index 000000000..bf1a37a93 --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/README.md @@ -0,0 +1,29 @@ +# Share Extension Creation Instructions +## Creating the Target +2. In Xcode select File->New->Target +3. Then search for Share Extension +4. Select Share Extension and click Next +5. In Product Name enter ShareExtension +6. Leave the rest of the options and click Finish +7. If the Activate "ShareExtension" scheme popup comes, click Activate + +## Configuring the Share Extension +1. Delete *ShareViewController.h* and *ShareViewController.m* from the project. (Select files and right click, then Delete) +2. On the confirmation popup, select Move to Trash +3. Now add *ShareViewController.h* and *ShareViewController.m* from *ShareExtension/Source/ShareExtenstion*. This can be done by right clicking on the *ShareExtension* folder in Xcode and selecting Add Files to "Joplin". Double check that the ShareExtension is checked for Add to targets and click Add. +4. Switch over to git and reset the changes done to *ShareExtension/Base.lproj/Maininterface.storyboard* and *ShareExtension/Info.plist*, as Xcode generated new versions of these files and overwrote ours. +5. Now select the ShareExtension Target and go to Signing & Capabilities +6. Click the + Capability and search for App Groups and add it +7. Back in git reset *ShareExtension/ShareExtension.entitlements* as Xcode just overwrote it. Back in Xcode you should see the app group set +8. Now switch to General, just left of Signing & Capabilites +9. Under Deployment Info, change the iOS version to match the Joplin target version, which is 9.0 + +## Configuring Joplin +1. We need to perform the same steps to add the App Group as done for the Share Extension, the only difference is the entitlements files is *Joplin.entitlements* + +## Final Steps +1. Run pod install from the ios directory to install the new pods + +# Other Information +## App Group +If the app group name needs to be changed for some reason, it needs to be changed for both the Share Extension and Joplin. Also *ShareExtensionGroupIdentifier* in *ShareExtension/Source/Common/ShareExtensionConstants.m* needs to be changed to match the new app group. \ No newline at end of file diff --git a/packages/app-mobile/ios/ShareExtension/ShareExtension.entitlements b/packages/app-mobile/ios/ShareExtension/ShareExtension.entitlements new file mode 100644 index 000000000..9d5fd8676 --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/ShareExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.net.cozic.joplin + + + diff --git a/packages/app-mobile/ios/ShareExtension/Source/Common/ShareData.h b/packages/app-mobile/ios/ShareExtension/Source/Common/ShareData.h new file mode 100644 index 000000000..b176c011c --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Source/Common/ShareData.h @@ -0,0 +1,24 @@ +// +// ShareData.h +// ShareExtension +// +// Created by Duncan Cunningham on 2/6/21. +// Copyright © 2021 joplinapp.org. All rights reserved. +// + +#import + +@interface ShareData : NSObject + +@property (nonatomic, strong) NSString* title; +@property (nonatomic, strong) NSString* text; +@property (nonatomic, strong) NSArray* resources; + +- (id)initWithDictionary:(NSDictionary*)dictionary; + +- (NSDictionary*)encodeToDictionary; + ++ (NSDictionary*)resourceDictionaryForURL:(NSString*)url withName:(NSString*)name andMimeType:(NSString*)mimeType; ++ (NSString*)resourceURLFromDictionary:(NSDictionary*)dictionary; + +@end diff --git a/packages/app-mobile/ios/ShareExtension/Source/Common/ShareData.m b/packages/app-mobile/ios/ShareExtension/Source/Common/ShareData.m new file mode 100644 index 000000000..752093755 --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Source/Common/ShareData.m @@ -0,0 +1,53 @@ +// +// ShareData.m +// ShareExtension +// +// Created by Duncan Cunningham on 2/6/21. +// Copyright © 2021 joplinapp.org. All rights reserved. +// + +#import "ShareData.h" + +static NSString* const ShareDataTitleKey = @"title"; +static NSString* const ShareDataTextKey = @"text"; +static NSString* const ShareDataResourcesKey = @"resources"; + +static NSString* const ShareDataResourceUriKey = @"uri"; +static NSString* const ShareDataResourceNameKey = @"name"; +static NSString* const ShareDataResourceMimeTypeKey = @"mimeType"; + +@implementation ShareData + +- (id)initWithDictionary:(NSDictionary*)dictionary { + self = [super init]; + + if (self != nil) { + self.title = [dictionary objectForKey:ShareDataTitleKey]; + self.text = [dictionary objectForKey:ShareDataTextKey]; + self.resources = [dictionary objectForKey:ShareDataResourcesKey]; + } + + return self; +} + +- (NSDictionary*)encodeToDictionary { + NSString* title = (self.title == nil) ? @"" : self.title; + NSString* text = (self.text == nil) ? @"" : self.text; + NSArray* resources = (self.resources == nil) ? @[] : self.resources; + + return @{ShareDataTitleKey: title, + ShareDataTextKey: text, + ShareDataResourcesKey: resources}; +} + ++ (NSDictionary*)resourceDictionaryForURL:(NSString*)url withName:(NSString*)name andMimeType:(NSString*)mimeType { + return @{ShareDataResourceUriKey: url, + ShareDataResourceNameKey: name, + ShareDataResourceMimeTypeKey: mimeType}; +} + ++ (NSString*)resourceURLFromDictionary:(NSDictionary*)dictionary { + return [dictionary objectForKey:ShareDataResourceUriKey]; +} + +@end diff --git a/packages/app-mobile/ios/ShareExtension/Source/Common/ShareExtensionConstants.h b/packages/app-mobile/ios/ShareExtension/Source/Common/ShareExtensionConstants.h new file mode 100644 index 000000000..d2263ef9c --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Source/Common/ShareExtensionConstants.h @@ -0,0 +1,16 @@ +// +// ShareExtensionConstants.h +// Joplin +// +// Created by Duncan Cunningham on 2/6/21. +// Copyright © 2021 joplinapp.org. All rights reserved. +// + +#ifndef ShareExtensionConstants_h +#define ShareExtensionConstants_h + +extern NSString* const ShareExtensionGroupIdentifier; +extern NSString* const ShareExtensionShareURL; +extern NSString* const ShareExtensionShareDataFilename; + +#endif /* ShareExtensionConstants_h */ diff --git a/packages/app-mobile/ios/ShareExtension/Source/Common/ShareExtensionConstants.m b/packages/app-mobile/ios/ShareExtension/Source/Common/ShareExtensionConstants.m new file mode 100644 index 000000000..5c032dbeb --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Source/Common/ShareExtensionConstants.m @@ -0,0 +1,13 @@ +// +// ShareExtensionConstants.m +// Joplin +// +// Created by Duncan Cunningham on 2/6/21. +// Copyright © 2021 joplinapp.org. All rights reserved. +// + +#import + +NSString* const ShareExtensionGroupIdentifier = @"group.net.cozic.joplin"; +NSString* const ShareExtensionShareURL = @"joplin://shareData"; +NSString* const ShareExtensionShareDataFilename = @"shareData"; diff --git a/packages/app-mobile/ios/ShareExtension/Source/RNShareExtension/ShareExtension.m b/packages/app-mobile/ios/ShareExtension/Source/RNShareExtension/ShareExtension.m new file mode 100644 index 000000000..cbaf0ab6a --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Source/RNShareExtension/ShareExtension.m @@ -0,0 +1,67 @@ +// +// ShareExtension.m +// Joplin +// +// Created by Duncan Cunningham on 2/6/21. +// Copyright © 2021 joplinapp.org. All rights reserved. +// + +#import +#import + +#import "ShareData.h" +#import "ShareExtensionConstants.h" + +@interface ShareExtension : NSObject + +@property (nonatomic, strong) ShareData* shareData; + +@end + +@implementation ShareExtension + +RCT_EXPORT_MODULE(); + +RCT_REMAP_METHOD(data, resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + NSDictionary* dictionary = [self decodeShareDataFromURL:self.shareDataURL]; + self.shareData = [[ShareData alloc] initWithDictionary:dictionary]; + resolve(dictionary); +} + +RCT_EXPORT_METHOD(close) { + if (self.shareData.resources != nil) { + for (NSDictionary* resource in self.shareData.resources) { + NSString* uri = [ShareData resourceURLFromDictionary:resource]; + if (uri != nil && [NSFileManager.defaultManager fileExistsAtPath:uri]) { + [NSFileManager.defaultManager removeItemAtPath:uri error:nil]; + } + } + } + + [NSFileManager.defaultManager removeItemAtPath:[[self shareDataURL] path] error:nil]; +} + ++ (BOOL)requiresMainQueueSetup { + return YES; +} + +- (NSDictionary*)constantsToExport { + return @{@"SHARE_EXTENSION_SHARE_URL": ShareExtensionShareURL}; +} + +- (NSDictionary*)decodeShareDataFromURL:(NSURL*)url { + NSData* data = [NSData dataWithContentsOfFile:[url path]]; + if (data != nil) { + return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + } else { + return nil; + } +} + +- (NSURL*)shareDataURL { + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSURL* sharedContainerURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:ShareExtensionGroupIdentifier]; + return [sharedContainerURL URLByAppendingPathComponent:ShareExtensionShareDataFilename]; +} + +@end diff --git a/packages/app-mobile/ios/ShareExtension/Source/ShareExtension/ShareViewController.h b/packages/app-mobile/ios/ShareExtension/Source/ShareExtension/ShareViewController.h new file mode 100644 index 000000000..edbddbe71 --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Source/ShareExtension/ShareViewController.h @@ -0,0 +1,13 @@ +// +// ShareViewController.h +// ShareExtension +// +// Created by Duncan Cunningham on 12/28/20. +// Copyright © 2020 joplinapp.org. All rights reserved. +// + +#import + +@interface ShareViewController : UIViewController + +@end diff --git a/packages/app-mobile/ios/ShareExtension/Source/ShareExtension/ShareViewController.m b/packages/app-mobile/ios/ShareExtension/Source/ShareExtension/ShareViewController.m new file mode 100644 index 000000000..3e1c230ae --- /dev/null +++ b/packages/app-mobile/ios/ShareExtension/Source/ShareExtension/ShareViewController.m @@ -0,0 +1,143 @@ +// +// ShareViewController.m +// ShareExtension +// +// Created by Duncan Cunningham on 12/28/20. +// Copyright © 2020 joplinapp.org. All rights reserved. +// + +#import "ShareViewController.h" +#import "ShareData.h" +#import "ShareExtensionConstants.h" + +#import + +@interface ShareViewController () + +@property (nonatomic, strong) NSURL* sharedContainerURL; + +@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *spinner; + +@end + +@implementation ShareViewController + +- (NSURL*)sharedContainerURL { + if (_sharedContainerURL == nil) { + NSFileManager* fileManager = [NSFileManager defaultManager]; + _sharedContainerURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:ShareExtensionGroupIdentifier]; + } + + return _sharedContainerURL; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self.spinner startAnimating]; + + [self extractDataFromContext:self.extensionContext withCallback:^(ShareData* shareData) { + [self serializeShareDataToSharedContainer:shareData]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){ + [self.spinner stopAnimating]; + [self.extensionContext completeRequestReturningItems:nil completionHandler:nil]; + [self launchMainApp]; + }); + }]; +} + +- (void)extractDataFromContext:(NSExtensionContext *)context withCallback:(void(^)(ShareData* shareData))callback { + __block ShareData* shareData = [[ShareData alloc] init]; + NSMutableArray* resources = [[NSMutableArray alloc] init]; + + NSExtensionItem* item = [context.inputItems firstObject]; + NSArray* attachments = item.attachments; + __block NSUInteger index = 0; + + [attachments enumerateObjectsUsingBlock:^(NSItemProvider* provider, NSUInteger idx, BOOL* stop) { + [provider.registeredTypeIdentifiers enumerateObjectsUsingBlock:^(NSString* identifier, NSUInteger idx, BOOL* stop) { + [provider loadItemForTypeIdentifier:identifier options:nil completionHandler:^(id item, NSError* error) { + index += 1; + + // is an URL - Can be a path or Web URL + if ([(NSObject*)item isKindOfClass:[NSURL class]]) { + NSURL* url = (NSURL*)item; + if ([[url pathExtension] isEqualToString:@""] || [url.scheme containsString:@"http"]) { + shareData.text = [url absoluteString]; + } else { + NSURL* copiedURL = [self copyURLToSharedContainer:url]; + NSDictionary* resource = [self resourceDictionaryForMediaURL:copiedURL]; + [resources addObject:resource]; + } + // is a String + } else if ([(NSObject*)item isKindOfClass:[NSString class]]) { + shareData.text = (NSString*)item; + // is an Image + } else if ([(NSObject*)item isKindOfClass:[UIImage class]]) { + UIImage* sharedImage = (UIImage*)item; + NSURL* path = [self copyImageToSharedContainer:sharedImage]; + NSDictionary* resource = [self resourceDictionaryForMediaURL:path]; + [resources addObject:resource]; + } + + if (index == [attachments count]) { + shareData.resources = resources; + callback(shareData); + } + }]; + + // We'll only use the first provider + *stop = YES; + }]; + }]; +} + +- (NSURL*)copyImageToSharedContainer:(UIImage*)image { + NSString* name = [NSString stringWithFormat:@"%@%@", [NSUUID new].UUIDString, @".png"]; + NSURL* path = [self.sharedContainerURL URLByAppendingPathComponent:name]; + [UIImagePNGRepresentation(image) writeToFile:[path path] atomically:YES]; + return path; +} + +- (NSURL*)copyURLToSharedContainer:(NSURL*)url { + NSString* name = [url lastPathComponent]; + NSURL* path = [self.sharedContainerURL URLByAppendingPathComponent:name]; + [[NSFileManager defaultManager] copyItemAtPath:[url path] toPath:[path path] error:nil]; + return path; +} + +- (void)serializeShareDataToSharedContainer:(ShareData*)shareData { + NSData* data = [NSJSONSerialization dataWithJSONObject:[shareData encodeToDictionary] options:0 error:nil]; + NSURL* path = [self.sharedContainerURL URLByAppendingPathComponent:ShareExtensionShareDataFilename isDirectory:NO]; + [data writeToFile:[path path] options:NSDataWritingAtomic error: nil]; +} + +- (NSDictionary*)resourceDictionaryForMediaURL:(NSURL*)url { + NSString* name = [url lastPathComponent]; + NSString* extension = [url pathExtension]; + NSString* mimeType = [self mimeTypeFor:extension]; + return [ShareData resourceDictionaryForURL:[url absoluteString] withName:name andMimeType:mimeType]; +} + +- (NSString*)mimeTypeFor:(NSString*)fileExtension{ + NSString* UTI = (__bridge_transfer NSString*)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL); + NSString* mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); + return mimeType; +} + +- (void)launchMainApp { + SEL selector = NSSelectorFromString(@"openURL:"); + + UIResponder* responder = self; + while (responder != nil) { + if ([responder respondsToSelector:selector]) { + [responder performSelector:selector withObject:[NSURL URLWithString:ShareExtensionShareURL]]; + break; + } + + responder = responder.nextResponder; + } +} + +@end diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index 395b70a92..ab3d0d522 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -27,7 +27,7 @@ import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/lo import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer'; import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive'; -const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native'); +const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking } = require('react-native'); import NetInfo from '@react-native-community/netinfo'; const DropdownAlert = require('react-native-dropdownalert').default; @@ -631,6 +631,12 @@ class AppComponent extends React.Component { this.onAppStateChange_ = () => { PoorManIntervals.update(); }; + + this.handleOpenURL_ = (event: any) => { + if (event.url == ShareExtension.shareURL) { + void this.handleShareData(); + } + }; } // 2020-10-08: It seems the initialisation code is quite fragile in general and should be kept simple. @@ -683,6 +689,8 @@ class AppComponent extends React.Component { }); } + Linking.addEventListener('url', this.handleOpenURL_); + BackButtonService.initialize(this.backButtonHandler_); AlarmService.setInAppNotificationHandler(async (alarmId: string) => { @@ -693,21 +701,14 @@ class AppComponent extends React.Component { AppState.addEventListener('change', this.onAppStateChange_); - const sharedData = await ShareExtension.data(); - if (sharedData) { - reg.logger().info('Received shared data'); - if (this.props.selectedFolderId) { - await handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch); - } else { - reg.logger().info('Cannot handle share - default folder id is not set'); - } - } + await this.handleShareData(); setUpQuickActions(this.props.dispatch, this.props.selectedFolderId); } componentWillUnmount() { AppState.removeEventListener('change', this.onAppStateChange_); + Linking.removeEventListener('url', this.handleOpenURL_); if (this.unsubscribeNetInfoHandler_) this.unsubscribeNetInfoHandler_(); } @@ -741,6 +742,18 @@ class AppComponent extends React.Component { return false; } + async handleShareData() { + const sharedData = await ShareExtension.data(); + if (sharedData) { + reg.logger().info('Received shared data'); + if (this.props.selectedFolderId) { + await handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch); + } else { + reg.logger().info('Cannot handle share - default folder id is not set'); + } + } + } + UNSAFE_componentWillReceiveProps(newProps: any) { if (newProps.syncStarted != this.lastSyncStarted_) { if (!newProps.syncStarted) FoldersScreenUtils.refreshFolders(); diff --git a/packages/app-mobile/utils/ShareExtension.ts b/packages/app-mobile/utils/ShareExtension.ts index fc4f97fb0..7f457400c 100644 --- a/packages/app-mobile/utils/ShareExtension.ts +++ b/packages/app-mobile/utils/ShareExtension.ts @@ -6,14 +6,16 @@ export interface SharedData { resources?: string[]; } -const ShareExtension = (Platform.OS === 'android' && NativeModules.ShareExtension) ? +const ShareExtension = (NativeModules.ShareExtension) ? { data: () => NativeModules.ShareExtension.data(), close: () => NativeModules.ShareExtension.close(), + shareURL: (Platform.OS === 'ios') ? NativeModules.ShareExtension.getConstants().SHARE_EXTENSION_SHARE_URL : '', } : { data: () => {}, close: () => {}, + shareURL: '', }; export default ShareExtension; diff --git a/packages/app-mobile/utils/shareHandler.ts b/packages/app-mobile/utils/shareHandler.ts index 21eec8ed6..35ae71b5b 100644 --- a/packages/app-mobile/utils/shareHandler.ts +++ b/packages/app-mobile/utils/shareHandler.ts @@ -5,16 +5,20 @@ import Note from '@joplin/lib/models/Note'; import checkPermissions from './checkPermissions.js'; const { ToastAndroid } = require('react-native'); const { PermissionsAndroid } = require('react-native'); +const { Platform } = require('react-native'); export default async (sharedData: SharedData, folderId: string, dispatch: Function) => { if (!!sharedData.resources && sharedData.resources.length > 0) { - const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE); + // No need to check permissions for iOS, the files are already in the shared container + if (Platform.OS === 'android') { + const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE); - if (response !== PermissionsAndroid.RESULTS.GRANTED) { - ToastAndroid.show('Cannot receive shared data - permission denied', ToastAndroid.SHORT); - ShareExtension.close(); - return; + if (response !== PermissionsAndroid.RESULTS.GRANTED) { + ToastAndroid.show('Cannot receive shared data - permission denied', ToastAndroid.SHORT); + ShareExtension.close(); + return; + } } }