diff --git a/README.md b/README.md index dd05d506..ceee7f38 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,33 @@ However, what exactly gets shared, depends on the application the user chooses t - Facebook iOS: message, image (other filetypes are not supported), link. Beware that since a Fb update in April 2015 sharing a prefilled message is no longer possible when the Fb app is installed (like Android), see #344. Alternative: use `shareViaFacebookWithPasteMessageHint`. ### Using the share sheet +Since versin 5.1.0 (for iOS and Android) it's recommended to use `shareWithOptions` as it's the most feature rich way to share stuff cross-platform. + +It will also tell you if sharing to an app completed and which app that was (if that app plays nice, that is). + +```js +// this is the complete list of currently supported params you can pass to the plugin (all optional) +var options = { + message: 'share this', // not supported on some apps (Facebook, Instagram) + subject: 'the subject', // fi. for email + files: ['', ''], // an array of filenames either locally or remotely + url: 'https://www.website.com/foo/#bar?a=b', + chooserTitle: 'Pick an app' // Android only, you can override the default share sheet title +} + +var onSuccess = function(result) { + console.log("Share completed? " + result.completed); // On Android apps mostly return false even while it's true + console.log("Shared to app: " + result.app); // On Android result.app is currently empty. On iOS it's empty when sharing is cancelled (result.completed=false) +} + +var onError = function(msg) { + console.log("Sharing failed with message: " + msg); +} + +window.plugins.socialsharing.shareWithOptions(options, onSuccess, onError); +``` + +#### You can still use the older `share` method as well Here are some examples you can copy-paste to test the various combinations: ```html diff --git a/package.json b/package.json index 0f77768d..b339f447 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-x-socialsharing", - "version": "5.0.12", + "version": "5.1.0", "description": "Share text, images (and other files), or a link via the native sharing widget of your device. Android is fully supported, as well as iOS 6 and up. WP8 has somewhat limited support.", "cordova": { "id": "cordova-plugin-x-socialsharing", diff --git a/plugin.xml b/plugin.xml index cecdba64..8924b041 100755 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="5.1.0"> SocialSharing diff --git a/src/android/nl/xservices/plugins/SocialSharing.java b/src/android/nl/xservices/plugins/SocialSharing.java index 0ffb4ac3..8b843370 100644 --- a/src/android/nl/xservices/plugins/SocialSharing.java +++ b/src/android/nl/xservices/plugins/SocialSharing.java @@ -36,6 +36,7 @@ public class SocialSharing extends CordovaPlugin { private static final String ACTION_AVAILABLE_EVENT = "available"; private static final String ACTION_SHARE_EVENT = "share"; + private static final String ACTION_SHARE_WITH_OPTIONS_EVENT = "shareWithOptions"; private static final String ACTION_CAN_SHARE_VIA = "canShareVia"; private static final String ACTION_CAN_SHARE_VIA_EMAIL = "canShareViaEmail"; private static final String ACTION_SHARE_VIA = "shareVia"; @@ -47,9 +48,10 @@ public class SocialSharing extends CordovaPlugin { private static final String ACTION_SHARE_VIA_SMS_EVENT = "shareViaSMS"; private static final String ACTION_SHARE_VIA_EMAIL_EVENT = "shareViaEmail"; - private static final int ACTIVITY_CODE_SEND = 1; - private static final int ACTIVITY_CODE_SENDVIAEMAIL = 2; - private static final int ACTIVITY_CODE_SENDVIAWHATSAPP = 3; + private static final int ACTIVITY_CODE_SEND__BOOLRESULT = 1; + private static final int ACTIVITY_CODE_SEND__OBJECT = 2; + private static final int ACTIVITY_CODE_SENDVIAEMAIL = 3; + private static final int ACTIVITY_CODE_SENDVIAWHATSAPP = 4; private CallbackContext _callbackContext; @@ -71,27 +73,29 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); return true; } else if (ACTION_SHARE_EVENT.equals(action)) { - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), null, false); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), null, null, false, true); + } else if (ACTION_SHARE_WITH_OPTIONS_EVENT.equals(action)) { + return shareWithOptions(callbackContext, args.getJSONObject(0)); } else if (ACTION_SHARE_VIA_TWITTER_EVENT.equals(action)) { - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "twitter", false); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "twitter", null, false, true); } else if (ACTION_SHARE_VIA_FACEBOOK_EVENT.equals(action)) { - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", false); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", null, false, true); } else if (ACTION_SHARE_VIA_FACEBOOK_WITH_PASTEMESSAGEHINT.equals(action)) { this.pasteMessage = args.getString(4); - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", false); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", null, false, true); } else if (ACTION_SHARE_VIA_WHATSAPP_EVENT.equals(action)) { if (notEmpty(args.getString(4))) { return shareViaWhatsAppDirectly(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4)); } else { - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "whatsapp", false); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "whatsapp", null, false, true); } } else if (ACTION_SHARE_VIA_INSTAGRAM_EVENT.equals(action)) { if (notEmpty(args.getString(0))) { copyHintToClipboard(args.getString(0), "Instagram paste message"); } - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "instagram", false); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "instagram", null, false, true); } else if (ACTION_CAN_SHARE_VIA.equals(action)) { - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), true); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), null, true, true); } else if (ACTION_CAN_SHARE_VIA_EMAIL.equals(action)) { if (isEmailAvailable()) { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); @@ -101,7 +105,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return false; } } else if (ACTION_SHARE_VIA.equals(action)) { - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), false); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), null, false, true); } else if (ACTION_SHARE_VIA_SMS_EVENT.equals(action)) { return invokeSMSIntent(callbackContext, args.getJSONObject(0), args.getString(1)); } else if (ACTION_SHARE_VIA_EMAIL_EVENT.equals(action)) { @@ -173,9 +177,9 @@ public void run() { // as an experiment for #300 we're explicitly running it on the ui thread here cordova.getActivity().runOnUiThread(new Runnable() { public void run() { - cordova.startActivityForResult(plugin, Intent.createChooser(draft, "Choose Email App"), ACTIVITY_CODE_SENDVIAEMAIL); - } - }); + cordova.startActivityForResult(plugin, Intent.createChooser(draft, "Choose Email App"), ACTIVITY_CODE_SENDVIAEMAIL); + } + }); } }); @@ -194,7 +198,30 @@ private String getDownloadDir() throws IOException { } } - private boolean doSendIntent(final CallbackContext callbackContext, final String msg, final String subject, final JSONArray files, final String url, final String appPackageName, final boolean peek) { + private boolean shareWithOptions(CallbackContext callbackContext, JSONObject jsonObject) { + return doSendIntent( + callbackContext, + jsonObject.optString("message", null), + jsonObject.optString("subject", null), + jsonObject.optJSONArray("files") == null ? new JSONArray() : jsonObject.optJSONArray("files"), + jsonObject.optString("url", null), + null, + jsonObject.optString("chooserTitle", null), + false, + false + ); + } + + private boolean doSendIntent( + final CallbackContext callbackContext, + final String msg, + final String subject, + final JSONArray files, + final String url, + final String appPackageName, + final String chooserTitle, + final boolean peek, + final boolean boolResult) { final CordovaInterface mycordova = cordova; final CordovaPlugin plugin = this; @@ -305,12 +332,12 @@ public void run() { // as an experiment for #300 we're explicitly running it on the ui thread here cordova.getActivity().runOnUiThread(new Runnable() { public void run() { - mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, null), ACTIVITY_CODE_SEND); + mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, chooserTitle), boolResult ? ACTIVITY_CODE_SEND__BOOLRESULT : ACTIVITY_CODE_SEND__OBJECT); } }); + } } } - } }); return true; } @@ -554,10 +581,26 @@ private JSONArray getShareActivities(List activityList) { public void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (_callbackContext != null) { - if (ACTIVITY_CODE_SENDVIAEMAIL == requestCode) { - _callbackContext.success(); - } else { - _callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, resultCode == Activity.RESULT_OK)); + switch (requestCode) { + case ACTIVITY_CODE_SEND__BOOLRESULT: + _callbackContext.sendPluginResult(new PluginResult( + PluginResult.Status.OK, + resultCode == Activity.RESULT_OK)); + break; + case ACTIVITY_CODE_SEND__OBJECT: + JSONObject json = new JSONObject(); + try { + json.put("completed", resultCode == Activity.RESULT_OK); + json.put("app", ""); // we need a completely different approach if we want to support this on Android. Idea: https://clickclickclack.wordpress.com/2012/01/03/intercepting-androids-action_send-intents/ + _callbackContext.sendPluginResult(new PluginResult( + PluginResult.Status.OK, + json)); + } catch (JSONException e) { + _callbackContext.error(e.getMessage()); + } + break; + default: + _callbackContext.success(); } } } diff --git a/src/ios/SocialSharing.h b/src/ios/SocialSharing.h index 399cd239..0c731450 100644 --- a/src/ios/SocialSharing.h +++ b/src/ios/SocialSharing.h @@ -11,6 +11,7 @@ - (void)available:(CDVInvokedUrlCommand*)command; - (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command; - (void)share:(CDVInvokedUrlCommand*)command; +- (void)shareWithOptions:(CDVInvokedUrlCommand*)command; - (void)canShareVia:(CDVInvokedUrlCommand*)command; - (void)canShareViaEmail:(CDVInvokedUrlCommand*)command; - (void)shareVia:(CDVInvokedUrlCommand*)command; diff --git a/src/ios/SocialSharing.m b/src/ios/SocialSharing.m index 31146a45..9042ea25 100644 --- a/src/ios/SocialSharing.m +++ b/src/ios/SocialSharing.m @@ -7,6 +7,11 @@ #import #import +static NSString *const kShareOptionMessage = @"message"; +static NSString *const kShareOptionSubject = @"subject"; +static NSString *const kShareOptionFiles = @"files"; +static NSString *const kShareOptionUrl = @"url"; + @implementation SocialSharing { UIPopoverController *_popover; NSString *_popupCoordinates; @@ -52,6 +57,26 @@ - (CGRect)getPopupRectFromIPadPopupCoordinates:(NSArray*)comps { } - (void)share:(CDVInvokedUrlCommand*)command { + [self shareInternal:command + withOptions:@{ + kShareOptionMessage: [command.arguments objectAtIndex:0], + kShareOptionSubject: [command.arguments objectAtIndex:1], + kShareOptionFiles: [command.arguments objectAtIndex:2], + kShareOptionUrl: [command.arguments objectAtIndex:3] + } + isBooleanResponse:YES +]; +} + +- (void)shareWithOptions:(CDVInvokedUrlCommand*)command { + NSDictionary* options = [command.arguments objectAtIndex:0]; + [self shareInternal:command + withOptions:options + isBooleanResponse:NO + ]; +} + +- (void)shareInternal:(CDVInvokedUrlCommand*)command withOptions:(NSDictionary*)options isBooleanResponse:(BOOL)boolResponse { [self.commandDelegate runInBackground:^{ //avoid main thread block especially if sharing big files from url if (!NSClassFromString(@"UIActivityViewController")) { CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; @@ -59,10 +84,10 @@ - (void)share:(CDVInvokedUrlCommand*)command { return; } - NSString *message = [command.arguments objectAtIndex:0]; - NSString *subject = [command.arguments objectAtIndex:1]; - NSArray *filenames = [command.arguments objectAtIndex:2]; - NSString *urlString = [command.arguments objectAtIndex:3]; + NSString *message = options[kShareOptionMessage]; + NSString *subject = options[kShareOptionSubject]; + NSArray *filenames = options[kShareOptionFiles]; + NSString *urlString = options[kShareOptionUrl]; NSMutableArray *activityItems = [[NSMutableArray alloc] init]; [activityItems addObject:message]; @@ -93,21 +118,26 @@ - (void)share:(CDVInvokedUrlCommand*)command { } if ([activityVC respondsToSelector:(@selector(setCompletionWithItemsHandler:))]) { - [activityVC setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray * __nullable returnedItems, NSError * __nullable activityError) { - [self cleanupStoredFiles]; - NSLog(@"SocialSharing app selected: %@", activityType); - CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; - }else{ + [activityVC setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray * __nullable returnedItems, NSError * __nullable activityError) { + [self cleanupStoredFiles]; + if (boolResponse) { + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed] + callbackId:command.callbackId]; + } else { + NSDictionary * result = @{@"completed":@(completed), @"app":activityType == nil ? @"" : activityType}; + [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result] + callbackId:command.callbackId]; + } + }]; + } else { // let's suppress this warning otherwise folks will start opening issues while it's not relevant #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - [activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) { - [self cleanupStoredFiles]; - NSLog(@"SocialSharing app selected: %@", activityType); - CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - }]; + [activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) { + [self cleanupStoredFiles]; + NSDictionary * result = @{@"completed":@(completed), @"app":activityType}; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; #pragma GCC diagnostic warning "-Wdeprecated-declarations" } diff --git a/www/SocialSharing.js b/www/SocialSharing.js index f056c9fd..823bca78 100644 --- a/www/SocialSharing.js +++ b/www/SocialSharing.js @@ -25,6 +25,11 @@ SocialSharing.prototype.available = function (callback) { }, null, "SocialSharing", "available", []); }; +// this is the recommended way to share as it is the most feature-rich with respect to what you pass in and get back +SocialSharing.prototype.shareWithOptions = function (options, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareWithOptions"), "SocialSharing", "shareWithOptions", [options]); +}; + SocialSharing.prototype.share = function (message, subject, fileOrFileArray, url, successCallback, errorCallback) { cordova.exec(successCallback, this._getErrorCallback(errorCallback, "share"), "SocialSharing", "share", [message, subject, this._asArray(fileOrFileArray), url]); };