diff options
| author | PliablePixels <pliablepixels@gmail.com> | 2015-06-27 09:52:06 -0400 |
|---|---|---|
| committer | PliablePixels <pliablepixels@gmail.com> | 2015-06-27 09:52:06 -0400 |
| commit | 319d4cb6670729708c19ad50b0146d1bcb7b4719 (patch) | |
| tree | c61e9723a1fd217b1816c987bba66e470e73bf02 /plugins/de.appplant.cordova.plugin.email-composer/src | |
| parent | fdc42fae48db0fef5fbdc9ef51a27d219aea3a72 (diff) | |
Added ability to log key events to file and email (useful for release debugging)
Diffstat (limited to 'plugins/de.appplant.cordova.plugin.email-composer/src')
6 files changed, 1579 insertions, 0 deletions
diff --git a/plugins/de.appplant.cordova.plugin.email-composer/src/android/EmailComposer.java b/plugins/de.appplant.cordova.plugin.email-composer/src/android/EmailComposer.java new file mode 100755 index 00000000..ddda3104 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.email-composer/src/android/EmailComposer.java @@ -0,0 +1,574 @@ +/* + Copyright 2013-2015 appPlant UG + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package de.appplant.cordova.emailcomposer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Build; +import android.text.Html; +import android.util.Base64; +import android.util.Log; + +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CallbackContext; +import org.apache.cordova.PluginResult; + +@SuppressWarnings("Convert2Diamond") +public class EmailComposer extends CordovaPlugin { + + // + static private final String STORAGE_FOLDER = "/email_composer"; + + // The callback context used when calling back into JavaScript + private CallbackContext command; + + /** + * Executes the request. + * + * This method is called from the WebView thread. + * To do a non-trivial amount of work, use: + * cordova.getThreadPool().execute(runnable); + * + * To run on the UI thread, use: + * cordova.getActivity().runOnUiThread(runnable); + * + * @param action The action to execute. + * @param args The exec() arguments in JSON form. + * @param callback The callback context used when calling + * back into JavaScript. + * @return Whether the action was valid. + */ + @Override + public boolean execute (String action, JSONArray args, + CallbackContext callback) throws JSONException { + + this.command = callback; + + if ("open".equals(action)) { + open(args); + + return true; + } + + if ("isAvailable".equals(action)) { + isAvailable(); + + return true; + } + + // Returning false results in a "MethodNotFound" error. + return false; + } + + /** + * Tells if the device has the capability to send emails. + */ + private void isAvailable () { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + Boolean available = isEmailAccountConfigured(); + PluginResult result = new PluginResult(PluginResult.Status.OK, available); + + command.sendPluginResult(result); + } + }); + } + + /** + * Sends an intent to the email app. + * + * @param args + * The email properties like subject or body + * + * @throws JSONException + */ + private void open (JSONArray args) throws JSONException { + JSONObject properties = args.getJSONObject(0); + Intent draft = getDraftWithProperties(properties); + + final Intent chooser = Intent.createChooser(draft, "Open with"); + final EmailComposer plugin = this; + + cordova.getThreadPool().execute(new Runnable() { + public void run() { + cordova.startActivityForResult( + plugin, chooser, 0); + } + }); + } + + /** + * The intent with the containing email properties. + * + * @param params + * The email properties like subject or body + * @return + * The resulting intent + * + * @throws JSONException + */ + private Intent getDraftWithProperties (JSONObject params) throws JSONException { + Intent mail = new Intent(Intent.ACTION_SEND_MULTIPLE); + String app = params.optString("app", null); + + if (params.has("subject")) + setSubject(params.getString("subject"), mail); + if (params.has("body")) + setBody(params.getString("body"), params.optBoolean("isHtml"), mail); + if (params.has("to")) + setRecipients(params.getJSONArray("to"), mail); + if (params.has("cc")) + setCcRecipients(params.getJSONArray("cc"), mail); + if (params.has("bcc")) + setBccRecipients(params.getJSONArray("bcc"), mail); + if (params.has("attachments")) + setAttachments(params.getJSONArray("attachments"), mail); + + if (app != null && isAppInstalled(app)) { + mail.setPackage(app); + } + + mail.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + return mail; + } + + /** + * Setter for the subject. + * + * @param subject + * The subject + * @param draft + * The intent + */ + private void setSubject (String subject, Intent draft) { + draft.putExtra(Intent.EXTRA_SUBJECT, subject); + } + + /** + * Setter for the body. + * + * @param body + * The body + * @param isHTML + * Indicates the encoding + * (HTML or plain text) + * @param draft + * The intent + */ + private void setBody (String body, Boolean isHTML, Intent draft) { + if (isHTML) { + draft.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(body)); + draft.setType("text/html"); + + if (Build.VERSION.SDK_INT > 15) { + draft.putExtra(Intent.EXTRA_HTML_TEXT, body); + } + } else { + draft.putExtra(Intent.EXTRA_TEXT, body); + draft.setType("text/plain"); + } + } + + /** + * Setter for the recipients. + * + * @param recipients + * List of email addresses + * @param draft + * The intent + * + * @throws JSONException + */ + private void setRecipients (JSONArray recipients, Intent draft) throws JSONException { + String[] receivers = new String[recipients.length()]; + + for (int i = 0; i < recipients.length(); i++) { + receivers[i] = recipients.getString(i); + } + + draft.putExtra(Intent.EXTRA_EMAIL, receivers); + } + + /** + * Setter for the cc recipients. + * + * @param recipients + * List of email addresses + * @param draft + * The intent + * + * @throws JSONException + */ + private void setCcRecipients (JSONArray recipients, Intent draft) throws JSONException { + String[] receivers = new String[recipients.length()]; + + for (int i = 0; i < recipients.length(); i++) { + receivers[i] = recipients.getString(i); + } + + draft.putExtra(Intent.EXTRA_CC, receivers); + } + + /** + * Setter for the bcc recipients. + * + * @param recipients + * List of email addresses + * @param draft + * The intent + * + * @throws JSONException + */ + private void setBccRecipients (JSONArray recipients, Intent draft) throws JSONException { + String[] receivers = new String[recipients.length()]; + + for (int i = 0; i < recipients.length(); i++) { + receivers[i] = recipients.getString(i); + } + + draft.putExtra(Intent.EXTRA_BCC, receivers); + } + + /** + * Setter for the attachments. + * + * @param attachments + * List of URIs + * @param draft + * The intent + * + * @throws JSONException + */ + private void setAttachments (JSONArray attachments, Intent draft) throws JSONException { + ArrayList<Uri> uris = new ArrayList<Uri>(); + + for (int i = 0; i < attachments.length(); i++) { + Uri uri = getUriForPath(attachments.getString(i)); + + uris.add(uri); + } + + draft.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + } + + /** + * The URI for an attachment path. + * + * @param path + * The given path to the attachment + * + * @return + * The URI pointing to the given path + */ + private Uri getUriForPath (String path) { + if (path.startsWith("res:")) { + return getUriForResourcePath(path); + } else if (path.startsWith("file:///")) { + return getUriForAbsolutePath(path); + } else if (path.startsWith("file://")) { + return getUriForAssetPath(path); + } else if (path.startsWith("base64:")) { + return getUriForBase64Content(path); + } + + return Uri.parse(path); + } + + /** + * The URI for a file. + * + * @param path + * The given absolute path + * + * @return + * The URI pointing to the given path + */ + private Uri getUriForAbsolutePath (String path) { + String absPath = path.replaceFirst("file://", ""); + File file = new File(absPath); + + if (!file.exists()) { + Log.e("EmailComposer", "File not found: " + file.getAbsolutePath()); + } + + return Uri.fromFile(file); + } + + /** + * The URI for an asset. + * + * @param path + * The given asset path + * + * @return + * The URI pointing to the given path + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + private Uri getUriForAssetPath (String path) { + String resPath = path.replaceFirst("file:/", "www"); + String fileName = resPath.substring(resPath.lastIndexOf('/') + 1); + File dir = cordova.getActivity().getExternalCacheDir(); + + if (dir == null) { + Log.e("EmailComposer", "Missing external cache dir"); + return Uri.EMPTY; + } + + String storage = dir.toString() + STORAGE_FOLDER; + File file = new File(storage, fileName); + + new File(storage).mkdir(); + + try { + AssetManager assets = cordova.getActivity().getAssets(); + + FileOutputStream outStream = new FileOutputStream(file); + InputStream inputStream = assets.open(resPath); + + copyFile(inputStream, outStream); + outStream.flush(); + outStream.close(); + } catch (Exception e) { + Log.e("EmailComposer", "File not found: assets/" + resPath); + e.printStackTrace(); + } + + return Uri.fromFile(file); + } + + /** + * The URI for a resource. + * + * @param path + * The given relative path + * + * @return + * The URI pointing to the given path + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + private Uri getUriForResourcePath (String path) { + String resPath = path.replaceFirst("res://", ""); + String fileName = resPath.substring(resPath.lastIndexOf('/') + 1); + String resName = fileName.substring(0, fileName.lastIndexOf('.')); + String extension = resPath.substring(resPath.lastIndexOf('.')); + File dir = cordova.getActivity().getExternalCacheDir(); + + if (dir == null) { + Log.e("EmailComposer", "Missing external cache dir"); + return Uri.EMPTY; + } + + String storage = dir.toString() + STORAGE_FOLDER; + int resId = getResId(resPath); + File file = new File(storage, resName + extension); + + if (resId == 0) { + Log.e("EmailComposer", "File not found: " + resPath); + } + + new File(storage).mkdir(); + + try { + Resources res = cordova.getActivity().getResources(); + FileOutputStream outStream = new FileOutputStream(file); + InputStream inputStream = res.openRawResource(resId); + + copyFile(inputStream, outStream); + outStream.flush(); + outStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return Uri.fromFile(file); + } + + /** + * The URI for a base64 encoded content. + * + * @param content + * The given base64 encoded content + * + * @return + * The URI including the given content + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + private Uri getUriForBase64Content (String content) { + String resName = content.substring(content.indexOf(":") + 1, content.indexOf("//")); + String resData = content.substring(content.indexOf("//") + 2); + File dir = cordova.getActivity().getExternalCacheDir(); + byte[] bytes; + + try { + bytes = Base64.decode(resData, 0); + } catch (Exception ignored) { + Log.e("EmailComposer", "Invalid Base64 string"); + return Uri.EMPTY; + } + + if (dir == null) { + Log.e("EmailComposer", "Missing external cache dir"); + return Uri.EMPTY; + } + + String storage = dir.toString() + STORAGE_FOLDER; + File file = new File(storage, resName); + + new File(storage).mkdir(); + + try { + FileOutputStream outStream = new FileOutputStream(file); + + outStream.write(bytes); + outStream.flush(); + outStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + + return Uri.fromFile(file); + } + + /** + * Writes an InputStream to an OutputStream + * + * @param in + * The input stream + * @param out + * The output stream + */ + private void copyFile (InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[1024]; + int read; + + while((read = in.read(buffer)) != -1){ + out.write(buffer, 0, read); + } + } + + /** + * @return + * The resource ID for the given resource. + */ + private int getResId (String resPath) { + Resources res = cordova.getActivity().getResources(); + int resId; + + String pkgName = getPackageName(); + String dirName = "drawable"; + String fileName = resPath; + + if (resPath.contains("/")) { + dirName = resPath.substring(0, resPath.lastIndexOf('/')); + fileName = resPath.substring(resPath.lastIndexOf('/') + 1); + } + + String resName = fileName.substring(0, fileName.lastIndexOf('.')); + + resId = res.getIdentifier(resName, dirName, pkgName); + + if (resId == 0) { + resId = res.getIdentifier(resName, "drawable", pkgName); + } + + return resId; + } + + /** + * If email apps are available. + * + * @return + * true if available, otherwise false + */ + private Boolean isEmailAccountConfigured () { + Uri uri = Uri.fromParts("mailto","max@mustermann.com", null); + Intent intent = new Intent(Intent.ACTION_SENDTO, uri); + PackageManager pm = cordova.getActivity().getPackageManager(); + int mailApps = pm.queryIntentActivities(intent, 0).size(); + Boolean available; + + available = mailApps > 0; + + return available; + } + + /** + * Ask the package manager if the app is installed on the device. + * + * @param id + * The app id + * + * @return + * true if yes otherwise false + */ + private boolean isAppInstalled(String id) { + PackageManager pm = cordova.getActivity().getPackageManager(); + + try { + pm.getPackageInfo(id, 0); + return true; + } catch(PackageManager.NameNotFoundException e) { + return false; + } + } + + /** + * The name for the package. + * + * @return + * The package name + */ + private String getPackageName () { + return cordova.getActivity().getPackageName(); + } + + /** + * Called when an activity you launched exits, giving you the reqCode you + * started it with, the resCode it returned, and any additional data from it. + * + * @param reqCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resCode The integer result code returned by the child activity + * through its setResult(). + * @param intent An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + */ + @Override + public void onActivityResult(int reqCode, int resCode, Intent intent) { + command.success(); + } +} diff --git a/plugins/de.appplant.cordova.plugin.email-composer/src/ios/APPEmailComposer.h b/plugins/de.appplant.cordova.plugin.email-composer/src/ios/APPEmailComposer.h new file mode 100644 index 00000000..c7ea09b8 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.email-composer/src/ios/APPEmailComposer.h @@ -0,0 +1,33 @@ +/* + Copyright 2013-2015 appPlant UG + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import <Foundation/Foundation.h> +#import <MessageUI/MFMailComposeViewController.h> +#import <Cordova/CDVPlugin.h> + +@interface APPEmailComposer : CDVPlugin <MFMailComposeViewControllerDelegate> + +// Shows the email composer view with pre-filled data +- (void) open:(CDVInvokedUrlCommand*)command; +// Checks if the mail composer is able to send mails +- (void) isAvailable:(CDVInvokedUrlCommand*)command; + +@end diff --git a/plugins/de.appplant.cordova.plugin.email-composer/src/ios/APPEmailComposer.m b/plugins/de.appplant.cordova.plugin.email-composer/src/ios/APPEmailComposer.m new file mode 100644 index 00000000..52bb66d7 --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.email-composer/src/ios/APPEmailComposer.m @@ -0,0 +1,490 @@ +/* + Copyright 2013-2015 appPlant UG + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +#import "APPEmailComposer.h" +#import "Cordova/NSData+Base64.h" +#import "Cordova/CDVAvailability.h" +#import <MobileCoreServices/MobileCoreServices.h> + +#include "TargetConditionals.h" + +@interface APPEmailComposer () + +@property (nonatomic, retain) CDVInvokedUrlCommand* command; + +@end + +@implementation APPEmailComposer + +#pragma mark - +#pragma mark Plugin interface methods + +/** + * Checks if the mail composer is able to send mails. + * + * @param callbackId + * The ID of the JS function to be called with the result + */ +- (void) isAvailable:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + bool canSendMail = [MFMailComposeViewController canSendMail]; + CDVPluginResult* result; + + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK + messageAsBool:canSendMail]; + + [self.commandDelegate sendPluginResult:result + callbackId:command.callbackId]; + }]; +} + +/** + * Shows the email composer view with pre-filled data. + * + * @param properties + * The email properties like subject, body, attachments + */ +- (void) open:(CDVInvokedUrlCommand*)command +{ + _command = command; + + if (TARGET_IPHONE_SIMULATOR && IsAtLeastiOSVersion(@"8.0")) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Email-Composer Plug-in" + message:@"Plug-in cannot run on the iOS8 Simulator.\nPlease downgrade or use a physical device." + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + [self execCallback]; + return; + } + + [self.commandDelegate runInBackground:^{ + NSArray* args = command.arguments; + NSDictionary* properties = [args objectAtIndex:0]; + MFMailComposeViewController* draft; + + draft = [self getDraftWithProperties:properties]; + + if (!draft) { + [self execCallback]; + return; + } + + [self openDraft:draft]; + }]; +} + +#pragma mark - +#pragma mark MFMailComposeViewControllerDelegate methods + +/** + * 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 +{ + [controller dismissViewControllerAnimated:YES completion:nil]; + + [self execCallback]; +} + +#pragma mark - +#pragma mark Plugin core methods + +/** + * Instantiates an email composer view. + * + * @param properties + * The email properties like subject, body, attachments + * + * @return + * The configured email composer view + */ +- (MFMailComposeViewController*) getDraftWithProperties:(NSDictionary*)properties +{ + // Falls das Gerät kein Email Interface unterstützt + if (![MFMailComposeViewController canSendMail]) { + return NULL; + } + + BOOL isHTML = [[properties objectForKey:@"isHtml"] boolValue]; + + MFMailComposeViewController* draft; + + draft = [[MFMailComposeViewController alloc] init]; + + // Subject + [self setSubject:[properties objectForKey:@"subject"] ofDraft:draft]; + // Body (as HTML) + [self setBody:[properties objectForKey:@"body"] ofDraft:draft isHTML:isHTML]; + // Recipients + [self setToRecipients:[properties objectForKey:@"to"] ofDraft:draft]; + // CC Recipients + [self setCcRecipients:[properties objectForKey:@"cc"] ofDraft:draft]; + // BCC Recipients + [self setBccRecipients:[properties objectForKey:@"bcc"] ofDraft:draft]; + // Attachments + [self setAttachments:[properties objectForKey:@"attachments"] ofDraft:draft]; + + draft.mailComposeDelegate = self; + + return draft; +} + +/** + * Displays the email draft. + * + * @param draft + * The email composer view + */ +- (void) openDraft:(MFMailComposeViewController*)draft +{ + [self.viewController presentViewController:draft + animated:YES + completion:NULL]; +} + +/** + * Sets the subject of the email draft. + * + * @param subject + * The subject of the email + * @param draft + * The email composer view + */ +- (void) setSubject:(NSString*)subject + ofDraft:(MFMailComposeViewController*)draft +{ + [draft setSubject:subject]; +} + +/** + * Sets the body of the email draft. + * + * @param body + * The body of the email + * @param isHTML + * Indicates if the body is an HTML encoded string + * @param draft + * The email composer view + */ +- (void) setBody:(NSString*)body ofDraft:(MFMailComposeViewController*)draft + isHTML:(BOOL)isHTML +{ + [draft setMessageBody:body isHTML:isHTML]; +} + +/** + * Sets the recipients of the email draft. + * + * @param recipients + * The recipients of the email + * @param draft + * The email composer view + */ +- (void) setToRecipients:(NSArray*)recipients + ofDraft:(MFMailComposeViewController*)draft +{ + [draft setToRecipients:recipients]; +} + +/** + * Sets the CC recipients of the email draft. + * + * @param ccRecipients + * The CC recipients of the email + * @param draft + * The email composer view + */ +- (void) setCcRecipients:(NSArray*)ccRecipients + ofDraft:(MFMailComposeViewController*)draft +{ + [draft setCcRecipients:ccRecipients]; +} + +/** + * Sets the BCC recipients of the email draft. + * + * @param bccRecipients + * The BCC recipients of the email + * @param draft + * The email composer view + */ +- (void) setBccRecipients:(NSArray*)bccRecipients + ofDraft:(MFMailComposeViewController*)draft +{ + [draft setBccRecipients:bccRecipients]; +} + +/** + * Sets the attachments of the email draft. + * + * @param attachments + * The attachments of the email + * @param draft + * The email composer view + */ +- (void) setAttachments:(NSArray*)attatchments + ofDraft:(MFMailComposeViewController*)draft +{ + if (attatchments) + { + for (NSString* path in attatchments) + { + NSData* data = [self getDataForAttachmentPath:path]; + + NSString* basename = [self getBasenameFromAttachmentPath:path]; + NSString* pathExt = [basename pathExtension]; + NSString* fileName = [basename pathComponents].lastObject; + NSString* mimeType = [self getMimeTypeFromFileExtension:pathExt]; + + // Couldn't find mimeType, must be some type of binary data + if (mimeType == nil) mimeType = @"application/octet-stream"; + + [draft addAttachmentData:data mimeType:mimeType fileName:fileName]; + } + } +} + +/** + * Returns the data for a given (relative) attachment path. + * + * @param path + * An absolute/relative path or the base64 data + * + * @return + * The data for the attachment + */ +- (NSData*) getDataForAttachmentPath:(NSString*)path +{ + if ([path hasPrefix:@"file:///"]) + { + return [self dataForAbsolutePath:path]; + } + else if ([path hasPrefix:@"res:"]) + { + return [self dataForResource:path]; + } + else if ([path hasPrefix:@"file://"]) + { + return [self dataForAsset:path]; + } + else if ([path hasPrefix:@"base64:"]) + { + return [self dataFromBase64:path]; + } + + NSFileManager* fileManager = [NSFileManager defaultManager]; + + if (![fileManager fileExistsAtPath:path]){ + NSLog(@"File not found: %@", path); + } + + return [fileManager contentsAtPath:path]; +} + +/** + * Retrieves the data for an absolute attachment path. + * + * @param path + * An absolute file path + * + * @return + * The data for the attachment + */ +- (NSData*) dataForAbsolutePath:(NSString*)path +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSString* absPath; + + absPath = [path stringByReplacingOccurrencesOfString:@"file://" + withString:@""]; + + if (![fileManager fileExistsAtPath:absPath]){ + NSLog(@"File not found: %@", absPath); + } + + NSData* data = [fileManager contentsAtPath:absPath]; + + return data; +} + +/** + * Retrieves the data for a resource path. + * + * @param path + * A relative file path + * + * @return + * The data for the attachment + */ +- (NSData*) dataForResource:(NSString*)path +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSString* absPath; + + NSBundle* mainBundle = [NSBundle mainBundle]; + NSString* bundlePath = [[mainBundle bundlePath] + stringByAppendingString:@"/"]; + + absPath = [path pathComponents].lastObject; + + absPath = [bundlePath stringByAppendingString:absPath]; + + if (![fileManager fileExistsAtPath:absPath]){ + NSLog(@"File not found: %@", absPath); + } + + NSData* data = [fileManager contentsAtPath:absPath]; + + return data; +} + +/** + * Retrieves the data for a asset path. + * + * @param path + * A relative www file path + * + * @return + * The data for the attachment + */ +- (NSData*) dataForAsset:(NSString*)path +{ + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSString* absPath; + + NSBundle* mainBundle = [NSBundle mainBundle]; + NSString* bundlePath = [[mainBundle bundlePath] + stringByAppendingString:@"/"]; + + absPath = [path stringByReplacingOccurrencesOfString:@"file:/" + withString:@"www"]; + + absPath = [bundlePath stringByAppendingString:absPath]; + + if (![fileManager fileExistsAtPath:absPath]){ + NSLog(@"File not found: %@", absPath); + } + + NSData* data = [fileManager contentsAtPath:absPath]; + + return data; +} + +/** + * Retrieves the data for a base64 encoded string. + * + * @param base64String + * Base64 encoded string + * + * @return + * The data for the attachment + */ +- (NSData*) dataFromBase64:(NSString*)base64String +{ + NSUInteger length = [base64String length]; + NSRegularExpression *regex; + NSString *dataString; + + regex = [NSRegularExpression regularExpressionWithPattern:@"^base64:[^/]+.." + options:NSRegularExpressionCaseInsensitive + error:Nil]; + + dataString = [regex stringByReplacingMatchesInString:base64String + options:0 + range:NSMakeRange(0, length) + withTemplate:@""]; + + NSData* data = [NSData dataFromBase64String:dataString]; + + return data; +} + +#pragma mark - +#pragma mark Plugin helper methods + +/** + * Retrieves the mime type from the file extension. + * + * @param extension + * The file's extension + * + * @return + * The coresponding MIME type + */ +- (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 + return (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType)); +} + +/** + * Retrieves the attachments basename. + * + * @param path + * The file path or bas64 data of the attachment + * + * @return + * The attachments basename + */ +- (NSString*) getBasenameFromAttachmentPath:(NSString*)path +{ + if ([path hasPrefix:@"base64:"]) + { + NSString* pathWithoutPrefix; + + pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:" + withString:@""]; + + return [pathWithoutPrefix substringToIndex: + [pathWithoutPrefix rangeOfString:@"//"].location]; + } + + return path; + +} + +/** + * Invokes the callback without any parameter. + */ +- (void) execCallback +{ + CDVPluginResult *result = [CDVPluginResult + resultWithStatus:CDVCommandStatus_OK]; + + [self.commandDelegate sendPluginResult:result + callbackId:_command.callbackId]; +} + +@end diff --git a/plugins/de.appplant.cordova.plugin.email-composer/src/windows/EmailComposerProxy.js b/plugins/de.appplant.cordova.plugin.email-composer/src/windows/EmailComposerProxy.js new file mode 100644 index 00000000..382eb79a --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.email-composer/src/windows/EmailComposerProxy.js @@ -0,0 +1,273 @@ +/* + Copyright 2013-2015 appPlant UG + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +/** + * Verifies if sending emails is supported on the device. + * + * @param {Function} success + * Success callback function + * @param {Function} error + * Error callback function + * @param {Array} args + * Interface arguments + */ +exports.isAvailable = function (success, error, args) { + success(true); +}; + +/** + * Displays the email composer pre-filled with data. + * + * @param {Function} success + * Success callback function + * @param {Function} error + * Error callback function + * @param {Array} args + * Interface arguments + */ +exports.open = function (success, error, args) { + var props = args[0], + email = exports.getDraftWithProperties(props); + + Windows.ApplicationModel.Email.EmailManager + .showComposeNewEmailAsync(email) + .done(success()); +}; + +/** + * The Email with the containing properties. + * + * @param {Object} props + * The email properties like subject or body + * @return {Windows.ApplicationModel.Email.EmailMessage} + * The resulting email draft + */ +exports.getDraftWithProperties = function (props) { + var mail = new Windows.ApplicationModel.Email.EmailMessage(); + + // subject + exports.setSubject(props.subject, mail); + // body + exports.setBody(props.body, props.isHtml, mail); + // To recipients + exports.setRecipients(props.to, mail); + // CC recipients + exports.setCcRecipients(props.cc, mail); + // BCC recipients + exports.setBccRecipients(props.bcc, mail); + // attachments + exports.setAttachments(props.attachments, mail); + + return mail; +}; + +/** + * Setter for the subject. + * + * @param {String} subject + * The subject + * @param {Windows.ApplicationModel.Email.EmailMessage} draft + * The draft + */ +exports.setSubject = function (subject, draft) { + draft.subject = subject; +}; + +/** + * Setter for the body. + * + * @param {String} body + * The body + * @param isHTML + * Indicates the encoding + * (HTML or plain text) + * @param {Windows.ApplicationModel.Email.EmailMessage} draft + * The draft + */ +exports.setBody = function (body, isHTML, draft) { + draft.body = body; +}; + +/** + * Setter for the recipients. + * + * @param {String[]} recipients + * List of mail addresses + * @param {Windows.ApplicationModel.Email.EmailMessage} draft + * The draft + */ +exports.setRecipients = function (recipients, draft) { + recipients.forEach(function (address) { + draft.to.push( + new Windows.ApplicationModel.Email.EmailRecipient(address)); + }); +}; + +/** + * Setter for the cc recipients. + * + * @param {String[]} recipients + * List of mail addresses + * @param {Windows.ApplicationModel.Email.EmailMessage} draft + * The draft + */ +exports.setCcRecipients = function (recipients, draft) { + recipients.forEach(function (address) { + draft.cc.push( + new Windows.ApplicationModel.Email.EmailRecipient(address)); + }); +}; + +/** + * Setter for the bcc recipients. + * + * @param {String[]} recipients + * List of mail addresses + * @param {Windows.ApplicationModel.Email.EmailMessage} draft + * The draft + */ +exports.setBccRecipients = function (recipients, draft) { + recipients.forEach(function (address) { + draft.bcc.push( + new Windows.ApplicationModel.Email.EmailRecipient(address)); + }); +}; + +/** + * Setter for the attachments. + * + * @param {String[]} attachments + * List of URIs + * @param {Windows.ApplicationModel.Email.EmailMessage} draft + * The draft + */ +exports.setAttachments = function (attachments, draft) { + attachments.forEach(function (path) { + var uri = exports.getUriForPath(path), + name = uri.path.split('/').reverse()[0], + stream = Windows.Storage.Streams.RandomAccessStreamReference + .createFromUri(uri); + + draft.attachments.push( + new Windows.ApplicationModel.Email. + EmailAttachment(name, stream) + ); + }); +}; + +/** + * The URI for an attachment path. + * + * @param {String} path + * The given path to the attachment + * + * @return + * The URI pointing to the given path + */ +exports.getUriForPath = function (path) { + if (path.match(/^res:/)) { + return exports.getUriForResourcePath(path); + } else if (path.match(/^file:\/{3}/)) { + return exports.getUriForAbsolutePath(path); + } else if (path.match(/^file:/)) { + return exports.getUriForAssetPath(path); + } else if (path.match(/^base64:/)) { + return exports.getUriForBase64Content(path); + } + + return new Windows.Foundation.Uri(path); +}; + +/** + * The URI for a file. + * + * @param {String} path + * The given absolute path + * + * @return + * The URI pointing to the given path + */ +exports.getUriForAbsolutePath = function (path) { + return new Windows.Foundation.Uri(path); +}; + +/** + * The URI for an asset. + * + * @param {String} path + * The given asset path + * + * @return + * The URI pointing to the given path + */ +exports.getUriForAssetPath = function (path) { + var host = document.location.host, + protocol = document.location.protocol, + resPath = path.replace('file:/', '/www'), + rawUri = protocol + '//' + host + resPath; + + return new Windows.Foundation.Uri(rawUri); +}; + +/** + * The URI for a resource. + * + * @param {String} path + * The given relative path + * + * @return + * The URI pointing to the given path + */ +exports.getUriForResourcePath = function (path) { + var host = document.location.host, + protocol = document.location.protocol, + resPath = path.replace('res:/', '/images'), + rawUri = protocol + '//' + host + resPath; + + return new Windows.Foundation.Uri(rawUri); +}; + +/** + * The URI for a base64 encoded content. + * + * @param {String} content + * The given base64 encoded content + * + * @return + * The URI including the given content + */ +exports.getUriForBase64Content = function (content) { + var match = content.match(/^base64:([^\/]+)\/\/(.*)/), + base64 = match[2], + name = match[1], + buffer = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(base64), + rwplus = Windows.Storage.CreationCollisionOption.openIfExists, + folder = Windows.Storage.ApplicationData.current.temporaryFolder, + uri = new Windows.Foundation.Uri('ms-appdata:///temp/' + name); + + folder.createFileAsync(name, rwplus).done(function (file) { + Windows.Storage.FileIO.writeBufferAsync(file, buffer); + }); + + return uri; +}; + +require('cordova/exec/proxy').add('EmailComposer', exports); diff --git a/plugins/de.appplant.cordova.plugin.email-composer/src/wp8/EmailComposer.cs b/plugins/de.appplant.cordova.plugin.email-composer/src/wp8/EmailComposer.cs new file mode 100644 index 00000000..db0d613e --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.email-composer/src/wp8/EmailComposer.cs @@ -0,0 +1,133 @@ +/* + Copyright 2013-2015 appPlant UG + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +using De.APPPlant.Cordova.Plugin.EmailComposer; +using Microsoft.Phone.Tasks; +using System; +using System.Linq; +using WPCordovaClassLib.Cordova; +using WPCordovaClassLib.Cordova.Commands; +using WPCordovaClassLib.Cordova.JSON; + +namespace Cordova.Extension.Commands +{ + /// <summary> + /// Implementes access to email composer task + /// http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394003(v=vs.105).aspx + /// </summary> + public class EmailComposer : BaseCommand + { + /// <summary> + /// Überprüft, ob Emails versendet werden können. + /// </summary> + public void isAvailable(string jsonArgs) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true)); + } + + /// <summary> + /// Öffnet den Email-Kontroller mit vorausgefüllten Daten. + /// </summary> + public void open(string jsonArgs) + { + string[] args = JsonHelper.Deserialize<string[]>(jsonArgs); + Options options = JsonHelper.Deserialize<Options>(args[0]); + EmailComposeTask draft = GetDraftWithProperties(options); + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true)); + + OpenDraft(draft); + } + + /// </summary> + /// Erstellt den Email-Composer und fügt die übergebenen Eigenschaften ein. + /// </summary> + private EmailComposeTask GetDraftWithProperties(Options options) + { + EmailComposeTask draft = new EmailComposeTask(); + + SetSubject(options.Subject, draft); + SetBody(options.Body, options.IsHtml, draft); + SetTo(options.To, draft); + SetCc(options.Cc, draft); + SetBcc(options.Bcc, draft); + SetAttachments(options.Attachments, draft); + + return draft; + } + + /// </summary> + /// Zeigt den ViewController zum Versenden/Bearbeiten der Mail an. + /// </summary> + private void OpenDraft(EmailComposeTask draft) + { + draft.Show(); + } + + /// </summary> + /// Setzt den Subject der Mail. + /// </summary> + private void SetSubject(string subject, EmailComposeTask draft) + { + draft.Subject = subject; + } + + /// </summary> + /// Setzt den Body der Mail. + /// </summary> + private void SetBody(string body, Boolean isHTML, EmailComposeTask draft) + { + draft.Body = body; + } + + /// </summary> + /// Setzt die Empfänger der Mail. + /// </summary> + private void SetTo(string[] recipients, EmailComposeTask draft) + { + draft.To = string.Join(",", recipients); + } + + /// </summary> + /// Setzt die CC-Empfänger der Mail. + /// </summary> + private void SetCc(string[] recipients, EmailComposeTask draft) + { + draft.Cc = string.Join(",", recipients); + } + + /// </summary> + /// Setzt die BCC-Empfänger der Mail. + /// </summary> + private void SetBcc(string[] recipients, EmailComposeTask draft) + { + draft.Bcc = string.Join(",", recipients); + } + + /// </summary> + /// Fügt die Anhände zur Mail hinzu. + /// </summary> + private void SetAttachments(string[] attachments, EmailComposeTask draft) + { + // Not supported on WP8.0 and WP8.1 Silverlight + } + } +} diff --git a/plugins/de.appplant.cordova.plugin.email-composer/src/wp8/Options.cs b/plugins/de.appplant.cordova.plugin.email-composer/src/wp8/Options.cs new file mode 100644 index 00000000..b7c7206e --- /dev/null +++ b/plugins/de.appplant.cordova.plugin.email-composer/src/wp8/Options.cs @@ -0,0 +1,76 @@ +/* + Copyright 2013-2015 appPlant UG + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +using System; +using System.Linq; +using System.Runtime.Serialization; + +namespace De.APPPlant.Cordova.Plugin.EmailComposer +{ + /// <summary> + /// Represents email composer task options + /// </summary> + [DataContract] + class Options + { + /// <summary> + /// Represents the subject of the email + /// </summary> + [DataMember(IsRequired = false, Name = "subject")] + public string Subject { get; set; } + + /// <summary> + /// Represents the email body (could be HTML code, in this case set isHtml to true) + /// </summary> + [DataMember(IsRequired = false, Name = "body")] + public string Body { get; set; } + + /// <summary> + /// Indicats if the body is HTML or plain text + /// </summary> + [DataMember(IsRequired = false, Name = "isHtml")] + public bool IsHtml { get; set; } + + /// <summary> + /// Contains all the email addresses for TO field + /// </summary> + [DataMember(IsRequired = false, Name = "to")] + public string[] To { get; set; } + + /// <summary> + /// Contains all the email addresses for CC field + /// </summary> + [DataMember(IsRequired = false, Name = "cc")] + public string[] Cc { get; set; } + + /// <summary> + /// Contains all the email addresses for BCC field + /// </summary> + [DataMember(IsRequired = false, Name = "bcc")] + public string[] Bcc { get; set; } + + /// <summary> + /// Contains all full paths to the files you want to attach + /// </summary> + [DataMember(IsRequired = false, Name = "attachments")] + public string[] Attachments { get; set; } + } +} |
