1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Android: Fixes #7791: Sharing pictures to Joplin creates recurring duplications (#7807)

This commit is contained in:
javad mnjd 2023-02-19 21:53:00 +03:30 committed by GitHub
parent 21dbc800d5
commit 18a0ca0881
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 84 additions and 57 deletions

View File

@ -77,18 +77,8 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity>
<!-- SHARE EXTENSION --> <!-- SHARE EXTENSION -->
<activity
android:noHistory="true"
android:name=".ShareActivity"
android:configChanges="orientation"
android:launchMode="singleTask"
android:label="@string/app_name"
android:excludeFromRecents="true"
android:theme="@style/AppTheme"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@ -99,8 +89,8 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
</intent-filter> </intent-filter>
<!-- /SHARE EXTENSION -->
</activity> </activity>
<!-- /SHARE EXTENSION -->
</application> </application>

View File

@ -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";
}
}

View File

@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext; 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.ReactMethod;
import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.ViewManager; import com.facebook.react.uimanager.ViewManager;
import java.io.File; import java.io.File;
@ -42,11 +44,14 @@ public class SharePackage implements ReactPackage {
return Collections.emptyList(); 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) { ShareModule(@NonNull ReactApplicationContext reactContext) {
super(reactContext); super(reactContext);
reactContext.addActivityEventListener(this); reactContext.addActivityEventListener(this);
reactContext.addLifecycleEventListener(this);
} }
@Override @Override
@ -55,6 +60,14 @@ public class SharePackage implements ReactPackage {
@Override @Override
public void onNewIntent(Intent intent) { 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 @NonNull
@ -77,37 +90,29 @@ public class SharePackage implements ReactPackage {
} }
private WritableMap processIntent() { private WritableMap processIntent() {
Activity currentActivity = getCurrentActivity();
WritableMap map = Arguments.createMap(); WritableMap map = Arguments.createMap();
if (currentActivity == null) { if (receivedShareIntent == null) {
return null; return null;
} }
Intent intent = currentActivity.getIntent(); if (receivedShareIntent.getBooleanExtra("is_processed", false)) {
if (intent == null || !(Intent.ACTION_SEND.equals(intent.getAction())
|| Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction()))) {
return null; return null;
} }
if (intent.getBooleanExtra("is_processed", false)) { String type = receivedShareIntent.getType() == null ? "" : receivedShareIntent.getType();
return null;
}
String type = intent.getType() == null ? "" : intent.getType();
map.putString("type", type); map.putString("type", type);
map.putString("title", getTitle(intent)); map.putString("title", getTitle(receivedShareIntent));
map.putString("text", intent.getStringExtra(Intent.EXTRA_TEXT)); map.putString("text", receivedShareIntent.getStringExtra(Intent.EXTRA_TEXT));
WritableArray resources = Arguments.createArray(); WritableArray resources = Arguments.createArray();
if (Intent.ACTION_SEND.equals(intent.getAction())) { if (Intent.ACTION_SEND.equals(receivedShareIntent.getAction())) {
if (intent.hasExtra(Intent.EXTRA_STREAM)) { if (receivedShareIntent.hasExtra(Intent.EXTRA_STREAM)) {
resources.pushMap(getFileData(intent.getParcelableExtra(Intent.EXTRA_STREAM))); resources.pushMap(getFileData(receivedShareIntent.getParcelableExtra(Intent.EXTRA_STREAM)));
} }
} else if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) { } else if (Intent.ACTION_SEND_MULTIPLE.equals(receivedShareIntent.getAction())) {
ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); ArrayList<Uri> imageUris = receivedShareIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (imageUris != null) { if (imageUris != null) {
for (Uri uri : imageUris) { for (Uri uri : imageUris) {
resources.pushMap(getFileData(uri)); resources.pushMap(getFileData(uri));
@ -116,7 +121,7 @@ public class SharePackage implements ReactPackage {
} }
map.putArray("resources", resources); map.putArray("resources", resources);
intent.putExtra("is_processed", true); receivedShareIntent.putExtra("is_processed", true);
return map; return map;
} }
@ -185,5 +190,36 @@ public class SharePackage implements ReactPackage {
} }
return ext; 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() {
}
} }
} }

View File

@ -139,15 +139,7 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
if (this.state.fromShare) { if (this.state.fromShare) {
// effectively the same as NAV_BACK but NAV_BACK causes undesired behaviour in this case: ShareExtension.close();
// - 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,
});
return true; return true;
} }
@ -458,10 +450,6 @@ class NoteScreenComponent extends BaseScreenComponent {
shared.uninstallResourceHandling(this.refreshResource); shared.uninstallResourceHandling(this.refreshResource);
if (this.state.fromShare) {
ShareExtension.close();
}
this.saveActionQueue(this.state.note.id).processAllNow(); this.saveActionQueue(this.state.note.id).processAllNow();
// It cannot theoretically be undefined, since componentDidMount should always be called before // It cannot theoretically be undefined, since componentDidMount should always be called before

View File

@ -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); this.handleScreenWidthChange_ = this.handleScreenWidthChange_.bind(this);
} }
@ -840,6 +849,11 @@ class AppComponent extends React.Component {
} }
if (this.unsubscribeNetInfoHandler_) this.unsubscribeNetInfoHandler_(); if (this.unsubscribeNetInfoHandler_) this.unsubscribeNetInfoHandler_();
if (this.unsubscribeNewShareListener_) {
this.unsubscribeNewShareListener_();
this.unsubscribeNewShareListener_ = undefined;
}
} }
public componentDidUpdate(prevProps: any) { public componentDidUpdate(prevProps: any) {

View File

@ -1,3 +1,5 @@
import { NativeEventEmitter } from 'react-native';
const { NativeModules, Platform } = require('react-native'); const { NativeModules, Platform } = require('react-native');
export interface SharedData { export interface SharedData {
@ -6,16 +8,25 @@ export interface SharedData {
resources?: string[]; resources?: string[];
} }
let eventEmitter: NativeEventEmitter | undefined;
const ShareExtension = (NativeModules.ShareExtension) ? const ShareExtension = (NativeModules.ShareExtension) ?
{ {
data: () => NativeModules.ShareExtension.data(), data: () => NativeModules.ShareExtension.data(),
close: () => NativeModules.ShareExtension.close(), close: () => NativeModules.ShareExtension.close(),
shareURL: (Platform.OS === 'ios') ? NativeModules.ShareExtension.getConstants().SHARE_EXTENSION_SHARE_URL : '', 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: () => {}, data: () => {},
close: () => {}, close: () => {},
shareURL: '', shareURL: '',
addShareListener: () => {},
}; };
export default ShareExtension; export default ShareExtension;