diff --git a/README.md b/README.md index 3bf1f4c..819cc10 100644 --- a/README.md +++ b/README.md @@ -124,12 +124,20 @@ export default class App extends Component { ccRecipients: ['supportCC@example.com'], bccRecipients: ['supportBCC@example.com'], body: 'A Bold Body', + customChooserTitle: 'This is my new title', // Android only (defaults to "Send Mail") isHTML: true, - attachment: { - path: '', // The absolute path of the file from which to read data. - type: '', // Mime Type: jpg, png, doc, ppt, html, pdf, csv - name: '', // Optional: Custom filename for attachment - } + attachments: [{ + // Specify either `path` or `uri` to indicate where to find the file data. + // The API used to create or locate the file will usually indicate which it returns. + // An absolute path will look like: /cacheDir/photos/some image.jpg + // A URI starts with a protocol and looks like: content://appname/cacheDir/photos/some%20image.jpg + path: '', // The absolute path of the file from which to read data. + uri: '', // The uri of the file from which to read the data. + // Specify either `type` or `mimeType` to indicate the type of data. + type: '', // Mime Type: jpg, png, doc, ppt, html, pdf, csv + mimeType: '', // - use only if you want to use custom type + name: '', // Optional: Custom filename for attachment + }] }, (error, event) => { Alert.alert( error, diff --git a/RNMail/RNMail.m b/RNMail/RNMail.m index 7e97c42..7c77eac 100644 --- a/RNMail/RNMail.m +++ b/RNMail/RNMail.m @@ -36,108 +36,121 @@ RCT_EXPORT_METHOD(mail:(NSDictionary *)options MFMailComposeViewController *mail = [[MFMailComposeViewController alloc] init]; mail.mailComposeDelegate = self; _callbacks[RCTKeyForInstance(mail)] = callback; - + if (options[@"subject"]){ NSString *subject = [RCTConvert NSString:options[@"subject"]]; [mail setSubject:subject]; } - - bool *isHTML = NO; - + + BOOL isHTML = NO; + if (options[@"isHTML"]){ isHTML = [options[@"isHTML"] boolValue]; } - + if (options[@"body"]){ NSString *body = [RCTConvert NSString:options[@"body"]]; [mail setMessageBody:body isHTML:isHTML]; } - + if (options[@"recipients"]){ NSArray *recipients = [RCTConvert NSArray:options[@"recipients"]]; [mail setToRecipients:recipients]; } - + if (options[@"ccRecipients"]){ NSArray *ccRecipients = [RCTConvert NSArray:options[@"ccRecipients"]]; [mail setCcRecipients:ccRecipients]; } - + if (options[@"bccRecipients"]){ NSArray *bccRecipients = [RCTConvert NSArray:options[@"bccRecipients"]]; [mail setBccRecipients:bccRecipients]; } - - if (options[@"attachment"] && options[@"attachment"][@"path"] && options[@"attachment"][@"type"]){ - NSString *attachmentPath = [RCTConvert NSString:options[@"attachment"][@"path"]]; - NSString *attachmentType = [RCTConvert NSString:options[@"attachment"][@"type"]]; - NSString *attachmentName = [RCTConvert NSString:options[@"attachment"][@"name"]]; - - // Set default filename if not specificed - if (!attachmentName) { - attachmentName = [[attachmentPath lastPathComponent] stringByDeletingPathExtension]; + if (options[@"attachments"]) { + NSArray *attachments = [RCTConvert NSArray:options[@"attachments"]]; + for (NSDictionary *attachment in attachments) { + if ((attachment[@"path"] || attachment[@"uri"]) && (attachment[@"type"] || attachment[@"mimeType"])) { + NSString *attachmentPath = [RCTConvert NSString:attachment[@"path"]]; + NSString *attachmentUri = [RCTConvert NSString:attachment[@"uri"]]; + NSString *attachmentType = [RCTConvert NSString:attachment[@"type"]]; + NSString *attachmentName = [RCTConvert NSString:attachment[@"name"]]; + NSString *attachmentMimeType = [RCTConvert NSString:attachment[@"mimeType"]]; + + // Set default filename if not specificed + if (!attachmentName) { + attachmentName = [[attachmentPath lastPathComponent] stringByDeletingPathExtension]; + } + + NSData *fileData; + if (attachmentPath) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath:attachmentPath]){ + callback(@[[NSString stringWithFormat: @"attachment file with path '%@' does not exist", attachmentPath]]); + return; + } + // Get the resource path and read the file using NSData + fileData = [NSData dataWithContentsOfFile:attachmentPath]; + } else if (attachmentUri) { + // Get the URI and read it using NSData + NSURL *attachmentURL = [[NSURLComponents componentsWithString:attachmentUri] URL]; + NSError *error = nil; + fileData = [NSData dataWithContentsOfURL:attachmentURL options:0 error:&error]; + if (!fileData) { + callback(@[[NSString stringWithFormat: @"attachment file with uri '%@' does not exist", attachmentUri]]); + return; + } + } + + // Determine the MIME type + NSString *mimeType; + if (attachmentType) { + /* + * Add additional mime types and PR if necessary. Find the list + * of supported formats at http://www.iana.org/assignments/media-types/media-types.xhtml + */ + NSDictionary *supportedMimeTypes = @{ + @"jpeg" : @"image/jpeg", + @"jpg" : @"image/jpeg", + @"png" : @"image/png", + @"doc" : @"application/msword", + @"docx" : @"application/vnd.openxmlformats-officedocument.wordprocessingml.document", + @"ppt" : @"application/vnd.ms-powerpoint", + @"pptx" : @"application/vnd.openxmlformats-officedocument.presentationml.presentation", + @"html" : @"text/html", + @"csv" : @"text/csv", + @"pdf" : @"application/pdf", + @"vcard" : @"text/vcard", + @"json" : @"application/json", + @"zip" : @"application/zip", + @"text" : @"text/*", + @"mp3" : @"audio/mpeg", + @"wav" : @"audio/wav", + @"aiff" : @"audio/aiff", + @"flac" : @"audio/flac", + @"ogg" : @"audio/ogg", + @"xls" : @"application/vnd.ms-excel", + @"ics" : @"text/calendar", + @"xlsx" : @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + }; + if([supportedMimeTypes objectForKey:attachmentType]) { + mimeType = [supportedMimeTypes objectForKey:attachmentType]; + } else { + callback(@[[NSString stringWithFormat: @"Mime type '%@' for attachment is not handled", attachmentType]]); + return; + } + } else if (attachmentMimeType) { + mimeType = attachmentMimeType; + } + + // Add attachment + [mail addAttachmentData:fileData mimeType:mimeType fileName:attachmentName]; + } } - - // Get the resource path and read the file using NSData - NSData *fileData = [NSData dataWithContentsOfFile:attachmentPath]; - - // Determine the MIME type - NSString *mimeType; - - /* - * Add additional mime types and PR if necessary. Find the list - * of supported formats at http://www.iana.org/assignments/media-types/media-types.xhtml - */ - if ([attachmentType isEqualToString:@"jpg"]) { - mimeType = @"image/jpeg"; - } else if ([attachmentType isEqualToString:@"png"]) { - mimeType = @"image/png"; - } else if ([attachmentType isEqualToString:@"doc"]) { - mimeType = @"application/msword"; - } else if ([attachmentType isEqualToString:@"docx"]) { - mimeType = @"application/vnd.openxmlformats-officedocument.wordprocessingml.document"; - } else if ([attachmentType isEqualToString:@"ppt"]) { - mimeType = @"application/vnd.ms-powerpoint"; - } else if ([attachmentType isEqualToString:@"pptx"]) { - mimeType = @"application/vnd.openxmlformats-officedocument.presentationml.presentation"; - } else if ([attachmentType isEqualToString:@"html"]) { - mimeType = @"text/html"; - } else if ([attachmentType isEqualToString:@"csv"]) { - mimeType = @"text/csv"; - } else if ([attachmentType isEqualToString:@"pdf"]) { - mimeType = @"application/pdf"; - } else if ([attachmentType isEqualToString:@"vcard"]) { - mimeType = @"text/vcard"; - } else if ([attachmentType isEqualToString:@"json"]) { - mimeType = @"application/json"; - } else if ([attachmentType isEqualToString:@"zip"]) { - mimeType = @"application/zip"; - } else if ([attachmentType isEqualToString:@"text"]) { - mimeType = @"text/*"; - } else if ([attachmentType isEqualToString:@"mp3"]) { - mimeType = @"audio/mpeg"; - } else if ([attachmentType isEqualToString:@"wav"]) { - mimeType = @"audio/wav"; - } else if ([attachmentType isEqualToString:@"aiff"]) { - mimeType = @"audio/aiff"; - } else if ([attachmentType isEqualToString:@"flac"]) { - mimeType = @"audio/flac"; - } else if ([attachmentType isEqualToString:@"ogg"]) { - mimeType = @"audio/ogg"; - } else if ([attachmentType isEqualToString:@"xls"]) { - mimeType = @"application/vnd.ms-excel"; - } else if ([attachmentType isEqualToString:@"ics"]) { - mimeType = @"text/calendar"; - } else if ([attachmentType isEqualToString:@"xlsx"]) { - mimeType = @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - } - - // Add attachment - [mail addAttachmentData:fileData mimeType:mimeType fileName:attachmentName]; } - + UIViewController *root = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - + while (root.presentedViewController) { root = root.presentedViewController; } diff --git a/android/build.gradle b/android/build.gradle index 9305052..df12459 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -9,7 +9,7 @@ android { buildToolsVersion project.hasProperty('buildToolsVersion') ? project.buildToolsVersion : "23.0.1" defaultConfig { - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion project.hasProperty('targetSdkVersion') ? project.targetSdkVersion : 22 versionCode 1 versionName "1.0" diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 914eec5..7a86d24 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,4 +1,20 @@ - - \ No newline at end of file + package="com.chirag.RNMail"> + + + + + + + + + + + + diff --git a/android/src/main/java/com/chirag/RNMail/RNMailFileProvider.java b/android/src/main/java/com/chirag/RNMail/RNMailFileProvider.java new file mode 100644 index 0000000..45cf23e --- /dev/null +++ b/android/src/main/java/com/chirag/RNMail/RNMailFileProvider.java @@ -0,0 +1,7 @@ +package com.chirag.RNMail; + +import androidx.core.content.FileProvider; + +public class RNMailFileProvider extends FileProvider { + +} diff --git a/android/src/main/java/com/chirag/RNMail/RNMailModule.java b/android/src/main/java/com/chirag/RNMail/RNMailModule.java index a0cb478..e93973e 100644 --- a/android/src/main/java/com/chirag/RNMail/RNMailModule.java +++ b/android/src/main/java/com/chirag/RNMail/RNMailModule.java @@ -5,6 +5,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.text.Html; +import androidx.core.content.FileProvider; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -15,10 +16,8 @@ import com.facebook.react.bridge.Callback; import java.util.List; import java.io.File; - -import android.app.Activity; -import android.support.v4.content.FileProvider; -import android.os.Build; +import java.net.URI; +import java.util.ArrayList; /** * NativeModule that allows JS to open emails sending apps chooser. @@ -55,37 +54,11 @@ public class RNMailModule extends ReactContextBaseJavaModule { return strArray; } - private Intent getIntent() { - Intent i; - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - // https://stackoverflow.com/a/42856167 - Intent emailSelectorIntent = new Intent(Intent.ACTION_SENDTO); - emailSelectorIntent.setData(Uri.parse("mailto:")); - - i = new Intent(Intent.ACTION_SEND); - i.setData(Uri.parse("mailto:")); - i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - i.setSelector( emailSelectorIntent ); - } else { - i = new Intent(Intent.ACTION_SEND); - i.setPackage("com.google.android.gm"); - } - - return i; - } - - private Uri getFileUri(String path) { - File file = new File(path); - file.setReadable(true, false); - final String providerName = reactContext.getPackageName() + ".provider"; - final Activity activity = getCurrentActivity(); - return FileProvider.getUriForFile(activity, providerName, file); - } - @ReactMethod public void mail(ReadableMap options, Callback callback) { - Intent i = getIntent(); + Intent i = new Intent(Intent.ACTION_SEND_MULTIPLE); + Intent selectorIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")); + i.setSelector(selectorIntent); if (options.hasKey("subject") && !options.isNull("subject")) { i.putExtra(Intent.EXTRA_SUBJECT, options.getString("subject")); @@ -115,13 +88,40 @@ public class RNMailModule extends ReactContextBaseJavaModule { i.putExtra(Intent.EXTRA_BCC, readableArrayToStringArray(bccRecipients)); } - if (options.hasKey("attachment") && !options.isNull("attachment")) { - ReadableMap attachment = options.getMap("attachment"); - if (attachment.hasKey("path") && !attachment.isNull("path")) { - String path = attachment.getString("path"); - final Uri p = getFileUri(path); - i.putExtra(Intent.EXTRA_STREAM, p); + if (options.hasKey("attachments") && !options.isNull("attachments")) { + ReadableArray r = options.getArray("attachments"); + int length = r.size(); + + String provider = reactContext.getApplicationContext().getPackageName() + ".rnmail.provider"; + List resolvedIntentActivities = reactContext.getPackageManager().queryIntentActivities(i, + PackageManager.MATCH_DEFAULT_ONLY); + + ArrayList uris = new ArrayList(); + for (int keyIndex = 0; keyIndex < length; keyIndex++) { + ReadableMap clip = r.getMap(keyIndex); + Uri uri; + if (clip.hasKey("path") && !clip.isNull("path")) { + String path = clip.getString("path"); + File file = new File(path); + uri = FileProvider.getUriForFile(reactContext, provider, file); + } else if (clip.hasKey("uri") && !clip.isNull("uri")) { + String uriPath = clip.getString("uri"); + uri = Uri.parse(uriPath); + } else { + callback.invoke("not_found"); + return; + } + uris.add(uri); + + for (ResolveInfo resolvedIntentInfo : resolvedIntentActivities) { + String packageName = resolvedIntentInfo.activityInfo.packageName; + reactContext.grantUriPermission(packageName, uri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); + } } + + i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + i.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); } PackageManager manager = reactContext.getPackageManager(); @@ -140,7 +140,13 @@ public class RNMailModule extends ReactContextBaseJavaModule { callback.invoke("error"); } } else { - Intent chooser = Intent.createChooser(i, "Send Mail"); + String chooserTitle = "Send Mail"; + + if (options.hasKey("customChooserTitle") && !options.isNull("customChooserTitle")) { + chooserTitle = options.getString("customChooserTitle"); + } + + Intent chooser = Intent.createChooser(i, chooserTitle); chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { diff --git a/android/src/main/res/xml/provider_paths.xml b/android/src/main/res/xml/provider_paths.xml new file mode 100644 index 0000000..97384e1 --- /dev/null +++ b/android/src/main/res/xml/provider_paths.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..765abbc --- /dev/null +++ b/index.d.ts @@ -0,0 +1,23 @@ +export namespace Mailer { + function mail(options: { + subject?: string; + recipients?: string[]; + ccRecipients?: string[]; + bccRecipients?: string[]; + body?: string; + customChooserTitle?: string; + isHTML?: boolean; + attachments?: { + path?: string; // Specify either 'path' or 'uri' + uri?: string; + type?: string; // Specify either 'type' or 'mimeType' + mimeType?: string; + name?: string; + }[] + }, callback: ( + error: string, + event?: string + ) => void): void; +} + +export default Mailer; diff --git a/package-lock.json b/package-lock.json index df4c0f3..9157b08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { "name": "react-native-mail", - "version": "3.0.7", + "version": "6.1.1", "lockfileVersion": 1 } diff --git a/package.json b/package.json index 6ce0769..e2d84df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-mail", - "version": "3.0.7", + "version": "6.1.1", "description": "A wrapper on top of MFMailComposeViewController from iOS and Mail Intent on android", "author": { "name": "Chirag Jain", diff --git a/react-native-mail.podspec b/react-native-mail.podspec index e31b682..41d4683 100644 --- a/react-native-mail.podspec +++ b/react-native-mail.podspec @@ -13,6 +13,6 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/chirag04/react-native-mail", :tag => "v#{s.version}" } s.source_files = 'RNMail/*.{h,m}' - s.dependency 'React' + s.dependency 'React-Core' end