socialsharing de events començat

This commit is contained in:
arnaucode
2017-02-18 16:54:28 +01:00
parent b5f4ee6bc3
commit 65f69551d0
32 changed files with 3298 additions and 78 deletions

View File

@@ -0,0 +1,5 @@
package nl.xservices.plugins;
public class FileProvider extends android.support.v4.content.FileProvider {
}

View File

@@ -0,0 +1,795 @@
package nl.xservices.plugins;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.*;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.text.Html;
import android.util.Base64;
import android.view.Gravity;
import android.widget.Toast;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.support.v4.content.FileProvider;
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";
private static final String ACTION_SHARE_VIA_TWITTER_EVENT = "shareViaTwitter";
private static final String ACTION_SHARE_VIA_FACEBOOK_EVENT = "shareViaFacebook";
private static final String ACTION_SHARE_VIA_FACEBOOK_WITH_PASTEMESSAGEHINT = "shareViaFacebookWithPasteMessageHint";
private static final String ACTION_SHARE_VIA_WHATSAPP_EVENT = "shareViaWhatsApp";
private static final String ACTION_SHARE_VIA_INSTAGRAM_EVENT = "shareViaInstagram";
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__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;
private String pasteMessage;
private abstract class SocialSharingRunnable implements Runnable {
public CallbackContext callbackContext;
SocialSharingRunnable(CallbackContext cb) {
this.callbackContext = cb;
}
}
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
this._callbackContext = callbackContext; // only used for onActivityResult
this.pasteMessage = null;
if (ACTION_AVAILABLE_EVENT.equals(action)) {
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, 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", 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", null, false, true, "com.facebook.composer.shareintent");
} 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", null, false, true, "com.facebook.composer.shareintent");
} 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", 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", 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), null, true, true);
} else if (ACTION_CAN_SHARE_VIA_EMAIL.equals(action)) {
if (isEmailAvailable()) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
return true;
} else {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "not available"));
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), 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)) {
return invokeEmailIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.isNull(3) ? null : args.getJSONArray(3), args.isNull(4) ? null : args.getJSONArray(4), args.isNull(5) ? null : args.getJSONArray(5));
} else {
callbackContext.error("socialSharing." + action + " is not a supported function. Did you mean '" + ACTION_SHARE_EVENT + "'?");
return false;
}
}
private boolean isEmailAvailable() {
final Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "someone@domain.com", null));
return cordova.getActivity().getPackageManager().queryIntentActivities(intent, 0).size() > 0;
}
private boolean invokeEmailIntent(final CallbackContext callbackContext, final String message, final String subject, final JSONArray to, final JSONArray cc, final JSONArray bcc, final JSONArray files) throws JSONException {
final SocialSharing plugin = this;
cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) {
public void run() {
final Intent draft = new Intent(Intent.ACTION_SEND_MULTIPLE);
if (notEmpty(message)) {
Pattern htmlPattern = Pattern.compile(".*\\<[^>]+>.*", Pattern.DOTALL);
if (htmlPattern.matcher(message).matches()) {
draft.putExtra(android.content.Intent.EXTRA_TEXT, Html.fromHtml(message));
draft.setType("text/html");
} else {
draft.putExtra(android.content.Intent.EXTRA_TEXT, message);
draft.setType("text/plain");
}
}
if (notEmpty(subject)) {
draft.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
}
try {
if (to != null && to.length() > 0) {
draft.putExtra(android.content.Intent.EXTRA_EMAIL, toStringArray(to));
}
if (cc != null && cc.length() > 0) {
draft.putExtra(android.content.Intent.EXTRA_CC, toStringArray(cc));
}
if (bcc != null && bcc.length() > 0) {
draft.putExtra(android.content.Intent.EXTRA_BCC, toStringArray(bcc));
}
if (files.length() > 0) {
final String dir = getDownloadDir();
if (dir != null) {
ArrayList<Uri> fileUris = new ArrayList<Uri>();
for (int i = 0; i < files.length(); i++) {
final Uri fileUri = getFileUriAndSetType(draft, dir, files.getString(i), subject, i);
if (fileUri != null) {
fileUris.add(fileUri);
}
}
if (!fileUris.isEmpty()) {
draft.putExtra(Intent.EXTRA_STREAM, fileUris);
}
}
}
} catch (Exception e) {
callbackContext.error(e.getMessage());
return;
}
// this was added to start the intent in a new window as suggested in #300 to prevent crashes upon return
draft.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
draft.setType("application/octet-stream");
// 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);
}
});
}
});
return true;
}
private String getDownloadDir() throws IOException {
// better check, otherwise it may crash the app
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
// we need to use external storage since we need to share to another app
final String dir = webView.getContext().getExternalFilesDir(null) + "/socialsharing-downloads";
createOrCleanDir(dir);
return dir;
} else {
return null;
}
}
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) {
return doSendIntent(callbackContext, msg, subject, files, url, appPackageName, chooserTitle, peek, boolResult, null);
}
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 String appName) {
final CordovaInterface mycordova = cordova;
final CordovaPlugin plugin = this;
cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) {
public void run() {
String message = msg;
final boolean hasMultipleAttachments = files.length() > 1;
final Intent sendIntent = new Intent(hasMultipleAttachments ? Intent.ACTION_SEND_MULTIPLE : Intent.ACTION_SEND);
sendIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
try {
if (files.length() > 0 && !"".equals(files.getString(0))) {
final String dir = getDownloadDir();
if (dir != null) {
ArrayList<Uri> fileUris = new ArrayList<Uri>();
Uri fileUri = null;
for (int i = 0; i < files.length(); i++) {
fileUri = getFileUriAndSetType(sendIntent, dir, files.getString(i), subject, i);
fileUri = FileProvider.getUriForFile(webView.getContext(), cordova.getActivity().getPackageName()+".sharing.provider", new File(fileUri.getPath()));
if (fileUri != null) {
fileUris.add(fileUri);
}
}
if (!fileUris.isEmpty()) {
if (hasMultipleAttachments) {
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUris);
} else {
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
}
}
} else {
sendIntent.setType("text/plain");
}
} else {
sendIntent.setType("text/plain");
}
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
if (notEmpty(subject)) {
sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
}
// add the URL to the message, as there seems to be no separate field
if (notEmpty(url)) {
if (notEmpty(message)) {
message += " " + url;
} else {
message = url;
}
}
if (notEmpty(message)) {
sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, message);
// sometimes required when the user picks share via sms
if (Build.VERSION.SDK_INT < 21) { // LOLLIPOP
sendIntent.putExtra("sms_body", message);
}
}
// this was added to start the intent in a new window as suggested in #300 to prevent crashes upon return
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (appPackageName != null) {
String packageName = appPackageName;
String passedActivityName = null;
if (packageName.contains("/")) {
String[] items = appPackageName.split("/");
packageName = items[0];
passedActivityName = items[1];
}
final ActivityInfo activity = getActivity(callbackContext, sendIntent, packageName, appName);
if (activity != null) {
if (peek) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
} else {
sendIntent.addCategory(Intent.CATEGORY_LAUNCHER);
sendIntent.setComponent(new ComponentName(activity.applicationInfo.packageName,
passedActivityName != null ? passedActivityName : activity.name));
// 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, sendIntent, 0);
}
});
if (pasteMessage != null) {
// add a little delay because target app (facebook only atm) needs to be started first
new Timer().schedule(new TimerTask() {
public void run() {
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
copyHintToClipboard(msg, pasteMessage);
showPasteMessage(pasteMessage);
}
});
}
}, 2000);
}
}
}
} else {
if (peek) {
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
} else {
// experimenting a bit
// 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, chooserTitle), boolResult ? ACTIVITY_CODE_SEND__BOOLRESULT : ACTIVITY_CODE_SEND__OBJECT);
}
});
}
}
}
});
return true;
}
@SuppressLint("NewApi")
private void copyHintToClipboard(String msg, String label) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return;
}
final ClipboardManager clipboard = (android.content.ClipboardManager) cordova.getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
final ClipData clip = android.content.ClipData.newPlainText(label, msg);
clipboard.setPrimaryClip(clip);
}
@SuppressLint("NewApi")
private void showPasteMessage(String label) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return;
}
final Toast toast = Toast.makeText(webView.getContext(), label, Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 0, 0);
toast.show();
}
private Uri getFileUriAndSetType(Intent sendIntent, String dir, String image, String subject, int nthFile) throws IOException {
// we're assuming an image, but this can be any filetype you like
String localImage = image;
if (image.endsWith("mp4") || image.endsWith("mov") || image.endsWith("3gp")){
sendIntent.setType("video/*");
} else {
sendIntent.setType("image/*");
}
if (image.startsWith("http") || image.startsWith("www/")) {
String filename = getFileName(image);
localImage = "file://" + dir + "/" + filename;
if (image.startsWith("http")) {
// filename optimisation taken from https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/pull/56
URLConnection connection = new URL(image).openConnection();
String disposition = connection.getHeaderField("Content-Disposition");
if (disposition != null) {
final Pattern dispositionPattern = Pattern.compile("filename=([^;]+)");
Matcher matcher = dispositionPattern.matcher(disposition);
if (matcher.find()) {
filename = matcher.group(1).replaceAll("[^a-zA-Z0-9._-]", "");
if (filename.length() == 0) {
// in this case we can't determine a filetype so some targets (gmail) may not render it correctly
filename = "file";
}
localImage = "file://" + dir + "/" + filename;
}
}
saveFile(getBytes(connection.getInputStream()), dir, filename);
} else {
saveFile(getBytes(webView.getContext().getAssets().open(image)), dir, filename);
}
} else if (image.startsWith("data:")) {
// safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43
if (!image.contains(";base64,")) {
sendIntent.setType("text/plain");
return null;
}
// image looks like this: data:image/png;base64,R0lGODlhDAA...
final String encodedImg = image.substring(image.indexOf(";base64,") + 8);
// correct the intent type if anything else was passed, like a pdf: data:application/pdf;base64,..
if (!image.contains("data:image/")) {
sendIntent.setType(image.substring(image.indexOf("data:") + 5, image.indexOf(";base64")));
}
// the filename needs a valid extension, so it renders correctly in target apps
final String imgExtension = image.substring(image.indexOf("/") + 1, image.indexOf(";base64"));
String fileName;
// if a subject was passed, use it as the filename
// filenames must be unique when passing in multiple files [#158]
if (notEmpty(subject)) {
fileName = sanitizeFilename(subject) + (nthFile == 0 ? "" : "_" + nthFile) + "." + imgExtension;
} else {
fileName = "file" + (nthFile == 0 ? "" : "_" + nthFile) + "." + imgExtension;
}
saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, fileName);
localImage = "file://" + dir + "/" + fileName;
} else if (image.startsWith("df:")) {
// safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43
if (!image.contains(";base64,")) {
sendIntent.setType("text/plain");
return null;
}
// format looks like this : df:filename.txt;data:image/png;base64,R0lGODlhDAA...
final String fileName = image.substring(image.indexOf("df:") + 3, image.indexOf(";data:"));
final String fileType = image.substring(image.indexOf(";data:") + 6, image.indexOf(";base64,"));
final String encodedImg = image.substring(image.indexOf(";base64,") + 8);
sendIntent.setType(fileType);
saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, sanitizeFilename(fileName));
localImage = "file://" + dir + "/" + sanitizeFilename(fileName);
} else if (!image.startsWith("file://")) {
throw new IllegalArgumentException("URL_NOT_SUPPORTED");
} else {
//get file MIME type
String type = getMIMEType(image);
//set intent data and Type
sendIntent.setType(type);
}
return Uri.parse(localImage);
}
private String getMIMEType(String fileName) {
String type = "*/*";
int dotIndex = fileName.lastIndexOf(".");
if (dotIndex == -1) {
return type;
}
final String end = fileName.substring(dotIndex+1, fileName.length()).toLowerCase();
String fromMap = MIME_Map.get(end);
return fromMap == null ? type : fromMap;
}
private static final Map<String, String> MIME_Map = new HashMap<String, String>();
static {
MIME_Map.put("3gp", "video/3gpp");
MIME_Map.put("apk", "application/vnd.android.package-archive");
MIME_Map.put("asf", "video/x-ms-asf");
MIME_Map.put("avi", "video/x-msvideo");
MIME_Map.put("bin", "application/octet-stream");
MIME_Map.put("bmp", "image/bmp");
MIME_Map.put("c", "text/plain");
MIME_Map.put("class", "application/octet-stream");
MIME_Map.put("conf", "text/plain");
MIME_Map.put("cpp", "text/plain");
MIME_Map.put("doc", "application/msword");
MIME_Map.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
MIME_Map.put("xls", "application/vnd.ms-excel");
MIME_Map.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
MIME_Map.put("exe", "application/octet-stream");
MIME_Map.put("gif", "image/gif");
MIME_Map.put("gtar", "application/x-gtar");
MIME_Map.put("gz", "application/x-gzip");
MIME_Map.put("h", "text/plain");
MIME_Map.put("htm", "text/html");
MIME_Map.put("html", "text/html");
MIME_Map.put("jar", "application/java-archive");
MIME_Map.put("java", "text/plain");
MIME_Map.put("jpeg", "image/jpeg");
MIME_Map.put("jpg", "image/*");
MIME_Map.put("js", "application/x-javascript");
MIME_Map.put("log", "text/plain");
MIME_Map.put("m3u", "audio/x-mpegurl");
MIME_Map.put("m4a", "audio/mp4a-latm");
MIME_Map.put("m4b", "audio/mp4a-latm");
MIME_Map.put("m4p", "audio/mp4a-latm");
MIME_Map.put("m4u", "video/vnd.mpegurl");
MIME_Map.put("m4v", "video/x-m4v");
MIME_Map.put("mov", "video/quicktime");
MIME_Map.put("mp2", "audio/x-mpeg");
MIME_Map.put("mp3", "audio/x-mpeg");
MIME_Map.put("mp4", "video/mp4");
MIME_Map.put("mpc", "application/vnd.mpohun.certificate");
MIME_Map.put("mpe", "video/mpeg");
MIME_Map.put("mpeg", "video/mpeg");
MIME_Map.put("mpg", "video/mpeg");
MIME_Map.put("mpg4", "video/mp4");
MIME_Map.put("mpga", "audio/mpeg");
MIME_Map.put("msg", "application/vnd.ms-outlook");
MIME_Map.put("ogg", "audio/ogg");
MIME_Map.put("pdf", "application/pdf");
MIME_Map.put("png", "image/png");
MIME_Map.put("pps", "application/vnd.ms-powerpoint");
MIME_Map.put("ppt", "application/vnd.ms-powerpoint");
MIME_Map.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
MIME_Map.put("prop", "text/plain");
MIME_Map.put("rc", "text/plain");
MIME_Map.put("rmvb", "audio/x-pn-realaudio");
MIME_Map.put("rtf", "application/rtf");
MIME_Map.put("sh", "text/plain");
MIME_Map.put("tar", "application/x-tar");
MIME_Map.put("tgz", "application/x-compressed");
MIME_Map.put("txt", "text/plain");
MIME_Map.put("wav", "audio/x-wav");
MIME_Map.put("wma", "audio/x-ms-wma");
MIME_Map.put("wmv", "audio/x-ms-wmv");
MIME_Map.put("wps", "application/vnd.ms-works");
MIME_Map.put("xml", "text/plain");
MIME_Map.put("z", "application/x-compress");
MIME_Map.put("zip", "application/x-zip-compressed");
MIME_Map.put("", "*/*");
}
private boolean shareViaWhatsAppDirectly(final CallbackContext callbackContext, String message, final String subject, final JSONArray files, final String url, final String number) {
// add the URL to the message, as there seems to be no separate field
if (notEmpty(url)) {
if (notEmpty(message)) {
message += " " + url;
} else {
message = url;
}
}
final String shareMessage = message;
final SocialSharing plugin = this;
cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) {
public void run() {
final Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("smsto:" + number));
intent.putExtra("sms_body", shareMessage);
intent.putExtra("sms_subject", subject);
intent.setPackage("com.whatsapp");
try {
if (files.length() > 0 && !"".equals(files.getString(0))) {
final boolean hasMultipleAttachments = files.length() > 1;
final String dir = getDownloadDir();
if (dir != null) {
ArrayList<Uri> fileUris = new ArrayList<Uri>();
Uri fileUri = null;
for (int i = 0; i < files.length(); i++) {
fileUri = getFileUriAndSetType(intent, dir, files.getString(i), subject, i);
if (fileUri != null) {
fileUris.add(fileUri);
}
}
if (!fileUris.isEmpty()) {
if (hasMultipleAttachments) {
intent.putExtra(Intent.EXTRA_STREAM, fileUris);
} else {
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
}
}
}
}
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
try {
// this was added to start the intent in a new window as suggested in #300 to prevent crashes upon return
// update: didn't help (doesn't seem to hurt either though)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 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, ACTIVITY_CODE_SENDVIAWHATSAPP);
}
});
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
return true;
}
private boolean invokeSMSIntent(final CallbackContext callbackContext, JSONObject options, String p_phonenumbers) {
final String message = options.optString("message");
// TODO test this on a real SMS enabled device before releasing it
// final String subject = options.optString("subject");
// final String image = options.optString("image");
final String subject = null; //options.optString("subject");
final String image = null; // options.optString("image");
final String phonenumbers = getPhoneNumbersWithManufacturerSpecificSeparators(p_phonenumbers);
final SocialSharing plugin = this;
cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) {
public void run() {
Intent intent;
if (Build.VERSION.SDK_INT >= 19) { // Build.VERSION_CODES.KITKAT) {
// passing in no phonenumbers for kitkat may result in an error,
// but it may also work for some devices, so documentation will need to cover this case
intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("smsto:" + (notEmpty(phonenumbers) ? phonenumbers : "")));
} else {
intent = new Intent(Intent.ACTION_VIEW);
intent.setType("vnd.android-dir/mms-sms");
if (phonenumbers != null) {
intent.putExtra("address", phonenumbers);
}
}
intent.putExtra("sms_body", message);
intent.putExtra("sms_subject", subject);
try {
if (image != null && !"".equals(image)) {
final Uri fileUri = getFileUriAndSetType(intent, getDownloadDir(), image, subject, 0);
if (fileUri != null) {
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
}
}
// this was added to start the intent in a new window as suggested in #300 to prevent crashes upon return
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
cordova.startActivityForResult(plugin, intent, 0);
} catch (Exception e) {
callbackContext.error(e.getMessage());
}
}
});
return true;
}
private static String getPhoneNumbersWithManufacturerSpecificSeparators(String phonenumbers) {
if (notEmpty(phonenumbers)) {
char separator;
if (android.os.Build.MANUFACTURER.equalsIgnoreCase("samsung")) {
separator = ',';
} else {
separator = ';';
}
return phonenumbers.replace(';', separator).replace(',', separator);
}
return null;
}
private ActivityInfo getActivity(final CallbackContext callbackContext, final Intent shareIntent, final String appPackageName, final String appName) {
final PackageManager pm = webView.getContext().getPackageManager();
List<ResolveInfo> activityList = pm.queryIntentActivities(shareIntent, 0);
for (final ResolveInfo app : activityList) {
if ((app.activityInfo.packageName).contains(appPackageName)) {
if (appName == null || (app.activityInfo.name).contains(appName)) {
return app.activityInfo;
}
}
}
// no matching app found
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, getShareActivities(activityList)));
return null;
}
private JSONArray getShareActivities(List<ResolveInfo> activityList) {
List<String> packages = new ArrayList<String>();
for (final ResolveInfo app : activityList) {
packages.add(app.activityInfo.packageName);
}
return new JSONArray(packages);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (_callbackContext != null) {
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();
}
}
}
private void createOrCleanDir(final String downloadDir) throws IOException {
final File dir = new File(downloadDir);
if (!dir.exists()) {
if (!dir.mkdirs()) {
throw new IOException("CREATE_DIRS_FAILED");
}
} else {
cleanupOldFiles(dir);
}
}
private static String getFileName(String url) {
if (url.endsWith("/")) {
url = url.substring(0, url.length()-1);
}
final String pattern = ".*/([^?#]+)?";
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(url);
if (m.find()) {
return m.group(1);
} else {
return "file";
}
}
private byte[] getBytes(InputStream is) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
}
private void saveFile(byte[] bytes, String dirName, String fileName) throws IOException {
final File dir = new File(dirName);
final FileOutputStream fos = new FileOutputStream(new File(dir, fileName));
fos.write(bytes);
fos.flush();
fos.close();
}
/**
* As file.deleteOnExit does not work on Android, we need to delete files manually.
* Deleting them in onActivityResult is not a good idea, because for example a base64 encoded file
* will not be available for upload to Facebook (it's deleted before it's uploaded).
* So the best approach is deleting old files when saving (sharing) a new one.
*/
private void cleanupOldFiles(File dir) {
for (File f : dir.listFiles()) {
//noinspection ResultOfMethodCallIgnored
f.delete();
}
}
private static boolean notEmpty(String what) {
return what != null &&
!"".equals(what) &&
!"null".equalsIgnoreCase(what);
}
private static String[] toStringArray(JSONArray jsonArray) throws JSONException {
String[] result = new String[jsonArray.length()];
for (int i = 0; i < jsonArray.length(); i++) {
result[i] = jsonArray.getString(i);
}
return result;
}
public static String sanitizeFilename(String name) {
return name.replaceAll("[:\\\\/*?|<> ]", "_");
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="Android/data/${applicationId}/socialsharing_downloads"
path="./socialsharing-downloads"/>
<root-path
name="root"
path="/"/>
</paths>

View File

@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
@interface NSString (URLEncoding)
@property (readonly) NSString *URLEncodedString;
@end

View File

@@ -0,0 +1,30 @@
#import "NSString+URLEncoding.h"
@implementation NSString (URLEncoding)
- (NSString*)URLEncodedString
{
NSString* result = (NSString *)CFBridgingRelease(
CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(CFStringRef)self,
CFSTR("#%"), // don't escape these
NULL, // allow escaping these
kCFStringEncodingUTF8
)
);
// we may have a URL with more than one '#' now - which iOS doesn't allow, so escape all but the first one
NSArray *parts = [result componentsSeparatedByString:@"#"];
NSString *finalResult = parts[0];
for (int i=1; i<parts.count; i++) {
NSString *part = [parts objectAtIndex:i];
if (i==1) {
finalResult = [finalResult stringByAppendingString:@"#"];
} else {
finalResult = [finalResult stringByAppendingString:@"%23"];
}
finalResult = [finalResult stringByAppendingString:part];
}
return finalResult;
}
@end

