diff --git a/ExampleApp/android/.project b/ExampleApp/android/.project
new file mode 100644
index 0000000..0e0a1ba
--- /dev/null
+++ b/ExampleApp/android/.project
@@ -0,0 +1,17 @@
+
+
+ android_
+ Project android_ created by Buildship.
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectnature
+
+
diff --git a/ExampleApp/android/.settings/org.eclipse.buildship.core.prefs b/ExampleApp/android/.settings/org.eclipse.buildship.core.prefs
new file mode 100644
index 0000000..e889521
--- /dev/null
+++ b/ExampleApp/android/.settings/org.eclipse.buildship.core.prefs
@@ -0,0 +1,2 @@
+connection.project.dir=
+eclipse.preferences.version=1
diff --git a/android/.project b/android/.project
new file mode 100644
index 0000000..3964dd3
--- /dev/null
+++ b/android/.project
@@ -0,0 +1,17 @@
+
+
+ android
+ Project android created by Buildship.
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectnature
+
+
diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs
new file mode 100644
index 0000000..e889521
--- /dev/null
+++ b/android/.settings/org.eclipse.buildship.core.prefs
@@ -0,0 +1,2 @@
+connection.project.dir=
+eclipse.preferences.version=1
diff --git a/android/src/main/java/com/reactlibrary/mailcompose/RNMailComposeModule.java b/android/src/main/java/com/reactlibrary/mailcompose/RNMailComposeModule.java
index f0f2bd0..acffcc9 100644
--- a/android/src/main/java/com/reactlibrary/mailcompose/RNMailComposeModule.java
+++ b/android/src/main/java/com/reactlibrary/mailcompose/RNMailComposeModule.java
@@ -1,23 +1,42 @@
package com.reactlibrary.mailcompose;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Bundle;
+import android.os.Parcelable;
import android.text.Html;
import android.text.Spanned;
import android.util.Base64;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import android.widget.Toast;
+import android.os.Build;
import com.facebook.react.bridge.ActivityEventListener;
-import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.ReadableNativeArray;
import com.facebook.react.bridge.ReadableType;
+import com.facebook.react.bridge.WritableArray;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeArray;
+import com.facebook.react.bridge.WritableNativeMap;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -30,35 +49,22 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
+import android.support.annotation.NonNull;
-public class RNMailComposeModule extends ReactContextBaseJavaModule {
- private static final int ACTIVITY_SEND = 129382;
-
+public class RNMailComposeModule extends ReactContextBaseJavaModule implements ActivityEventListener {
+ private final ReactApplicationContext reactContext;
+ private static final int ACTIVITY_SEND = 12938;
+ public static String lastSelection = null;
private Promise mPromise;
- private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {
-
- @Override
- public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
- if (requestCode == ACTIVITY_SEND) {
- if (mPromise != null) {
- if (resultCode == Activity.RESULT_CANCELED) {
- mPromise.reject("cancelled", "Operation has been cancelled");
- } else {
- mPromise.resolve("sent");
- }
- mPromise = null;
- }
- }
- }
- };
-
public RNMailComposeModule(final ReactApplicationContext reactContext) {
super(reactContext);
- reactContext.addActivityEventListener(mActivityEventListener);
+ reactContext.addActivityEventListener(this);
+ this.reactContext = reactContext;
}
@Override
@@ -97,7 +103,7 @@ private void putExtra(Intent intent, String key, ArrayList value) {
}
}
- private void addAttachments(Intent intent, ReadableArray attachments) {
+ private void addAttachments(Intent intent, ReadableArray attachments, String fileProviderUri) {
if (attachments == null) return;
ArrayList uris = new ArrayList<>();
@@ -105,7 +111,14 @@ private void addAttachments(Intent intent, ReadableArray attachments) {
if (attachments.getType(i) == ReadableType.Map) {
ReadableMap attachment = attachments.getMap(i);
if (attachment != null) {
- byte[] blob = getBlob(attachment, "data");
+ Uri contentUri = null;
+ byte[] blob = null;
+ if (attachment.hasKey("url") && attachment.getType("url") == ReadableType.String && fileProviderUri != null) {
+ contentUri = FileProvider.getUriForFile(this.reactContext, fileProviderUri, new File(attachment.getString("url")));
+ } else {
+ blob = getBlob(attachment, "data");
+ }
+
String text = getString(attachment, "text");
// String mimeType = getString(attachment, "mimeType");
String filename = getString(attachment, "filename");
@@ -114,12 +127,16 @@ private void addAttachments(Intent intent, ReadableArray attachments) {
}
String ext = getString(attachment, "ext");
- File tempFile = createTempFile(filename, ext);
+ File tempFile = null;
if (blob != null) {
+ createTempFile(filename, ext);
tempFile = writeBlob(tempFile, blob);
} else if (text != null) {
+ createTempFile(filename, ext);
tempFile = writeText(tempFile, text);
+ } else if (contentUri != null) {
+ uris.add(contentUri);
}
if (tempFile != null) {
@@ -283,15 +300,132 @@ private File writeBlob(File file, byte[] blob) {
return null;
}
+ public static class ChooserBroadcast extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String result = String.valueOf(intent.getExtras().get(Intent.EXTRA_CHOSEN_COMPONENT));
+ String chosenComponent = result.substring(result.indexOf("{") + 1, result.indexOf("/"));
+
+ RNMailComposeModule.lastSelection = chosenComponent;
+ }
+ }
+
+ @ReactMethod
+ public void hasMailApp(String appName, Promise promise) {
+
+ // Get App infos
+ Intent emailAppIntent = getEmailAppIntent();
+ List emailAppInfos = getCurrentActivity().getPackageManager().queryIntentActivities(emailAppIntent, PackageManager.MATCH_ALL);
+
+ for (int i = 0; i < emailAppInfos.size(); i++) {
+ String packageName = emailAppInfos.get(i).activityInfo.packageName;
+ if (packageName.equals(appName)) {
+ promise.resolve(true);
+ return;
+ }
+ }
+ promise.resolve(false);
+ }
+
+ @ReactMethod
+ public void getMailAppData(Promise promise) {
+
+ WritableArray emailAppArray = new WritableNativeArray();
+
+ // Get App infos
+ Intent emailAppIntent = getEmailAppIntent();
+ PackageManager packageManager = getCurrentActivity().getPackageManager();
+ List emailAppInfos = packageManager.queryIntentActivities(emailAppIntent, PackageManager.MATCH_ALL);
+
+ ArrayList addedPackages = new ArrayList<>();
+ for (int i = 0; i < emailAppInfos.size(); i++) {
+ ActivityInfo activityInfo = emailAppInfos.get(i).activityInfo;
+ // Prevent Duplicated
+ if (addedPackages.indexOf(activityInfo.packageName) == -1) {
+ addedPackages.add(activityInfo.packageName);
+
+ // Build Map with app data
+ WritableMap emailAppData = new WritableNativeMap();
+ emailAppData.putString("name", activityInfo.packageName);
+ emailAppData.putString("raw", packageManager.getApplicationLabel(activityInfo.applicationInfo).toString());
+
+ Drawable icon = packageManager.getApplicationIcon(activityInfo.applicationInfo);
+ emailAppData.putString("icon", getBase64(icon != null ? icon : packageManager.getDefaultActivityIcon()));
+
+ // Add to array
+ emailAppArray.pushMap(emailAppData);
+ }
+ }
+ promise.resolve(emailAppArray);
+ }
+
+ @NonNull
+ static private Bitmap getBitmapFromDrawable(@NonNull Drawable drawable) {
+ final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bmp);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bmp;
+ }
+
+ private String getBase64(Drawable icon) {
+ Bitmap bitmap = getBitmapFromDrawable(icon);
+ return encodeToBase64(bitmap, Bitmap.CompressFormat.PNG, 100);
+ }
+
+ private String encodeToBase64(Bitmap image, Bitmap.CompressFormat compressFormat, int quality) {
+ ByteArrayOutputStream byteArrayOS = new ByteArrayOutputStream();
+ image.compress(compressFormat, quality, byteArrayOS);
+ return Base64.encodeToString(byteArrayOS.toByteArray(), Base64.DEFAULT);
+ }
+
+ @ReactMethod
+ public void getLastSelection(Promise promise) {
+ String selected = lastSelection;
+ lastSelection = null;
+ promise.resolve(selected);
+ }
+
@ReactMethod
- public void send(ReadableMap data, Promise promise) throws IOException {
+ public void send(ReadableMap data, Promise promise) {
if (mPromise != null) {
mPromise.reject("timeout", "Operation has timed out");
mPromise = null;
}
- Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ try {
+ // Check if Mail App exists
+ ArrayList mailIntents = getEmailAppLauncherIntentsWithData(data);
+ if (mailIntents == null || mailIntents.size() == 0) {
+ Toast.makeText(getCurrentActivity(), "No matching app found", Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ // Create chooser
+ Intent chooserIntent;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ chooserIntent = Intent.createChooser(new Intent(), "Select email app:", getIntentSender());
+ } else {
+ chooserIntent = Intent.createChooser(new Intent(), "Select email app:");
+ }
+ chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, mailIntents.toArray( new Parcelable[mailIntents.size()] ));
+ getCurrentActivity().startActivityForResult(chooserIntent, ACTIVITY_SEND);
+ mPromise = promise;
+ } catch (NullPointerException e) {
+ promise.reject("failed", "StartActivityForResult failed");
+ } catch (ActivityNotFoundException e) {
+ promise.reject("failed", "Activity Not Found");
+ } catch (RuntimeException e) {
+ promise.reject("failed", "External App Probably Cannot Handle Parcelable");
+ } catch (Exception e) {
+ promise.reject("failed", "Unknown Error");
+ }
+ }
+ // Create intent for data share
+ private Intent getDataIntent (ReadableMap data) {
+ Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
String text = getString(data, "body");
String html = getString(data, "html");
if (!isEmpty(html)) {
@@ -299,30 +433,80 @@ public void send(ReadableMap data, Promise promise) throws IOException {
putExtra(intent, Intent.EXTRA_TEXT, Html.fromHtml(html));
putExtra(intent, Intent.EXTRA_HTML_TEXT, Html.fromHtml(html));
} else {
- intent.setType("text/plain");
+ // intent.setType("text/plain");
+ intent.setType("message/rfc822");
+
if (!isEmpty(text)) {
putExtra(intent, Intent.EXTRA_TEXT, text);
}
}
-
putExtra(intent, Intent.EXTRA_SUBJECT, getString(data, "subject"));
putExtra(intent, Intent.EXTRA_EMAIL, getStringArray(data, "toRecipients"));
putExtra(intent, Intent.EXTRA_CC, getStringArray(data, "ccRecipients"));
putExtra(intent, Intent.EXTRA_BCC, getStringArray(data, "bccRecipients"));
- addAttachments(intent, getArray(data, "attachments"));
-
+ addAttachments(intent, getArray(data, "attachments"), getString(data, "fileProviderUri"));
intent.putExtra("exit_on_sent", true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- try {
- getCurrentActivity().startActivityForResult(Intent.createChooser(intent, "Send Mail"), ACTIVITY_SEND);
- mPromise = promise;
- } catch (ActivityNotFoundException e) {
- promise.reject("failed", "Activity Not Found");
- } catch (Exception e) {
- promise.reject("failed", "Unknown Error");
+ return intent;
+ }
+
+ // Intent that only email apps can handle
+ private Intent getEmailAppIntent() {
+ Intent emailAppIntent = new Intent(Intent.ACTION_SENDTO);
+ emailAppIntent.setData(Uri.parse("mailto:"));
+ emailAppIntent.putExtra(Intent.EXTRA_EMAIL, "");
+ emailAppIntent.putExtra(Intent.EXTRA_SUBJECT, "");
+
+ return emailAppIntent;
+ }
+
+ // Get E-Mail App intents only for share picker (filtered for duplicates)
+ private ArrayList getEmailAppLauncherIntentsWithData (ReadableMap data) {
+ ArrayList emailAppLauncherIntentsWithData = new ArrayList<>();
+ Intent dataIntent = getDataIntent(data);
+ Intent emailAppIntent = getEmailAppIntent();
+
+ String selectedApp = getString(data, "selectedApp");
+
+ if (selectedApp != null) {
+ // Set selected mail app
+ emailAppLauncherIntentsWithData.add(((Intent) dataIntent.clone()).setPackage(selectedApp));
+ } else {
+ // Get All installed apps that can handle email intent
+ PackageManager packageManager = getCurrentActivity().getPackageManager();
+ List emailApps = packageManager.queryIntentActivities(emailAppIntent, PackageManager.GET_META_DATA);
+ ArrayList addedPackages = new ArrayList<>();
+ for (int i = 0; i < emailApps.size(); i++) {
+ String packageName = emailApps.get(i).activityInfo.packageName;
+ if (addedPackages.indexOf(packageName) == -1) {
+ addedPackages.add(packageName);
+ emailAppLauncherIntentsWithData.add(((Intent) dataIntent.clone()).setPackage(packageName));
+ }
+ }
}
+ return emailAppLauncherIntentsWithData;
}
-}
+ private IntentSender getIntentSender () {
+ Intent receiverIntent = new Intent(reactContext, ChooserBroadcast.class);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(reactContext, ACTIVITY_SEND, receiverIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return pendingIntent.getIntentSender();
+ }
+
+ @Override
+ public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
+ if (requestCode == ACTIVITY_SEND) {
+
+ if (mPromise != null) {
+ mPromise.resolve("unknown");
+ mPromise = null;
+ }
+ }
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ }
+}
diff --git a/ios/RNMailCompose/RNMailCompose.swift b/ios/RNMailCompose/RNMailCompose.swift
index 5bc690a..2aa36c6 100644
--- a/ios/RNMailCompose/RNMailCompose.swift
+++ b/ios/RNMailCompose/RNMailCompose.swift
@@ -48,45 +48,54 @@ class RNMailCompose: NSObject, MFMailComposeViewControllerDelegate {
return
}
- let vc = MFMailComposeViewController()
-
- if let value = data["subject"] as? String {
- vc.setSubject(value)
- }
- if let value = data["toRecipients"] as? [String] {
- vc.setToRecipients(value)
- }
- if let value = data["ccRecipients"] as? [String] {
- vc.setCcRecipients(value)
- }
- if let value = data["bccRecipients"] as? [String] {
- vc.setBccRecipients(value)
- }
- if let value = data["body"] as? String {
- vc.setMessageBody(value, isHTML: false)
- }
- if let value = data["html"] as? String {
- vc.setMessageBody(value, isHTML: true)
- }
-
- if let value = data["attachments"] as? [[String: String]] {
- for dict in value {
- if let data = textToData(utf8: dict["text"], base64: dict["data"]), let mimeType = dict["mimeType"], let filename = toFilename(filename: dict["filename"], ext: dict["ext"]) {
- vc.addAttachmentData(data, mimeType: mimeType, fileName: filename)
+ DispatchQueue.main.async {
+ let vc = MFMailComposeViewController()
+
+ if let value = data["subject"] as? String {
+ vc.setSubject(value)
+ }
+ if let value = data["toRecipients"] as? [String] {
+ vc.setToRecipients(value)
+ }
+ if let value = data["ccRecipients"] as? [String] {
+ vc.setCcRecipients(value)
+ }
+ if let value = data["bccRecipients"] as? [String] {
+ vc.setBccRecipients(value)
+ }
+ if let value = data["body"] as? String {
+ vc.setMessageBody(value, isHTML: false)
+ }
+ if let value = data["html"] as? String {
+ vc.setMessageBody(value, isHTML: true)
+ }
+
+ if let value = data["attachments"] as? [[String: String]] {
+ for dict in value {
+ if let data = self.textToData(utf8: dict["text"], base64: dict["data"]), let mimeType = dict["mimeType"], let filename = self.toFilename(filename: dict["filename"], ext: dict["ext"]) {
+ vc.addAttachmentData(data, mimeType: mimeType, fileName: filename)
+ }
+ if let url = dict["url"], let mimeType = dict["mimeType"], let filename = self.toFilename(filename: dict["filename"], ext: dict["ext"]) {
+ do {
+ try vc.addAttachmentData(Data(contentsOf: URL(fileURLWithPath: url)), mimeType: mimeType, fileName: filename)
+ } catch let error {
+ reject("fileNotFound", "File not found", error)
+ }
+ }
}
}
+
+ vc.mailComposeDelegate = self
+
+ self.resolve = resolve
+ self.reject = reject
+
+ var rootVC = UIApplication.shared.keyWindow?.rootViewController;
+ while (rootVC?.presentedViewController != nil) {
+ rootVC = rootVC?.presentedViewController;
+ }
+ rootVC?.present(vc, animated: true, completion: nil)
}
-
- vc.mailComposeDelegate = self
-
- self.resolve = resolve
- self.reject = reject
-
- var rootVC = UIApplication.shared.keyWindow?.rootViewController;
- while (rootVC?.presentedViewController != nil) {
- rootVC = rootVC?.presentedViewController;
- }
- rootVC?.present(vc, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
@@ -109,4 +118,9 @@ class RNMailCompose: NSObject, MFMailComposeViewControllerDelegate {
controller.dismiss(animated: true, completion: nil)
}
+
+ @objc
+ static func requiresMainQueueSetup() -> Bool {
+ return true
+ }
}
diff --git a/ios/RNMailCompose/RNMailComposeBridge.m b/ios/RNMailCompose/RNMailComposeBridge.m
index f174ff1..2c5be95 100644
--- a/ios/RNMailCompose/RNMailComposeBridge.m
+++ b/ios/RNMailCompose/RNMailComposeBridge.m
@@ -21,4 +21,9 @@ @interface RCT_EXTERN_MODULE(RNMailCompose, NSObject)
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject);
++ (BOOL)requiresMainQueueSetup
+{
+ return NO;
+}
+
@end
diff --git a/js/RNMailCompose.android.js b/js/RNMailCompose.android.js
index 1476c94..de6f4eb 100644
--- a/js/RNMailCompose.android.js
+++ b/js/RNMailCompose.android.js
@@ -6,8 +6,16 @@ const {RNMailCompose} = NativeModules;
export default {
name: RNMailCompose.name,
-
- send(data) {
- return RNMailCompose.send(data);
+ hasMailApp(appName) {
+ return RNMailCompose.hasMailApp(appName)
+ },
+ getMailAppData() {
+ return RNMailCompose.getMailAppData()
},
+ getLastSelection() {
+ return RNMailCompose.getLastSelection()
+ },
+ send(data) {
+ return RNMailCompose.send(data)
+ }
};
diff --git a/js/RNMailCompose.ios.js b/js/RNMailCompose.ios.js
index 774bb3d..5e34557 100644
--- a/js/RNMailCompose.ios.js
+++ b/js/RNMailCompose.ios.js
@@ -6,12 +6,10 @@ const {RNMailCompose} = NativeModules;
export default {
name: RNMailCompose.name,
-
canSendMail() {
return RNMailCompose.canSendMail();
},
-
send(data) {
return RNMailCompose.send(data);
- },
+ }
};
diff --git a/react-native-mail-compose.podspec b/react-native-mail-compose.podspec
index 4d5be68..799ff7b 100644
--- a/react-native-mail-compose.podspec
+++ b/react-native-mail-compose.podspec
@@ -1,13 +1,13 @@
Pod::Spec.new do |s|
- s.name = "react-native-mail-compose"
- s.version = "0.0.3"
- s.summary = "React Native library for composing email. Wraps MFMailComposeViewController for iOS and Intent for Android."
+ s.name = 'react-native-mail-compose'
+ s.version = '0.0.3'
+ s.summary = 'React Native library for composing email. Wraps MFMailComposeViewController for iOS and Intent for Android.'
s.requires_arc = true
s.license = 'MIT'
s.homepage = 'https://github.com/joonhocho/react-native-mail-compose'
- s.author = "Joon Ho Cho"
- s.source = { :git => "https://github.com/joonhocho/react-native-mail-compose.git" }
+ s.author = 'Joon Ho Cho'
+ s.source = { :git => 'https://github.com/joonhocho/react-native-mail-compose.git' }
s.source_files = 'ios/**/*.{h,m,swift}'
- s.platform = :ios, "8.0"
- s.dependency 'React/Core'
+ s.platform = :ios, '8.0'
+ s.dependency 'React'
end