├── .gitignore
├── LICENSE
├── README.md
├── android
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── chat
│ └── rocket
│ ├── RealPathUtil.java
│ ├── ShareModule.java
│ └── SharePackage.java
├── assets
├── NSExtensionActivationRule.png
├── android-demo.gif
├── ios-demo.gif
├── ios_step_01.png
├── ios_step_02.png
├── ios_step_03.png
├── ios_step_04.png
├── ios_step_05.png
├── ios_step_06.png
├── ios_step_07.png
├── ios_step_08.png
├── ios_step_09.png
├── ios_step_10.png
├── ios_step_11.png
├── ios_step_12.png
├── ios_step_13.png
├── ios_step_14.png
├── ios_step_15.png
├── ios_step_16.png
└── ios_step_17.png
├── bin
└── react-native-xcode.sh
├── ios
├── ReactNativeShareExtension.h
├── ReactNativeShareExtension.m
└── ReactNativeShareExtension.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── ali.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata
│ └── ali.xcuserdatad
│ └── xcschemes
│ ├── ReactNativeShareExtension.xcscheme
│ └── xcschememanagement.plist
├── lib
└── index.js
├── package.json
└── rn-extensions-share.podspec
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.iml
3 | .tags
4 | .tags1
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Ali Najafizadeh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RN Share Extension
2 |
3 | This is a helper module which brings react native as an engine to drive share extension for your app.
4 |
5 | Fork from: [react-native-share-extension](https://github.com/alinz/react-native-share-extension)
6 |
7 |
8 |
9 |
10 |
11 |
12 | # Example
13 |
14 | [RN-Extensions-Share-Example](https://github.com/djorkaeffalexandre/share-extension)
15 |
16 | # Installation
17 |
18 | Installation should be very easy by just installing it from npm.
19 |
20 | ```js
21 | npm install rn-extensions-share --save
22 | ```
23 | or
24 | ```js
25 | yarn add rn-extensions-share
26 | ```
27 |
28 | # Setup
29 |
30 | The setup requires a little bit more work. I will try to describe as detail as possible. I would love to use `rnpm` so I will welcome pull request.
31 |
32 | ## iOS
33 |
34 | - Click on your project's name
35 | - Click on `+` sign
36 |
37 |
38 |
39 |
40 |
41 | - Select `Share Extension` under `iOS > Application Extension`
42 |
43 |
44 |
45 |
46 |
47 | - Select a name for your new share extension, in my case I chose `MyShareEx`
48 |
49 |
50 |
51 |
52 |
53 | - Delete both `ShareViewController.h` and `ShareViewController.m`. make sure to click on the `Move to Trash` button during deletion.
54 |
55 |
56 |
57 |
58 |
59 | - Create a new file under your share extension group, in my case it was `MyShareEx`
60 |
61 |
62 |
63 |
64 |
65 | - Make sure that the type of that object is `Objective-C File`, e.g. for `MyShareEx` name it `MyShareEx.m`
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | - Since we deleted `ShareViewController.m`, we need to tell the storyboard of your share extension where the view needs to be loaded. So click on `MainInterface.storyboard` and replace the class field from `ShareViewController` to whatever you chose above (in my case `MyShareEx`)
76 |
77 |
78 |
79 |
80 |
81 | - Now it's time to add our library. Right click on the `Libraries` group and select `Add Files to "Sample1"...`
82 |
83 |
84 |
85 |
86 |
87 | - select `node_modules` > `rn-extensions-share` > `ios` > `ReactNativeShareExtension.xcodeproj`
88 |
89 |
90 |
91 |
92 |
93 | - Now we need to tell the share extension that we want to read new header files. Click on project name (in my case `Sample1`), then click on your extension name (in my case `MyShareEx`). After that click on Build Settings and search for `Header Search Paths`
94 |
95 |
96 |
97 |
98 |
99 | - Add the new path `$(SRCROOT)/../node_modules/rn-extensions-share/ios` with `recursive` selected
100 |
101 |
102 |
103 |
104 |
105 | - We need to add some linker flags as well, so search for `Other Linker Flags` and add `-ObjC` and `-lc++`
106 |
107 |
108 |
109 |
110 |
111 | - We also need to add all the static libraries such as `React` and `React Native Share Extension`. Select the `General` tab and under `Linked frameworks and Libraries` click on `+` and add all of the selected static binaries there
112 |
113 |
114 |
115 |
116 |
117 | - We need to modify the `Info.plist` inside the extension (e.g. `MyShareEx/Info.plist`) to make sure that our share extension can connect to internet. This is useful if you need your share extension connects to your API server or react-native remote server dev. For doing that we need to `App Transport Security Settings` to `Info.plist`
118 |
119 |
120 |
121 |
122 |
123 | - Now go back to your extension file (in my case `MyShareEx.m`) and paste the following code there **being sure to substitute `MyShareEx` in all three places for whatever you chose above**
124 |
125 | > If your project entry is `index.js` instead of `index.ios.js` then needs to replace `@"index.ios"` with `@"index"`
126 |
127 | ```objective-c
128 | #import
129 | #import "ReactNativeShareExtension.h"
130 | #import
131 | #import
132 | #import
133 |
134 | @interface MyShareEx : ReactNativeShareExtension
135 | @end
136 |
137 | @implementation MyShareEx
138 |
139 | RCT_EXPORT_MODULE();
140 |
141 | - (UIView*) shareView {
142 | NSURL *jsCodeLocation;
143 |
144 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
145 |
146 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
147 | moduleName:@"MyShareEx"
148 | initialProperties:nil
149 | launchOptions:nil];
150 | rootView.backgroundColor = nil;
151 |
152 | // Uncomment for console output in Xcode console for release mode on device:
153 | // RCTSetLogThreshold(RCTLogLevelInfo - 1);
154 |
155 | return rootView;
156 | }
157 |
158 | @end
159 | ```
160 |
161 | # Set the NSExtensionActivationRule key in your Info.plist
162 |
163 | For the time being, this package only handles sharing of urls specifically from browsers. In order to tell the system to show your extension only when sharing a url, you must set the `NSExtensionActivationRule` key (under `NSExtensionAttributes`) in the share extension's Info.plist file as follows (this is also needed to pass Apple's reveiw):
164 |
165 | ```
166 | NSExtensionAttributes
167 |
168 | NSExtensionActivationRule
169 |
170 | NSExtensionActivationSupportsWebURLWithMaxCount
171 | 1
172 |
173 |
174 | ```
175 |
176 |
177 |
178 |
179 |
180 | Note that while the above will prevent many apps from wrongly sharing using your extension, some apps (e.g., YouTube) will still allow sharing using your extension, which might cause your extension to crash. Check out [this issue](https://github.com/alinz/react-native-share-extension/issues/40) for details.
181 |
182 | For reference about `NSExtensionActivationRule` checkout [Apple's docs](https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW8)
183 |
184 |
185 | - Try to build the project, it should now build successfully!
186 |
187 |
188 | ## Android
189 |
190 | - Edit `android/settings.gradle` and add the following
191 |
192 | ```
193 | include ':app', ':rn-extensions-share'
194 |
195 | project(':rn-extensions-share').projectDir = new File(rootProject.projectDir, '../node_modules/rn-extensions-share/android')
196 | ```
197 |
198 | - Edit `android/app/build.gradle` and add the following line before the react section in dependencies
199 |
200 | ```
201 | dependencies {
202 | ...
203 | compile project(':rn-extensions-share')
204 | compile "com.facebook.react:react-native:+"
205 | }
206 | ```
207 |
208 | - Create a folder called `share` under your java project and create two files. Call them `ShareActivity.java` and `ShareApplication.java`....just like your main project.
209 |
210 | - ShareActivity should look like this
211 |
212 | ```java
213 | // define your share project, if your main project is com.sample1, then com.sample1.share makes sense....
214 | package com.sample1.share;
215 |
216 |
217 | // import ReactActivity
218 | import com.facebook.react.ReactActivity;
219 |
220 |
221 | public class ShareActivity extends ReactActivity {
222 | @Override
223 | protected String getMainComponentName() {
224 | // this is the name AppRegistry will use to launch the Share View
225 | return "MyShareEx";
226 | }
227 |
228 | }
229 | ```
230 |
231 | - ShareApplication should now look like this
232 |
233 | ```java
234 | // your package you defined in ShareActivity
235 | package com.sample1.share;
236 | // import build config
237 | import com.sample1.BuildConfig;
238 |
239 | import chat.rocket.SharePackage;
240 |
241 | import android.app.Application;
242 |
243 | import com.facebook.react.shell.MainReactPackage;
244 | import com.facebook.react.ReactNativeHost;
245 | import com.facebook.react.ReactApplication;
246 | import com.facebook.react.ReactPackage;
247 |
248 | import java.util.Arrays;
249 | import java.util.List;
250 |
251 |
252 | public class ShareApplication extends Application implements ReactApplication {
253 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
254 | @Override
255 | protected boolean getUseDeveloperSupport() {
256 | return BuildConfig.DEBUG;
257 |
258 | }
259 |
260 | @Override
261 | protected List getPackages() {
262 | return Arrays.asList(
263 | new MainReactPackage(),
264 | new SharePackage()
265 | );
266 | }
267 | };
268 |
269 | @Override
270 | public ReactNativeHost getReactNativeHost() {
271 | return mReactNativeHost;
272 | }
273 | }
274 | ```
275 |
276 | - MainApplication should now look like this
277 |
278 | ```java
279 | // your package you defined in ShareActivity
280 | package com.sample1;
281 |
282 | import android.app.Application;
283 | import android.util.Log;
284 |
285 | import com.facebook.react.ReactApplication;
286 | import com.facebook.react.ReactInstanceManager;
287 | import com.facebook.react.ReactNativeHost;
288 | import com.facebook.react.ReactPackage;
289 | import com.facebook.react.shell.MainReactPackage;
290 |
291 | import chat.rocket.SharePackage;
292 |
293 | import java.util.Arrays;
294 | import java.util.List;
295 |
296 | public class MainApplication extends Application implements ReactApplication {
297 |
298 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
299 | @Override
300 | protected boolean getUseDeveloperSupport() {
301 | return BuildConfig.DEBUG;
302 | }
303 |
304 | @Override
305 | protected List getPackages() {
306 | return Arrays.asList(
307 | new MainReactPackage(),
308 | new SharePackage()
309 | );
310 | }
311 | };
312 |
313 | @Override
314 | public ReactNativeHost getReactNativeHost() {
315 | return mReactNativeHost;
316 | }
317 | }
318 | ```
319 |
320 | - Edit `android/app/src/main/AndroidMainfest.xml` and add the new `activity` right after `devSettingActivity`.
321 |
322 | ```xml
323 |
324 |
325 |
332 |
333 |
334 |
335 | // for sharing links include
336 |
337 | // for sharing photos include
338 |
339 | // for all types
340 |
341 |
342 |
343 | ```
344 |
345 | in this new `activity` I have used `@string/title_activity_share`, you can add those in `res/values`.
346 |
347 | So in `values/strings.xml`
348 |
349 | ```xml
350 |
351 | ...
352 | MyShareEx
353 |
354 | ```
355 |
356 | - Now you should be able to compile the code without any errors!
357 |
358 | > If you need to add more packages to your share extension, do not override
359 | `getPackages`, instead override the `getMorePackages` method under `ShareExActivity`.
360 |
361 | # Share Component
362 |
363 | So both share extension and main application are using the same code base, or same main.jsbundle file. So the trick to separate Share and Main App is registering 2 different Component entries with `AppRegistry.registerComponent`.
364 |
365 | In both the iOS and Android share extensions we are telling react to load the extension component (in my case `MyShareEx`) from js.
366 |
367 | So in `index.ios.js` and `index.android.js` we are writing the same code:
368 |
369 | ```js
370 | //index.android.js
371 | import React from 'react'
372 | import { AppRegistry } from 'react-native'
373 |
374 | import App from './app.android'
375 | import Share from './share.android'
376 |
377 | AppRegistry.registerComponent('Sample1', () => App)
378 | AppRegistry.registerComponent('MyShareEx', () => Share) // TODO: Replace MyShareEx with my extension name
379 | ```
380 |
381 | ```js
382 | //index.ios.js
383 | import React from 'react'
384 | import { AppRegistry } from 'react-native'
385 |
386 | import App from './app.ios'
387 | import Share from './share.ios'
388 |
389 | AppRegistry.registerComponent('Sample1', () => App)
390 | AppRegistry.registerComponent('MyShareEx', () => Share) // TODO: Replace MyShareEx with my extension name
391 | ```
392 |
393 | So the `app.ios` and `app.android.js` refers to main app and `share.ios.js` and `share.android.js` refers to share extension.
394 |
395 | # Share Extension APIs
396 |
397 | - `data()` is a function that returns a promise. Once the promise is resolved, you get two values, `type` and `value`.
398 |
399 |
400 | ```js
401 | import ShareExtension from 'rn-extensions-share'
402 | ...
403 |
404 | const { type, value } = await ShareExtension.data(); // type = 'media' | 'text'
405 | ```
406 |
407 | - `close()`
408 |
409 | Simply closes the share extension and returns the touch event back to application that triggered the share.
410 |
411 | # On iOS: Re-harvesting a shared image
412 |
413 | If your share extension is being used to process shared images (be it to social media or processing the image for information), `rn-extensions-share` will provide a URL within `value` with the location of the image.
414 |
415 | If you wish to pass this URL back down to Swift or Objective-C for whatever reason, you can use the following to convert the URL back into a UIImage:
416 |
417 | ```swift
418 | func harvestImage(from imageURL: String) {
419 | if let imgData = FileManager.default.contents(atPath: imageURL) {
420 | if let img = UIImage(data: data){
421 | // Process image..
422 | }
423 | }
424 | }
425 | ```
426 |
427 | or in Objective-C:
428 |
429 | ```smalltalk
430 | -(void)harvestImage:(NSString *)imageURL {
431 | NSFileManager *fileManager = [NSFileManager defaultManager];
432 | NSData *imgData = [fileManager contentsAtPath:imageURL];
433 | UIImage img = [UIImage imageWithData:imgData];
434 | // Process Image..
435 | }
436 | ```
437 |
438 |
439 | # Test on Device without dev-server
440 |
441 | Because a share extension in ios is treated as a separate container, they do not have access to main app folder. A resolution for this is that you have to build the script twice and package it inside the share extension container. The easiest way of doing this is create a `New Script Phase` in `Build Phases` of your share extension and copy the following line
442 |
443 | ```bash
444 | export NODE_BINARY=node
445 | ../node_modules/react-native/scripts/react-native-xcode.sh
446 | ```
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 | # App and app extension bundles
458 |
459 | The app and app extension bundles can be shared or separated. Separating bundles allows for a minimal footprint for both app and app extension.
460 |
461 | #### plist key legend
462 |
463 | `BundleEntryFilename` - react-native index or shared index filename.
464 |
465 | `BundleSkipped` - Skips bundling when true.
466 |
467 | `BundleCopied` - Copies bundle instead of building when true. (Note: Should be set as true for share extension plist only when bundles are shared.)
468 |
469 | `BundleForced` - Forces bundling when true.
470 |
471 | ### Shared bundles
472 |
473 | The app extension target builds pre-loaded bundle and is copied to the app target.
474 |
475 | #### app plist values
476 |
477 | `BundleEntryFilename` = 'index.js'
478 |
479 | `BundleSkipped` = true
480 |
481 | `BundleCopied` = true
482 |
483 | #### app target's "Bundle React Native code and images" phase
484 | ```
485 | export NODE_BINARY=node
486 | ../bin/react-native-xcode.sh
487 | ```
488 |
489 | #### appShareExtension plist values
490 |
491 | `BundleEntryFilename` = 'index.js'
492 |
493 | `BundleForced` = true
494 |
495 | #### appShareExtension target's "Bundle React Native code and images" phase
496 | ```
497 | cd ../
498 | npm run cp-native-assets
499 | cd ios/
500 | export NODE_BINARY=node
501 | ../bin/react-native-xcode.sh
502 | ```
503 |
504 | ### Separated bundles
505 |
506 | The app extension and app targets build their own unique bundles.
507 |
508 | NSNotificationCenter will kill app extensions that are unable to free memory resources when receiving low memory warnings. Also, shared bundles introduce library/pod dependencies that aren't needed by both apps. Configuring separate bundles via Xcode requires customizing react-native-xcode.sh; a quick example customization can be found in the bin directory. Update the path to the packager in both the app and app extension target's "Bundle React Native code and images" Build Phases.
509 |
510 | Build time can be halved while debugging by disabling the bundle for whichever target you aren't debugging (app or app ex).
511 |
512 | #### app plist values
513 |
514 | `BundleEntryFilename` = 'index.js'
515 |
516 | #### app target's "Bundle React Native code and images" phase
517 | ```
518 | export NODE_BINARY=node
519 | #export ENTRY_FILENAME=index
520 | ../bin/react-native-xcode.sh
521 | ```
522 |
523 | #### appShareExtension plist values
524 |
525 | `BundleEntryFilename` = 'share.index.js'
526 |
527 | `BundleForced` = true
528 |
529 | #### appShareExtension target's "Bundle React Native code and images" phase
530 | ```
531 | cd ../
532 | npm run cp-native-assets
533 | cd ios/
534 | export NODE_BINARY=node
535 | ../bin/react-native-xcode.sh
536 | ```
537 |
538 | # Open container app
539 | Steps needed to open the host application from the share extension.
540 | 1) Allow your app to be opened via URL Scheme - [Learn more](https://medium.com/react-native-training/deep-linking-your-react-native-app-d87c39a1ad5e)
541 | 2) In xcode, select share extension and go to Build Settings and set **Require Only App-Extension-Safe API** to `NO`.
542 |
543 | Then you can open your app from the share extension by calling openURL:
544 |
545 | ```
546 | import ShareExtension from 'rn-extensions-share';
547 |
548 | ShareExtension.openURL('sample://example/url');
549 | ```
550 |
551 | # Troubleshooting on iOS devices
552 |
553 | Using the iOS Simulator and remote react-native debugger to develop the extension can hide issues that won't occur until testing on device. If you're experiencing issues running the extension on iOS devices, examine the Xcode console or device log for any obvious errors. If the Xcode console isn't receiving console output, ensure that the OS_ACTIVITY_MODE=disable environment var isn't enabled for the active scheme (see https://github.com/facebook/react-native/issues/10027). OS_ACTIVITY_MODE will hide device logging in the Xcode console, so its use is only advisable for iOS Simulator. For release mode, in order to view console output and see all output in the syslog, uncomment the `RCTSetLogThreshold(RCTLogLevelInfo - 1);` statement in your MyShareEx class.
554 |
555 | 1. If you're using react-native latest, error boundaries might help with JS errors. Another option is to catch render exceptions or test for errors, then render that output with something like a Text component. As long as your share app initializes, you should be able to see yellowbox/redbox errors. If you're not seeing them, you likely have an initialization issue.
556 | 2. Disable bundling on the main target when debugging the extension target, it's not needed when you're not working with the main app.
557 | 3. [Enable breaking on exceptions](http://blog.manbolo.com/2012/01/23/xcode-tips-1-break-on-exceptions). This is helpful if there are any exceptions in the extension itself; perhaps most useful if you've customized the native module.
558 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 |
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:1.1.3'
8 | }
9 | }
10 |
11 | apply plugin: 'com.android.library'
12 |
13 | android {
14 | compileSdkVersion 23
15 | buildToolsVersion "23.0.1"
16 |
17 | defaultConfig {
18 | minSdkVersion 16
19 | targetSdkVersion 22
20 | versionCode 1
21 | versionName "1.0"
22 | }
23 | lintOptions {
24 | abortOnError false
25 | }
26 | }
27 |
28 | repositories {
29 | mavenCentral()
30 | }
31 |
32 | dependencies {
33 | implementation "com.facebook.react:react-native:+"
34 | }
35 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/src/main/java/chat/rocket/RealPathUtil.java:
--------------------------------------------------------------------------------
1 | // https://github.com/ivpusic/react-native-image-crop-picker/blob/master/android/src/main/java/com/reactnative/ivpusic/imagepicker/RealPathUtil.java
2 | package chat.rocket;
3 |
4 | import android.annotation.TargetApi;
5 | import android.content.ContentUris;
6 | import android.content.Context;
7 | import android.database.Cursor;
8 | import android.net.Uri;
9 | import android.os.Build;
10 | import android.os.Environment;
11 | import android.provider.DocumentsContract;
12 | import android.provider.MediaStore;
13 |
14 | import java.io.File;
15 | import java.io.FileOutputStream;
16 | import java.io.IOException;
17 | import java.io.InputStream;
18 |
19 | class RealPathUtil {
20 | @TargetApi(Build.VERSION_CODES.KITKAT)
21 | static String getRealPathFromURI(final Context context, final Uri uri) throws IOException {
22 |
23 | final boolean isKitKat = Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT;
24 |
25 | // DocumentProvider
26 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
27 | // ExternalStorageProvider
28 | if (isExternalStorageDocument(uri)) {
29 | final String docId = DocumentsContract.getDocumentId(uri);
30 | final String[] split = docId.split(":");
31 | final String type = split[0];
32 |
33 | if ("primary".equalsIgnoreCase(type)) {
34 | return Environment.getExternalStorageDirectory() + "/" + split[1];
35 | } else {
36 | final int splitIndex = docId.indexOf(':', 1);
37 | final String tag = docId.substring(0, splitIndex);
38 | final String path = docId.substring(splitIndex + 1);
39 |
40 | String nonPrimaryVolume = getPathToNonPrimaryVolume(context, tag);
41 | if (nonPrimaryVolume != null) {
42 | String result = nonPrimaryVolume + "/" + path;
43 | File file = new File(result);
44 | if (file.exists() && file.canRead()) {
45 | return result;
46 | }
47 | return null;
48 | }
49 | }
50 | }
51 | // DownloadsProvider
52 | else if (isDownloadsDocument(uri)) {
53 | final String id = DocumentsContract.getDocumentId(uri);
54 | final Uri contentUri = ContentUris.withAppendedId(
55 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
56 |
57 | return getDataColumn(context, contentUri, null, null);
58 | }
59 | // MediaProvider
60 | else if (isMediaDocument(uri)) {
61 | final String docId = DocumentsContract.getDocumentId(uri);
62 | final String[] split = docId.split(":");
63 | final String type = split[0];
64 |
65 | Uri contentUri = null;
66 | if ("image".equals(type)) {
67 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
68 | } else if ("video".equals(type)) {
69 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
70 | } else if ("audio".equals(type)) {
71 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
72 | }
73 |
74 | final String selection = "_id=?";
75 | final String[] selectionArgs = new String[] {
76 | split[1]
77 | };
78 |
79 | return getDataColumn(context, contentUri, selection, selectionArgs);
80 | }
81 | }
82 | // MediaStore (and general)
83 | else if ("content".equalsIgnoreCase(uri.getScheme())) {
84 | // Return the remote address
85 | if (isGooglePhotosUri(uri))
86 | return uri.getLastPathSegment();
87 | return getDataColumn(context, uri, null, null);
88 | }
89 | // File
90 | else if ("file".equalsIgnoreCase(uri.getScheme())) {
91 | return uri.getPath();
92 | }
93 |
94 | return null;
95 | }
96 |
97 | /**
98 | * If an image/video has been selected from a cloud storage, this method
99 | * should be call to download the file in the cache folder.
100 | *
101 | * @param context The context
102 | * @param fileName donwloaded file's name
103 | * @param uri file's URI
104 | * @return file that has been written
105 | */
106 | private static File writeToFile(Context context, String fileName, Uri uri) {
107 | String tmpDir = context.getCacheDir() + "/rocket-chat";
108 | Boolean created = new File(tmpDir).mkdir();
109 | fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
110 | File path = new File(tmpDir);
111 | File file = new File(path, fileName);
112 | try {
113 | FileOutputStream oos = new FileOutputStream(file);
114 | byte[] buf = new byte[8192];
115 | InputStream is = context.getContentResolver().openInputStream(uri);
116 | int c = 0;
117 | while ((c = is.read(buf, 0, buf.length)) > 0) {
118 | oos.write(buf, 0, c);
119 | oos.flush();
120 | }
121 | oos.close();
122 | is.close();
123 | } catch (Exception e) {
124 | e.printStackTrace();
125 | }
126 | return file;
127 | }
128 |
129 | /**
130 | * Get the value of the data column for this Uri. This is useful for
131 | * MediaStore Uris, and other file-based ContentProviders.
132 | *
133 | * @param context The context.
134 | * @param uri The Uri to query.
135 | * @param selection (Optional) Filter used in the query.
136 | * @param selectionArgs (Optional) Selection arguments used in the query.
137 | * @return The value of the _data column, which is typically a file path.
138 | */
139 | private static String getDataColumn(Context context, Uri uri, String selection,
140 | String[] selectionArgs) {
141 |
142 | Cursor cursor = null;
143 | final String[] projection = {
144 | MediaStore.MediaColumns.DATA,
145 | MediaStore.MediaColumns.DISPLAY_NAME,
146 | };
147 |
148 | try {
149 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
150 | null);
151 | if (cursor != null && cursor.moveToFirst()) {
152 | // Fall back to writing to file if _data column does not exist
153 | final int index = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
154 | String path = index > -1 ? cursor.getString(index) : null;
155 | if (path != null) {
156 | return cursor.getString(index);
157 | } else {
158 | final int indexDisplayName = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
159 | String fileName = cursor.getString(indexDisplayName);
160 | File fileWritten = writeToFile(context, fileName, uri);
161 | return fileWritten.getAbsolutePath();
162 | }
163 | }
164 | } finally {
165 | if (cursor != null)
166 | cursor.close();
167 | }
168 | return null;
169 | }
170 |
171 |
172 | /**
173 | * @param uri The Uri to check.
174 | * @return Whether the Uri authority is ExternalStorageProvider.
175 | */
176 | private static boolean isExternalStorageDocument(Uri uri) {
177 | return "com.android.externalstorage.documents".equals(uri.getAuthority());
178 | }
179 |
180 | /**
181 | * @param uri The Uri to check.
182 | * @return Whether the Uri authority is DownloadsProvider.
183 | */
184 | private static boolean isDownloadsDocument(Uri uri) {
185 | return "com.android.providers.downloads.documents".equals(uri.getAuthority());
186 | }
187 |
188 | /**
189 | * @param uri The Uri to check.
190 | * @return Whether the Uri authority is MediaProvider.
191 | */
192 | private static boolean isMediaDocument(Uri uri) {
193 | return "com.android.providers.media.documents".equals(uri.getAuthority());
194 | }
195 |
196 | /**
197 | * @param uri The Uri to check.
198 | * @return Whether the Uri authority is Google Photos.
199 | */
200 | private static boolean isGooglePhotosUri(Uri uri) {
201 | return "com.google.android.apps.photos.content".equals(uri.getAuthority());
202 | }
203 |
204 | @TargetApi(Build.VERSION_CODES.KITKAT)
205 | private static String getPathToNonPrimaryVolume(Context context, String tag) {
206 | File[] volumes = context.getExternalCacheDirs();
207 | if (volumes != null) {
208 | for (File volume : volumes) {
209 | if (volume != null) {
210 | String path = volume.getAbsolutePath();
211 | if (path != null) {
212 | int index = path.indexOf(tag);
213 | if (index != -1) {
214 | return path.substring(0, index) + tag;
215 | }
216 | }
217 | }
218 | }
219 | }
220 | return null;
221 | }
222 |
223 | }
--------------------------------------------------------------------------------
/android/src/main/java/chat/rocket/ShareModule.java:
--------------------------------------------------------------------------------
1 | package chat.rocket;
2 |
3 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
4 | import com.facebook.react.bridge.ReactApplicationContext;
5 | import com.facebook.react.bridge.Promise;
6 | import com.facebook.react.bridge.ReactMethod;
7 | import com.facebook.react.bridge.WritableMap;
8 | import com.facebook.react.bridge.WritableArray;
9 | import com.facebook.react.bridge.Arguments;
10 |
11 | import android.app.Activity;
12 | import android.content.Intent;
13 | import android.net.Uri;
14 |
15 | import java.io.File;
16 | import java.util.ArrayList;
17 |
18 | public class ShareModule extends ReactContextBaseJavaModule {
19 | private File tempFolder;
20 | public static final String CACHE_DIR = "rcShare";
21 |
22 | public ShareModule(ReactApplicationContext reactContext) {
23 | super(reactContext);
24 | }
25 |
26 | @Override
27 | public String getName() {
28 | return "ReactNativeShareExtension";
29 | }
30 |
31 | @ReactMethod
32 | public void close() {
33 | getCurrentActivity().finish();
34 | }
35 |
36 | @ReactMethod
37 | public void data(Promise promise) {
38 | promise.resolve(processIntent());
39 | }
40 |
41 | public WritableArray processIntent() {
42 | WritableMap map = Arguments.createMap();
43 | WritableArray items = Arguments.createArray();
44 |
45 | String text = "";
46 | String type = "";
47 | String action = "";
48 |
49 | Activity currentActivity = getCurrentActivity();
50 |
51 | if (currentActivity != null) {
52 | tempFolder = new File(currentActivity.getCacheDir(), CACHE_DIR);
53 |
54 | Intent intent = currentActivity.getIntent();
55 | action = intent.getAction();
56 | type = intent.getType();
57 |
58 | // Received some text
59 | if (Intent.ACTION_SEND.equals(action) && "text/plain".equals(type)) {
60 | text = intent.getStringExtra(Intent.EXTRA_TEXT);
61 |
62 | map.putString("value", text);
63 | map.putString("type", "text");
64 |
65 | items.pushMap(map);
66 |
67 | // Received a single file
68 | } else if (Intent.ACTION_SEND.equals(action)) {
69 | Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
70 |
71 | if (uri != null) {
72 | try {
73 | text = "file://" + RealPathUtil.getRealPathFromURI(currentActivity, uri);
74 | } catch (Exception e) {
75 | e.printStackTrace();
76 | }
77 |
78 | map.putString("value", text);
79 | map.putString("type", "media");
80 |
81 | items.pushMap(map);
82 | }
83 |
84 | // Received multiple files
85 | } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
86 | ArrayList uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
87 |
88 | for (Uri uri : uris) {
89 | String filePath = "";
90 | try {
91 | filePath = RealPathUtil.getRealPathFromURI(currentActivity, uri);
92 | } catch (Exception e) {
93 | e.printStackTrace();
94 | }
95 |
96 | map = Arguments.createMap();
97 | text = "file://" + filePath;
98 |
99 | map.putString("value", text);
100 | map.putString("type", "media");
101 |
102 | items.pushMap(map);
103 | }
104 | }
105 | }
106 |
107 | return items;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/android/src/main/java/chat/rocket/SharePackage.java:
--------------------------------------------------------------------------------
1 | package chat.rocket;
2 |
3 | import com.facebook.react.bridge.ReactApplicationContext;
4 | import com.facebook.react.bridge.JavaScriptModule;
5 | import com.facebook.react.bridge.NativeModule;
6 | import com.facebook.react.uimanager.ViewManager;
7 | import com.facebook.react.ReactPackage;
8 |
9 | import java.util.Arrays;
10 | import java.util.Collections;
11 | import java.util.List;
12 |
13 | public class SharePackage implements ReactPackage {
14 | @Override
15 | public List createNativeModules(ReactApplicationContext reactContext) {
16 | return Arrays.asList(new ShareModule(reactContext));
17 | }
18 |
19 | public List> createJSModules() {
20 | return Collections.emptyList();
21 | }
22 |
23 | @Override
24 | public List createViewManagers(ReactApplicationContext reactContext) {
25 | return Collections.emptyList();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/assets/NSExtensionActivationRule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/NSExtensionActivationRule.png
--------------------------------------------------------------------------------
/assets/android-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/android-demo.gif
--------------------------------------------------------------------------------
/assets/ios-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios-demo.gif
--------------------------------------------------------------------------------
/assets/ios_step_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_01.png
--------------------------------------------------------------------------------
/assets/ios_step_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_02.png
--------------------------------------------------------------------------------
/assets/ios_step_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_03.png
--------------------------------------------------------------------------------
/assets/ios_step_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_04.png
--------------------------------------------------------------------------------
/assets/ios_step_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_05.png
--------------------------------------------------------------------------------
/assets/ios_step_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_06.png
--------------------------------------------------------------------------------
/assets/ios_step_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_07.png
--------------------------------------------------------------------------------
/assets/ios_step_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_08.png
--------------------------------------------------------------------------------
/assets/ios_step_09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_09.png
--------------------------------------------------------------------------------
/assets/ios_step_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_10.png
--------------------------------------------------------------------------------
/assets/ios_step_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_11.png
--------------------------------------------------------------------------------
/assets/ios_step_12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_12.png
--------------------------------------------------------------------------------
/assets/ios_step_13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_13.png
--------------------------------------------------------------------------------
/assets/ios_step_14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_14.png
--------------------------------------------------------------------------------
/assets/ios_step_15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_15.png
--------------------------------------------------------------------------------
/assets/ios_step_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_16.png
--------------------------------------------------------------------------------
/assets/ios_step_17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/assets/ios_step_17.png
--------------------------------------------------------------------------------
/bin/react-native-xcode.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright (c) 2015-present, Facebook, Inc.
3 | # All rights reserved.
4 | #
5 | # This source code is licensed under the BSD-style license found in the
6 | # LICENSE file in the root directory of this source tree. An additional grant
7 | # of patent rights can be found in the PATENTS file in the same directory.
8 |
9 | # Bundle React Native app's code and image assets.
10 | # This script is supposed to be invoked as part of Xcode build process
11 | # and relies on environment variables (including PWD) set by Xcode
12 |
13 | DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
14 | MAIN_BUNDLE="main.jsbundle"
15 | BUNDLE_FILE="$DEST/$MAIN_BUNDLE"
16 | TMP_PATH="/tmp"
17 | PLISTBUDDY='/usr/libexec/PlistBuddy'
18 | PLIST=$TARGET_BUILD_DIR/$INFOPLIST_PATH
19 |
20 | [ -z "$SKIP_BUNDLING" ] && SKIP_BUNDLING=$($PLISTBUDDY -c "Print :BundleSkipped" "${PLIST}")
21 | [ -z "$CP_BUNDLING" ] && CP_BUNDLING=$($PLISTBUDDY -c "Print :BundleCopied" "${PLIST}")
22 |
23 | if [[ "$SKIP_BUNDLING" && $SKIP_BUNDLING == "true" ]]; then
24 | echo "SKIP_BUNDLING enabled; skipping."
25 | if [[ "$CP_BUNDLING" && $CP_BUNDLING == "true" ]]; then
26 | TMP_BUNDLE="$TMP_PATH/$MAIN_BUNDLE"
27 | echo "CP_BUNDLING enabled; copying $TMP_BUNDLE to $DEST/"
28 | if [ -f "$TMP_BUNDLE" ]; then
29 | cp "$TMP_PATH/$MAIN_BUNDLE"* "$DEST/"
30 | else
31 | echo "CP_BUNDLING $TMP_BUNDLE does not exist!"
32 | fi
33 | fi
34 | exit 0;
35 | fi
36 |
37 | [ -z "$IS_DEV" ] && IS_DEV=$($PLISTBUDDY -c "Print :BundleDev" "${PLIST}")
38 | [ -z "$FORCE_BUNDLING" ] && FORCE_BUNDLING=$($PLISTBUDDY -c "Print :BundleForced" "${PLIST}")
39 |
40 | if [ -z "$IS_DEV" ]; then
41 | case "$CONFIGURATION" in
42 | *Debug*)
43 | if [[ "$PLATFORM_NAME" == *simulator ]]; then
44 | if [[ "$FORCE_BUNDLING" && $FORCE_BUNDLING == "true" ]]; then
45 | echo "FORCE_BUNDLING enabled; continuing to bundle."
46 | else
47 | echo "Skipping bundling in Debug for the Simulator (since the packager bundles for you). Use the FORCE_BUNDLING env flag or BundleForced plist key to change this behavior."
48 | exit 0;
49 | fi
50 | else
51 | echo "Bundling for physical device. Use the SKIP_BUNDLING flag to change this behavior."
52 | fi
53 |
54 | DEV=true
55 | ;;
56 | "")
57 | echo "$0 must be invoked by Xcode"
58 | exit 1
59 | ;;
60 | *)
61 | DEV=false
62 | ;;
63 | esac
64 | else
65 | if [[ "$PLATFORM_NAME" == *simulator ]]; then
66 | if [[ "$FORCE_BUNDLING" && $FORCE_BUNDLING == "true" ]]; then
67 | echo "FORCE_BUNDLING enabled; continuing to bundle."
68 | else
69 | echo "Skipping bundling in Debug for the Simulator (since the packager bundles for you). Use the FORCE_BUNDLING flag to change this behavior."
70 | exit 0;
71 | fi
72 | else
73 | echo "Bundling for physical device. Use the SKIP_BUNDLING flag to change this behavior."
74 | fi
75 | DEV=$IS_DEV
76 | fi
77 |
78 | # Path to react-native folder inside node_modules
79 | # REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
80 | # Path to react-native folder inside src/native/utils/bin
81 | REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../node_modules/react-native" && pwd)"
82 | echo "REACT_NATIVE_DIR: $REACT_NATIVE_DIR"
83 |
84 | # Xcode project file for React Native apps is located in ios/ subfolder
85 | cd "${REACT_NATIVE_DIR}"/../..
86 |
87 | # Define NVM_DIR and source the nvm.sh setup script
88 | [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm"
89 |
90 | # Define default ENTRY_FILENAME
91 | [ -z "$ENTRY_FILENAME" ] && ENTRY_FILENAME=$($PLISTBUDDY -c "Print :BundleEntryFilename" "${PLIST}")
92 | [ -z "$ENTRY_FILENAME" ] && ENTRY_FILENAME="index.js"
93 | echo "ENTRY_FILENAME: $ENTRY_FILENAME"
94 |
95 | js_file_type=.js
96 | ios_file_type=.ios.js
97 | ios_file_name="${ENTRY_FILENAME/$js_file_type/$ios_file_type}"
98 |
99 | # Define entry file
100 | if [[ -s $ios_file_name ]]; then
101 | ENTRY_FILE=${1:-$ios_file_name}
102 | else
103 | ENTRY_FILE=${1:-$ENTRY_FILENAME}
104 | fi
105 |
106 | if [[ -s "$HOME/.nvm/nvm.sh" ]]; then
107 | . "$HOME/.nvm/nvm.sh"
108 | elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then
109 | . "$(brew --prefix nvm)/nvm.sh"
110 | fi
111 |
112 | # Set up the nodenv node version manager if present
113 | if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then
114 | eval "$("$HOME/.nodenv/bin/nodenv" init -)"
115 | fi
116 |
117 | [ -z "$NODE_BINARY" ] && export NODE_BINARY="node"
118 |
119 | [ -z "$CLI_PATH" ] && export CLI_PATH="$REACT_NATIVE_DIR/local-cli/cli.js"
120 |
121 | nodejs_not_found()
122 | {
123 | echo "error: Can't find '$NODE_BINARY' binary to build React Native bundle" >&2
124 | echo "If you have non-standard nodejs installation, select your project in Xcode," >&2
125 | echo "find 'Build Phases' - 'Bundle React Native code and images'" >&2
126 | echo "and change NODE_BINARY to absolute path to your node executable" >&2
127 | echo "(you can find it by invoking 'which node' in the terminal)" >&2
128 | exit 2
129 | }
130 |
131 | type $NODE_BINARY >/dev/null 2>&1 || nodejs_not_found
132 |
133 | # Print commands before executing them (useful for troubleshooting)
134 | set -x
135 | # DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
136 |
137 | if [[ "$CONFIGURATION" = "Debug" && ! "$PLATFORM_NAME" == *simulator ]]; then
138 | BUNDLE_SERVER=$($PLISTBUDDY -c "Print :BundleServer" "${PLIST}")
139 | echo "BUNDLE_SERVER: ${BUNDLE_SERVER}"
140 | if [ -z "$BUNDLE_SERVER" ]; then
141 | IP=$(ipconfig getifaddr en0)
142 | if [ -z "$IP" ]; then
143 | IP=$(ifconfig | grep 'inet ' | grep -v ' 127.' | cut -d\ -f2 | awk 'NR==1{print $1}')
144 | fi
145 | else
146 | IP=$BUNDLE_SERVER
147 | fi
148 |
149 | if [ -z ${DISABLE_XIP+x} ]; then
150 | IP="$IP.xip.io"
151 | fi
152 |
153 | $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:localhost:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST"
154 | $PLISTBUDDY -c "Add NSAppTransportSecurity:NSExceptionDomains:$IP:NSTemporaryExceptionAllowsInsecureHTTPLoads bool true" "$PLIST"
155 | echo "$IP" > "$DEST/ip.txt"
156 | fi
157 |
158 | $NODE_BINARY "$CLI_PATH" bundle \
159 | --entry-file "$ENTRY_FILE" \
160 | --platform ios \
161 | --dev $DEV \
162 | --reset-cache \
163 | --bundle-output "$BUNDLE_FILE" \
164 | --assets-dest "$DEST"
165 |
166 | if [[ $DEV != true && ! -f "$BUNDLE_FILE" ]]; then
167 | echo "error: File $BUNDLE_FILE does not exist. This must be a bug with" >&2
168 | echo "React Native, please report it here: https://github.com/facebook/react-native/issues"
169 | exit 2
170 | else
171 | cp "$BUNDLE_FILE"* $TMP_PATH
172 | if [[ $DEV == "true" ]]; then
173 | if nc -w 5 -z localhost 8081 ; then
174 | if ! curl -s "http://localhost:8081/status" | grep -q "packager-status:running"; then
175 | echo "Port 8081 already in use, packager is either not running or not running correctly"
176 | exit 0
177 | fi
178 | else
179 | open "$REACT_NATIVE_DIR/scripts/launchPackager.command" || echo "Can't start packager automatically"
180 | fi
181 | fi
182 | fi
183 |
--------------------------------------------------------------------------------
/ios/ReactNativeShareExtension.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "React/RCTBridgeModule.h"
3 |
4 | @interface ReactNativeShareExtension : UIViewController
5 | - (UIView*) shareView;
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/ReactNativeShareExtension.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #if __has_include()
5 | #import
6 | #endif
7 |
8 | #import "ReactNativeShareExtension.h"
9 |
10 | NSExtensionContext* extensionContext;
11 |
12 | @implementation ReactNativeShareExtension
13 |
14 | - (UIView*) shareView
15 | {
16 | return nil;
17 | }
18 |
19 | - (BOOL)isModalInPresentation {
20 | return true;
21 | }
22 |
23 | RCT_EXPORT_MODULE();
24 |
25 | - (void)viewDidLoad
26 | {
27 | [super viewDidLoad];
28 |
29 | extensionContext = self.extensionContext;
30 |
31 | UIView *rootView = [self shareView];
32 | if (rootView.backgroundColor == nil) {
33 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1 green:1 blue:1 alpha:0.1];
34 | }
35 |
36 | #if __has_include()
37 | [RCTUtilsUIOverride setPresentedViewController:self];
38 | #endif
39 |
40 | self.view = rootView;
41 | }
42 |
43 | RCT_EXPORT_METHOD(openURL:(NSString *)url)
44 | {
45 | UIApplication *application = [UIApplication sharedApplication];
46 | NSURL *openUrl = [NSURL URLWithString:[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
47 | if (@available(iOS 10.0, *)) {
48 | [application openURL:openUrl options:@{} completionHandler: nil];
49 | }
50 | }
51 |
52 | RCT_EXPORT_METHOD(close)
53 | {
54 | [extensionContext completeRequestReturningItems:nil
55 | completionHandler:nil];
56 | exit(0);
57 | }
58 |
59 | RCT_REMAP_METHOD(data, resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
60 | {
61 | [self extractDataFromContext:extensionContext withCallback:^(NSArray* items, NSException* err) {
62 | resolve(items);
63 | }];
64 | }
65 |
66 | - (void)extractDataFromContext:(NSExtensionContext *)context withCallback:(void(^)(NSArray *items, NSException *exception))callback
67 | {
68 | __block NSMutableArray *data = [NSMutableArray new];
69 |
70 | NSExtensionItem *item = [context.inputItems firstObject];
71 | NSArray *attachments = item.attachments;
72 | __block NSUInteger index = 0;
73 |
74 | [attachments enumerateObjectsUsingBlock:^(NSItemProvider *provider, NSUInteger idx, BOOL *stop)
75 | {
76 | [provider.registeredTypeIdentifiers enumerateObjectsUsingBlock:^(NSString *identifier, NSUInteger idx, BOOL *stop)
77 | {
78 | [provider loadItemForTypeIdentifier:identifier options:nil completionHandler:^(id item, NSError *error)
79 | {
80 | index += 1;
81 |
82 | NSString *string;
83 | NSString *type;
84 |
85 | // is an URL - Can be a path or Web URL
86 | if ([(NSObject *)item isKindOfClass:[NSURL class]]) {
87 | NSURL *url = (NSURL *) item;
88 | string = [url absoluteString];
89 | type = ([[string pathExtension] isEqualToString:@""]) || [url.scheme containsString:@"http"] ? @"text" : @"media";
90 |
91 | [data addObject:@{ @"value": string, @"type": type }];
92 |
93 | // is a String
94 | } else if ([(NSObject *)item isKindOfClass:[NSString class]]) {
95 | string = (NSString *)item;
96 | type = @"text";
97 |
98 | [data addObject:@{ @"value": string, @"type": type }];
99 |
100 | // is an Image
101 | } else if ([(NSObject *)item isKindOfClass:[UIImage class]]) {
102 | UIImage *sharedImage = (UIImage *)item;
103 | NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"image.png"];
104 | [UIImagePNGRepresentation(sharedImage) writeToFile:path atomically:YES];
105 | string = [NSString stringWithFormat:@"%@%@", @"file://", path];
106 | type = @"media";
107 |
108 | [data addObject:@{ @"value": string, @"type": type }];
109 | }
110 |
111 | if (index == [attachments count]) {
112 | callback(data, nil);
113 | }
114 | }];
115 |
116 | // We'll only use the first provider
117 | *stop = YES;
118 | }];
119 | }];
120 | }
121 |
122 | @end
123 |
--------------------------------------------------------------------------------
/ios/ReactNativeShareExtension.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 41B5DE3E1D0B50D300949BD5 /* ReactNativeShareExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B5DE3D1D0B50D300949BD5 /* ReactNativeShareExtension.m */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXCopyFilesBuildPhase section */
14 | 41B5DE2E1D0B505800949BD5 /* CopyFiles */ = {
15 | isa = PBXCopyFilesBuildPhase;
16 | buildActionMask = 2147483647;
17 | dstPath = "include/$(PRODUCT_NAME)";
18 | dstSubfolderSpec = 16;
19 | files = (
20 | );
21 | runOnlyForDeploymentPostprocessing = 0;
22 | };
23 | /* End PBXCopyFilesBuildPhase section */
24 |
25 | /* Begin PBXFileReference section */
26 | 41B5DE301D0B505800949BD5 /* libReactNativeShareExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactNativeShareExtension.a; sourceTree = BUILT_PRODUCTS_DIR; };
27 | 41B5DE3D1D0B50D300949BD5 /* ReactNativeShareExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeShareExtension.m; sourceTree = ""; };
28 | 41B5DE3F1D0B50FA00949BD5 /* ReactNativeShareExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactNativeShareExtension.h; sourceTree = ""; };
29 | /* End PBXFileReference section */
30 |
31 | /* Begin PBXFrameworksBuildPhase section */
32 | 41B5DE2D1D0B505800949BD5 /* Frameworks */ = {
33 | isa = PBXFrameworksBuildPhase;
34 | buildActionMask = 2147483647;
35 | files = (
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | 41B5DE271D0B505800949BD5 = {
43 | isa = PBXGroup;
44 | children = (
45 | 41B5DE3C1D0B506B00949BD5 /* src */,
46 | 41B5DE311D0B505800949BD5 /* Products */,
47 | );
48 | sourceTree = "";
49 | };
50 | 41B5DE311D0B505800949BD5 /* Products */ = {
51 | isa = PBXGroup;
52 | children = (
53 | 41B5DE301D0B505800949BD5 /* libReactNativeShareExtension.a */,
54 | );
55 | name = Products;
56 | sourceTree = "";
57 | };
58 | 41B5DE3C1D0B506B00949BD5 /* src */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 41B5DE3F1D0B50FA00949BD5 /* ReactNativeShareExtension.h */,
62 | 41B5DE3D1D0B50D300949BD5 /* ReactNativeShareExtension.m */,
63 | );
64 | name = src;
65 | sourceTree = "";
66 | };
67 | /* End PBXGroup section */
68 |
69 | /* Begin PBXNativeTarget section */
70 | 41B5DE2F1D0B505800949BD5 /* ReactNativeShareExtension */ = {
71 | isa = PBXNativeTarget;
72 | buildConfigurationList = 41B5DE391D0B505800949BD5 /* Build configuration list for PBXNativeTarget "ReactNativeShareExtension" */;
73 | buildPhases = (
74 | 41B5DE2C1D0B505800949BD5 /* Sources */,
75 | 41B5DE2D1D0B505800949BD5 /* Frameworks */,
76 | 41B5DE2E1D0B505800949BD5 /* CopyFiles */,
77 | );
78 | buildRules = (
79 | );
80 | dependencies = (
81 | );
82 | name = ReactNativeShareExtension;
83 | productName = ReactNativeShareExtension;
84 | productReference = 41B5DE301D0B505800949BD5 /* libReactNativeShareExtension.a */;
85 | productType = "com.apple.product-type.library.static";
86 | };
87 | /* End PBXNativeTarget section */
88 |
89 | /* Begin PBXProject section */
90 | 41B5DE281D0B505800949BD5 /* Project object */ = {
91 | isa = PBXProject;
92 | attributes = {
93 | LastUpgradeCheck = 0730;
94 | ORGANIZATIONNAME = "Ali Najafizadeh";
95 | TargetAttributes = {
96 | 41B5DE2F1D0B505800949BD5 = {
97 | CreatedOnToolsVersion = 7.3.1;
98 | };
99 | };
100 | };
101 | buildConfigurationList = 41B5DE2B1D0B505800949BD5 /* Build configuration list for PBXProject "ReactNativeShareExtension" */;
102 | compatibilityVersion = "Xcode 3.2";
103 | developmentRegion = English;
104 | hasScannedForEncodings = 0;
105 | knownRegions = (
106 | en,
107 | );
108 | mainGroup = 41B5DE271D0B505800949BD5;
109 | productRefGroup = 41B5DE311D0B505800949BD5 /* Products */;
110 | projectDirPath = "";
111 | projectRoot = "";
112 | targets = (
113 | 41B5DE2F1D0B505800949BD5 /* ReactNativeShareExtension */,
114 | );
115 | };
116 | /* End PBXProject section */
117 |
118 | /* Begin PBXSourcesBuildPhase section */
119 | 41B5DE2C1D0B505800949BD5 /* Sources */ = {
120 | isa = PBXSourcesBuildPhase;
121 | buildActionMask = 2147483647;
122 | files = (
123 | 41B5DE3E1D0B50D300949BD5 /* ReactNativeShareExtension.m in Sources */,
124 | );
125 | runOnlyForDeploymentPostprocessing = 0;
126 | };
127 | /* End PBXSourcesBuildPhase section */
128 |
129 | /* Begin XCBuildConfiguration section */
130 | 41B5DE371D0B505800949BD5 /* Debug */ = {
131 | isa = XCBuildConfiguration;
132 | buildSettings = {
133 | ALWAYS_SEARCH_USER_PATHS = NO;
134 | CLANG_ANALYZER_NONNULL = YES;
135 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
136 | CLANG_CXX_LIBRARY = "libc++";
137 | CLANG_ENABLE_MODULES = YES;
138 | CLANG_ENABLE_OBJC_ARC = YES;
139 | CLANG_WARN_BOOL_CONVERSION = YES;
140 | CLANG_WARN_CONSTANT_CONVERSION = YES;
141 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
142 | CLANG_WARN_EMPTY_BODY = YES;
143 | CLANG_WARN_ENUM_CONVERSION = YES;
144 | CLANG_WARN_INT_CONVERSION = YES;
145 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
146 | CLANG_WARN_UNREACHABLE_CODE = YES;
147 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
148 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
149 | COPY_PHASE_STRIP = NO;
150 | DEBUG_INFORMATION_FORMAT = dwarf;
151 | ENABLE_STRICT_OBJC_MSGSEND = YES;
152 | ENABLE_TESTABILITY = YES;
153 | GCC_C_LANGUAGE_STANDARD = gnu99;
154 | GCC_DYNAMIC_NO_PIC = NO;
155 | GCC_NO_COMMON_BLOCKS = YES;
156 | GCC_OPTIMIZATION_LEVEL = 0;
157 | GCC_PREPROCESSOR_DEFINITIONS = (
158 | "DEBUG=1",
159 | "$(inherited)",
160 | );
161 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
162 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
163 | GCC_WARN_UNDECLARED_SELECTOR = YES;
164 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
165 | GCC_WARN_UNUSED_FUNCTION = YES;
166 | GCC_WARN_UNUSED_VARIABLE = YES;
167 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
168 | MTL_ENABLE_DEBUG_INFO = YES;
169 | ONLY_ACTIVE_ARCH = YES;
170 | SDKROOT = iphoneos;
171 | };
172 | name = Debug;
173 | };
174 | 41B5DE381D0B505800949BD5 /* Release */ = {
175 | isa = XCBuildConfiguration;
176 | buildSettings = {
177 | ALWAYS_SEARCH_USER_PATHS = NO;
178 | CLANG_ANALYZER_NONNULL = YES;
179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
180 | CLANG_CXX_LIBRARY = "libc++";
181 | CLANG_ENABLE_MODULES = YES;
182 | CLANG_ENABLE_OBJC_ARC = YES;
183 | CLANG_WARN_BOOL_CONVERSION = YES;
184 | CLANG_WARN_CONSTANT_CONVERSION = YES;
185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
186 | CLANG_WARN_EMPTY_BODY = YES;
187 | CLANG_WARN_ENUM_CONVERSION = YES;
188 | CLANG_WARN_INT_CONVERSION = YES;
189 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
190 | CLANG_WARN_UNREACHABLE_CODE = YES;
191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
192 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
193 | COPY_PHASE_STRIP = NO;
194 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
195 | ENABLE_NS_ASSERTIONS = NO;
196 | ENABLE_STRICT_OBJC_MSGSEND = YES;
197 | GCC_C_LANGUAGE_STANDARD = gnu99;
198 | GCC_NO_COMMON_BLOCKS = YES;
199 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
200 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
201 | GCC_WARN_UNDECLARED_SELECTOR = YES;
202 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
203 | GCC_WARN_UNUSED_FUNCTION = YES;
204 | GCC_WARN_UNUSED_VARIABLE = YES;
205 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
206 | MTL_ENABLE_DEBUG_INFO = NO;
207 | SDKROOT = iphoneos;
208 | VALIDATE_PRODUCT = YES;
209 | };
210 | name = Release;
211 | };
212 | 41B5DE3A1D0B505800949BD5 /* Debug */ = {
213 | isa = XCBuildConfiguration;
214 | buildSettings = {
215 | HEADER_SEARCH_PATHS = (
216 | "$(inhereted)",
217 | "$(SRCROOT)/../../../node_module/React/**",
218 | "$(SRCROOT)/../../../node_modules/react-native/React/**",
219 | );
220 | OTHER_LDFLAGS = (
221 | "-ObjC",
222 | "-lc++",
223 | );
224 | PRODUCT_NAME = "$(TARGET_NAME)";
225 | SKIP_INSTALL = YES;
226 | };
227 | name = Debug;
228 | };
229 | 41B5DE3B1D0B505800949BD5 /* Release */ = {
230 | isa = XCBuildConfiguration;
231 | buildSettings = {
232 | HEADER_SEARCH_PATHS = (
233 | "$(inhereted)",
234 | "$(SRCROOT)/../../../node_module/React/**",
235 | "$(SRCROOT)/../../../node_modules/react-native/React/**",
236 | );
237 | OTHER_LDFLAGS = (
238 | "-ObjC",
239 | "-lc++",
240 | );
241 | PRODUCT_NAME = "$(TARGET_NAME)";
242 | SKIP_INSTALL = YES;
243 | };
244 | name = Release;
245 | };
246 | /* End XCBuildConfiguration section */
247 |
248 | /* Begin XCConfigurationList section */
249 | 41B5DE2B1D0B505800949BD5 /* Build configuration list for PBXProject "ReactNativeShareExtension" */ = {
250 | isa = XCConfigurationList;
251 | buildConfigurations = (
252 | 41B5DE371D0B505800949BD5 /* Debug */,
253 | 41B5DE381D0B505800949BD5 /* Release */,
254 | );
255 | defaultConfigurationIsVisible = 0;
256 | defaultConfigurationName = Release;
257 | };
258 | 41B5DE391D0B505800949BD5 /* Build configuration list for PBXNativeTarget "ReactNativeShareExtension" */ = {
259 | isa = XCConfigurationList;
260 | buildConfigurations = (
261 | 41B5DE3A1D0B505800949BD5 /* Debug */,
262 | 41B5DE3B1D0B505800949BD5 /* Release */,
263 | );
264 | defaultConfigurationIsVisible = 0;
265 | };
266 | /* End XCConfigurationList section */
267 | };
268 | rootObject = 41B5DE281D0B505800949BD5 /* Project object */;
269 | }
270 |
--------------------------------------------------------------------------------
/ios/ReactNativeShareExtension.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/ReactNativeShareExtension.xcodeproj/project.xcworkspace/xcuserdata/ali.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RocketChat/rn-extensions-share/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c/ios/ReactNativeShareExtension.xcodeproj/project.xcworkspace/xcuserdata/ali.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/ios/ReactNativeShareExtension.xcodeproj/xcuserdata/ali.xcuserdatad/xcschemes/ReactNativeShareExtension.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/ios/ReactNativeShareExtension.xcodeproj/xcuserdata/ali.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ReactNativeShareExtension.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 41B5DE2F1D0B505800949BD5
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import { NativeModules } from 'react-native'
2 |
3 | export default {
4 | data: () => NativeModules.ReactNativeShareExtension.data(),
5 | close: () => NativeModules.ReactNativeShareExtension.close(),
6 | openURL: (url) => NativeModules.ReactNativeShareExtension.openURL(url)
7 | };
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rn-extensions-share",
3 | "version": "2.4.1",
4 | "description": "Share-Extension using react-native for both ios and android",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/RocketChat/rn-extensions-share"
12 | },
13 | "keywords": [
14 | "react-component",
15 | "react-native",
16 | "share-extension"
17 | ],
18 | "author": {
19 | "name": "Djorkaeff Alexandre",
20 | "email": "djorkaeffalexandre@gmail.com",
21 | "url": "http://github.com/djorkaeffalexandre"
22 | },
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/RocketChat/rn-extensions-share"
26 | },
27 | "homepage": "https://github.com/RocketChat/rn-extensions-share"
28 | }
29 |
--------------------------------------------------------------------------------
/rn-extensions-share.podspec:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4 |
5 | Pod::Spec.new do |s|
6 | s.name = package['name']
7 | s.version = package['version']
8 | s.summary = package['description']
9 | s.license = package['license']
10 |
11 | s.authors = package['author']
12 | s.homepage = package['homepage']
13 | s.platform = :ios, "8.0"
14 |
15 | s.source = { :git => "https://github.com/RocketChat/rn-extensions-share.git", :tag => "master" }
16 | s.source_files = "ios/*.{h,m}"
17 |
18 | s.dependency 'React'
19 | end
--------------------------------------------------------------------------------