View File

@@ -0,0 +1,28 @@
#import <Cordova/CDV.h>
#import <MessageUI/MFMailComposeViewController.h>
@interface SocialSharing : CDVPlugin <UIPopoverControllerDelegate, MFMailComposeViewControllerDelegate, UIDocumentInteractionControllerDelegate>
@property (nonatomic, strong) MFMailComposeViewController *globalMailComposer;
@property (nonatomic, strong) UIDocumentInteractionController * documentInteractionController;
@property (retain) NSString * tempStoredFile;
@property (retain) CDVInvokedUrlCommand * command;
- (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;
- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command;
- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command;
- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command;
- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command;
- (void)shareViaSMS:(CDVInvokedUrlCommand*)command;
- (void)shareViaEmail:(CDVInvokedUrlCommand*)command;
- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command;
- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command;
@end

View File

@@ -0,0 +1,808 @@
#import "SocialSharing.h"
#import "NSString+URLEncoding.h"
#import <Cordova/CDV.h>
#import <Social/Social.h>
#import <Foundation/NSException.h>
#import <MessageUI/MFMessageComposeViewController.h>
#import <MessageUI/MFMailComposeViewController.h>
#import <MobileCoreServices/MobileCoreServices.h>
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;
}
- (void)pluginInitialize {
if ([self isEmailAvailable]) {
[self cycleTheGlobalMailComposer];
}
}
- (void)available:(CDVInvokedUrlCommand*)command {
BOOL avail = NO;
if (NSClassFromString(@"UIActivityViewController")) {
avail = YES;
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:avail];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (NSString*)getIPadPopupCoordinates {
if (_popupCoordinates != nil) {
return _popupCoordinates;
}
if ([self.webView respondsToSelector:@selector(stringByEvaluatingJavaScriptFromString:)]) {
return [(UIWebView*)self.webView stringByEvaluatingJavaScriptFromString:@"window.plugins.socialsharing.iPadPopupCoordinates();"];
} else {
// prolly a wkwebview, ignoring for now
return nil;
}
}
- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command {
_popupCoordinates = [command.arguments objectAtIndex:0];
}
- (CGRect)getPopupRectFromIPadPopupCoordinates:(NSArray*)comps {
CGRect rect = CGRectZero;
if ([comps count] == 4) {
rect = CGRectMake([[comps objectAtIndex:0] integerValue], [[comps objectAtIndex:1] integerValue], [[comps objectAtIndex:2] integerValue], [[comps objectAtIndex:3] integerValue]);
}
return rect;
}
- (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"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
return;
}
NSString *message = options[kShareOptionMessage];
NSString *subject = options[kShareOptionSubject];
NSArray *filenames = options[kShareOptionFiles];
NSString *urlString = options[kShareOptionUrl];
NSMutableArray *activityItems = [[NSMutableArray alloc] init];
if (message != (id)[NSNull null] && message != nil) {
[activityItems addObject:message];
}
if (filenames != (id)[NSNull null] && filenames != nil && filenames.count > 0) {
NSMutableArray *files = [[NSMutableArray alloc] init];
for (NSString* filename in filenames) {
NSObject *file = [self getImage:filename];
if (file == nil) {
file = [self getFile:filename];
}
if (file != nil) {
[files addObject:file];
}
}
[activityItems addObjectsFromArray:files];
}
if (urlString != (id)[NSNull null] && urlString != nil) {
[activityItems addObject:[NSURL URLWithString:[urlString URLEncodedString]]];
}
UIActivity *activity = [[UIActivity alloc] init];
NSArray *applicationActivities = [[NSArray alloc] initWithObjects:activity, nil];
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities];
if (subject != (id)[NSNull null] && subject != nil) {
[activityVC setValue:subject forKey:@"subject"];
}
if ([activityVC respondsToSelector:(@selector(setCompletionWithItemsHandler:))]) {
[activityVC setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray * returnedItems, NSError * 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];
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"
}
NSArray * socialSharingExcludeActivities = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"SocialSharingExcludeActivities"];
if (socialSharingExcludeActivities!=nil && [socialSharingExcludeActivities count] > 0) {
activityVC.excludedActivityTypes = socialSharingExcludeActivities;
}
dispatch_async(dispatch_get_main_queue(), ^(void){
// iPad on iOS >= 8 needs a different approach
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
NSString* iPadCoords = [self getIPadPopupCoordinates];
if (iPadCoords != nil && ![iPadCoords isEqual:@"-1,-1,-1,-1"]) {
NSArray *comps = [iPadCoords componentsSeparatedByString:@","];
CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps];
if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported
activityVC.popoverPresentationController.sourceView = self.webView;
activityVC.popoverPresentationController.sourceRect = rect;
#endif
} else {
_popover = [[UIPopoverController alloc] initWithContentViewController:activityVC];
_popover.delegate = self;
[_popover presentPopoverFromRect:rect inView:self.webView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
} else if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported
activityVC.popoverPresentationController.sourceView = self.webView;
// position the popup at the bottom, just like iOS < 8 did (and iPhone still does on iOS 8)
NSArray *comps = [NSArray arrayWithObjects:
[NSNumber numberWithInt:(self.viewController.view.frame.size.width/2)-200],
[NSNumber numberWithInt:self.viewController.view.frame.size.height],
[NSNumber numberWithInt:400],
[NSNumber numberWithInt:400],
nil];
CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps];
activityVC.popoverPresentationController.sourceRect = rect;
#endif
}
}
[[self getTopMostViewController] presentViewController:activityVC animated:YES completion:nil];
});
}];
}
- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command {
[self shareViaInternal:command type:SLServiceTypeTwitter];
}
- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command {
[self shareViaInternal:command type:SLServiceTypeFacebook];
}
- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command {
// If Fb app is installed a message is not prefilled.
// When shared through the default iOS widget (iOS Settings > Facebook) the message is prefilled already.
NSString *message = [command.arguments objectAtIndex:0];
if (message != (id)[NSNull null]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1000 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
BOOL fbAppInstalled = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"fb://"]]; // requires whitelisting on iOS9
if (fbAppInstalled) {
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setValue:message forPasteboardType:@"public.text"];
NSString *hint = [command.arguments objectAtIndex:4];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:hint delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
[alert show];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2800 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
[alert dismissWithClickedButtonIndex:-1 animated:YES];
});
}
});
}
[self shareViaInternal:command type:SLServiceTypeFacebook];
}
- (void)shareVia:(CDVInvokedUrlCommand*)command {
[self shareViaInternal:command type:[command.arguments objectAtIndex:4]];
}
- (void)canShareVia:(CDVInvokedUrlCommand*)command {
NSString *via = [command.arguments objectAtIndex:4];
CDVPluginResult * pluginResult;
if ([@"sms" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaSMS]) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else if ([@"email" caseInsensitiveCompare:via] == NSOrderedSame && [self isEmailAvailable]) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else if ([@"whatsapp" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaWhatsApp]) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else if ([@"instagram" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaInstagram]) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else if ([self isAvailableForSharing:command type:via]) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command {
if ([self isEmailAvailable]) {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} else {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}
- (bool)isEmailAvailable {
Class messageClass = (NSClassFromString(@"MFMailComposeViewController"));
return messageClass != nil && [messageClass canSendMail];
}
- (bool)isAvailableForSharing:(CDVInvokedUrlCommand*)command
type:(NSString *) type {
// isAvailableForServiceType returns true if you pass it a type that is not
// in the defined constants, this is probably a bug on apples part
if(!([type isEqualToString:SLServiceTypeFacebook]
|| [type isEqualToString:SLServiceTypeTwitter]
|| [type isEqualToString:SLServiceTypeTencentWeibo]
|| [type isEqualToString:SLServiceTypeSinaWeibo])) {
return false;
}
// wrapped in try-catch, because isAvailableForServiceType may crash if an invalid type is passed
@try {
return [SLComposeViewController isAvailableForServiceType:type];
}
@catch (NSException* exception) {
return false;
}
}
- (void)shareViaInternal:(CDVInvokedUrlCommand*)command
type:(NSString *) type {
NSString *message = [command.arguments objectAtIndex:0];
// subject is not supported by the SLComposeViewController
NSArray *filenames = [command.arguments objectAtIndex:2];
NSString *urlString = [command.arguments objectAtIndex:3];
// boldly invoke the target app, because the phone will display a nice message asking to configure the app
SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:type];
if (message != (id)[NSNull null]) {
[composeViewController setInitialText:message];
}
for (NSString* filename in filenames) {
UIImage* image = [self getImage:filename];
if (image != nil) {
[composeViewController addImage:image];
}
}
if (urlString != (id)[NSNull null]) {
[composeViewController addURL:[NSURL URLWithString:[urlString URLEncodedString]]];
}
[composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) {
if (SLComposeViewControllerResultCancelled == result) {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"cancelled"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} else if ([self isAvailableForSharing:command type:type]) {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:SLComposeViewControllerResultDone == result];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} else {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
// required for iOS6 (issues #162 and #167)
[self.viewController dismissViewControllerAnimated:YES completion:nil];
}];
[[self getTopMostViewController] presentViewController:composeViewController animated:YES completion:nil];
}
- (void)shareViaEmail:(CDVInvokedUrlCommand*)command {
if ([self isEmailAvailable]) {
if (TARGET_IPHONE_SIMULATOR && IsAtLeastiOSVersion(@"8.0")) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"SocialSharing plugin"
message:@"Sharing via email is not supported on the iOS 8 simulator."
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
return;
}
[self cycleTheGlobalMailComposer];
self.globalMailComposer.mailComposeDelegate = self;
if ([command.arguments objectAtIndex:0] != (id)[NSNull null]) {
NSString *message = [command.arguments objectAtIndex:0];
BOOL isHTML = [message rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch].location != NSNotFound;
[self.globalMailComposer setMessageBody:message isHTML:isHTML];
}
if ([command.arguments objectAtIndex:1] != (id)[NSNull null]) {
[self.globalMailComposer setSubject: [command.arguments objectAtIndex:1]];
}
if ([command.arguments objectAtIndex:2] != (id)[NSNull null]) {
[self.globalMailComposer setToRecipients:[command.arguments objectAtIndex:2]];
}
if ([command.arguments objectAtIndex:3] != (id)[NSNull null]) {
[self.globalMailComposer setCcRecipients:[command.arguments objectAtIndex:3]];
}
if ([command.arguments objectAtIndex:4] != (id)[NSNull null]) {
[self.globalMailComposer setBccRecipients:[command.arguments objectAtIndex:4]];
}
if ([command.arguments objectAtIndex:5] != (id)[NSNull null]) {
NSArray* attachments = [command.arguments objectAtIndex:5];
NSFileManager* fileManager = [NSFileManager defaultManager];
for (NSString* path in attachments) {
NSURL *file = [self getFile:path];
NSData* data = [fileManager contentsAtPath:file.path];
if (!data) {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"invalid attachment"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
return;
}
NSString* fileName;
NSString* mimeType;
NSString* basename = [self getBasenameFromAttachmentPath:path];
//Find data anywhere in string
NSRange rangeData = [basename rangeOfString:@"data:"];
if (rangeData.location == NSNotFound)
{
fileName = [basename pathComponents].lastObject;
mimeType = [self getMimeTypeFromFileExtension:[basename pathExtension]];
}
else
{
mimeType = (NSString*)[[[basename substringFromIndex:rangeData.location+rangeData.length] componentsSeparatedByString: @";"] objectAtIndex:0];
//Find df anywhere in string
NSRange rangeDF = [basename rangeOfString:@"df:"];
//If not found fallback to default name
if (rangeDF.location == NSNotFound) {
fileName = @"attachment.";
fileName = [fileName stringByAppendingString:(NSString*)[[mimeType componentsSeparatedByString: @"/"] lastObject]];
} else {
//Found, apply name
fileName = (NSString*)[[[basename substringFromIndex:rangeDF.location+rangeDF.length] componentsSeparatedByString: @";"] objectAtIndex:0];
}
NSString *base64content = (NSString*)[[basename componentsSeparatedByString: @","] lastObject];
data = [SocialSharing dataFromBase64String:base64content];
}
[self.globalMailComposer addAttachmentData:data mimeType:mimeType fileName:fileName];
}
}
// remember the command, because we need it in the didFinishWithResult method
_command = command;
[self.commandDelegate runInBackground:^{
[[self getTopMostViewController] presentViewController:self.globalMailComposer animated:YES completion:nil];
}];
} else {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}
- (UIViewController*) getTopMostViewController {
UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;
while (presentingViewController.presentedViewController != nil) {
presentingViewController = presentingViewController.presentedViewController;
}
return presentingViewController;
}
- (NSString*) getBasenameFromAttachmentPath:(NSString*)path {
if ([path hasPrefix:@"base64:"]) {
NSString* pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:" withString:@""];
return [pathWithoutPrefix substringToIndex:[pathWithoutPrefix rangeOfString:@"//"].location];
}
return [path componentsSeparatedByString: @"?"][0];
}
- (NSString*) getMimeTypeFromFileExtension:(NSString*)extension {
if (!extension) {
return nil;
}
// Get the UTI from the file's extension
CFStringRef ext = (CFStringRef)CFBridgingRetain(extension);
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL);
// Converting UTI to a mime type
NSString *result = (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType));
CFRelease(ext);
CFRelease(type);
return result;
}
/**
* Delegate will be called after the mail composer did finish an action
* to dismiss the view.
*/
- (void) mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error {
bool ok = result == MFMailComposeResultSent;
[self.globalMailComposer dismissViewControllerAnimated:YES completion:^{[self cycleTheGlobalMailComposer];}];
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok];
[self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId];
}
-(void)cycleTheGlobalMailComposer {
// we are cycling the damned GlobalMailComposer: http://stackoverflow.com/questions/25604552/i-have-real-misunderstanding-with-mfmailcomposeviewcontroller-in-swift-ios8-in/25604976#25604976
self.globalMailComposer = nil;
self.globalMailComposer = [[MFMailComposeViewController alloc] init];
}
- (bool)canShareViaSMS {
Class messageClass = (NSClassFromString(@"MFMessageComposeViewController"));
return messageClass != nil && [messageClass canSendText];
}
- (void)shareViaSMS:(CDVInvokedUrlCommand*)command {
if ([self canShareViaSMS]) {
NSDictionary* options = [command.arguments objectAtIndex:0];
NSString *phonenumbers = [command.arguments objectAtIndex:1];
NSString *message = [options objectForKey:@"message"];
NSString *subject = [options objectForKey:@"subject"];
NSString *image = [options objectForKey:@"image"];
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.messageComposeDelegate = (id) self;
if (message != (id)[NSNull null]) {
picker.body = message;
}
if (subject != (id)[NSNull null]) {
[picker setSubject:subject];
}
if (image != nil && image != (id)[NSNull null]) {
BOOL canSendAttachments = [[MFMessageComposeViewController class] respondsToSelector:@selector(canSendAttachments)];
if (canSendAttachments) {
NSURL *file = [self getFile:image];
if (file != nil) {
[picker addAttachmentURL:file withAlternateFilename:nil];
}
}
}
if (phonenumbers != (id)[NSNull null]) {
[picker setRecipients:[phonenumbers componentsSeparatedByString:@","]];
}
// remember the command, because we need it in the didFinishWithResult method
_command = command;
[self.commandDelegate runInBackground:^{
picker.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[[self getTopMostViewController] presentViewController:picker animated:NO completion:nil];
}];
} else {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}
// Dismisses the SMS composition interface when users taps Cancel or Send
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result {
bool ok = result == MessageComposeResultSent;
[[self getTopMostViewController] dismissViewControllerAnimated:YES completion:nil];
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok];
[self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId];
}
- (bool)canShareViaInstagram {
return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"instagram://app"]]; // requires whitelisting on iOS9
}
- (bool)canShareViaWhatsApp {
return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"whatsapp://app"]]; // requires whitelisting on iOS9
}
// this is only an internal test method for now, can be used to open a share sheet with 'Open in xx' links for tumblr, drive, dropbox, ..
- (void)openImage:(NSString *)imageName {
UIImage* image =[self getImage:imageName];
if (image != nil) {
NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myTempImage.jpg"];
[UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES];
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]];
_documentInteractionController.UTI = @""; // TODO find the scheme for google drive and create a shareViaGoogleDrive function
[_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES];
}
}
- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command {
// on iOS9 canShareVia('instagram'..) will only work if instagram:// is whitelisted.
// If it's not, this method will ask permission to the user on iOS9 for opening the app,
// which is of course better than Instagram sharing not working at all because you forgot to whitelist it.
// Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('instagram'..)
if (!IsAtLeastiOSVersion(@"9.0")) {
if (![self canShareViaInstagram]) {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
return;
}
}
NSString *message = [command.arguments objectAtIndex:0];
// subject is not supported by the SLComposeViewController
NSArray *filenames = [command.arguments objectAtIndex:2];
// only use the first image (for now.. maybe we can share in a loop?)
UIImage* image = nil;
for (NSString* filename in filenames) {
image = [self getImage:filename];
break;
}
// NSData *imageObj = [NSData dataFromBase64String:objectAtIndex0];
NSString *tmpDir = NSTemporaryDirectory();
NSString *path = [tmpDir stringByAppendingPathComponent:@"instagram.igo"];
[UIImageJPEGRepresentation(image, 1.0) writeToFile:path atomically:YES];
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]];
_documentInteractionController.delegate = self;
_documentInteractionController.UTI = @"com.instagram.exclusivegram";
if (message != (id)[NSNull null]) {
// no longer working, so ..
_documentInteractionController.annotation = @{@"InstagramCaption" : message};
// .. we put the message on the clipboard (you app can prompt the user to paste it)
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setValue:message forPasteboardType:@"public.text"];
}
// remember the command for the delegate method
_command = command;
// test for #513
dispatch_async(dispatch_get_main_queue(), ^(void){
[_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.webView animated:YES];
});
}
- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command {
// on iOS9 canShareVia('whatsapp'..) will only work if whatsapp:// is whitelisted.
// If it's not, this method will ask permission to the user on iOS9 for opening the app,
// which is of course better than WhatsApp sharing not working at all because you forgot to whitelist it.
// Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('whatsapp'..)
if (!IsAtLeastiOSVersion(@"9.0")) {
if (![self canShareViaWhatsApp]) {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
return;
}
}
NSString *message = [command.arguments objectAtIndex:0];
// subject is not supported by the SLComposeViewController
NSArray *filenames = [command.arguments objectAtIndex:2];
NSString *urlString = [command.arguments objectAtIndex:3];
NSString *abid = [command.arguments objectAtIndex:4];
// only use the first image (for now.. maybe we can share in a loop?)
UIImage* image = nil;
for (NSString* filename in filenames) {
image = [self getImage:filename];
break;
}
// with WhatsApp, we can share an image OR text+url.. image wins if set
if (image != nil) {
NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/whatsAppTmp.wai"];
[UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES];
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]];
_documentInteractionController.UTI = @"net.whatsapp.image";
_documentInteractionController.delegate = self;
_command = command;
[_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES];
} else {
// append an url to a message, if both are passed
NSString * shareString = @"";
if (message != (id)[NSNull null]) {
shareString = message;
}
if (urlString != (id)[NSNull null]) {
if ([shareString isEqual: @""]) {
shareString = urlString;
} else {
shareString = [NSString stringWithFormat:@"%@ %@", shareString, [urlString URLEncodedString]];
}
}
NSString * encodedShareString = [shareString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// also encode the '=' character
encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"];
encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"&" withString:@"%26"];
NSString * abidString = @"";
if (abid != (id)[NSNull null]) {
abidString = [NSString stringWithFormat:@"abid=%@&", abid];
}
NSString * encodedShareStringForWhatsApp = [NSString stringWithFormat:@"whatsapp://send?%@text=%@", abidString, encodedShareString];
NSURL *whatsappURL = [NSURL URLWithString:encodedShareStringForWhatsApp];
[[UIApplication sharedApplication] openURL: whatsappURL];
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}
- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command {
self.command = command;
NSArray *filenames = [command.arguments objectAtIndex:0];
[self.commandDelegate runInBackground:^{
bool shared = false;
for (NSString* filename in filenames) {
UIImage* image = [self getImage:filename];
if (image != nil) {
shared = true;
UIImageWriteToSavedPhotosAlbum(image, self, @selector(thisImage:wasSavedToPhotoAlbumWithError:contextInfo:), nil);
}
}
if (!shared) {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no valid image was passed"];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
}
}];
}
// called from saveToPhotoAlbum, note that we only send feedback for the first image that's being saved (not keeping the callback)
// but since the UIImageWriteToSavedPhotosAlbum function is only called with valid images that should not be a problem
- (void)thisImage:(UIImage *)image wasSavedToPhotoAlbumWithError:(NSError *)error contextInfo:(void*)ctxInfo {
if (error) {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.localizedDescription];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
} else {
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
}
}
-(UIImage*)getImage: (NSString *)imageName {
UIImage *image = nil;
if (imageName != (id)[NSNull null]) {
if ([imageName hasPrefix:@"http"]) {
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]]];
} else if ([imageName hasPrefix:@"www/"]) {
image = [UIImage imageNamed:imageName];
} else if ([imageName hasPrefix:@"file://"]) {
image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSURL URLWithString:imageName] path]]];
} else if ([imageName hasPrefix:@"data:"]) {
// using a base64 encoded string
NSURL *imageURL = [NSURL URLWithString:imageName];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
image = [UIImage imageWithData:imageData];
} else if ([imageName hasPrefix:@"assets-library://"]) {
// use assets-library
NSURL *imageURL = [NSURL URLWithString:imageName];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
image = [UIImage imageWithData:imageData];
} else {
// assume anywhere else, on the local filesystem
image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imageName]];
}
}
return image;
}
-(NSURL*)getFile: (NSString *)fileName {
NSURL *file = nil;
if (fileName != (id)[NSNull null]) {
NSRange rangeData = [fileName rangeOfString:@"data:"];
if ([fileName hasPrefix:@"http"]) {
NSURL *url = [NSURL URLWithString:fileName];
NSData *fileData = [NSData dataWithContentsOfURL:url];
NSString *name = (NSString*)[[fileName componentsSeparatedByString: @"/"] lastObject];
file = [NSURL fileURLWithPath:[self storeInFile:[name componentsSeparatedByString: @"?"][0] fileData:fileData]];
} else if ([fileName hasPrefix:@"www/"]) {
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *fullPath = [NSString stringWithFormat:@"%@/%@", bundlePath, fileName];
file = [NSURL fileURLWithPath:fullPath];
} else if ([fileName hasPrefix:@"file://"]) {
// stripping the first 6 chars, because the path should start with / instead of file://
file = [NSURL fileURLWithPath:[fileName substringFromIndex:6]];
} else if (rangeData.location != NSNotFound ){
//If found "data:"
NSString *fileType = (NSString*)[[[fileName substringFromIndex:rangeData.location+rangeData.length] componentsSeparatedByString: @";"] objectAtIndex:0];
NSString* attachmentName;
//Find df anywhere in string
NSRange rangeDF = [fileName rangeOfString:@"df:"];
//If not found fallback to default name
if (rangeDF.location == NSNotFound) {
attachmentName = @"attachment.";
attachmentName = [attachmentName stringByAppendingString:(NSString*)[[fileType componentsSeparatedByString: @"/"] lastObject]];
} else {
//Found, apply name
attachmentName = (NSString*)[[[fileName substringFromIndex:rangeDF.location+rangeDF.length] componentsSeparatedByString: @";"] objectAtIndex:0];
}
NSString *base64content = (NSString*)[[fileName componentsSeparatedByString: @","] lastObject];
NSData* data = [SocialSharing dataFromBase64String:base64content];
file = [NSURL fileURLWithPath:[self storeInFile:attachmentName fileData:data]];
} else {
// assume anywhere else, on the local filesystem
file = [NSURL fileURLWithPath:fileName];
}
}
return file;
}
-(NSString*) storeInFile: (NSString*) fileName
fileData: (NSData*) fileData {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
[fileData writeToFile:filePath atomically:YES];
_tempStoredFile = filePath;
return filePath;
}
- (void) cleanupStoredFiles {
if (_tempStoredFile != nil) {
NSError *error;
[[NSFileManager defaultManager]removeItemAtPath:_tempStoredFile error:&error];
}
}
+ (NSData*) dataFromBase64String:(NSString*)aString {
return [[NSData alloc] initWithBase64EncodedString:aString options:0];
}
#pragma mark - UIPopoverControllerDelegate methods
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view {
NSArray *comps = [[self getIPadPopupCoordinates] componentsSeparatedByString:@","];
CGRect newRect = [self getPopupRectFromIPadPopupCoordinates:comps];
rect->origin = newRect.origin;
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
_popover = nil;
}
#pragma mark - UIDocumentInteractionControllerDelegate methods
- (void) documentInteractionController: (UIDocumentInteractionController *) controller willBeginSendingToApplication: (NSString *) application {
// note that the application actually contains the app bundle id which was picked (for whatsapp and instagram only)
NSLog(@"SocialSharing app selected: %@", application);
}
- (void) documentInteractionControllerDidDismissOpenInMenu: (UIDocumentInteractionController *) controller {
if (self.command != nil) {
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:result callbackId: self.command.callbackId];
}
}
@end

