diff options
| author | Rene Ae <aehtyb@gmail.com> | 2015-12-01 00:51:02 -0600 |
|---|---|---|
| committer | Rene Ae <aehtyb@gmail.com> | 2015-12-01 00:51:02 -0600 |
| commit | 6415f506034262dd7151be2e35e1e1c1e97f4dfa (patch) | |
| tree | c6e564e374967725a40fd87f7c5f3a1ba8019089 /StoneIsland | |
| parent | 5e07e273e18a609978253c45f3c5f702b0de4991 (diff) | |
| parent | 9497b50fa02f3cfa9cb263ce3a96fa725282d60d (diff) | |
Merge branch 'master' of https://github.com/okfocus/stone-island
Diffstat (limited to 'StoneIsland')
86 files changed, 6179 insertions, 136 deletions
diff --git a/StoneIsland/platforms/ios/StoneIsland.xcodeproj/project.pbxproj b/StoneIsland/platforms/ios/StoneIsland.xcodeproj/project.pbxproj index e75b8393..53038bdc 100644 --- a/StoneIsland/platforms/ios/StoneIsland.xcodeproj/project.pbxproj +++ b/StoneIsland/platforms/ios/StoneIsland.xcodeproj/project.pbxproj @@ -55,6 +55,9 @@ C2BF0352F9A246A886C16676 /* CDVViewController+SplashScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5B2F329484022BEE14D58 /* CDVViewController+SplashScreen.m */; }; 4A80D26AF40046D7AD4979A9 /* AppDelegate+notification.m in Sources */ = {isa = PBXBuildFile; fileRef = EE6BC93769C04564BA35B9B4 /* AppDelegate+notification.m */; }; 4ABEABC16E324DEEB7A72E3B /* PushPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 450F6AEB11F34A6080834666 /* PushPlugin.m */; }; + 6084459294A8473FB8AA20F3 /* SocialSharing.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D2A7B40020F4AAEBE6DCEB1 /* SocialSharing.m */; }; + 98F1852ABB5E4B118CC6C27B /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88275732925E4321970403F3 /* Social.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 4C059853D8B843F88F01C944 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A186FB94DE714DEC95EA29D9 /* MessageUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -148,6 +151,10 @@ 450F6AEB11F34A6080834666 /* PushPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "PushPlugin.m"; path = "phonegap-plugin-push/PushPlugin.m"; sourceTree = "<group>"; fileEncoding = 4; }; 9F746E936D46499681DECD6E /* AppDelegate+notification.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "AppDelegate+notification.h"; path = "phonegap-plugin-push/AppDelegate+notification.h"; sourceTree = "<group>"; fileEncoding = 4; }; A3C287A7B1C74C02A8585FC2 /* PushPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "PushPlugin.h"; path = "phonegap-plugin-push/PushPlugin.h"; sourceTree = "<group>"; fileEncoding = 4; }; + 0D2A7B40020F4AAEBE6DCEB1 /* SocialSharing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "SocialSharing.m"; path = "cordova-plugin-x-socialsharing/SocialSharing.m"; sourceTree = "<group>"; fileEncoding = 4; }; + 8A80AE176DAB4F84AC8031D7 /* SocialSharing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SocialSharing.h"; path = "cordova-plugin-x-socialsharing/SocialSharing.h"; sourceTree = "<group>"; fileEncoding = 4; }; + 88275732925E4321970403F3 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "Social.framework"; path = "System/Library/Frameworks/Social.framework"; sourceTree = SDKROOT; fileEncoding = 4; }; + A186FB94DE714DEC95EA29D9 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "MessageUI.framework"; path = "System/Library/Frameworks/MessageUI.framework"; sourceTree = SDKROOT; fileEncoding = 4; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -162,6 +169,8 @@ 05EBE3ED6EA64212BCC52906 /* AudioToolbox.framework in Frameworks */, 85552B118FDD4F9286C3D33C /* CoreLocation.framework in Frameworks */, 28FA0D2F417F4E6891D4A2A3 /* SystemConfiguration.framework in Frameworks */, + 98F1852ABB5E4B118CC6C27B /* Social.framework in Frameworks */, + 4C059853D8B843F88F01C944 /* MessageUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -238,6 +247,8 @@ 270ADA31CA7B4A42B185E451 /* AudioToolbox.framework */, 88109882DED84831BEC5BBB0 /* CoreLocation.framework */, E63CFDA045E649E8A2E41A6E /* SystemConfiguration.framework */, + 88275732925E4321970403F3 /* Social.framework */, + A186FB94DE714DEC95EA29D9 /* MessageUI.framework */, ); name = Frameworks; sourceTree = "<group>"; @@ -277,6 +288,8 @@ 450F6AEB11F34A6080834666 /* PushPlugin.m */, 9F746E936D46499681DECD6E /* AppDelegate+notification.h */, A3C287A7B1C74C02A8585FC2 /* PushPlugin.h */, + 0D2A7B40020F4AAEBE6DCEB1 /* SocialSharing.m */, + 8A80AE176DAB4F84AC8031D7 /* SocialSharing.h */, ); name = Plugins; path = "StoneIsland/Plugins"; @@ -474,6 +487,7 @@ C2BF0352F9A246A886C16676 /* CDVViewController+SplashScreen.m in Sources */, 4A80D26AF40046D7AD4979A9 /* AppDelegate+notification.m in Sources */, 4ABEABC16E324DEEB7A72E3B /* PushPlugin.m in Sources */, + 6084459294A8473FB8AA20F3 /* SocialSharing.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/StoneIsland/platforms/ios/StoneIsland/Plugins/cordova-plugin-x-socialsharing/SocialSharing.h b/StoneIsland/platforms/ios/StoneIsland/Plugins/cordova-plugin-x-socialsharing/SocialSharing.h new file mode 100755 index 00000000..b51474d3 --- /dev/null +++ b/StoneIsland/platforms/ios/StoneIsland/Plugins/cordova-plugin-x-socialsharing/SocialSharing.h @@ -0,0 +1,27 @@ +#import <Cordova/CDV.h> +#import <MessageUI/MFMailComposeViewController.h> + +@interface SocialSharing : CDVPlugin <UIPopoverControllerDelegate, MFMailComposeViewControllerDelegate, UIDocumentInteractionControllerDelegate> + +@property (nonatomic, strong) MFMailComposeViewController *globalMailComposer; +@property (retain) UIDocumentInteractionController * documentInteractionController; +@property (retain) NSString * tempStoredFile; +@property (retain) CDVInvokedUrlCommand * command; + +- (void)available:(CDVInvokedUrlCommand*)command; +- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command; +- (void)share:(CDVInvokedUrlCommand*)command; +- (void)canShareVia:(CDVInvokedUrlCommand*)command; +- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command; +- (void)shareVia:(CDVInvokedUrlCommand*)command; +- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command; +- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command; +- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command; +- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command; +- (void)shareViaSMS:(CDVInvokedUrlCommand*)command; +- (void)shareViaEmail:(CDVInvokedUrlCommand*)command; +- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command; + +- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command; + +@end diff --git a/StoneIsland/platforms/ios/StoneIsland/Plugins/cordova-plugin-x-socialsharing/SocialSharing.m b/StoneIsland/platforms/ios/StoneIsland/Plugins/cordova-plugin-x-socialsharing/SocialSharing.m new file mode 100755 index 00000000..cd0913a4 --- /dev/null +++ b/StoneIsland/platforms/ios/StoneIsland/Plugins/cordova-plugin-x-socialsharing/SocialSharing.m @@ -0,0 +1,715 @@ +#import "SocialSharing.h" +#import <Cordova/CDV.h> +#import <Social/Social.h> +#import <Foundation/NSException.h> +#import <MessageUI/MFMessageComposeViewController.h> +#import <MessageUI/MFMailComposeViewController.h> +#import <MobileCoreServices/MobileCoreServices.h> + +@implementation SocialSharing { + UIPopoverController *_popover; + NSString *_popupCoordinates; +} + +- (void)pluginInitialize { + if ([self isEmailAvailable]) { + [self cycleTheGlobalMailComposer]; + } +} + +- (void)available:(CDVInvokedUrlCommand*)command { + BOOL avail = NO; + if (NSClassFromString(@"UIActivityViewController")) { + avail = YES; + } + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:avail]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (NSString*)getIPadPopupCoordinates { + if (_popupCoordinates != nil) { + return _popupCoordinates; + } + if ([self.webView respondsToSelector:@selector(stringByEvaluatingJavaScriptFromString:)]) { + return [(UIWebView*)self.webView stringByEvaluatingJavaScriptFromString:@"window.plugins.socialsharing.iPadPopupCoordinates();"]; + } else { + // prolly a wkwebview, ignoring for now + return nil; + } +} + +- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command { + _popupCoordinates = [command.arguments objectAtIndex:0]; +} + +- (CGRect)getPopupRectFromIPadPopupCoordinates:(NSArray*)comps { + CGRect rect = CGRectZero; + if ([comps count] == 4) { + rect = CGRectMake([[comps objectAtIndex:0] integerValue], [[comps objectAtIndex:1] integerValue], [[comps objectAtIndex:2] integerValue], [[comps objectAtIndex:3] integerValue]); + } + return rect; +} + +- (void)share:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ //avoid main thread block especially if sharing big files from url + if (!NSClassFromString(@"UIActivityViewController")) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + + NSString *message = [command.arguments objectAtIndex:0]; + NSString *subject = [command.arguments objectAtIndex:1]; + NSArray *filenames = [command.arguments objectAtIndex:2]; + NSString *urlString = [command.arguments objectAtIndex:3]; + + NSMutableArray *activityItems = [[NSMutableArray alloc] init]; + [activityItems addObject:message]; + + NSMutableArray *files = [[NSMutableArray alloc] init]; + if (filenames != (id)[NSNull null] && filenames.count > 0) { + for (NSString* filename in filenames) { + NSObject *file = [self getImage:filename]; + if (file == nil) { + file = [self getFile:filename]; + } + if (file != nil) { + [files addObject:file]; + } + } + [activityItems addObjectsFromArray:files]; + } + + if (urlString != (id)[NSNull null]) { + [activityItems addObject:[NSURL URLWithString:urlString]]; + } + + UIActivity *activity = [[UIActivity alloc] init]; + NSArray *applicationActivities = [[NSArray alloc] initWithObjects:activity, nil]; + UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities]; + if (subject != (id)[NSNull null]) { + [activityVC setValue:subject forKey:@"subject"]; + } + + // TODO deprecated in iOS 8.0, change this some day + [activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) { + [self cleanupStoredFiles]; + NSLog(@"SocialSharing app selected: %@", activityType); + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; + + NSArray * socialSharingExcludeActivities = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"SocialSharingExcludeActivities"]; + if (socialSharingExcludeActivities!=nil && [socialSharingExcludeActivities count] > 0) { + activityVC.excludedActivityTypes = socialSharingExcludeActivities; + } + + dispatch_async(dispatch_get_main_queue(), ^(void){ + // iPad on iOS >= 8 needs a different approach + if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { + NSString* iPadCoords = [self getIPadPopupCoordinates]; + if (iPadCoords != nil && ![iPadCoords isEqual:@"-1,-1,-1,-1"]) { + NSArray *comps = [iPadCoords componentsSeparatedByString:@","]; + CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps]; + if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported + activityVC.popoverPresentationController.sourceView = self.webView; + activityVC.popoverPresentationController.sourceRect = rect; +#endif + } else { + _popover = [[UIPopoverController alloc] initWithContentViewController:activityVC]; + _popover.delegate = self; + [_popover presentPopoverFromRect:rect inView:self.webView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; + } + } else if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported + activityVC.popoverPresentationController.sourceView = self.webView; + // position the popup at the bottom, just like iOS < 8 did (and iPhone still does on iOS 8) + NSArray *comps = [NSArray arrayWithObjects: + [NSNumber numberWithInt:(self.viewController.view.frame.size.width/2)-200], + [NSNumber numberWithInt:self.viewController.view.frame.size.height], + [NSNumber numberWithInt:400], + [NSNumber numberWithInt:400], + nil]; + CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps]; + activityVC.popoverPresentationController.sourceRect = rect; +#endif + } + } + [[self getTopMostViewController] presentViewController:activityVC animated:YES completion:nil]; + }); + }]; +} + +- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command { + [self shareViaInternal:command type:SLServiceTypeTwitter]; +} + +- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command { + [self shareViaInternal:command type:SLServiceTypeFacebook]; +} + +- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command { + // If Fb app is installed a message is not prefilled. + // When shared through the default iOS widget (iOS Settings > Facebook) the message is prefilled already. + NSString *message = [command.arguments objectAtIndex:0]; + if (message != (id)[NSNull null]) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1000 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ + BOOL fbAppInstalled = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"fb://"]]; // requires whitelisting on iOS9 + if (fbAppInstalled) { + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + [pasteboard setValue:message forPasteboardType:@"public.text"]; + NSString *hint = [command.arguments objectAtIndex:4]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:hint delegate:nil cancelButtonTitle:nil otherButtonTitles:nil]; + [alert show]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2800 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ + [alert dismissWithClickedButtonIndex:-1 animated:YES]; + }); + } + }); + } + [self shareViaInternal:command type:SLServiceTypeFacebook]; +} + +- (void)shareVia:(CDVInvokedUrlCommand*)command { + [self shareViaInternal:command type:[command.arguments objectAtIndex:4]]; +} + +- (void)canShareVia:(CDVInvokedUrlCommand*)command { + NSString *via = [command.arguments objectAtIndex:4]; + CDVPluginResult * pluginResult; + if ([@"sms" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaSMS]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else if ([@"email" caseInsensitiveCompare:via] == NSOrderedSame && [self isEmailAvailable]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else if ([@"whatsapp" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaWhatsApp]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else if ([@"instagram" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaInstagram]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else if ([self isAvailableForSharing:command type:via]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command { + if ([self isEmailAvailable]) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } +} + +- (bool)isEmailAvailable { + Class messageClass = (NSClassFromString(@"MFMailComposeViewController")); + return messageClass != nil && [messageClass canSendMail]; +} + +- (bool)isAvailableForSharing:(CDVInvokedUrlCommand*)command + type:(NSString *) type { + // isAvailableForServiceType returns true if you pass it a type that is not + // in the defined constants, this is probably a bug on apples part + if(!([type isEqualToString:SLServiceTypeFacebook] + || [type isEqualToString:SLServiceTypeTwitter] + || [type isEqualToString:SLServiceTypeTencentWeibo] + || [type isEqualToString:SLServiceTypeSinaWeibo])) { + return false; + } + // wrapped in try-catch, because isAvailableForServiceType may crash if an invalid type is passed + @try { + return [SLComposeViewController isAvailableForServiceType:type]; + } + @catch (NSException* exception) { + return false; + } +} + +- (void)shareViaInternal:(CDVInvokedUrlCommand*)command + type:(NSString *) type { + + NSString *message = [command.arguments objectAtIndex:0]; + // subject is not supported by the SLComposeViewController + NSArray *filenames = [command.arguments objectAtIndex:2]; + NSString *urlString = [command.arguments objectAtIndex:3]; + + // boldly invoke the target app, because the phone will display a nice message asking to configure the app + SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:type]; + if (message != (id)[NSNull null]) { + [composeViewController setInitialText:message]; + } + + for (NSString* filename in filenames) { + UIImage* image = [self getImage:filename]; + if (image != nil) { + [composeViewController addImage:image]; + } + } + + if (urlString != (id)[NSNull null]) { + [composeViewController addURL:[NSURL URLWithString:urlString]]; + } + + [composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) { + if (SLComposeViewControllerResultCancelled == result) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"cancelled"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } else if ([self isAvailableForSharing:command type:type]) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:SLComposeViewControllerResultDone == result]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } + // required for iOS6 (issues #162 and #167) + [self.viewController dismissViewControllerAnimated:YES completion:nil]; + }]; + [[self getTopMostViewController] presentViewController:composeViewController animated:YES completion:nil]; +} + +- (void)shareViaEmail:(CDVInvokedUrlCommand*)command { + if ([self isEmailAvailable]) { + + if (TARGET_IPHONE_SIMULATOR && IsAtLeastiOSVersion(@"8.0")) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"SocialSharing plugin" + message:@"Sharing via email is not supported on the iOS 8 simulator." + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + return; + } + + self.globalMailComposer.mailComposeDelegate = self; + + if ([command.arguments objectAtIndex:0] != (id)[NSNull null]) { + NSString *message = [command.arguments objectAtIndex:0]; + BOOL isHTML = [message rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch].location != NSNotFound; + [self.globalMailComposer setMessageBody:message isHTML:isHTML]; + } + + if ([command.arguments objectAtIndex:1] != (id)[NSNull null]) { + [self.globalMailComposer setSubject: [command.arguments objectAtIndex:1]]; + } + + if ([command.arguments objectAtIndex:2] != (id)[NSNull null]) { + [self.globalMailComposer setToRecipients:[command.arguments objectAtIndex:2]]; + } + + if ([command.arguments objectAtIndex:3] != (id)[NSNull null]) { + [self.globalMailComposer setCcRecipients:[command.arguments objectAtIndex:3]]; + } + + if ([command.arguments objectAtIndex:4] != (id)[NSNull null]) { + [self.globalMailComposer setBccRecipients:[command.arguments objectAtIndex:4]]; + } + + if ([command.arguments objectAtIndex:5] != (id)[NSNull null]) { + NSArray* attachments = [command.arguments objectAtIndex:5]; + NSFileManager* fileManager = [NSFileManager defaultManager]; + for (NSString* path in attachments) { + NSURL *file = [self getFile:path]; + NSData* data = [fileManager contentsAtPath:file.path]; + + NSString* fileName; + NSString* mimeType; + NSString* basename = [self getBasenameFromAttachmentPath:path]; + + if ([basename hasPrefix:@"data:"]) { + mimeType = (NSString*)[[[basename substringFromIndex:5] componentsSeparatedByString: @";"] objectAtIndex:0]; + fileName = @"attachment."; + fileName = [fileName stringByAppendingString:(NSString*)[[mimeType componentsSeparatedByString: @"/"] lastObject]]; + NSString *base64content = (NSString*)[[basename componentsSeparatedByString: @","] lastObject]; + data = [SocialSharing dataFromBase64String:base64content]; + } else { + fileName = [basename pathComponents].lastObject; + mimeType = [self getMimeTypeFromFileExtension:[basename pathExtension]]; + } + [self.globalMailComposer addAttachmentData:data mimeType:mimeType fileName:fileName]; + } + } + + // remember the command, because we need it in the didFinishWithResult method + _command = command; + + [self.commandDelegate runInBackground:^{ + [[self getTopMostViewController] presentViewController:self.globalMailComposer animated:YES completion:nil]; + }]; + + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } +} + +- (UIViewController*) getTopMostViewController { + UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController; + while (presentingViewController.presentedViewController != nil) { + presentingViewController = presentingViewController.presentedViewController; + } + return presentingViewController; +} + +- (NSString*) getBasenameFromAttachmentPath:(NSString*)path { + if ([path hasPrefix:@"base64:"]) { + NSString* pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:" withString:@""]; + return [pathWithoutPrefix substringToIndex:[pathWithoutPrefix rangeOfString:@"//"].location]; + } + return path; +} + +- (NSString*) getMimeTypeFromFileExtension:(NSString*)extension { + if (!extension) { + return nil; + } + // Get the UTI from the file's extension + CFStringRef ext = (CFStringRef)CFBridgingRetain(extension); + CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL); + // Converting UTI to a mime type + NSString *result = (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType)); + CFRelease(ext); + CFRelease(type); + return result; +} + +/** + * Delegate will be called after the mail composer did finish an action + * to dismiss the view. + */ +- (void) mailComposeController:(MFMailComposeViewController*)controller + didFinishWithResult:(MFMailComposeResult)result + error:(NSError*)error { + bool ok = result == MFMailComposeResultSent; + [self.globalMailComposer dismissViewControllerAnimated:YES completion:^{[self cycleTheGlobalMailComposer];}]; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId]; +} + +-(void)cycleTheGlobalMailComposer { + // we are cycling the damned GlobalMailComposer: http://stackoverflow.com/questions/25604552/i-have-real-misunderstanding-with-mfmailcomposeviewcontroller-in-swift-ios8-in/25604976#25604976 + self.globalMailComposer = nil; + self.globalMailComposer = [[MFMailComposeViewController alloc] init]; +} + +- (bool)canShareViaSMS { + Class messageClass = (NSClassFromString(@"MFMessageComposeViewController")); + return messageClass != nil && [messageClass canSendText]; +} + +- (void)shareViaSMS:(CDVInvokedUrlCommand*)command { + if ([self canShareViaSMS]) { + NSDictionary* options = [command.arguments objectAtIndex:0]; + NSString *phonenumbers = [command.arguments objectAtIndex:1]; + NSString *message = [options objectForKey:@"message"]; + NSString *subject = [options objectForKey:@"subject"]; + NSString *image = [options objectForKey:@"image"]; + + MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init]; + picker.messageComposeDelegate = (id) self; + if (message != (id)[NSNull null]) { + picker.body = message; + } + if (subject != (id)[NSNull null]) { + [picker setSubject:subject]; + } + if (image != nil && image != (id)[NSNull null]) { + BOOL canSendAttachments = [[MFMessageComposeViewController class] respondsToSelector:@selector(canSendAttachments)]; + if (canSendAttachments) { + NSURL *file = [self getFile:image]; + if (file != nil) { + [picker addAttachmentURL:file withAlternateFilename:nil]; + } + } + } + + if (phonenumbers != (id)[NSNull null]) { + [picker setRecipients:[phonenumbers componentsSeparatedByString:@","]]; + } + // remember the command, because we need it in the didFinishWithResult method + _command = command; + [self.commandDelegate runInBackground:^{ + picker.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + [[self getTopMostViewController] presentViewController:picker animated:YES completion:nil]; + }]; + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } +} + +// Dismisses the SMS composition interface when users taps Cancel or Send +- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result { + bool ok = result == MessageComposeResultSent; + [[self getTopMostViewController] dismissViewControllerAnimated:YES completion:nil]; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId]; +} + +- (bool)canShareViaInstagram { + return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"instagram://app"]]; // requires whitelisting on iOS9 +} + +- (bool)canShareViaWhatsApp { + return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"whatsapp://app"]]; // requires whitelisting on iOS9 +} + +// this is only an internal test method for now, can be used to open a share sheet with 'Open in xx' links for tumblr, drive, dropbox, .. +- (void)openImage:(NSString *)imageName { + UIImage* image =[self getImage:imageName]; + if (image != nil) { + NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myTempImage.jpg"]; + [UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES]; + _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]]; + _documentInteractionController.UTI = @""; // TODO find the scheme for google drive and create a shareViaGoogleDrive function + [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES]; + } +} + +- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command { + + // on iOS9 canShareVia('instagram'..) will only work if instagram:// is whitelisted. + // If it's not, this method will ask permission to the user on iOS9 for opening the app, + // which is of course better than Instagram sharing not working at all because you forgot to whitelist it. + // Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('instagram'..) + if (!IsAtLeastiOSVersion(@"9.0")) { + if (![self canShareViaInstagram]) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + } + + NSString *message = [command.arguments objectAtIndex:0]; + // subject is not supported by the SLComposeViewController + NSArray *filenames = [command.arguments objectAtIndex:2]; + + // only use the first image (for now.. maybe we can share in a loop?) + UIImage* image = nil; + for (NSString* filename in filenames) { + image = [self getImage:filename]; + break; + } + +// NSData *imageObj = [NSData dataFromBase64String:objectAtIndex0]; + NSString *tmpDir = NSTemporaryDirectory(); + NSString *path = [tmpDir stringByAppendingPathComponent:@"instagram.igo"]; + [UIImageJPEGRepresentation(image, 1.0) writeToFile:path atomically:YES]; + + _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]]; + _documentInteractionController.delegate = self; + _documentInteractionController.UTI = @"com.instagram.exclusivegram"; + + if (message != (id)[NSNull null]) { + // no longer working, so .. + _documentInteractionController.annotation = @{@"InstagramCaption" : message}; + + // .. we put the message on the clipboard (you app can prompt the user to paste it) + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + [pasteboard setValue:message forPasteboardType:@"public.text"]; + } + + // remember the command for the delegate method + _command = command; + [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.webView animated:YES]; +} + +- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command { + + // on iOS9 canShareVia('whatsapp'..) will only work if whatsapp:// is whitelisted. + // If it's not, this method will ask permission to the user on iOS9 for opening the app, + // which is of course better than WhatsApp sharing not working at all because you forgot to whitelist it. + // Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('whatsapp'..) + if (!IsAtLeastiOSVersion(@"9.0")) { + if (![self canShareViaWhatsApp]) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + } + + NSString *message = [command.arguments objectAtIndex:0]; + // subject is not supported by the SLComposeViewController + NSArray *filenames = [command.arguments objectAtIndex:2]; + NSString *urlString = [command.arguments objectAtIndex:3]; + + // only use the first image (for now.. maybe we can share in a loop?) + UIImage* image = nil; + for (NSString* filename in filenames) { + image = [self getImage:filename]; + break; + } + + // with WhatsApp, we can share an image OR text+url.. image wins if set + if (image != nil) { + NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/whatsAppTmp.wai"]; + [UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES]; + _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]]; + _documentInteractionController.UTI = @"net.whatsapp.image"; + _documentInteractionController.delegate = self; + _command = command; + [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES]; + } else { + // append an url to a message, if both are passed + NSString * shareString = @""; + if (message != (id)[NSNull null]) { + shareString = message; + } + if (urlString != (id)[NSNull null]) { + if ([shareString isEqual: @""]) { + shareString = urlString; + } else { + shareString = [NSString stringWithFormat:@"%@ %@", shareString, urlString]; + } + } + NSString * encodedShareString = [shareString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + // also encode the '=' character + encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"]; + encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"&" withString:@"%26"]; + NSString * encodedShareStringForWhatsApp = [NSString stringWithFormat:@"whatsapp://send?text=%@", encodedShareString]; + + NSURL *whatsappURL = [NSURL URLWithString:encodedShareStringForWhatsApp]; + [[UIApplication sharedApplication] openURL: whatsappURL]; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } +} + +- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command { + self.command = command; + NSArray *filenames = [command.arguments objectAtIndex:0]; + [self.commandDelegate runInBackground:^{ + bool shared = false; + for (NSString* filename in filenames) { + UIImage* image = [self getImage:filename]; + if (image != nil) { + shared = true; + UIImageWriteToSavedPhotosAlbum(image, self, @selector(thisImage:wasSavedToPhotoAlbumWithError:contextInfo:), nil); + } + } + if (!shared) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no valid image was passed"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; + } + }]; +} + +// called from saveToPhotoAlbum, note that we only send feedback for the first image that's being saved (not keeping the callback) +// but since the UIImageWriteToSavedPhotosAlbum function is only called with valid images that should not be a problem +- (void)thisImage:(UIImage *)image wasSavedToPhotoAlbumWithError:(NSError *)error contextInfo:(void*)ctxInfo { + if (error) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.localizedDescription]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; + } +} + +-(UIImage*)getImage: (NSString *)imageName { + UIImage *image = nil; + if (imageName != (id)[NSNull null]) { + if ([imageName hasPrefix:@"http"]) { + image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]]]; + } else if ([imageName hasPrefix:@"www/"]) { + image = [UIImage imageNamed:imageName]; + } else if ([imageName hasPrefix:@"file://"]) { + image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSURL URLWithString:imageName] path]]]; + } else if ([imageName hasPrefix:@"data:"]) { + // using a base64 encoded string + NSURL *imageURL = [NSURL URLWithString:imageName]; + NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; + image = [UIImage imageWithData:imageData]; + } else if ([imageName hasPrefix:@"assets-library://"]) { + // use assets-library + NSURL *imageURL = [NSURL URLWithString:imageName]; + NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; + image = [UIImage imageWithData:imageData]; + } else { + // assume anywhere else, on the local filesystem + image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imageName]]; + } + } + return image; +} + +-(NSURL*)getFile: (NSString *)fileName { + NSURL *file = nil; + if (fileName != (id)[NSNull null]) { + if ([fileName hasPrefix:@"http"]) { + NSURL *url = [NSURL URLWithString:fileName]; + NSData *fileData = [NSData dataWithContentsOfURL:url]; + file = [NSURL fileURLWithPath:[self storeInFile:(NSString*)[[fileName componentsSeparatedByString: @"/"] lastObject] fileData:fileData]]; + } else if ([fileName hasPrefix:@"www/"]) { + NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; + NSString *fullPath = [NSString stringWithFormat:@"%@/%@", bundlePath, fileName]; + file = [NSURL fileURLWithPath:fullPath]; + } else if ([fileName hasPrefix:@"file://"]) { + // stripping the first 6 chars, because the path should start with / instead of file:// + file = [NSURL fileURLWithPath:[fileName substringFromIndex:6]]; + } else if ([fileName hasPrefix:@"data:"]) { + // using a base64 encoded string + // extract some info from the 'fileName', which is for example: data:text/calendar;base64,<encoded stuff here> + NSString *fileType = (NSString*)[[[fileName substringFromIndex:5] componentsSeparatedByString: @";"] objectAtIndex:0]; + fileType = (NSString*)[[fileType componentsSeparatedByString: @"/"] lastObject]; + NSString *base64content = (NSString*)[[fileName componentsSeparatedByString: @","] lastObject]; + NSData *fileData = [SocialSharing dataFromBase64String:base64content]; + file = [NSURL fileURLWithPath:[self storeInFile:[NSString stringWithFormat:@"%@.%@", @"file", fileType] fileData:fileData]]; + } else { + // assume anywhere else, on the local filesystem + file = [NSURL fileURLWithPath:fileName]; + } + } + return file; +} + +-(NSString*) storeInFile: (NSString*) fileName + fileData: (NSData*) fileData { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName]; + [fileData writeToFile:filePath atomically:YES]; + _tempStoredFile = filePath; + return filePath; +} + +- (void) cleanupStoredFiles { + if (_tempStoredFile != nil) { + NSError *error; + [[NSFileManager defaultManager]removeItemAtPath:_tempStoredFile error:&error]; + } +} + ++ (NSData*) dataFromBase64String:(NSString*)aString { + return [[NSData alloc] initWithBase64EncodedString:aString options:0]; +} + +#pragma mark - UIPopoverControllerDelegate methods + +- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view { + NSArray *comps = [[self getIPadPopupCoordinates] componentsSeparatedByString:@","]; + CGRect newRect = [self getPopupRectFromIPadPopupCoordinates:comps]; + rect->origin = newRect.origin; +} + +- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController { + _popover = nil; +} + +#pragma mark - UIDocumentInteractionControllerDelegate methods + +- (void) documentInteractionController: (UIDocumentInteractionController *) controller willBeginSendingToApplication: (NSString *) application { + // note that the application actually contains the app bundle id which was picked (for whatsapp and instagram only) + NSLog(@"SocialSharing app selected: %@", application); +} + +- (void) documentInteractionControllerDidDismissOpenInMenu: (UIDocumentInteractionController *) controller { + if (self.command != nil) { + CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:result callbackId: self.command.callbackId]; + } +} + +@end diff --git a/StoneIsland/platforms/ios/StoneIsland/StoneIsland-Info.plist b/StoneIsland/platforms/ios/StoneIsland/StoneIsland-Info.plist index 160c02b8..1e1baf13 100644 --- a/StoneIsland/platforms/ios/StoneIsland/StoneIsland-Info.plist +++ b/StoneIsland/platforms/ios/StoneIsland/StoneIsland-Info.plist @@ -182,5 +182,20 @@ <array> <string>remote-notification</string> </array> + <key>CFBundleURLTypes</key> + <array> + <dict> + <key>CFBundleURLSchemes</key> + <array> + <string>stoneisland</string> + </array> + </dict> + <dict> + <key>CFBundleURLSchemes</key> + <array> + <string>stoneisland</string> + </array> + </dict> + </array> </dict> </plist>
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/StoneIsland/config.xml b/StoneIsland/platforms/ios/StoneIsland/config.xml index 3eaf2fb1..2fccdd54 100755 --- a/StoneIsland/platforms/ios/StoneIsland/config.xml +++ b/StoneIsland/platforms/ios/StoneIsland/config.xml @@ -36,6 +36,13 @@ <param name="ios-package" value="CDVSplashScreen" /> <param name="onload" value="true" /> </feature> + <feature name="PushNotification"> + <param name="ios-package" value="PushPlugin" /> + </feature> + <feature name="SocialSharing"> + <param name="ios-package" value="SocialSharing" /> + <param name="onload" value="true" /> + </feature> <allow-intent href="itms:*" /> <allow-intent href="itms-apps:*" /> <preference name="KeyboardDisplayRequiresUserAction" value="false" /> @@ -63,7 +70,4 @@ <preference name="StatusBarOverlaysWebView" value="false" /> <preference name="StatusBarBackgroundColor" value="#000000" /> <preference name="StatusBarStyle" value="lightcontent" /> - <feature name="PushNotification"> - <param name="ios-package" value="PushPlugin" /> - </feature> </widget> diff --git a/StoneIsland/platforms/ios/www/cordova_plugins.js b/StoneIsland/platforms/ios/www/cordova_plugins.js index fbdc76c9..f3038524 100644 --- a/StoneIsland/platforms/ios/www/cordova_plugins.js +++ b/StoneIsland/platforms/ios/www/cordova_plugins.js @@ -91,6 +91,20 @@ module.exports = [ "clobbers": [ "PushNotification" ] + }, + { + "file": "plugins/cordova-plugin-customurlscheme/www/ios/LaunchMyApp.js", + "id": "cordova-plugin-customurlscheme.LaunchMyApp", + "clobbers": [ + "window.plugins.launchmyapp" + ] + }, + { + "file": "plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js", + "id": "cordova-plugin-x-socialsharing.SocialSharing", + "clobbers": [ + "window.plugins.socialsharing" + ] } ]; module.exports.metadata = @@ -104,7 +118,9 @@ module.exports.metadata = "cordova-plugin-geolocation": "1.0.1", "cordova-plugin-network-information": "1.0.1", "cordova-plugin-splashscreen": "2.1.0", - "phonegap-plugin-push": "1.4.4" + "phonegap-plugin-push": "1.4.4", + "cordova-plugin-customurlscheme": "4.0.0", + "cordova-plugin-x-socialsharing": "5.0.7" } // BOTTOM OF METADATA });
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/css/account.css b/StoneIsland/platforms/ios/www/css/account.css index 31433871..3c04e1b1 100644 --- a/StoneIsland/platforms/ios/www/css/account.css +++ b/StoneIsland/platforms/ios/www/css/account.css @@ -38,15 +38,17 @@ #orders { display: none; } - -#orders.list { +#orders #order_list { + display: block; } -#orders.single { +#orders #single_order { + display: none; } - -#order_list { +#orders.single #order_list { + display: none; } -#single_order { +#orders.single #single_order { + display: block; } .settings #settings { display: block } @@ -403,4 +405,16 @@ input.switch:checked + label:after { color: #a9a9a9; padding-top: 9px; font-size: 14px; +} + +.container-row input:first-child { +margin-top:10px +} + +.container-row .half-input input { +margin-top:0px +} + +#login .container-row input:first-child { +margin-bottom:9px!important }
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/css/blogs.css b/StoneIsland/platforms/ios/www/css/blogs.css index 0dd14166..0b9f8eed 100644 --- a/StoneIsland/platforms/ios/www/css/blogs.css +++ b/StoneIsland/platforms/ios/www/css/blogs.css @@ -53,18 +53,23 @@ padding: 10px; } -#content .content .body { +#hub .content .body { letter-spacing:0.35px; font-size:12px; width:calc(100vw - 40px); box-sizing:border-box; margin:10px auto 20px; + clear:both } -#content .content .body:last-child { +#hub .content .body:last-child { margin:10px auto 100px; } +.hub_item { + position: relative; +} + .content-header { width:calc(100vw - 40px); box-sizing:border-box; @@ -221,6 +226,10 @@ ul.links { transform: translateZ(0) translateX(-50%) translateY(-50%); } +#archive .menu .items { + border-top: 1px solid transparent; +} + #archive.menu .menu { opacity: 1; pointer-events: auto; @@ -237,6 +246,7 @@ ul.links { } #archive .menu .items { width: 100%; + } #archive .menu .item:first-of-type { border-top: 1px solid black; @@ -246,6 +256,9 @@ ul.links { text-align: center; border-bottom: 1px solid black; background: white; + padding:12px 0; + font-size:14px; + letter-spacing:0.7px } #archive .scroll { @@ -266,3 +279,32 @@ ul.links { #archive .row .text { width: 80%; } + +.gallery-video-post { +position:relative; +} + +.gallery-video-post .play { +width:60px; +height:60px; +border-radius:100px; +background:white; +box-shadow:0px 0px 2px #000; +position:absolute; +transform:translateY(-50%) translateX(-50%); +top:50%; +left:50%; +} + +.gallery-video-post .play:before { +content:''; + width: 0; + height: 0; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + border-left: 8px solid black; + position:absolute; + top:50%; + left:50%; + transform:translateY(-50%) translateX(-50%); +}
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/css/cart.css b/StoneIsland/platforms/ios/www/css/cart.css index a927f511..52b2a4cb 100644 --- a/StoneIsland/platforms/ios/www/css/cart.css +++ b/StoneIsland/platforms/ios/www/css/cart.css @@ -128,6 +128,42 @@ padding-bottom:5px; padding-left:5px; } +.cart_item_price .remove { +display:block; +width:20px; +height:20px; +border:1px solid #d2d2d2; +float:right; +margin-bottom:10px; +position:relative; +} + +.cart_item_price .remove:after { +content:''; +width:1px; +height:22px; +background:#bbb; +position:absolute; +top:50%; +left:50%; +z-index:3; +transform-origin:top left; +transform:rotate(45deg) translateX(-50%) translateY(-50%) +} + +.cart_item_price .remove:before { +content:''; +width:1px; +height:22px; +background:#bbb; +position:absolute; +top:50%; +left:50%; +z-index:3; +transform-origin:top left; +transform:rotate(-45deg) translateX(-50%) translateY(-50%) +} + .cart_item_price .price { font-size:11px; font-weight:bold; @@ -251,3 +287,7 @@ color:#000; .address .save_as_default { display: none; } + +#cart_shipping h3 { +margin-left:5px; +} diff --git a/StoneIsland/platforms/ios/www/css/nav.css b/StoneIsland/platforms/ios/www/css/nav.css index 3d2278d4..61834f8b 100644 --- a/StoneIsland/platforms/ios/www/css/nav.css +++ b/StoneIsland/platforms/ios/www/css/nav.css @@ -58,13 +58,15 @@ padding-bottom:45px!important; } -#nav .main_menu { display: block; } +#nav .main_menu { display: block; animation: mfadein 0.3s; } #nav .account_menu { display: none; } #nav .faq_menu { display: none; } #nav.faq .main_menu, #nav.account .main_menu { display: none; } #nav.account .account_menu { display: block; } -#nav.faq .faq_menu { display: block; } +#nav.faq .faq_menu { display: block; animation: mfadein 0.3s; } + +@keyframes mfadein { 0% { display: none; opacity: 0; } 1% { display:block; opacity: 0; } 100% { opacity: 1; } } #nav .submenu { position: absolute; diff --git a/StoneIsland/platforms/ios/www/css/products.css b/StoneIsland/platforms/ios/www/css/products.css index 2e9ead53..78d02811 100644 --- a/StoneIsland/platforms/ios/www/css/products.css +++ b/StoneIsland/platforms/ios/www/css/products.css @@ -12,6 +12,9 @@ width: 49vw; height: 63vw; } +#collection.gray { + background: rgba(245,245,245,1.0); +} .product #product { display: block } #product { @@ -170,7 +173,12 @@ letter-spacing:0.5px; text-decoration:underline; text-transform:uppercase; - padding:12px 0 2px; + padding:12px 0 0; + +} + +.product .content .fit { + } #product #gallery::before { @@ -215,3 +223,15 @@ display:block; } +#product .content .body { + letter-spacing:0.35px; + font-size:12px; + box-sizing:border-box; + margin:0px auto 20px; + clear:both; + padding:13px 0; +} + +#collection h1 { +background:white +} diff --git a/StoneIsland/platforms/ios/www/img/compass-logo.png b/StoneIsland/platforms/ios/www/img/compass-logo.png Binary files differindex d280a7fa..bdc8d946 100644..100755 --- a/StoneIsland/platforms/ios/www/img/compass-logo.png +++ b/StoneIsland/platforms/ios/www/img/compass-logo.png diff --git a/StoneIsland/platforms/ios/www/img/compass-logo.png.old b/StoneIsland/platforms/ios/www/img/compass-logo.png.old Binary files differnew file mode 100644 index 00000000..d280a7fa --- /dev/null +++ b/StoneIsland/platforms/ios/www/img/compass-logo.png.old diff --git a/StoneIsland/platforms/ios/www/img/spinner.gif b/StoneIsland/platforms/ios/www/img/spinner.gif Binary files differnew file mode 100644 index 00000000..d9e986d3 --- /dev/null +++ b/StoneIsland/platforms/ios/www/img/spinner.gif diff --git a/StoneIsland/platforms/ios/www/index.html b/StoneIsland/platforms/ios/www/index.html index 1f5a8f55..03d4a958 100644 --- a/StoneIsland/platforms/ios/www/index.html +++ b/StoneIsland/platforms/ios/www/index.html @@ -138,8 +138,10 @@ <h1>HUB</h1> <div class="content"> <script type="text/html" class="template"> - <div> + <div class="hub_item"> <div class="gallery gallery-{{id}}"></div> + <div class="gallery-left"></div> + <div class="gallery-right"></div> <div class="content-header"> <div class="content-share"> SHARE + @@ -252,6 +254,8 @@ <div class="item" style="background-image:url({{image}})"></div> </script> </div> + <div class="gallery-left"></div> + <div class="gallery-right"></div> <div class="content"> <div class="product-header"> <span class="num"></span> @@ -301,7 +305,7 @@ <form> <div class="container"> <div class="container-row"> - <input style="margin-bottom:10px" type="email" name="Email" placeholder="EMAIL ADDRESS" required> + <input type="email" name="Email" placeholder="EMAIL ADDRESS" required> <input type="password" name="Password" placeholder="PASSWORD" required> </div> <div class="container-fill"> @@ -342,14 +346,6 @@ <input type="email" name="ConfirmEmail" placeholder="CONFIRM EMAIL ADDRESS" required> <input type="date" name="BirthDay" placeholder="BIRTHDAY (MM/DD/YYYY)" required> - <div class="select-wrapper"> - <span>GENDER</span> - <select name="Gender"> - <option value="NONE">Gender</option> - <option value="U">Male</option> - <option value="D">Female</option> - </select> - </div> <h2>PASSWORD</h2> <input type="password" name="Password" placeholder="PASSWORD (7 CHARACTERS OR MORE)" required> <input type="password" name="Password2" placeholder="CONFIRM PASSWORD" required> @@ -403,15 +399,6 @@ <input type="email" name="Email" placeholder="EMAIL ADDRESS" required> <input type="date" name="BirthDay" placeholder="BIRTHDAY (MM/DD/YYYY)" required> - <div class="select-wrapper"> - <span>GENDER</span> - <select name="Gender"> - <option value="NONE">Gender</option> - <option value="U">Male</option> - <option value="D">Female</option> - </select> - </div> - <h2>CHANGE PASSWORD</h2> <input type="password" name="CurrentPassword" placeholder="CURRENT PASSWORD" required> <input type="password" name="NewPassword" placeholder="NEW PASSWORD (7 CHARACTERS OR MORE)" required> @@ -514,31 +501,75 @@ </div> <div id="orders"> - <h1>ORDERS</h1> - <div id="order_list"> - <div class="scroll"> - <script type="text/html" class="template"> - <div class="item"> + <div class="scroll"> + <h1>ORDERS</h1> + <div id="order_list"> + <div class="list"></div> + <div class="empty">You have no orders.</div> + <script type="text/html" class="list_template"> + <div class="item" data-id="{{id}}"> <div class="details"> Details > </div> <div class="txt"> {{date}}<br> - {{order_id}}<br> + {{id}}<br> {{total}} </div> - <div id="images"> + <div class="images"> </div> </div> </script> </div> - </div> - <div id="single_order"> - <div class="scroll"> - <div class="content"> + + <div id="single_order"> + <div class="order_section"> + <h2>ORDER SUMMARY</h2> + + <div class="rows"> + <script type="text/html" class="item_template"> + <div class="cart_item_info"> + <span class="sku">{{sku}}</span> + <span class="title">{{title}}</span> + <span class="type">{{type}}</span> + <div class="meta"> + <div class="meta-size"><b>SIZE:</b> {{size}}</div> + <div class="meta-color"><b>COLOR:</b> {{color}}</div> + <div class="meta-quantity"><b>QUANTITY:</b> {{quantity}}</div> + </div> + </div> + <div class="cart_item_price"> + <span class="price">{{price}}</span> + </div> + </script> + </div> + <div class="cart-summary"> + <div class="cart-summary-row"> + <span class="label">SUB TOTAL</span> + <span class="subtotal"></span> + </div> + <div class="cart-summary-row"> + <span class="label">ESTIMATED SHIPPING<br>& HANDLING</span> + <span class="shipping"></span> + </div> + <div class="cart-summary-row"> + <span class="label">TAX</span> + <span class="tax"></span> + </div> + <div class="cart-summary-row"> + <span class="label">TOTAL</span> + <span class="total"></span> + </div> + </div> </div> - <script type="text/html" class="template"> - </script> + + <div class="order_section"> + <h2>SHIP TO</h2> + + <div class="shipping_address"></div> + <div class="shipping_method"></div> + </div> + </div> </div> </div> @@ -582,7 +613,7 @@ </div> </div> <div class="cart_item_price"> - <span class="remove">x Remove from cart</span> + <span class="remove"></span> <span class="price">{{price}}</span> </div> </script> @@ -753,6 +784,10 @@ <div class="cc"></div> + <div class="cc_confirm"> + <h3>PLEASE ENTER YOUR SECURITY CODE TO CONFIRM</h3> + <input type="number" name="CvvConfirm" placeholder="SECURITY CODE" required> + </div> </div> <div class="container-fill"> @@ -764,6 +799,7 @@ </div> </div> </div> + <br><br><br><br> </form> </div> @@ -958,13 +994,27 @@ <option value="WV">West Virginia</option> <option value="WI">Wisconsin</option> <option value="WY">Wyoming</option> + <option disabled>_________________</option> + <option value="AB">Alberta</option> + <option value="BC">British Columbia</option> + <option value="MB">Manitoba</option> + <option value="NB">New Brunswick</option> + <option value="NL">Newfoundland and Labrador</option> + <option value="NS">Nova Scotia</option> + <option value="NT">Northwest Territories</option> + <option value="NU">Nunavut</option> + <option value="ON">Ontario</option> + <option value="PE">Prince Edward Island</option> + <option value="SK">Saskatchewan</option> + <option value="QC">Quebec</option> + <option value="YT">Yukon</option> </select> </div> </div> <div class="half-input"> <input type="text" name="ZipCode" placeholder="ZIP" required> <div class="country-wrapper-static"> - UNITED STATES + <span class="country-label">UNITED STATES</span> <!-- <div id="country-select"> <input type="text" name="Country" placeholder="UNITED STATES" required> @@ -1013,6 +1063,10 @@ <script src="js/sdk/product.js"></script> <script src="js/sdk/shipping.js"></script> +<script src="js/lib/etc/push.js"></script> +<script src="js/lib/etc/deeplink.js"></script> +<script src="js/lib/etc/geo.js"></script> + <script src="js/lib/view/View.js"></script> <script src="js/lib/view/Router.js"></script> <script src="js/lib/view/Scrollable.js"></script> @@ -1044,8 +1098,6 @@ <script src="js/lib/account/ShippingView.js"></script> <script src="js/lib/account/SettingsView.js"></script> <script src="js/lib/account/OrdersView.js"></script> -<script src="js/lib/account/orders/OrderList.js"></script> -<script src="js/lib/account/orders/SingleOrder.js"></script> <script src="js/lib/products/CollectionView.js"></script> <script src="js/lib/products/filters/CategoryFilter.js"></script> diff --git a/StoneIsland/platforms/ios/www/js/index.js b/StoneIsland/platforms/ios/www/js/index.js index 546bd637..e6bdf49f 100644 --- a/StoneIsland/platforms/ios/www/js/index.js +++ b/StoneIsland/platforms/ios/www/js/index.js @@ -63,12 +63,13 @@ var app = (function(){ app.ready = function(){ if (window.cordova) { - // cordova.plugins.Keyboard.disableScroll(true) + cordova.plugins.Keyboard.disableScroll(true) + geo.fetch() } app.view = null app.router = new SiteRouter () - app.account.connect( app.router.route.bind(app.router) ) + app.account.connect( app.router.launch.bind(app.router) ) $("body").removeClass("loading") } diff --git a/StoneIsland/platforms/ios/www/js/lib/_router.js b/StoneIsland/platforms/ios/www/js/lib/_router.js index b70d9be8..23daf4c7 100644 --- a/StoneIsland/platforms/ios/www/js/lib/_router.js +++ b/StoneIsland/platforms/ios/www/js/lib/_router.js @@ -47,6 +47,17 @@ var SiteRouter = Router.extend({ } } }, + + initial_route: null, + launch: function(){ + if (this.initial_route) { + this.parseRoute( this.initial_route ) + } + else { + this.route() + } + this.initial_route = null + }, go: function(url){ if (app.view && app.view.hide) { diff --git a/StoneIsland/platforms/ios/www/js/lib/account/OrdersView.js b/StoneIsland/platforms/ios/www/js/lib/account/OrdersView.js index 7283b65c..49901daa 100644 --- a/StoneIsland/platforms/ios/www/js/lib/account/OrdersView.js +++ b/StoneIsland/platforms/ios/www/js/lib/account/OrdersView.js @@ -1,29 +1,188 @@ -var OrdersView = View.extend({ +var OrdersView = ScrollableView.extend({ el: "#orders", + + loaded: false, + + list_template: $("#orders .list_template").html(), + item_template: $("#orders .item_template").html(), events: { "click .back": "back", - "click .details": "load_single", + "click .item": "load_single", }, initialize: function(){ - this.single = new SingleOrder ({ parent: this }) - this.list = new OrderList ({ parent: this }) + this.$list = this.$(".list") + this.$empty = this.$(".empty") + this.$single_order = this.$("#single_order") + + this.$rows = this.$(".rows") + this.$subtotal = this.$(".subtotal") + this.$shipping = this.$(".shipping") + this.$tax = this.$(".tax") + this.$total = this.$(".total") + + this.$shipping_address = this.$(".shipping_address") + this.$shipping_method = this.$(".shipping_method") + + this.scroller = new IScroll('#orders', app.iscroll_options) }, show: function(){ if (! auth.logged_in()) { return app.router.go("intro") } + app.header.set_back(false) app.footer.hide() document.body.className = "orders" - this.el.className = "list" + this.el.className = "" + + if (this.loaded) { + this.populate() + } + else { + this.fetch() + } + }, + + orders: null, + orderLookup: {}, + + fetch: function(){ + this.$list.empty() + this.$empty.hide() + this.loader = new Loader(this.ready.bind(this)) + app.curtain.show("loading") + sdk.account.fetch_orders({ + success: function(data){ + this.loader.register("orders") + this.orders = data.OrderDetails + data.OrderDetails.forEach(function(row){ + this.loader.register(row.OrderNumber) + sdk.account.fetch_single_order({ + id: row.OrderNumber, + success: function(row_data){ + this.orderLookup[ row.OrderNumber ] = row_data.OrderFullDetails + this.loader.ready(row.OrderNumber) + }.bind(this), + error: function(){ + this.orderLookup[ row.OrderNumber ] = null + this.loader.ready(row.OrderNumber) + }.bind(this), + }) + }.bind(this)) + this.loader.ready("orders") + }.bind(this), + error: function(){ + console.log("error fetching orders") + }.bind(this), + }) + }, + + ready: function(){ + this.populate() + app.curtain.hide("loading") + }, + + populate: function(){ + this.$list.empty() + + if (! this.orders.length) { + this.$empty.show() + return + } + else { + this.$empty.hide() + } + this.orders.forEach(function(row){ + var order = this.orderLookup[ row.OrderNumber ] + if (! order) { return } + var t = this.list_template.replace(/{{date}}/g, moment(order['Date']).format("ddd MM/DD/YYYY").toUpperCase()) + .replace(/{{id}}/g, row.OrderNumber) + .replace(/{{total}}/g, as_cash( order.TotalAmount )) + var $t = $(t), $images = $t.find(".images") + order.Items.forEach(function(item){ + var img = new Image () + img.src = sdk.image(item['Code10'], "11_f") + $images.append(img) + }.bind(this)) + this.$list.append($t) + }.bind(this)) + + this.refreshScroller() }, + + load_single: function(e){ + var id = $(e.currentTarget).data("id") + var order = this.orderLookup[ id ] + if (! order) { return } + + console.log(order) + + this.$rows.empty() + + order.Items.forEach(function(item){ + var $el = $("<div class='item'><img src='img/spinner.gif'></div>") + this.$rows.append($el) + var code_ten = item.Code10 + + var code = code_ten.substr(0, 8) + app.product.find(code, function(data, details){ + var descriptions = app.product.get_descriptions( details ) + + var name_partz = descriptions['ModelNames'].split(' ') + var num = name_partz.shift() + var title = name_partz.join(' ') + var type = title_case( descriptions['MicroCategory'] ) + + var color_name, size_name - load_single: function(){ + details.Item.ModelColors.some(function(color){ + if (color['Code10'] == code_ten) { + color_name = color['ColorDescription'] + return true + } + return false + }) + size_name = item.DefaultSize + " " + item.DefaultSizeClassFamily + + var t = this.item_template + .replace(/{{image}}/, sdk.image(item['Code10'], '11_f')) + .replace(/{{sku}}/, num) + .replace(/{{title}}/, title) + .replace(/{{type}}/, type) + .replace(/{{size}}/, size_name || "DEFAULT") + .replace(/{{color}}/, color_name || "DEFAULT") + .replace(/{{quantity}}/, 1) + .replace(/{{price}}/, as_cash(details.Item.Price.DiscountedPrice)) + $el.data("price", details.Item.Price.DiscountedPrice) + $el.html(t) + this.refreshScroller() + }.bind(this)) + }.bind(this)) + + var subtotal = order.ItemsTotalAmount + var shipping_cost = order.Delivery.Amount + var tax = order.SalesTaxAmount + var total = order.TotalToPay + + this.$subtotal.html( as_cash(subtotal) ) + this.$shipping.html( as_cash(shipping_cost) ) + this.$tax.html( as_cash(tax) ) + this.$total.html( as_cash(total) ) + + var street = order.Delivery.Address.replace(/\n$/,"").replace("\n","<br>") + var address = order.Delivery.Name + "<br>" + street + "<br>" + order.Delivery.City + " " + order.Delivery.ZipCode + this.$shipping_address.html(address) + this.$shipping_method.html(order.Delivery.Type + " - " + order.Delivery.Time) + + app.header.set_back(true) + this.$el.addClass("single") }, back: function(){ - this.list.show() + app.header.set_back(false) + this.el.className = "" }, }) + diff --git a/StoneIsland/platforms/ios/www/js/lib/account/ProfileView.js b/StoneIsland/platforms/ios/www/js/lib/account/ProfileView.js index 999e8d65..da25fefc 100644 --- a/StoneIsland/platforms/ios/www/js/lib/account/ProfileView.js +++ b/StoneIsland/platforms/ios/www/js/lib/account/ProfileView.js @@ -5,6 +5,8 @@ var ProfileView = FormView.extend({ events: { }, + action: sdk.account.update, + initialize: function(){ this.$form = this.$("form") this.$msg = this.$(".msg") @@ -30,7 +32,7 @@ var ProfileView = FormView.extend({ if (! data.CurrentPassword && (data.NewPassword || data.Email !== auth.user.Email)) { errors.push([ "CurrentPassword", "Please enter your current password." ]) } if (data.CurrentPassword && ! data.NewPassword) { errors.push([ "NewPassword", "Please enter your new password." ]) } if (data.NewPassword && data.NewPassword.length < 7) { errors.push([ "CurrentPassword", "New password must be 7 characters or more." ]) } - if (data.Gender === "NONE") { errors.push([ "Gender", "Please supply your gender." ]) } + // if (data.Gender === "NONE") { errors.push([ "Gender", "Please supply your gender." ]) } }, finalize: function(data){ @@ -50,7 +52,8 @@ var ProfileView = FormView.extend({ }) } - var submissible_data = _.pick(data, "Name Surname BirthDay Gender YooxLetter".split(" ")) + var submissible_data = _.pick(data, "Name Surname BirthDay YooxLetter".split(" ")) + submissible_data.Gender = "U" // submissible_data.idUser = auth.user_id // submissible_data.AccessToken = auth.access_token // submissible_data.Premium = "false" diff --git a/StoneIsland/platforms/ios/www/js/lib/account/orders/OrderList.js b/StoneIsland/platforms/ios/www/js/lib/account/orders/OrderList.js deleted file mode 100644 index 876f609e..00000000 --- a/StoneIsland/platforms/ios/www/js/lib/account/orders/OrderList.js +++ /dev/null @@ -1,17 +0,0 @@ -var OrderList = ScrollableView.extend({ - - el: "#order_list", - - template: $("#order_list .template"), - - initialize: function(opt){ - this.parent = opt.parent - this.scroller = new IScroll('#order_list', app.iscroll_options) - }, - - show: function(){ - document.body.className = "orders" - app.orders.el.className = "list" - }, - -}) diff --git a/StoneIsland/platforms/ios/www/js/lib/account/orders/SingleOrder.js b/StoneIsland/platforms/ios/www/js/lib/account/orders/SingleOrder.js deleted file mode 100644 index 21416401..00000000 --- a/StoneIsland/platforms/ios/www/js/lib/account/orders/SingleOrder.js +++ /dev/null @@ -1,18 +0,0 @@ -var SingleOrder = ScrollableView.extend({ - - el: "#single_order", - - template: $("#single_order .template"), - - initialize: function(opt){ - this.parent = opt.parent - this.scroller = new IScroll('#single_order', app.iscroll_options) - }, - - show: function(id){ - document.body.className = "orders" - app.orders.el.className = "single" - this.deferScrollToTop() - }, - -}) diff --git a/StoneIsland/platforms/ios/www/js/lib/auth/SignupView.js b/StoneIsland/platforms/ios/www/js/lib/auth/SignupView.js index 7e6fc04d..22b310de 100644 --- a/StoneIsland/platforms/ios/www/js/lib/auth/SignupView.js +++ b/StoneIsland/platforms/ios/www/js/lib/auth/SignupView.js @@ -57,7 +57,7 @@ var SignupView = FormView.extend({ if (data.Password !== data.Password2) { errors.push([ "Password2", "Passwords don't match." ]) } if (! data.Email.match("@")) { errors.push([ "Email", "Email address is not valid." ]) } if (data.Email.toLowerCase() !== data.ConfirmEmail.toLowerCase()) { errors.push([ "ConfirmEmail", "Email addresses don't match." ]) } - if (data.Gender === "NONE") { errors.push([ "Gender", "Please supply your gender." ]) } + // if (data.Gender === "NONE") { errors.push([ "Gender", "Please supply your gender." ]) } if (data.DataProfiling !== "true") { errors.push([ "DataProfiling", "You must consent to use this service." ]) } if (data.DataProfiling2 !== "true") { errors.push([ "DataProfiling2", "You must consent to use this service." ]) } if (! data.YooxLetter) { data.YooxLetter = false } @@ -67,6 +67,7 @@ var SignupView = FormView.extend({ delete data.DataProfiling2 delete data.ConfirmEmail + data.Gender = "U" data.BirthDay += "T00:00:00Z" this.last_data = data diff --git a/StoneIsland/platforms/ios/www/js/lib/blogs/ArchiveView.js b/StoneIsland/platforms/ios/www/js/lib/blogs/ArchiveView.js index 3db5c8da..f0a796bf 100644 --- a/StoneIsland/platforms/ios/www/js/lib/blogs/ArchiveView.js +++ b/StoneIsland/platforms/ios/www/js/lib/blogs/ArchiveView.js @@ -33,6 +33,8 @@ var ArchiveView = ScrollableView.extend({ }, populate: function(data){ + if (this.loaded) { return } + this.loaded = true this.data = data this.$loader.hide() this.$content.empty() diff --git a/StoneIsland/platforms/ios/www/js/lib/blogs/BlogView.js b/StoneIsland/platforms/ios/www/js/lib/blogs/BlogView.js index 19666f8b..3ea35418 100644 --- a/StoneIsland/platforms/ios/www/js/lib/blogs/BlogView.js +++ b/StoneIsland/platforms/ios/www/js/lib/blogs/BlogView.js @@ -42,6 +42,19 @@ var BlogView = View.extend({ if (data.store[0].StoreIsOpen !== "true") { app.closed.storeIsClosed = true } + var fits_large = (data.store[0].FitsLarge === "true") + + app.product.$fit.toggle( fits_large ) + app.product.$sizing.toggle( fits_large ) + + if (data.store[0].BackgroundIsGray === "true") { + app.collection.$el.addClass("gray") + app.product.gallery.$el.addClass("gray") + } + + app.gallery_id = data.store[0].CollectionId + + app.collection.fetch() }, })
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/js/lib/blogs/HubView.js b/StoneIsland/platforms/ios/www/js/lib/blogs/HubView.js index 3b2900ad..a6ae958e 100644 --- a/StoneIsland/platforms/ios/www/js/lib/blogs/HubView.js +++ b/StoneIsland/platforms/ios/www/js/lib/blogs/HubView.js @@ -5,6 +5,8 @@ var HubView = ScrollableView.extend({ events: { "click .store": "store_link", + "click .gallery-left": "gallery_left", + "click .gallery-right": "gallery_right", }, initialize: function(){ @@ -73,6 +75,8 @@ var HubView = ScrollableView.extend({ play.className = "play" $(".gallery-" + row.id).append(play) } + $t.find("gallery-left").remove() + $t.find("gallery-right").remove() } }.bind(this)) @@ -83,5 +87,12 @@ var HubView = ScrollableView.extend({ store_link: function(){ app.router.go("store") }, + + gallery_prev: function(e){ + $(e.currentTarget).closest("hub_item").flickity('prev') + }, + gallery_next: function(e){ + $(e.currentTarget).closest("hub_item").flickity('next') + }, })
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/js/lib/cart/CartShipping.js b/StoneIsland/platforms/ios/www/js/lib/cart/CartShipping.js index 1a9653e1..9b8205c1 100644 --- a/StoneIsland/platforms/ios/www/js/lib/cart/CartShipping.js +++ b/StoneIsland/platforms/ios/www/js/lib/cart/CartShipping.js @@ -101,7 +101,7 @@ var CartShipping = FormView.extend({ shipping_info.City = address_data.City shipping_info.Province = address_data.Province shipping_info.Region = address_data.Province - shipping_info.CountryCode = "US" + shipping_info.CountryCode = CANADIAN_LOOKUP[ address_data.Province ] ? "CA" : "US" return shipping_info }, diff --git a/StoneIsland/platforms/ios/www/js/lib/cart/CartSummary.js b/StoneIsland/platforms/ios/www/js/lib/cart/CartSummary.js index 9a24afa5..72c44405 100644 --- a/StoneIsland/platforms/ios/www/js/lib/cart/CartSummary.js +++ b/StoneIsland/platforms/ios/www/js/lib/cart/CartSummary.js @@ -64,6 +64,7 @@ var CartSummary = ScrollableView.extend({ var size_id = item['Size'] var $el = $("<div>").addClass("cart_item_row") + $el.html("<img src='img/spinner.gif'>") $el.data({ code: code_ten, size: size_id, @@ -95,6 +96,10 @@ var CartSummary = ScrollableView.extend({ // console.log(size) size_name = size['Default']['Text'] size_name = SIZE_LOOKUP[ size_name ] || size_name + if (! size_name && ! size['Default']['Labeled']) { + size_name = size['Default']['Text'] + " " + size['Default']['ClassFamily'] + } + return true } return false @@ -109,7 +114,6 @@ var CartSummary = ScrollableView.extend({ .replace(/{{color}}/, color_name) .replace(/{{quantity}}/, 1) .replace(/{{price}}/, as_cash(details.Item.Price.DiscountedPrice)) - $el.data("price", details.Item.Price.DiscountedPrice) $el.html(t) this.refreshScroller() }.bind(this)) diff --git a/StoneIsland/platforms/ios/www/js/lib/cart/CartThanks.js b/StoneIsland/platforms/ios/www/js/lib/cart/CartThanks.js index 993fd9ac..eb95197b 100644 --- a/StoneIsland/platforms/ios/www/js/lib/cart/CartThanks.js +++ b/StoneIsland/platforms/ios/www/js/lib/cart/CartThanks.js @@ -14,6 +14,8 @@ var CartThanks = View.extend({ app.cart.el.className = "thanks" app.footer.show("< BACK TO COLLECTION") app.footer.hide() + + app.orders.loaded = false }, ok: function(){ diff --git a/StoneIsland/platforms/ios/www/js/lib/etc/deeplink.js b/StoneIsland/platforms/ios/www/js/lib/etc/deeplink.js new file mode 100644 index 00000000..648dd167 --- /dev/null +++ b/StoneIsland/platforms/ios/www/js/lib/etc/deeplink.js @@ -0,0 +1,3 @@ +function handleOpenURL (url) { + app.router.initial_route = url +}
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/js/lib/etc/geo.js b/StoneIsland/platforms/ios/www/js/lib/etc/geo.js new file mode 100644 index 00000000..0270d681 --- /dev/null +++ b/StoneIsland/platforms/ios/www/js/lib/etc/geo.js @@ -0,0 +1,33 @@ +var geo = (function(){ + var geo = {} + + geo.fetch = function(){ + navigator.geolocation.getCurrentPosition(geo.success, geo.error, {timeout: 15000}) + } + + geo.success = function(position){ + var lat_str = as_degrees( position.coords.latitude ) + var lng_str = as_degrees( position.coords.longitude ) + } + + geo.error = function(error){ + $(".latlng").html( "+40° 58' 90\" -74° 04' 46\"" ) + } + + function as_degrees (n) { + var s = "" + if (n >= 0) s += "+" + s += Math.floor(n) + "° " + + n %= 1 + n *= 60 + s += Math.floor(n) + "'" + + n %= 1 + n *= 60 + + s += Math.floor(n) + '"' + } + + return geo +})()
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/js/lib/etc/push.js b/StoneIsland/platforms/ios/www/js/lib/etc/push.js new file mode 100644 index 00000000..ab0c0141 --- /dev/null +++ b/StoneIsland/platforms/ios/www/js/lib/etc/push.js @@ -0,0 +1 @@ +//
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/js/lib/nav/AddressView.js b/StoneIsland/platforms/ios/www/js/lib/nav/AddressView.js index 31e9d802..ad5745fb 100644 --- a/StoneIsland/platforms/ios/www/js/lib/nav/AddressView.js +++ b/StoneIsland/platforms/ios/www/js/lib/nav/AddressView.js @@ -6,6 +6,7 @@ var AddressView = SerializableView.extend({ disabled: false, events: { + "change [name=Province]": 'update_country', }, initialize: function(opt){ @@ -22,6 +23,7 @@ var AddressView = SerializableView.extend({ data.Address2 = address[1] this.$(".address input").val("") this.load_data(data) + this.update_country() }, validate_presence: { @@ -43,8 +45,22 @@ var AddressView = SerializableView.extend({ delete data.Address2 }, + update_country: function(){ + var state = this.$("[name=Province]").val() + console.log(state) + if (CANADIAN_LOOKUP[state]) { + this.$(".country-label").html("CANADA") + } + else { + this.$(".country-label").html("UNITED STATES") + } + }, + }) +var CANADIAN_PROVINCES = "AB BC MB NB NL NS NT NU ON PE SK QC YT".split(" ") +var CANADIAN_LOOKUP = {} +CANADIAN_PROVINCES.forEach(function(k){ CANADIAN_LOOKUP[k] = true }) var COUNTRIES = [ ['Country Name', 'NONE'], diff --git a/StoneIsland/platforms/ios/www/js/lib/nav/CreditCardView.js b/StoneIsland/platforms/ios/www/js/lib/nav/CreditCardView.js index 1855b7a9..ba3ac54a 100644 --- a/StoneIsland/platforms/ios/www/js/lib/nav/CreditCardView.js +++ b/StoneIsland/platforms/ios/www/js/lib/nav/CreditCardView.js @@ -4,7 +4,7 @@ var CreditCardView = SerializableView.extend({ template: $("#creditcard_template").html(), cardOptions: { - accept: ['visa', 'mastercard', 'amex'], + accept: ['visa', 'mastercard', 'amex', 'jcb'], }, events: { @@ -46,8 +46,15 @@ var CreditCardView = SerializableView.extend({ if (! data.ExpirationYear || data.ExpirationYear == "NONE") { errors.push([ "ExpirationYear", "Please select the expiration month." ]) } data.UserId = auth.user_id if (card.valid) { - data.Type = card.card_type.name + data.Type = YOOX_CREDIT_CARD_NAME_LOOKUP[ card.card_type.name ] } }, }) + +var YOOX_CREDIT_CARD_NAME_LOOKUP = { + "visa": "Visa", + "mastercard": "Mastercard", + "amex": "AmericanExpress", + "jcb": "JCB", +} diff --git a/StoneIsland/platforms/ios/www/js/lib/products/CollectionView.js b/StoneIsland/platforms/ios/www/js/lib/products/CollectionView.js index e35b789d..056f2a52 100644 --- a/StoneIsland/platforms/ios/www/js/lib/products/CollectionView.js +++ b/StoneIsland/platforms/ios/www/js/lib/products/CollectionView.js @@ -33,7 +33,7 @@ var CollectionView = ScrollableView.extend({ if (this.loaded) { return this.populate(this.data) } - this.fetch() + // this.fetch() }, save: function(){ @@ -44,7 +44,7 @@ var CollectionView = ScrollableView.extend({ if (this.loaded) return this.$loader.show() sdk.product.collection({ - gallery_id: 32780, + gallery_id: app.gallery_id, success: this.populate.bind(this) }) }, @@ -65,6 +65,7 @@ var CollectionView = ScrollableView.extend({ this.deferScrollToTop() } this.afterFetchCallback && this.afterFetchCallback() + app.collection.deferRefresh() }, append: function(item){ diff --git a/StoneIsland/platforms/ios/www/js/lib/products/GalleryView.js b/StoneIsland/platforms/ios/www/js/lib/products/GalleryView.js index ea4637eb..b36c5df2 100644 --- a/StoneIsland/platforms/ios/www/js/lib/products/GalleryView.js +++ b/StoneIsland/platforms/ios/www/js/lib/products/GalleryView.js @@ -31,7 +31,7 @@ var GalleryView = View.extend({ valid_styles[style] = size } }) - Object.keys(valid_styles).forEach(function(style){ + Object.keys(valid_styles).sort(sort_image_styles).forEach(function(style){ var id = valid_styles[style] + "_" + style var t = this.template.replace(/{{image}}/, sdk.image(code, id)) this.$el.append(t) @@ -62,4 +62,8 @@ var GalleryView = View.extend({ touchend: function(e){ }, -})
\ No newline at end of file +}) + +var YOOX_IMAGE_STYLE_ORDER = "ZZZ d f".split(" ") + +function sort_image_styles (b,a){ return (YOOX_IMAGE_STYLE_ORDER.indexOf(a)) - (YOOX_IMAGE_STYLE_ORDER.indexOf(b)) }
\ No newline at end of file diff --git a/StoneIsland/platforms/ios/www/js/lib/products/ProductView.js b/StoneIsland/platforms/ios/www/js/lib/products/ProductView.js index e151c208..92a0e0f7 100644 --- a/StoneIsland/platforms/ios/www/js/lib/products/ProductView.js +++ b/StoneIsland/platforms/ios/www/js/lib/products/ProductView.js @@ -4,9 +4,12 @@ var ProductView = ScrollableView.extend({ el: "#product", events: { + "click .fit": "scroll_to_bottom", "click .size": "select_size", "click .color": "select_color", "click .share": "share", + "click .gallery-left": "gallery_left", + "click .gallery-right": "gallery_right", }, initialize: function(){ @@ -20,6 +23,8 @@ var ProductView = ScrollableView.extend({ this.$size = this.$(".size") this.$color = this.$(".color") this.$body = this.$(".body") + this.$fit = this.$(".fit") + this.$sizing = this.$(".sizing") }, show: function(){ @@ -41,8 +46,15 @@ var ProductView = ScrollableView.extend({ cache: {}, + gallery_prev: function(){ + this.gallery.gallery.flickity('prev') + }, + gallery_right: function(){ + this.gallery.gallery.flickity('next') + }, + find: function(code, cb){ - data = app.collection.items[code] + data = app.collection.items[code] || {} if (code in this.cache) { return cb(data, this.cache[code]) } @@ -165,9 +177,14 @@ var ProductView = ScrollableView.extend({ sizes: {}, } }) + details['Item']['ModelSizes'].forEach(function(size){ var label = SIZE_LOOKUP[ size['Default']['Text'] ] + if (! label && ! size['Default']['Labeled']) { + label = size['Default']['Text'] + " " + size['Default']['ClassFamily'] + } size_lookup[ label ] = size['SizeId'] + console.log( label ) sizes[ size['SizeId'] ] = { id: label, label: label.toUpperCase(), @@ -242,6 +259,9 @@ var ProductView = ScrollableView.extend({ app.router.go('store') }, + scroll_to_bottom: function(){ + }, + share: function(){ }, diff --git a/StoneIsland/platforms/ios/www/js/lib/view/Scrollable.js b/StoneIsland/platforms/ios/www/js/lib/view/Scrollable.js index 7cd96f89..d06ed590 100644 --- a/StoneIsland/platforms/ios/www/js/lib/view/Scrollable.js +++ b/StoneIsland/platforms/ios/www/js/lib/view/Scrollable.js @@ -1,7 +1,7 @@ var ScrollableView = View.extend({ events: { - "load img": "deferScrollToTop", + "load img": "deferRefresh", }, deferScrollToTop: function(){ @@ -10,8 +10,15 @@ var ScrollableView = View.extend({ refreshScroller: function(){ this.scroller.refresh() + clearTimeout( this.scrollerRefreshTimeout ) }, - + + scrollerRefreshTimeout: null, + deferRefresh: function(){ + clearTimeout( this.scrollerRefreshTimeout ) + this.scrollerRefreshTimeout = setTimeout(this.refreshScroller.bind(this)) + }, + scrollToTop: function(){ this.scroller.refresh() app.collection.scroller.scrollTo(0, 0) diff --git a/StoneIsland/platforms/ios/www/js/sdk/account.js b/StoneIsland/platforms/ios/www/js/sdk/account.js index fe5f03cd..3eb3f3bd 100644 --- a/StoneIsland/platforms/ios/www/js/sdk/account.js +++ b/StoneIsland/platforms/ios/www/js/sdk/account.js @@ -96,6 +96,38 @@ sdk.account = (function(){ }) } + account.fetch_orders = function(opt){ + return $.ajax({ + method: "GET", + url: sdk.path("Account.API/1.5", "users/" + auth.user_id + "/orders.json"), + headers: { + "x-yoox-appname": auth.appname, + "x-yoox-account-token": auth.access_token, + }, + data: JSON.stringify( opt.data ), + success: function(data){ + opt.success(data) + }, + error: opt.error, + }) + } + + account.fetch_single_order = function(opt){ + return $.ajax({ + method: "GET", + url: sdk.path("Account.API/1.5", "users/" + auth.user_id + "/orders/" + opt.id + ".json"), + headers: { + "x-yoox-appname": auth.appname, + "x-yoox-account-token": auth.access_token, + }, + data: JSON.stringify( opt.data ), + success: function(data){ + opt.success(data) + }, + error: opt.error, + }) + } + return account })() diff --git a/StoneIsland/platforms/ios/www/js/sdk/cart.js b/StoneIsland/platforms/ios/www/js/sdk/cart.js index a5e85089..3ff2e1d2 100644 --- a/StoneIsland/platforms/ios/www/js/sdk/cart.js +++ b/StoneIsland/platforms/ios/www/js/sdk/cart.js @@ -140,6 +140,22 @@ sdk.cart = (function(){ }) } + cart.get_card_types = function(opt){ + return $.ajax({ + method: "GET", + url: sdk.path("Cart.API/1.6", "cardTypes.json"), + headers: { + "x-yoox-appname": auth.appname, + "x-yoox-cart-token": cart.token, + }, + data: "", + success: function(data){ + opt.success(data) + }, + error: opt.error, + }) + } + // use with full CC data if not storing it in wallet cart.set_credit_card = function(opt){ return $.ajax({ diff --git a/StoneIsland/platforms/ios/www/plugins/cordova-plugin-customurlscheme/www/ios/LaunchMyApp.js b/StoneIsland/platforms/ios/www/plugins/cordova-plugin-customurlscheme/www/ios/LaunchMyApp.js new file mode 100644 index 00000000..3568c73f --- /dev/null +++ b/StoneIsland/platforms/ios/www/plugins/cordova-plugin-customurlscheme/www/ios/LaunchMyApp.js @@ -0,0 +1,11 @@ +cordova.define("cordova-plugin-customurlscheme.LaunchMyApp", function(require, exports, module) { "use strict"; + +/* + Q: Why an empty file? + A: iOS doesn't need plumbing to get the plugin to work, so.. + - Including no file would mean the import in index.html would differ per platform. + - Also, using one version and adding a userAgent check for Android feels wrong. + - And if you're not using PhoneGap Build, you could paste your handleOpenUrl JS function here. +*/ + +}); diff --git a/StoneIsland/platforms/ios/www/plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js b/StoneIsland/platforms/ios/www/plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js new file mode 100644 index 00000000..aa82acf6 --- /dev/null +++ b/StoneIsland/platforms/ios/www/plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js @@ -0,0 +1,117 @@ +cordova.define("cordova-plugin-x-socialsharing.SocialSharing", function(require, exports, module) { var cordova = require('cordova'); + +function SocialSharing() { +} + +// Override this method (after deviceready) to set the location where you want the iPad popup arrow to appear. +// If not overridden with different values, the popup is not used. Example: +// +// window.plugins.socialsharing.iPadPopupCoordinates = function() { +// return "100,100,200,300"; +// }; +SocialSharing.prototype.iPadPopupCoordinates = function () { + // left,top,width,height + return "-1,-1,-1,-1"; +}; + +SocialSharing.prototype.setIPadPopupCoordinates = function (coords) { + // left,top,width,height + cordova.exec(function() {}, this._getErrorCallback(function() {}, "setIPadPopupCoordinates"), "SocialSharing", "setIPadPopupCoordinates", [coords]); +}; + +SocialSharing.prototype.available = function (callback) { + cordova.exec(function (avail) { + callback(avail ? true : false); + }, null, "SocialSharing", "available", []); +}; + +SocialSharing.prototype.share = function (message, subject, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "share"), "SocialSharing", "share", [message, subject, this._asArray(fileOrFileArray), url]); +}; + +SocialSharing.prototype.shareViaTwitter = function (message, file /* multiple not allowed by twitter */, url, successCallback, errorCallback) { + var fileArray = this._asArray(file); + var ecb = this._getErrorCallback(errorCallback, "shareViaTwitter"); + if (fileArray.length > 1) { + ecb("shareViaTwitter supports max one file"); + } else { + cordova.exec(successCallback, ecb, "SocialSharing", "shareViaTwitter", [message, null, fileArray, url]); + } +}; + +SocialSharing.prototype.shareViaFacebook = function (message, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaFacebook"), "SocialSharing", "shareViaFacebook", [message, null, this._asArray(fileOrFileArray), url]); +}; + +SocialSharing.prototype.shareViaFacebookWithPasteMessageHint = function (message, fileOrFileArray, url, pasteMessageHint, successCallback, errorCallback) { + pasteMessageHint = pasteMessageHint || "If you like you can paste a message from your clipboard"; + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaFacebookWithPasteMessageHint"), "SocialSharing", "shareViaFacebookWithPasteMessageHint", [message, null, this._asArray(fileOrFileArray), url, pasteMessageHint]); +}; + +SocialSharing.prototype.shareViaWhatsApp = function (message, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsApp"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url]); +}; + +SocialSharing.prototype.shareViaSMS = function (options, phonenumbers, successCallback, errorCallback) { + var opts = options; + if (typeof options == "string") { + opts = {"message":options}; // for backward compatibility as the options param used to be the message + } + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaSMS"), "SocialSharing", "shareViaSMS", [opts, phonenumbers]); +}; + +SocialSharing.prototype.shareViaEmail = function (message, subject, toArray, ccArray, bccArray, fileOrFileArray, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaEmail"), "SocialSharing", "shareViaEmail", [message, subject, this._asArray(toArray), this._asArray(ccArray), this._asArray(bccArray), this._asArray(fileOrFileArray)]); +}; + +SocialSharing.prototype.canShareVia = function (via, message, subject, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "canShareVia"), "SocialSharing", "canShareVia", [message, subject, this._asArray(fileOrFileArray), url, via]); +}; + +SocialSharing.prototype.canShareViaEmail = function (successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "canShareViaEmail"), "SocialSharing", "canShareViaEmail", []); +}; + +SocialSharing.prototype.shareViaInstagram = function (message, fileOrFileArray, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaInstagram"), "SocialSharing", "shareViaInstagram", [message, null, this._asArray(fileOrFileArray), null]); +}; + +SocialSharing.prototype.shareVia = function (via, message, subject, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareVia"), "SocialSharing", "shareVia", [message, subject, this._asArray(fileOrFileArray), url, via]); +}; + +SocialSharing.prototype.saveToPhotoAlbum = function (fileOrFileArray, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "saveToPhotoAlbum"), "SocialSharing", "saveToPhotoAlbum", [this._asArray(fileOrFileArray)]); +}; + +SocialSharing.prototype._asArray = function (param) { + if (param == null) { + param = []; + } else if (typeof param === 'string') { + param = new Array(param); + } + return param; +}; + +SocialSharing.prototype._getErrorCallback = function (ecb, functionName) { + if (typeof ecb === 'function') { + return ecb; + } else { + return function (result) { + console.log("The injected error callback of '" + functionName + "' received: " + JSON.stringify(result)); + } + } +}; + +SocialSharing.install = function () { + if (!window.plugins) { + window.plugins = {}; + } + + window.plugins.socialsharing = new SocialSharing(); + return window.plugins.socialsharing; +}; + +cordova.addConstructor(SocialSharing.install); + +}); diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/LaunchMyApp-PhoneGap-Plugin.iws b/StoneIsland/plugins/cordova-plugin-customurlscheme/LaunchMyApp-PhoneGap-Plugin.iws new file mode 100644 index 00000000..9879f7f4 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/LaunchMyApp-PhoneGap-Plugin.iws @@ -0,0 +1,1242 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ChangeListManager"> + <list default="true" id="96110866-69f3-4eb0-8539-5f26f68fe1ac" name="Default" comment="" /> + <ignored path="LaunchMyApp-PhoneGap-Plugin.iws" /> + <ignored path=".idea/workspace.xml" /> + <ignored path="$PROJECT_DIR$/out/" /> + <ignored path=".idea/dataSources.local.xml" /> + <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" /> + <option name="TRACKING_ENABLED" value="true" /> + <option name="SHOW_DIALOG" value="false" /> + <option name="HIGHLIGHT_CONFLICTS" value="true" /> + <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> + <option name="LAST_RESOLUTION" value="IGNORE" /> + </component> + <component name="ChangesViewManager" flattened_view="true" show_ignored="false" /> + <component name="CreatePatchCommitExecutor"> + <option name="PATCH_PATH" value="" /> + </component> + <component name="DaemonCodeAnalyzer"> + <disable_hints /> + </component> + <component name="DebuggerManager"> + <breakpoint_any default_suspend_policy="SuspendAll" default_condition_enabled="true" converted="true"> + <breakpoint> + <option name="NOTIFY_CAUGHT" value="true" /> + <option name="NOTIFY_UNCAUGHT" value="true" /> + <option name="ENABLED" value="false" /> + <option name="LOG_ENABLED" value="false" /> + <option name="LOG_EXPRESSION_ENABLED" value="false" /> + <option name="REMOVE_AFTER_HIT" value="false" /> + <option name="SUSPEND_POLICY" value="SuspendAll" /> + <option name="SUSPEND" value="true" /> + <option name="COUNT_FILTER_ENABLED" value="false" /> + <option name="COUNT_FILTER" value="0" /> + <option name="CONDITION_ENABLED" value="true" /> + <option name="CLASS_FILTERS_ENABLED" value="false" /> + <option name="INSTANCE_FILTERS_ENABLED" value="false" /> + <option name="CONDITION" value="" /> + <option name="LOG_MESSAGE" value="" /> + </breakpoint> + <breakpoint> + <option name="NOTIFY_CAUGHT" value="true" /> + <option name="NOTIFY_UNCAUGHT" value="true" /> + <option name="ENABLED" value="false" /> + <option name="LOG_ENABLED" value="false" /> + <option name="LOG_EXPRESSION_ENABLED" value="false" /> + <option name="REMOVE_AFTER_HIT" value="false" /> + <option name="SUSPEND_POLICY" value="SuspendAll" /> + <option name="SUSPEND" value="true" /> + <option name="COUNT_FILTER_ENABLED" value="false" /> + <option name="COUNT_FILTER" value="0" /> + <option name="CONDITION_ENABLED" value="true" /> + <option name="CLASS_FILTERS_ENABLED" value="false" /> + <option name="INSTANCE_FILTERS_ENABLED" value="false" /> + <option name="CONDITION" value="" /> + <option name="LOG_MESSAGE" value="" /> + </breakpoint> + </breakpoint_any> + <breakpoint_rules converted="true" /> + <ui_properties converted="true" /> + </component> + <component name="ExecutionTargetManager" SELECTED_TARGET="default_target" /> + <component name="FavoritesManager"> + <favorites_list name="LaunchMyApp-PhoneGap-Plugin" /> + </component> + <component name="FileEditorManager"> + <leaf> + <file leaf-file-name="package.json" pinned="false" current-in-tab="false"> + <entry file="file://$PROJECT_DIR$/package.json"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="-5.625" vertical-offset="0" max-vertical-offset="720"> + <caret line="9" column="17" selection-start-line="9" selection-start-column="17" selection-end-line="9" selection-end-column="17" /> + <folding /> + </state> + </provider> + </entry> + </file> + <file leaf-file-name="plugin.xml" pinned="false" current-in-tab="false"> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="1695"> + <caret line="7" column="0" selection-start-line="7" selection-start-column="0" selection-end-line="7" selection-end-column="0" /> + <folding /> + </state> + </provider> + </entry> + </file> + <file leaf-file-name="README.md" pinned="false" current-in-tab="false"> + <entry file="file://$PROJECT_DIR$/README.md"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="-2.0" vertical-offset="0" max-vertical-offset="3105"> + <caret line="4" column="24" selection-start-line="4" selection-start-column="24" selection-end-line="4" selection-end-column="24" /> + <folding /> + </state> + </provider> + <provider editor-type-id="MarkdownPreviewEditor"> + <state /> + </provider> + </entry> + </file> + <file leaf-file-name="LaunchMyApp.java" pinned="false" current-in-tab="true"> + <entry file="file://$PROJECT_DIR$/src/android/nl/xservices/plugins/LaunchMyApp.java"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.13907285" vertical-offset="60" max-vertical-offset="1920"> + <caret line="22" column="43" selection-start-line="22" selection-start-column="43" selection-end-line="22" selection-end-column="43" /> + <folding /> + </state> + </provider> + </entry> + </file> + </leaf> + </component> + <component name="FindManager"> + <FindUsagesManager> + <setting name="OPEN_NEW_TAB" value="false" /> + </FindUsagesManager> + </component> + <component name="Git.Settings"> + <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> + </component> + <component name="GitLogSettings"> + <option name="myDateState"> + <MyDateState /> + </option> + </component> + <component name="IdeDocumentHistory"> + <option name="CHANGED_PATHS"> + <list> + <option value="$PROJECT_DIR$/www/android/LaunchMyApp.js" /> + <option value="$PROJECT_DIR$/plugin.xml" /> + <option value="$PROJECT_DIR$/README.md" /> + <option value="$PROJECT_DIR$/package.json" /> + <option value="$PROJECT_DIR$/src/android/nl/xservices/plugins/LaunchMyApp.java" /> + </list> + </option> + </component> + <component name="JsGulpfileManager"> + <detection-done>true</detection-done> + </component> + <component name="PhpWorkspaceProjectConfiguration" backward_compatibility_performed="true" /> + <component name="ProjectFrameBounds"> + <option name="x" value="587" /> + <option name="y" value="23" /> + <option name="width" value="1095" /> + <option name="height" value="926" /> + </component> + <component name="ProjectLevelVcsManager" settingsEditedManually="false"> + <OptionsSetting value="true" id="Add" /> + <OptionsSetting value="true" id="Remove" /> + <OptionsSetting value="true" id="Checkout" /> + <OptionsSetting value="false" id="Update" /> + <OptionsSetting value="true" id="Status" /> + <OptionsSetting value="true" id="Edit" /> + <OptionsSetting value="true" id="Undo Check Out" /> + <OptionsSetting value="true" id="Get Latest Version" /> + <ConfirmationsSetting value="1" id="Add" /> + <ConfirmationsSetting value="0" id="Remove" /> + </component> + <component name="ProjectView"> + <navigator currentView="ProjectPane" proportions="" version="1"> + <flattenPackages /> + <showMembers /> + <showModules /> + <showLibraryContents /> + <hideEmptyPackages /> + <abbreviatePackageNames /> + <autoscrollToSource /> + <autoscrollFromSource /> + <sortByType /> + </navigator> + <panes> + <pane id="ProjectPane"> + <subPane> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="www" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="windows" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="www" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="www" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="android" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="LaunchMyApp-PhoneGap-Plugin" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="src" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="android" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="nl" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="xservices" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="plugins" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + </subPane> + </pane> + <pane id="Scope" /> + <pane id="PackagesPane" /> + </panes> + </component> + <component name="PropertiesComponent"> + <property name="FileHistory.git4idea.history.GitHistoryProvider_flatWidth0" value="64" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_flatWidth1" value="72" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_treeWidth0" value="90" /> + <property name="MemberChooser.sorted" value="false" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_flatOrder1" value="1" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_flatOrder0" value="0" /> + <property name="last_opened_file_path" value="$PROJECT_DIR$/LaunchMyApp-PhoneGap-Plugin.ipr" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_flatWidth2" value="75" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_treeWidth3" value="749" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_flatWidth3" value="812" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_treeWidth2" value="94" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_treeWidth1" value="90" /> + <property name="GoToClass.toSaveIncludeLibraries" value="false" /> + <property name="WebServerToolWindowFactoryState" value="true" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_flatOrder2" value="2" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_flatOrder3" value="3" /> + <property name="MemberChooser.showClasses" value="true" /> + <property name="FullScreen" value="false" /> + <property name="GoToClass.includeLibraries" value="false" /> + <property name="GoToFile.includeJavaFiles" value="false" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_treeOrder0" value="0" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_treeOrder3" value="3" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_treeOrder1" value="1" /> + <property name="FileHistory.git4idea.history.GitHistoryProvider_treeOrder2" value="2" /> + <property name="MemberChooser.copyJavadoc" value="false" /> + </component> + <component name="RecentsManager"> + <key name="CopyFile.RECENT_KEYS"> + <recent name="$PROJECT_DIR$" /> + <recent name="$PROJECT_DIR$/www/ios" /> + <recent name="$PROJECT_DIR$/www/android" /> + <recent name="$PROJECT_DIR$/www" /> + </key> + <key name="MoveFile.RECENT_KEYS"> + <recent name="$PROJECT_DIR$/www/android" /> + <recent name="$PROJECT_DIR$/www" /> + </key> + </component> + <component name="RunManager"> + <configuration default="true" type="#org.jetbrains.idea.devkit.run.PluginConfigurationType" factoryName="Plugin"> + <module name="" /> + <option name="VM_PARAMETERS" value="-Xmx512m -Xms256m -XX:MaxPermSize=250m" /> + <option name="PROGRAM_PARAMETERS" /> + <method /> + </configuration> + <configuration default="true" type="GrailsRunConfigurationType" factoryName="Grails"> + <module name="" /> + <setting name="vmparams" value="" /> + <setting name="cmdLine" value="run-app" /> + <setting name="depsClasspath" value="false" /> + <setting name="passParentEnv" value="true" /> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <setting name="launchBrowser" value="false" /> + <method /> + </configuration> + <configuration default="true" type="Remote" factoryName="Remote"> + <option name="USE_SOCKET_TRANSPORT" value="true" /> + <option name="SERVER_MODE" value="false" /> + <option name="SHMEM_ADDRESS" value="javadebug" /> + <option name="HOST" value="localhost" /> + <option name="PORT" value="5005" /> + <method /> + </configuration> + <configuration default="true" type="PhpLocalRunConfigurationType" factoryName="PHP Console"> + <method /> + </configuration> + <configuration default="true" type="JavascriptDebugType" factoryName="JavaScript Debug"> + <method /> + </configuration> + <configuration default="true" type="Applet" factoryName="Applet"> + <module name="" /> + <option name="MAIN_CLASS_NAME" /> + <option name="HTML_FILE_NAME" /> + <option name="HTML_USED" value="false" /> + <option name="WIDTH" value="400" /> + <option name="HEIGHT" value="300" /> + <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" /> + <option name="VM_PARAMETERS" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" /> + <method /> + </configuration> + <configuration default="true" type="TestNG" factoryName="TestNG"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <module name="" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" /> + <option name="SUITE_NAME" /> + <option name="PACKAGE_NAME" /> + <option name="MAIN_CLASS_NAME" /> + <option name="METHOD_NAME" /> + <option name="GROUP_NAME" /> + <option name="TEST_OBJECT" value="CLASS" /> + <option name="VM_PARAMETERS" value="-ea" /> + <option name="PARAMETERS" /> + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> + <option name="OUTPUT_DIRECTORY" /> + <option name="ANNOTATION_TYPE" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <option name="TEST_SEARCH_SCOPE"> + <value defaultName="moduleWithDependencies" /> + </option> + <option name="USE_DEFAULT_REPORTERS" value="false" /> + <option name="PROPERTIES_FILE" /> + <envs /> + <properties /> + <listeners /> + <method /> + </configuration> + <configuration default="true" type="js.build_tools.gulp" factoryName="Gulp.js"> + <node-options /> + <gulpfile /> + <tasks /> + <pass-parent-envs>true</pass-parent-envs> + <envs /> + <method /> + </configuration> + <configuration default="true" type="PhpUnitRemoteRunConfigurationType" factoryName="PHPUnit on Server"> + <method /> + </configuration> + <configuration default="true" type="JUnit" factoryName="JUnit"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <module name="" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" /> + <option name="PACKAGE_NAME" /> + <option name="MAIN_CLASS_NAME" /> + <option name="METHOD_NAME" /> + <option name="TEST_OBJECT" value="class" /> + <option name="VM_PARAMETERS" value="-ea" /> + <option name="PARAMETERS" /> + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <option name="TEST_SEARCH_SCOPE"> + <value defaultName="moduleWithDependencies" /> + </option> + <envs /> + <patterns /> + <method /> + </configuration> + <configuration default="true" type="FlashRunConfigurationType" factoryName="Flash App"> + <option name="BCName" value="" /> + <option name="IOSSimulatorSdkPath" value="" /> + <option name="adlOptions" value="" /> + <option name="airProgramParameters" value="" /> + <option name="appDescriptorForEmulator" value="Android" /> + <option name="debugTransport" value="USB" /> + <option name="debuggerSdkRaw" value="BC SDK" /> + <option name="emulator" value="NexusOne" /> + <option name="emulatorAdlOptions" value="" /> + <option name="fastPackaging" value="true" /> + <option name="fullScreenHeight" value="0" /> + <option name="fullScreenWidth" value="0" /> + <option name="launchUrl" value="false" /> + <option name="launcherParameters"> + <LauncherParameters> + <option name="browser" value="a7bb68e0-33c0-4d6f-a81a-aac1fdb870c8" /> + <option name="launcherType" value="OSDefault" /> + <option name="newPlayerInstance" value="false" /> + <option name="playerPath" value="/Applications/Flash Player Debugger.app" /> + </LauncherParameters> + </option> + <option name="mobileRunTarget" value="Emulator" /> + <option name="moduleName" value="" /> + <option name="overriddenMainClass" value="" /> + <option name="overriddenOutputFileName" value="" /> + <option name="overrideMainClass" value="false" /> + <option name="runTrusted" value="true" /> + <option name="screenDpi" value="0" /> + <option name="screenHeight" value="0" /> + <option name="screenWidth" value="0" /> + <option name="url" value="http://" /> + <option name="usbDebugPort" value="7936" /> + <method /> + </configuration> + <configuration default="true" type="PHPUnitRunConfigurationType" factoryName="PHPUnit"> + <TestRunner /> + <method /> + </configuration> + <configuration default="true" type="AndroidTestRunConfigurationType" factoryName="Android Tests"> + <module name="" /> + <option name="TESTING_TYPE" value="0" /> + <option name="INSTRUMENTATION_RUNNER_CLASS" value="" /> + <option name="METHOD_NAME" value="" /> + <option name="CLASS_NAME" value="" /> + <option name="PACKAGE_NAME" value="" /> + <option name="TARGET_SELECTION_MODE" value="EMULATOR" /> + <option name="USE_LAST_SELECTED_DEVICE" value="false" /> + <option name="PREFERRED_AVD" value="" /> + <option name="USE_COMMAND_LINE" value="true" /> + <option name="COMMAND_LINE" value="" /> + <option name="WIPE_USER_DATA" value="false" /> + <option name="DISABLE_BOOT_ANIMATION" value="false" /> + <option name="NETWORK_SPEED" value="full" /> + <option name="NETWORK_LATENCY" value="none" /> + <option name="CLEAR_LOGCAT" value="false" /> + <option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" /> + <option name="FILTER_LOGCAT_AUTOMATICALLY" value="true" /> + <method /> + </configuration> + <configuration default="true" type="BashConfigurationType" factoryName="Bash"> + <option name="INTERPRETER_OPTIONS" value="" /> + <option name="INTERPRETER_PATH" value="/bin/bash" /> + <option name="WORKING_DIRECTORY" value="" /> + <option name="PARENT_ENVS" value="true" /> + <option name="SCRIPT_NAME" value="" /> + <option name="PARAMETERS" value="" /> + <module name="" /> + <envs /> + <method /> + </configuration> + <configuration default="true" type="JarApplication" factoryName="JAR Application"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <envs /> + <method /> + </configuration> + <configuration default="true" type="GradleRunConfiguration" factoryName="Gradle"> + <ExternalSystemSettings> + <option name="executionName" /> + <option name="externalProjectPath" /> + <option name="externalSystemIdString" value="GRADLE" /> + <option name="scriptParameters" /> + <option name="taskDescriptions"> + <list /> + </option> + <option name="taskNames"> + <list /> + </option> + <option name="vmOptions" /> + </ExternalSystemSettings> + <method /> + </configuration> + <configuration default="true" type="FlexUnitRunConfigurationType" factoryName="FlexUnit" appDescriptorForEmulator="Android" class_name="" emulatorAdlOptions="" method_name="" package_name="" scope="Class"> + <option name="BCName" value="" /> + <option name="launcherParameters"> + <LauncherParameters> + <option name="browser" value="a7bb68e0-33c0-4d6f-a81a-aac1fdb870c8" /> + <option name="launcherType" value="OSDefault" /> + <option name="newPlayerInstance" value="false" /> + <option name="playerPath" value="/Applications/Flash Player Debugger.app" /> + </LauncherParameters> + </option> + <option name="moduleName" value="" /> + <option name="trusted" value="true" /> + <method /> + </configuration> + <configuration default="true" type="CucumberJavaRunConfigurationType" factoryName="Cucumber java"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <option name="myFilePath" /> + <option name="GLUE" /> + <option name="myNameFilter" /> + <option name="myGeneratedName" /> + <option name="MAIN_CLASS_NAME" /> + <option name="VM_PARAMETERS" /> + <option name="PROGRAM_PARAMETERS" /> + <option name="WORKING_DIRECTORY" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" /> + <option name="ENABLE_SWING_INSPECTOR" value="false" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <module name="" /> + <envs /> + <method /> + </configuration> + <configuration default="true" type="Application" factoryName="Application"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <option name="MAIN_CLASS_NAME" /> + <option name="VM_PARAMETERS" /> + <option name="PROGRAM_PARAMETERS" /> + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" /> + <option name="ENABLE_SWING_INSPECTOR" value="false" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <module name="" /> + <envs /> + <method /> + </configuration> + <configuration default="true" type="AndroidRunConfigurationType" factoryName="Android Application"> + <module name="" /> + <option name="ACTIVITY_CLASS" value="" /> + <option name="MODE" value="default_activity" /> + <option name="DEPLOY" value="true" /> + <option name="ARTIFACT_NAME" value="" /> + <option name="TARGET_SELECTION_MODE" value="EMULATOR" /> + <option name="USE_LAST_SELECTED_DEVICE" value="false" /> + <option name="PREFERRED_AVD" value="" /> + <option name="USE_COMMAND_LINE" value="true" /> + <option name="COMMAND_LINE" value="" /> + <option name="WIPE_USER_DATA" value="false" /> + <option name="DISABLE_BOOT_ANIMATION" value="false" /> + <option name="NETWORK_SPEED" value="full" /> + <option name="NETWORK_LATENCY" value="none" /> + <option name="CLEAR_LOGCAT" value="false" /> + <option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" /> + <option name="FILTER_LOGCAT_AUTOMATICALLY" value="true" /> + <method /> + </configuration> + <list size="0" /> + <configuration name="<template>" type="WebApp" default="true" selected="false"> + <Host>localhost</Host> + <Port>5050</Port> + </configuration> + </component> + <component name="ShelveChangesManager" show_recycled="false" /> + <component name="SvnConfiguration" myUseAcceleration="nothing"> + <configuration /> + </component> + <component name="TaskManager"> + <task active="true" id="Default" summary="Default task"> + <changelist id="96110866-69f3-4eb0-8539-5f26f68fe1ac" name="Default" comment="" /> + <created>1380298653954</created> + <option name="number" value="Default" /> + <updated>1380298653954</updated> + <workItem from="1390857442497" duration="680000" /> + <workItem from="1391027761184" duration="538000" /> + <workItem from="1402047801442" duration="226000" /> + <workItem from="1417632415028" duration="123000" /> + <workItem from="1417702903217" duration="1164000" /> + <workItem from="1417732337674" duration="68000" /> + <workItem from="1432718035482" duration="300000" /> + <workItem from="1434477283713" duration="33000" /> + <workItem from="1434530183600" duration="166000" /> + <workItem from="1434576966771" duration="61000" /> + <workItem from="1435160289082" duration="779000" /> + <workItem from="1437822576701" duration="751000" /> + <workItem from="1440661703645" duration="1158000" /> + </task> + <task id="LOCAL-00001" summary="initial checkin"> + <created>1380299229689</created> + <option name="number" value="00001" /> + <option name="project" value="LOCAL" /> + <updated>1380299229689</updated> + </task> + <task id="LOCAL-00002" summary="initial checkin"> + <created>1380302935690</created> + <option name="number" value="00002" /> + <option name="project" value="LOCAL" /> + <updated>1380302935690</updated> + </task> + <task id="LOCAL-00003" summary="testing scheme"> + <created>1380305538088</created> + <option name="number" value="00003" /> + <option name="project" value="LOCAL" /> + <updated>1380305538088</updated> + </task> + <task id="LOCAL-00004" summary="testing scheme"> + <created>1380305862564</created> + <option name="number" value="00004" /> + <option name="project" value="LOCAL" /> + <updated>1380305862564</updated> + </task> + <task id="LOCAL-00005" summary="testing scheme"> + <created>1380306489891</created> + <option name="number" value="00005" /> + <option name="project" value="LOCAL" /> + <updated>1380306489891</updated> + </task> + <task id="LOCAL-00006" summary="testing scheme"> + <created>1380307532008</created> + <option name="number" value="00006" /> + <option name="project" value="LOCAL" /> + <updated>1380307532008</updated> + </task> + <task id="LOCAL-00007" summary="testing scheme"> + <created>1380307669209</created> + <option name="number" value="00007" /> + <option name="project" value="LOCAL" /> + <updated>1380307669209</updated> + </task> + <task id="LOCAL-00008" summary="testing scheme"> + <created>1380311306948</created> + <option name="number" value="00008" /> + <option name="project" value="LOCAL" /> + <updated>1380311306949</updated> + </task> + <task id="LOCAL-00009" summary="testing scheme"> + <created>1380311921427</created> + <option name="number" value="00009" /> + <option name="project" value="LOCAL" /> + <updated>1380311921427</updated> + </task> + <task id="LOCAL-00010" summary="Android w.i.p."> + <created>1380317008739</created> + <option name="number" value="00010" /> + <option name="project" value="LOCAL" /> + <updated>1380317008739</updated> + </task> + <task id="LOCAL-00011" summary="Android w.i.p."> + <created>1380317829887</created> + <option name="number" value="00011" /> + <option name="project" value="LOCAL" /> + <updated>1380317829888</updated> + </task> + <task id="LOCAL-00012" summary="Android w.i.p."> + <created>1380353871771</created> + <option name="number" value="00012" /> + <option name="project" value="LOCAL" /> + <updated>1380353871771</updated> + </task> + <task id="LOCAL-00013" summary="testing schemes"> + <created>1380358237732</created> + <option name="number" value="00013" /> + <option name="project" value="LOCAL" /> + <updated>1380358237732</updated> + </task> + <task id="LOCAL-00014" summary="testing schemes"> + <created>1380358605885</created> + <option name="number" value="00014" /> + <option name="project" value="LOCAL" /> + <updated>1380358605885</updated> + </task> + <task id="LOCAL-00015" summary="testing schemes"> + <created>1380376149014</created> + <option name="number" value="00015" /> + <option name="project" value="LOCAL" /> + <updated>1380376149014</updated> + </task> + <task id="LOCAL-00016" summary="testing schemes"> + <created>1380378153190</created> + <option name="number" value="00016" /> + <option name="project" value="LOCAL" /> + <updated>1380378153205</updated> + </task> + <task id="LOCAL-00017" summary="cleanup"> + <created>1380383948466</created> + <option name="number" value="00017" /> + <option name="project" value="LOCAL" /> + <updated>1380383948466</updated> + </task> + <task id="LOCAL-00018" summary="docs"> + <created>1380393159906</created> + <option name="number" value="00018" /> + <option name="project" value="LOCAL" /> + <updated>1380393159906</updated> + </task> + <task id="LOCAL-00019" summary="docs"> + <created>1380393508799</created> + <option name="number" value="00019" /> + <option name="project" value="LOCAL" /> + <updated>1380393508799</updated> + </task> + <task id="LOCAL-00020" summary="docs"> + <created>1380393757355</created> + <option name="number" value="00020" /> + <option name="project" value="LOCAL" /> + <updated>1380393757355</updated> + </task> + <task id="LOCAL-00021" summary="docs"> + <created>1380393772924</created> + <option name="number" value="00021" /> + <option name="project" value="LOCAL" /> + <updated>1380393772924</updated> + </task> + <task id="LOCAL-00022" summary="docs"> + <created>1380402190419</created> + <option name="number" value="00022" /> + <option name="project" value="LOCAL" /> + <updated>1380402190419</updated> + </task> + <task id="LOCAL-00023" summary="docs"> + <created>1380402398063</created> + <option name="number" value="00023" /> + <option name="project" value="LOCAL" /> + <updated>1380402398063</updated> + </task> + <task id="LOCAL-00024" summary="docs"> + <created>1380402815702</created> + <option name="number" value="00024" /> + <option name="project" value="LOCAL" /> + <updated>1380402815702</updated> + </task> + <task id="LOCAL-00025" summary="found a way to pass a custom urlscheme when the plugin is added"> + <created>1380488689110</created> + <option name="number" value="00025" /> + <option name="project" value="LOCAL" /> + <updated>1380488689110</updated> + </task> + <task id="LOCAL-00026" summary="docs"> + <created>1380488797067</created> + <option name="number" value="00026" /> + <option name="project" value="LOCAL" /> + <updated>1380488797067</updated> + </task> + <task id="LOCAL-00027" summary="docs"> + <created>1380488875151</created> + <option name="number" value="00027" /> + <option name="project" value="LOCAL" /> + <updated>1380488875151</updated> + </task> + <task id="LOCAL-00028" summary="docs"> + <created>1380489049263</created> + <option name="number" value="00028" /> + <option name="project" value="LOCAL" /> + <updated>1380489049263</updated> + </task> + <task id="LOCAL-00029" summary="- docs - version++"> + <created>1380489820453</created> + <option name="number" value="00029" /> + <option name="project" value="LOCAL" /> + <updated>1380489820453</updated> + </task> + <task id="LOCAL-00030" summary="Implemented feedback by Adobe (plugin should indeed work if no JS handler. Thx guys!)."> + <created>1380567506465</created> + <option name="number" value="00030" /> + <option name="project" value="LOCAL" /> + <updated>1380567506465</updated> + </task> + <task id="LOCAL-00031" summary="Implemented feedback by Adobe (plugin should indeed work if no JS handler. Thx guys!)."> + <created>1380567576964</created> + <option name="number" value="00031" /> + <option name="project" value="LOCAL" /> + <updated>1380567576964</updated> + </task> + <task id="LOCAL-00032" summary="Implemented feedback by Adobe (plugin should indeed work if no JS handler. Thx guys!)."> + <created>1380567665822</created> + <option name="number" value="00032" /> + <option name="project" value="LOCAL" /> + <updated>1380567665822</updated> + </task> + <task id="LOCAL-00033" summary="Upgrading to PhoneGap 3.x"> + <created>1383169425770</created> + <option name="number" value="00033" /> + <option name="project" value="LOCAL" /> + <updated>1383169425770</updated> + </task> + <task id="LOCAL-00034" summary="Upgrading to PhoneGap 3.x (DroidGap is gone)"> + <created>1383172326587</created> + <option name="number" value="00034" /> + <option name="project" value="LOCAL" /> + <updated>1383172326587</updated> + </task> + <task id="LOCAL-00035" summary="Upgrading to PhoneGap 3.x (DroidGap is gone)"> + <created>1383174045796</created> + <option name="number" value="00035" /> + <option name="project" value="LOCAL" /> + <updated>1383174045796</updated> + </task> + <task id="LOCAL-00036" summary="- Check intent when the app is resumed - Plugin rename: launchMyApp --> Custom URL Scheme"> + <created>1390857761557</created> + <option name="number" value="00036" /> + <option name="project" value="LOCAL" /> + <updated>1390857761557</updated> + </task> + <task id="LOCAL-00037" summary="- Check intent when the app is resumed - Plugin rename: launchMyApp --> Custom URL Scheme "> + <created>1390857936690</created> + <option name="number" value="00037" /> + <option name="project" value="LOCAL" /> + <updated>1390857936690</updated> + </task> + <task id="LOCAL-00038" summary="Hotfix for silly clobbers comment f*$%#p"> + <created>1391028041914</created> + <option name="number" value="00038" /> + <option name="project" value="LOCAL" /> + <updated>1391028041914</updated> + </task> + <task id="LOCAL-00039" summary="[#13] cleaned up some now obsolete workarounds for singleTask mode, thanks a lot https://github.com/cef62 !"> + <created>1402048017666</created> + <option name="number" value="00039" /> + <option name="project" value="LOCAL" /> + <updated>1402048017666</updated> + </task> + <task id="LOCAL-00040" summary="Hardening against XSS attacks, thanks Neil Bergman (nbergman@cigital.com)!"> + <created>1417632494865</created> + <option name="number" value="00040" /> + <option name="project" value="LOCAL" /> + <updated>1417632494865</updated> + </task> + <task id="LOCAL-00041" summary="#55 #2 Less aggressive cleanup of the intent"> + <created>1417703047123</created> + <option name="number" value="00041" /> + <option name="project" value="LOCAL" /> + <updated>1417703047123</updated> + </task> + <task id="LOCAL-00042" summary="#55 #2 Less aggressive cleanup of the intent"> + <created>1417706007258</created> + <option name="number" value="00042" /> + <option name="project" value="LOCAL" /> + <updated>1417706007258</updated> + </task> + <task id="LOCAL-00043" summary="Possible fix for #65"> + <created>1434477311221</created> + <option name="number" value="00043" /> + <option name="project" value="LOCAL" /> + <updated>1434477311221</updated> + </task> + <task id="LOCAL-00044" summary="Definite fix for #65"> + <created>1434530307503</created> + <option name="number" value="00044" /> + <option name="project" value="LOCAL" /> + <updated>1434530307503</updated> + </task> + <task id="LOCAL-00045" summary="More definite fix for #65"> + <created>1434577022103</created> + <option name="number" value="00045" /> + <option name="project" value="LOCAL" /> + <updated>1434577022103</updated> + </task> + <task id="LOCAL-00046" summary="Even more definite fix for #65, now also compatible with launching your app from another app (which frankly... doesn't pass a category=browsable)"> + <created>1435161053225</created> + <option name="number" value="00046" /> + <option name="project" value="LOCAL" /> + <updated>1435161053225</updated> + </task> + <task id="LOCAL-00047" summary="#95 Make plugin available on npm"> + <created>1440664137793</created> + <option name="number" value="00047" /> + <option name="project" value="LOCAL" /> + <updated>1440664137793</updated> + </task> + <task id="LOCAL-00048" summary="#90 Attempted to send a second callback for ID: LaunchMyApp Result was: "Invalid action""> + <created>1440664585797</created> + <option name="number" value="00048" /> + <option name="project" value="LOCAL" /> + <updated>1440664585797</updated> + </task> + <option name="localTasksCounter" value="49" /> + <servers /> + </component> + <component name="TimeTrackingManager"> + <option name="totallyTimeSpent" value="6047000" /> + </component> + <component name="ToolWindowManager"> + <frame x="587" y="23" width="1095" height="926" extended-state="0" /> + <editor active="true" /> + <layout> + <window_info id="Palette	" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="UI Designer" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="Changes" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32763532" sideWeight="0.6703807" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="Designer" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Atlassian " active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Palette" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" /> + <window_info id="Remote Host" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="IDEtalk Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="true" content_ui="tabs" /> + <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" /> + <window_info id="IDEtalk" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" /> + <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32439336" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" /> + <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="10" side_tool="false" content_ui="tabs" /> + <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="Maven Projects" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" /> + <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="SLIDING" type="SLIDING" visible="false" weight="0.4" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="Application Servers" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.24121557" sideWeight="0.67006487" order="0" side_tool="false" content_ui="combo" /> + <window_info id="CDI" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" /> + <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" /> + <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="9" side_tool="false" content_ui="combo" /> + <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" /> + <window_info id="JetGradle" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" /> + <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" /> + <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="9" side_tool="false" content_ui="tabs" /> + </layout> + <layout-to-restore> + <window_info id="UI Designer" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Palette	" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Changes" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32763532" sideWeight="0.6703807" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="10" side_tool="false" content_ui="tabs" /> + <window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Remote Host" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" /> + <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="3" side_tool="true" content_ui="tabs" /> + <window_info id="IDEtalk Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="IDEtalk" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" /> + <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.32439336" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" /> + <window_info id="CDI" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="13" side_tool="false" content_ui="tabs" /> + <window_info id="Application Servers" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" /> + <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" weight="0.24121557" sideWeight="0.67006487" order="1" side_tool="false" content_ui="combo" /> + <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" /> + <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="11" side_tool="false" content_ui="combo" /> + <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="10" side_tool="false" content_ui="tabs" /> + <window_info id="Designer" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="JetGradle" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" /> + <window_info id="Atlassian " active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" /> + <window_info id="Palette" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="9" side_tool="false" content_ui="tabs" /> + <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="4" side_tool="true" content_ui="tabs" /> + <window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="11" side_tool="false" content_ui="tabs" /> + <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.25" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Maven Projects" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.33" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" /> + <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="SLIDING" type="SLIDING" visible="false" weight="0.4" sideWeight="0.5" order="9" side_tool="false" content_ui="tabs" /> + <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" weight="0.4" sideWeight="0.5" order="12" side_tool="false" content_ui="tabs" /> + </layout-to-restore> + </component> + <component name="Vcs.Log.UiProperties"> + <option name="RECENTLY_FILTERED_USER_GROUPS"> + <collection /> + </option> + <option name="RECENTLY_FILTERED_BRANCH_GROUPS"> + <collection /> + </option> + </component> + <component name="VcsContentAnnotationSettings"> + <option name="myLimit" value="2678400000" /> + </component> + <component name="VcsManagerConfiguration"> + <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" /> + <option name="CHECK_NEW_TODO" value="false" /> + <option name="myTodoPanelSettings"> + <TodoPanelSettings /> + </option> + <MESSAGE value="initial checkin" /> + <MESSAGE value="testing scheme" /> + <MESSAGE value="Android w.i.p." /> + <MESSAGE value="testing schemes" /> + <MESSAGE value="cleanup" /> + <MESSAGE value="found a way to pass a custom urlscheme when the plugin is added" /> + <MESSAGE value="docs" /> + <MESSAGE value="- docs - version++" /> + <MESSAGE value="Implemented feedback by Adobe (plugin should indeed work if no JS handler. Thx guys!)." /> + <MESSAGE value="Upgrading to PhoneGap 3.x" /> + <MESSAGE value="Upgrading to PhoneGap 3.x (DroidGap is gone)" /> + <MESSAGE value="- Check intent when the app is resumed - Plugin rename: launchMyApp --> Custom URL Scheme" /> + <MESSAGE value="- Check intent when the app is resumed - Plugin rename: launchMyApp --> Custom URL Scheme " /> + <MESSAGE value="Hotfix for silly clobbers comment f*$%#p" /> + <MESSAGE value="[#13] cleaned up some now obsolete workarounds for singleTask mode, thanks a lot https://github.com/cef62 !" /> + <MESSAGE value="Hardening against XSS attacks, thanks Neil Bergman (nbergman@cigital.com)!" /> + <MESSAGE value="#55 #2 Less aggressive cleanup of the intent" /> + <MESSAGE value="Possible fix for #65" /> + <MESSAGE value="Definite fix for #65" /> + <MESSAGE value="More definite fix for #65" /> + <MESSAGE value="Even more definite fix for #65, now also compatible with launching your app from another app (which frankly... doesn't pass a category=browsable)" /> + <MESSAGE value="#95 Make plugin available on npm" /> + <MESSAGE value="#90 Attempted to send a second callback for ID: LaunchMyApp Result was: "Invalid action"" /> + <option name="LAST_COMMIT_MESSAGE" value="#90 Attempted to send a second callback for ID: LaunchMyApp Result was: "Invalid action"" /> + </component> + <component name="XDebuggerManager"> + <breakpoint-manager> + <option name="time" value="1" /> + </breakpoint-manager> + <watches-manager /> + </component> + <component name="antWorkspaceConfiguration"> + <option name="IS_AUTOSCROLL_TO_SOURCE" value="false" /> + <option name="FILTER_TARGETS" value="false" /> + </component> + <component name="atlassian-ide-plugin-workspace"> + <option name="bambooConfiguration"> + <BambooWorkspaceConfiguration> + <option name="view"> + <BambooViewConfigurationBean /> + </option> + </BambooWorkspaceConfiguration> + </option> + <option name="defaultCredentials"> + <UserCfgBean /> + </option> + </component> + <component name="atlassian-ide-plugin-workspace-issues"> + <option name="view"> + <JiraViewConfigurationBean> + <option name="viewFilterId" value="" /> + </JiraViewConfigurationBean> + </option> + </component> + <component name="editorHistoryManager"> + <entry file="file://$PROJECT_DIR$/www/android/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="450"> + <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="285" max-vertical-offset="1035"> + <caret line="19" column="12" selection-start-line="19" selection-start-column="12" selection-end-line="19" selection-end-column="12" /> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/www/android/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="225" max-vertical-offset="315"> + <caret line="15" column="0" selection-start-line="15" selection-start-column="0" selection-end-line="15" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/www/android/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="8" column="22" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/src/android/nl/xservices/plugins/LaunchMyApp.java"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="19" column="16" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="28" column="14" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/www/android/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="6" column="25" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/src/android/nl/xservices/plugins/LaunchMyApp.java"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="26" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/www/android/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="8" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/src/android/nl/xservices/plugins/LaunchMyApp.java"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="26" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/www/android/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="8" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="38" column="18" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/src/android/nl/xservices/plugins/LaunchMyApp.java"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="26" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="16" column="16" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/.gitignore"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.014299333" vertical-offset="0" max-vertical-offset="0"> + <caret line="1" column="5" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/../mgggplus/mgggplus-frontend/www/config.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="0"> + <caret line="12" column="23" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/www/ios/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="743"> + <caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/www/android/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="450"> + <caret line="18" column="22" selection-start-line="18" selection-start-column="22" selection-end-line="18" selection-end-column="22" /> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/www/windows/LaunchMyApp.js"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.1986755" vertical-offset="0" max-vertical-offset="755"> + <caret line="10" column="0" selection-start-line="10" selection-start-column="0" selection-end-line="10" selection-end-column="0" /> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/README.md"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="-2.0" vertical-offset="0" max-vertical-offset="3105"> + <caret line="4" column="24" selection-start-line="4" selection-start-column="24" selection-end-line="4" selection-end-column="24" /> + <folding /> + </state> + </provider> + <provider editor-type-id="MarkdownPreviewEditor"> + <state /> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/package.json"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="-5.625" vertical-offset="0" max-vertical-offset="720"> + <caret line="9" column="17" selection-start-line="9" selection-start-column="17" selection-end-line="9" selection-end-column="17" /> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/plugin.xml"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.0" vertical-offset="0" max-vertical-offset="1695"> + <caret line="7" column="0" selection-start-line="7" selection-start-column="0" selection-end-line="7" selection-end-column="0" /> + <folding /> + </state> + </provider> + </entry> + <entry file="file://$PROJECT_DIR$/src/android/nl/xservices/plugins/LaunchMyApp.java"> + <provider selected="true" editor-type-id="text-editor"> + <state vertical-scroll-proportion="0.13907285" vertical-offset="60" max-vertical-offset="1920"> + <caret line="22" column="43" selection-start-line="22" selection-start-column="43" selection-end-line="22" selection-end-column="43" /> + <folding /> + </state> + </provider> + </entry> + </component> +</project>
\ No newline at end of file diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/README.md b/StoneIsland/plugins/cordova-plugin-customurlscheme/README.md new file mode 100644 index 00000000..2de829c2 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/README.md @@ -0,0 +1,201 @@ +# Custom URL scheme PhoneGap Plugin +#### launch your app by a link like this: `mycoolapp://` +for iOS and Android, by [Eddy Verbruggen](http://www.x-services.nl) +- This repo is for PhoneGap 3.0.0 and up +- For PhoneGap 2.9.0 and lower, [switch to the phonegap-2.9.0-and-lower branch](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin/tree/phonegap-2.9.0-and-lower) + +1. [Description](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#1-description) +2. [Installation](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#2-installation) + 2. [Automatically (CLI / Plugman)](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#automatically-cli--plugman) + 2. [Manually](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#manually) + 2. [PhoneGap Build](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#phonegap-build) +3. [Usage](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#3-usage) +4. [URL Scheme hints](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#4-url-scheme-hints) +5. [License](https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin#5-license) + + +### BEWARE: [This Apache Cordova issue](https://issues.apache.org/jira/browse/CB-7606) causes problems with Cordova-iOS 3.7.0: the `handleOpenURL` function is not invoked upon cold start. Use a higher or lower version than 3.7.0. + +## 1. Description + +This plugin allows you to start your app by calling it with a URL like `mycoolapp://path?foo=bar` + +* Compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman) +* Submitted and waiting for approval at PhoneGap Build ([more information](https://build.phonegap.com/plugins)) + +### iOS specifics +* Forget about [using config.xml to define a URL scheme](https://build.phonegap.com/docs/config-xml#url_schemes). This plugin adds 2 essential enhancements: + - Uniform URL scheme with Android (for which there is no option to define a URL scheme via PhoneGap configuration at all). + - You still need to wire up the Javascript to handle incoming events. This plugin assists you with that. +* Tested on iOS 5.1, 6 and 7. + +### Android specifics +* Unlike iOS, there is no way to use config.xml to define a scheme for your app. Now there is. +* Tested on Android 4.3, will most likely work with 2.2 and up. + + +## 2. Installation + +### Automatically (CLI / Plugman) +LaunchMyApp is compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman). +Replace `mycoolapp` by a nice scheme you want to have your app listen to: + +Latest release on npm: +``` +$ cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=mycoolapp +``` + +Bleeding edge master version from Github: +``` +$ cordova plugin add https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin.git --variable URL_SCHEME=mycoolapp +``` +(Note that the Phonegap CLI didn't support `--variable` before version 3.6.3, so please use the Cordova CLI as shown above in case you're on an older version) + +The LaunchMyApp.js file is brought in automatically. + +Note for iOS: there was a bug in CLI which caused an error in your `*-Info.plist`. +Please manually remove the blank line and whitespace (if any) from `NSMainNibFile` and `NSMainNibFile~ipad` (or your app won't start at all). + + +### Manually +Don't shoot yourself in the foot - use the CLI! That being said, here goes: + +#### iOS +1\. `Copy www/ios/LaunchMyApp.js` to `www/js/plugins/LaunchMyApp.js` and reference it in your `index.html`: +```html +<script type="text/javascript" src="js/plugins/LaunchMyApp.js"></script> +``` + +2\. Add this to your `*-Info.plist` (replace `URL_SCHEME` by a nice scheme you want to have your app listen to, like `mycoolapp`): +```xml +<key>CFBundleURLTypes</key> +<array> + <dict> + <key>CFBundleURLSchemes</key> + <array> + <string>URL_SCHEME</string> + </array> + </dict> +</array> +``` + +#### Android +1\. Copy www/android/LaunchMyApp.js to www/js/plugins/LaunchMyApp.js and reference it in your `index.html`: +```html +<script type="text/javascript" src="js/plugins/LaunchMyApp.js"></script> +``` + +2\. Add the following xml to your `config.xml` to always use the latest version of this plugin: +```xml +<plugin name="LaunchMyApp" value="nl.xservices.plugins.LaunchMyApp"/> +``` + +3\. Copy `LaunchMyApp.java` to `platforms/android/src/nl/xservices/plugins` (create the folders) + +4\. Add the following to your `AndroidManifest.xml` inside the `/manifest/application/activity` node (replace `URL_SCHEME` by a nice scheme you want to have your app listen to, like `mycoolapp`): +```xml +<intent-filter> + <data android:scheme="URL_SCHEME"/> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> +</intent-filter> +``` + +5\. In `AndroidManifest.xml` set the launchMode to singleTask to avoid issues like [#24]. `<activity android:launchMode="singleTask" ..` + +### PhoneGap Build + +Using LaunchMyApp with PhoneGap Build requires you to add the following xml to your `config.xml` to use the latest version of this plugin (replace `mycoolapp` by a nice scheme you want to have your app listen to): +```xml +<gap:plugin name="cordova-plugin-customurlscheme" source="npm"> + <param name="URL_SCHEME" value="mycoolapp" /> +</gap:plugin> +``` + +The LaunchMyApp.js file is brought in automatically. + +NOTE: When Hydration is enabled at PGB, this plugin may not work. + +### Restoring cordova plugin settings on plugin add or update +In order to be able to restore the plugin settings on `cordova plugin add`, one need to add the following feature into config.xml. Note that if you added the plugin with the `--save` param you will find this in your `config.xml` already, except for the `variable` tag which is likely a `param` tag. [Change that.](https://github.com/EddyVerbruggen/Custom-URL-scheme/issues/76) +```xml + <feature name="Custom URL scheme"> + <param name="id" value="cordova-plugin-customurlscheme" /> + <param name="url" value="https://github.com/EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin.git" /> + <variable name="URL_SCHEME" value="mycoolapp" /><!-- change as appropriate --> + </feature> +``` + +Please notice that URL_SCHEME is saved as `variable`, not as `prop`. However if you do `cordova plugin add` with a --save option, cordova will write the URL_SCHEME as a `prop`, you need to change the tag name from `param` to `variable` in this case. + +These plugin restore instructions are tested on: +cordova-cli 4.3.+ and cordova-android 3.7.1+ + + +## 3. Usage + +1a\. Your app can be launced by linking to it like this from a website or an email for example (all of these will work): +```html +<a href="mycoolapp://">Open my app</a> +<a href="mycoolapp://somepath">Open my app</a> +<a href="mycoolapp://somepath?foo=bar">Open my app</a> +<a href="mycoolapp://?foo=bar">Open my app</a> +``` + +`mycoolapp` is the value of URL_SCHEME you used while installing this plugin. + +1b\. If you're trying to open your app from another PhoneGap app, use the InAppBrowser plugin and launch the receiving app like this, to avoid a 'protocol not supported' error: +```html +<button onclick="window.open('mycoolapp://', '_system')">Open the other app</button> +``` + +2\. When your app is launched by a URL, you probably want to do something based on the path and parameters in the URL. For that, you could implement the (optional) `handleOpenURL(url)` method, which receives the URL that was used to launch your app. +```javascript +function handleOpenURL(url) { + console.log("received url: " + url); +} +``` + +If you want to alert the URL for testing the plugin, at least on iOS you need to wrap it in a timeout like this: +```javascript +function handleOpenURL(url) { + setTimeout(function() { + alert("received url: " + url); + }, 0); +} +``` +A more useful implementation would mean parsing the URL, saving any params to sessionStorage and redirecting the app to the correct page inside your app. +All this happens before the first page is loaded. + +## 4. URL Scheme hints +Please choose a URL_SCHEME which which complies to these restrictions: +- Don't use an already registered scheme (like `fb`, `twitter`, `comgooglemaps`, etc). +- Use only lowercase characters. +- Don't use a dash `-` because on Android it will become underscore `_`. +- Use only 1 word (no spaces). + +TIP: test your scheme by installing the app on a device or simulator and typing yourscheme:// in the browser URL bar, or create a test HTML page with a link to your app to impress your buddies. + + +## 5. License + +[The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/atlassian-ide-plugin.xml b/StoneIsland/plugins/cordova-plugin-customurlscheme/atlassian-ide-plugin.xml new file mode 100644 index 00000000..858eed55 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/atlassian-ide-plugin.xml @@ -0,0 +1,5 @@ +<atlassian-ide-plugin>
+ <project-configuration id="1">
+ <servers id="2" />
+ </project-configuration>
+</atlassian-ide-plugin>
\ No newline at end of file diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/package.json b/StoneIsland/plugins/cordova-plugin-customurlscheme/package.json new file mode 100644 index 00000000..de90c8e2 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/package.json @@ -0,0 +1,43 @@ +{ + "name": "cordova-plugin-customurlscheme", + "version": "4.0.0", + "description": "Launch your app by using this URL: mycoolapp://, you can add a path and even pass params like this: mycoolerapp://somepath?foo=bar", + "cordova": { + "id": "cordova-plugin-customurlscheme", + "platforms": [ + "ios", + "android", + "windows8", + "windows" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/EddyVerbruggen/Custom-URL-scheme.git" + }, + "keywords": [ + "Custom URL Scheme", + "URLscheme", + "URL scheme", + "Custom URL", + "Launch My App", + "Launch App", + "ecosystem:cordova", + "cordova-ios", + "cordova-android", + "cordova-windows", + "cordova-windows8" + ], + "engines": [ + { + "name": "cordova", + "version": ">=3.0.0" + } + ], + "author": "Eddy Verbruggen <eddyverbruggen@gmail.com> (https://github.com/EddyVerbruggen)", + "license": "MIT", + "bugs": { + "url": "https://github.com/EddyVerbruggen/Custom-URL-scheme/issues" + }, + "homepage": "https://github.com/EddyVerbruggen/Custom-URL-scheme#readme" +}
\ No newline at end of file diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/plugin.xml b/StoneIsland/plugins/cordova-plugin-customurlscheme/plugin.xml new file mode 100755 index 00000000..1e43e724 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/plugin.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + xmlns:android="http://schemas.android.com/apk/res/android" + id="cordova-plugin-customurlscheme" + version="4.0.0"> + + <name>Custom URL scheme</name> + + <description> + Launch your app by using this URL: mycoolapp:// + You can add a path and even pass params like this: mycoolerapp://somepath?foo=bar + </description> + + <author>Eddy Verbruggen</author> + + <license>MIT</license> + + <keywords>Custom URL Scheme, URLscheme, URL scheme, Custom URL, Launch My App, Launch App</keywords> + + <repo>https://github.com/EddyVerbruggen/Custom-URL-scheme.git</repo> + + <issue>https://github.com/EddyVerbruggen/Custom-URL-scheme/issues</issue> + + <preference name="URL_SCHEME" /> + + <engines> + <engine name="cordova" version=">=3.0.0"/> + </engines> + + <!-- ios --> + <platform name="ios"> + <js-module src="www/ios/LaunchMyApp.js" name="LaunchMyApp"> + <clobbers target="window.plugins.launchmyapp" /> + </js-module> + + <config-file target="*-Info.plist" parent="CFBundleURLTypes"> + <array> + <dict> + <key>CFBundleURLSchemes</key> + <array> + <string>$URL_SCHEME</string> + </array> + </dict> + </array> + </config-file> + </platform> + + <!-- android --> + <platform name="android"> + <js-module src="www/android/LaunchMyApp.js" name="LaunchMyApp"> + <clobbers target="window.plugins.launchmyapp" /> + </js-module> + + <config-file target="res/xml/config.xml" parent="/*"> + <feature name="LaunchMyApp"> + <param name="android-package" value="nl.xservices.plugins.LaunchMyApp"/> + </feature> + </config-file> + + <source-file src="src/android/nl/xservices/plugins/LaunchMyApp.java" target-dir="src/nl/xservices/plugins"/> + + <config-file target="AndroidManifest.xml" parent="/*/application/activity"> + <intent-filter> + <data android:scheme="$URL_SCHEME"/> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + </intent-filter> + </config-file> + </platform> + + <!-- windows8 --> + <platform name="windows8"> + <config-file target="package.appxmanifest" parent="/Package/Applications/Application/Extensions"> + <Extension Category="windows.protocol" StartPage="www/index.html"> + <Protocol Name="$URL_SCHEME" /> + </Extension> + </config-file> + + <js-module src="www/windows/LaunchMyApp.js" name="LaunchMyApp"> + <clobbers target="window.plugins.launchmyapp" /> + </js-module> + </platform> + + <!-- windows --> + <platform name="windows"> + <config-file target="package.windows.appxmanifest" parent="/Package/Applications/Application/Extensions"> + <Extension Category="windows.protocol" StartPage="www/index.html"> + <Protocol Name="$URL_SCHEME" /> + </Extension> + </config-file> + <config-file target="package.windows80.appxmanifest" parent="/Package/Applications/Application/Extensions"> + <Extension Category="windows.protocol" StartPage="www/index.html"> + <Protocol Name="$URL_SCHEME" /> + </Extension> + </config-file> + <config-file target="package.phone.appxmanifest" parent="/Package/Applications/Application/Extensions"> + <Extension Category="windows.protocol" StartPage="www/index.html"> + <Protocol Name="$URL_SCHEME" /> + </Extension> + </config-file> + <js-module src="www/windows/LaunchMyApp.js" name="LaunchMyApp"> + <clobbers target="window.plugins.launchmyapp" /> + </js-module> + </platform> + +</plugin> diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/src/android/nl/xservices/plugins/LaunchMyApp.java b/StoneIsland/plugins/cordova-plugin-customurlscheme/src/android/nl/xservices/plugins/LaunchMyApp.java new file mode 100644 index 00000000..9d3f3d63 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/src/android/nl/xservices/plugins/LaunchMyApp.java @@ -0,0 +1,136 @@ +package nl.xservices.plugins; + +import android.content.Intent; +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaActivity; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Locale; + +public class LaunchMyApp extends CordovaPlugin { + + private static final String ACTION_CHECKINTENT = "checkIntent"; + + @Override + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + if (ACTION_CHECKINTENT.equalsIgnoreCase(action)) { + final Intent intent = ((CordovaActivity) this.webView.getContext()).getIntent(); + final String intentString = intent.getDataString(); + if (intentString != null && intentString.contains("://") && intent.getScheme() != null) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, intent.getDataString())); + intent.setData(null); + } else { + callbackContext.error("App was not started via the launchmyapp URL scheme. Ignoring this errorcallback is the best approach."); + } + return true; + } else { + callbackContext.error("This plugin only responds to the " + ACTION_CHECKINTENT + " action."); + return false; + } + } + + @Override + public void onNewIntent(Intent intent) { + final String intentString = intent.getDataString(); + if (intentString != null && intentString.contains("://") && intent.getScheme() != null) { + intent.setData(null); + try { + StringWriter writer = new StringWriter(intentString.length() * 2); + escapeJavaStyleString(writer, intentString, true, false); + webView.loadUrl("javascript:handleOpenURL('" + writer.toString() + "');"); + } catch (IOException ignore) { + } + } + } + + // Taken from commons StringEscapeUtils + private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote, + boolean escapeForwardSlash) throws IOException { + if (out == null) { + throw new IllegalArgumentException("The Writer must not be null"); + } + if (str == null) { + return; + } + int sz; + sz = str.length(); + for (int i = 0; i < sz; i++) { + char ch = str.charAt(i); + + // handle unicode + if (ch > 0xfff) { + out.write("\\u" + hex(ch)); + } else if (ch > 0xff) { + out.write("\\u0" + hex(ch)); + } else if (ch > 0x7f) { + out.write("\\u00" + hex(ch)); + } else if (ch < 32) { + switch (ch) { + case '\b': + out.write('\\'); + out.write('b'); + break; + case '\n': + out.write('\\'); + out.write('n'); + break; + case '\t': + out.write('\\'); + out.write('t'); + break; + case '\f': + out.write('\\'); + out.write('f'); + break; + case '\r': + out.write('\\'); + out.write('r'); + break; + default: + if (ch > 0xf) { + out.write("\\u00" + hex(ch)); + } else { + out.write("\\u000" + hex(ch)); + } + break; + } + } else { + switch (ch) { + case '\'': + if (escapeSingleQuote) { + out.write('\\'); + } + out.write('\''); + break; + case '"': + out.write('\\'); + out.write('"'); + break; + case '\\': + out.write('\\'); + out.write('\\'); + break; + case '/': + if (escapeForwardSlash) { + out.write('\\'); + } + out.write('/'); + break; + default: + out.write(ch); + break; + } + } + } + } + + private static String hex(char ch) { + return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH); + } +}
\ No newline at end of file diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/www/android/LaunchMyApp.js b/StoneIsland/plugins/cordova-plugin-customurlscheme/www/android/LaunchMyApp.js new file mode 100644 index 00000000..19dbdc0b --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/www/android/LaunchMyApp.js @@ -0,0 +1,24 @@ +(function () { + "use strict"; + + var remainingAttempts = 10; + + function waitForAndCallHandlerFunction(url) { + if (typeof window.handleOpenURL == "function") { + window.handleOpenURL(url); + } else if (remainingAttempts-- > 0) { + setTimeout(function(){waitForAndCallHandlerFunction(url)}, 500); + } + } + + function triggerOpenURL() { + cordova.exec( + waitForAndCallHandlerFunction, + null, + "LaunchMyApp", + "checkIntent", + []); + } + + document.addEventListener("deviceready", triggerOpenURL, false); +}()); diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/www/ios/LaunchMyApp.js b/StoneIsland/plugins/cordova-plugin-customurlscheme/www/ios/LaunchMyApp.js new file mode 100644 index 00000000..a1d60818 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/www/ios/LaunchMyApp.js @@ -0,0 +1,9 @@ +"use strict"; + +/* + Q: Why an empty file? + A: iOS doesn't need plumbing to get the plugin to work, so.. + - Including no file would mean the import in index.html would differ per platform. + - Also, using one version and adding a userAgent check for Android feels wrong. + - And if you're not using PhoneGap Build, you could paste your handleOpenUrl JS function here. +*/ diff --git a/StoneIsland/plugins/cordova-plugin-customurlscheme/www/windows/LaunchMyApp.js b/StoneIsland/plugins/cordova-plugin-customurlscheme/www/windows/LaunchMyApp.js new file mode 100644 index 00000000..a70d5e94 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-customurlscheme/www/windows/LaunchMyApp.js @@ -0,0 +1,10 @@ +(function () { + function activatedHandler(e) { + if (typeof handleOpenURL == 'function' && e.uri) { + handleOpenURL(e.uri.rawUri); + } + }; + + var wui = Windows.UI.WebUI.WebUIApplication; + wui.addEventListener("activated", activatedHandler, false); +}()); diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/README.md b/StoneIsland/plugins/cordova-plugin-x-socialsharing/README.md new file mode 100644 index 00000000..21709982 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/README.md @@ -0,0 +1,471 @@ +# PhoneGap Social Sharing plugin for Android, iOS and Windows Phone + +by [@EddyVerbruggen](http://www.twitter.com/eddyverbruggen), [read my blog about this plugin](http://www.x-services.nl/phonegap-share-plugin-facebook-twitter-social-media/754) + +[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=eddyverbruggen%40gmail%2ecom&lc=US&item_name=cordova%2dplugin%2dsocialsharing¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted) +Every now and then kind folks ask me how they can give me all their money. So if you want to contribute to my pension fund, then please go ahead :) + +<table width="100%"> + <tr> + <td width="100"><a href="http://plugins.telerik.com/plugin/socialsharing"><img src="http://www.x-services.nl/github-images/telerik-verified-plugins-marketplace.png" width="97px" height="71px" alt="Marketplace logo"/></a></td> + <td>For a quick demo app and easy code samples, check out the plugin page at the Verified Plugins Marketplace: http://plugins.telerik.com/plugin/socialsharing</td> + </tr> +</table> + +## 0. Index + +1. [Description](#1-description) +2. [Screenshots](#2-screenshots) +3. [Installation](#3-installation) + 3. [Automatically (CLI / Plugman)](#automatically-cli--plugman) + 3. [Manually](#manually) + 3. [PhoneGap Build](#phonegap-build) +4. Usage + 4. [iOS and Android](#4a-usage-on-ios-and-android) + 4. [Windows Phone](#4b-usage-on-windows-phone) + 4. [Share-popover on iPad](#4c-share-popover-on-ipad) +5. [Credits](#5-credits) +6. [License](#6-license) + +## 1. Description + +This plugin allows you to use the native sharing window of your mobile device. + +* Works on Android, version 2.3.3 and higher (probably 2.2 as well). +* Works on iOS6 and up. +* Works on Windows Phone 8 since v4.0 of this plugin (maybe even WP7, but I have no such testdevice). +* Share text, a link, a images (or other files like pdf or ics). Subject is also supported, when the receiving app supports it. +* Supports sharing files from the internet, the local filesystem, or from the www folder. +* You can skip the sharing dialog and directly share to Twitter, Facebook, or other apps. +* Compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman). +* Officially supported by [PhoneGap Build](https://build.phonegap.com/plugins). + +## 2. Screenshots +iOS 7 (iPhone) + + + +Sharing options are based on what has been setup in the device settings + + + +iOS 7 (iPad) - a popup like this requires [a little more effort](#4c-share-popover-on-ipad) + + + +iOS 6 (iPhone) + + + +Android + + + +Windows Phone 8 + + + +#### Alternative ShareSheet (iOS only, using the [Cordova ActionSheet plugin](https://github.com/EddyVerbruggen/cordova-plugin-actionsheet)) + + + +## 3. Installation + +### Automatically (CLI / Plugman) +SocialSharing is compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman), compatible with [PhoneGap 3.0 CLI](http://docs.phonegap.com/en/3.0.0/guide_cli_index.md.html#The%20Command-line%20Interface_add_features), here's how it works with the CLI: + +``` +$ phonegap local plugin add https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git +``` + +or with Cordova CLI, from npm: +``` +$ cordova plugin add cordova-plugin-x-socialsharing +$ cordova prepare +``` + +SocialSharing.js is brought in automatically. There is no need to change or add anything in your html. + +### Manually + +1\. Add the following xml to all the `config.xml` files you can find: +```xml +<!-- for iOS --> +<feature name="SocialSharing"> + <param name="ios-package" value="SocialSharing" /> +</feature> +``` +```xml +<!-- for Android (you will find one in res/xml) --> +<feature name="SocialSharing"> + <param name="android-package" value="nl.xservices.plugins.SocialSharing" /> +</feature> +``` +```xml +<!-- for Windows Phone --> +<feature name="SocialSharing"> + <param name="wp-package" value="SocialSharing"/> +</feature> +``` + +For sharing remote images (or other files) on Android, the file needs to be stored locally first, so add this permission to `AndroidManifest.xml`: +```xml +<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> +``` + +For iOS, you'll need to add the `Social.framework` and `MessageUI.framework` to your project. Click your project, Build Phases, Link Binary With Libraries, search for and add `Social.framework` and `MessageUI.framework`. + +2\. Grab a copy of SocialSharing.js, add it to your project and reference it in `index.html`: +```html +<script type="text/javascript" src="js/SocialSharing.js"></script> +``` + +3\. Download the source files for iOS and/or Android and copy them to your project. + +iOS: Copy `SocialSharing.h` and `SocialSharing.m` to `platforms/ios/<ProjectName>/Plugins` + +Android: Copy `SocialSharing.java` to `platforms/android/src/nl/xservices/plugins` (create the folders) + +Window Phone: Copy `SocialSharing.cs` to `platforms/wp8/Plugins/nl.x-services.plugins.socialsharing` (create the folders) + +### PhoneGap Build +Just add the following xml to your `config.xml` to always use the latest version of this plugin (which is published to plugins.cordova.io these days): +```xml +<gap:plugin name="cordova-plugin-x-socialsharing" source="npm" /> +``` +or to use an older version, hosted at phonegap build: +```xml +<gap:plugin name="nl.x-services.plugins.socialsharing" version="4.3.16" /> +``` + +SocialSharing.js is brought in automatically. Make sure though you include a reference to cordova.js in your index.html's head: +```html +<script type="text/javascript" src="cordova.js"></script> +``` + +## 4a. Usage on iOS and Android +You can share text, a subject (in case the user selects the email application), (any type and location of) file (like an image), and a link. +However, what exactly gets shared, depends on the application the user chooses to complete the action. A few examples: +- Mail: message, subject, file. +- Twitter: message, image (other filetypes are not supported), link (which is automatically shortened if the Twitter client deems it necessary). +- Google+ / Hangouts (Android only): message, subject, link +- Flickr: message, image (an image is required for this option to show up). +- Facebook Android: sharing a message is not possible. You can share either a link or an image (not both), but a description can not be prefilled. See [this Facebook issue which they won't solve](https://developers.facebook.com/x/bugs/332619626816423/). As an alternative you can use `shareViaFacebookWithPasteMessageHint` since plugin version 4.3.4. See below for details. Also note that sharing a URL on a non standard domain (like .fail) [may not work on Android](https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/issues/253). Make sure you test this. You can use a [link shortener](https://goo.gl) to workaround this issue. +- Facebook iOS: message, image (other filetypes are not supported), link. Beware that since a Fb update in April 2015 sharing a prefilled message is no longer possible when the Fb app is installed (like Android), see #344. Alternative: use `shareViaFacebookWithPasteMessageHint`. + +### Using the share sheet +Here are some examples you can copy-paste to test the various combinations: +```html +<button onclick="window.plugins.socialsharing.share('Message only')">message only</button> +<button onclick="window.plugins.socialsharing.share('Message and subject', 'The subject')">message and subject</button> +<button onclick="window.plugins.socialsharing.share(null, null, null, 'http://www.x-services.nl')">link only</button> +<button onclick="window.plugins.socialsharing.share('Message and link', null, null, 'http://www.x-services.nl')">message and link</button> +<button onclick="window.plugins.socialsharing.share(null, null, 'https://www.google.nl/images/srpr/logo4w.png', null)">image only</button> +// Beware: passing a base64 file as 'data:' is not supported on Android 2.x: https://code.google.com/p/android/issues/detail?id=7901#c43 +// Hint: when sharing a base64 encoded file on Android you can set the filename by passing it as the subject (second param) +<button onclick="window.plugins.socialsharing.share(null, 'Android filename', 'data:image/png;base64,R0lGODlhDAAMALMBAP8AAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAUKAAEALAAAAAAMAAwAQAQZMMhJK7iY4p3nlZ8XgmNlnibXdVqolmhcRQA7', null)">base64 image only</button> +// Hint: you can share multiple files by using an array as thirds param: ['file 1','file 2', ..], but beware of this Android Kitkat Facebook issue: [#164] +<button onclick="window.plugins.socialsharing.share('Message and image', null, 'https://www.google.nl/images/srpr/logo4w.png', null)">message and image</button> +<button onclick="window.plugins.socialsharing.share('Message, image and link', null, 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl')">message, image and link</button> +<button onclick="window.plugins.socialsharing.share('Message, subject, image and link', 'The subject', 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl')">message, subject, image and link</button> +``` + +Example: share a PDF file from the local www folder: +```html +<button onclick="window.plugins.socialsharing.share('Here is your PDF file', 'Your PDF', 'www/files/manual.pdf')">Share PDF</button> +``` + +### Sharing directly to.. + +####Twitter +```html +<!-- unlike most apps Twitter doesn't like it when you use an array to pass multiple files as the second param --> +<button onclick="window.plugins.socialsharing.shareViaTwitter('Message via Twitter')">message via Twitter</button> +<button onclick="window.plugins.socialsharing.shareViaTwitter('Message and link via Twitter', null /* img */, 'http://www.x-services.nl')">msg and link via Twitter</button> +``` + +####Facebook +```html +<button onclick="window.plugins.socialsharing.shareViaFacebook('Message via Facebook', null /* img */, null /* url */, function() {console.log('share ok')}, function(errormsg){alert(errormsg)})">msg via Facebook (with errcallback)</button> +``` + +Facebook with prefilled message - as a workaround for [this Facebook (Android) bug](https://developers.facebook.com/x/bugs/332619626816423/). BEWARE: it's against Facebooks policy to prefil the message, or even hint the user to prefill a message for them. [See this page for details.](https://developers.facebook.com/docs/apps/review/prefill) + +* On Android the user will see a Toast message with a message you control (default: "If you like you can paste a message from your clipboard"). +* On iOS this function used to behave the same as `shareViaFacebook`, but since 4.3.18 a short message is shown prompting the user to paste a message (like Android). This message is not shown in case the Fb app is not installed since the internal iOS Fb share widget still supports prefilling the message. +* iOS9 quirk: if you want to use this method, you need to whitelist `fb://` in your .plist file. +```html +<button onclick="window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint('Message via Facebook', null /* img */, null /* url */, 'Paste it dude!', function() {console.log('share ok')}, function(errormsg){alert(errormsg)})">msg via Facebook (with errcallback)</button> +``` + +Whitelisting Facebook in your app's .plist: +```xml +<key>LSApplicationQueriesSchemes</key> +<array> + <string>fb</string> +</array> +``` + +####Instagram +```html +<button onclick="window.plugins.socialsharing.shareViaInstagram('Message via Instagram', 'https://www.google.nl/images/srpr/logo4w.png', function() {console.log('share ok')}, function(errormsg){alert(errormsg)})">msg via Instagram</button> +``` + +Quirks: +* Instagram no longer permits prefilling a message - you can prompt the user to paste the message you've passed to the plugin because we're adding it to the clipboard for you. + +iOS Quirks: +* Before trying to invoke `shareViaInstagram` please use `canShareVia('instagram'..` AND whitelist the urlscheme (see below). +* Although this plugin follows the Instagram sharing guidelines, the user may not only see Instagram in the share sheet, but also other apps that listen to the "Instagram sharing ID". Just google "com.instagram.exclusivegram" and you see what I mean. + + +#### WhatsApp +* Note that on iOS when sharing an image and text, only the image is shared - let's hope WhatsApp creates a proper iOS extension to fix this. +* Before using this method you may want to use `canShareVia('whatsapp'..` (see below). +```html +<button onclick="window.plugins.socialsharing.shareViaWhatsApp('Message via WhatsApp', null /* img */, null /* url */, function() {console.log('share ok')}, function(errormsg){alert(errormsg)})">msg via WhatsApp (with errcallback)</button> +``` + +####SMS +Note that on Android, SMS via Hangouts may not behave correctly +```html +<!-- Want to share a prefilled SMS text? --> +<button onclick="window.plugins.socialsharing.shareViaSMS('My cool message', null /* see the note below */, function(msg) {console.log('ok: ' + msg)}, function(msg) {alert('error: ' + msg)})">share via SMS</button> +<!-- Want to prefill some phonenumbers as well? Pass this instead of null. Important notes: For stable usage of shareViaSMS on Android 4.4 and up you require to add at least one phonenumber! Also, on Android make sure you use v4.0.3 or higher of this plugin, otherwise sharing multiple numbers to non-Samsung devices will fail --> +<button onclick="window.plugins.socialsharing.shareViaSMS('My cool message', '0612345678,0687654321', function(msg) {console.log('ok: ' + msg)}, function(msg) {alert('error: ' + msg)})">share via SMS</button> +<!-- Need a subject and even image sharing? It's only supported on iOS for now and falls back to just message sharing on Android --> +<button onclick="window.plugins.socialsharing.shareViaSMS({'message':'My cool message', 'subject':'The subject', 'image':'https://www.google.nl/images/srpr/logo4w.png'}, '0612345678,0687654321', function(msg) {console.log('ok: ' + msg)}, function(msg) {alert('error: ' + msg)})">share via SMS</button> +``` + +####Email +Code inspired by the [EmailComposer plugin](https://github.com/katzer/cordova-plugin-email-composer), note that this is not supported on the iOS 8 simulator (an alert will be shown if your try to). +```js +window.plugins.socialsharing.shareViaEmail( + 'Message', // can contain HTML tags, but support on Android is rather limited: http://stackoverflow.com/questions/15136480/how-to-send-html-content-with-image-through-android-default-email-client + 'Subject', + ['to@person1.com', 'to@person2.com'], // TO: must be null or an array + ['cc@person1.com'], // CC: must be null or an array + null, // BCC: must be null or an array + ['https://www.google.nl/images/srpr/logo4w.png','www/localimage.png'], // FILES: can be null, a string, or an array + onSuccess, // called when sharing worked, but also when the user cancelled sharing via email (I've found no way to detect the difference) + onError // called when sh*t hits the fan +); +``` + +If Facebook, Twitter, Instagram, WhatsApp, SMS or Email is not available, the errorCallback is called with the text 'not available'. + +If you feel lucky, you can even try to start any application with the `shareVia` function: +```html +<!-- start facebook on iOS (same as `shareViaFacebook`), if Facebook is not installed, the errorcallback will be invoked with message 'not available' --> +<button onclick="window.plugins.socialsharing.shareVia('com.apple.social.facebook', 'Message via FB', null, null, null, function(){console.log('share ok')}, function(msg) {alert('error: ' + msg)})">message via Facebook</button> +<!-- start facebook on Android (same as `shareViaFacebook`), if Facebook is not installed, the errorcallback will be invoked with message 'not available' --> +<button onclick="window.plugins.socialsharing.shareVia('facebook', 'Message via FB', null, null, null, function(){console.log('share ok')}, function(msg) {alert('error: ' + msg)})">message via Facebook</button> +<!-- start twitter on iOS (same as `shareViaTwitter`), if Twitter is not installed, the errorcallback will be invoked with message 'not available' --> +<button onclick="window.plugins.socialsharing.shareVia('com.apple.social.twitter', 'Message via Twitter', null, null, 'http://www.x-services.nl', function(){console.log('share ok')}, function(msg) {alert('error: ' + msg)})">message and link via Twitter on iOS</button> +<!-- if you share to a non existing/supported app, the errorcallback will be invoked with message 'not available' --> +<button onclick="window.plugins.socialsharing.shareVia('bogus_app', 'Message via Bogus App', null, null, null, function(){console.log('share ok')}, function(msg) {alert('error: ' + msg)})">message via Bogus App</button> +``` + +What can we pass to the `shareVia` function? +* iOS: You are limited to 'com.apple.social.[facebook | twitter | sinaweibo | tencentweibo]'. If an app does not exist, the errorcallback is invoked and iOS shows a popup message asking the user to configure the app. +* Android: Anything that would otherwise appear in the sharing dialoge (in case the `share` function was used. Pass a (part of the) packagename of the app you want to share to. The `shareViaFacebook` function for instance uses `com.facebook.katana` as the packagename fragment. Things like `weibo`, `pinterest` and `com.google.android.apps.plus` (Google+) should work just fine. + +You can even test if a sharing option is available with `canShareVia`! +You'll need to pass everything you want to share, because (at least on Android) some apps may only become available when an image is added. +The function will invoke the successCallback when it can be shared to via `shareVia`, and the errorCallback if not. As a bonus on Android, the errorCallback contains a JSON Array of available packages you can pass to shareVia. +You can even specify the activity if the app offers multiple sharing ways, passing 'packageName/activityName'. (for example, WeChat, passing 'com.tencent.mm' or 'com.tencent.mm/com.tencent.mm.ui.tools.ShareImgUI' to share to chat, passing 'com.tencent.mm/com.tencent.mm.ui.tools.ShareToTimeLineUI' to share to moments). + +```html +<button onclick="window.plugins.socialsharing.canShareVia('com.tencent.mm/com.tencent.mm.ui.tools.ShareToTimeLineUI', 'msg', null, img, null, function(e){alert(e)}, function(e){alert(e)})">is WeChat available on Android?</button> +<button onclick="window.plugins.socialsharing.canShareVia('com.apple.social.facebook', 'msg', null, null, null, function(e){alert(e)}, function(e){alert(e)})">is facebook available on iOS?</button> +// this one requires whitelisting of whatsapp:// on iOS9 in your .plist file +<button onclick="window.plugins.socialsharing.canShareVia('whatsapp', 'msg', null, null, null, function(e){alert(e)}, function(e){alert(e)})">is WhatsApp available?</button> +<button onclick="window.plugins.socialsharing.canShareVia('sms', 'msg', null, null, null, function(e){alert(e)}, function(e){alert(e)})">is SMS available?</button> +<button onclick="window.plugins.socialsharing.canShareVia('instagram', 'msg', null, null, null, function(e){alert(e)}, function(e){alert(e)})">is Instagram available?</button> +<!-- Email is a different beast, so I added a specific method for it --> +<button onclick="window.plugins.socialsharing.canShareViaEmail(function(e){alert(e)}, function(e){alert(e)})">is Email available?</button> +``` + +Want to share images from a local folder (like an image you just selected from the CameraRoll)? +```javascript +// use a local image from inside the www folder: +window.plugins.socialsharing.share(null, null, 'www/image.gif', null); // success/error callback params may be added as 5th and 6th param +// .. or a local image from anywhere else (if permitted): +// local-iOS: +window.plugins.socialsharing.share(null, null, '/Users/username/Library/Application Support/iPhone/6.1/Applications/25A1E7CF-079F-438D-823B-55C6F8CD2DC0/Documents/.nl.x-services.appname/pics/img.jpg'); +// local-iOS-alt: +window.plugins.socialsharing.share(null, null, 'file:///Users/username/Library/Application Support/iPhone/6.1/Applications/25A1E7CF-079F-438D-823B-55C6F8CD2DC0/Documents/.nl.x-services.appname/pics/img.jpg'); +// local-Android: +window.plugins.socialsharing.share(null, null, 'file:///storage/emulated/0/nl.xservices.testapp/5359/Photos/16832/Thumb.jpg'); +// .. or an image from the internet: +window.plugins.socialsharing.share(null, null, 'http://domain.com/image.jpg'); +``` + +If your app still supports iOS5, you'll want to check whether or not the plugin is available as it only supports iOS6 and up. +```javascript +window.plugins.socialsharing.available(function(isAvailable) { + // the boolean is only false on iOS < 6 + if (isAvailable) { + // now use any of the share() functions + } +}); +``` + +If you can't get the plugin to work, have a look at [this demo project](https://github.com/EddyVerbruggen/X-Services-PhoneGap-Build-Plugins-Demo). + +#### Notes about the successCallback (you can just ignore the callbacks if you like) +Since version 3.8 the plugin passes a boolean to the successCallback to let the app know whether or not content was actually shared, or the share widget was closed by the user. +On iOS this works as expected (except for Facebook, in case the app is installed), but on Android some sharing targets may return false, even though sharing succeeded. This is not a limitation of the plugin, it's the target app which doesn't play nice. +To make it more confusing, when sharing via SMS on Android, you'll likely always have the successCallback invoked. Thanks Google. + +#### Sharing multiple images (or other files) +Since version 4.3.0 of this plugin you can pass an array of files to the share and shareVia functions. +```js +// sharing multiple images via Facebook (you can mix protocols and file locations) +window.plugins.socialsharing.shareViaFacebook( + 'Optional message, may be ignored by Facebook app', + ['https://www.google.nl/images/srpr/logo4w.png','www/image.gif'], + null); + +// sharing a PDF and an image +window.plugins.socialsharing.share( + 'Optional message', + 'Optional title', + ['www/manual.pdf','https://www.google.nl/images/srpr/logo4w.png'], + 'http://www.myurl.com'); +``` + +Note that a lot of apps support sharing multiple files, but Twitter just doesn't accept more that one file. + +#### Saving images to the photo album (iOS only currently) +Since version 4.3.16 of this plugin you can save an array of images to the camera roll: +```js +window.plugins.socialsharing.saveToPhotoAlbum( + ['https://www.google.nl/images/srpr/logo4w.png','www/image.gif'], + onSuccess, // optional success function + onError // optional error function +); +``` + +#### iOS quirk (with camera plugin) +When using this plugin in the callback of the Phonegap camera plugin, wrap the call to `share()` in a `setTimeout()`. +The share widget has the same limitation as the alert dialogue [mentioned in the Phonegap documentation](http://docs.phonegap.com/en/2.9.0/cordova_camera_camera.md.html#camera.getPicture_ios_quirks). + +#### Excluding some options from the widget +If you want to exclude (for example) the assign-to-contact and copy-to-pasteboard options, add this to your main plist file: + +```xml +<key>SocialSharingExcludeActivities</key> +<array> + <string>com.apple.UIKit.activity.AssignToContact</string> + <string>com.apple.UIKit.activity.CopyToPasteboard</string> +</array> +``` + +Here's the list of available activities you can disable : + + - com.apple.UIKit.activity.PostToFacebook + - com.apple.UIKit.activity.PostToTwitter + - com.apple.UIKit.activity.PostToFlickr + - com.apple.UIKit.activity.PostToWeibo + - com.apple.UIKit.activity.PostToVimeo + - com.apple.UIKit.activity.TencentWeibo + - com.apple.UIKit.activity.Message + - com.apple.UIKit.activity.Mail + - com.apple.UIKit.activity.Print + - com.apple.UIKit.activity.CopyToPasteboard + - com.apple.UIKit.activity.AssignToContact + - com.apple.UIKit.activity.SaveToCameraRoll + - com.apple.UIKit.activity.AddToReadingList + - com.apple.UIKit.activity.AirDrop + + +## 4b. Usage on Windows Phone +The available methods on WP8 are: `available`, `canShareViaEmail`, `share`, `shareViaEmail` and `shareViaSMS`. +Currently the first two always return true, but this may change in the future in case I can find a way to truly detect the availability. + +The `share` function on WP8 supports two flavours: message only, or a combination of message, title and link. + +Beware: for now please pass null values for all non used attributes, like in the examples below. + +Sharing a message: +```html +<button onclick="window.plugins.socialsharing.share('Message only', null, null, null)">message only</button> +``` + +Sharing a link: +```html +<button onclick="window.plugins.socialsharing.share('Optional message', 'Optional title', null, 'http://www.x-services.nl')">message, title, link</button> +``` + +Sharing an image (only images from the internet are supported). If you pass more than one image as an array, only the first one is used: +```html +<button onclick="window.plugins.socialsharing.share('Optional message', 'Optional title', 'https://www.google.nl/images/srpr/logo4w.png', null)">image only</button> +``` + +## 4c. Share-popover on iPad +Carlos Sola-Llonch, a user of this plugin, pointed me at an [iOS document](https://developer.apple.com/library/ios/documentation/uikit/reference/UIActivityViewController_Class/Reference/Reference.html) +stating "On iPad, you must present the view controller in a popover. On iPhone and iPod touch, you must present it modally." + +He also provided me with the required code to do so (thanks!). I've adapted it a little to make sure current behaviour is +not altered, but with a little extra effort you can use this new popover feature. + +The trick is overriding the function `window.plugins.socialsharing.iPadPopupCoordinates` by your own implementation +to tell the iPad where to show the popup exactly. It need to be a string like "100,200,300,300" (left,top,width,height). + +You need to override this method after `deviceready` has fired. + +You have various options, like checking the click event on a button and determine the event.clientX and event.clientY, +or use this code Carlos showed me to grab the coordinates of a static button somewhere on your page: + +```js +window.plugins.socialsharing.iPadPopupCoordinates = function() { + var rect = document.getElementById('share_button').getBoundingClientRect(); + return rect.left + "," + rect.top + "," + rect.width + "," + rect.height; +}; +``` + +Note that since iOS 8 this popup is the only way Apple allows you to share stuff, so this plugin has been adjusted to use this plugin as standard for iOS 8 and positions +the popup at the bottom of the screen (seems like a logical default because that's where it previously was as well). +You can however override this position in the same way as explained above. + +**Note**: when using the [WkWebView polyfill](https://github.com/Telerik-Verified-Plugins/WKWebView) the `iPadPopupCoordinates` overrides [doesn't work](https://github.com/Telerik-Verified-Plugins/WKWebView/issues/77) so you can call the alternative `setIPadPopupCoordinates` method to define the popup position just before you call the `share` method. + +example : + +```js +var targetRect = event.targetElement.getBoundingClientRect(), + targetBounds = targetRect.left + ',' + targetRect.top + ',' + targetRect.width + ',' + targetRect.height; + +window.plugins.socialsharing.setIPadPopupCoordinates(targetBounds); +window.plugins.socialsharing.share('Hello from iOS :)') +``` + +## 5. Credits ## + +This plugin was enhanced for Plugman / PhoneGap Build by [Eddy Verbruggen](http://www.x-services.nl). +The Android and Windows Phone code was entirely created by the author. +The first iteration of the iOS code was inspired by [Cameron Lerch](https://github.com/bfcam/phonegap-ios-social-plugin). + + +## 6. License + +[The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/package.json b/StoneIsland/plugins/cordova-plugin-x-socialsharing/package.json new file mode 100644 index 00000000..66bc9646 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/package.json @@ -0,0 +1,46 @@ +{ + "name": "cordova-plugin-x-socialsharing", + "version": "5.0.7", + "description": "Share text, images (and other files), or a link via the native sharing widget of your device. Android is fully supported, as well as iOS 6 and up. WP8 has somewhat limited support.", + "cordova": { + "id": "cordova-plugin-x-socialsharing", + "platforms": [ + "ios", + "android", + "wp8" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git" + }, + "keywords": [ + "Social", + "Share", + "Twitter", + "Facebook", + "Email", + "SMS", + "WhatsApp", + "Tumblr", + "Pocket", + "LinkedIn", + "cordova", + "ecosystem:cordova", + "cordova-ios", + "cordova-android", + "cordova-wp8" + ], + "engines": [ + { + "name": "cordova", + "version": ">=3.0.0" + } + ], + "author": "Eddy Verbruggen - @EddyVerbruggen", + "license": "MIT", + "bugs": { + "url": "https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/issues" + }, + "homepage": "https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin#readme" +} diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/plugin.xml b/StoneIsland/plugins/cordova-plugin-x-socialsharing/plugin.xml new file mode 100755 index 00000000..ba7183cb --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/plugin.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + xmlns:android="http://schemas.android.com/apk/res/android" + id="cordova-plugin-x-socialsharing" + version="5.0.7"> + + <name>SocialSharing</name> + + <description> + Share text, images (and other files), or a link via the native sharing widget of your device. + Android is fully supported, as well as iOS 6 and up. WP8 has somewhat limited support. + </description> + + <author>Eddy Verbruggen</author> + + <license>MIT</license> + + <keywords>Social, Share, Twitter, Facebook, Email, SMS, WhatsApp, Tumblr, Pocket, LinkedIn</keywords> + + <repo>https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git</repo> + + <issue>https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/issues</issue> + + <engines> + <engine name="cordova" version=">=3.0.0"/> + </engines> + + <js-module src="www/SocialSharing.js" name="SocialSharing"> + <clobbers target="window.plugins.socialsharing" /> + </js-module> + + <!-- ios --> + <platform name="ios"> + + <config-file target="config.xml" parent="/*"> + <feature name="SocialSharing"> + <param name="ios-package" value="SocialSharing"/> + <param name="onload" value="true" /> + </feature> + </config-file> + + <header-file src="src/ios/SocialSharing.h"/> + <source-file src="src/ios/SocialSharing.m"/> + + <framework src="Social.framework" weak="true" /> + <framework src="MessageUI.framework" weak="true" /> + </platform> + + <!-- android --> + <platform name="android"> + + <config-file target="res/xml/config.xml" parent="/*"> + <feature name="SocialSharing"> + <param name="android-package" value="nl.xservices.plugins.SocialSharing" /> + </feature> + </config-file> + + <config-file target="AndroidManifest.xml" parent="/*"> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + </config-file> + + <source-file src="src/android/nl/xservices/plugins/SocialSharing.java" target-dir="src/nl/xservices/plugins"/> + </platform> + + <!-- wp8 --> + <platform name="wp8"> + <config-file target="config.xml" parent="/*"> + <feature name="SocialSharing"> + <param name="wp-package" value="SocialSharing"/> + </feature> + </config-file> + + <source-file src="src/wp8/SocialSharing.cs" /> + <framework src="src/wp8/SocialSharingSDK/Newtonsoft.Json.dll" custom="true" /> + </platform> + + <!-- windows --> + <platform name="windows"> + <js-module src="src/windows/SocialSharingProxy.js" name="SocialSharingProxy"> + <merges target="" /> + </js-module> + <source-file src="" /> + </platform> + +</plugin> diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-android-share.png b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-android-share.png Binary files differnew file mode 100644 index 00000000..f3ab135f --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-android-share.png diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios6-share.png b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios6-share.png Binary files differnew file mode 100644 index 00000000..63b0b4c5 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios6-share.png diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios7-ipad-share.png b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios7-ipad-share.png Binary files differnew file mode 100644 index 00000000..640c94f8 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios7-ipad-share.png diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios7-share.png b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios7-share.png Binary files differnew file mode 100644 index 00000000..7545d10d --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-ios7-share.png diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-wp8-share.jpg b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-wp8-share.jpg Binary files differnew file mode 100644 index 00000000..0ced2eb5 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshot-wp8-share.jpg diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshots-ios7-shareconfig.png b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshots-ios7-shareconfig.png Binary files differnew file mode 100644 index 00000000..3afe5fef --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/screenshots/screenshots-ios7-shareconfig.png diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/android/nl/xservices/plugins/SocialSharing.java b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/android/nl/xservices/plugins/SocialSharing.java new file mode 100644 index 00000000..f1168f89 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/android/nl/xservices/plugins/SocialSharing.java @@ -0,0 +1,533 @@ +package nl.xservices.plugins; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.*; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.text.Html; +import android.util.Base64; +import android.view.Gravity; +import android.widget.Toast; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SocialSharing extends CordovaPlugin { + + private static final String ACTION_AVAILABLE_EVENT = "available"; + private static final String ACTION_SHARE_EVENT = "share"; + private static final String ACTION_CAN_SHARE_VIA = "canShareVia"; + private static final String ACTION_CAN_SHARE_VIA_EMAIL = "canShareViaEmail"; + private static final String ACTION_SHARE_VIA = "shareVia"; + private static final String ACTION_SHARE_VIA_TWITTER_EVENT = "shareViaTwitter"; + private static final String ACTION_SHARE_VIA_FACEBOOK_EVENT = "shareViaFacebook"; + private static final String ACTION_SHARE_VIA_FACEBOOK_WITH_PASTEMESSAGEHINT = "shareViaFacebookWithPasteMessageHint"; + private static final String ACTION_SHARE_VIA_WHATSAPP_EVENT = "shareViaWhatsApp"; + private static final String ACTION_SHARE_VIA_INSTAGRAM_EVENT = "shareViaInstagram"; + private static final String ACTION_SHARE_VIA_SMS_EVENT = "shareViaSMS"; + private static final String ACTION_SHARE_VIA_EMAIL_EVENT = "shareViaEmail"; + + private static final int ACTIVITY_CODE_SENDVIAEMAIL = 2; + + private CallbackContext _callbackContext; + + private String pasteMessage; + + private abstract class SocialSharingRunnable implements Runnable { + public CallbackContext callbackContext; + SocialSharingRunnable(CallbackContext cb) { + this.callbackContext = cb; + } + } + + @Override + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + this._callbackContext = callbackContext; // only used for onActivityResult + this.pasteMessage = null; + if (ACTION_AVAILABLE_EVENT.equals(action)) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); + return true; + } else if (ACTION_SHARE_EVENT.equals(action)) { + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), null, false); + } else if (ACTION_SHARE_VIA_TWITTER_EVENT.equals(action)) { + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "twitter", false); + } else if (ACTION_SHARE_VIA_FACEBOOK_EVENT.equals(action)) { + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", false); + } else if (ACTION_SHARE_VIA_FACEBOOK_WITH_PASTEMESSAGEHINT.equals(action)) { + this.pasteMessage = args.getString(4); + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", false); + } else if (ACTION_SHARE_VIA_WHATSAPP_EVENT.equals(action)) { + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "whatsapp", false); + } else if (ACTION_SHARE_VIA_INSTAGRAM_EVENT.equals(action)) { + if (notEmpty(args.getString(0))) { + copyHintToClipboard(args.getString(0), "Instagram paste message"); + } + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "instagram", false); + } else if (ACTION_CAN_SHARE_VIA.equals(action)) { + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), true); + } else if (ACTION_CAN_SHARE_VIA_EMAIL.equals(action)) { + if (isEmailAvailable()) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); + return true; + } else { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "not available")); + return false; + } + } else if (ACTION_SHARE_VIA.equals(action)) { + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), false); + } else if (ACTION_SHARE_VIA_SMS_EVENT.equals(action)) { + return invokeSMSIntent(callbackContext, args.getJSONObject(0), args.getString(1)); + } else if (ACTION_SHARE_VIA_EMAIL_EVENT.equals(action)) { + return invokeEmailIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.isNull(3) ? null : args.getJSONArray(3), args.isNull(4) ? null : args.getJSONArray(4), args.isNull(5) ? null : args.getJSONArray(5)); + } else { + callbackContext.error("socialSharing." + action + " is not a supported function. Did you mean '" + ACTION_SHARE_EVENT + "'?"); + return false; + } + } + + private boolean isEmailAvailable() { + final Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "someone@domain.com", null)); + return cordova.getActivity().getPackageManager().queryIntentActivities(intent, 0).size() > 0; + } + + private boolean invokeEmailIntent(final CallbackContext callbackContext, final String message, final String subject, final JSONArray to, final JSONArray cc, final JSONArray bcc, final JSONArray files) throws JSONException { + + final SocialSharing plugin = this; + cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { + public void run() { + final Intent draft = new Intent(Intent.ACTION_SEND_MULTIPLE); + if (notEmpty(message)) { + Pattern htmlPattern = Pattern.compile(".*\\<[^>]+>.*", Pattern.DOTALL); + if (htmlPattern.matcher(message).matches()) { + draft.putExtra(android.content.Intent.EXTRA_TEXT, Html.fromHtml(message)); + draft.setType("text/html"); + } else { + draft.putExtra(android.content.Intent.EXTRA_TEXT, message); + draft.setType("text/plain"); + } + } + if (notEmpty(subject)) { + draft.putExtra(android.content.Intent.EXTRA_SUBJECT, subject); + } + try { + if (to != null && to.length() > 0) { + draft.putExtra(android.content.Intent.EXTRA_EMAIL, toStringArray(to)); + } + if (cc != null && cc.length() > 0) { + draft.putExtra(android.content.Intent.EXTRA_CC, toStringArray(cc)); + } + if (bcc != null && bcc.length() > 0) { + draft.putExtra(android.content.Intent.EXTRA_BCC, toStringArray(bcc)); + } + if (files.length() > 0) { + final String dir = getDownloadDir(); + if (dir != null) { + ArrayList<Uri> fileUris = new ArrayList<Uri>(); + for (int i = 0; i < files.length(); i++) { + final Uri fileUri = getFileUriAndSetType(draft, dir, files.getString(i), subject, i); + if (fileUri != null) { + fileUris.add(fileUri); + } + } + if (!fileUris.isEmpty()) { + draft.putExtra(Intent.EXTRA_STREAM, fileUris); + } + } + } + } catch (Exception e) { + callbackContext.error(e.getMessage()); + } + + draft.setType("application/octet-stream"); + cordova.startActivityForResult(plugin, Intent.createChooser(draft, "Choose Email App"), ACTIVITY_CODE_SENDVIAEMAIL); + } + }); + + return true; + } + + private String getDownloadDir() throws IOException { + // better check, otherwise it may crash the app + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + // we need to use external storage since we need to share to another app + final String dir = webView.getContext().getExternalFilesDir(null) + "/socialsharing-downloads"; + createOrCleanDir(dir); + return dir; + } else { + return null; + } + } + + private boolean doSendIntent(final CallbackContext callbackContext, final String msg, final String subject, final JSONArray files, final String url, final String appPackageName, final boolean peek) { + + final CordovaInterface mycordova = cordova; + final CordovaPlugin plugin = this; + + cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { + public void run() { + String message = msg; + final boolean hasMultipleAttachments = files.length() > 1; + final Intent sendIntent = new Intent(hasMultipleAttachments ? Intent.ACTION_SEND_MULTIPLE : Intent.ACTION_SEND); + sendIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + + try { + if (files.length() > 0 && !"".equals(files.getString(0))) { + final String dir = getDownloadDir(); + if (dir != null) { + ArrayList<Uri> fileUris = new ArrayList<Uri>(); + Uri fileUri = null; + for (int i = 0; i < files.length(); i++) { + fileUri = getFileUriAndSetType(sendIntent, dir, files.getString(i), subject, i); + if (fileUri != null) { + fileUris.add(fileUri); + } + } + if (!fileUris.isEmpty()) { + if (hasMultipleAttachments) { + sendIntent.putExtra(Intent.EXTRA_STREAM, fileUris); + } else { + sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri); + } + } + } else { + sendIntent.setType("text/plain"); + } + } else { + sendIntent.setType("text/plain"); + } + } catch (Exception e) { + callbackContext.error(e.getMessage()); + } + + if (notEmpty(subject)) { + sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + } + // add the URL to the message, as there seems to be no separate field + if (notEmpty(url)) { + if (notEmpty(message)) { + message += " " + url; + } else { + message = url; + } + } + if (notEmpty(message)) { + sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, message); + // sometimes required when the user picks share via sms + if (Build.VERSION.SDK_INT < 21) { // LOLLIPOP + sendIntent.putExtra("sms_body", message); + } + } + + if (appPackageName != null) { + String packageName = appPackageName; + String passedActivityName = null; + if (packageName.contains("/")) { + String[] items = appPackageName.split("/"); + packageName = items[0]; + passedActivityName = items[1]; + } + final ActivityInfo activity = getActivity(callbackContext, sendIntent, packageName); + if (activity != null) { + if (peek) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); + } else { + sendIntent.addCategory(Intent.CATEGORY_LAUNCHER); + sendIntent.setComponent(new ComponentName(activity.applicationInfo.packageName, + passedActivityName != null ? passedActivityName : activity.name)); + mycordova.startActivityForResult(plugin, sendIntent, 0); + + if (pasteMessage != null) { + // add a little delay because target app (facebook only atm) needs to be started first + new Timer().schedule(new TimerTask() { + public void run() { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + copyHintToClipboard(msg, pasteMessage); + showPasteMessage(pasteMessage); + } + }); + } + }, 2000); + } + } + } + } else { + if (peek) { + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); + } else { + mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, null), 1); + } + } + } + }); + return true; + } + + @SuppressLint("NewApi") + private void copyHintToClipboard(String msg, String label) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + return; + } + final ClipboardManager clipboard = (android.content.ClipboardManager) cordova.getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + final ClipData clip = android.content.ClipData.newPlainText(label, msg); + clipboard.setPrimaryClip(clip); + } + + @SuppressLint("NewApi") + private void showPasteMessage(String label) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + return; + } + final Toast toast = Toast.makeText(webView.getContext(), label, Toast.LENGTH_LONG); + toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 0, 0); + toast.show(); + } + + private Uri getFileUriAndSetType(Intent sendIntent, String dir, String image, String subject, int nthFile) throws IOException { + // we're assuming an image, but this can be any filetype you like + String localImage = image; + sendIntent.setType("image/*"); + if (image.startsWith("http") || image.startsWith("www/")) { + String filename = getFileName(image); + localImage = "file://" + dir + "/" + filename; + if (image.startsWith("http")) { + // filename optimisation taken from https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/pull/56 + URLConnection connection = new URL(image).openConnection(); + String disposition = connection.getHeaderField("Content-Disposition"); + if (disposition != null) { + final Pattern dispositionPattern = Pattern.compile("filename=([^;]+)"); + Matcher matcher = dispositionPattern.matcher(disposition); + if (matcher.find()) { + filename = matcher.group(1).replaceAll("[^a-zA-Z0-9._-]", ""); + localImage = "file://" + dir + "/" + filename; + } + } + saveFile(getBytes(connection.getInputStream()), dir, filename); + } else { + saveFile(getBytes(webView.getContext().getAssets().open(image)), dir, filename); + } + } else if (image.startsWith("data:")) { + // safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43 + if (!image.contains(";base64,")) { + sendIntent.setType("text/plain"); + return null; + } + // image looks like this: data:image/png;base64,R0lGODlhDAA... + final String encodedImg = image.substring(image.indexOf(";base64,") + 8); + // correct the intent type if anything else was passed, like a pdf: data:application/pdf;base64,.. + if (!image.contains("data:image/")) { + sendIntent.setType(image.substring(image.indexOf("data:") + 5, image.indexOf(";base64"))); + } + // the filename needs a valid extension, so it renders correctly in target apps + final String imgExtension = image.substring(image.indexOf("/") + 1, image.indexOf(";base64")); + String fileName; + // if a subject was passed, use it as the filename + // filenames must be unique when passing in multiple files [#158] + if (notEmpty(subject)) { + fileName = sanitizeFilename(subject) + (nthFile == 0 ? "" : "_" + nthFile) + "." + imgExtension; + } else { + fileName = "file" + (nthFile == 0 ? "" : "_" + nthFile) + "." + imgExtension; + } + saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, fileName); + localImage = "file://" + dir + "/" + fileName; + } else if (image.startsWith("df:")) { + // safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43 + if (!image.contains(";base64,")) { + sendIntent.setType("text/plain"); + return null; + } + // format looks like this : df:filename.txt;data:image/png;base64,R0lGODlhDAA... + final String fileName = image.substring(image.indexOf("df:") + 3, image.indexOf(";data:")); + final String fileType = image.substring(image.indexOf(";data:") + 6, image.indexOf(";base64,")); + final String encodedImg = image.substring(image.indexOf(";base64,") + 8); + sendIntent.setType(fileType); + saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, sanitizeFilename(fileName)); + localImage = "file://" + dir + "/" + fileName; + } else if (!image.startsWith("file://")) { + throw new IllegalArgumentException("URL_NOT_SUPPORTED"); + } + return Uri.parse(localImage); + } + + private boolean invokeSMSIntent(final CallbackContext callbackContext, JSONObject options, String p_phonenumbers) { + final String message = options.optString("message"); + // TODO test this on a real SMS enabled device before releasing it +// final String subject = options.optString("subject"); +// final String image = options.optString("image"); + final String subject = null; //options.optString("subject"); + final String image = null; // options.optString("image"); + final String phonenumbers = getPhoneNumbersWithManufacturerSpecificSeparators(p_phonenumbers); + final SocialSharing plugin = this; + cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { + public void run() { + Intent intent; + + if (Build.VERSION.SDK_INT >= 19) { // Build.VERSION_CODES.KITKAT) { + // passing in no phonenumbers for kitkat may result in an error, + // but it may also work for some devices, so documentation will need to cover this case + intent = new Intent(Intent.ACTION_SENDTO); + intent.setData(Uri.parse("smsto:" + (notEmpty(phonenumbers) ? phonenumbers : ""))); + } else { + intent = new Intent(Intent.ACTION_VIEW); + intent.setType("vnd.android-dir/mms-sms"); + if (phonenumbers != null) { + intent.putExtra("address", phonenumbers); + } + } + intent.putExtra("sms_body", message); + intent.putExtra("sms_subject", subject); + + try { + if (image != null && !"".equals(image)) { + final Uri fileUri = getFileUriAndSetType(intent, getDownloadDir(), image, subject, 0); + if (fileUri != null) { + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + } + } + cordova.startActivityForResult(plugin, intent, 0); + } catch (Exception e) { + callbackContext.error(e.getMessage()); + } + } + }); + return true; + } + + private static String getPhoneNumbersWithManufacturerSpecificSeparators(String phonenumbers) { + if (notEmpty(phonenumbers)) { + char separator; + if (android.os.Build.MANUFACTURER.equalsIgnoreCase("samsung")) { + separator = ','; + } else { + separator = ';'; + } + return phonenumbers.replace(';', separator).replace(',', separator); + } + return null; + } + + private ActivityInfo getActivity(final CallbackContext callbackContext, final Intent shareIntent, final String appPackageName) { + final PackageManager pm = webView.getContext().getPackageManager(); + List<ResolveInfo> activityList = pm.queryIntentActivities(shareIntent, 0); + for (final ResolveInfo app : activityList) { + if ((app.activityInfo.packageName).contains(appPackageName)) { + return app.activityInfo; + } + } + // no matching app found + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, getShareActivities(activityList))); + return null; + } + + private JSONArray getShareActivities(List<ResolveInfo> activityList) { + List<String> packages = new ArrayList<String>(); + for (final ResolveInfo app : activityList) { + packages.add(app.activityInfo.packageName); + } + return new JSONArray(packages); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + if (_callbackContext != null) { + if (ACTIVITY_CODE_SENDVIAEMAIL == requestCode) { + _callbackContext.success(); + } else { + _callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, resultCode == Activity.RESULT_OK)); + } + } + } + + private void createOrCleanDir(final String downloadDir) throws IOException { + final File dir = new File(downloadDir); + if (!dir.exists()) { + if (!dir.mkdirs()) { + throw new IOException("CREATE_DIRS_FAILED"); + } + } else { + cleanupOldFiles(dir); + } + } + + private static String getFileName(String url) { + final String pattern = ".*/([^?#]+)?"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(url); + if (m.find()) { + return m.group(1); + } else { + return null; + } + } + + private byte[] getBytes(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[16384]; + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + return buffer.toByteArray(); + } + + private void saveFile(byte[] bytes, String dirName, String fileName) throws IOException { + final File dir = new File(dirName); + final FileOutputStream fos = new FileOutputStream(new File(dir, fileName)); + fos.write(bytes); + fos.flush(); + fos.close(); + } + + /** + * As file.deleteOnExit does not work on Android, we need to delete files manually. + * Deleting them in onActivityResult is not a good idea, because for example a base64 encoded file + * will not be available for upload to Facebook (it's deleted before it's uploaded). + * So the best approach is deleting old files when saving (sharing) a new one. + */ + private void cleanupOldFiles(File dir) { + for (File f : dir.listFiles()) { + //noinspection ResultOfMethodCallIgnored + f.delete(); + } + } + + private static boolean notEmpty(String what) { + return what != null && + !"".equals(what) && + !"null".equalsIgnoreCase(what); + } + + private static String[] toStringArray(JSONArray jsonArray) throws JSONException { + String[] result = new String[jsonArray.length()]; + for (int i = 0; i < jsonArray.length(); i++) { + result[i] = jsonArray.getString(i); + } + return result; + } + + public static String sanitizeFilename(String name) { + return name.replaceAll("[:\\\\/*?|<> ]", "_"); + } +} diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.h b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.h new file mode 100755 index 00000000..b51474d3 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.h @@ -0,0 +1,27 @@ +#import <Cordova/CDV.h> +#import <MessageUI/MFMailComposeViewController.h> + +@interface SocialSharing : CDVPlugin <UIPopoverControllerDelegate, MFMailComposeViewControllerDelegate, UIDocumentInteractionControllerDelegate> + +@property (nonatomic, strong) MFMailComposeViewController *globalMailComposer; +@property (retain) UIDocumentInteractionController * documentInteractionController; +@property (retain) NSString * tempStoredFile; +@property (retain) CDVInvokedUrlCommand * command; + +- (void)available:(CDVInvokedUrlCommand*)command; +- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command; +- (void)share:(CDVInvokedUrlCommand*)command; +- (void)canShareVia:(CDVInvokedUrlCommand*)command; +- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command; +- (void)shareVia:(CDVInvokedUrlCommand*)command; +- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command; +- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command; +- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command; +- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command; +- (void)shareViaSMS:(CDVInvokedUrlCommand*)command; +- (void)shareViaEmail:(CDVInvokedUrlCommand*)command; +- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command; + +- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command; + +@end diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.m b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.m new file mode 100755 index 00000000..cd0913a4 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/ios/SocialSharing.m @@ -0,0 +1,715 @@ +#import "SocialSharing.h" +#import <Cordova/CDV.h> +#import <Social/Social.h> +#import <Foundation/NSException.h> +#import <MessageUI/MFMessageComposeViewController.h> +#import <MessageUI/MFMailComposeViewController.h> +#import <MobileCoreServices/MobileCoreServices.h> + +@implementation SocialSharing { + UIPopoverController *_popover; + NSString *_popupCoordinates; +} + +- (void)pluginInitialize { + if ([self isEmailAvailable]) { + [self cycleTheGlobalMailComposer]; + } +} + +- (void)available:(CDVInvokedUrlCommand*)command { + BOOL avail = NO; + if (NSClassFromString(@"UIActivityViewController")) { + avail = YES; + } + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:avail]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (NSString*)getIPadPopupCoordinates { + if (_popupCoordinates != nil) { + return _popupCoordinates; + } + if ([self.webView respondsToSelector:@selector(stringByEvaluatingJavaScriptFromString:)]) { + return [(UIWebView*)self.webView stringByEvaluatingJavaScriptFromString:@"window.plugins.socialsharing.iPadPopupCoordinates();"]; + } else { + // prolly a wkwebview, ignoring for now + return nil; + } +} + +- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command { + _popupCoordinates = [command.arguments objectAtIndex:0]; +} + +- (CGRect)getPopupRectFromIPadPopupCoordinates:(NSArray*)comps { + CGRect rect = CGRectZero; + if ([comps count] == 4) { + rect = CGRectMake([[comps objectAtIndex:0] integerValue], [[comps objectAtIndex:1] integerValue], [[comps objectAtIndex:2] integerValue], [[comps objectAtIndex:3] integerValue]); + } + return rect; +} + +- (void)share:(CDVInvokedUrlCommand*)command { + [self.commandDelegate runInBackground:^{ //avoid main thread block especially if sharing big files from url + if (!NSClassFromString(@"UIActivityViewController")) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + + NSString *message = [command.arguments objectAtIndex:0]; + NSString *subject = [command.arguments objectAtIndex:1]; + NSArray *filenames = [command.arguments objectAtIndex:2]; + NSString *urlString = [command.arguments objectAtIndex:3]; + + NSMutableArray *activityItems = [[NSMutableArray alloc] init]; + [activityItems addObject:message]; + + NSMutableArray *files = [[NSMutableArray alloc] init]; + if (filenames != (id)[NSNull null] && filenames.count > 0) { + for (NSString* filename in filenames) { + NSObject *file = [self getImage:filename]; + if (file == nil) { + file = [self getFile:filename]; + } + if (file != nil) { + [files addObject:file]; + } + } + [activityItems addObjectsFromArray:files]; + } + + if (urlString != (id)[NSNull null]) { + [activityItems addObject:[NSURL URLWithString:urlString]]; + } + + UIActivity *activity = [[UIActivity alloc] init]; + NSArray *applicationActivities = [[NSArray alloc] initWithObjects:activity, nil]; + UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities]; + if (subject != (id)[NSNull null]) { + [activityVC setValue:subject forKey:@"subject"]; + } + + // TODO deprecated in iOS 8.0, change this some day + [activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) { + [self cleanupStoredFiles]; + NSLog(@"SocialSharing app selected: %@", activityType); + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; + + NSArray * socialSharingExcludeActivities = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"SocialSharingExcludeActivities"]; + if (socialSharingExcludeActivities!=nil && [socialSharingExcludeActivities count] > 0) { + activityVC.excludedActivityTypes = socialSharingExcludeActivities; + } + + dispatch_async(dispatch_get_main_queue(), ^(void){ + // iPad on iOS >= 8 needs a different approach + if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { + NSString* iPadCoords = [self getIPadPopupCoordinates]; + if (iPadCoords != nil && ![iPadCoords isEqual:@"-1,-1,-1,-1"]) { + NSArray *comps = [iPadCoords componentsSeparatedByString:@","]; + CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps]; + if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported + activityVC.popoverPresentationController.sourceView = self.webView; + activityVC.popoverPresentationController.sourceRect = rect; +#endif + } else { + _popover = [[UIPopoverController alloc] initWithContentViewController:activityVC]; + _popover.delegate = self; + [_popover presentPopoverFromRect:rect inView:self.webView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; + } + } else if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported + activityVC.popoverPresentationController.sourceView = self.webView; + // position the popup at the bottom, just like iOS < 8 did (and iPhone still does on iOS 8) + NSArray *comps = [NSArray arrayWithObjects: + [NSNumber numberWithInt:(self.viewController.view.frame.size.width/2)-200], + [NSNumber numberWithInt:self.viewController.view.frame.size.height], + [NSNumber numberWithInt:400], + [NSNumber numberWithInt:400], + nil]; + CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps]; + activityVC.popoverPresentationController.sourceRect = rect; +#endif + } + } + [[self getTopMostViewController] presentViewController:activityVC animated:YES completion:nil]; + }); + }]; +} + +- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command { + [self shareViaInternal:command type:SLServiceTypeTwitter]; +} + +- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command { + [self shareViaInternal:command type:SLServiceTypeFacebook]; +} + +- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command { + // If Fb app is installed a message is not prefilled. + // When shared through the default iOS widget (iOS Settings > Facebook) the message is prefilled already. + NSString *message = [command.arguments objectAtIndex:0]; + if (message != (id)[NSNull null]) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1000 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ + BOOL fbAppInstalled = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"fb://"]]; // requires whitelisting on iOS9 + if (fbAppInstalled) { + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + [pasteboard setValue:message forPasteboardType:@"public.text"]; + NSString *hint = [command.arguments objectAtIndex:4]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:hint delegate:nil cancelButtonTitle:nil otherButtonTitles:nil]; + [alert show]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2800 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ + [alert dismissWithClickedButtonIndex:-1 animated:YES]; + }); + } + }); + } + [self shareViaInternal:command type:SLServiceTypeFacebook]; +} + +- (void)shareVia:(CDVInvokedUrlCommand*)command { + [self shareViaInternal:command type:[command.arguments objectAtIndex:4]]; +} + +- (void)canShareVia:(CDVInvokedUrlCommand*)command { + NSString *via = [command.arguments objectAtIndex:4]; + CDVPluginResult * pluginResult; + if ([@"sms" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaSMS]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else if ([@"email" caseInsensitiveCompare:via] == NSOrderedSame && [self isEmailAvailable]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else if ([@"whatsapp" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaWhatsApp]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else if ([@"instagram" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaInstagram]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else if ([self isAvailableForSharing:command type:via]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command { + if ([self isEmailAvailable]) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } +} + +- (bool)isEmailAvailable { + Class messageClass = (NSClassFromString(@"MFMailComposeViewController")); + return messageClass != nil && [messageClass canSendMail]; +} + +- (bool)isAvailableForSharing:(CDVInvokedUrlCommand*)command + type:(NSString *) type { + // isAvailableForServiceType returns true if you pass it a type that is not + // in the defined constants, this is probably a bug on apples part + if(!([type isEqualToString:SLServiceTypeFacebook] + || [type isEqualToString:SLServiceTypeTwitter] + || [type isEqualToString:SLServiceTypeTencentWeibo] + || [type isEqualToString:SLServiceTypeSinaWeibo])) { + return false; + } + // wrapped in try-catch, because isAvailableForServiceType may crash if an invalid type is passed + @try { + return [SLComposeViewController isAvailableForServiceType:type]; + } + @catch (NSException* exception) { + return false; + } +} + +- (void)shareViaInternal:(CDVInvokedUrlCommand*)command + type:(NSString *) type { + + NSString *message = [command.arguments objectAtIndex:0]; + // subject is not supported by the SLComposeViewController + NSArray *filenames = [command.arguments objectAtIndex:2]; + NSString *urlString = [command.arguments objectAtIndex:3]; + + // boldly invoke the target app, because the phone will display a nice message asking to configure the app + SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:type]; + if (message != (id)[NSNull null]) { + [composeViewController setInitialText:message]; + } + + for (NSString* filename in filenames) { + UIImage* image = [self getImage:filename]; + if (image != nil) { + [composeViewController addImage:image]; + } + } + + if (urlString != (id)[NSNull null]) { + [composeViewController addURL:[NSURL URLWithString:urlString]]; + } + + [composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) { + if (SLComposeViewControllerResultCancelled == result) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"cancelled"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } else if ([self isAvailableForSharing:command type:type]) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:SLComposeViewControllerResultDone == result]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } + // required for iOS6 (issues #162 and #167) + [self.viewController dismissViewControllerAnimated:YES completion:nil]; + }]; + [[self getTopMostViewController] presentViewController:composeViewController animated:YES completion:nil]; +} + +- (void)shareViaEmail:(CDVInvokedUrlCommand*)command { + if ([self isEmailAvailable]) { + + if (TARGET_IPHONE_SIMULATOR && IsAtLeastiOSVersion(@"8.0")) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"SocialSharing plugin" + message:@"Sharing via email is not supported on the iOS 8 simulator." + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil]; + [alert show]; + return; + } + + self.globalMailComposer.mailComposeDelegate = self; + + if ([command.arguments objectAtIndex:0] != (id)[NSNull null]) { + NSString *message = [command.arguments objectAtIndex:0]; + BOOL isHTML = [message rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch].location != NSNotFound; + [self.globalMailComposer setMessageBody:message isHTML:isHTML]; + } + + if ([command.arguments objectAtIndex:1] != (id)[NSNull null]) { + [self.globalMailComposer setSubject: [command.arguments objectAtIndex:1]]; + } + + if ([command.arguments objectAtIndex:2] != (id)[NSNull null]) { + [self.globalMailComposer setToRecipients:[command.arguments objectAtIndex:2]]; + } + + if ([command.arguments objectAtIndex:3] != (id)[NSNull null]) { + [self.globalMailComposer setCcRecipients:[command.arguments objectAtIndex:3]]; + } + + if ([command.arguments objectAtIndex:4] != (id)[NSNull null]) { + [self.globalMailComposer setBccRecipients:[command.arguments objectAtIndex:4]]; + } + + if ([command.arguments objectAtIndex:5] != (id)[NSNull null]) { + NSArray* attachments = [command.arguments objectAtIndex:5]; + NSFileManager* fileManager = [NSFileManager defaultManager]; + for (NSString* path in attachments) { + NSURL *file = [self getFile:path]; + NSData* data = [fileManager contentsAtPath:file.path]; + + NSString* fileName; + NSString* mimeType; + NSString* basename = [self getBasenameFromAttachmentPath:path]; + + if ([basename hasPrefix:@"data:"]) { + mimeType = (NSString*)[[[basename substringFromIndex:5] componentsSeparatedByString: @";"] objectAtIndex:0]; + fileName = @"attachment."; + fileName = [fileName stringByAppendingString:(NSString*)[[mimeType componentsSeparatedByString: @"/"] lastObject]]; + NSString *base64content = (NSString*)[[basename componentsSeparatedByString: @","] lastObject]; + data = [SocialSharing dataFromBase64String:base64content]; + } else { + fileName = [basename pathComponents].lastObject; + mimeType = [self getMimeTypeFromFileExtension:[basename pathExtension]]; + } + [self.globalMailComposer addAttachmentData:data mimeType:mimeType fileName:fileName]; + } + } + + // remember the command, because we need it in the didFinishWithResult method + _command = command; + + [self.commandDelegate runInBackground:^{ + [[self getTopMostViewController] presentViewController:self.globalMailComposer animated:YES completion:nil]; + }]; + + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } +} + +- (UIViewController*) getTopMostViewController { + UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController; + while (presentingViewController.presentedViewController != nil) { + presentingViewController = presentingViewController.presentedViewController; + } + return presentingViewController; +} + +- (NSString*) getBasenameFromAttachmentPath:(NSString*)path { + if ([path hasPrefix:@"base64:"]) { + NSString* pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:" withString:@""]; + return [pathWithoutPrefix substringToIndex:[pathWithoutPrefix rangeOfString:@"//"].location]; + } + return path; +} + +- (NSString*) getMimeTypeFromFileExtension:(NSString*)extension { + if (!extension) { + return nil; + } + // Get the UTI from the file's extension + CFStringRef ext = (CFStringRef)CFBridgingRetain(extension); + CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL); + // Converting UTI to a mime type + NSString *result = (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType)); + CFRelease(ext); + CFRelease(type); + return result; +} + +/** + * Delegate will be called after the mail composer did finish an action + * to dismiss the view. + */ +- (void) mailComposeController:(MFMailComposeViewController*)controller + didFinishWithResult:(MFMailComposeResult)result + error:(NSError*)error { + bool ok = result == MFMailComposeResultSent; + [self.globalMailComposer dismissViewControllerAnimated:YES completion:^{[self cycleTheGlobalMailComposer];}]; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId]; +} + +-(void)cycleTheGlobalMailComposer { + // we are cycling the damned GlobalMailComposer: http://stackoverflow.com/questions/25604552/i-have-real-misunderstanding-with-mfmailcomposeviewcontroller-in-swift-ios8-in/25604976#25604976 + self.globalMailComposer = nil; + self.globalMailComposer = [[MFMailComposeViewController alloc] init]; +} + +- (bool)canShareViaSMS { + Class messageClass = (NSClassFromString(@"MFMessageComposeViewController")); + return messageClass != nil && [messageClass canSendText]; +} + +- (void)shareViaSMS:(CDVInvokedUrlCommand*)command { + if ([self canShareViaSMS]) { + NSDictionary* options = [command.arguments objectAtIndex:0]; + NSString *phonenumbers = [command.arguments objectAtIndex:1]; + NSString *message = [options objectForKey:@"message"]; + NSString *subject = [options objectForKey:@"subject"]; + NSString *image = [options objectForKey:@"image"]; + + MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init]; + picker.messageComposeDelegate = (id) self; + if (message != (id)[NSNull null]) { + picker.body = message; + } + if (subject != (id)[NSNull null]) { + [picker setSubject:subject]; + } + if (image != nil && image != (id)[NSNull null]) { + BOOL canSendAttachments = [[MFMessageComposeViewController class] respondsToSelector:@selector(canSendAttachments)]; + if (canSendAttachments) { + NSURL *file = [self getFile:image]; + if (file != nil) { + [picker addAttachmentURL:file withAlternateFilename:nil]; + } + } + } + + if (phonenumbers != (id)[NSNull null]) { + [picker setRecipients:[phonenumbers componentsSeparatedByString:@","]]; + } + // remember the command, because we need it in the didFinishWithResult method + _command = command; + [self.commandDelegate runInBackground:^{ + picker.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + [[self getTopMostViewController] presentViewController:picker animated:YES completion:nil]; + }]; + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } +} + +// Dismisses the SMS composition interface when users taps Cancel or Send +- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result { + bool ok = result == MessageComposeResultSent; + [[self getTopMostViewController] dismissViewControllerAnimated:YES completion:nil]; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId]; +} + +- (bool)canShareViaInstagram { + return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"instagram://app"]]; // requires whitelisting on iOS9 +} + +- (bool)canShareViaWhatsApp { + return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"whatsapp://app"]]; // requires whitelisting on iOS9 +} + +// this is only an internal test method for now, can be used to open a share sheet with 'Open in xx' links for tumblr, drive, dropbox, .. +- (void)openImage:(NSString *)imageName { + UIImage* image =[self getImage:imageName]; + if (image != nil) { + NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myTempImage.jpg"]; + [UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES]; + _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]]; + _documentInteractionController.UTI = @""; // TODO find the scheme for google drive and create a shareViaGoogleDrive function + [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES]; + } +} + +- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command { + + // on iOS9 canShareVia('instagram'..) will only work if instagram:// is whitelisted. + // If it's not, this method will ask permission to the user on iOS9 for opening the app, + // which is of course better than Instagram sharing not working at all because you forgot to whitelist it. + // Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('instagram'..) + if (!IsAtLeastiOSVersion(@"9.0")) { + if (![self canShareViaInstagram]) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + } + + NSString *message = [command.arguments objectAtIndex:0]; + // subject is not supported by the SLComposeViewController + NSArray *filenames = [command.arguments objectAtIndex:2]; + + // only use the first image (for now.. maybe we can share in a loop?) + UIImage* image = nil; + for (NSString* filename in filenames) { + image = [self getImage:filename]; + break; + } + +// NSData *imageObj = [NSData dataFromBase64String:objectAtIndex0]; + NSString *tmpDir = NSTemporaryDirectory(); + NSString *path = [tmpDir stringByAppendingPathComponent:@"instagram.igo"]; + [UIImageJPEGRepresentation(image, 1.0) writeToFile:path atomically:YES]; + + _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]]; + _documentInteractionController.delegate = self; + _documentInteractionController.UTI = @"com.instagram.exclusivegram"; + + if (message != (id)[NSNull null]) { + // no longer working, so .. + _documentInteractionController.annotation = @{@"InstagramCaption" : message}; + + // .. we put the message on the clipboard (you app can prompt the user to paste it) + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + [pasteboard setValue:message forPasteboardType:@"public.text"]; + } + + // remember the command for the delegate method + _command = command; + [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.webView animated:YES]; +} + +- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command { + + // on iOS9 canShareVia('whatsapp'..) will only work if whatsapp:// is whitelisted. + // If it's not, this method will ask permission to the user on iOS9 for opening the app, + // which is of course better than WhatsApp sharing not working at all because you forgot to whitelist it. + // Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('whatsapp'..) + if (!IsAtLeastiOSVersion(@"9.0")) { + if (![self canShareViaWhatsApp]) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + } + + NSString *message = [command.arguments objectAtIndex:0]; + // subject is not supported by the SLComposeViewController + NSArray *filenames = [command.arguments objectAtIndex:2]; + NSString *urlString = [command.arguments objectAtIndex:3]; + + // only use the first image (for now.. maybe we can share in a loop?) + UIImage* image = nil; + for (NSString* filename in filenames) { + image = [self getImage:filename]; + break; + } + + // with WhatsApp, we can share an image OR text+url.. image wins if set + if (image != nil) { + NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/whatsAppTmp.wai"]; + [UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES]; + _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]]; + _documentInteractionController.UTI = @"net.whatsapp.image"; + _documentInteractionController.delegate = self; + _command = command; + [_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES]; + } else { + // append an url to a message, if both are passed + NSString * shareString = @""; + if (message != (id)[NSNull null]) { + shareString = message; + } + if (urlString != (id)[NSNull null]) { + if ([shareString isEqual: @""]) { + shareString = urlString; + } else { + shareString = [NSString stringWithFormat:@"%@ %@", shareString, urlString]; + } + } + NSString * encodedShareString = [shareString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + // also encode the '=' character + encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"]; + encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"&" withString:@"%26"]; + NSString * encodedShareStringForWhatsApp = [NSString stringWithFormat:@"whatsapp://send?text=%@", encodedShareString]; + + NSURL *whatsappURL = [NSURL URLWithString:encodedShareStringForWhatsApp]; + [[UIApplication sharedApplication] openURL: whatsappURL]; + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + } +} + +- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command { + self.command = command; + NSArray *filenames = [command.arguments objectAtIndex:0]; + [self.commandDelegate runInBackground:^{ + bool shared = false; + for (NSString* filename in filenames) { + UIImage* image = [self getImage:filename]; + if (image != nil) { + shared = true; + UIImageWriteToSavedPhotosAlbum(image, self, @selector(thisImage:wasSavedToPhotoAlbumWithError:contextInfo:), nil); + } + } + if (!shared) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no valid image was passed"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; + } + }]; +} + +// called from saveToPhotoAlbum, note that we only send feedback for the first image that's being saved (not keeping the callback) +// but since the UIImageWriteToSavedPhotosAlbum function is only called with valid images that should not be a problem +- (void)thisImage:(UIImage *)image wasSavedToPhotoAlbumWithError:(NSError *)error contextInfo:(void*)ctxInfo { + if (error) { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.localizedDescription]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; + } else { + CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; + } +} + +-(UIImage*)getImage: (NSString *)imageName { + UIImage *image = nil; + if (imageName != (id)[NSNull null]) { + if ([imageName hasPrefix:@"http"]) { + image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]]]; + } else if ([imageName hasPrefix:@"www/"]) { + image = [UIImage imageNamed:imageName]; + } else if ([imageName hasPrefix:@"file://"]) { + image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSURL URLWithString:imageName] path]]]; + } else if ([imageName hasPrefix:@"data:"]) { + // using a base64 encoded string + NSURL *imageURL = [NSURL URLWithString:imageName]; + NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; + image = [UIImage imageWithData:imageData]; + } else if ([imageName hasPrefix:@"assets-library://"]) { + // use assets-library + NSURL *imageURL = [NSURL URLWithString:imageName]; + NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; + image = [UIImage imageWithData:imageData]; + } else { + // assume anywhere else, on the local filesystem + image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imageName]]; + } + } + return image; +} + +-(NSURL*)getFile: (NSString *)fileName { + NSURL *file = nil; + if (fileName != (id)[NSNull null]) { + if ([fileName hasPrefix:@"http"]) { + NSURL *url = [NSURL URLWithString:fileName]; + NSData *fileData = [NSData dataWithContentsOfURL:url]; + file = [NSURL fileURLWithPath:[self storeInFile:(NSString*)[[fileName componentsSeparatedByString: @"/"] lastObject] fileData:fileData]]; + } else if ([fileName hasPrefix:@"www/"]) { + NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; + NSString *fullPath = [NSString stringWithFormat:@"%@/%@", bundlePath, fileName]; + file = [NSURL fileURLWithPath:fullPath]; + } else if ([fileName hasPrefix:@"file://"]) { + // stripping the first 6 chars, because the path should start with / instead of file:// + file = [NSURL fileURLWithPath:[fileName substringFromIndex:6]]; + } else if ([fileName hasPrefix:@"data:"]) { + // using a base64 encoded string + // extract some info from the 'fileName', which is for example: data:text/calendar;base64,<encoded stuff here> + NSString *fileType = (NSString*)[[[fileName substringFromIndex:5] componentsSeparatedByString: @";"] objectAtIndex:0]; + fileType = (NSString*)[[fileType componentsSeparatedByString: @"/"] lastObject]; + NSString *base64content = (NSString*)[[fileName componentsSeparatedByString: @","] lastObject]; + NSData *fileData = [SocialSharing dataFromBase64String:base64content]; + file = [NSURL fileURLWithPath:[self storeInFile:[NSString stringWithFormat:@"%@.%@", @"file", fileType] fileData:fileData]]; + } else { + // assume anywhere else, on the local filesystem + file = [NSURL fileURLWithPath:fileName]; + } + } + return file; +} + +-(NSString*) storeInFile: (NSString*) fileName + fileData: (NSData*) fileData { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName]; + [fileData writeToFile:filePath atomically:YES]; + _tempStoredFile = filePath; + return filePath; +} + +- (void) cleanupStoredFiles { + if (_tempStoredFile != nil) { + NSError *error; + [[NSFileManager defaultManager]removeItemAtPath:_tempStoredFile error:&error]; + } +} + ++ (NSData*) dataFromBase64String:(NSString*)aString { + return [[NSData alloc] initWithBase64EncodedString:aString options:0]; +} + +#pragma mark - UIPopoverControllerDelegate methods + +- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view { + NSArray *comps = [[self getIPadPopupCoordinates] componentsSeparatedByString:@","]; + CGRect newRect = [self getPopupRectFromIPadPopupCoordinates:comps]; + rect->origin = newRect.origin; +} + +- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController { + _popover = nil; +} + +#pragma mark - UIDocumentInteractionControllerDelegate methods + +- (void) documentInteractionController: (UIDocumentInteractionController *) controller willBeginSendingToApplication: (NSString *) application { + // note that the application actually contains the app bundle id which was picked (for whatsapp and instagram only) + NSLog(@"SocialSharing app selected: %@", application); +} + +- (void) documentInteractionControllerDidDismissOpenInMenu: (UIDocumentInteractionController *) controller { + if (self.command != nil) { + CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:result callbackId: self.command.callbackId]; + } +} + +@end diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/windows/SocialSharingProxy.js b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/windows/SocialSharingProxy.js new file mode 100644 index 00000000..99e0af15 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/windows/SocialSharingProxy.js @@ -0,0 +1,114 @@ +var cordova = require('cordova'); + +module.exports = { + share: function (win, fail, args) { + //Text Message + var message = args[0]; + //Title + var subject = args[1]; + //File(s) Path + var fileOrFileArray = args[2]; + //Web link + var url = args[3]; + + var doShare = function (e) { + e.request.data.properties.title = subject?subject: "Sharing"; + if (message) e.request.data.setText(message); + if (url) e.request.data.setWebLink(new Windows.Foundation.Uri(url)); + if (fileOrFileArray.length > 0) { + var deferral = e.request.getDeferral(); + var storageItems = []; + var filesCount = fileOrFileArray.length; + for (var i = 0; i < fileOrFileArray.length; i++) { + Windows.Storage.StorageFile.getFileFromPathAsync(fileOrFileArray[i]).done( + function (file) { + storageItems.push(file); + if (!--filesCount) { + e.request.data.setStorageItems(storageItems); + deferral.complete(); + } + }, + function() { + if (!--filesCount) { + e.request.data.setStorageItems(storageItems); + deferral.complete(); + } + } + ); + } + } + } + + + var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); + + dataTransferManager.addEventListener("datarequested", doShare); + + try { + Windows.ApplicationModel.DataTransfer.DataTransferManager.showShareUI(); + win(true); + } catch (err) { + fail(err); + } + }, + + canShareViaEmail: function (win, fail, args) { + win(true); + }, + + shareViaEmail: function (win, fail, args) { + //Text Message + var message = args[0]; + //Title + var subject = args[1]; + //File(s) Path + var fileOrFileArray = args[5]; + + var doShare = function (e) { + e.request.data.properties.title = subject ? subject : "Sharing"; + if (message) { + var htmlFormat = Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.createHtmlFormat(message); + e.request.data.setHtmlFormat(htmlFormat); + } + + if (fileOrFileArray.length > 0) { + var deferral = e.request.getDeferral(); + var storageItems = []; + var filesCount = fileOrFileArray.length; + for (var i = 0; i < fileOrFileArray.length; i++) { + Windows.Storage.StorageFile.getFileFromPathAsync(fileOrFileArray[i]).done( + function (index, file) { + var path = fileOrFileArray[index]; + var streamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(file); + e.request.data.resourceMap[path] = streamRef; + storageItems.push(file); + if (!--filesCount) { + e.request.data.setStorageItems(storageItems); + deferral.complete(); + } + }.bind(this, i), + function () { + if (!--filesCount) { + e.request.data.setStorageItems(storageItems); + deferral.complete(); + } + } + ); + } + } + } + + var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); + + dataTransferManager.addEventListener("datarequested", doShare); + + try { + Windows.ApplicationModel.DataTransfer.DataTransferManager.showShareUI(); + win(true); + } catch (err) { + fail(err); + } + } +}; + +require("cordova/exec/proxy").add("SocialSharing", module.exports);
\ No newline at end of file diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/wp8/SocialSharing.cs b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/wp8/SocialSharing.cs new file mode 100644 index 00000000..1a165127 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/src/wp8/SocialSharing.cs @@ -0,0 +1,103 @@ +using Microsoft.Phone.Tasks; + +using WPCordovaClassLib.Cordova; +using WPCordovaClassLib.Cordova.Commands; +using WPCordovaClassLib.Cordova.JSON; + +using Newtonsoft.Json; + +namespace Cordova.Extension.Commands +{ + public class SocialSharing : BaseCommand + { + + public void available(string jsonArgs) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); + } + + public void share(string jsonArgs) + { + + var options = JsonHelper.Deserialize<string[]>(jsonArgs); + + var message = options[0]; + var title = options[1]; + var files = JsonHelper.Deserialize<string[]>(options[2]); + var link = options[3]; + + if (!"null".Equals(link)) + { + ShareLinkTask shareLinkTask = new ShareLinkTask(); + shareLinkTask.Title = title; + shareLinkTask.LinkUri = new System.Uri(link, System.UriKind.Absolute); + shareLinkTask.Message = message; + shareLinkTask.Show(); + } + else if (files != null && files.Length > 0) + { + ShareLinkTask shareLinkTask = new ShareLinkTask(); + shareLinkTask.Title = title; + shareLinkTask.LinkUri = new System.Uri(files[0], System.UriKind.Absolute); + shareLinkTask.Message = message; + shareLinkTask.Show(); + } + else + { + var shareStatusTask = new ShareStatusTask { Status = message }; + shareStatusTask.Show(); + } + // unfortunately, there is no way to tell if something was shared, so just invoke the successCallback + DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); + } + + public void canShareViaEmail(string jsonArgs) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); + } + + // HTML and attachments are currently not supported on WP8 + public void shareViaEmail(string jsonArgs) + { + var options = JsonHelper.Deserialize<string[]>(jsonArgs); + EmailComposeTask draft = new EmailComposeTask(); + draft.Body = options[0]; + draft.Subject = options[1]; + if (!"null".Equals(options[2])) + { + draft.To = string.Join(",", options[2]); + } + if (!"null".Equals(options[3])) + { + draft.Cc = string.Join(",", options[3]); + } + if (!"null".Equals(options[4])) + { + draft.Bcc = string.Join(",", options[4]); + } + draft.Show(); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true)); + } + + public void shareViaSMS(string jsonArgs) + { + var options = JsonHelper.Deserialize<string[]>(jsonArgs); + + SmsComposeTask smsComposeTask = new SmsComposeTask(); + + smsComposeTask.To = options[1]; + SMSMessageClass m = JsonConvert.DeserializeObject<SMSMessageClass>(options[0]); + smsComposeTask.Body = m.message; + + smsComposeTask.Show(); + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true)); + } + } + + public class SMSMessageClass + { + public string message { get; set; } + } + +}
\ No newline at end of file diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/tests/plugin.xml b/StoneIsland/plugins/cordova-plugin-x-socialsharing/tests/plugin.xml new file mode 100644 index 00000000..1ccb2217 --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/tests/plugin.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + xmlns:rim="http://www.blackberry.com/ns/widgets" + xmlns:android="http://schemas.android.com/apk/res/android" + id="nl.x-services.plugins.socialsharing.tests" + version="4.3.15"> + <name>SocialSharing Tests</name> + <author>Nicolas Oliver</author> + <license>MIT</license> + + <js-module src="test.js" name="tests"></js-module> +</plugin>
\ No newline at end of file diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/tests/test.js b/StoneIsland/plugins/cordova-plugin-x-socialsharing/tests/test.js new file mode 100644 index 00000000..630899db --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/tests/test.js @@ -0,0 +1,363 @@ +/** + * Jasmine Based test suites + * + * Several of SocialSharing APIs cannot be automatically tested, because + * they depend on user interaction in order to call success or fail + * handlers. For most of them, there is a basic test that assert the presence of + * the API. + * + * There are some cases that test automation can be applied, i.e in "canShareVia", + * "canShareViaEmail" and "available" methods. For those cases, there is some level + * of automatic test coverage. + */ +exports.defineAutoTests = function () { + 'use strict'; + + describe('socialsharing', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing).toBeDefined(); + }); + + describe('share', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.share).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.share).toEqual('function'); + }); + }); + + describe('shareVia', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareVia).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareVia).toEqual('function'); + }); + }); + + describe('shareViaTwitter', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaTwitter).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaTwitter).toEqual('function'); + }); + }); + + describe('shareViaFacebook', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaFacebook).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaFacebook).toEqual('function'); + }); + }); + + describe('shareViaFacebookWithPasteMessageHint', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint).toEqual('function'); + }); + }); + + describe('shareViaWhatsApp', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaWhatsApp).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaWhatsApp).toEqual('function'); + }); + }); + + describe('shareViaSMS', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaSMS).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaSMS).toEqual('function'); + }); + }); + + describe('shareViaEmail', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaEmail).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaEmail).toEqual('function'); + }); + }); + + describe('canShareVia', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.canShareVia).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.canShareVia).toEqual('function'); + }); + + it('should always call callback or error function', function (done) { + function onSuccess(data){ + expect(data).not.toEqual(null); + done(); + } + + function onError(error){ + expect(error).not.toEqual(null); + done(); + } + + window.plugins.socialsharing.canShareVia('dummytarget','dummymessage', null, null, null, onSuccess, onError); + }); + }); + + describe('canshareViaEmail', function(){ + it('should be defined', function () { + expect(window.plugins.socialsharing.canShareViaEmail).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.canShareViaEmail).toEqual('function'); + }); + + it('should always call callback or error function', function (done) { + function onSuccess(data){ + expect(data).not.toEqual(null); + done(); + } + + function onError(error){ + expect(error).not.toEqual(null); + done(); + } + + window.plugins.socialsharing.canShareViaEmail(onSuccess, onError); + }); + }); + + describe('availabe', function(){ + it('should be defined', function () { + expect(window.plugins.socialsharing.available).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.available).toEqual('function'); + }); + + it('should return a boolean when called', function(done){ + window.plugins.socialsharing.available(function(isAvailable) { + expect(typeof isAvailable).toEqual('boolean'); + done(); + }); + }); + }); + }); +}; + +/** + * Manual tests suites + * + * Some actions buttons to execute SocialSharing plugin methods + */ +exports.defineManualTests = function (contentEl, createActionButton) { + 'use strict'; + + /** helper function to log messages in the log div element */ + function logMessage(message, color) { + var log = document.getElementById('info'), + logLine = document.createElement('div'); + + if (color) { + logLine.style.color = color; + } + + logLine.innerHTML = message; + log.appendChild(logLine); + } + + /** helper function to clear the log div element */ + function clearLog() { + var log = document.getElementById('info'); + log.innerHTML = ''; + } + + /** helper function to declare a not implemented test */ + function testNotImplemented(testName) { + return function () { + console.error(testName, 'test not implemented'); + }; + } + + /** init method called on deviceready event */ + function init() {} + + /** object to hold properties and configs */ + var TestSuite = {}; + + TestSuite.getCanShareViaTarget = function(){ + return document.getElementById('inputCanShareVia').value; + }; + + TestSuite.$markup = '' + + '<fieldset>' + + '<legend>Available Tests</legend>' + + + '<h3>Available</h3>' + + '<div id="buttonIsAvailable"></div>' + + 'Expected result: Should log if the plugin is available or not' + + '</fieldset>' + + + '<fieldset>' + + '<legend>Share Tests</legend>' + + + '<h3>Share Message</h3>' + + '<div id="buttonShareMessage"></div>' + + 'Expected result: Should display share widget, and the message to share should contain "Message body"' + + + '<h3>Share Message with Subject</h3>' + + '<div id="buttonShareMessageWithSubject"></div>' + + 'Expected result: Should display share widget, and the message to share should contain "Message body", and the subject should be "Message subject"' + + + '<h3>Share Link</h3>' + + '<div id="buttonShareLink"></div>' + + 'Expected result: Should display share widget, and the message to share should contain "http://www.x-services.nl"' + + + '<h3>Share Message with Link</h3>' + + '<div id="buttonShareMessageAndLink"></div>' + + 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl"' + + + '<h3>Share Image</h3>' + + '<div id="buttonShareImage"></div>' + + 'Expected result: Should display share widget, and the message to share should contain an image' + + + '<h3>Share Image in base 64</h3>' + + '<div id="buttonShareImageBase64"></div>' + + 'Expected result: Should display share widget, and the message to share should contain an image. The image is encoded in base 64' + + + '<h3>Share Image with Message</h3>' + + '<div id="buttonShareMessageImage"></div>' + + 'Expected result: Should display share widget, and the message to share should contain "Message body" and an image' + + + '<h3>Share Image with Message and Link</h3>' + + '<div id="buttonShareMessageImageLink"></div>' + + 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl" and an image' + + + '<h3>Share Image with Message, Subject and Link</h3>' + + '<div id="buttonShareMessageSubjectImageLink"></div>' + + 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl", "Message subject" as subject, and an image' + + '</fieldset>' + + + '<fieldset>' + + '<legend>Can Share Tests</legend>' + + + 'Target: <input id="inputCanShareVia" type="text"/><br>' + + + '<h3>Can Share via</h3>' + + '<div id="buttonCanShareVia"></div>' + + 'Expected result: should log OK if can share, or should log a list of available share targets' + + + '<h3>Can Share via Email</h3>' + + '<div id="buttonCanShareViaEmail"></div>' + + 'Expected result: should log OK if can share' + + '</fieldset>' + + ''; + + contentEl.innerHTML = '<div id="info"></div>' + TestSuite.$markup; + + createActionButton('availabe', function () { + clearLog(); + window.plugins.socialsharing.available(function(isAvailable) { + var message = 'is this plugin available? '; + message += isAvailable? 'Yes' : 'No'; + + logMessage(message, isAvailable? 'green' : 'red'); + }); + }, 'buttonIsAvailable'); + + createActionButton('share message', function () { + window.plugins.socialsharing.share('Message body'); + }, 'buttonShareMessage'); + + createActionButton('share message and subject', function () { + window.plugins.socialsharing.share('Message body', 'Message subject'); + }, 'buttonShareMessageWithSubject'); + + createActionButton('share link', function () { + window.plugins.socialsharing.share(null, null, null, 'http://www.x-services.nl'); + }, 'buttonShareLink'); + + createActionButton('share message and link', function () { + window.plugins.socialsharing.share('Message body', null, null, 'http://www.x-services.nl'); + }, 'buttonShareMessageAndLink'); + + createActionButton('share image', function () { + window.plugins.socialsharing.share(null, null, 'https://www.google.nl/images/srpr/logo4w.png', null); + }, 'buttonShareImage'); + + createActionButton('share image base 64', function () { + window.plugins.socialsharing.share(null, 'Android filename', 'data:image/png;base64,R0lGODlhDAAMALMBAP8AAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAUKAAEALAAAAAAMAAwAQAQZMMhJK7iY4p3nlZ8XgmNlnibXdVqolmhcRQA7', null); + }, 'buttonShareImageBase64'); + + createActionButton('share message with image', function () { + window.plugins.socialsharing.share('Message body', null, 'https://www.google.nl/images/srpr/logo4w.png', null); + }, 'buttonShareMessageImage'); + + createActionButton('share message, image, and link', function () { + window.plugins.socialsharing.share('Message body', null, 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl'); + }, 'buttonShareMessageImageLink'); + + createActionButton('share message, subject, image, and link', function () { + window.plugins.socialsharing.share('Message body', 'Message subject', 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl'); + }, 'buttonShareMessageSubjectImageLink'); + + createActionButton('can share via', function () { + var target = TestSuite.getCanShareViaTarget(); + + if(!target){ + console.error('must have a canShareVia target'); + } + else { + clearLog(); + window.plugins.socialsharing.canShareVia(target, 'msg', null, null, null, function(e){ + console.log('canShareVia success, see log for more information'); + logMessage('canShareVia: ' + e,'green'); + }, function(e){ + console.error('canShareVia fail, see log for more information'); + var message = "Share targets<br>"; + + message += "<ul>"; + + e.forEach(function(target){ + message += "<li>" + target + "</li>"; + }); + + message += "</ul>"; + logMessage(message,'red'); + }); + } + }, 'buttonCanShareVia'); + + createActionButton('can share via email', function () { + clearLog(); + window.plugins.socialsharing.canShareViaEmail(function(e){ + console.log('canShareViaEmail success, see log for more information'); + logMessage('canShareViaEmail: ' + e,'green'); + }, function(e){ + console.error('canShareViaEmail fail, see log for more information'); + logMessage('canShareViaEmail: ' + e,'red'); + }); + }, 'buttonCanShareViaEmail'); + + document.addEventListener('deviceready', init, false); +}; diff --git a/StoneIsland/plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js b/StoneIsland/plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js new file mode 100644 index 00000000..6ccd567b --- /dev/null +++ b/StoneIsland/plugins/cordova-plugin-x-socialsharing/www/SocialSharing.js @@ -0,0 +1,115 @@ +var cordova = require('cordova'); + +function SocialSharing() { +} + +// Override this method (after deviceready) to set the location where you want the iPad popup arrow to appear. +// If not overridden with different values, the popup is not used. Example: +// +// window.plugins.socialsharing.iPadPopupCoordinates = function() { +// return "100,100,200,300"; +// }; +SocialSharing.prototype.iPadPopupCoordinates = function () { + // left,top,width,height + return "-1,-1,-1,-1"; +}; + +SocialSharing.prototype.setIPadPopupCoordinates = function (coords) { + // left,top,width,height + cordova.exec(function() {}, this._getErrorCallback(function() {}, "setIPadPopupCoordinates"), "SocialSharing", "setIPadPopupCoordinates", [coords]); +}; + +SocialSharing.prototype.available = function (callback) { + cordova.exec(function (avail) { + callback(avail ? true : false); + }, null, "SocialSharing", "available", []); +}; + +SocialSharing.prototype.share = function (message, subject, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "share"), "SocialSharing", "share", [message, subject, this._asArray(fileOrFileArray), url]); +}; + +SocialSharing.prototype.shareViaTwitter = function (message, file /* multiple not allowed by twitter */, url, successCallback, errorCallback) { + var fileArray = this._asArray(file); + var ecb = this._getErrorCallback(errorCallback, "shareViaTwitter"); + if (fileArray.length > 1) { + ecb("shareViaTwitter supports max one file"); + } else { + cordova.exec(successCallback, ecb, "SocialSharing", "shareViaTwitter", [message, null, fileArray, url]); + } +}; + +SocialSharing.prototype.shareViaFacebook = function (message, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaFacebook"), "SocialSharing", "shareViaFacebook", [message, null, this._asArray(fileOrFileArray), url]); +}; + +SocialSharing.prototype.shareViaFacebookWithPasteMessageHint = function (message, fileOrFileArray, url, pasteMessageHint, successCallback, errorCallback) { + pasteMessageHint = pasteMessageHint || "If you like you can paste a message from your clipboard"; + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaFacebookWithPasteMessageHint"), "SocialSharing", "shareViaFacebookWithPasteMessageHint", [message, null, this._asArray(fileOrFileArray), url, pasteMessageHint]); +}; + +SocialSharing.prototype.shareViaWhatsApp = function (message, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsApp"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url]); +}; + +SocialSharing.prototype.shareViaSMS = function (options, phonenumbers, successCallback, errorCallback) { + var opts = options; + if (typeof options == "string") { + opts = {"message":options}; // for backward compatibility as the options param used to be the message + } + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaSMS"), "SocialSharing", "shareViaSMS", [opts, phonenumbers]); +}; + +SocialSharing.prototype.shareViaEmail = function (message, subject, toArray, ccArray, bccArray, fileOrFileArray, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaEmail"), "SocialSharing", "shareViaEmail", [message, subject, this._asArray(toArray), this._asArray(ccArray), this._asArray(bccArray), this._asArray(fileOrFileArray)]); +}; + +SocialSharing.prototype.canShareVia = function (via, message, subject, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "canShareVia"), "SocialSharing", "canShareVia", [message, subject, this._asArray(fileOrFileArray), url, via]); +}; + +SocialSharing.prototype.canShareViaEmail = function (successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "canShareViaEmail"), "SocialSharing", "canShareViaEmail", []); +}; + +SocialSharing.prototype.shareViaInstagram = function (message, fileOrFileArray, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaInstagram"), "SocialSharing", "shareViaInstagram", [message, null, this._asArray(fileOrFileArray), null]); +}; + +SocialSharing.prototype.shareVia = function (via, message, subject, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareVia"), "SocialSharing", "shareVia", [message, subject, this._asArray(fileOrFileArray), url, via]); +}; + +SocialSharing.prototype.saveToPhotoAlbum = function (fileOrFileArray, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "saveToPhotoAlbum"), "SocialSharing", "saveToPhotoAlbum", [this._asArray(fileOrFileArray)]); +}; + +SocialSharing.prototype._asArray = function (param) { + if (param == null) { + param = []; + } else if (typeof param === 'string') { + param = new Array(param); + } + return param; +}; + +SocialSharing.prototype._getErrorCallback = function (ecb, functionName) { + if (typeof ecb === 'function') { + return ecb; + } else { + return function (result) { + console.log("The injected error callback of '" + functionName + "' received: " + JSON.stringify(result)); + } + } +}; + +SocialSharing.install = function () { + if (!window.plugins) { + window.plugins = {}; + } + + window.plugins.socialsharing = new SocialSharing(); + return window.plugins.socialsharing; +}; + +cordova.addConstructor(SocialSharing.install); diff --git a/StoneIsland/plugins/fetch.json b/StoneIsland/plugins/fetch.json index b57114f3..b27f2f2d 100644 --- a/StoneIsland/plugins/fetch.json +++ b/StoneIsland/plugins/fetch.json @@ -70,5 +70,23 @@ }, "is_top_level": true, "variables": {} + }, + "cordova-plugin-customurlscheme": { + "source": { + "type": "registry", + "id": "cordova-plugin-customurlscheme" + }, + "is_top_level": true, + "variables": { + "URL_SCHEME": "stoneisland" + } + }, + "cordova-plugin-x-socialsharing": { + "source": { + "type": "registry", + "id": "cordova-plugin-x-socialsharing" + }, + "is_top_level": true, + "variables": {} } }
\ No newline at end of file diff --git a/StoneIsland/plugins/ios.json b/StoneIsland/plugins/ios.json index 337f2532..77cd0086 100644 --- a/StoneIsland/plugins/ios.json +++ b/StoneIsland/plugins/ios.json @@ -39,6 +39,10 @@ { "xml": "<feature name=\"PushNotification\"><param name=\"ios-package\" value=\"PushPlugin\" /></feature>", "count": 1 + }, + { + "xml": "<feature name=\"SocialSharing\"><param name=\"ios-package\" value=\"SocialSharing\" /><param name=\"onload\" value=\"true\" /></feature>", + "count": 1 } ] } @@ -68,6 +72,18 @@ "xml": false, "count": 1 } + ], + "Social.framework": [ + { + "xml": true, + "count": 1 + } + ], + "MessageUI.framework": [ + { + "xml": true, + "count": 1 + } ] } }, @@ -84,6 +100,12 @@ "xml": "<array><string>remote-notification</string></array>", "count": 1 } + ], + "CFBundleURLTypes": [ + { + "xml": "<array><dict><key>CFBundleURLSchemes</key><array><string>stoneisland</string></array></dict></array>", + "count": 1 + } ] } } @@ -116,6 +138,13 @@ }, "phonegap-plugin-push": { "PACKAGE_NAME": "io.cordova.hellocordova" + }, + "cordova-plugin-customurlscheme": { + "URL_SCHEME": "stoneisland", + "PACKAGE_NAME": "io.cordova.hellocordova" + }, + "cordova-plugin-x-socialsharing": { + "PACKAGE_NAME": "io.cordova.hellocordova" } }, "dependent_plugins": {} diff --git a/StoneIsland/www/css/blogs.css b/StoneIsland/www/css/blogs.css index f64aff29..0b9f8eed 100644 --- a/StoneIsland/www/css/blogs.css +++ b/StoneIsland/www/css/blogs.css @@ -226,6 +226,10 @@ ul.links { transform: translateZ(0) translateX(-50%) translateY(-50%); } +#archive .menu .items { + border-top: 1px solid transparent; +} + #archive.menu .menu { opacity: 1; pointer-events: auto; diff --git a/StoneIsland/www/css/nav.css b/StoneIsland/www/css/nav.css index 61834f8b..814a8c3f 100644 --- a/StoneIsland/www/css/nav.css +++ b/StoneIsland/www/css/nav.css @@ -334,3 +334,8 @@ h1 { letter-spacing: 1px; font-weight: bold; } + +.msg { + padding: 20px; + display: inline-block; +} diff --git a/StoneIsland/www/index.html b/StoneIsland/www/index.html index 24708536..2c6031c7 100644 --- a/StoneIsland/www/index.html +++ b/StoneIsland/www/index.html @@ -865,7 +865,6 @@ <div class="payment_name"></div> <div class="payment_method"></div> - <div class="payment_address"></div> </div> </div> @@ -1067,6 +1066,10 @@ <script src="js/sdk/product.js"></script> <script src="js/sdk/shipping.js"></script> +<script src="js/lib/etc/push.js"></script> +<script src="js/lib/etc/deeplink.js"></script> +<script src="js/lib/etc/geo.js"></script> + <script src="js/lib/view/View.js"></script> <script src="js/lib/view/Router.js"></script> <script src="js/lib/view/Scrollable.js"></script> diff --git a/StoneIsland/www/js/index.js b/StoneIsland/www/js/index.js index 208eab21..e6bdf49f 100644 --- a/StoneIsland/www/js/index.js +++ b/StoneIsland/www/js/index.js @@ -64,11 +64,12 @@ var app = (function(){ app.ready = function(){ if (window.cordova) { cordova.plugins.Keyboard.disableScroll(true) + geo.fetch() } app.view = null app.router = new SiteRouter () - app.account.connect( app.router.route.bind(app.router) ) + app.account.connect( app.router.launch.bind(app.router) ) $("body").removeClass("loading") } diff --git a/StoneIsland/www/js/lib/_router.js b/StoneIsland/www/js/lib/_router.js index b70d9be8..23daf4c7 100644 --- a/StoneIsland/www/js/lib/_router.js +++ b/StoneIsland/www/js/lib/_router.js @@ -47,6 +47,17 @@ var SiteRouter = Router.extend({ } } }, + + initial_route: null, + launch: function(){ + if (this.initial_route) { + this.parseRoute( this.initial_route ) + } + else { + this.route() + } + this.initial_route = null + }, go: function(url){ if (app.view && app.view.hide) { diff --git a/StoneIsland/www/js/lib/auth/SignupView.js b/StoneIsland/www/js/lib/auth/SignupView.js index 22b310de..a78af233 100644 --- a/StoneIsland/www/js/lib/auth/SignupView.js +++ b/StoneIsland/www/js/lib/auth/SignupView.js @@ -4,7 +4,8 @@ var SignupView = FormView.extend({ action: sdk.account.signup, last_data: null, - + +/* test_data: { "Email": "testit.account" + Math.floor(Math.random() * 10000000) + "@yoox.com", "Password": "TestPassword", @@ -16,7 +17,8 @@ var SignupView = FormView.extend({ "DataProfiling": true, "DataProfiling2": true, }, - +*/ + events: { "click .privacy-msg": "privacy_link", "submit form": "save", diff --git a/StoneIsland/www/js/lib/blogs/HubView.js b/StoneIsland/www/js/lib/blogs/HubView.js index a6ae958e..001b8161 100644 --- a/StoneIsland/www/js/lib/blogs/HubView.js +++ b/StoneIsland/www/js/lib/blogs/HubView.js @@ -4,6 +4,7 @@ var HubView = ScrollableView.extend({ template: $("#hub .template").html(), events: { + "click .share": "content-share", "click .store": "store_link", "click .gallery-left": "gallery_left", "click .gallery-right": "gallery_right", @@ -95,4 +96,8 @@ var HubView = ScrollableView.extend({ $(e.currentTarget).closest("hub_item").flickity('next') }, + share: function(){ + window.plugins.socialsharing.share( this.item['ModelNames'], null, null, "http://stoneisland.com/") + }, + })
\ No newline at end of file diff --git a/StoneIsland/www/js/lib/cart/CartConfirm.js b/StoneIsland/www/js/lib/cart/CartConfirm.js index aa6ec9e4..171f41a1 100644 --- a/StoneIsland/www/js/lib/cart/CartConfirm.js +++ b/StoneIsland/www/js/lib/cart/CartConfirm.js @@ -2,11 +2,26 @@ var CartConfirm = FormView.extend({ el: "#cart_confirm", + template: $("#cart_confirm .template").html(), + events: { }, initialize: function(opt){ this.parent = opt.parent + this.$rows = this.$(".rows") + this.$subtotal = this.$(".subtotal") + this.$shipping = this.$(".shipping") + this.$tax = this.$(".tax") + this.$total = this.$(".total") + + this.$shipping_address = this.$(".shipping_address") + this.$shipping_method = this.$(".shipping_method") + + this.$payment_name = this.$(".payment_name") + this.$payment_method = this.$(".payment_method") + this.$payment_address = this.$(".payment_address") + this.scroller = new IScroll('#cart_confirm', app.iscroll_options) }, @@ -16,9 +31,99 @@ var CartConfirm = FormView.extend({ app.footer.show("PLACE ORDER", "CANCEL") window.location.hash = "#/cart/confirm" this.deferScrollToTop() + + app.curtain.show("loading") + promise(sdk.cart.get_status).then( this.populate.bind(this) ) }, - populate: function(){ + populate: function(data){ + console.log(data) + + data = data.Cart + + this.$rows.empty() + + data.Items.forEach(function(item){ + var $el = $("<div class='item'><img src='img/spinner.gif'></div>") + this.$rows.append($el) + var code_ten = item.Code10 + var size_id = item.Size + + var code = code_ten.substr(0, 8) + app.product.find(code, function(data, details){ + var descriptions = app.product.get_descriptions( details ) + + var name_partz = descriptions['ModelNames'].split(' ') + var num = name_partz.shift() + var title = name_partz.join(' ') + var type = title_case( descriptions['MicroCategory'] ) + + var color_name, size_name + + details.Item.ModelColors.some(function(color){ + if (color['Code10'] == code_ten) { + color_name = color['ColorDescription'] + return true + } + return false + }) + + details.Item.ModelSizes.some(function(size){ + if (size['SizeId'] == size_id) { + // console.log(size) + size_name = size['Default']['Text'] + size_name = SIZE_LOOKUP[ size_name ] || size_name + if (! size_name && ! size['Default']['Labeled']) { + size_name = size['Default']['Text'] + " " + size['Default']['ClassFamily'] + } + + return true + } + return false + }) + +// size_name = item.DefaultSize + " " + item.DefaultSizeClassFamily + + var t = this.template + .replace(/{{image}}/, sdk.image(item['Code10'], '11_f')) + .replace(/{{sku}}/, num) + .replace(/{{title}}/, title) + .replace(/{{type}}/, type) + .replace(/{{size}}/, size_name || "DEFAULT") + .replace(/{{color}}/, color_name || "DEFAULT") + .replace(/{{quantity}}/, 1) + .replace(/{{price}}/, as_cash(details.Item.Price.DiscountedPrice)) + $el.data("price", details.Item.Price.DiscountedPrice) + $el.html(t) + this.refreshScroller() + }.bind(this)) + }.bind(this)) + + var subtotal = data.Totals.TotalWithoutPromotions + var shipping_cost = data.DeliveryMethod.Selected.Amount.Total + var tax = data.Totals.TotalSalesTax + var total = data.Totals.TotalToPay + + this.$subtotal.html( as_cash(subtotal) ) + this.$shipping.html( as_cash(shipping_cost) ) + this.$tax.html( as_cash(tax) ) + this.$total.html( as_cash(total) ) + + var street = data.Receiver.StreetWithNumber.replace(/\n$/,"").replace("\n", ", ") + var address = data.Receiver.Name.toUpperCase() + " " + data.Receiver.Surname.toUpperCase() + "<br>" + street + ", " + address += data.Receiver.City + ", " + data.Receiver.Region + " " + data.Receiver.PostalCode + + this.$shipping_address.html(address) + this.$shipping_method.html(data.DeliveryMethod.Selected.Type == 1 ? "* STANDARD SHIPPING" : "* EXPRESS SHIPPING") + + var cc = data.Payment.CreditCard + var cc_street = cc.HolderAddress.replace(/\n$/,"").replace("\n", ", ") + var cc_type = cc.Type == "AmericanExpress" ? "American Express" : cc.Type + + this.$payment_name.html( cc.HolderName.toUpperCase() + " " + cc.HolderSurname.toUpperCase() ) + this.$payment_method.html( cc_type.toUpperCase() + " XXXX-XXXX-XXXX-" + cc.Last4 ) + + app.curtain.hide("loading") }, save: function(){ diff --git a/StoneIsland/www/js/lib/cart/CartPayment.js b/StoneIsland/www/js/lib/cart/CartPayment.js index fec5e1d1..11d6cddf 100644 --- a/StoneIsland/www/js/lib/cart/CartPayment.js +++ b/StoneIsland/www/js/lib/cart/CartPayment.js @@ -9,6 +9,7 @@ var CartPayment = FormView.extend({ address_list_mode: false, cc_list_mode: false, + data: {}, events: { "change [name=SameAsShipping]": "toggle_shipping", @@ -28,6 +29,7 @@ var CartPayment = FormView.extend({ this.$cc_list = this.$(".cc_list") this.$cc_form = this.$(".cc") this.$cc_dropdown = this.$(".cc_dropdown") + this.$cc_confirm = this.$(".cc_confirm") this.address = new AddressView ({ parent: this, checkPhone: false }) this.cc = new CreditCardView ({ parent: this }) @@ -50,6 +52,7 @@ var CartPayment = FormView.extend({ setTimeout(function(){ var state = this.$same_as_shipping.prop("checked") this.$billing_address_rapper.toggle( ! state ) + this.address.disabled = state }.bind(this)) }, @@ -75,6 +78,7 @@ var CartPayment = FormView.extend({ this.cc.disabled = this.cc_list_mode this.$cc_form.toggle(! this.cc_list_mode) this.$cc_list.toggle(this.cc_list_mode) + this.$cc_confirm.toggle(this.cc_list_mode) }, populate: function(){ @@ -107,37 +111,62 @@ var CartPayment = FormView.extend({ }, finalize: function(data){ - var shipping_info = {}, address_data, address_id, cc_info = {}, cc_data, cc_id + var shipping_info = {}, address_data, address_id, cc_info = {}, cc_data, cc_id var shipping_type = $("[name=ShippingType]").filter(function(){ return $(this).prop("checked") }).val() - if (this.list_mode) { - address_id = $("[name=AddressId]").filter(function(){ return $(this).prop("checked") }).val() + if (this.$same_as_shipping.prop("checked")) { + address_data = app.cart.shipping.data + } + else if (this.address_list_mode) { + address_id = this.$("[name=AddressId]").filter(function(){ return $(this).prop("checked") }).val() address_data = app.account.addressLookup[ address_id ] } else { address_data = data } + if (this.cc_list_mode) { - cc_id = $("[name=CCId]").filter(function(){ return $(this).prop("checked") }).val() + cc_id = this.$("[name=CCId]").filter(function(){ return $(this).prop("checked") }).val() cc_data = app.account.ccLookup[ cc_id ] + + var card_on_file = { + "guid": cc_data.Guid, + "cvv": this.$("[name=CvvConfirm]"), + } + + app.curtain.show("loading") + promise(sdk.cart.use_stored_credit_card, { data: card_on_file }).then(function(data){ + app.curtain.hide("loading") + this.success() + }.bind(this)).error(function(data){ + app.curtain.hide("loading") + }.bind(this)) + + return } else { cc_data = data } - shipping_info.Name = address_data.Name - shipping_info.Surname = address_data.Surname - shipping_info.Email = auth.user.Email - shipping_info.Phone = address_data.Phone - shipping_info.Mobile = address_data.Phone - shipping_info.StreetWithNumber = address_data.Address - shipping_info.PostalCode = address_data.ZipCode - shipping_info.City = address_data.City - shipping_info.Province = address_data.Province - shipping_info.Region = address_data.Province - shipping_info.CountryCode = "US" + var credit_info = { + "HolderName": address_data.Name, + "HolderSurname": address_data.Surname, + "HolderAddress": address_data.Address || address_data.StreetWithNumber, + "HolderCity": address_data.City, + "HolderProvince": address_data.Province, + "HolderZip": address_data.PostalCode || address_data.ZipCode, + "HolderISOCountry": CANADIAN_LOOKUP[ address_data.Province ] ? "CA" : "US", + "HolderEmail": auth.user.Email, + "CardNumber": cc_data['Number'], + "Type": cc_data.Type, + "ExpirationMonth": cc_data.ExpirationMonth, + "ExpirationYear": cc_data.ExpirationYear.substr(2,3), + "Cvv": cc_data.Cvv, + } - return shipping_info + console.log( credit_info ) + + return credit_info }, success: function(){ diff --git a/StoneIsland/www/js/lib/cart/CartShipping.js b/StoneIsland/www/js/lib/cart/CartShipping.js index 1a9653e1..ef906804 100644 --- a/StoneIsland/www/js/lib/cart/CartShipping.js +++ b/StoneIsland/www/js/lib/cart/CartShipping.js @@ -5,6 +5,7 @@ var CartShipping = FormView.extend({ action: sdk.cart.set_shipping_address, list_mode: true, + data: {}, template: $("#cart_shipping .template").html(), @@ -53,6 +54,22 @@ var CartShipping = FormView.extend({ }.bind(this)) }, + load_form: function(cart_data){ + var data = cart_data.Cart.Receiver + var addy = data.StreetWithNumber.split("\n") + data.Address1 = addy[0] || "" + data.Address2 = addy[1] || "" + data.ZipCode = data.PostalCode + data.Province = data.Region + this.load_data(data) + + this.data = data + if (cart_data.Cart.DeliveryMethod && cart_data.Cart.DeliveryMethod.Selected && cart_data.Cart.DeliveryMethod.Type) { + $("#standard-shipping").prop("checked", cart_data.Cart.DeliveryMethod.Type == 1) + $("#express-shipping").prop("checked", cart_data.Cart.DeliveryMethod.Type == 2) + } + }, + toggle_dropdown: function(state){ if (! app.account.addresses.length) { state = false @@ -84,7 +101,7 @@ var CartShipping = FormView.extend({ }) if (this.list_mode) { - address_id = $("[name=AddressId]").filter(function(){ return $(this).prop("checked") }).val() + address_id = this.$("[name=AddressId]").filter(function(){ return $(this).prop("checked") }).val() address_data = app.account.addressLookup[ address_id ] } else { @@ -101,7 +118,9 @@ var CartShipping = FormView.extend({ shipping_info.City = address_data.City shipping_info.Province = address_data.Province shipping_info.Region = address_data.Province - shipping_info.CountryCode = "US" + shipping_info.CountryCode = CANADIAN_LOOKUP[ address_data.Province ] ? "CA" : "US" + + this.data = shipping_info return shipping_info }, diff --git a/StoneIsland/www/js/lib/cart/CartSummary.js b/StoneIsland/www/js/lib/cart/CartSummary.js index 72c44405..0ad57020 100644 --- a/StoneIsland/www/js/lib/cart/CartSummary.js +++ b/StoneIsland/www/js/lib/cart/CartSummary.js @@ -119,6 +119,10 @@ var CartSummary = ScrollableView.extend({ }.bind(this)) }.bind(this)) + if (data.Cart.Receiver && data.Cart.Receiver.City) { + app.cart.shipping.load_form( data ) + } + this.updateTotals() this.el.className = "full" @@ -133,7 +137,7 @@ var CartSummary = ScrollableView.extend({ updateTotals: function(){ var subtotal = this.data.Cart.Totals.TotalWithoutPromotions var shipping_cost = this.data.Cart.DeliveryMethod.Selected.Amount.Total - var tax = 0 + var tax = this.data.Cart.Totals.TotalSalesTax var total = this.data.Cart.Totals.TotalToPay this.$subtotal.html( as_cash(subtotal) ) diff --git a/StoneIsland/www/js/lib/etc/deeplink.js b/StoneIsland/www/js/lib/etc/deeplink.js new file mode 100644 index 00000000..648dd167 --- /dev/null +++ b/StoneIsland/www/js/lib/etc/deeplink.js @@ -0,0 +1,3 @@ +function handleOpenURL (url) { + app.router.initial_route = url +}
\ No newline at end of file diff --git a/StoneIsland/www/js/lib/etc/geo.js b/StoneIsland/www/js/lib/etc/geo.js new file mode 100644 index 00000000..0270d681 --- /dev/null +++ b/StoneIsland/www/js/lib/etc/geo.js @@ -0,0 +1,33 @@ +var geo = (function(){ + var geo = {} + + geo.fetch = function(){ + navigator.geolocation.getCurrentPosition(geo.success, geo.error, {timeout: 15000}) + } + + geo.success = function(position){ + var lat_str = as_degrees( position.coords.latitude ) + var lng_str = as_degrees( position.coords.longitude ) + } + + geo.error = function(error){ + $(".latlng").html( "+40° 58' 90\" -74° 04' 46\"" ) + } + + function as_degrees (n) { + var s = "" + if (n >= 0) s += "+" + s += Math.floor(n) + "° " + + n %= 1 + n *= 60 + s += Math.floor(n) + "'" + + n %= 1 + n *= 60 + + s += Math.floor(n) + '"' + } + + return geo +})()
\ No newline at end of file diff --git a/StoneIsland/www/js/lib/etc/push.js b/StoneIsland/www/js/lib/etc/push.js new file mode 100644 index 00000000..ab0c0141 --- /dev/null +++ b/StoneIsland/www/js/lib/etc/push.js @@ -0,0 +1 @@ +//
\ No newline at end of file diff --git a/StoneIsland/www/js/lib/nav/CreditCardView.js b/StoneIsland/www/js/lib/nav/CreditCardView.js index ba3ac54a..63784618 100644 --- a/StoneIsland/www/js/lib/nav/CreditCardView.js +++ b/StoneIsland/www/js/lib/nav/CreditCardView.js @@ -40,6 +40,7 @@ var CreditCardView = SerializableView.extend({ }, validate_fields: function(data, errors){ + if (this.disabled) { return } var card = this.$number.validateCreditCard(this.cardOptions) if (! card.valid) { errors.push([ "Number", "Your card number is invalid." ]) } if (! data.ExpirationMonth || data.ExpirationMonth == "NONE") { errors.push([ "ExpirationMonth", "Please enter the expiration month." ]) } diff --git a/StoneIsland/www/js/lib/products/ProductView.js b/StoneIsland/www/js/lib/products/ProductView.js index 92a0e0f7..5b7ee6d2 100644 --- a/StoneIsland/www/js/lib/products/ProductView.js +++ b/StoneIsland/www/js/lib/products/ProductView.js @@ -240,14 +240,18 @@ var ProductView = ScrollableView.extend({ app.last_view = app.cart } else if ( ! auth.has_cart() ) { + app.curtain.show("loading") auth.create_cart(function(){ auth.add_deferred_product_to_cart(function(){ + app.curtain.hide("loading") app.router.go("cart") }) }) } else { + app.curtain.show("loading") auth.add_deferred_product_to_cart(function(){ + app.curtain.hide("loading") if (opt.route) { app.router.go("cart") } @@ -263,6 +267,7 @@ var ProductView = ScrollableView.extend({ }, share: function(){ + window.plugins.socialsharing.share( this.item['ModelNames'], null, null, "http://stoneisland.com/") }, }) diff --git a/StoneIsland/www/js/lib/view/Serializable.js b/StoneIsland/www/js/lib/view/Serializable.js index b1e095d3..6ef8eda3 100644 --- a/StoneIsland/www/js/lib/view/Serializable.js +++ b/StoneIsland/www/js/lib/view/Serializable.js @@ -68,9 +68,11 @@ var SerializableView = View.extend({ var data = data || this.serialize() var errors = errors || [] var presence_msgs = this.validate_presence || {} - Object.keys(presence_msgs).forEach(function(k){ - if (! data[k]) errors.push( [ k, presence_msgs[k] ] ) - }) + if (! this.disabled) { + Object.keys(presence_msgs).forEach(function(k){ + if (! data[k]) errors.push( [ k, presence_msgs[k] ] ) + }) + } this.validate_fields && this.validate_fields(data, errors) this.cc && this.cc.validate(data, errors) this.address && this.address.validate(data, errors) |
