diff --git a/.gitignore b/.gitignore index f3485004..156a3967 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ npm-debug.log* yarn-error.log* .vscode .idea +cordova-ionic-phonegap-branch-deep-linking-attribution.iml src/android/.idea \ No newline at end of file diff --git a/package.json b/package.json index 1f898058..245e90de 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "branch-cordova-sdk", "description": "Branch Metrics Cordova SDK", "main": "src/index.js", - "version": "5.1.0", + "version": "5.2.0", "homepage": "https://github.com/BranchMetrics/cordova-ionic-phonegap-branch-deep-linking", "repository": { "type": "git", diff --git a/plugin.xml b/plugin.xml index 07ca4aca..5d746a60 100644 --- a/plugin.xml +++ b/plugin.xml @@ -63,7 +63,7 @@ SOFTWARE. - + @@ -87,7 +87,7 @@ SOFTWARE. - + diff --git a/src/android/io/branch/BranchSDK.java b/src/android/io/branch/BranchSDK.java index 97dddfd8..cb839b8e 100644 --- a/src/android/io/branch/BranchSDK.java +++ b/src/android/io/branch/BranchSDK.java @@ -6,6 +6,7 @@ import android.net.Uri; import android.os.Build; import android.util.Log; +import android.util.Base64; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; @@ -16,6 +17,7 @@ import java.util.ArrayList; import java.util.Iterator; +import java.io.IOException; import io.branch.indexing.BranchUniversalObject; import io.branch.referral.Branch; @@ -24,6 +26,7 @@ import io.branch.referral.ServerRequestGetCPID.BranchCrossPlatformIdListener; import io.branch.referral.ServerRequestGetLATD.BranchLastAttributedTouchDataListener; import io.branch.referral.SharingHelper; +import io.branch.referral.QRCode.BranchQRCode; import io.branch.referral.util.BRANCH_STANDARD_EVENT; import io.branch.referral.util.BranchCPID; import io.branch.referral.util.BranchEvent; @@ -31,6 +34,7 @@ import io.branch.referral.util.CurrencyType; import io.branch.referral.util.ShareSheetStyle; + public class BranchSDK extends CordovaPlugin { static class BranchLinkProperties extends io.branch.referral.util.LinkProperties { @@ -137,14 +141,14 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return true; } else if (action.equals("userCompletedAction")) { if (args.length() < 1 && args.length() > 2) { - callbackContext.error(String.format("Parameter mismatched. 1-2 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); return true; } else if (action.equals("sendBranchEvent")) { if (args.length() < 1 && args.length() > 2) { - callbackContext.error(String.format("Parameter mismatched. 1-2 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); @@ -160,7 +164,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return true; } else if (action.equals("createBranchUniversalObject")) { if (args.length() != 1) { - callbackContext.error(String.format("Parameter mismatched. 1 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); @@ -176,21 +180,21 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo } else if (action.equals(("generateShortUrl"))) { if (args.length() != 3) { - callbackContext.error(String.format("Parameter mismatched. 3 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); return true; } else if (action.equals("registerView")) { if (args.length() != 1) { - callbackContext.error(String.format("Parameter mismatched. 1 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); return true; } else if (action.equals("showShareSheet")) { if (args.length() < 3) { - callbackContext.error(String.format("Parameter mismatched. 3 is required but %d is given", args.length())); + callbackContext.error(String.format("Parameter count mismatch")); return false; } cordova.getActivity().runOnUiThread(r); @@ -223,6 +227,13 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo branchObjectWrappers.set(args.getInt(0), branchObjWrapper); + } else if (action.equals("getBranchQRCode")) { + if (args.length() != 4) { + callbackContext.error(String.format("Parameter count mismatch")); + return false; + } + cordova.getActivity().runOnUiThread(r); + return true; } return true; @@ -508,6 +519,70 @@ private void generateShortUrl(int instanceIdx, JSONObject options, JSONObject co } + /** + * Generate a QR code. + * + * @param qrCodeSettings A {@link JSONObject} value to set QR cide options. + * @param instanceIdx The instance index from branchObjects array + * @param options A {@link JSONObject} value to set URL options. + * @param controlParams A {@link JSONObject} value to set the URL control parameters. + */ + private void getBranchQRCode(JSONObject qrCodeSettings, int instanceIdx, JSONObject options, JSONObject controlParams, CallbackContext callbackContext) throws JSONException { + + BranchLinkProperties linkProperties = createLinkProperties(options, controlParams); + + BranchUniversalObjectWrapper branchUniversalWrapper = (BranchUniversalObjectWrapper) this.branchObjectWrappers.get(instanceIdx); + BranchUniversalObject buo = branchUniversalWrapper.branchUniversalObj; + + BranchQRCode branchQRCode = new BranchQRCode(); + if (qrCodeSettings.has("codeColor")) { + branchQRCode.setCodeColor(qrCodeSettings.getString("codeColor")); + } + if (qrCodeSettings.has("backgroundColor")) { + branchQRCode.setBackgroundColor(qrCodeSettings.getString("backgroundColor")); + } + if (qrCodeSettings.has("centerLogo")) { + branchQRCode.setCenterLogo(qrCodeSettings.getString("centerLogo")); + } + if (qrCodeSettings.has("width")) { + branchQRCode.setWidth(qrCodeSettings.getInt("width")); + } + if (qrCodeSettings.has("margin")) { + branchQRCode.setMargin(qrCodeSettings.getInt("margin")); + } + if (qrCodeSettings.has("imageFormat")) { + String imageFormat = qrCodeSettings.getString("imageFormat"); + if (imageFormat != null ) { + if (imageFormat.equals("JPEG")) { + branchQRCode.setImageFormat(BranchQRCode.BranchImageFormat.JPEG); + } else { + branchQRCode.setImageFormat(BranchQRCode.BranchImageFormat.PNG); + } + } + } + + try { + branchQRCode.getQRCodeAsData(this.activity, buo, linkProperties, new BranchQRCode.BranchQRCodeDataHandler() { + @Override + public void onSuccess(byte[] qrCodeData) { + String qrCodeString = Base64.encodeToString(qrCodeData, Base64.DEFAULT); + Log.d(LCAT, qrCodeString); + callbackContext.success(qrCodeString); + } + + @Override + public void onFailure(Exception e) { + Log.d(LCAT, e.getMessage()); + callbackContext.error(e.getMessage()); + } + }); + } catch (IOException e) { + e.printStackTrace(); + Log.d(LCAT, e.getMessage()); + callbackContext.error(e.getMessage()); + } + } + /** *

Sets the cookie based matching for all incoming requests.

*

If you want cookie based matching, call this before initUserSession

@@ -1017,7 +1092,7 @@ public void onLinkCreate(String url, BranchError error) { } } - + protected class ShowShareSheetListener implements Branch.BranchLinkShareListener { private CallbackContext _onShareLinkDialogLaunched; @@ -1197,6 +1272,8 @@ public void run() { lastAttributedTouchData(this.callbackContext); } else if (this.action.equals(("generateShortUrl"))) { generateShortUrl(this.args.getInt(0), this.args.getJSONObject(1), this.args.getJSONObject(2), this.callbackContext); + } else if (this.action.equals(("getBranchQRCode"))) { + getBranchQRCode(this.args.getJSONObject(0), this.args.getInt(1), this.args.getJSONObject(2), this.args.getJSONObject(3), this.callbackContext); } else if (this.action.equals("registerView")) { registerView(this.args.getInt(0), this.callbackContext); } else if (this.action.equals("showShareSheet")) { diff --git a/src/index.js b/src/index.js index 05b03757..eae03123 100644 --- a/src/index.js +++ b/src/index.js @@ -39,7 +39,7 @@ var Branch = function Branch() { this.sessionInitialized = false; }; -// JavsSript to SDK wrappers +// JavaScript to SDK wrappers function execute(method, params) { var output = !params ? [] : params; @@ -314,5 +314,29 @@ Branch.prototype.lastAttributedTouchData = function lastAttributedTouchData() { return execute("lastAttributedTouchData"); }; +Branch.prototype.getBranchQRCode = function getBranchQRCode( + qrCodeSettings, + branchUniversalObject, + analytics, + properties +) { + var args = []; + if (qrCodeSettings) { + args.push(qrCodeSettings); + } + if (branchUniversalObject) { + args.push(branchUniversalObject.instanceId); + } + if (analytics) { + args.push(analytics); + } + if (properties) { + args.push(properties); + } + + return execute("getBranchQRCode", args); +}; + + // export Branch object module.exports = new Branch(); diff --git a/src/ios/BranchSDK.m b/src/ios/BranchSDK.m index ca38c9c7..5b971816 100644 --- a/src/ios/BranchSDK.m +++ b/src/ios/BranchSDK.m @@ -648,6 +648,129 @@ - (void)lastAttributedTouchData:(CDVInvokedUrlCommand *)command { }]; } +- (void)getBranchQRCode:(CDVInvokedUrlCommand*)command +{ + int branchUniversalObjectId = [[command.arguments objectAtIndex:1] intValue]; + NSMutableDictionary *branchUniversalObjDict = [self.branchUniversalObjArray objectAtIndex:branchUniversalObjectId]; + BranchUniversalObject *branchUniversalObj = [branchUniversalObjDict objectForKey:@"branchUniversalObj"]; + + BranchLinkProperties *linkProperties = [BranchLinkProperties new]; + + NSDictionary *arg1 = [command.arguments objectAtIndex:2]; + NSDictionary *arg2 = [command.arguments objectAtIndex:3]; + + for (id key in arg1) { + if ([key isEqualToString:@"duration"]) { + linkProperties.matchDuration = (NSUInteger)[((NSNumber *)[arg1 objectForKey:key]) integerValue]; + } + else if ([key isEqualToString:@"feature"]) { + linkProperties.feature = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"stage"]) { + linkProperties.stage = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"campaign"]) { + linkProperties.campaign = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"alias"]) { + linkProperties.alias = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"channel"]) { + linkProperties.channel = [arg1 objectForKey:key]; + } + else if ([key isEqualToString:@"tags"] && [[arg1 objectForKey:key] isKindOfClass:[NSArray class]]) { + linkProperties.tags = [arg1 objectForKey:key]; + } + } + if (arg2) { + for (id key in arg2) { + [linkProperties addControlParam:key withValue:[arg2 objectForKey:key]]; + } + } + + NSMutableDictionary *qrCodeSettingsMap = [command.arguments objectAtIndex:0]; + + BranchQRCode *qrCode = [BranchQRCode new]; + + if (qrCodeSettingsMap[@"codeColor"]) { + qrCode.codeColor = [self colorWithHexString:qrCodeSettingsMap[@"codeColor"]]; + } + if (qrCodeSettingsMap[@"backgroundColor"]) { + qrCode.backgroundColor = [self colorWithHexString:qrCodeSettingsMap[@"backgroundColor"]]; + } + if (qrCodeSettingsMap[@"centerLogo"]) { + qrCode.centerLogo = qrCodeSettingsMap[@"centerLogo"]; + } + if (qrCodeSettingsMap[@"width"]) { + qrCode.width = qrCodeSettingsMap[@"width"]; + } + if (qrCodeSettingsMap[@"margin"]) { + qrCode.margin = qrCodeSettingsMap[@"margin"]; + } + if (qrCodeSettingsMap[@"imageFormat"]) { + if ([qrCodeSettingsMap[@"imageFormat"] isEqual:@"JPEG"]) { + qrCode.imageFormat = BranchQRCodeImageFormatJPEG; + } else { + qrCode.imageFormat = BranchQRCodeImageFormatPNG; + } + } + + [qrCode getQRCodeAsData:branchUniversalObj linkProperties:linkProperties completion:^(NSData * _Nonnull qrCodeData, NSError * _Nonnull error) { + CDVPluginResult* pluginResult = nil; + + if (!error) { + NSString* imageString = [qrCodeData base64EncodedStringWithOptions:nil]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:imageString]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; + } + + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; +} + +- (UIColor *) colorWithHexString: (NSString *) hexString { + NSString *colorString = [[hexString stringByReplacingOccurrencesOfString: @"#" withString: @""] uppercaseString]; + CGFloat alpha, red, blue, green; + switch ([colorString length]) { + case 3: // #RGB + alpha = 1.0f; + red = [self colorComponentFrom: colorString start: 0 length: 1]; + green = [self colorComponentFrom: colorString start: 1 length: 1]; + blue = [self colorComponentFrom: colorString start: 2 length: 1]; + break; + case 4: // #ARGB + alpha = [self colorComponentFrom: colorString start: 0 length: 1]; + red = [self colorComponentFrom: colorString start: 1 length: 1]; + green = [self colorComponentFrom: colorString start: 2 length: 1]; + blue = [self colorComponentFrom: colorString start: 3 length: 1]; + break; + case 6: // #RRGGBB + alpha = 1.0f; + red = [self colorComponentFrom: colorString start: 0 length: 2]; + green = [self colorComponentFrom: colorString start: 2 length: 2]; + blue = [self colorComponentFrom: colorString start: 4 length: 2]; + break; + case 8: // #AARRGGBB + alpha = [self colorComponentFrom: colorString start: 0 length: 2]; + red = [self colorComponentFrom: colorString start: 2 length: 2]; + green = [self colorComponentFrom: colorString start: 4 length: 2]; + blue = [self colorComponentFrom: colorString start: 6 length: 2]; + break; + default: + NSLog(@"Error: Invalid color value. It should be a hex value of the form #RBG, #ARGB, #RRGGBB, or #AARRGGBB"); + break; + } + return [UIColor colorWithRed: red green: green blue: blue alpha: alpha]; +} + +- (CGFloat) colorComponentFrom: (NSString *) string start: (NSUInteger) start length: (NSUInteger) length { + NSString *substring = [string substringWithRange: NSMakeRange(start, length)]; + NSString *fullHex = length == 2 ? substring : [NSString stringWithFormat: @"%@%@", substring, substring]; + unsigned hexComponent; + [[NSScanner scannerWithString: fullHex] scanHexInt: &hexComponent]; + return hexComponent / 255.0; +} #pragma mark - URL Methods (not fully implemented YET!)