View File

@@ -0,0 +1,177 @@
var cordova = require('cordova');
module.exports = {
share: function (win, fail, args) {
//Text Message
var message = args[0];
//Title
var subject = args[1];
//File(s) Path
var fileOrFileArray = args[2];
//Web link
var url = args[3];
var folder = Windows.Storage.ApplicationData.current.temporaryFolder;
var getExtension = function (strBase64) {
return strBase64.substring(strBase64.indexOf("/") + 1, strBase64.indexOf(";base64"));
};
var replaceAll = function (str, find, replace) {
return str.replace(new RegExp(find, 'g'), replace);
};
var sanitizeFilename = function (name) {
return replaceAll(name, "[:\\\\/*?|<> ]", "_");
};
var getFileName = function (position, fileExtension) {
var fileName = (subject ? sanitizeFilename(subject) : "file") + (position == 0 ? "" : "_" + position) + "." + fileExtension;
return fileName;
};
var createTemporalFile = function (fileName, buffer) {
var filePath = "";
return folder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file) {
filePath = file.path;
return Windows.Storage.FileIO.writeBufferAsync(file, buffer);
}).then(function(){
return Windows.Storage.StorageFile.getFileFromPathAsync(filePath);
});
};
var doShare = function (e) {
e.request.data.properties.title = subject?subject: "Sharing";
if (message) e.request.data.setText(message);
if (url) e.request.data.setWebLink(new Windows.Foundation.Uri(url));
if (fileOrFileArray.length > 0) {
var deferral = e.request.getDeferral();
var storageItems = [];
var filesCount = fileOrFileArray.length;
var completeFile = function () {
if (!--filesCount) {
storageItems.length && e.request.data.setStorageItems(storageItems);
deferral.complete();
}
};
for (var i = 0; i < fileOrFileArray.length; i++) {
var file = fileOrFileArray[i];
if (file.indexOf("data:") >= 0) {
var fileName = getFileName(i, getExtension(file));
var buffer = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(file.split(',')[1]);
if (buffer) {
createTemporalFile(fileName, buffer).done(
function (file) {
storageItems.push(file);
completeFile();
},
function () {
completeFile();
}
);
}
else {
completeFile();
}
}
else {
Windows.Storage.StorageFile.getFileFromPathAsync(file).done(
function (file) {
storageItems.push(file);
completeFile();
},
function () {
completeFile();
}
);
}
}
}
};
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
dataTransferManager.addEventListener("datarequested", doShare);
try {
Windows.ApplicationModel.DataTransfer.DataTransferManager.showShareUI();
win(true);
} catch (err) {
fail(err);
}
},
canShareViaEmail: function (win, fail, args) {
win(true);
},
shareViaEmail: function (win, fail, args) {
//Text Message
var message = args[0];
//Title
var subject = args[1];
//File(s) Path
var fileOrFileArray = args[5];
var doShare = function (e) {
e.request.data.properties.title = subject ? subject : "Sharing";
if (message) {
var htmlFormat = Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.createHtmlFormat(message);
e.request.data.setHtmlFormat(htmlFormat);
}
if (fileOrFileArray.length > 0) {
var deferral = e.request.getDeferral();
var storageItems = [];
var filesCount = fileOrFileArray.length;
for (var i = 0; i < fileOrFileArray.length; i++) {
Windows.Storage.StorageFile.getFileFromPathAsync(fileOrFileArray[i]).done(
function (index, file) {
var path = fileOrFileArray[index];
var streamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(file);
e.request.data.resourceMap[path] = streamRef;
storageItems.push(file);
if (!--filesCount) {
e.request.data.setStorageItems(storageItems);
deferral.complete();
}
}.bind(this, i),
function () {
if (!--filesCount) {
e.request.data.setStorageItems(storageItems);
deferral.complete();
}
}
);
}
}
};
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
dataTransferManager.addEventListener("datarequested", doShare);
try {
Windows.ApplicationModel.DataTransfer.DataTransferManager.showShareUI();
win(true);
} catch (err) {
fail(err);
}
},
shareViaSMS: function (win, fail, args) {
var chatMessage = new Windows.ApplicationModel.Chat.ChatMessage();
chatMessage.body = args[0].message;
if (!!args[1]) {
chatMessage.recipients.push(args[1]);
}
Windows.ApplicationModel.Chat.ChatMessageManager.showComposeSmsMessageAsync(chatMessage).done(win, fail);
}
};
require("cordova/exec/proxy").add("SocialSharing", module.exports);

