openEventsPlatformApp/plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.m
plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.mRaw
1#import "SocialSharing.h"
2#import "NSString+URLEncoding.h"
3#import <Cordova/CDV.h>
4#import <Social/Social.h>
5#import <Foundation/NSException.h>
6#import <MessageUI/MFMessageComposeViewController.h>
7#import <MessageUI/MFMailComposeViewController.h>
8#import <MobileCoreServices/MobileCoreServices.h>
9
10static NSString *const kShareOptionMessage = @"message";
11static NSString *const kShareOptionSubject = @"subject";
12static NSString *const kShareOptionFiles = @"files";
13static NSString *const kShareOptionUrl = @"url";
14
15@implementation SocialSharing {
16 UIPopoverController *_popover;
17 NSString *_popupCoordinates;
18}
19
20- (void)pluginInitialize {
21 if ([self isEmailAvailable]) {
22 [self cycleTheGlobalMailComposer];
23 }
24}
25
26- (void)available:(CDVInvokedUrlCommand*)command {
27 BOOL avail = NO;
28 if (NSClassFromString(@"UIActivityViewController")) {
29 avail = YES;
30 }
31 CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:avail];
32 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
33}
34
35- (NSString*)getIPadPopupCoordinates {
36 if (_popupCoordinates != nil) {
37 return _popupCoordinates;
38 }
39 if ([self.webView respondsToSelector:@selector(stringByEvaluatingJavaScriptFromString:)]) {
40 return [(UIWebView*)self.webView stringByEvaluatingJavaScriptFromString:@"window.plugins.socialsharing.iPadPopupCoordinates();"];
41 } else {
42 // prolly a wkwebview, ignoring for now
43 return nil;
44 }
45}
46
47- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command {
48 _popupCoordinates = [command.arguments objectAtIndex:0];
49}
50
51- (CGRect)getPopupRectFromIPadPopupCoordinates:(NSArray*)comps {
52 CGRect rect = CGRectZero;
53 if ([comps count] == 4) {
54 rect = CGRectMake([[comps objectAtIndex:0] integerValue], [[comps objectAtIndex:1] integerValue], [[comps objectAtIndex:2] integerValue], [[comps objectAtIndex:3] integerValue]);
55 }
56 return rect;
57}
58
59- (void)share:(CDVInvokedUrlCommand*)command {
60 [self shareInternal:command
61 withOptions:@{
62 kShareOptionMessage: [command.arguments objectAtIndex:0],
63 kShareOptionSubject: [command.arguments objectAtIndex:1],
64 kShareOptionFiles: [command.arguments objectAtIndex:2],
65 kShareOptionUrl: [command.arguments objectAtIndex:3]
66 }
67 isBooleanResponse:YES
68];
69}
70
71- (void)shareWithOptions:(CDVInvokedUrlCommand*)command {
72 NSDictionary* options = [command.arguments objectAtIndex:0];
73 [self shareInternal:command
74 withOptions:options
75 isBooleanResponse:NO
76 ];
77}
78
79- (void)shareInternal:(CDVInvokedUrlCommand*)command withOptions:(NSDictionary*)options isBooleanResponse:(BOOL)boolResponse {
80 [self.commandDelegate runInBackground:^{ //avoid main thread block especially if sharing big files from url
81 if (!NSClassFromString(@"UIActivityViewController")) {
82 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
83 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
84 return;
85 }
86
87 NSString *message = options[kShareOptionMessage];
88 NSString *subject = options[kShareOptionSubject];
89 NSArray *filenames = options[kShareOptionFiles];
90 NSString *urlString = options[kShareOptionUrl];
91
92 NSMutableArray *activityItems = [[NSMutableArray alloc] init];
93
94 if (message != (id)[NSNull null] && message != nil) {
95 [activityItems addObject:message];
96 }
97
98 if (filenames != (id)[NSNull null] && filenames != nil && filenames.count > 0) {
99 NSMutableArray *files = [[NSMutableArray alloc] init];
100 for (NSString* filename in filenames) {
101 NSObject *file = [self getImage:filename];
102 if (file == nil) {
103 file = [self getFile:filename];
104 }
105 if (file != nil) {
106 [files addObject:file];
107 }
108 }
109 [activityItems addObjectsFromArray:files];
110 }
111
112 if (urlString != (id)[NSNull null] && urlString != nil) {
113 [activityItems addObject:[NSURL URLWithString:[urlString URLEncodedString]]];
114 }
115
116 UIActivity *activity = [[UIActivity alloc] init];
117 NSArray *applicationActivities = [[NSArray alloc] initWithObjects:activity, nil];
118 UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities];
119 if (subject != (id)[NSNull null] && subject != nil) {
120 [activityVC setValue:subject forKey:@"subject"];
121 }
122
123 if ([activityVC respondsToSelector:(@selector(setCompletionWithItemsHandler:))]) {
124 [activityVC setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray * returnedItems, NSError * activityError) {
125 [self cleanupStoredFiles];
126 if (boolResponse) {
127 [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed]
128 callbackId:command.callbackId];
129 } else {
130 NSDictionary * result = @{@"completed":@(completed), @"app":activityType == nil ? @"" : activityType};
131 [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]
132 callbackId:command.callbackId];
133 }
134 }];
135 } else {
136 // let's suppress this warning otherwise folks will start opening issues while it's not relevant
137#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
138 [activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) {
139 [self cleanupStoredFiles];
140 NSDictionary * result = @{@"completed":@(completed), @"app":activityType};
141 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
142 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
143 }];
144#pragma GCC diagnostic warning "-Wdeprecated-declarations"
145 }
146
147 NSArray * socialSharingExcludeActivities = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"SocialSharingExcludeActivities"];
148 if (socialSharingExcludeActivities!=nil && [socialSharingExcludeActivities count] > 0) {
149 activityVC.excludedActivityTypes = socialSharingExcludeActivities;
150 }
151
152 dispatch_async(dispatch_get_main_queue(), ^(void){
153 // iPad on iOS >= 8 needs a different approach
154 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
155 NSString* iPadCoords = [self getIPadPopupCoordinates];
156 if (iPadCoords != nil && ![iPadCoords isEqual:@"-1,-1,-1,-1"]) {
157 NSArray *comps = [iPadCoords componentsSeparatedByString:@","];
158 CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps];
159 if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) {
160#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported
161 activityVC.popoverPresentationController.sourceView = self.webView;
162 activityVC.popoverPresentationController.sourceRect = rect;
163#endif
164 } else {
165 _popover = [[UIPopoverController alloc] initWithContentViewController:activityVC];
166 _popover.delegate = self;
167 [_popover presentPopoverFromRect:rect inView:self.webView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
168 }
169 } else if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) {
170#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported
171 activityVC.popoverPresentationController.sourceView = self.webView;
172 // position the popup at the bottom, just like iOS < 8 did (and iPhone still does on iOS 8)
173 NSArray *comps = [NSArray arrayWithObjects:
174 [NSNumber numberWithInt:(self.viewController.view.frame.size.width/2)-200],
175 [NSNumber numberWithInt:self.viewController.view.frame.size.height],
176 [NSNumber numberWithInt:400],
177 [NSNumber numberWithInt:400],
178 nil];
179 CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps];
180 activityVC.popoverPresentationController.sourceRect = rect;
181#endif
182 }
183 }
184 [[self getTopMostViewController] presentViewController:activityVC animated:YES completion:nil];
185 });
186 }];
187}
188
189- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command {
190 [self shareViaInternal:command type:SLServiceTypeTwitter];
191}
192
193- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command {
194 [self shareViaInternal:command type:SLServiceTypeFacebook];
195}
196
197- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command {
198 // If Fb app is installed a message is not prefilled.
199 // When shared through the default iOS widget (iOS Settings > Facebook) the message is prefilled already.
200 NSString *message = [command.arguments objectAtIndex:0];
201 if (message != (id)[NSNull null]) {
202 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1000 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
203 BOOL fbAppInstalled = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"fb://"]]; // requires whitelisting on iOS9
204 if (fbAppInstalled) {
205 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
206 [pasteboard setValue:message forPasteboardType:@"public.text"];
207 NSString *hint = [command.arguments objectAtIndex:4];
208 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:hint delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
209 [alert show];
210 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2800 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
211 [alert dismissWithClickedButtonIndex:-1 animated:YES];
212 });
213 }
214 });
215 }
216 [self shareViaInternal:command type:SLServiceTypeFacebook];
217}
218
219- (void)shareVia:(CDVInvokedUrlCommand*)command {
220 [self shareViaInternal:command type:[command.arguments objectAtIndex:4]];
221}
222
223- (void)canShareVia:(CDVInvokedUrlCommand*)command {
224 NSString *via = [command.arguments objectAtIndex:4];
225 CDVPluginResult * pluginResult;
226 if ([@"sms" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaSMS]) {
227 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
228 } else if ([@"email" caseInsensitiveCompare:via] == NSOrderedSame && [self isEmailAvailable]) {
229 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
230 } else if ([@"whatsapp" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaWhatsApp]) {
231 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
232 } else if ([@"instagram" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaInstagram]) {
233 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
234 } else if ([self isAvailableForSharing:command type:via]) {
235 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
236 } else {
237 pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
238 }
239 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
240}
241
242- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command {
243 if ([self isEmailAvailable]) {
244 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
245 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
246 } else {
247 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
248 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
249 }
250}
251
252- (bool)isEmailAvailable {
253 Class messageClass = (NSClassFromString(@"MFMailComposeViewController"));
254 return messageClass != nil && [messageClass canSendMail];
255}
256
257- (bool)isAvailableForSharing:(CDVInvokedUrlCommand*)command
258 type:(NSString *) type {
259 // isAvailableForServiceType returns true if you pass it a type that is not
260 // in the defined constants, this is probably a bug on apples part
261 if(!([type isEqualToString:SLServiceTypeFacebook]
262 || [type isEqualToString:SLServiceTypeTwitter]
263 || [type isEqualToString:SLServiceTypeTencentWeibo]
264 || [type isEqualToString:SLServiceTypeSinaWeibo])) {
265 return false;
266 }
267 // wrapped in try-catch, because isAvailableForServiceType may crash if an invalid type is passed
268 @try {
269 return [SLComposeViewController isAvailableForServiceType:type];
270 }
271 @catch (NSException* exception) {
272 return false;
273 }
274}
275
276- (void)shareViaInternal:(CDVInvokedUrlCommand*)command
277 type:(NSString *) type {
278
279 NSString *message = [command.arguments objectAtIndex:0];
280 // subject is not supported by the SLComposeViewController
281 NSArray *filenames = [command.arguments objectAtIndex:2];
282 NSString *urlString = [command.arguments objectAtIndex:3];
283
284 // boldly invoke the target app, because the phone will display a nice message asking to configure the app
285 SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:type];
286 if (message != (id)[NSNull null]) {
287 [composeViewController setInitialText:message];
288 }
289
290 for (NSString* filename in filenames) {
291 UIImage* image = [self getImage:filename];
292 if (image != nil) {
293 [composeViewController addImage:image];
294 }
295 }
296
297 if (urlString != (id)[NSNull null]) {
298 [composeViewController addURL:[NSURL URLWithString:[urlString URLEncodedString]]];
299 }
300
301 [composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) {
302 if (SLComposeViewControllerResultCancelled == result) {
303 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"cancelled"];
304 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
305 } else if ([self isAvailableForSharing:command type:type]) {
306 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:SLComposeViewControllerResultDone == result];
307 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
308 } else {
309 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
310 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
311 }
312 // required for iOS6 (issues #162 and #167)
313 [self.viewController dismissViewControllerAnimated:YES completion:nil];
314 }];
315 [[self getTopMostViewController] presentViewController:composeViewController animated:YES completion:nil];
316}
317
318- (void)shareViaEmail:(CDVInvokedUrlCommand*)command {
319 if ([self isEmailAvailable]) {
320
321 if (TARGET_IPHONE_SIMULATOR && IsAtLeastiOSVersion(@"8.0")) {
322 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"SocialSharing plugin"
323 message:@"Sharing via email is not supported on the iOS 8 simulator."
324 delegate:nil
325 cancelButtonTitle:@"OK"
326 otherButtonTitles:nil];
327 [alert show];
328 return;
329 }
330
331 [self cycleTheGlobalMailComposer];
332
333 self.globalMailComposer.mailComposeDelegate = self;
334
335 if ([command.arguments objectAtIndex:0] != (id)[NSNull null]) {
336 NSString *message = [command.arguments objectAtIndex:0];
337 BOOL isHTML = [message rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch].location != NSNotFound;
338 [self.globalMailComposer setMessageBody:message isHTML:isHTML];
339 }
340
341 if ([command.arguments objectAtIndex:1] != (id)[NSNull null]) {
342 [self.globalMailComposer setSubject: [command.arguments objectAtIndex:1]];
343 }
344
345 if ([command.arguments objectAtIndex:2] != (id)[NSNull null]) {
346 [self.globalMailComposer setToRecipients:[command.arguments objectAtIndex:2]];
347 }
348
349 if ([command.arguments objectAtIndex:3] != (id)[NSNull null]) {
350 [self.globalMailComposer setCcRecipients:[command.arguments objectAtIndex:3]];
351 }
352
353 if ([command.arguments objectAtIndex:4] != (id)[NSNull null]) {
354 [self.globalMailComposer setBccRecipients:[command.arguments objectAtIndex:4]];
355 }
356
357 if ([command.arguments objectAtIndex:5] != (id)[NSNull null]) {
358 NSArray* attachments = [command.arguments objectAtIndex:5];
359 NSFileManager* fileManager = [NSFileManager defaultManager];
360 for (NSString* path in attachments) {
361 NSURL *file = [self getFile:path];
362 NSData* data = [fileManager contentsAtPath:file.path];
363
364 if (!data) {
365 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"invalid attachment"];
366 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
367 return;
368 }
369
370 NSString* fileName;
371 NSString* mimeType;
372 NSString* basename = [self getBasenameFromAttachmentPath:path];
373
374 //Find data anywhere in string
375 NSRange rangeData = [basename rangeOfString:@"data:"];
376 if (rangeData.location == NSNotFound)
377 {
378 fileName = [basename pathComponents].lastObject;
379 mimeType = [self getMimeTypeFromFileExtension:[basename pathExtension]];
380 }
381 else
382 {
383 mimeType = (NSString*)[[[basename substringFromIndex:rangeData.location+rangeData.length] componentsSeparatedByString: @";"] objectAtIndex:0];
384
385 //Find df anywhere in string
386 NSRange rangeDF = [basename rangeOfString:@"df:"];
387 //If not found fallback to default name
388 if (rangeDF.location == NSNotFound) {
389 fileName = @"attachment.";
390 fileName = [fileName stringByAppendingString:(NSString*)[[mimeType componentsSeparatedByString: @"/"] lastObject]];
391 } else {
392 //Found, apply name
393 fileName = (NSString*)[[[basename substringFromIndex:rangeDF.location+rangeDF.length] componentsSeparatedByString: @";"] objectAtIndex:0];
394 }
395
396
397 NSString *base64content = (NSString*)[[basename componentsSeparatedByString: @","] lastObject];
398 data = [SocialSharing dataFromBase64String:base64content];
399 }
400 [self.globalMailComposer addAttachmentData:data mimeType:mimeType fileName:fileName];
401 }
402 }
403
404 // remember the command, because we need it in the didFinishWithResult method
405 _command = command;
406
407 [self.commandDelegate runInBackground:^{
408 [[self getTopMostViewController] presentViewController:self.globalMailComposer animated:YES completion:nil];
409 }];
410
411 } else {
412 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
413 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
414 }
415}
416
417- (UIViewController*) getTopMostViewController {
418 UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;
419 while (presentingViewController.presentedViewController != nil) {
420 presentingViewController = presentingViewController.presentedViewController;
421 }
422 return presentingViewController;
423}
424
425- (NSString*) getBasenameFromAttachmentPath:(NSString*)path {
426 if ([path hasPrefix:@"base64:"]) {
427 NSString* pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:" withString:@""];
428 return [pathWithoutPrefix substringToIndex:[pathWithoutPrefix rangeOfString:@"//"].location];
429 }
430 return [path componentsSeparatedByString: @"?"][0];
431}
432
433- (NSString*) getMimeTypeFromFileExtension:(NSString*)extension {
434 if (!extension) {
435 return nil;
436 }
437 // Get the UTI from the file's extension
438 CFStringRef ext = (CFStringRef)CFBridgingRetain(extension);
439 CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL);
440 // Converting UTI to a mime type
441 NSString *result = (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType));
442 CFRelease(ext);
443 CFRelease(type);
444 return result;
445}
446
447/**
448 * Delegate will be called after the mail composer did finish an action
449 * to dismiss the view.
450 */
451- (void) mailComposeController:(MFMailComposeViewController*)controller
452 didFinishWithResult:(MFMailComposeResult)result
453 error:(NSError*)error {
454 bool ok = result == MFMailComposeResultSent;
455 [self.globalMailComposer dismissViewControllerAnimated:YES completion:^{[self cycleTheGlobalMailComposer];}];
456 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok];
457 [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId];
458}
459
460-(void)cycleTheGlobalMailComposer {
461 // we are cycling the damned GlobalMailComposer: http://stackoverflow.com/questions/25604552/i-have-real-misunderstanding-with-mfmailcomposeviewcontroller-in-swift-ios8-in/25604976#25604976
462 self.globalMailComposer = nil;
463 self.globalMailComposer = [[MFMailComposeViewController alloc] init];
464}
465
466- (bool)canShareViaSMS {
467 Class messageClass = (NSClassFromString(@"MFMessageComposeViewController"));
468 return messageClass != nil && [messageClass canSendText];
469}
470
471- (void)shareViaSMS:(CDVInvokedUrlCommand*)command {
472 if ([self canShareViaSMS]) {
473 NSDictionary* options = [command.arguments objectAtIndex:0];
474 NSString *phonenumbers = [command.arguments objectAtIndex:1];
475 NSString *message = [options objectForKey:@"message"];
476 NSString *subject = [options objectForKey:@"subject"];
477 NSString *image = [options objectForKey:@"image"];
478
479 MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
480 picker.messageComposeDelegate = (id) self;
481 if (message != (id)[NSNull null]) {
482 picker.body = message;
483 }
484 if (subject != (id)[NSNull null]) {
485 [picker setSubject:subject];
486 }
487 if (image != nil && image != (id)[NSNull null]) {
488 BOOL canSendAttachments = [[MFMessageComposeViewController class] respondsToSelector:@selector(canSendAttachments)];
489 if (canSendAttachments) {
490 NSURL *file = [self getFile:image];
491 if (file != nil) {
492 [picker addAttachmentURL:file withAlternateFilename:nil];
493 }
494 }
495 }
496
497 if (phonenumbers != (id)[NSNull null]) {
498 [picker setRecipients:[phonenumbers componentsSeparatedByString:@","]];
499 }
500 // remember the command, because we need it in the didFinishWithResult method
501 _command = command;
502 [self.commandDelegate runInBackground:^{
503 picker.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
504 [[self getTopMostViewController] presentViewController:picker animated:NO completion:nil];
505 }];
506 } else {
507 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
508 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
509 }
510}
511
512// Dismisses the SMS composition interface when users taps Cancel or Send
513- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result {
514 bool ok = result == MessageComposeResultSent;
515 [[self getTopMostViewController] dismissViewControllerAnimated:YES completion:nil];
516 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok];
517 [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId];
518}
519
520- (bool)canShareViaInstagram {
521 return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"instagram://app"]]; // requires whitelisting on iOS9
522}
523
524- (bool)canShareViaWhatsApp {
525 return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"whatsapp://app"]]; // requires whitelisting on iOS9
526}
527
528// 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, ..
529- (void)openImage:(NSString *)imageName {
530 UIImage* image =[self getImage:imageName];
531 if (image != nil) {
532 NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myTempImage.jpg"];
533 [UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES];
534 _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]];
535 _documentInteractionController.UTI = @""; // TODO find the scheme for google drive and create a shareViaGoogleDrive function
536 [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES];
537 }
538}
539
540- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command {
541
542 // on iOS9 canShareVia('instagram'..) will only work if instagram:// is whitelisted.
543 // If it's not, this method will ask permission to the user on iOS9 for opening the app,
544 // which is of course better than Instagram sharing not working at all because you forgot to whitelist it.
545 // Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('instagram'..)
546 if (!IsAtLeastiOSVersion(@"9.0")) {
547 if (![self canShareViaInstagram]) {
548 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
549 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
550 return;
551 }
552 }
553
554 NSString *message = [command.arguments objectAtIndex:0];
555 // subject is not supported by the SLComposeViewController
556 NSArray *filenames = [command.arguments objectAtIndex:2];
557
558 // only use the first image (for now.. maybe we can share in a loop?)
559 UIImage* image = nil;
560 for (NSString* filename in filenames) {
561 image = [self getImage:filename];
562 break;
563 }
564
565// NSData *imageObj = [NSData dataFromBase64String:objectAtIndex0];
566 NSString *tmpDir = NSTemporaryDirectory();
567 NSString *path = [tmpDir stringByAppendingPathComponent:@"instagram.igo"];
568 [UIImageJPEGRepresentation(image, 1.0) writeToFile:path atomically:YES];
569
570 _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]];
571 _documentInteractionController.delegate = self;
572 _documentInteractionController.UTI = @"com.instagram.exclusivegram";
573
574 if (message != (id)[NSNull null]) {
575 // no longer working, so ..
576 _documentInteractionController.annotation = @{@"InstagramCaption" : message};
577
578 // .. we put the message on the clipboard (you app can prompt the user to paste it)
579 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
580 [pasteboard setValue:message forPasteboardType:@"public.text"];
581 }
582
583 // remember the command for the delegate method
584 _command = command;
585
586 // test for #513
587 dispatch_async(dispatch_get_main_queue(), ^(void){
588 [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.webView animated:YES];
589 });
590}
591
592- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command {
593
594 // on iOS9 canShareVia('whatsapp'..) will only work if whatsapp:// is whitelisted.
595 // If it's not, this method will ask permission to the user on iOS9 for opening the app,
596 // which is of course better than WhatsApp sharing not working at all because you forgot to whitelist it.
597 // Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('whatsapp'..)
598 if (!IsAtLeastiOSVersion(@"9.0")) {
599 if (![self canShareViaWhatsApp]) {
600 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"];
601 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
602 return;
603 }
604 }
605
606 NSString *message = [command.arguments objectAtIndex:0];
607 // subject is not supported by the SLComposeViewController
608 NSArray *filenames = [command.arguments objectAtIndex:2];
609 NSString *urlString = [command.arguments objectAtIndex:3];
610 NSString *abid = [command.arguments objectAtIndex:4];
611
612 // only use the first image (for now.. maybe we can share in a loop?)
613 UIImage* image = nil;
614 for (NSString* filename in filenames) {
615 image = [self getImage:filename];
616 break;
617 }
618
619 // with WhatsApp, we can share an image OR text+url.. image wins if set
620 if (image != nil) {
621 NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/whatsAppTmp.wai"];
622 [UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES];
623 _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]];
624 _documentInteractionController.UTI = @"net.whatsapp.image";
625 _documentInteractionController.delegate = self;
626 _command = command;
627 [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES];
628 } else {
629 // append an url to a message, if both are passed
630 NSString * shareString = @"";
631 if (message != (id)[NSNull null]) {
632 shareString = message;
633 }
634 if (urlString != (id)[NSNull null]) {
635 if ([shareString isEqual: @""]) {
636 shareString = urlString;
637 } else {
638 shareString = [NSString stringWithFormat:@"%@ %@", shareString, [urlString URLEncodedString]];
639 }
640 }
641 NSString * encodedShareString = [shareString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
642 // also encode the '=' character
643 encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"];
644 encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"&" withString:@"%26"];
645 NSString * abidString = @"";
646 if (abid != (id)[NSNull null]) {
647 abidString = [NSString stringWithFormat:@"abid=%@&", abid];
648 }
649 NSString * encodedShareStringForWhatsApp = [NSString stringWithFormat:@"whatsapp://send?%@text=%@", abidString, encodedShareString];
650
651 NSURL *whatsappURL = [NSURL URLWithString:encodedShareStringForWhatsApp];
652 [[UIApplication sharedApplication] openURL: whatsappURL];
653 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
654 [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
655 }
656}
657
658- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command {
659 self.command = command;
660 NSArray *filenames = [command.arguments objectAtIndex:0];
661 [self.commandDelegate runInBackground:^{
662 bool shared = false;
663 for (NSString* filename in filenames) {
664 UIImage* image = [self getImage:filename];
665 if (image != nil) {
666 shared = true;
667 UIImageWriteToSavedPhotosAlbum(image, self, @selector(thisImage:wasSavedToPhotoAlbumWithError:contextInfo:), nil);
668 }
669 }
670 if (!shared) {
671 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no valid image was passed"];
672 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
673 }
674 }];
675}
676
677// called from saveToPhotoAlbum, note that we only send feedback for the first image that's being saved (not keeping the callback)
678// but since the UIImageWriteToSavedPhotosAlbum function is only called with valid images that should not be a problem
679- (void)thisImage:(UIImage *)image wasSavedToPhotoAlbumWithError:(NSError *)error contextInfo:(void*)ctxInfo {
680 if (error) {
681 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.localizedDescription];
682 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
683 } else {
684 CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
685 [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId];
686 }
687}
688
689-(UIImage*)getImage: (NSString *)imageName {
690 UIImage *image = nil;
691 if (imageName != (id)[NSNull null]) {
692 if ([imageName hasPrefix:@"http"]) {
693 image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]]];
694 } else if ([imageName hasPrefix:@"www/"]) {
695 image = [UIImage imageNamed:imageName];
696 } else if ([imageName hasPrefix:@"file://"]) {
697 image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSURL URLWithString:imageName] path]]];
698 } else if ([imageName hasPrefix:@"data:"]) {
699 // using a base64 encoded string
700 NSURL *imageURL = [NSURL URLWithString:imageName];
701 NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
702 image = [UIImage imageWithData:imageData];
703 } else if ([imageName hasPrefix:@"assets-library://"]) {
704 // use assets-library
705 NSURL *imageURL = [NSURL URLWithString:imageName];
706 NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
707 image = [UIImage imageWithData:imageData];
708 } else {
709 // assume anywhere else, on the local filesystem
710 image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imageName]];
711 }
712 }
713 return image;
714}
715
716-(NSURL*)getFile: (NSString *)fileName {
717 NSURL *file = nil;
718 if (fileName != (id)[NSNull null]) {
719 NSRange rangeData = [fileName rangeOfString:@"data:"];
720 if ([fileName hasPrefix:@"http"]) {
721 NSURL *url = [NSURL URLWithString:fileName];
722 NSData *fileData = [NSData dataWithContentsOfURL:url];
723 NSString *name = (NSString*)[[fileName componentsSeparatedByString: @"/"] lastObject];
724 file = [NSURL fileURLWithPath:[self storeInFile:[name componentsSeparatedByString: @"?"][0] fileData:fileData]];
725 } else if ([fileName hasPrefix:@"www/"]) {
726 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
727 NSString *fullPath = [NSString stringWithFormat:@"%@/%@", bundlePath, fileName];
728 file = [NSURL fileURLWithPath:fullPath];
729 } else if ([fileName hasPrefix:@"file://"]) {
730 // stripping the first 6 chars, because the path should start with / instead of file://
731 file = [NSURL fileURLWithPath:[fileName substringFromIndex:6]];
732 } else if (rangeData.location != NSNotFound ){
733 //If found "data:"
734 NSString *fileType = (NSString*)[[[fileName substringFromIndex:rangeData.location+rangeData.length] componentsSeparatedByString: @";"] objectAtIndex:0];
735
736 NSString* attachmentName;
737 //Find df anywhere in string
738 NSRange rangeDF = [fileName rangeOfString:@"df:"];
739 //If not found fallback to default name
740 if (rangeDF.location == NSNotFound) {
741 attachmentName = @"attachment.";
742 attachmentName = [attachmentName stringByAppendingString:(NSString*)[[fileType componentsSeparatedByString: @"/"] lastObject]];
743 } else {
744 //Found, apply name
745 attachmentName = (NSString*)[[[fileName substringFromIndex:rangeDF.location+rangeDF.length] componentsSeparatedByString: @";"] objectAtIndex:0];
746 }
747
748
749 NSString *base64content = (NSString*)[[fileName componentsSeparatedByString: @","] lastObject];
750 NSData* data = [SocialSharing dataFromBase64String:base64content];
751 file = [NSURL fileURLWithPath:[self storeInFile:attachmentName fileData:data]];
752
753 } else {
754 // assume anywhere else, on the local filesystem
755 file = [NSURL fileURLWithPath:fileName];
756 }
757 }
758 return file;
759}
760
761-(NSString*) storeInFile: (NSString*) fileName
762 fileData: (NSData*) fileData {
763 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
764 NSString *documentsDirectory = [paths objectAtIndex:0];
765 NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
766 [fileData writeToFile:filePath atomically:YES];
767 _tempStoredFile = filePath;
768 return filePath;
769}
770
771- (void) cleanupStoredFiles {
772 if (_tempStoredFile != nil) {
773 NSError *error;
774 [[NSFileManager defaultManager]removeItemAtPath:_tempStoredFile error:&error];
775 }
776}
777
778+ (NSData*) dataFromBase64String:(NSString*)aString {
779 return [[NSData alloc] initWithBase64EncodedString:aString options:0];
780}
781
782#pragma mark - UIPopoverControllerDelegate methods
783
784- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view {
785 NSArray *comps = [[self getIPadPopupCoordinates] componentsSeparatedByString:@","];
786 CGRect newRect = [self getPopupRectFromIPadPopupCoordinates:comps];
787 rect->origin = newRect.origin;
788}
789
790- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
791 _popover = nil;
792}
793
794#pragma mark - UIDocumentInteractionControllerDelegate methods
795
796- (void) documentInteractionController: (UIDocumentInteractionController *) controller willBeginSendingToApplication: (NSString *) application {
797 // note that the application actually contains the app bundle id which was picked (for whatsapp and instagram only)
798 NSLog(@"SocialSharing app selected: %@", application);
799}
800
801- (void) documentInteractionControllerDidDismissOpenInMenu: (UIDocumentInteractionController *) controller {
802 if (self.command != nil) {
803 CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
804 [self.commandDelegate sendPluginResult:result callbackId: self.command.callbackId];
805 }
806}
807
808@end