diff --git a/packages/app-mobile/android/app/src/main/AndroidManifest.xml b/packages/app-mobile/android/app/src/main/AndroidManifest.xml
index 453811060..27e2d9506 100644
--- a/packages/app-mobile/android/app/src/main/AndroidManifest.xml
+++ b/packages/app-mobile/android/app/src/main/AndroidManifest.xml
@@ -77,18 +77,8 @@
-
-
-
+
@@ -99,8 +89,8 @@
+
-
diff --git a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/ShareActivity.java b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/ShareActivity.java
deleted file mode 100644
index 492f1dda5..000000000
--- a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/ShareActivity.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package net.cozic.joplin;
-
-import com.facebook.react.ReactActivity;
-
-public class ShareActivity extends ReactActivity {
- @Override
- protected String getMainComponentName() {
- // this is the name AppRegistry will use to launch the Share View
- return "Joplin";
- }
-}
-
diff --git a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/share/SharePackage.java b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/share/SharePackage.java
index f85aa2604..981fd37b2 100644
--- a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/share/SharePackage.java
+++ b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/share/SharePackage.java
@@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
@@ -21,6 +22,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.ViewManager;
import java.io.File;
@@ -42,11 +44,14 @@ public class SharePackage implements ReactPackage {
return Collections.emptyList();
}
- public static class ShareModule extends ReactContextBaseJavaModule implements ActivityEventListener {
+ public static class ShareModule extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener {
+ private boolean handledStartIntent = false;
+ private Intent receivedShareIntent = null;
ShareModule(@NonNull ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addActivityEventListener(this);
+ reactContext.addLifecycleEventListener(this);
}
@Override
@@ -55,6 +60,14 @@ public class SharePackage implements ReactPackage {
@Override
public void onNewIntent(Intent intent) {
+ if (intent == null || !(Intent.ACTION_SEND.equals(intent.getAction())
+ || Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction()))) {
+ return;
+ }
+ receivedShareIntent = intent;
+ this.getReactApplicationContext()
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
+ .emit("new_share_intent", null);
}
@NonNull
@@ -77,37 +90,29 @@ public class SharePackage implements ReactPackage {
}
private WritableMap processIntent() {
- Activity currentActivity = getCurrentActivity();
WritableMap map = Arguments.createMap();
- if (currentActivity == null) {
+ if (receivedShareIntent == null) {
return null;
}
- Intent intent = currentActivity.getIntent();
-
- if (intent == null || !(Intent.ACTION_SEND.equals(intent.getAction())
- || Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction()))) {
+ if (receivedShareIntent.getBooleanExtra("is_processed", false)) {
return null;
}
- if (intent.getBooleanExtra("is_processed", false)) {
- return null;
- }
-
- String type = intent.getType() == null ? "" : intent.getType();
+ String type = receivedShareIntent.getType() == null ? "" : receivedShareIntent.getType();
map.putString("type", type);
- map.putString("title", getTitle(intent));
- map.putString("text", intent.getStringExtra(Intent.EXTRA_TEXT));
+ map.putString("title", getTitle(receivedShareIntent));
+ map.putString("text", receivedShareIntent.getStringExtra(Intent.EXTRA_TEXT));
WritableArray resources = Arguments.createArray();
- if (Intent.ACTION_SEND.equals(intent.getAction())) {
- if (intent.hasExtra(Intent.EXTRA_STREAM)) {
- resources.pushMap(getFileData(intent.getParcelableExtra(Intent.EXTRA_STREAM)));
+ if (Intent.ACTION_SEND.equals(receivedShareIntent.getAction())) {
+ if (receivedShareIntent.hasExtra(Intent.EXTRA_STREAM)) {
+ resources.pushMap(getFileData(receivedShareIntent.getParcelableExtra(Intent.EXTRA_STREAM)));
}
- } else if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
- ArrayList imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ } else if (Intent.ACTION_SEND_MULTIPLE.equals(receivedShareIntent.getAction())) {
+ ArrayList imageUris = receivedShareIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (imageUris != null) {
for (Uri uri : imageUris) {
resources.pushMap(getFileData(uri));
@@ -116,7 +121,7 @@ public class SharePackage implements ReactPackage {
}
map.putArray("resources", resources);
- intent.putExtra("is_processed", true);
+ receivedShareIntent.putExtra("is_processed", true);
return map;
}
@@ -185,5 +190,36 @@ public class SharePackage implements ReactPackage {
}
return ext;
}
+
+ @ReactMethod
+ public void addListener(String eventName) {
+ // Set up any upstream listeners or background tasks as necessary
+ }
+
+ @ReactMethod
+ public void removeListeners(Integer count) {
+ // Remove upstream listeners, stop unnecessary background tasks
+ }
+
+ @Override
+ public void onHostResume() {
+ if (this.getCurrentActivity() != null) {
+ Intent intent = this.getCurrentActivity().getIntent();
+ if (this.handledStartIntent) {
+ // sometimes onHostResume is fired after onNewIntent
+ // and we only care about the activity intent when the first time app opens
+ return;
+ }
+ this.handledStartIntent = true;
+ this.onNewIntent(intent);
+ }
+ }
+
+ @Override
+ public void onHostPause() {}
+
+ @Override
+ public void onHostDestroy() {
+ }
}
}
diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx
index 42363688a..e41492773 100644
--- a/packages/app-mobile/components/screens/Note.tsx
+++ b/packages/app-mobile/components/screens/Note.tsx
@@ -139,15 +139,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}
if (this.state.fromShare) {
- // effectively the same as NAV_BACK but NAV_BACK causes undesired behaviour in this case:
- // - share to Joplin from some other app
- // - open Joplin and open any note
- // - go back -- with NAV_BACK this causes the app to exit rather than just showing notes
- this.props.dispatch({
- type: 'NAV_GO',
- routeName: 'Notes',
- folderId: this.state.note.parent_id,
- });
+ ShareExtension.close();
return true;
}
@@ -458,10 +450,6 @@ class NoteScreenComponent extends BaseScreenComponent {
shared.uninstallResourceHandling(this.refreshResource);
- if (this.state.fromShare) {
- ShareExtension.close();
- }
-
this.saveActionQueue(this.state.note.id).processAllNow();
// It cannot theoretically be undefined, since componentDidMount should always be called before
diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx
index 6bbd2d978..349205d65 100644
--- a/packages/app-mobile/root.tsx
+++ b/packages/app-mobile/root.tsx
@@ -735,6 +735,15 @@ class AppComponent extends React.Component {
}
};
+ this.handleNewShare_ = () => {
+ // look at this.handleOpenURL_ comment
+ if (this.props.biometricsDone) {
+ void this.handleShareData();
+ }
+ };
+
+ this.unsubscribeNewShareListener_ = ShareExtension.addShareListener(this.handleNewShare_);
+
this.handleScreenWidthChange_ = this.handleScreenWidthChange_.bind(this);
}
@@ -840,6 +849,11 @@ class AppComponent extends React.Component {
}
if (this.unsubscribeNetInfoHandler_) this.unsubscribeNetInfoHandler_();
+
+ if (this.unsubscribeNewShareListener_) {
+ this.unsubscribeNewShareListener_();
+ this.unsubscribeNewShareListener_ = undefined;
+ }
}
public componentDidUpdate(prevProps: any) {
diff --git a/packages/app-mobile/utils/ShareExtension.ts b/packages/app-mobile/utils/ShareExtension.ts
index 7f457400c..3d26d5661 100644
--- a/packages/app-mobile/utils/ShareExtension.ts
+++ b/packages/app-mobile/utils/ShareExtension.ts
@@ -1,3 +1,5 @@
+import { NativeEventEmitter } from 'react-native';
+
const { NativeModules, Platform } = require('react-native');
export interface SharedData {
@@ -6,16 +8,25 @@ export interface SharedData {
resources?: string[];
}
+let eventEmitter: NativeEventEmitter | undefined;
+
const ShareExtension = (NativeModules.ShareExtension) ?
{
data: () => NativeModules.ShareExtension.data(),
close: () => NativeModules.ShareExtension.close(),
shareURL: (Platform.OS === 'ios') ? NativeModules.ShareExtension.getConstants().SHARE_EXTENSION_SHARE_URL : '',
+ addShareListener: (Platform.OS === 'android') ? ((handler: (event: any)=> void) => {
+ if (!eventEmitter) {
+ eventEmitter = new NativeEventEmitter(NativeModules.ShareExtension);
+ }
+ return eventEmitter.addListener('new_share_intent', handler).remove;
+ }) : (() => {}),
} :
{
data: () => {},
close: () => {},
shareURL: '',
+ addShareListener: () => {},
};
export default ShareExtension;