View File

@@ -0,0 +1,103 @@
using Microsoft.Phone.Tasks;
using WPCordovaClassLib.Cordova;
using WPCordovaClassLib.Cordova.Commands;
using WPCordovaClassLib.Cordova.JSON;
using Newtonsoft.Json;
namespace Cordova.Extension.Commands
{
public class SocialSharing : BaseCommand
{
public void available(string jsonArgs)
{
DispatchCommandResult(new PluginResult(PluginResult.Status.OK));
}
public void share(string jsonArgs)
{
var options = JsonHelper.Deserialize<string[]>(jsonArgs);
var message = options[0];
var title = options[1];
var files = JsonHelper.Deserialize<string[]>(options[2]);
var link = options[3];
if (link != null && !"null".Equals(link))
{
ShareLinkTask shareLinkTask = new ShareLinkTask();
shareLinkTask.Title = title;
shareLinkTask.LinkUri = new System.Uri(link, System.UriKind.Absolute);
shareLinkTask.Message = message;
shareLinkTask.Show();
}
else if (files != null && files.Length > 0)
{
ShareLinkTask shareLinkTask = new ShareLinkTask();
shareLinkTask.Title = title;
shareLinkTask.LinkUri = new System.Uri(files[0], System.UriKind.Absolute);
shareLinkTask.Message = message;
shareLinkTask.Show();
}
else
{
var shareStatusTask = new ShareStatusTask { Status = message };
shareStatusTask.Show();
}
// unfortunately, there is no way to tell if something was shared, so just invoke the successCallback
DispatchCommandResult(new PluginResult(PluginResult.Status.OK));
}
public void canShareViaEmail(string jsonArgs)
{
DispatchCommandResult(new PluginResult(PluginResult.Status.OK));
}
// HTML and attachments are currently not supported on WP8
public void shareViaEmail(string jsonArgs)
{
var options = JsonHelper.Deserialize<string[]>(jsonArgs);
EmailComposeTask draft = new EmailComposeTask();
draft.Body = options[0];
draft.Subject = options[1];
if (!"null".Equals(options[2]))
{
draft.To = string.Join(",", options[2]);
}
if (!"null".Equals(options[3]))
{
draft.Cc = string.Join(",", options[3]);
}
if (!"null".Equals(options[4]))
{
draft.Bcc = string.Join(",", options[4]);
}
draft.Show();
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true));
}
public void shareViaSMS(string jsonArgs)
{
var options = JsonHelper.Deserialize<string[]>(jsonArgs);
SmsComposeTask smsComposeTask = new SmsComposeTask();
smsComposeTask.To = options[1];
SMSMessageClass m = JsonConvert.DeserializeObject<SMSMessageClass>(options[0]);
smsComposeTask.Body = m.message;
smsComposeTask.Show();
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true));
}
}
public class SMSMessageClass
{
public string message { get; set; }
}
}