From 54f2d710643e23cb592713a444db3e0c9d58d37b Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Wed, 8 Jun 2022 01:00:00 -0300 Subject: [PATCH 001/125] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f4b4445..e7fba02d 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ List BUO on Search / Remove BUO from Search| X | X | Not supported Register view| X | X | X Track User Actions and Events| X | X | X Init Branch Session and Deep Link| X | X | X -Referral rewards| X | X | X +Last Attributed Touch Data| X | X | X ## Getting Started ### Configure Branch Dashboard From 8dc708cf4f9e5b4e779bf11975aa64fa2b5a12db Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 16 Jun 2022 16:06:14 -0300 Subject: [PATCH 002/125] New Version 6.0 ## 6.0.0 * Updated Native `Android` SDK: * Android Native SDK Update 5.2.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) ###BREAKING CHANGE * Minimum required Dart SDK version to 2.17 (Flutter 3.0) * Removed deprecated methods: * `initWeb` * `loadRewards` * `redeemRewards` * `getCreditHistory` ###Refactors * General improvements in code * Fix analyzer code style warnings --- CHANGELOG.md | 25 +- LICENSE | 22 +- analysis_options.yaml | 7 + android/.gitignore | 17 +- android/build.gradle | 83 +- android/gradle.properties | 4 - .../gradle/wrapper/gradle-wrapper.properties | 5 - android/settings.gradle | 2 +- android/src/main/AndroidManifest.xml | 8 +- .../ApplicationInfoHelper.java | 100 +- .../FlutterBranchSdkHelper.java | 506 ++--- .../FlutterBranchSdkInit.java | 62 +- .../FlutterBranchSdkPlugin.java | 1782 +++++++-------- .../flutter_branch_sdk/LogUtils.java | 24 +- example/.gitignore | 84 +- example/.metadata | 10 - example/README.md | 25 +- example/analysis_options.yaml | 29 + example/android/.gitignore | 20 +- example/android/app/build.gradle | 128 +- example/android/app/proguard-rules.pro | 30 +- .../android/app/src/debug/AndroidManifest.xml | 15 +- .../android/app/src/main/AndroidManifest.xml | 126 +- .../MainActivity.java | 13 +- .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 24 +- .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 26 +- .../app/src/profile/AndroidManifest.xml | 15 +- example/android/build.gradle | 60 +- example/android/gradle.properties | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 12 +- example/android/settings.gradle | 26 +- example/android/settings_aar.gradle | 1 - example/ios/.gitignore | 66 +- example/ios/Flutter/.last_build_id | 1 - example/ios/Flutter/AppFrameworkInfo.plist | 52 +- example/ios/Flutter/Debug.xcconfig | 4 +- example/ios/Flutter/Release.xcconfig | 4 +- example/ios/Podfile.lock | 2 +- example/ios/Runner.xcodeproj/project.pbxproj | 210 +- .../contents.xcworkspacedata | 14 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 174 +- .../xcshareddata/IDEWorkspaceChecks.plist | 16 +- .../xcshareddata/WorkspaceSettings.xcsettings | 8 + example/ios/Runner/AppDelegate.swift | 26 +- .../AppIcon.appiconset/Contents.json | 244 +-- .../LaunchImage.imageset/Contents.json | 46 +- .../LaunchImage.imageset/README.md | 8 +- .../Runner/Base.lproj/LaunchScreen.storyboard | 74 +- example/ios/Runner/Base.lproj/Main.storyboard | 52 +- example/ios/Runner/Info.plist | 18 +- example/ios/Runner/Runner-Bridging-Header.h | 2 +- example/ios/Runner/Runner.entitlements | 10 +- example/lib/custom_button.dart | 39 +- example/lib/main.dart | 1167 +++++----- example/pubspec.lock | 18 +- example/pubspec.yaml | 147 +- example/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes example/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes example/web/index.html | 122 +- example/web/manifest.json | 58 +- ios/.gitignore | 73 +- ios/Classes/SwiftFlutterBranchSdkPlugin.swift | 120 +- ios/flutter_branch_sdk.podspec | 48 +- lib/flutter_branch_sdk.dart | 19 +- lib/src/branch_event.dart | 121 -- ...dk_plugin.dart => flutter_branch_sdk.dart} | 377 ++-- ...=> flutter_branch_sdk_method_channel.dart} | 659 +++--- ...flutter_branch_sdk_platform_interface.dart | 420 ++-- lib/src/flutter_branch_sdk_web.dart | 867 ++++---- .../app_tracking_transparency.dart | 32 +- lib/src/objects/branch_event.dart | 131 ++ lib/src/{ => objects}/branch_response.dart | 40 +- .../branch_universal_object.dart | 316 +-- lib/src/{ => objects}/content_meta_data.dart | 919 ++++---- lib/src/{ => objects}/content_schema.dart | 88 +- lib/src/{ => objects}/link_properties.dart | 152 +- lib/src/web/branch_js.dart | 1912 ++++++++--------- pubspec.lock | 12 +- pubspec.yaml | 79 +- 83 files changed, 5958 insertions(+), 6352 deletions(-) create mode 100644 analysis_options.yaml delete mode 100644 android/gradle.properties delete mode 100644 android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 example/.metadata create mode 100644 example/analysis_options.yaml mode change 100755 => 100644 example/android/app/proguard-rules.pro create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/values-night/styles.xml delete mode 100644 example/android/settings_aar.gradle delete mode 100644 example/ios/Flutter/.last_build_id create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/web/icons/Icon-maskable-192.png create mode 100644 example/web/icons/Icon-maskable-512.png delete mode 100644 lib/src/branch_event.dart rename lib/src/{flutter_branch_sdk_plugin.dart => flutter_branch_sdk.dart} (69%) rename lib/src/{flutter_branch_sdk_mobile.dart => flutter_branch_sdk_method_channel.dart} (51%) rename lib/src/{ => objects}/app_tracking_transparency.dart (96%) create mode 100644 lib/src/objects/branch_event.dart rename lib/src/{ => objects}/branch_response.dart (87%) rename lib/src/{ => objects}/branch_universal_object.dart (63%) rename lib/src/{ => objects}/content_meta_data.dart (61%) rename lib/src/{ => objects}/content_schema.dart (95%) rename lib/src/{ => objects}/link_properties.dart (87%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86c8969e..f1054d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## 6.0.0 +* Updated Native `Android` SDK: + * Android Native SDK Update 5.2.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) + +###BREAKING CHANGE + +* Minimum required Dart SDK version to 2.17 (Flutter 3.0) +* Removed deprecated methods: + * `initWeb` + * `loadRewards` + * `redeemRewards` + * `getCreditHistory` + +###Refactors + +* General improvements in code +* Fix analyzer code style warnings + ## 5.1.1 * Updated Native `Android` SDK: * Android Native SDK Update 5.1.5 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) @@ -13,7 +31,7 @@ * iOS Native SDK Update 1.42.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) ## 5.0.0 -**BREAKING CHANGE**: +###BREAKING CHANGE: * `FlutterBranchSdk.initWeb` deprecated. * Branch for Flutter Web initialized in `index.html`, see `Web Integration` section @@ -87,7 +105,7 @@ * Added new method `getAdvertisingIdentifier`: Return Device Advertising Identifier ## 3.0.0 -* Initial support to Flutter Web . Thanks @mathatan +* Initial support to Flutter Web. Thanks @mathatan ## 2.0.0 * Stable null safety release. @@ -123,7 +141,8 @@ Updated Native ```Android``` and ```iOS``` SDKs * Android Native SDK Update 5.0.3 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) * iOS Native SDK Update 0.35.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) - __BREAKING CHANGES__ +###BREAKING CHANGES + Add KEY ```branch_check_apple_ads``` in INFO.PLIST to enable checking for Apple Search Ads before Branch initialization ## 1.0.0 diff --git a/LICENSE b/LICENSE index ab866628..97cfb48e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1 @@ -MIT License - -Copyright (c) 2019 Rodrigo de Souza Marques - -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. +TODO: Add your license here. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..8eed5e63 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options +linter: + rules: + constant_identifier_names: false \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore index c6cbe562..a2b1dea3 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,8 +1,9 @@ -*.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/android/build.gradle b/android/build.gradle index 07fc2ddf..63a0c1b8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,42 +1,43 @@ -group 'br.com.rsmarques.flutter_branch_sdk' -version '1.0' - -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.2.2' - } -} - -rootProject.allprojects { - repositories { - google() - mavenCentral() - } -} - -apply plugin: 'com.android.library' - -android { - compileSdkVersion 31 - - defaultConfig { - minSdkVersion 21 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } -} - -dependencies { - implementation 'io.branch.sdk.android:library:5.1.5' - implementation 'com.google.firebase:firebase-appindexing:19.0.0' - implementation 'com.google.android.gms:play-services-ads-identifier:17.1.0+' - implementation 'androidx.browser:browser:1.4.0' - implementation 'androidx.lifecycle:lifecycle-runtime:2.4.0' +group 'br.com.rsmarques.flutter_branch_sdk' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 31 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 21 + } +} + +dependencies { + implementation 'io.branch.sdk.android:library:5.2.+' + implementation 'com.google.firebase:firebase-appindexing:19.0.0' + implementation 'com.google.android.gms:play-services-ads-identifier:17.1.0+' + implementation 'androidx.browser:browser:1.4.0' + implementation 'androidx.lifecycle:lifecycle-runtime:2.4.1' } \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 38c8d454..00000000 --- a/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true -android.useAndroidX=true -android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 9fe8d05d..00000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 8b8035fd..989ef1a9 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1 @@ -rootProject.name = 'flutter_branch_sdk' +rootProject.name = 'flutter_branch_sdk' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 014c920c..e0c7b550 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - - - + + + diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/ApplicationInfoHelper.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/ApplicationInfoHelper.java index 82ed09b2..532cb9f8 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/ApplicationInfoHelper.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/ApplicationInfoHelper.java @@ -1,50 +1,50 @@ -package br.com.rsmarques.flutter_branch_sdk; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; - -import io.flutter.BuildConfig; - -public class ApplicationInfoHelper { - private static Context context; - - ApplicationInfoHelper(Context context) { - this.context = context; - } - - public static boolean getEnableLog() { - try { - final ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); - if (ai.metaData != null) { - if (BuildConfig.DEBUG) { - return ai.metaData.getBoolean("branch_enable_log", - true); - } else { - return ai.metaData.getBoolean("branch_enable_log", - false); - } - } else { - return BuildConfig.DEBUG; - } - } catch (Exception e) { - LogUtils.debug("FlutterBranchSDK", "ApplicationInfoHelper error: " + e.getLocalizedMessage()); - } - return false; - } - - public static boolean getEnableFacebookAds() { - try { - final ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); - if (ai.metaData != null) { - return ai.metaData.getBoolean("branch_enable_facebook_ads", - false); - } else { - return false; - } - } catch (Exception e) { - LogUtils.debug("FlutterBranchSDK", "ApplicationInfoHelper error: " + e.getLocalizedMessage()); - } - return false; - } -} +package br.com.rsmarques.flutter_branch_sdk; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import io.flutter.BuildConfig; + +public class ApplicationInfoHelper { + private static Context context; + + ApplicationInfoHelper(Context context) { + this.context = context; + } + + public static boolean getEnableLog() { + try { + final ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + if (ai.metaData != null) { + if (BuildConfig.DEBUG) { + return ai.metaData.getBoolean("branch_enable_log", + true); + } else { + return ai.metaData.getBoolean("branch_enable_log", + false); + } + } else { + return BuildConfig.DEBUG; + } + } catch (Exception e) { + LogUtils.debug("FlutterBranchSDK", "ApplicationInfoHelper error: " + e.getLocalizedMessage()); + } + return false; + } + + public static boolean getEnableFacebookAds() { + try { + final ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + if (ai.metaData != null) { + return ai.metaData.getBoolean("branch_enable_facebook_ads", + false); + } else { + return false; + } + } catch (Exception e) { + LogUtils.debug("FlutterBranchSDK", "ApplicationInfoHelper error: " + e.getLocalizedMessage()); + } + return false; + } +} diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkHelper.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkHelper.java index 08657d23..a3d8bab8 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkHelper.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkHelper.java @@ -1,253 +1,253 @@ -package br.com.rsmarques.flutter_branch_sdk; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import io.branch.indexing.BranchUniversalObject; -import io.branch.referral.util.AdType; -import io.branch.referral.util.BRANCH_STANDARD_EVENT; -import io.branch.referral.util.BranchContentSchema; -import io.branch.referral.util.BranchEvent; -import io.branch.referral.util.ContentMetadata; -import io.branch.referral.util.CurrencyType; -import io.branch.referral.util.LinkProperties; -import io.branch.referral.util.ProductCategory; - -public class FlutterBranchSdkHelper { - /**--------------------------------------------------------------------------------------------- - Object Conversion Functions - --------------------------------------------------------------------------------------------**/ - BranchUniversalObject convertToBUO(HashMap argsMap) { - - BranchUniversalObject buo = new BranchUniversalObject(); - String canonicalIdentifier = (String) argsMap.get("canonicalIdentifier"); - buo.setCanonicalIdentifier(canonicalIdentifier); - - if (argsMap.containsKey("canonicalUrl")) - buo.setCanonicalUrl((String) argsMap.get("canonicalUrl")); - if (argsMap.containsKey("title")) - buo.setTitle((String) argsMap.get("title")); - if (argsMap.containsKey("contentDescription")) - buo.setContentDescription((String) argsMap.get("contentDescription")); - if (argsMap.containsKey("imageUrl")) - buo.setContentImageUrl((String) argsMap.get("imageUrl")); - if (argsMap.containsKey("keywords")) - buo.addKeyWords((ArrayList) argsMap.get("keywords")); - if (argsMap.containsKey("expirationDate")) - buo.setContentExpiration(new Date((long) argsMap.get("expirationDate"))); - - if (argsMap.containsKey("locallyIndex")) { - boolean value = (boolean) argsMap.get("locallyIndex"); - if (value) { - buo.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); - } else - buo.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); - } - if (argsMap.containsKey("publiclyIndex")) { - boolean value = (boolean) argsMap.get("publiclyIndex"); - if (value) { - buo.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); - } else - buo.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); - } - if (argsMap.containsKey("contentMetadata")) { - HashMap contentMap = (HashMap) argsMap.get("contentMetadata"); - ContentMetadata contentMetadata = new ContentMetadata(); - if (contentMap.containsKey("quantity")) - contentMetadata.setQuantity((double) contentMap.get("quantity")); - if (contentMap.containsKey("price") && contentMap.containsKey("currency")) { - contentMetadata.setPrice((double) contentMap.get("price"), CurrencyType.getValue((String) contentMap.get("currency"))); - } - if (contentMap.containsKey("rating_average") || contentMap.containsKey("rating_count") || - contentMap.containsKey("rating_max") || contentMap.containsKey("rating")) { - Double rating = null; - if (contentMap.containsKey("rating")) { - rating = (double) contentMap.get("rating"); - } - Double rating_average = null; - if (contentMap.containsKey("rating_average")) { - rating_average = (double) contentMap.get("rating_average"); - } - Integer rating_count = null; - if (contentMap.containsKey("rating_count")) { - rating_count = (Integer) contentMap.get("rating_count"); - } - Double rating_max = null; - if (contentMap.containsKey("rating_max")) { - rating_max = (double) contentMap.get("rating_max"); - } - contentMetadata.setRating(rating, rating_average, rating_max, rating_count); - } - if (contentMap.containsKey("latitude") && contentMap.containsKey("longitude")) { - contentMetadata.setLocation((double) contentMap.get("latitude"), (double) contentMap.get("longitude")); - } - if (contentMap.containsKey("address_street") || contentMap.containsKey("address_city") || - contentMap.containsKey("address_region") || contentMap.containsKey("address_country") || contentMap.containsKey("address_postal_code")) { - String street = (String) contentMap.get("address_street"); - String city = (String) contentMap.get("address_city"); - String region = (String) contentMap.get("address_region"); - String country = (String) contentMap.get("address_country"); - String postal_code = (String) contentMap.get("address_postal_code"); - contentMetadata.setAddress(street, city, region, country, postal_code); - } - if (contentMap.containsKey("content_schema")) { - contentMetadata.setContentSchema(BranchContentSchema.getValue((String) contentMap.get("content_schema"))); - } - if (contentMap.containsKey("sku")) { - contentMetadata.setSku((String) contentMap.get("sku")); - } - if (contentMap.containsKey("product_name")) { - contentMetadata.setProductName((String) contentMap.get("product_name")); - } - if (contentMap.containsKey("product_brand")) { - contentMetadata.setProductBrand((String) contentMap.get("product_brand")); - } - if (contentMap.containsKey("product_category")) { - contentMetadata.setProductCategory(ProductCategory.getValue((String) contentMap.get("product_category"))); - } - if (contentMap.containsKey("product_variant")) { - contentMetadata.setProductVariant((String) contentMap.get("product_variant")); - } - if (contentMap.containsKey("condition")) { - contentMetadata.setProductCondition(ContentMetadata.CONDITION.getValue((String) contentMap.get("product_category"))); - } - if (contentMap.containsKey("image_captions")) { - ArrayList _imageCaptions = (ArrayList) contentMap.get("image_captions"); - for (int i = 0; i < _imageCaptions.size(); i++) { - contentMetadata.addImageCaptions(_imageCaptions.get(i)); - } - } - if (contentMap.containsKey("customMetadata")) { - for (Map.Entry customMetaData : ((HashMap) contentMap.get("customMetadata")).entrySet()) { - contentMetadata.addCustomMetadata(customMetaData.getKey(), customMetaData.getValue().toString()); - } - } - buo.setContentMetadata(contentMetadata); - } - return buo; - } - - LinkProperties convertToLinkProperties(HashMap argsMap) { - - LinkProperties linkProperties = new LinkProperties(); - - if (argsMap.containsKey("channel")) - linkProperties.setChannel((String) argsMap.get("channel")); - if (argsMap.containsKey("feature")) - linkProperties.setFeature((String) argsMap.get("feature")); - if (argsMap.containsKey("campaign")) - linkProperties.setCampaign((String) argsMap.get("campaign")); - if (argsMap.containsKey("stage")) - linkProperties.setStage((String) argsMap.get("stage")); - if (argsMap.containsKey("alias")) - linkProperties.setAlias((String) argsMap.get("alias")); - if (argsMap.containsKey("matchDuration")) - linkProperties.setDuration((int) argsMap.get("matchDuration")); - if (argsMap.containsKey("tags")) { - ArrayList _tags = (ArrayList) argsMap.get("tags"); - for (int i = 0; i < _tags.size(); i++) { - linkProperties.addTag(_tags.get(i)); - } - } - if (argsMap.containsKey("controlParams")) { - for (Map.Entry content : ((HashMap) argsMap.get("controlParams")).entrySet()) { - linkProperties.addControlParameter(content.getKey(), content.getValue()); - } - } - return linkProperties; - } - - BranchEvent convertToEvent(HashMap eventMap) { - BranchEvent event; - - if ((boolean) eventMap.get("isStandardEvent")) { - event = new BranchEvent(BRANCH_STANDARD_EVENT.valueOf((String) eventMap.get("eventName"))); - } else { - event = new BranchEvent((String) eventMap.get("eventName")); - } - - if (eventMap.containsKey("transactionID")) - event.setTransactionID((String) eventMap.get("transactionID")); - if (eventMap.containsKey("currency")) - event.setCurrency(CurrencyType.getValue((String) eventMap.get("currency"))); - if (eventMap.containsKey("revenue")) - event.setRevenue((Double) eventMap.get("revenue")); - if (eventMap.containsKey("shipping")) - event.setShipping((Double) eventMap.get("shipping")); - if (eventMap.containsKey("tax")) - event.setTax((Double) eventMap.get("tax")); - if (eventMap.containsKey("coupon")) - event.setCoupon((String) eventMap.get("coupon")); - if (eventMap.containsKey("affiliation")) - event.setAffiliation((String) eventMap.get("affiliation")); - if (eventMap.containsKey("eventDescription")) - event.setDescription((String) eventMap.get("eventDescription")); - if (eventMap.containsKey("searchQuery")) - event.setSearchQuery((String) eventMap.get("searchQuery")); - if (eventMap.containsKey("adType")) - event.setAdType(convertToAdType((String) eventMap.get("adType"))); - if (eventMap.containsKey("customData")) { - for (Map.Entry customData : ((HashMap) eventMap.get("customData")).entrySet()) { - event.addCustomDataProperty(customData.getKey(), customData.getValue()); - } - } - return event; - } - - AdType convertToAdType(String adType) { - switch (adType) { - case "BANNER": - return AdType.BANNER; - case "INTERSTITIAL": - return AdType.INTERSTITIAL; - case "REWARDED_VIDEO": - return AdType.REWARDED_VIDEO; - case "NATIVE": - return AdType.NATIVE; - default: - throw new IllegalStateException("Unexpected value: " + adType); - } - } - - //---------------------------------------------------------------------------------------------- - - Map paramsToMap(JSONObject jsonObject) throws JSONException { - Map map = new HashMap<>(); - Iterator keys = jsonObject.keys(); - while (keys.hasNext()) { - String key = keys.next(); - Object value = jsonObject.get(key); - if (value instanceof JSONArray) { - value = jsonArrayToList((JSONArray) value); - } else if (value instanceof JSONObject) { - value = paramsToMap((JSONObject) value); - } - map.put(key, value); - } - return map; - } - - List jsonArrayToList(JSONArray array) throws JSONException { - List list = new ArrayList<>(); - for (int i = 0; i < array.length(); i++) { - Object value = array.get(i); - if (value instanceof JSONArray) { - value = jsonArrayToList((JSONArray) value); - } else if (value instanceof JSONObject) { - value = paramsToMap((JSONObject) value); - } - list.add(value); - } - return list; - } -} +package br.com.rsmarques.flutter_branch_sdk; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import io.branch.indexing.BranchUniversalObject; +import io.branch.referral.util.AdType; +import io.branch.referral.util.BRANCH_STANDARD_EVENT; +import io.branch.referral.util.BranchContentSchema; +import io.branch.referral.util.BranchEvent; +import io.branch.referral.util.ContentMetadata; +import io.branch.referral.util.CurrencyType; +import io.branch.referral.util.LinkProperties; +import io.branch.referral.util.ProductCategory; + +public class FlutterBranchSdkHelper { + /**--------------------------------------------------------------------------------------------- + Object Conversion Functions + --------------------------------------------------------------------------------------------**/ + BranchUniversalObject convertToBUO(HashMap argsMap) { + + BranchUniversalObject buo = new BranchUniversalObject(); + String canonicalIdentifier = (String) argsMap.get("canonicalIdentifier"); + buo.setCanonicalIdentifier(canonicalIdentifier); + + if (argsMap.containsKey("canonicalUrl")) + buo.setCanonicalUrl((String) argsMap.get("canonicalUrl")); + if (argsMap.containsKey("title")) + buo.setTitle((String) argsMap.get("title")); + if (argsMap.containsKey("contentDescription")) + buo.setContentDescription((String) argsMap.get("contentDescription")); + if (argsMap.containsKey("imageUrl")) + buo.setContentImageUrl((String) argsMap.get("imageUrl")); + if (argsMap.containsKey("keywords")) + buo.addKeyWords((ArrayList) argsMap.get("keywords")); + if (argsMap.containsKey("expirationDate")) + buo.setContentExpiration(new Date((long) argsMap.get("expirationDate"))); + + if (argsMap.containsKey("locallyIndex")) { + boolean value = (boolean) argsMap.get("locallyIndex"); + if (value) { + buo.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); + } else + buo.setLocalIndexMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); + } + if (argsMap.containsKey("publiclyIndex")) { + boolean value = (boolean) argsMap.get("publiclyIndex"); + if (value) { + buo.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); + } else + buo.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PRIVATE); + } + if (argsMap.containsKey("contentMetadata")) { + HashMap contentMap = (HashMap) argsMap.get("contentMetadata"); + ContentMetadata contentMetadata = new ContentMetadata(); + if (contentMap.containsKey("quantity")) + contentMetadata.setQuantity((double) contentMap.get("quantity")); + if (contentMap.containsKey("price") && contentMap.containsKey("currency")) { + contentMetadata.setPrice((double) contentMap.get("price"), CurrencyType.getValue((String) contentMap.get("currency"))); + } + if (contentMap.containsKey("rating_average") || contentMap.containsKey("rating_count") || + contentMap.containsKey("rating_max") || contentMap.containsKey("rating")) { + Double rating = null; + if (contentMap.containsKey("rating")) { + rating = (double) contentMap.get("rating"); + } + Double rating_average = null; + if (contentMap.containsKey("rating_average")) { + rating_average = (double) contentMap.get("rating_average"); + } + Integer rating_count = null; + if (contentMap.containsKey("rating_count")) { + rating_count = (Integer) contentMap.get("rating_count"); + } + Double rating_max = null; + if (contentMap.containsKey("rating_max")) { + rating_max = (double) contentMap.get("rating_max"); + } + contentMetadata.setRating(rating, rating_average, rating_max, rating_count); + } + if (contentMap.containsKey("latitude") && contentMap.containsKey("longitude")) { + contentMetadata.setLocation((double) contentMap.get("latitude"), (double) contentMap.get("longitude")); + } + if (contentMap.containsKey("address_street") || contentMap.containsKey("address_city") || + contentMap.containsKey("address_region") || contentMap.containsKey("address_country") || contentMap.containsKey("address_postal_code")) { + String street = (String) contentMap.get("address_street"); + String city = (String) contentMap.get("address_city"); + String region = (String) contentMap.get("address_region"); + String country = (String) contentMap.get("address_country"); + String postal_code = (String) contentMap.get("address_postal_code"); + contentMetadata.setAddress(street, city, region, country, postal_code); + } + if (contentMap.containsKey("content_schema")) { + contentMetadata.setContentSchema(BranchContentSchema.getValue((String) contentMap.get("content_schema"))); + } + if (contentMap.containsKey("sku")) { + contentMetadata.setSku((String) contentMap.get("sku")); + } + if (contentMap.containsKey("product_name")) { + contentMetadata.setProductName((String) contentMap.get("product_name")); + } + if (contentMap.containsKey("product_brand")) { + contentMetadata.setProductBrand((String) contentMap.get("product_brand")); + } + if (contentMap.containsKey("product_category")) { + contentMetadata.setProductCategory(ProductCategory.getValue((String) contentMap.get("product_category"))); + } + if (contentMap.containsKey("product_variant")) { + contentMetadata.setProductVariant((String) contentMap.get("product_variant")); + } + if (contentMap.containsKey("condition")) { + contentMetadata.setProductCondition(ContentMetadata.CONDITION.getValue((String) contentMap.get("product_category"))); + } + if (contentMap.containsKey("image_captions")) { + ArrayList _imageCaptions = (ArrayList) contentMap.get("image_captions"); + for (int i = 0; i < _imageCaptions.size(); i++) { + contentMetadata.addImageCaptions(_imageCaptions.get(i)); + } + } + if (contentMap.containsKey("customMetadata")) { + for (Map.Entry customMetaData : ((HashMap) contentMap.get("customMetadata")).entrySet()) { + contentMetadata.addCustomMetadata(customMetaData.getKey(), customMetaData.getValue().toString()); + } + } + buo.setContentMetadata(contentMetadata); + } + return buo; + } + + LinkProperties convertToLinkProperties(HashMap argsMap) { + + LinkProperties linkProperties = new LinkProperties(); + + if (argsMap.containsKey("channel")) + linkProperties.setChannel((String) argsMap.get("channel")); + if (argsMap.containsKey("feature")) + linkProperties.setFeature((String) argsMap.get("feature")); + if (argsMap.containsKey("campaign")) + linkProperties.setCampaign((String) argsMap.get("campaign")); + if (argsMap.containsKey("stage")) + linkProperties.setStage((String) argsMap.get("stage")); + if (argsMap.containsKey("alias")) + linkProperties.setAlias((String) argsMap.get("alias")); + if (argsMap.containsKey("matchDuration")) + linkProperties.setDuration((int) argsMap.get("matchDuration")); + if (argsMap.containsKey("tags")) { + ArrayList _tags = (ArrayList) argsMap.get("tags"); + for (int i = 0; i < _tags.size(); i++) { + linkProperties.addTag(_tags.get(i)); + } + } + if (argsMap.containsKey("controlParams")) { + for (Map.Entry content : ((HashMap) argsMap.get("controlParams")).entrySet()) { + linkProperties.addControlParameter(content.getKey(), content.getValue()); + } + } + return linkProperties; + } + + BranchEvent convertToEvent(HashMap eventMap) { + BranchEvent event; + + if ((boolean) eventMap.get("isStandardEvent")) { + event = new BranchEvent(BRANCH_STANDARD_EVENT.valueOf((String) eventMap.get("eventName"))); + } else { + event = new BranchEvent((String) eventMap.get("eventName")); + } + + if (eventMap.containsKey("transactionID")) + event.setTransactionID((String) eventMap.get("transactionID")); + if (eventMap.containsKey("currency")) + event.setCurrency(CurrencyType.getValue((String) eventMap.get("currency"))); + if (eventMap.containsKey("revenue")) + event.setRevenue((Double) eventMap.get("revenue")); + if (eventMap.containsKey("shipping")) + event.setShipping((Double) eventMap.get("shipping")); + if (eventMap.containsKey("tax")) + event.setTax((Double) eventMap.get("tax")); + if (eventMap.containsKey("coupon")) + event.setCoupon((String) eventMap.get("coupon")); + if (eventMap.containsKey("affiliation")) + event.setAffiliation((String) eventMap.get("affiliation")); + if (eventMap.containsKey("eventDescription")) + event.setDescription((String) eventMap.get("eventDescription")); + if (eventMap.containsKey("searchQuery")) + event.setSearchQuery((String) eventMap.get("searchQuery")); + if (eventMap.containsKey("adType")) + event.setAdType(convertToAdType((String) eventMap.get("adType"))); + if (eventMap.containsKey("customData")) { + for (Map.Entry customData : ((HashMap) eventMap.get("customData")).entrySet()) { + event.addCustomDataProperty(customData.getKey(), customData.getValue()); + } + } + return event; + } + + AdType convertToAdType(String adType) { + switch (adType) { + case "BANNER": + return AdType.BANNER; + case "INTERSTITIAL": + return AdType.INTERSTITIAL; + case "REWARDED_VIDEO": + return AdType.REWARDED_VIDEO; + case "NATIVE": + return AdType.NATIVE; + default: + throw new IllegalStateException("Unexpected value: " + adType); + } + } + + //---------------------------------------------------------------------------------------------- + + Map paramsToMap(JSONObject jsonObject) throws JSONException { + Map map = new HashMap<>(); + Iterator keys = jsonObject.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = jsonObject.get(key); + if (value instanceof JSONArray) { + value = jsonArrayToList((JSONArray) value); + } else if (value instanceof JSONObject) { + value = paramsToMap((JSONObject) value); + } + map.put(key, value); + } + return map; + } + + List jsonArrayToList(JSONArray array) throws JSONException { + List list = new ArrayList<>(); + for (int i = 0; i < array.length(); i++) { + Object value = array.get(i); + if (value instanceof JSONArray) { + value = jsonArrayToList((JSONArray) value); + } else if (value instanceof JSONObject) { + value = paramsToMap((JSONObject) value); + } + list.add(value); + } + return list; + } +} diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkInit.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkInit.java index 1160501e..009cb7fc 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkInit.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkInit.java @@ -1,31 +1,31 @@ -package br.com.rsmarques.flutter_branch_sdk; - -import android.content.Context; -import android.util.Log; - -import io.branch.referral.Branch; - -public class FlutterBranchSdkInit { - private static final String DEBUG_NAME = "FlutterBranchSDK"; - private static final String PLUGIN_NAME = "Flutter"; - private static final String PLUGIN_VERSION = "5.1.1"; - - public static void init(Context context) { - ApplicationInfoHelper applicationInfoHelper = new ApplicationInfoHelper(context); - - if (applicationInfoHelper.getEnableLog()) { - LogUtils.debug(DEBUG_NAME, "Branch SDK with log enable"); - Branch.enableLogging(); - } else { - Log.i(DEBUG_NAME, "Branch SDK with out log"); - } - - if (applicationInfoHelper.getEnableFacebookAds()) { - Branch.getAutoInstance(context).enableFacebookAppLinkCheck(); - } - - // Branch object initialization - Branch.registerPlugin(PLUGIN_NAME, PLUGIN_VERSION); - Branch.getAutoInstance(context); - } -} +package br.com.rsmarques.flutter_branch_sdk; + +import android.content.Context; +import android.util.Log; + +import io.branch.referral.Branch; + +public class FlutterBranchSdkInit { + private static final String DEBUG_NAME = "FlutterBranchSDK"; + private static final String PLUGIN_NAME = "Flutter"; + private static final String PLUGIN_VERSION = "5.1.1"; + + public static void init(Context context) { + ApplicationInfoHelper applicationInfoHelper = new ApplicationInfoHelper(context); + + if (applicationInfoHelper.getEnableLog()) { + LogUtils.debug(DEBUG_NAME, "Branch SDK with log enable"); + Branch.enableLogging(); + } else { + Log.i(DEBUG_NAME, "Branch SDK with out log"); + } + + if (applicationInfoHelper.getEnableFacebookAds()) { + Branch.getAutoInstance(context).enableFacebookAppLinkCheck(); + } + + // Branch object initialization + Branch.registerPlugin(PLUGIN_NAME, PLUGIN_VERSION); + Branch.getAutoInstance(context); + } +} diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java index 1165d1f5..1ae62679 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java @@ -1,956 +1,826 @@ -package br.com.rsmarques.flutter_branch_sdk; - -import android.app.Activity; -import android.app.Application; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import io.branch.indexing.BranchUniversalObject; -import io.branch.referral.Branch; -import io.branch.referral.BranchError; -import io.branch.referral.ServerRequestGetLATD; -import io.branch.referral.util.BranchEvent; -import io.branch.referral.util.LinkProperties; -import io.branch.referral.util.ShareSheetStyle; -import io.branch.referral.validators.IntegrationValidator; -import io.flutter.embedding.android.FlutterFragmentActivity; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.EventChannel.StreamHandler; -import io.flutter.plugin.common.EventChannel.EventSink; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.NewIntentListener; - -public class FlutterBranchSdkPlugin implements FlutterPlugin, MethodCallHandler, StreamHandler, NewIntentListener, ActivityAware, - Application.ActivityLifecycleCallbacks { - private static final String DEBUG_NAME = "FlutterBranchSDK"; - private Activity activity; - private Context context; - private ActivityPluginBinding activityPluginBinding; - - private static final String MESSAGE_CHANNEL = "flutter_branch_sdk/message"; - private static final String EVENT_CHANNEL = "flutter_branch_sdk/event"; - private EventSink eventSink = null; - private Map initialParams = null; - private BranchError initialError = null; - - private final FlutterBranchSdkHelper branchSdkHelper = new FlutterBranchSdkHelper(); - - /** - * --------------------------------------------------------------------------------------------- - * Plugin registry - * -------------------------------------------------------------------------------------------- - **/ - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - LogUtils.debug(DEBUG_NAME, "onAttachedToEngine call"); - setupChannels(binding.getBinaryMessenger(), binding.getApplicationContext()); - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - LogUtils.debug(DEBUG_NAME, "onDetachedFromEngine call"); - teardownChannels(); - } - - private void setupChannels(BinaryMessenger messenger, Context context) { - LogUtils.debug(DEBUG_NAME, "setupChannels call"); - this.context = context; - - MethodChannel methodChannel = new MethodChannel(messenger, MESSAGE_CHANNEL); - EventChannel eventChannel = new EventChannel(messenger, EVENT_CHANNEL); - - methodChannel.setMethodCallHandler(this); - eventChannel.setStreamHandler(this); - - FlutterBranchSdkInit.init(context); - } - - private void setActivity(Activity activity) { - LogUtils.debug(DEBUG_NAME, "setActivity call"); - this.activity = activity; - activity.getApplication().registerActivityLifecycleCallbacks(this); - - if (this.activity != null && FlutterFragmentActivity.class.isAssignableFrom(activity.getClass())) { - Branch.sessionBuilder(activity).withCallback(branchReferralInitListener).withData(activity.getIntent() != null ? activity.getIntent().getData() : null).init(); - } - } - - private void teardownChannels() { - LogUtils.debug(DEBUG_NAME, "teardownChannels call"); - this.activityPluginBinding = null; - this.activity = null; - this.context = null; - } - - /** - * --------------------------------------------------------------------------------------------- - * ActivityAware Interface Methods - * -------------------------------------------------------------------------------------------- - **/ - @Override - public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) { - LogUtils.debug(DEBUG_NAME, "onAttachedToActivity call"); - this.activityPluginBinding = activityPluginBinding; - setActivity(activityPluginBinding.getActivity()); - activityPluginBinding.addOnNewIntentListener(this); - } - - @Override - public void onDetachedFromActivity() { - LogUtils.debug(DEBUG_NAME, "onDetachedFromActivity call"); - activityPluginBinding.removeOnNewIntentListener(this); - this.activity = null; - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - LogUtils.debug(DEBUG_NAME, "onDetachedFromActivityForConfigChanges call"); - onDetachedFromActivity(); - } - - @Override - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding activityPluginBinding) { - LogUtils.debug(DEBUG_NAME, "onReattachedToActivityForConfigChanges call"); - onAttachedToActivity(activityPluginBinding); - } - - /** - * --------------------------------------------------------------------------------------------- - * StreamHandler Interface Methods - * -------------------------------------------------------------------------------------------- - **/ - @Override - public void onListen(Object o, EventChannel.EventSink eventSink) { - LogUtils.debug(DEBUG_NAME, "onListen call"); - this.eventSink = new MainThreadEventSink(eventSink); - if (initialParams != null) { - eventSink.success(initialParams); - initialParams = null; - initialError = null; - } else if (initialError != null) { - eventSink.error(String.valueOf(initialError.getErrorCode()), initialError.getMessage(), null); - initialParams = null; - initialError = null; - } - } - - @Override - public void onCancel(Object o) { - LogUtils.debug(DEBUG_NAME, "onCancel call"); - this.eventSink = new MainThreadEventSink(null); - initialError = null; - initialParams = null; - } - - /** - * --------------------------------------------------------------------------------------------- - * ActivityLifecycleCallbacks Interface Methods - * -------------------------------------------------------------------------------------------- - **/ - @Override - public void onActivityCreated(Activity activity, Bundle bundle) { - } - - @Override - public void onActivityStarted(Activity activity) { - LogUtils.debug(DEBUG_NAME, "onActivityStarted call"); - Branch.sessionBuilder(activity).withCallback(branchReferralInitListener).withData(activity.getIntent() != null ? activity.getIntent().getData() : null).init(); - } - - @Override - public void onActivityResumed(Activity activity) { - } - - @Override - public void onActivityPaused(Activity activity) { - } - - @Override - public void onActivityStopped(Activity activity) { - LogUtils.debug(DEBUG_NAME, "onActivityStopped call"); - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { - } - - @Override - public void onActivityDestroyed(Activity activity) { - LogUtils.debug(DEBUG_NAME, "onActivityDestroyed call"); - if (this.activity == activity) { - activity.getApplication().unregisterActivityLifecycleCallbacks(this); - } - } - - /** - * --------------------------------------------------------------------------------------------- - * NewIntentListener Interface Methods - * -------------------------------------------------------------------------------------------- - **/ - @Override - public boolean onNewIntent(Intent intent) { - LogUtils.debug(DEBUG_NAME, "onNewIntent call"); - if (this.activity != null) { - this.activity.setIntent(intent); - - if (intent != null && - intent.hasExtra("branch_force_new_session") && - intent.getBooleanExtra("branch_force_new_session", false)) { - Branch.sessionBuilder(this.activity).withCallback(branchReferralInitListener).reInit(); - } - return true; - } - return false; - } - - /** - * --------------------------------------------------------------------------------------------- - * MethodCallHandler Interface Methods - * -------------------------------------------------------------------------------------------- - **/ - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull Result rawResult) { - Result result = new MethodResultWrapper(rawResult); - switch (call.method) { - case "getShortUrl": - getShortUrl(call, result); - break; - case "showShareSheet": - showShareSheet(call, result); - break; - case "registerView": - registerView(call); - break; - case "listOnSearch": - listOnSearch(call, result); - break; - case "removeFromSearch": - removeFromSearch(call, result); - break; - case "trackContent": - trackContent(call); - break; - case "trackContentWithoutBuo": - trackContentWithoutBuo(call); - break; - case "setIdentity": - setIdentity(call); - break; - case "setRequestMetadata": - setRequestMetadata(call); - break; - case "logout": - logout(); - break; - case "getLatestReferringParams": - getLatestReferringParams(result); - break; - case "getFirstReferringParams": - getFirstReferringParams(result); - break; - case "setTrackingDisabled": - setTrackingDisabled(call); - break; - case "validateSDKIntegration": - validateSDKIntegration(); - break; - case "loadRewards": - loadRewards(call, result); - break; - case "redeemRewards": - redeemRewards(call, result); - break; - case "getCreditHistory": - getCreditHistory(call, result); - break; - case "isUserIdentified": - isUserIdentified(result); - break; - case "setConnectTimeout": - setConnectTimeout(call); - break; - case "setTimeout": - setTimeout(call); - break; - case "setRetryCount": - setRetryCount(call); - break; - case "setRetryInterval": - setRetryInterval(call); - break; - case "getLastAttributedTouchData": - getLastAttributedTouchData(call, result); - break; - default: - result.notImplemented(); - break; - } - } - - /** - * --------------------------------------------------------------------------------------------- - * Branch SDK Call Methods - * -------------------------------------------------------------------------------------------- - **/ - private final Branch.BranchReferralInitListener branchReferralInitListener = new - Branch.BranchReferralInitListener() { - @Override - public void onInitFinished(JSONObject params, BranchError error) { - if (error == null) { - LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - params: " + params.toString()); - try { - initialParams = branchSdkHelper.paramsToMap(params); - } catch (JSONException e) { - LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - error to Map: " + e.getLocalizedMessage()); - return; - } - if (eventSink != null) { - eventSink.success(initialParams); - initialParams = null; - } - } else { - if (error.getErrorCode() == BranchError.ERR_BRANCH_ALREADY_INITIALIZED || error.getErrorCode() == BranchError.ERR_IMPROPER_REINITIALIZATION) { - return; - } - LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - error: " + error); - if (eventSink != null) { - eventSink.error(String.valueOf(error.getErrorCode()), error.getMessage(), null); - initialError = null; - } else { - initialError = error; - } - } - } - }; - - private void validateSDKIntegration() { - IntegrationValidator.validate(activity); - } - - private void getShortUrl(MethodCall call, final Result result) { - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - - HashMap argsMap = (HashMap) call.arguments; - BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); - - LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); - - final Map response = new HashMap<>(); - - buo.generateShortUrl(activity, linkProperties, new Branch.BranchLinkCreateListener() { - @Override - public void onLinkCreate(String url, BranchError error) { - - if (error == null) { - LogUtils.debug(DEBUG_NAME, "Branch link to share: " + url); - response.put("success", true); - response.put("url", url); - } else { - response.put("success", false); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - }); - } - - private void showShareSheet(MethodCall call, final Result result) { - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - HashMap argsMap = (HashMap) call.arguments; - BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); - - LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); - String messageText = (String) argsMap.get("messageText"); - String messageTitle = (String) argsMap.get("messageTitle"); - String sharingTitle = (String) argsMap.get("sharingTitle"); - - final Map response = new HashMap<>(); - - ShareSheetStyle shareSheetStyle = new ShareSheetStyle(activity, messageTitle, messageText) - .setAsFullWidthStyle(true) - .setSharingTitle(sharingTitle); - - buo.showShareSheet(activity, - linkProperties, - shareSheetStyle, - new Branch.ExtendedBranchLinkShareListener() { - @Override - public void onShareLinkDialogLaunched() { - } - - @Override - public void onShareLinkDialogDismissed() { - } - - @Override - public void onLinkShareResponse(String sharedLink, String sharedChannel, BranchError error) { - if (error == null) { - LogUtils.debug(DEBUG_NAME, "Branch link share: " + sharedLink); - response.put("success", Boolean.TRUE); - response.put("url", sharedLink); - } else { - response.put("success", Boolean.FALSE); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - - @Override - public void onChannelSelected(String channelName) { - - } - - @Override - public boolean onChannelSelected(String channelName, BranchUniversalObject buo, LinkProperties linkProperties) { - return false; - } - }); - } - - private void registerView(MethodCall call) { - LogUtils.debug(DEBUG_NAME, "registerView call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - HashMap argsMap = (HashMap) call.arguments; - final BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - buo.registerView(); - } - }); - } - - private void listOnSearch(MethodCall call, Result result) { - LogUtils.debug(DEBUG_NAME, "listOnSearch call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - HashMap argsMap = (HashMap) call.arguments; - BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); - if (argsMap.containsKey("lp")) { - LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); - buo.listOnGoogleSearch(context, linkProperties); - } else { - buo.listOnGoogleSearch(context); - } - result.success(Boolean.TRUE); - } - - private void removeFromSearch(MethodCall call, Result result) { - LogUtils.debug(DEBUG_NAME, "removeFromSearch call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - HashMap argsMap = (HashMap) call.arguments; - BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); - if (argsMap.containsKey("lp")) { - LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); - buo.removeFromLocalIndexing(context, linkProperties); - } else { - buo.removeFromLocalIndexing(context); - } - result.success(Boolean.TRUE); - } - - private void trackContent(MethodCall call) { - LogUtils.debug(DEBUG_NAME, "trackContent call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - HashMap argsMap = (HashMap) call.arguments; - - final List buo = new ArrayList(); - for (HashMap b : (List>) argsMap.get("buo")) { - buo.add(branchSdkHelper.convertToBUO(b)); - } - final BranchEvent event = branchSdkHelper.convertToEvent((HashMap) argsMap.get("event")); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - event.addContentItems(buo).logEvent(context); - } - }); - } - - private void trackContentWithoutBuo(MethodCall call) { - LogUtils.debug(DEBUG_NAME, "trackContentWithoutBuo call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - HashMap argsMap = (HashMap) call.arguments; - final BranchEvent event = branchSdkHelper.convertToEvent((HashMap) argsMap.get("event")); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - event.logEvent(context); - } - }); - } - - private void setIdentity(MethodCall call) { - LogUtils.debug(DEBUG_NAME, "setIdentity call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - final String userId = call.argument("userId"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).setIdentity(userId); - } - }); - } - - private void setRequestMetadata(MethodCall call) { - LogUtils.debug(DEBUG_NAME, "setRequestMetadata call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - final String key = call.argument("key"); - final String value = call.argument("value"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).setRequestMetadata(key, value); - } - }); - } - - private void logout() { - LogUtils.debug(DEBUG_NAME, "logout call"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).logout(); - } - }); - } - - private void getLatestReferringParams(Result result) { - LogUtils.debug(DEBUG_NAME, "getLatestReferringParams call"); - JSONObject sessionParams = Branch.getAutoInstance(context).getLatestReferringParams(); - try { - result.success(branchSdkHelper.paramsToMap(sessionParams)); - } catch (JSONException e) { - e.printStackTrace(); - result.error(DEBUG_NAME, e.getMessage(), null); - } - } - - private void getFirstReferringParams(Result result) { - LogUtils.debug(DEBUG_NAME, "getFirstReferringParams call"); - JSONObject sessionParams = Branch.getAutoInstance(context).getFirstReferringParams(); - try { - result.success(branchSdkHelper.paramsToMap(sessionParams)); - } catch (JSONException e) { - e.printStackTrace(); - result.error(DEBUG_NAME, e.getMessage(), null); - } - } - - private void setTrackingDisabled(MethodCall call) { - LogUtils.debug(DEBUG_NAME, "setTrackingDisabled call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - final boolean value = call.argument("disable"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).disableTracking(value); - } - }); - } - - private void loadRewards(final MethodCall call, final Result result) { - LogUtils.debug(DEBUG_NAME, "loadRewards call"); - final Map response = new HashMap<>(); - Branch.getAutoInstance(context).loadRewards(new Branch.BranchReferralStateChangedListener() { - @Override - public void onStateChanged(boolean changed, @Nullable BranchError error) { - int credits; - if (error == null) { - if (!call.hasArgument("bucket")) { - credits = Branch.getAutoInstance(context).getCredits(); - } else { - credits = Branch.getAutoInstance(context).getCreditsForBucket(call.argument("bucket").toString()); - } - response.put("success", Boolean.TRUE); - response.put("credits", credits); - } else { - response.put("success", Boolean.FALSE); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - }); - } - - private void redeemRewards(final MethodCall call, final Result result) { - LogUtils.debug(DEBUG_NAME, "redeemRewards call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - - final int count = call.argument("count"); - final Map response = new HashMap<>(); - - if (!call.hasArgument("bucket")) { - Branch.getAutoInstance(context).redeemRewards(count, new Branch.BranchReferralStateChangedListener() { - @Override - public void onStateChanged(boolean changed, @Nullable BranchError error) { - if (error == null) { - response.put("success", Boolean.TRUE); - } else { - response.put("success", Boolean.FALSE); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - }); - } else { - Branch.getAutoInstance(context).redeemRewards(call.argument("bucket").toString(), count, new Branch.BranchReferralStateChangedListener() { - @Override - public void onStateChanged(boolean changed, @Nullable BranchError error) { - if (error == null) { - response.put("success", Boolean.TRUE); - } else { - response.put("success", Boolean.FALSE); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - }); - } - } - - private void getCreditHistory(final MethodCall call, final Result result) { - LogUtils.debug(DEBUG_NAME, "getCreditHistory call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - final Map response = new HashMap<>(); - - if (!call.hasArgument("bucket")) { - Branch.getAutoInstance(context).getCreditHistory(new Branch.BranchListResponseListener() { - @Override - public void onReceivingResponse(JSONArray list, BranchError error) { - if (error == null) { - response.put("success", Boolean.TRUE); - JSONObject jo = new JSONObject(); - try { - jo.put("history", list); - response.put("data", branchSdkHelper.paramsToMap(jo)); - } catch (JSONException e) { - e.printStackTrace(); - } - } else { - response.put("success", Boolean.FALSE); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - }); - } else { - Branch.getAutoInstance(context).getCreditHistory(call.argument("bucket").toString(), new Branch.BranchListResponseListener() { - @Override - public void onReceivingResponse(JSONArray list, BranchError error) { - if (error == null) { - response.put("success", Boolean.TRUE); - JSONObject jo = new JSONObject(); - try { - jo.put("history", list); - response.put("data", branchSdkHelper.paramsToMap(jo)); - } catch (JSONException e) { - e.printStackTrace(); - } - - } else { - response.put("success", Boolean.FALSE); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - }); - } - } - - private void isUserIdentified(Result result) { - LogUtils.debug(DEBUG_NAME, "isUserIdentified call"); - result.success(Branch.getAutoInstance(context).isUserIdentified()); - } - - private void setConnectTimeout(final MethodCall call) { - LogUtils.debug(DEBUG_NAME, "setConnectTimeout call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - final int value = call.argument("connectTimeout"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).setNetworkConnectTimeout(value); - } - }); - } - - private void setTimeout(final MethodCall call) { - LogUtils.debug(DEBUG_NAME, "setConnectTimeout call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - final int value = call.argument("timeout"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).setNetworkTimeout(value); - } - }); - } - - private void setRetryCount(final MethodCall call) { - LogUtils.debug(DEBUG_NAME, "setRetryCount call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - final int value = call.argument("retryCount"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).setRetryCount(value); - } - }); - } - - private void setRetryInterval(final MethodCall call) { - LogUtils.debug(DEBUG_NAME, "setRetryInterval call"); - if (!(call.arguments instanceof Map)) { - throw new IllegalArgumentException("Map argument expected"); - } - final int value = call.argument("retryInterval"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).setRetryInterval(value); - } - }); - } - - private void getLastAttributedTouchData(final MethodCall call, final Result result) { - LogUtils.debug(DEBUG_NAME, "getLastAttributedTouchData call"); - - final Map response = new HashMap<>(); - - if (call.hasArgument("attributionWindow")) { - final int attributionWindow = call.argument("attributionWindow"); - Branch.getAutoInstance(context).getLastAttributedTouchData( - new ServerRequestGetLATD.BranchLastAttributedTouchDataListener() { - @Override - public void onDataFetched(JSONObject jsonObject, BranchError error) { - if (error == null) { - response.put("success", Boolean.TRUE); - JSONObject jo = new JSONObject(); - try { - jo.put("latd", jsonObject); - response.put("data", branchSdkHelper.paramsToMap(jo)); - } catch (JSONException e) { - e.printStackTrace(); - } - } else { - response.put("success", Boolean.FALSE); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - }, attributionWindow); - - } else { - Branch.getAutoInstance(context).getLastAttributedTouchData( - new ServerRequestGetLATD.BranchLastAttributedTouchDataListener() { - @Override - public void onDataFetched(JSONObject jsonObject, BranchError error) { - if (error == null) { - response.put("success", Boolean.TRUE); - JSONObject jo = new JSONObject(); - try { - jo.put("latd", jsonObject); - response.put("data", branchSdkHelper.paramsToMap(jo)); - } catch (JSONException e) { - e.printStackTrace(); - } - } else { - response.put("success", Boolean.FALSE); - response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorMessage", error.getMessage()); - } - result.success(response); - } - }); - } - } - - // MethodChannel.Result wrapper that responds on the platform thread. - private static class MethodResultWrapper implements Result { - private final Result methodResult; - private final Handler handler; - - MethodResultWrapper(Result result) { - methodResult = result; - handler = new Handler(Looper.getMainLooper()); - } - - @Override - public void success(final Object result) { - handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.success(result); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - @Override - public void error( - final String errorCode, final String errorMessage, final Object errorDetails) { - handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.error(errorCode, errorMessage, errorDetails); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - @Override - public void notImplemented() { - handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.notImplemented(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - } - - private static class MainThreadEventSink implements EventChannel.EventSink { - private final EventChannel.EventSink eventSink; - private final Handler handler; - - MainThreadEventSink(EventChannel.EventSink eventSink) { - this.eventSink = eventSink; - handler = new Handler(Looper.getMainLooper()); - } - - @Override - public void success(final Object o) { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (eventSink != null) { - eventSink.success(o); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - @Override - public void error(final String s, final String s1, final Object o) { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (eventSink != null) { - eventSink.error(s, s1, o); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - @Override - public void endOfStream() { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (eventSink != null) { - eventSink.endOfStream(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - } -} - - +package br.com.rsmarques.flutter_branch_sdk; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.branch.indexing.BranchUniversalObject; +import io.branch.referral.Branch; +import io.branch.referral.BranchError; +import io.branch.referral.ServerRequestGetLATD; +import io.branch.referral.util.BranchEvent; +import io.branch.referral.util.LinkProperties; +import io.branch.referral.util.ShareSheetStyle; +import io.branch.referral.validators.IntegrationValidator; +import io.flutter.embedding.android.FlutterFragmentActivity; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.EventChannel.StreamHandler; +import io.flutter.plugin.common.EventChannel.EventSink; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry.NewIntentListener; + +public class FlutterBranchSdkPlugin implements FlutterPlugin, MethodCallHandler, StreamHandler, NewIntentListener, ActivityAware, + Application.ActivityLifecycleCallbacks { + private static final String DEBUG_NAME = "FlutterBranchSDK"; + private Activity activity; + private Context context; + private ActivityPluginBinding activityPluginBinding; + + private static final String MESSAGE_CHANNEL = "flutter_branch_sdk/message"; + private static final String EVENT_CHANNEL = "flutter_branch_sdk/event"; + private EventSink eventSink = null; + private Map initialParams = null; + private BranchError initialError = null; + + private final FlutterBranchSdkHelper branchSdkHelper = new FlutterBranchSdkHelper(); + + /** + * --------------------------------------------------------------------------------------------- + * Plugin registry + * -------------------------------------------------------------------------------------------- + **/ + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + LogUtils.debug(DEBUG_NAME, "onAttachedToEngine call"); + setupChannels(binding.getBinaryMessenger(), binding.getApplicationContext()); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + LogUtils.debug(DEBUG_NAME, "onDetachedFromEngine call"); + teardownChannels(); + } + + private void setupChannels(BinaryMessenger messenger, Context context) { + LogUtils.debug(DEBUG_NAME, "setupChannels call"); + this.context = context; + + MethodChannel methodChannel = new MethodChannel(messenger, MESSAGE_CHANNEL); + EventChannel eventChannel = new EventChannel(messenger, EVENT_CHANNEL); + + methodChannel.setMethodCallHandler(this); + eventChannel.setStreamHandler(this); + + FlutterBranchSdkInit.init(context); + } + + private void setActivity(Activity activity) { + LogUtils.debug(DEBUG_NAME, "setActivity call"); + this.activity = activity; + activity.getApplication().registerActivityLifecycleCallbacks(this); + + if (this.activity != null && FlutterFragmentActivity.class.isAssignableFrom(activity.getClass())) { + Branch.sessionBuilder(activity).withCallback(branchReferralInitListener).withData(activity.getIntent() != null ? activity.getIntent().getData() : null).init(); + } + } + + private void teardownChannels() { + LogUtils.debug(DEBUG_NAME, "teardownChannels call"); + this.activityPluginBinding = null; + this.activity = null; + this.context = null; + } + + /** + * --------------------------------------------------------------------------------------------- + * ActivityAware Interface Methods + * -------------------------------------------------------------------------------------------- + **/ + @Override + public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) { + LogUtils.debug(DEBUG_NAME, "onAttachedToActivity call"); + this.activityPluginBinding = activityPluginBinding; + setActivity(activityPluginBinding.getActivity()); + activityPluginBinding.addOnNewIntentListener(this); + } + + @Override + public void onDetachedFromActivity() { + LogUtils.debug(DEBUG_NAME, "onDetachedFromActivity call"); + activityPluginBinding.removeOnNewIntentListener(this); + this.activity = null; + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + LogUtils.debug(DEBUG_NAME, "onDetachedFromActivityForConfigChanges call"); + onDetachedFromActivity(); + } + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding activityPluginBinding) { + LogUtils.debug(DEBUG_NAME, "onReattachedToActivityForConfigChanges call"); + onAttachedToActivity(activityPluginBinding); + } + + /** + * --------------------------------------------------------------------------------------------- + * StreamHandler Interface Methods + * -------------------------------------------------------------------------------------------- + **/ + @Override + public void onListen(Object o, EventChannel.EventSink eventSink) { + LogUtils.debug(DEBUG_NAME, "onListen call"); + this.eventSink = new MainThreadEventSink(eventSink); + if (initialParams != null) { + eventSink.success(initialParams); + initialParams = null; + initialError = null; + } else if (initialError != null) { + eventSink.error(String.valueOf(initialError.getErrorCode()), initialError.getMessage(), null); + initialParams = null; + initialError = null; + } + } + + @Override + public void onCancel(Object o) { + LogUtils.debug(DEBUG_NAME, "onCancel call"); + this.eventSink = new MainThreadEventSink(null); + initialError = null; + initialParams = null; + } + + /** + * --------------------------------------------------------------------------------------------- + * ActivityLifecycleCallbacks Interface Methods + * -------------------------------------------------------------------------------------------- + **/ + @Override + public void onActivityCreated(Activity activity, Bundle bundle) { + } + + @Override + public void onActivityStarted(Activity activity) { + LogUtils.debug(DEBUG_NAME, "onActivityStarted call"); + Branch.sessionBuilder(activity).withCallback(branchReferralInitListener).withData(activity.getIntent() != null ? activity.getIntent().getData() : null).init(); + } + + @Override + public void onActivityResumed(Activity activity) { + } + + @Override + public void onActivityPaused(Activity activity) { + } + + @Override + public void onActivityStopped(Activity activity) { + LogUtils.debug(DEBUG_NAME, "onActivityStopped call"); + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + LogUtils.debug(DEBUG_NAME, "onActivityDestroyed call"); + if (this.activity == activity) { + activity.getApplication().unregisterActivityLifecycleCallbacks(this); + } + } + + /** + * --------------------------------------------------------------------------------------------- + * NewIntentListener Interface Methods + * -------------------------------------------------------------------------------------------- + **/ + @Override + public boolean onNewIntent(Intent intent) { + LogUtils.debug(DEBUG_NAME, "onNewIntent call"); + if (this.activity != null) { + this.activity.setIntent(intent); + + if (intent != null && + intent.hasExtra("branch_force_new_session") && + intent.getBooleanExtra("branch_force_new_session", false)) { + Branch.sessionBuilder(this.activity).withCallback(branchReferralInitListener).reInit(); + } + return true; + } + return false; + } + + /** + * --------------------------------------------------------------------------------------------- + * MethodCallHandler Interface Methods + * -------------------------------------------------------------------------------------------- + **/ + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull Result rawResult) { + Result result = new MethodResultWrapper(rawResult); + switch (call.method) { + case "getShortUrl": + getShortUrl(call, result); + break; + case "showShareSheet": + showShareSheet(call, result); + break; + case "registerView": + registerView(call); + break; + case "listOnSearch": + listOnSearch(call, result); + break; + case "removeFromSearch": + removeFromSearch(call, result); + break; + case "trackContent": + trackContent(call); + break; + case "trackContentWithoutBuo": + trackContentWithoutBuo(call); + break; + case "setIdentity": + setIdentity(call); + break; + case "setRequestMetadata": + setRequestMetadata(call); + break; + case "logout": + logout(); + break; + case "getLatestReferringParams": + getLatestReferringParams(result); + break; + case "getFirstReferringParams": + getFirstReferringParams(result); + break; + case "setTrackingDisabled": + setTrackingDisabled(call); + break; + case "validateSDKIntegration": + validateSDKIntegration(); + break; + case "isUserIdentified": + isUserIdentified(result); + break; + case "setConnectTimeout": + setConnectTimeout(call); + break; + case "setTimeout": + setTimeout(call); + break; + case "setRetryCount": + setRetryCount(call); + break; + case "setRetryInterval": + setRetryInterval(call); + break; + case "getLastAttributedTouchData": + getLastAttributedTouchData(call, result); + break; + default: + result.notImplemented(); + break; + } + } + + /** + * --------------------------------------------------------------------------------------------- + * Branch SDK Call Methods + * -------------------------------------------------------------------------------------------- + **/ + private final Branch.BranchReferralInitListener branchReferralInitListener = new + Branch.BranchReferralInitListener() { + @Override + public void onInitFinished(JSONObject params, BranchError error) { + if (error == null) { + LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - params: " + params.toString()); + try { + initialParams = branchSdkHelper.paramsToMap(params); + } catch (JSONException e) { + LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - error to Map: " + e.getLocalizedMessage()); + return; + } + if (eventSink != null) { + eventSink.success(initialParams); + initialParams = null; + } + } else { + if (error.getErrorCode() == BranchError.ERR_BRANCH_ALREADY_INITIALIZED || error.getErrorCode() == BranchError.ERR_IMPROPER_REINITIALIZATION) { + return; + } + LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - error: " + error); + if (eventSink != null) { + eventSink.error(String.valueOf(error.getErrorCode()), error.getMessage(), null); + initialError = null; + } else { + initialError = error; + } + } + } + }; + + private void validateSDKIntegration() { + IntegrationValidator.validate(activity); + } + + private void getShortUrl(MethodCall call, final Result result) { + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + + HashMap argsMap = (HashMap) call.arguments; + BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); + + LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); + + final Map response = new HashMap<>(); + + buo.generateShortUrl(activity, linkProperties, new Branch.BranchLinkCreateListener() { + @Override + public void onLinkCreate(String url, BranchError error) { + + if (error == null) { + LogUtils.debug(DEBUG_NAME, "Branch link to share: " + url); + response.put("success", true); + response.put("url", url); + } else { + response.put("success", false); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + }); + } + + private void showShareSheet(MethodCall call, final Result result) { + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + HashMap argsMap = (HashMap) call.arguments; + BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); + + LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); + String messageText = (String) argsMap.get("messageText"); + String messageTitle = (String) argsMap.get("messageTitle"); + String sharingTitle = (String) argsMap.get("sharingTitle"); + + final Map response = new HashMap<>(); + + ShareSheetStyle shareSheetStyle = new ShareSheetStyle(activity, messageTitle, messageText) + .setAsFullWidthStyle(true) + .setSharingTitle(sharingTitle); + + buo.showShareSheet(activity, + linkProperties, + shareSheetStyle, + new Branch.ExtendedBranchLinkShareListener() { + @Override + public void onShareLinkDialogLaunched() { + } + + @Override + public void onShareLinkDialogDismissed() { + } + + @Override + public void onLinkShareResponse(String sharedLink, String sharedChannel, BranchError error) { + if (error == null) { + LogUtils.debug(DEBUG_NAME, "Branch link share: " + sharedLink); + response.put("success", Boolean.TRUE); + response.put("url", sharedLink); + } else { + response.put("success", Boolean.FALSE); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + + @Override + public void onChannelSelected(String channelName) { + + } + + @Override + public boolean onChannelSelected(String channelName, BranchUniversalObject buo, LinkProperties linkProperties) { + return false; + } + }); + } + + private void registerView(MethodCall call) { + LogUtils.debug(DEBUG_NAME, "registerView call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + HashMap argsMap = (HashMap) call.arguments; + final BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + buo.registerView(); + } + }); + } + + private void listOnSearch(MethodCall call, Result result) { + LogUtils.debug(DEBUG_NAME, "listOnSearch call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + HashMap argsMap = (HashMap) call.arguments; + BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); + if (argsMap.containsKey("lp")) { + LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); + buo.listOnGoogleSearch(context, linkProperties); + } else { + buo.listOnGoogleSearch(context); + } + result.success(Boolean.TRUE); + } + + private void removeFromSearch(MethodCall call, Result result) { + LogUtils.debug(DEBUG_NAME, "removeFromSearch call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + HashMap argsMap = (HashMap) call.arguments; + BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); + if (argsMap.containsKey("lp")) { + LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); + buo.removeFromLocalIndexing(context, linkProperties); + } else { + buo.removeFromLocalIndexing(context); + } + result.success(Boolean.TRUE); + } + + private void trackContent(MethodCall call) { + LogUtils.debug(DEBUG_NAME, "trackContent call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + HashMap argsMap = (HashMap) call.arguments; + + final List buo = new ArrayList(); + for (HashMap b : (List>) argsMap.get("buo")) { + buo.add(branchSdkHelper.convertToBUO(b)); + } + final BranchEvent event = branchSdkHelper.convertToEvent((HashMap) argsMap.get("event")); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + event.addContentItems(buo).logEvent(context); + } + }); + } + + private void trackContentWithoutBuo(MethodCall call) { + LogUtils.debug(DEBUG_NAME, "trackContentWithoutBuo call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + HashMap argsMap = (HashMap) call.arguments; + final BranchEvent event = branchSdkHelper.convertToEvent((HashMap) argsMap.get("event")); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + event.logEvent(context); + } + }); + } + + private void setIdentity(MethodCall call) { + LogUtils.debug(DEBUG_NAME, "setIdentity call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final String userId = call.argument("userId"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Branch.getAutoInstance(context).setIdentity(userId); + } + }); + } + + private void setRequestMetadata(MethodCall call) { + LogUtils.debug(DEBUG_NAME, "setRequestMetadata call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final String key = call.argument("key"); + final String value = call.argument("value"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Branch.getAutoInstance(context).setRequestMetadata(key, value); + } + }); + } + + private void logout() { + LogUtils.debug(DEBUG_NAME, "logout call"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Branch.getAutoInstance(context).logout(); + } + }); + } + + private void getLatestReferringParams(Result result) { + LogUtils.debug(DEBUG_NAME, "getLatestReferringParams call"); + JSONObject sessionParams = Branch.getAutoInstance(context).getLatestReferringParams(); + try { + result.success(branchSdkHelper.paramsToMap(sessionParams)); + } catch (JSONException e) { + e.printStackTrace(); + result.error(DEBUG_NAME, e.getMessage(), null); + } + } + + private void getFirstReferringParams(Result result) { + LogUtils.debug(DEBUG_NAME, "getFirstReferringParams call"); + JSONObject sessionParams = Branch.getAutoInstance(context).getFirstReferringParams(); + try { + result.success(branchSdkHelper.paramsToMap(sessionParams)); + } catch (JSONException e) { + e.printStackTrace(); + result.error(DEBUG_NAME, e.getMessage(), null); + } + } + + private void setTrackingDisabled(MethodCall call) { + LogUtils.debug(DEBUG_NAME, "setTrackingDisabled call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final boolean value = call.argument("disable"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Branch.getAutoInstance(context).disableTracking(value); + } + }); + } + + private void isUserIdentified(Result result) { + LogUtils.debug(DEBUG_NAME, "isUserIdentified call"); + result.success(Branch.getAutoInstance(context).isUserIdentified()); + } + + private void setConnectTimeout(final MethodCall call) { + LogUtils.debug(DEBUG_NAME, "setConnectTimeout call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final int value = call.argument("connectTimeout"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Branch.getAutoInstance(context).setNetworkConnectTimeout(value); + } + }); + } + + private void setTimeout(final MethodCall call) { + LogUtils.debug(DEBUG_NAME, "setConnectTimeout call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final int value = call.argument("timeout"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Branch.getAutoInstance(context).setNetworkTimeout(value); + } + }); + } + + private void setRetryCount(final MethodCall call) { + LogUtils.debug(DEBUG_NAME, "setRetryCount call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final int value = call.argument("retryCount"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Branch.getAutoInstance(context).setRetryCount(value); + } + }); + } + + private void setRetryInterval(final MethodCall call) { + LogUtils.debug(DEBUG_NAME, "setRetryInterval call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final int value = call.argument("retryInterval"); + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + Branch.getAutoInstance(context).setRetryInterval(value); + } + }); + } + + private void getLastAttributedTouchData(final MethodCall call, final Result result) { + LogUtils.debug(DEBUG_NAME, "getLastAttributedTouchData call"); + + final Map response = new HashMap<>(); + + if (call.hasArgument("attributionWindow")) { + final int attributionWindow = call.argument("attributionWindow"); + Branch.getAutoInstance(context).getLastAttributedTouchData( + new ServerRequestGetLATD.BranchLastAttributedTouchDataListener() { + @Override + public void onDataFetched(JSONObject jsonObject, BranchError error) { + if (error == null) { + response.put("success", Boolean.TRUE); + JSONObject jo = new JSONObject(); + try { + jo.put("latd", jsonObject); + response.put("data", branchSdkHelper.paramsToMap(jo)); + } catch (JSONException e) { + e.printStackTrace(); + } + } else { + response.put("success", Boolean.FALSE); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + }, attributionWindow); + + } else { + Branch.getAutoInstance(context).getLastAttributedTouchData( + new ServerRequestGetLATD.BranchLastAttributedTouchDataListener() { + @Override + public void onDataFetched(JSONObject jsonObject, BranchError error) { + if (error == null) { + response.put("success", Boolean.TRUE); + JSONObject jo = new JSONObject(); + try { + jo.put("latd", jsonObject); + response.put("data", branchSdkHelper.paramsToMap(jo)); + } catch (JSONException e) { + e.printStackTrace(); + } + } else { + response.put("success", Boolean.FALSE); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + }); + } + } + + // MethodChannel.Result wrapper that responds on the platform thread. + private static class MethodResultWrapper implements Result { + private final Result methodResult; + private final Handler handler; + + MethodResultWrapper(Result result) { + methodResult = result; + handler = new Handler(Looper.getMainLooper()); + } + + @Override + public void success(final Object result) { + handler.post( + new Runnable() { + @Override + public void run() { + try { + methodResult.success(result); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void error( + final String errorCode, final String errorMessage, final Object errorDetails) { + handler.post( + new Runnable() { + @Override + public void run() { + try { + methodResult.error(errorCode, errorMessage, errorDetails); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void notImplemented() { + handler.post( + new Runnable() { + @Override + public void run() { + try { + methodResult.notImplemented(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } + + private static class MainThreadEventSink implements EventChannel.EventSink { + private final EventChannel.EventSink eventSink; + private final Handler handler; + + MainThreadEventSink(EventChannel.EventSink eventSink) { + this.eventSink = eventSink; + handler = new Handler(Looper.getMainLooper()); + } + + @Override + public void success(final Object o) { + handler.post(new Runnable() { + @Override + public void run() { + try { + if (eventSink != null) { + eventSink.success(o); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void error(final String s, final String s1, final Object o) { + handler.post(new Runnable() { + @Override + public void run() { + try { + if (eventSink != null) { + eventSink.error(s, s1, o); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void endOfStream() { + handler.post(new Runnable() { + @Override + public void run() { + try { + if (eventSink != null) { + eventSink.endOfStream(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } +} + + diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/LogUtils.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/LogUtils.java index bd037a90..d47a7e10 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/LogUtils.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/LogUtils.java @@ -1,11 +1,13 @@ -package br.com.rsmarques.flutter_branch_sdk; - -import android.util.Log; - -public class LogUtils { - public static void debug(final String tag, String message) { - if (BuildConfig.DEBUG) { - Log.d(tag, message); - } - } -} +package br.com.rsmarques.flutter_branch_sdk; + +import android.util.Log; + +import io.flutter.BuildConfig; + +public class LogUtils { + public static void debug(final String tag, String message) { + if (BuildConfig.DEBUG) { + Log.d(tag, message); + } + } +} diff --git a/example/.gitignore b/example/.gitignore index ae1f1838..504d1e56 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,37 +1,47 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Exceptions to above rules. -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/.metadata b/example/.metadata deleted file mode 100644 index 1b5cec02..00000000 --- a/example/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 27321ebbad34b0a3fafe99fac037102196d655ff - channel: stable - -project_type: app diff --git a/example/README.md b/example/README.md index 94c1b38d..6cab3168 100644 --- a/example/README.md +++ b/example/README.md @@ -1,9 +1,16 @@ -# flutter_branch_sdk_example - -Demonstrates how to use the flutter_branch_sdk plugin. - -## Getting Started - -See the `example` directory for a complete sample app using Branch SDK. - -![Example app](https://user-images.githubusercontent.com/17687286/70445281-0b87c180-1a7a-11ea-8611-7217d46c75a7.png) +# flutter_branch_sdk_example + +Demonstrates how to use the flutter_branch_sdk plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 00000000..b4ce1be2 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore index bc2100d8..5d99765d 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -1,7 +1,13 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 312b0e55..17024421 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,65 +1,63 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 31 - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "br.com.rsmarques.flutter_branch_sdk_example" - minSdkVersion 21 - targetSdkVersion 31 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - multiDexEnabled true - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - shrinkResources true - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } -} - -flutter { - source '../..' -} - -dependencies { - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' -} +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "br.com.rsmarques.flutter_branch_sdk_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + multiDexEnabled true + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + shrinkResources true + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +flutter { + source '../..' +} diff --git a/example/android/app/proguard-rules.pro b/example/android/app/proguard-rules.pro old mode 100755 new mode 100644 index be37f52e..24f63cf8 --- a/example/android/app/proguard-rules.pro +++ b/example/android/app/proguard-rules.pro @@ -1,16 +1,16 @@ -#Flutter Wrapper --keep class io.flutter.app.** { *; } --keep class io.flutter.plugin.** { *; } --keep class io.flutter.util.** { *; } --keep class io.flutter.view.** { *; } --keep class io.flutter.** { *; } --keep class io.flutter.plugins.** { *; } --keep class com.google.android.gms.ads.identifier.** { *; } --keep class com.google.android.gms.* {*;} --keep class com.google.android.gms.ads.identifier.AdvertisingIdClient { - com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context); -} --keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info { - java.lang.String getId(); - boolean isLimitAdTrackingEnabled(); +#Flutter Wrapper +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } +-keep class com.google.android.gms.ads.identifier.** { *; } +-keep class com.google.android.gms.* {*;} +-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient { + com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context); +} +-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info { + java.lang.String getId(); + boolean isLimitAdTrackingEnabled(); } \ No newline at end of file diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 5e6de6c3..3cc9bbcf 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,7 +1,8 @@ - - - - + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index cc885d41..f935ae78 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,61 +1,65 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/java/br/com/rsmarques/flutter_branch_sdk_example/MainActivity.java b/example/android/app/src/main/java/br/com/rsmarques/flutter_branch_sdk_example/MainActivity.java index b6a37ecb..e2b26366 100644 --- a/example/android/app/src/main/java/br/com/rsmarques/flutter_branch_sdk_example/MainActivity.java +++ b/example/android/app/src/main/java/br/com/rsmarques/flutter_branch_sdk_example/MainActivity.java @@ -1,7 +1,6 @@ -package br.com.rsmarques.flutter_branch_sdk_example; - -import io.flutter.embedding.android.FlutterActivity; - -public class MainActivity extends FlutterActivity { - -} +package br.com.rsmarques.flutter_branch_sdk_example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..1cb7aa2f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml index 304732f8..84037589 100644 --- a/example/android/app/src/main/res/drawable/launch_background.xml +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,12 @@ - - - - - - - - + + + + + + + + diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..360a1605 --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index 00fa4417..5fac6796 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ - - - - + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 5e6de6c3..3cc9bbcf 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,7 +1,8 @@ - - - - + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle index 0b4cf534..104a4864 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,29 +1,31 @@ -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 94adc3a3..46c1f169 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true -android.enableJetifier=true +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 939efa29..258d5e1f 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 5a2f14fb..33f0745d 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,15 +1,11 @@ -include ':app' - -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() - -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} - -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example/android/settings_aar.gradle b/example/android/settings_aar.gradle deleted file mode 100644 index e7b4def4..00000000 --- a/example/android/settings_aar.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/example/ios/.gitignore b/example/ios/.gitignore index e96ef602..ad322bc0 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -1,32 +1,34 @@ -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id deleted file mode 100644 index a8c8b3dd..00000000 --- a/example/ios/Flutter/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -c7efa40e8c2c02006bfd59baf861b387 \ No newline at end of file diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index f2872cf4..80391e41 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -1,26 +1,26 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 9.0 - - + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index e8efba11..dfd26268 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 399e9340..a97381ae 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 6034cd70..45d125bb 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -22,7 +22,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Branch: a6f1d597dc7c027360f386d05e8d109043b207d8 Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a - flutter_branch_sdk: ecb5dd985a092ef5aecc9195c6645649e6ea1b9f + flutter_branch_sdk: c7161eca896193a81bbb452f2cb492d5d89f0d91 PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index fd09e50a..15c0cdc0 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,15 +3,14 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5B1C365AF4DCE9C8CC3B7F6D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9EBDF3395510D4A330D468B /* Pods_Runner.framework */; }; - 5B6F7EDD23B72088001411F0 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B6F7EDC23B72088001411F0 /* SafariServices.framework */; }; - 5BDFC8F823A9F2D000352C5A /* Runner.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = 5BDFC8F723A9F28700352C5A /* Runner.entitlements */; }; + 530C6B849848A7137517F90B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5955988AC5EF176002150C7E /* Pods_Runner.framework */; }; + 5B86E4EB285AF073001770A9 /* Runner.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = 5B86E4EA285AF021001770A9 /* Runner.entitlements */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -32,17 +31,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 124E06BB8FCD54487179B61F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 5B6F7EDC23B72088001411F0 /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = System/Library/Frameworks/SafariServices.framework; sourceTree = SDKROOT; }; - 5BDFC8F723A9F28700352C5A /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 5955988AC5EF176002150C7E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5B86E4EA285AF021001770A9 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 76B772D28CA1719E9F34FD8C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7D4B4F1BEF1D1B627C15D066 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 946D0500AB85035C69611626 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 896072A09BADE8B62197469F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -50,7 +48,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A9EBDF3395510D4A330D468B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A35C6EF0AAAF92EE49DA1FCA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -58,29 +56,27 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5B6F7EDD23B72088001411F0 /* SafariServices.framework in Frameworks */, - 5B1C365AF4DCE9C8CC3B7F6D /* Pods_Runner.framework in Frameworks */, + 530C6B849848A7137517F90B /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 28F1E736C41452F4D999097B /* Frameworks */ = { + 4B08117C6FAFAE2CA00517DF /* Frameworks */ = { isa = PBXGroup; children = ( - 5B6F7EDC23B72088001411F0 /* SafariServices.framework */, - A9EBDF3395510D4A330D468B /* Pods_Runner.framework */, + 5955988AC5EF176002150C7E /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; - 648E178D9C596B1BBB8D9F3B /* Pods */ = { + 83204363E71EE2485FF0D939 /* Pods */ = { isa = PBXGroup; children = ( - 946D0500AB85035C69611626 /* Pods-Runner.debug.xcconfig */, - 7D4B4F1BEF1D1B627C15D066 /* Pods-Runner.release.xcconfig */, - 76B772D28CA1719E9F34FD8C /* Pods-Runner.profile.xcconfig */, + 124E06BB8FCD54487179B61F /* Pods-Runner.debug.xcconfig */, + A35C6EF0AAAF92EE49DA1FCA /* Pods-Runner.release.xcconfig */, + 896072A09BADE8B62197469F /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -102,8 +98,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 648E178D9C596B1BBB8D9F3B /* Pods */, - 28F1E736C41452F4D999097B /* Frameworks */, + 83204363E71EE2485FF0D939 /* Pods */, + 4B08117C6FAFAE2CA00517DF /* Frameworks */, ); sourceTree = ""; }; @@ -118,12 +114,11 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 5BDFC8F723A9F28700352C5A /* Runner.entitlements */, + 5B86E4EA285AF021001770A9 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -132,13 +127,6 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -146,14 +134,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 369621B7B15D68E43DA939A7 /* [CP] Check Pods Manifest.lock */, + 51684BB5E9E4DDF572E3CBA1 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 8E96BCAB8DDECFEFCEBC5B91 /* [CP] Embed Pods Frameworks */, + F2E1F5326562B92A6334FD21 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -171,17 +159,16 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 1300; - ORGANIZATIONNAME = "The Chromium Authors"; + ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = XM2A23Q5KM; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -203,7 +190,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5BDFC8F823A9F2D000352C5A /* Runner.entitlements in Resources */, + 5B86E4EB285AF073001770A9 /* Runner.entitlements in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, @@ -214,7 +201,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 369621B7B15D68E43DA939A7 /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 51684BB5E9E4DDF572E3CBA1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -236,54 +237,37 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Thin Binary"; + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - 8E96BCAB8DDECFEFCEBC5B91 /* [CP] Embed Pods Frameworks */ = { + F2E1F5326562B92A6334FD21 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Branch/Branch.framework", - "${BUILT_PRODUCTS_DIR}/flutter_branch_sdk/flutter_branch_sdk.framework", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Branch.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_branch_sdk.framework", + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -378,39 +362,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = XM2A23Q5KM; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - OTHER_LDFLAGS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "-framework", - "\"AdSupport\"", - "-framework", - "\"Branch\"", - "-framework", - "\"CoreTelephony\"", - "-framework", - "\"Flutter\"", - "-framework", - "\"MobileCoreServices\"", - "-framework", - "\"SystemConfiguration\"", - "-framework", - "\"WebKit\"", - "-framework", - "\"flutter_branch_sdk\"", - "-framework", - "\"iAd\"", - "-framework", - "\"Flutter\"", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "br.com.rsmarques.flutter-branch-sdk-example"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -520,7 +475,8 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -536,39 +492,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = XM2A23Q5KM; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-framework", - "\"AdSupport\"", - "-framework", - "\"Branch\"", - "-framework", - "\"CoreTelephony\"", - "-framework", - "\"Flutter\"", - "-framework", - "\"MobileCoreServices\"", - "-framework", - "\"SystemConfiguration\"", - "-framework", - "\"WebKit\"", - "-framework", - "\"flutter_branch_sdk\"", - "-framework", - "\"iAd\"", - "-framework", - "\"Flutter\"", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "br.com.rsmarques.flutter-branch-sdk-example"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -589,39 +516,10 @@ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = XM2A23Q5KM; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - OTHER_LDFLAGS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "-framework", - "\"AdSupport\"", - "-framework", - "\"Branch\"", - "-framework", - "\"CoreTelephony\"", - "-framework", - "\"Flutter\"", - "-framework", - "\"MobileCoreServices\"", - "-framework", - "\"SystemConfiguration\"", - "-framework", - "\"WebKit\"", - "-framework", - "\"flutter_branch_sdk\"", - "-framework", - "\"iAd\"", - "-framework", - "\"Flutter\"", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "br.com.rsmarques.flutter-branch-sdk-example"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 919434a6..c4b79bd8 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..fc6bf807 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..af0309c4 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..f9cbb254 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,87 +1,87 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index 18d98100..fc6bf807 100644 --- a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..af0309c4 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4a..37636837 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,13 +1,13 @@ -import UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fab..1950fd80 100644 --- a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,122 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json index 0bedcf2f..d08a4de3 100644 --- a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -1,23 +1,23 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md index 89c2725b..65a94b5d 100644 --- a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -1,5 +1,5 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard index f2e259c7..497371ea 100644 --- a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard index f3c28516..bbb83caa 100644 --- a/example/ios/Runner/Base.lproj/Main.storyboard +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index e78dfcbd..f986b2c3 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -2,8 +2,12 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Flutter Branch Sdk CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +15,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - Flutter Branch SDK Example + flutter_branch_sdk_example CFBundlePackageType APPL CFBundleShortVersionString @@ -31,6 +35,8 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSUserTrackingUsageDescription + App would like to access IDFA for tracking purpose UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -56,15 +62,11 @@ branch_check_pasteboard - branch_key - key_test_ipQTteg11ENANDeCzSXgqdgfuycWoXYH - NSUserTrackingUsageDescription - App would like to access IDFA for tracking purpose - branch_enable_log - branch_enable_facebook_ads - CADisableMinimumFrameDurationOnPhone + branch_enable_log + branch_key + key_test_ipQTteg11ENANDeCzSXgqdgfuycWoXYH diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h index 7335fdf9..fae207f9 100644 --- a/example/ios/Runner/Runner-Bridging-Header.h +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements index 5731f420..f03a42e3 100644 --- a/example/ios/Runner/Runner.entitlements +++ b/example/ios/Runner/Runner.entitlements @@ -2,12 +2,12 @@ - com.apple.developer.associated-domains - - applinks:flutterbranchsdk.app.link - applinks:flutterbranchsdk-alternate.app.link + com.apple.developer.associated-domains + + applinks:flutterbranchsdk.app.link + applinks:flutterbranchsdk-alternate.app.link applinks:flutterbranchsdk.test-app.link applinks:flutterbranchsdk-alternate.test-app.link - + diff --git a/example/lib/custom_button.dart b/example/lib/custom_button.dart index 809da8db..cee297ac 100644 --- a/example/lib/custom_button.dart +++ b/example/lib/custom_button.dart @@ -1,19 +1,20 @@ -import 'package:flutter/material.dart'; - -class CustomButton extends StatelessWidget { - CustomButton({required this.onPressed, required this.child}); - - final GestureTapCallback onPressed; - final Widget child; - - @override - Widget build(BuildContext context) { - return Container( - height: 50, - padding: EdgeInsets.symmetric(vertical: 4, horizontal: 2), - child: ElevatedButton( - child: child, - onPressed: onPressed, - )); - } -} +import 'package:flutter/material.dart'; + +class CustomButton extends StatelessWidget { + const CustomButton({Key? key, required this.onPressed, required this.child}) + : super(key: key); + + final GestureTapCallback onPressed; + final Widget child; + + @override + Widget build(BuildContext context) { + return Container( + height: 50, + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 2), + child: ElevatedButton( + onPressed: onPressed, + child: child, + )); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 4e054f3a..08928a05 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,574 +1,593 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; - -import 'custom_button.dart'; - -void main() { - WidgetsFlutterBinding.ensureInitialized(); - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - Widget build(BuildContext context) { - return MaterialApp( - title: "Flutter Branch SDK Example", - debugShowCheckedModeBanner: false, - home: HomePage(), - ); - } -} - -class HomePage extends StatefulWidget { - @override - _HomePageState createState() => _HomePageState(); -} - -class _HomePageState extends State { - final GlobalKey scaffoldMessengerKey = - GlobalKey(); - - BranchContentMetaData metadata = BranchContentMetaData(); - BranchUniversalObject? buo; - BranchLinkProperties lp = BranchLinkProperties(); - BranchEvent? eventStandart; - BranchEvent? eventCustom; - - StreamSubscription? streamSubscription; - StreamController controllerData = StreamController(); - StreamController controllerInitSession = StreamController(); - StreamController controllerUrl = StreamController(); - - @override - void initState() { - super.initState(); - - listenDynamicLinks(); - - initDeepLinkData(); - - FlutterBranchSdk.setIdentity('branch_user_test'); - - //requestATTTracking(); - } - - void requestATTTracking() async { - AppTrackingStatus status; - status = await FlutterBranchSdk.requestTrackingAuthorization(); - print(status); - - status = await FlutterBranchSdk.getTrackingAuthorizationStatus(); - print(status); - - final uuid = await FlutterBranchSdk.getAdvertisingIdentifier(); - print(uuid); - } - - void listenDynamicLinks() async { - streamSubscription = FlutterBranchSdk.initSession().listen((data) { - print('listenDynamicLinks - DeepLink Data: $data'); - controllerData.sink.add((data.toString())); - if (data.containsKey('+clicked_branch_link') && - data['+clicked_branch_link'] == true) { - print( - '------------------------------------Link clicked----------------------------------------------'); - print('Custom string: ${data['custom_string']}'); - print('Custom number: ${data['custom_number']}'); - print('Custom bool: ${data['custom_bool']}'); - print('Custom list number: ${data['custom_list_number']}'); - print( - '------------------------------------------------------------------------------------------------'); - showSnackBar( - context: context, - message: 'Link clicked: Custom string - ${data['custom_string']}', - duration: 10); - } - }, onError: (error) { - PlatformException platformException = error as PlatformException; - print( - 'InitSession error: ${platformException.code} - ${platformException.message}'); - controllerInitSession.add( - 'InitSession error: ${platformException.code} - ${platformException.message}'); - }); - } - - void initDeepLinkData() { - metadata = BranchContentMetaData() - ..addCustomMetadata('custom_string', 'abc') - ..addCustomMetadata('custom_number', 12345) - ..addCustomMetadata('custom_bool', true) - ..addCustomMetadata('custom_list_number', [1, 2, 3, 4, 5]) - ..addCustomMetadata('custom_list_string', ['a', 'b', 'c']) - //--optional Custom Metadata - ..contentSchema = BranchContentSchema.COMMERCE_PRODUCT - ..price = 50.99 - ..currencyType = BranchCurrencyType.BRL - ..quantity = 50 - ..sku = 'sku' - ..productName = 'productName' - ..productBrand = 'productBrand' - ..productCategory = BranchProductCategory.ELECTRONICS - ..productVariant = 'productVariant' - ..condition = BranchCondition.NEW - ..rating = 100 - ..ratingAverage = 50 - ..ratingMax = 100 - ..ratingCount = 2 - ..setAddress( - street: 'street', - city: 'city', - region: 'ES', - country: 'Brazil', - postalCode: '99999-987') - ..setLocation(31.4521685, -114.7352207); - - buo = BranchUniversalObject( - canonicalIdentifier: 'flutter/branch', - //parameter canonicalUrl - //If your content lives both on the web and in the app, make sure you set its canonical URL - // (i.e. the URL of this piece of content on the web) when building any BUO. - // By doing so, we’ll attribute clicks on the links that you generate back to their original web page, - // even if the user goes to the app instead of your website! This will help your SEO efforts. - canonicalUrl: 'https://flutter.dev', - title: 'Flutter Branch Plugin', - imageUrl: - 'https://flutter.dev/assets/flutter-lockup-4cb0ee072ab312e59784d9fbf4fb7ad42688a7fdaea1270ccf6bbf4f34b7e03f.svg', - contentDescription: 'Flutter Branch Description', - /* - contentMetadata: BranchContentMetaData() - ..addCustomMetadata('custom_string', 'abc') - ..addCustomMetadata('custom_number', 12345) - ..addCustomMetadata('custom_bool', true) - ..addCustomMetadata('custom_list_number', [1, 2, 3, 4, 5]) - ..addCustomMetadata('custom_list_string', ['a', 'b', 'c']), - */ - contentMetadata: metadata, - keywords: ['Plugin', 'Branch', 'Flutter'], - publiclyIndex: true, - locallyIndex: true, - expirationDateInMilliSec: - DateTime.now().add(Duration(days: 365)).millisecondsSinceEpoch); - - lp = BranchLinkProperties( - channel: 'facebook', - feature: 'sharing', - //parameter alias - //Instead of our standard encoded short url, you can specify the vanity alias. - // For example, instead of a random string of characters/integers, you can set the vanity alias as *.app.link/devonaustin. - // Aliases are enforced to be unique** and immutable per domain, and per link - they cannot be reused unless deleted. - //alias: 'https://branch.io' //define link url, - stage: 'new share', - campaign: 'xxxxx', - tags: ['one', 'two', 'three']) - ..addControlParam('\$uri_redirect_mode', '1') - ..addControlParam('referring_user_id', 'asdf'); - - eventStandart = BranchEvent.standardEvent(BranchStandardEvent.ADD_TO_CART) - //--optional Event data - ..transactionID = '12344555' - ..currency = BranchCurrencyType.BRL - ..revenue = 1.5 - ..shipping = 10.2 - ..tax = 12.3 - ..coupon = 'test_coupon' - ..affiliation = 'test_affiliation' - ..eventDescription = 'Event_description' - ..searchQuery = 'item 123' - ..adType = BranchEventAdType.BANNER - ..addCustomData( - 'Custom_Event_Property_Key1', 'Custom_Event_Property_val1') - ..addCustomData( - 'Custom_Event_Property_Key2', 'Custom_Event_Property_val2'); - - eventCustom = BranchEvent.customEvent('Custom_event') - ..addCustomData( - 'Custom_Event_Property_Key1', 'Custom_Event_Property_val1') - ..addCustomData( - 'Custom_Event_Property_Key2', 'Custom_Event_Property_val2'); - } - - void showSnackBar( - {required BuildContext context, - required String message, - int duration = 1}) { - scaffoldMessengerKey.currentState!.removeCurrentSnackBar(); - scaffoldMessengerKey.currentState!.showSnackBar( - SnackBar( - content: Text(message), - duration: Duration(seconds: duration), - ), - ); - } - - @override - Widget build(BuildContext context) { - return ScaffoldMessenger( - key: scaffoldMessengerKey, - child: Scaffold( - appBar: AppBar( - title: const Text('Branch.io Plugin Example App'), - ), - body: Scrollbar( - isAlwaysShown: true, - child: SingleChildScrollView( - padding: EdgeInsets.all(10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - StreamBuilder( - stream: controllerInitSession.stream, - initialData: '', - builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data!.isNotEmpty) { - return Column( - children: [ - Center( - child: Text( - snapshot.data!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.red), - )) - ], - ); - } else { - return Container(); - } - }, - ), - CustomButton( - child: Text('Validate SDK Integration'), - onPressed: () { - if (kIsWeb) { - showSnackBar( - context: context, - message: - 'validateSDKIntegration() not available in Flutter Web'); - return; - } - - FlutterBranchSdk.validateSDKIntegration(); - if (Platform.isAndroid) { - showSnackBar( - context: context, - message: 'Check messages in run log or logcat'); - } - }, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: CustomButton( - child: Text('Enable tracking'), - onPressed: () { - FlutterBranchSdk.disableTracking(false); - showSnackBar( - context: context, message: 'Tracking enabled'); - }, - ), - ), - SizedBox( - width: 10, - ), - Expanded( - child: CustomButton( - child: Text('Disable tracking'), - onPressed: () { - FlutterBranchSdk.disableTracking(true); - showSnackBar( - context: context, message: 'Tracking disabled'); - }, - ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: CustomButton( - child: Text('Identify user'), - onPressed: () { - FlutterBranchSdk.setIdentity('branch_user_test'); - showSnackBar( - context: context, - message: 'User branch_user_test identfied'); - }, - ), - ), - SizedBox( - width: 10, - ), - Expanded( - child: CustomButton( - child: Text('User logout'), - onPressed: () { - FlutterBranchSdk.logout(); - showSnackBar( - context: context, - message: 'User branch_user_test logout'); - }, - ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: CustomButton( - child: Text('Register view'), - onPressed: () { - FlutterBranchSdk.registerView(buo: buo!); - - showSnackBar( - context: context, message: 'Event Registered'); - }, - ), - ), - SizedBox( - width: 10, - ), - Expanded( - child: CustomButton( - child: Text('Track content'), - onPressed: () { - //FlutterBranchSdk.trackContent( - // buo: [buo!], branchEvent: eventStandart!); - - FlutterBranchSdk.trackContent( - buo: [buo!], branchEvent: eventCustom!); - /* - FlutterBranchSdk.trackContentWithoutBuo( - branchEvent: eventStandart!); - FlutterBranchSdk.trackContentWithoutBuo( - branchEvent: eventCustom!); - */ - showSnackBar( - context: context, message: 'Tracked content'); - }, - ), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: CustomButton( - child: Text('Get First Parameters'), - onPressed: () async { - Map params = - await FlutterBranchSdk.getFirstReferringParams(); - controllerData.sink.add(params.toString()); - showSnackBar( - context: context, - message: 'First Parameters recovered'); - }, - ), - ), - SizedBox( - width: 10, - ), - Expanded( - child: CustomButton( - child: Text('Get Last Parameters'), - onPressed: () async { - Map params = - await FlutterBranchSdk.getLatestReferringParams(); - controllerData.sink.add(params.toString()); - showSnackBar( - context: context, - message: 'Last Parameters recovered'); - }, - ), - ), - ], - ), - CustomButton( - child: Text('Get last Attributed'), - onPressed: getLastAttributed, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Expanded( - child: CustomButton( - child: Text('List on Search'), - onPressed: () async { - if (kIsWeb) { - showSnackBar( - context: context, - message: - 'listOnSearch() not available in Flutter Web'); - return; - } - bool success = - await FlutterBranchSdk.listOnSearch(buo: buo!); - - success = await FlutterBranchSdk.listOnSearch( - buo: buo!, linkProperties: lp); - - if (success) { - showSnackBar( - context: context, message: 'Listed on Search'); - } - }, - ), - ), - SizedBox( - width: 10, - ), - Expanded( - child: CustomButton( - child: Text('Remove from Search'), - onPressed: () async { - if (kIsWeb) { - showSnackBar( - context: context, - message: - 'removeFromSearch() not available in Flutter Web'); - return; - } - bool success = - await FlutterBranchSdk.removeFromSearch( - buo: buo!); - success = await FlutterBranchSdk.removeFromSearch( - buo: buo!, linkProperties: lp); - if (success) { - showSnackBar( - context: context, - message: 'Removed from Search'); - } - }, - ), - ), - ], - ), - CustomButton( - child: Text('Generate Link'), - onPressed: generateLink, - ), - StreamBuilder( - stream: controllerUrl.stream, - initialData: '', - builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data!.isNotEmpty) { - return Column( - children: [ - Center( - child: Text( - 'Link build', - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold), - )), - Center(child: Text(snapshot.data!)) - ], - ); - } else { - return Container(); - } - }, - ), - CustomButton( - child: Text('Share Link'), - onPressed: shareLink, - ), - SizedBox( - height: 10, - ), - Divider(), - Center( - child: Text( - 'Deep Link data', - style: TextStyle( - color: Colors.blue, fontWeight: FontWeight.bold), - ), - ), - Divider(), - StreamBuilder( - stream: controllerData.stream, - initialData: '', - builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data!.isNotEmpty) { - return Column( - children: [ - Center(child: Text(snapshot.data!)), - ], - ); - } else { - return Container(); - } - }, - ), - ], - ), - ), - ), - ), - ); - } - - void generateLink() async { - BranchResponse response = - await FlutterBranchSdk.getShortUrl(buo: buo!, linkProperties: lp); - if (response.success) { - controllerUrl.sink.add('${response.result}'); - } else { - controllerUrl.sink - .add('Error : ${response.errorCode} - ${response.errorMessage}'); - } - } - - void shareLink() async { - BranchResponse response = await FlutterBranchSdk.showShareSheet( - buo: buo!, - linkProperties: lp, - messageText: 'My Share text', - androidMessageTitle: 'My Message Title', - androidSharingTitle: 'My Share with'); - - if (response.success) { - showSnackBar( - context: context, message: 'showShareSheet Success', duration: 5); - } else { - showSnackBar( - context: context, - message: - 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', - duration: 5); - } - } - - void getLastAttributed() async { - BranchResponse response = - await FlutterBranchSdk.getLastAttributedTouchData(); - if (response.success) { - showSnackBar( - context: context, message: response.result.toString(), duration: 5); - } else { - showSnackBar( - context: context, - message: - 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', - duration: 5); - } - } - - @override - void dispose() { - super.dispose(); - controllerData.close(); - controllerUrl.close(); - controllerInitSession.close(); - streamSubscription?.cancel(); - } -} +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; + +import 'custom_button.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + title: "Flutter Branch SDK Example", + debugShowCheckedModeBanner: false, + home: HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + final GlobalKey scaffoldMessengerKey = + GlobalKey(); + + BranchContentMetaData metadata = BranchContentMetaData(); + BranchUniversalObject? buo; + BranchLinkProperties lp = BranchLinkProperties(); + BranchEvent? eventStandart; + BranchEvent? eventCustom; + + StreamSubscription? streamSubscription; + StreamController controllerData = StreamController(); + StreamController controllerInitSession = StreamController(); + StreamController controllerUrl = StreamController(); + + @override + void initState() { + super.initState(); + + listenDynamicLinks(); + + initDeepLinkData(); + + FlutterBranchSdk.setIdentity('branch_user_test'); + + //requestATTTracking(); + } + + void requestATTTracking() async { + AppTrackingStatus status; + status = await FlutterBranchSdk.requestTrackingAuthorization(); + if (kDebugMode) { + print(status); + } + + status = await FlutterBranchSdk.getTrackingAuthorizationStatus(); + if (kDebugMode) { + print(status); + } + + final uuid = await FlutterBranchSdk.getAdvertisingIdentifier(); + if (kDebugMode) { + print(uuid); + } + } + + void listenDynamicLinks() async { + streamSubscription = FlutterBranchSdk.initSession().listen((data) { + print('listenDynamicLinks - DeepLink Data: $data'); + controllerData.sink.add((data.toString())); + if (data.containsKey('+clicked_branch_link') && + data['+clicked_branch_link'] == true) { + print( + '------------------------------------Link clicked----------------------------------------------'); + print('Custom string: ${data['custom_string']}'); + print('Custom number: ${data['custom_number']}'); + print('Custom bool: ${data['custom_bool']}'); + print('Custom list number: ${data['custom_list_number']}'); + print( + '------------------------------------------------------------------------------------------------'); + showSnackBar( + context: context, + message: 'Link clicked: Custom string - ${data['custom_string']}', + duration: 10); + } + }, onError: (error) { + print('InitSesseion error: ${error.toString()}'); + }); + } + + void initDeepLinkData() { + metadata = BranchContentMetaData() + ..addCustomMetadata('custom_string', 'abc') + ..addCustomMetadata('custom_number', 12345) + ..addCustomMetadata('custom_bool', true) + ..addCustomMetadata('custom_list_number', [1, 2, 3, 4, 5]) + ..addCustomMetadata('custom_list_string', ['a', 'b', 'c']) + //--optional Custom Metadata + ..contentSchema = BranchContentSchema.COMMERCE_PRODUCT + ..price = 50.99 + ..currencyType = BranchCurrencyType.BRL + ..quantity = 50 + ..sku = 'sku' + ..productName = 'productName' + ..productBrand = 'productBrand' + ..productCategory = BranchProductCategory.ELECTRONICS + ..productVariant = 'productVariant' + ..condition = BranchCondition.NEW + ..rating = 100 + ..ratingAverage = 50 + ..ratingMax = 100 + ..ratingCount = 2 + ..setAddress( + street: 'street', + city: 'city', + region: 'ES', + country: 'Brazil', + postalCode: '99999-987') + ..setLocation(31.4521685, -114.7352207); + + buo = BranchUniversalObject( + canonicalIdentifier: 'flutter/branch', + //parameter canonicalUrl + //If your content lives both on the web and in the app, make sure you set its canonical URL + // (i.e. the URL of this piece of content on the web) when building any BUO. + // By doing so, we’ll attribute clicks on the links that you generate back to their original web page, + // even if the user goes to the app instead of your website! This will help your SEO efforts. + canonicalUrl: 'https://flutter.dev', + title: 'Flutter Branch Plugin', + imageUrl: + 'https://flutter.dev/assets/flutter-lockup-4cb0ee072ab312e59784d9fbf4fb7ad42688a7fdaea1270ccf6bbf4f34b7e03f.svg', + contentDescription: 'Flutter Branch Description', + /* + contentMetadata: BranchContentMetaData() + ..addCustomMetadata('custom_string', 'abc') + ..addCustomMetadata('custom_number', 12345) + ..addCustomMetadata('custom_bool', true) + ..addCustomMetadata('custom_list_number', [1, 2, 3, 4, 5]) + ..addCustomMetadata('custom_list_string', ['a', 'b', 'c']), + */ + contentMetadata: metadata, + keywords: ['Plugin', 'Branch', 'Flutter'], + publiclyIndex: true, + locallyIndex: true, + expirationDateInMilliSec: DateTime.now() + .add(const Duration(days: 365)) + .millisecondsSinceEpoch); + + lp = BranchLinkProperties( + channel: 'facebook', + feature: 'sharing', + //parameter alias + //Instead of our standard encoded short url, you can specify the vanity alias. + // For example, instead of a random string of characters/integers, you can set the vanity alias as *.app.link/devonaustin. + // Aliases are enforced to be unique** and immutable per domain, and per link - they cannot be reused unless deleted. + //alias: 'https://branch.io' //define link url, + stage: 'new share', + campaign: 'xxxxx', + tags: ['one', 'two', 'three']) + ..addControlParam('\$uri_redirect_mode', '1') + ..addControlParam('referring_user_id', 'asdf'); + + eventStandart = BranchEvent.standardEvent(BranchStandardEvent.ADD_TO_CART) + //--optional Event data + ..transactionID = '12344555' + ..currency = BranchCurrencyType.BRL + ..revenue = 1.5 + ..shipping = 10.2 + ..tax = 12.3 + ..coupon = 'test_coupon' + ..affiliation = 'test_affiliation' + ..eventDescription = 'Event_description' + ..searchQuery = 'item 123' + ..adType = BranchEventAdType.BANNER + ..addCustomData( + 'Custom_Event_Property_Key1', 'Custom_Event_Property_val1') + ..addCustomData( + 'Custom_Event_Property_Key2', 'Custom_Event_Property_val2'); + + eventCustom = BranchEvent.customEvent('Custom_event') + ..addCustomData( + 'Custom_Event_Property_Key1', 'Custom_Event_Property_val1') + ..addCustomData( + 'Custom_Event_Property_Key2', 'Custom_Event_Property_val2'); + } + + void showSnackBar( + {required BuildContext context, + required String message, + int duration = 1}) { + scaffoldMessengerKey.currentState!.removeCurrentSnackBar(); + scaffoldMessengerKey.currentState!.showSnackBar( + SnackBar( + content: Text(message), + duration: Duration(seconds: duration), + ), + ); + } + + @override + Widget build(BuildContext context) { + return ScaffoldMessenger( + key: scaffoldMessengerKey, + child: Scaffold( + appBar: AppBar( + title: const Text('Branch.io Plugin Example App'), + ), + body: Scrollbar( + thumbVisibility: true, + child: SingleChildScrollView( + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + StreamBuilder( + stream: controllerInitSession.stream, + initialData: '', + builder: (context, snapshot) { + if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return Column( + children: [ + Center( + child: Text( + snapshot.data!, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.red), + )) + ], + ); + } else { + return Container(); + } + }, + ), + CustomButton( + child: const Text('Validate SDK Integration'), + onPressed: () { + if (kIsWeb) { + showSnackBar( + context: context, + message: + 'validateSDKIntegration() not available in Flutter Web'); + return; + } + + FlutterBranchSdk.validateSDKIntegration(); + if (Platform.isAndroid) { + showSnackBar( + context: context, + message: 'Check messages in run log or logcat'); + } + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: CustomButton( + child: const Text('Enable tracking'), + onPressed: () { + FlutterBranchSdk.disableTracking(false); + showSnackBar( + context: context, message: 'Tracking enabled'); + }, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: CustomButton( + child: const Text('Disable tracking'), + onPressed: () { + FlutterBranchSdk.disableTracking(true); + showSnackBar( + context: context, message: 'Tracking disabled'); + }, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: CustomButton( + child: const Text('Identify user'), + onPressed: () { + FlutterBranchSdk.setIdentity('branch_user_test'); + showSnackBar( + context: context, + message: 'User branch_user_test identfied'); + }, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: CustomButton( + child: const Text('User logout'), + onPressed: () { + FlutterBranchSdk.logout(); + showSnackBar( + context: context, + message: 'User branch_user_test logout'); + }, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: CustomButton( + child: const Text('Register view'), + onPressed: () { + FlutterBranchSdk.registerView(buo: buo!); + + showSnackBar( + context: context, message: 'Event Registered'); + }, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: CustomButton( + child: const Text('Track content'), + onPressed: () { + //FlutterBranchSdk.trackContent( + // buo: [buo!], branchEvent: eventStandart!); + + FlutterBranchSdk.trackContent( + buo: [buo!], branchEvent: eventCustom!); + + FlutterBranchSdk.trackContentWithoutBuo( + branchEvent: eventStandart!); + FlutterBranchSdk.trackContentWithoutBuo( + branchEvent: eventCustom!); + + showSnackBar( + context: context, message: 'Tracked content'); + }, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: CustomButton( + child: const Text('Get First Parameters'), + onPressed: () async { + Map params = + await FlutterBranchSdk.getFirstReferringParams(); + controllerData.sink.add(params.toString()); + showSnackBar( + context: context, + message: 'First Parameters recovered'); + }, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: CustomButton( + child: const Text('Get Last Parameters'), + onPressed: () async { + Map params = + await FlutterBranchSdk.getLatestReferringParams(); + controllerData.sink.add(params.toString()); + showSnackBar( + context: context, + message: 'Last Parameters recovered'); + }, + ), + ), + ], + ), + CustomButton( + onPressed: getLastAttributed, + child: const Text('Get last Attributed'), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: CustomButton( + child: const Text('List on Search'), + onPressed: () async { + if (kIsWeb) { + showSnackBar( + context: context, + message: + 'listOnSearch() not available in Flutter Web'); + return; + } + bool success = + await FlutterBranchSdk.listOnSearch(buo: buo!); + + success = await FlutterBranchSdk.listOnSearch( + buo: buo!, linkProperties: lp); + + if (success) { + showSnackBar( + context: context, message: 'Listed on Search'); + } + }, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: CustomButton( + child: const Text('Remove from Search'), + onPressed: () async { + if (kIsWeb) { + showSnackBar( + context: context, + message: + 'removeFromSearch() not available in Flutter Web'); + return; + } + bool success = + await FlutterBranchSdk.removeFromSearch( + buo: buo!); + success = await FlutterBranchSdk.removeFromSearch( + buo: buo!, linkProperties: lp); + if (success) { + showSnackBar( + context: context, + message: 'Removed from Search'); + } + }, + ), + ), + ], + ), + CustomButton( + onPressed: generateLink, + child: const Text('Generate Link'), + ), + StreamBuilder( + stream: controllerUrl.stream, + initialData: '', + builder: (context, snapshot) { + if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return Column( + children: [ + const Center( + child: Text( + 'Link build', + style: TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold), + )), + Center( + child: GestureDetector( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: snapshot.data!)); + showSnackBar( + context: context, + message: 'Copied to Clipboard', + duration: 2); + }, + child: Text(snapshot.data!), + )) + ], + ); + } else { + return Container(); + } + }, + ), + CustomButton( + onPressed: shareLink, + child: const Text('Share Link'), + ), + const SizedBox( + height: 10, + ), + const Divider(), + const Center( + child: Text( + 'Deep Link data', + style: TextStyle( + color: Colors.blue, fontWeight: FontWeight.bold), + ), + ), + const Divider(), + StreamBuilder( + stream: controllerData.stream, + initialData: '', + builder: (context, snapshot) { + if (snapshot.hasData && snapshot.data!.isNotEmpty) { + return Column( + children: [ + Center(child: Text(snapshot.data!)), + ], + ); + } else { + return Container(); + } + }, + ), + ], + ), + ), + ), + ), + ); + } + + void generateLink() async { + BranchResponse response = + await FlutterBranchSdk.getShortUrl(buo: buo!, linkProperties: lp); + if (response.success) { + controllerUrl.sink.add('${response.result}'); + } else { + controllerUrl.sink + .add('Error : ${response.errorCode} - ${response.errorMessage}'); + } + } + + void shareLink() async { + BranchResponse response = await FlutterBranchSdk.showShareSheet( + buo: buo!, + linkProperties: lp, + messageText: 'My Share text', + androidMessageTitle: 'My Message Title', + androidSharingTitle: 'My Share with'); + + if (response.success) { + showSnackBar( + context: context, message: 'showShareSheet Success', duration: 5); + } else { + showSnackBar( + context: context, + message: + 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', + duration: 5); + } + } + + void getLastAttributed() async { + BranchResponse response = + await FlutterBranchSdk.getLastAttributedTouchData(); + if (response.success) { + showSnackBar( + context: context, message: response.result.toString(), duration: 5); + } else { + showSnackBar( + context: context, + message: + 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', + duration: 5); + } + } + + @override + void dispose() { + super.dispose(); + controllerData.close(); + controllerUrl.close(); + controllerInitSession.close(); + streamSubscription?.cancel(); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index c7baa79f..80008c16 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -49,7 +49,7 @@ packages: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.5" fake_async: dependency: transitive description: @@ -63,19 +63,19 @@ packages: source: sdk version: "0.0.0" flutter_branch_sdk: - dependency: "direct dev" + dependency: "direct main" description: path: ".." relative: true source: path - version: "5.1.0" + version: "6.0.0" flutter_lints: - dependency: transitive + dependency: "direct dev" description: name: flutter_lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -99,7 +99,7 @@ packages: name: lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" matcher: dependency: transitive description: @@ -134,7 +134,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.2" sky_engine: dependency: transitive description: flutter @@ -190,5 +190,5 @@ packages: source: hosted version: "2.1.2" sdks: - dart: ">=2.17.0-0 <3.0.0" - flutter: ">=1.22.0" + dart: ">=2.17.3 <3.0.0" + flutter: ">=2.5.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 7a13e348..e401bea9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,63 +1,84 @@ -name: flutter_branch_sdk_example -description: Demonstrates how to use the flutter_branch_sdk plugin. -publish_to: 'none' - -environment: - sdk: ">=2.12.0-0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - -dev_dependencies: - flutter_test: - sdk: flutter - - flutter_branch_sdk: - path: ../ - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages +name: flutter_branch_sdk_example +description: Demonstrates how to use the flutter_branch_sdk plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=2.17.3 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + flutter_branch_sdk: + # When depending on this package from a real application you should use: + # flutter_branch_sdk: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/example/web/index.html b/example/web/index.html index a3d6b244..c41563fc 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -1,54 +1,68 @@ - - - - - - - - - - - - - - - - - - - - flutter_branch_sdk_example - - - - - - - - - + + + + + + + + + + + + + + + + + + + + flutter_branch_sdk_example + + + + + + + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json index 7e4b4461..20445697 100644 --- a/example/web/manifest.json +++ b/example/web/manifest.json @@ -1,23 +1,35 @@ -{ - "name": "flutter_branch_sdk_example", - "short_name": "flutter_branch_sdk_example", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "Demonstrates how to use the flutter_branch_sdk plugin.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - } - ] -} +{ + "name": "flutter_branch_sdk_example", + "short_name": "flutter_branch_sdk_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Demonstrates how to use the flutter_branch_sdk plugin.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/ios/.gitignore b/ios/.gitignore index aa479fd3..f6ebbf64 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,37 +1,38 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 - -!default.pbxuser -!default.mode1v3 -!default.mode2v3 -!default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/Generated.xcconfig +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ /Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift index 2b9cb17d..71cfc554 100644 --- a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift +++ b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift @@ -191,15 +191,6 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream case "validateSDKIntegration": validateSDKIntegration() break - case "loadRewards": - loadRewards(call: call, result: result) - break - case "redeemRewards": - redeemRewards(call: call, result: result) - break - case "getCreditHistory": - getCreditHistory(call: call, result: result) - break case "isUserIdentified": isUserIdentified(result: result) break @@ -283,6 +274,9 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream if let err = (error as NSError?) { response["errorCode"] = String(err.code) response["errorMessage"] = err.localizedDescription + } else { + response["errorCode"] = "-999" + response["errorMessage"] = "No message" } } DispatchQueue.main.async { @@ -433,114 +427,6 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream } } - private func loadRewards(call: FlutterMethodCall, result: @escaping FlutterResult) { - let args = call.arguments as! [String: Any?] - let response : NSMutableDictionary! = [:] - - Branch.getInstance().loadRewards { (changed, error) in - if (error == nil) { - var credits : Int = 0 - if let bucket = args["bucket"] as? String { - credits = Branch.getInstance().getCreditsForBucket(bucket) - } else { - credits = Branch.getInstance().getCredits() - } - response["success"] = NSNumber(value: true) - response["credits"] = credits - } else { - let err = (error! as NSError) - response["success"] = NSNumber(value: false) - response["errorCode"] = String(err.code) - response["errorMessage"] = err.localizedDescription - } - DispatchQueue.main.async { - result(response) - } - } - } - - private func redeemRewards(call: FlutterMethodCall, result: @escaping FlutterResult) { - let args = call.arguments as! [String: Any?] - let count = args["count"] as! Int - let response : NSMutableDictionary! = [:] - - if let bucket = args["bucket"] as? String { - Branch.getInstance().redeemRewards(count, forBucket: bucket, callback: {(success, error) in - if success { - response["success"] = NSNumber(value: true) - } - else { - print("Failed to redeem credits: \(String(describing: error))") - let err = (error! as NSError) - response["success"] = NSNumber(value: false) - response["errorCode"] = String(err.code) - response["errorMessage"] = err.localizedDescription - } - DispatchQueue.main.async { - result(response) - } - }) - } else { - Branch.getInstance().redeemRewards(count, callback: {(success, error) in - if success { - response["success"] = NSNumber(value: true) - } - else { - print("Failed to redeem credits: \(String(describing: error))") - let err = (error! as NSError) - response["success"] = NSNumber(value: false) - response["errorCode"] = String(err.code) - response["errorMessage"] = err.localizedDescription - } - DispatchQueue.main.async { - result(response) - } - }) - } - } - - private func getCreditHistory(call: FlutterMethodCall, result: @escaping FlutterResult) { - let args = call.arguments as! [String: Any?] - let response : NSMutableDictionary! = [:] - let data : NSMutableDictionary! = [:] - - if let bucket = args["bucket"] as? String { - Branch.getInstance().getCreditHistory(forBucket: bucket, andCallback: { (creditHistory, error) in - if error == nil { - data["history"] = creditHistory - response["success"] = NSNumber(value: true) - response["data"] = data - } else { - print("Failed to redeem credits: \(String(describing: error))") - let err = (error! as NSError) - response["success"] = NSNumber(value: false) - response["errorCode"] = String(err.code) - response["errorMessage"] = err.localizedDescription - } - DispatchQueue.main.async { - result(response) - } - }) - } else { - Branch.getInstance().getCreditHistory { (creditHistory, error) in - if error == nil { - data["history"] = creditHistory - response["success"] = NSNumber(value: true) - response["data"] = data - } else { - print("Failed to redeem credits: \(String(describing: error))") - let err = (error! as NSError) - response["success"] = NSNumber(value: false) - response["errorCode"] = String(err.code) - response["errorMessage"] = err.localizedDescription - } - DispatchQueue.main.async { - result(response) - } - } - } - } - private func getLastAttributedTouchData(call: FlutterMethodCall, result: @escaping FlutterResult) { let args = call.arguments as! [String: Any?] diff --git a/ios/flutter_branch_sdk.podspec b/ios/flutter_branch_sdk.podspec index 2cb40fce..7882649d 100644 --- a/ios/flutter_branch_sdk.podspec +++ b/ios/flutter_branch_sdk.podspec @@ -1,24 +1,24 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint flutter_branch_sdk.podspec' to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'flutter_branch_sdk' - s.version = '3.0.0' - s.summary = 'Flutter Plugin for Brach Metrics SDK - https://branch.io' - s.description = <<-DESC -Flutter Plugin for Brach Metrics SDK - https://branch.io - DESC - s.homepage = 'https://github.com/RodrigoSMarques/flutter_branch_sdk' - s.license = { :file => '../LICENSE' } - s.author = { 'Rodrigo S. Marques' => 'rodrigosmarques@gmail.com' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'Flutter' - s.dependency 'Branch', '~> 1.42.0' - s.platform = :ios, '9.0' - - # Flutter.framework does not contain a i386 slice. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } - s.swift_version = '5.0' -end +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint flutter_branch_sdk.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'flutter_branch_sdk' + s.version = '3.0.0' + s.summary = 'Flutter Plugin for create deep link using Brach SDK (https://branch.io). This plugin provides a cross-platform (iOS, Android, Web).' + s.description = <<-DESC +Flutter Plugin for create deep link using Brach SDK (https://branch.io). This plugin provides a cross-platform (iOS, Android, Web). + DESC + s.homepage = 'https://github.com/RodrigoSMarques/flutter_branch_sdk' + s.license = { :file => '../LICENSE' } + s.author = { 'Rodrigo S. Marques' => 'rodrigosmarques@gmail.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.dependency 'Branch', '~> 1.42.0' + s.platform = :ios, '9.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/lib/flutter_branch_sdk.dart b/lib/flutter_branch_sdk.dart index 640a6003..224e4fb8 100644 --- a/lib/flutter_branch_sdk.dart +++ b/lib/flutter_branch_sdk.dart @@ -1,9 +1,10 @@ -library flutter_branch_sdk; - -import 'src/app_tracking_transparency.dart'; -import 'src/flutter_branch_sdk_platform_interface.dart'; - -export 'src/app_tracking_transparency.dart'; -export 'src/branch_universal_object.dart'; - -part 'src/flutter_branch_sdk_plugin.dart'; +library flutter_branch_sdk; + +import 'src/flutter_branch_sdk_platform_interface.dart'; +import 'src/objects/app_tracking_transparency.dart'; +import 'src/objects/branch_universal_object.dart'; + +export 'src/objects/app_tracking_transparency.dart'; +export 'src/objects/branch_universal_object.dart'; + +part 'src/flutter_branch_sdk.dart'; diff --git a/lib/src/branch_event.dart b/lib/src/branch_event.dart deleted file mode 100644 index 3086a3ff..00000000 --- a/lib/src/branch_event.dart +++ /dev/null @@ -1,121 +0,0 @@ -part of flutter_branch_sdk_objects; -/* -* Enum for creating Branch events for tracking and analytical purpose. -* Enum class represent a standard or custom BranchEvents. Standard Branch events are defined with BRANCH_STANDARD_EVENT}. -* Please use #logEvent() method to log the events for tracking. -*/ - -enum BranchStandardEvent { - // Commerce events - ADD_TO_CART, - ADD_TO_WISHLIST, - VIEW_CART, - INITIATE_PURCHASE, - ADD_PAYMENT_INFO, - PURCHASE, - SPEND_CREDITS, - // Content Events - SEARCH, - VIEW_ITEM, - VIEW_ITEMS, - RATE, - SHARE, - // User Lifecycle Events - COMPLETE_REGISTRATION, - COMPLETE_TUTORIAL, - ACHIEVE_LEVEL, - UNLOCK_ACHIEVEMENT -} - -String getBranchStandardEventString(BranchStandardEvent branchStandardEvent) { - return branchStandardEvent.toString().split('.').last; -} - -enum BranchEventAdType { BANNER, INTERSTITIAL, REWARDED_VIDEO, NATIVE } - -String getBranchEventAdTypeString(BranchEventAdType branchEventAdType) { - return branchEventAdType.toString().split('.').last; -} - -class BranchEvent { - String _eventName = ''; - bool _isStandardEvent = true; - String transactionID = ''; - BranchCurrencyType? currency; - double revenue = -1; - double shipping = -1; - double tax = -1; - String coupon = ''; - String affiliation = ''; - String eventDescription = ''; - String searchQuery = ''; - BranchEventAdType? adType; - Map _customData = {}; - - BranchEvent.standardEvent(BranchStandardEvent branchStandardEvent) { - this._eventName = getBranchStandardEventString(branchStandardEvent); - this._isStandardEvent = true; - } - - BranchEvent.customEvent(this._eventName) { - this._isStandardEvent = false; - } - - String get eventName => _eventName; - bool get isStandardEvent => _isStandardEvent; - - void addCustomData(String key, dynamic value) { - this._customData[key] = value; - } - - void removeCustomData(String key) { - this._customData.remove(key); - } - - Map toMap() { - Map ret = Map(); - - ret["eventName"] = this._eventName; - ret["isStandardEvent"] = this._isStandardEvent; - if (this.transactionID.isNotEmpty) - ret["transactionID"] = this.transactionID; - if (this.currency != null) - ret["currency"] = getCurrencyTypeString(this.currency!); - if (this.revenue != -1) ret["revenue"] = this.revenue; - if (this.shipping != -1) ret["shipping"] = this.shipping; - if (this.tax != -1) ret["tax"] = this.tax; - if (this.coupon.isNotEmpty) ret["coupon"] = this.coupon; - if (this.affiliation.isNotEmpty) ret["affiliation"] = this.affiliation; - if (this.eventDescription.isNotEmpty) - ret["eventDescription"] = this.eventDescription; - if (this.searchQuery.isNotEmpty) ret["searchQuery"] = this.searchQuery; - if (this.adType != null) - ret["adType"] = getBranchEventAdTypeString(this.adType!); - if (this._customData.isNotEmpty) ret["customData"] = _customData; - return ret; - } - - Map toMapWeb() { - Map ret = Map(); - if (this._isStandardEvent) { - if (this.transactionID.isNotEmpty) - ret["transactionID"] = this.transactionID; - if (this.currency != null) - ret["currency"] = getCurrencyTypeString(this.currency!); - if (this.revenue != -1) ret["revenue"] = this.revenue; - if (this.shipping != -1) ret["shipping"] = this.shipping; - if (this.tax != -1) ret["tax"] = this.tax; - if (this.coupon.isNotEmpty) ret["coupon"] = this.coupon; - if (this.affiliation.isNotEmpty) ret["affiliation"] = this.affiliation; - if (this.eventDescription.isNotEmpty) - ret["eventDescription"] = this.eventDescription; - if (this.searchQuery.isNotEmpty) ret["searchQuery"] = this.searchQuery; - if (this.adType != null) - ret["adType"] = getBranchEventAdTypeString(this.adType!); - } - this._customData.forEach((key, value) { - ret['$key'] = value; - }); - return ret; - } -} diff --git a/lib/src/flutter_branch_sdk_plugin.dart b/lib/src/flutter_branch_sdk.dart similarity index 69% rename from lib/src/flutter_branch_sdk_plugin.dart rename to lib/src/flutter_branch_sdk.dart index 30fc7a56..68b7bf95 100644 --- a/lib/src/flutter_branch_sdk_plugin.dart +++ b/lib/src/flutter_branch_sdk.dart @@ -1,203 +1,174 @@ -part of flutter_branch_sdk; - -class FlutterBranchSdk { - static FlutterBranchSdkPlatform? __platform; - - static FlutterBranchSdkPlatform get _platform { - __platform ??= FlutterBranchSdkPlatform.instance; - return __platform!; - } - - @Deprecated('version 5.0.0') - static void initWeb({required String branchKey}) { - _platform.initWeb(branchKey: branchKey); - } - - ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value - static void setIdentity(String userId) { - _platform.setIdentity(userId); - } - - ///Add key value pairs to all requests - static void setRequestMetadata(String key, String value) { - _platform.setRequestMetadata(key, value); - } - - ///This method should be called if you know that a different person is about to use the app - static void logout() { - _platform.logout(); - } - - ///Returns the last parameters associated with the link that referred the user - static Future> getLatestReferringParams() async { - return await _platform.getLatestReferringParams(); - } - - ///Returns the first parameters associated with the link that referred the user - static Future> getFirstReferringParams() async { - return await _platform.getFirstReferringParams(); - } - - ///Method to change the Tracking state. If disabled SDK will not track any user data or state. - ///SDK will not send any network calls except for deep linking when tracking is disabled - static void disableTracking(bool value) async { - return _platform.disableTracking(value); - } - - ///Initialises a session with the Branch API - ///Listen click em Branch Deeplinks - static Stream> initSession() { - return _platform.initSession(); - } - - ///Use the SDK integration validator to check that you've added the Branch SDK and - ///handle deep links correctly when you first integrate Branch into your app. - static void validateSDKIntegration() { - _platform.validateSDKIntegration(); - } - - ///Creates a short url for the BUO - static Future getShortUrl( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties}) async { - return _platform.getShortUrl(buo: buo, linkProperties: linkProperties); - } - - ///Showing a Share Sheet - static Future showShareSheet( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties, - required String messageText, - String androidMessageTitle = '', - String androidSharingTitle = ''}) async { - return _platform.showShareSheet( - buo: buo, - linkProperties: linkProperties, - messageText: messageText, - androidMessageTitle: androidMessageTitle, - androidSharingTitle: androidSharingTitle); - } - - ///Logs this BranchEvent to Branch for tracking and analytics - static void trackContent( - {required List buo, - required BranchEvent branchEvent}) { - return _platform.trackContent(buo: buo, branchEvent: branchEvent); - } - - ///Logs this BranchEvent to Branch for tracking and analytics - static void trackContentWithoutBuo({required BranchEvent branchEvent}) { - return _platform.trackContentWithoutBuo(branchEvent: branchEvent); - } - - ///Mark the content referred by this object as viewed. This increment the view count of the contents referred by this object. - static void registerView({required BranchUniversalObject buo}) { - return _platform.registerView(buo: buo); - } - - ///For Android: Publish this BUO with Google app indexing so that the contents will be available with google search - ///For iOS: List items on Spotlight - static Future listOnSearch( - {required BranchUniversalObject buo, - BranchLinkProperties? linkProperties}) async { - return _platform.listOnSearch(buo: buo, linkProperties: linkProperties); - } - - ///For Android: Remove the BUO from the local indexing if it is added to the local indexing already - /// This will remove the content from Google(Firebase) and other supported Indexing services - ///For iOS: Remove Branch Universal Object from Spotlight if privately indexed - static Future removeFromSearch( - {required BranchUniversalObject buo, - BranchLinkProperties? linkProperties}) async { - return _platform.removeFromSearch(buo: buo, linkProperties: linkProperties); - } - - ///Retrieves rewards for the current user/session - @Deprecated('version 4.0.0') - static Future loadRewards({String bucket = 'default'}) async { - return _platform.loadRewards(bucket: bucket); - } - - ///Redeems the specified number of credits. if there are sufficient credits within it. - ///If the number to redeem exceeds the number available in the bucket, all of the - ///available credits will be redeemed instead. - @Deprecated('version 4.0.0') - static Future redeemRewards( - {required int count, String bucket = 'default'}) async { - return _platform.redeemRewards(count: count, bucket: bucket); - } - - ///Gets the credit history - @Deprecated('version 4.0.0') - static Future getCreditHistory( - {String bucket = 'default'}) async { - return _platform.getCreditHistory(bucket: bucket); - } - - ///Set time window for SKAdNetwork callouts in Hours (Only iOS) - ///By default, Branch limits calls to SKAdNetwork to within 72 hours after first install. - static void setIOSSKAdNetworkMaxTime(int hours) { - return _platform.setIOSSKAdNetworkMaxTime(hours); - } - - ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. - ///If you call setIdentity, this device will have that identity associated with this user until logout is called. - ///This includes persisting through uninstalls, as we track device id. - static Future isUserIdentified() async { - return _platform.isUserIdentified(); - } - - /// request AppTracking Autorization and return AppTrackingStatus - /// on Android returns notSupported - static Future requestTrackingAuthorization() async { - return _platform.requestTrackingAuthorization(); - } - - /// return AppTrackingStatus - /// on Android returns notSupported - static Future getTrackingAuthorizationStatus() async { - return _platform.getTrackingAuthorizationStatus(); - } - - /// return advertising identifier (ie tracking data). - /// on Android returns empty string - static Future getAdvertisingIdentifier() async { - return _platform.getAdvertisingIdentifier(); - } - - ///Sets the duration in milliseconds that the system should wait for initializing - ///a network * request. - static void setConnectTimeout(int connectTimeout) { - return _platform.setConnectTimeout(connectTimeout); - } - - ///Sets the duration in milliseconds that the system should wait for a response - ///before timing out any Branch API. - ///Default 5500 ms. Note that this is the total time allocated for all request - ///retries as set in setRetryCount(int). - static void setTimeout(int timeout) { - return _platform.setTimeout(timeout); - } - - ///Sets the max number of times to re-attempt a timed-out request to the Branch API, before - /// considering the request to have failed entirely. Default to 3. - /// Note that the the network timeout, as set in setNetworkTimeout(int), - /// together with the retry interval value from setRetryInterval(int) will - /// determine if the max retry count will be attempted. - static void setRetryCount(int retryCount) { - return _platform.setRetryCount(retryCount); - } - - ///Sets the amount of time in milliseconds to wait before re-attempting a - ///timed-out request to the Branch API. Default 1000 ms. - static void setRetryInterval(int retryInterval) { - return _platform.setRetryInterval(retryInterval); - } - - ///Gets the available last attributed touch data with a custom set attribution window. - static Future getLastAttributedTouchData( - {int? attributionWindow}) async { - return _platform.getLastAttributedTouchData( - attributionWindow: attributionWindow); - } -} +part of flutter_branch_sdk; + +class FlutterBranchSdk { + ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value + static void setIdentity(String userId) { + FlutterBranchSdkPlatform.instance.setIdentity(userId); + } + + ///Add key value pairs to all requests + static void setRequestMetadata(String key, String value) { + FlutterBranchSdkPlatform.instance.setRequestMetadata(key, value); + } + + ///This method should be called if you know that a different person is about to use the app + static void logout() { + FlutterBranchSdkPlatform.instance.logout(); + } + + ///Returns the last parameters associated with the link that referred the user + static Future> getLatestReferringParams() async { + return await FlutterBranchSdkPlatform.instance.getLatestReferringParams(); + } + + ///Returns the first parameters associated with the link that referred the user + static Future> getFirstReferringParams() async { + return await FlutterBranchSdkPlatform.instance.getFirstReferringParams(); + } + + ///Method to change the Tracking state. If disabled SDK will not track any user data or state. + ///SDK will not send any network calls except for deep linking when tracking is disabled + static void disableTracking(bool value) async { + return FlutterBranchSdkPlatform.instance.disableTracking(value); + } + + ///Initialises a session with the Branch API + ///Listen click em Branch Deeplinks + static Stream> initSession() { + return FlutterBranchSdkPlatform.instance.initSession(); + } + + ///Use the SDK integration validator to check that you've added the Branch SDK and + ///handle deep links correctly when you first integrate Branch into your app. + static void validateSDKIntegration() { + FlutterBranchSdkPlatform.instance.validateSDKIntegration(); + } + + ///Creates a short url for the BUO + static Future getShortUrl( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties}) async { + return FlutterBranchSdkPlatform.instance + .getShortUrl(buo: buo, linkProperties: linkProperties); + } + + ///Showing a Share Sheet + static Future showShareSheet( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required String messageText, + String androidMessageTitle = '', + String androidSharingTitle = ''}) async { + return FlutterBranchSdkPlatform.instance.showShareSheet( + buo: buo, + linkProperties: linkProperties, + messageText: messageText, + androidMessageTitle: androidMessageTitle, + androidSharingTitle: androidSharingTitle); + } + + ///Logs this BranchEvent to Branch for tracking and analytics + static void trackContent( + {required List buo, + required BranchEvent branchEvent}) { + return FlutterBranchSdkPlatform.instance + .trackContent(buo: buo, branchEvent: branchEvent); + } + + ///Logs this BranchEvent to Branch for tracking and analytics + static void trackContentWithoutBuo({required BranchEvent branchEvent}) { + return FlutterBranchSdkPlatform.instance + .trackContentWithoutBuo(branchEvent: branchEvent); + } + + ///Mark the content referred by this object as viewed. This increment the view count of the contents referred by this object. + static void registerView({required BranchUniversalObject buo}) { + return FlutterBranchSdkPlatform.instance.registerView(buo: buo); + } + + ///For Android: Publish this BUO with Google app indexing so that the contents will be available with google search + ///For iOS: List items on Spotlight + static Future listOnSearch( + {required BranchUniversalObject buo, + BranchLinkProperties? linkProperties}) async { + return FlutterBranchSdkPlatform.instance + .listOnSearch(buo: buo, linkProperties: linkProperties); + } + + ///For Android: Remove the BUO from the local indexing if it is added to the local indexing already + /// This will remove the content from Google(Firebase) and other supported Indexing services + ///For iOS: Remove Branch Universal Object from Spotlight if privately indexed + static Future removeFromSearch( + {required BranchUniversalObject buo, + BranchLinkProperties? linkProperties}) async { + return FlutterBranchSdkPlatform.instance + .removeFromSearch(buo: buo, linkProperties: linkProperties); + } + + ///Set time window for SKAdNetwork callouts in Hours (Only iOS) + ///By default, Branch limits calls to SKAdNetwork to within 72 hours after first install. + static void setIOSSKAdNetworkMaxTime(int hours) { + return FlutterBranchSdkPlatform.instance.setIOSSKAdNetworkMaxTime(hours); + } + + ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. + ///If you call setIdentity, this device will have that identity associated with this user until logout is called. + ///This includes persisting through uninstalls, as we track device id. + static Future isUserIdentified() async { + return FlutterBranchSdkPlatform.instance.isUserIdentified(); + } + + /// request AppTracking Autorization and return AppTrackingStatus + /// on Android returns notSupported + static Future requestTrackingAuthorization() async { + return FlutterBranchSdkPlatform.instance.requestTrackingAuthorization(); + } + + /// return AppTrackingStatus + /// on Android returns notSupported + static Future getTrackingAuthorizationStatus() async { + return FlutterBranchSdkPlatform.instance.getTrackingAuthorizationStatus(); + } + + /// return advertising identifier (ie tracking data). + /// on Android returns empty string + static Future getAdvertisingIdentifier() async { + return FlutterBranchSdkPlatform.instance.getAdvertisingIdentifier(); + } + + ///Sets the duration in milliseconds that the system should wait for initializing + ///a network * request. + static void setConnectTimeout(int connectTimeout) { + return FlutterBranchSdkPlatform.instance.setConnectTimeout(connectTimeout); + } + + ///Sets the duration in milliseconds that the system should wait for a response + ///before timing out any Branch API. + ///Default 5500 ms. Note that this is the total time allocated for all request + ///retries as set in setRetryCount(int). + static void setTimeout(int timeout) { + return FlutterBranchSdkPlatform.instance.setTimeout(timeout); + } + + ///Sets the max number of times to re-attempt a timed-out request to the Branch API, before + /// considering the request to have failed entirely. Default to 3. + /// Note that the the network timeout, as set in setNetworkTimeout(int), + /// together with the retry interval value from setRetryInterval(int) will + /// determine if the max retry count will be attempted. + static void setRetryCount(int retryCount) { + return FlutterBranchSdkPlatform.instance.setRetryCount(retryCount); + } + + ///Sets the amount of time in milliseconds to wait before re-attempting a + ///timed-out request to the Branch API. Default 1000 ms. + static void setRetryInterval(int retryInterval) { + return FlutterBranchSdkPlatform.instance.setRetryInterval(retryInterval); + } + + ///Gets the available last attributed touch data with a custom set attribution window. + static Future getLastAttributedTouchData( + {int? attributionWindow}) async { + return FlutterBranchSdkPlatform.instance + .getLastAttributedTouchData(attributionWindow: attributionWindow); + } +} diff --git a/lib/src/flutter_branch_sdk_mobile.dart b/lib/src/flutter_branch_sdk_method_channel.dart similarity index 51% rename from lib/src/flutter_branch_sdk_mobile.dart rename to lib/src/flutter_branch_sdk_method_channel.dart index 37d7a201..dcc0c953 100644 --- a/lib/src/flutter_branch_sdk_mobile.dart +++ b/lib/src/flutter_branch_sdk_method_channel.dart @@ -1,384 +1,275 @@ -import 'dart:io'; - -import 'package:flutter/services.dart'; - -import 'app_tracking_transparency.dart'; -import 'flutter_branch_sdk_platform_interface.dart'; - -class FlutterBranchSdkMobile implements FlutterBranchSdkPlatform { - static const _MESSAGE_CHANNEL = 'flutter_branch_sdk/message'; - static const _EVENT_CHANNEL = 'flutter_branch_sdk/event'; - - static const MethodChannel _messageChannel = - const MethodChannel(_MESSAGE_CHANNEL); - - static const EventChannel _eventChannel = const EventChannel(_EVENT_CHANNEL); - - static Stream? _initSessionStream; - - static FlutterBranchSdkMobile? _singleton; - - /// Constructs a singleton instance of [FlutterBranchSdkMobile]. - factory FlutterBranchSdkMobile() { - if (_singleton == null) { - _singleton = FlutterBranchSdkMobile._(); - } - return _singleton!; - } - - FlutterBranchSdkMobile._(); - - ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value - - @Deprecated('version 5.0.0') - @override - void initWeb({required String branchKey}) { - //nothing - } - - ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value - @override - void setIdentity(String userId) { - Map _params = {}; - _params['userId'] = userId; - _messageChannel.invokeMethod('setIdentity', _params); - } - - ///Add key value pairs to all requests - @override - void setRequestMetadata(String key, String value) { - Map _params = {}; - _params['key'] = key; - _params['value'] = value; - - _messageChannel.invokeMethod('setRequestMetadata', _params); - } - - ///This method should be called if you know that a different person is about to use the app - @override - void logout() { - _messageChannel.invokeMethod('logout'); - } - - ///Returns the last parameters associated with the link that referred the user - @override - Future> getLatestReferringParams() async { - return await _messageChannel.invokeMethod('getLatestReferringParams'); - } - - ///Returns the first parameters associated with the link that referred the user - @override - Future> getFirstReferringParams() async { - return await _messageChannel.invokeMethod('getFirstReferringParams'); - } - - ///Method to change the Tracking state. If disabled SDK will not track any user data or state. - ///SDK will not send any network calls except for deep linking when tracking is disabled - @override - void disableTracking(bool value) async { - Map _params = {}; - _params['disable'] = value; - _messageChannel.invokeMethod('setTrackingDisabled', _params); - } - - ///Initialises a session with the Branch API - ///Listen click em Branch Deeplinks - @override - Stream> initSession() { - if (_initSessionStream == null) - _initSessionStream = - _eventChannel.receiveBroadcastStream().cast>(); - - return _initSessionStream!; - } - - ///Use the SDK integration validator to check that you've added the Branch SDK and - ///handle deep links correctly when you first integrate Branch into your app. - @override - void validateSDKIntegration() { - _messageChannel.invokeMethod('validateSDKIntegration'); - } - - ///Creates a short url for the BUO - @override - Future getShortUrl( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties}) async { - Map _params = {}; - _params['buo'] = buo.toMap(); - _params['lp'] = linkProperties.toMap(); - - Map response = - await _messageChannel.invokeMethod('getShortUrl', _params); - - if (response['success']) { - return BranchResponse.success(result: response['url']); - } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); - } - } - - ///Showing a Share Sheet - @override - Future showShareSheet( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties, - required String messageText, - String androidMessageTitle = '', - String androidSharingTitle = ''}) async { - Map _params = {}; - _params['buo'] = buo.toMap(); - _params['lp'] = linkProperties.toMap(); - _params['messageText'] = messageText; - _params['messageTitle'] = androidMessageTitle; - _params['sharingTitle'] = androidSharingTitle; - - Map response = - await _messageChannel.invokeMethod('showShareSheet', _params); - - if (response['success']) { - return BranchResponse.success(result: response['url']); - } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); - } - } - - ///Logs this BranchEvent to Branch for tracking and analytics - @override - void trackContent( - {required List buo, - required BranchEvent branchEvent}) { - Map _params = {}; - - _params['buo'] = buo.map((b) => b.toMap()).toList(); - if (branchEvent.toMap().isNotEmpty) { - _params['event'] = branchEvent.toMap(); - } - _messageChannel.invokeMethod('trackContent', _params); - } - - ///Logs this BranchEvent to Branch for tracking and analytics - @override - void trackContentWithoutBuo({required BranchEvent branchEvent}) { - Map _params = {}; - - if (branchEvent.toMap().isEmpty) { - throw ArgumentError('branchEvent is required'); - } - _params['event'] = branchEvent.toMap(); - - _messageChannel.invokeMethod('trackContentWithoutBuo', _params); - } - - ///Mark the content referred by this object as viewed. This increment the view count of the contents referred by this object. - @override - void registerView({required BranchUniversalObject buo}) { - Map _params = {}; - - _params['buo'] = buo.toMap(); - - _messageChannel.invokeMethod('registerView', _params); - } - - ///For Android: Publish this BUO with Google app indexing so that the contents will be available with google search - ///For iOS: List items on Spotlight - @override - Future listOnSearch( - {required BranchUniversalObject buo, - BranchLinkProperties? linkProperties}) async { - Map _params = {}; - - _params['buo'] = buo.toMap(); - if (linkProperties != null && linkProperties.toMap().isNotEmpty) { - _params['lp'] = linkProperties.toMap(); - } - - return await _messageChannel.invokeMethod('listOnSearch', _params); - } - - ///For Android: Remove the BUO from the local indexing if it is added to the local indexing already - /// This will remove the content from Google(Firebase) and other supported Indexing services - ///For iOS: Remove Branch Universal Object from Spotlight if privately indexed - @override - Future removeFromSearch( - {required BranchUniversalObject buo, - BranchLinkProperties? linkProperties}) async { - Map _params = {}; - _params['buo'] = buo.toMap(); - if (linkProperties != null && linkProperties.toMap().isNotEmpty) { - _params['lp'] = linkProperties.toMap(); - } - return await _messageChannel.invokeMethod('removeFromSearch', _params); - } - - ///Retrieves rewards for the current user/session - @override - Future loadRewards({String bucket = 'default'}) async { - Map _params = {}; - _params['bucket'] = bucket; - - Map response = - await _messageChannel.invokeMethod('loadRewards', _params); - - if (response['success']) { - return BranchResponse.success(result: response['credits']); - } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); - } - } - - ///Redeems the specified number of credits. if there are sufficient credits within it. - ///If the number to redeem exceeds the number available in the bucket, all of the - ///available credits will be redeemed instead. - @override - Future redeemRewards( - {required int count, String bucket = 'default'}) async { - Map _params = {}; - _params['count'] = count; - _params['bucket'] = bucket; - - Map response = - await _messageChannel.invokeMethod('redeemRewards', _params); - - if (response['success']) { - return BranchResponse.success(result: true); - } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); - } - } - - ///Gets the credit history - @override - Future getCreditHistory({String bucket = 'default'}) async { - Map _params = {}; - _params['bucket'] = bucket; - - Map response = - await _messageChannel.invokeMethod('getCreditHistory', _params); - - print('GetCreditHistory ${response.toString()}'); - - if (response['success']) { - return BranchResponse.success(result: response['data']['history']); - } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); - } - } - - ///Set time window for SKAdNetwork callouts in Hours (Only iOS) - ///By default, Branch limits calls to SKAdNetwork to within 72 hours after first install. - @override - void setIOSSKAdNetworkMaxTime(int hours) { - if (!Platform.isIOS) { - return; - } - - Map _params = {}; - _params['maxTimeInterval'] = hours; - _messageChannel.invokeMethod('setSKAdNetworkMaxTime', _params); - } - - ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. - ///If you call setIdentity, this device will have that identity associated with this user until logout is called. - ///This includes persisting through uninstalls, as we track device id. - @override - Future isUserIdentified() async { - return await _messageChannel.invokeMethod('isUserIdentified'); - } - - /// request AppTracking Autorization and return AppTrackingStatus - /// on Android returns notSupported - @override - Future requestTrackingAuthorization() async { - if (!Platform.isIOS) { - return AppTrackingStatus.notSupported; - } - final int status = (await _messageChannel - .invokeMethod('requestTrackingAuthorization'))!; - return AppTrackingStatus.values[status]; - } - - /// return AppTrackingStatus - /// on Android returns notSupported - @override - Future getTrackingAuthorizationStatus() async { - if (!Platform.isIOS) { - return AppTrackingStatus.notSupported; - } - final int status = (await _messageChannel - .invokeMethod('getTrackingAuthorizationStatus'))!; - return AppTrackingStatus.values[status]; - } - - /// return advertising identifier (ie tracking data). - /// on Android returns empty string - @override - Future getAdvertisingIdentifier() async { - if (!Platform.isIOS) { - return ""; - } - - final String uuid = (await _messageChannel - .invokeMethod('getAdvertisingIdentifier'))!; - return uuid; - } - - @override - void setConnectTimeout(int connectTimeout) { - Map _params = {}; - _params['connectTimeout'] = connectTimeout; - _messageChannel.invokeMethod('setConnectTimeout', _params); - } - - @override - void setRetryCount(int retryCount) { - Map _params = {}; - _params['retryCount'] = retryCount; - _messageChannel.invokeMethod('setRetryCount', _params); - } - - @override - void setRetryInterval(int retryInterval) { - Map _params = {}; - _params['retryInterval'] = retryInterval; - _messageChannel.invokeMethod('setRetryInterval', _params); - } - - @override - void setTimeout(int timeout) { - Map _params = {}; - _params['timeout'] = timeout; - _messageChannel.invokeMethod('setTimeout', _params); - } - - @override - Future getLastAttributedTouchData( - {int? attributionWindow}) async { - Map params = {}; - - if (attributionWindow != null) { - params['attributionWindow'] = attributionWindow; - } - - Map response = await _messageChannel.invokeMethod( - 'getLastAttributedTouchData', params); - - if (response['success']) { - return BranchResponse.success(result: response['data']['latd']); - } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); - } - } -} +import 'dart:io'; + +import 'package:flutter/services.dart'; + +import 'flutter_branch_sdk_platform_interface.dart'; +import 'objects/app_tracking_transparency.dart'; +import 'objects/branch_universal_object.dart'; + +/// An implementation of [FlutterBranchSdkPlatform] that uses method channels. +class FlutterBranchSdkMethodChannel extends FlutterBranchSdkPlatform { + static const MESSAGE_CHANNEL = 'flutter_branch_sdk/message'; + static const EVENT_CHANNEL = 'flutter_branch_sdk/event'; + + /// The method channel used to interact with the native platform. + final messageChannel = const MethodChannel(MESSAGE_CHANNEL); + final eventChannel = const EventChannel(EVENT_CHANNEL); + + static Stream>? _initSessionStream; + + ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value + @override + void setIdentity(String userId) { + messageChannel.invokeMethod('setIdentity', {'userId': userId}); + } + + ///Add key value pairs to all requests + @override + void setRequestMetadata(String key, String value) { + messageChannel + .invokeMethod('setRequestMetadata', {'key': key, 'value': value}); + } + + ///This method should be called if you know that a different person is about to use the app + @override + void logout() { + messageChannel.invokeMethod('logout'); + } + + ///Returns the last parameters associated with the link that referred the user + @override + Future> getLatestReferringParams() async { + return await messageChannel.invokeMethod('getLatestReferringParams'); + } + + ///Returns the first parameters associated with the link that referred the user + @override + Future> getFirstReferringParams() async { + return await messageChannel.invokeMethod('getFirstReferringParams'); + } + + ///Method to change the Tracking state. If disabled SDK will not track any user data or state. + ///SDK will not send any network calls except for deep linking when tracking is disabled + @override + void disableTracking(bool value) async { + messageChannel.invokeMethod('setTrackingDisabled', {'disable': value}); + } + + ///Initialises a session with the Branch API + ///Listen click em Branch Deeplinks + @override + Stream> initSession() { + _initSessionStream ??= + eventChannel.receiveBroadcastStream().cast>(); + + return _initSessionStream!; + } + + ///Use the SDK integration validator to check that you've added the Branch SDK and + ///handle deep links correctly when you first integrate Branch into your app. + @override + void validateSDKIntegration() { + messageChannel.invokeMethod('validateSDKIntegration'); + } + + ///Creates a short url for the BUO + @override + Future getShortUrl( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties}) async { + Map response = await messageChannel.invokeMethod( + 'getShortUrl', {'buo': buo.toMap(), 'lp': linkProperties.toMap()}); + + if (response['success']) { + return BranchResponse.success(result: response['url']); + } else { + return BranchResponse.error( + errorCode: response['errorCode'], + errorMessage: response['errorMessage']); + } + } + + ///Showing a Share Sheet + @override + Future showShareSheet( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required String messageText, + String androidMessageTitle = '', + String androidSharingTitle = ''}) async { + Map params = {}; + params['buo'] = buo.toMap(); + params['lp'] = linkProperties.toMap(); + params['messageText'] = messageText; + params['messageTitle'] = androidMessageTitle; + params['sharingTitle'] = androidSharingTitle; + + Map response = + await messageChannel.invokeMethod('showShareSheet', params); + + if (response['success']) { + return BranchResponse.success(result: response['url']); + } else { + return BranchResponse.error( + errorCode: response['errorCode'], + errorMessage: response['errorMessage']); + } + } + + ///Logs this BranchEvent to Branch for tracking and analytics + @override + void trackContent( + {required List buo, + required BranchEvent branchEvent}) { + Map params = {}; + + params['buo'] = buo.map((b) => b.toMap()).toList(); + if (branchEvent.toMap().isNotEmpty) { + params['event'] = branchEvent.toMap(); + } + messageChannel.invokeMethod('trackContent', params); + } + + ///Logs this BranchEvent to Branch for tracking and analytics + @override + void trackContentWithoutBuo({required BranchEvent branchEvent}) { + if (branchEvent.toMap().isEmpty) { + throw ArgumentError('branchEvent is required'); + } + messageChannel + .invokeMethod('trackContentWithoutBuo', {'event': branchEvent.toMap()}); + } + + ///Mark the content referred by this object as viewed. This increment the view count of the contents referred by this object. + @override + void registerView({required BranchUniversalObject buo}) { + messageChannel.invokeMethod('registerView', {'buo': buo.toMap()}); + } + + ///For Android: Publish this BUO with Google app indexing so that the contents will be available with google search + ///For iOS: List items on Spotlight + @override + Future listOnSearch( + {required BranchUniversalObject buo, + BranchLinkProperties? linkProperties}) async { + Map params = {}; + params['buo'] = buo.toMap(); + if (linkProperties != null && linkProperties.toMap().isNotEmpty) { + params['lp'] = linkProperties.toMap(); + } + return await messageChannel.invokeMethod('listOnSearch', params); + } + + ///For Android: Remove the BUO from the local indexing if it is added to the local indexing already + /// This will remove the content from Google(Firebase) and other supported Indexing services + ///For iOS: Remove Branch Universal Object from Spotlight if privately indexed + @override + Future removeFromSearch( + {required BranchUniversalObject buo, + BranchLinkProperties? linkProperties}) async { + Map params = {}; + params['buo'] = buo.toMap(); + if (linkProperties != null && linkProperties.toMap().isNotEmpty) { + params['lp'] = linkProperties.toMap(); + } + return await messageChannel.invokeMethod('removeFromSearch', params); + } + + ///Set time window for SKAdNetwork callouts in Hours (Only iOS) + ///By default, Branch limits calls to SKAdNetwork to within 72 hours after first install. + @override + void setIOSSKAdNetworkMaxTime(int hours) { + if (!Platform.isIOS) { + return; + } + messageChannel + .invokeMethod('setSKAdNetworkMaxTime', {'maxTimeInterval': hours}); + } + + ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. + ///If you call setIdentity, this device will have that identity associated with this user until logout is called. + ///This includes persisting through uninstalls, as we track device id. + @override + Future isUserIdentified() async { + return await messageChannel.invokeMethod('isUserIdentified'); + } + + /// request AppTracking Autorization and return AppTrackingStatus + /// on Android returns notSupported + @override + Future requestTrackingAuthorization() async { + if (!Platform.isIOS) { + return AppTrackingStatus.notSupported; + } + final int status = (await messageChannel + .invokeMethod('requestTrackingAuthorization'))!; + return AppTrackingStatus.values[status]; + } + + /// return AppTrackingStatus + /// on Android returns notSupported + @override + Future getTrackingAuthorizationStatus() async { + if (!Platform.isIOS) { + return AppTrackingStatus.notSupported; + } + final int status = (await messageChannel + .invokeMethod('getTrackingAuthorizationStatus'))!; + return AppTrackingStatus.values[status]; + } + + /// return advertising identifier (ie tracking data). + /// on Android returns empty string + @override + Future getAdvertisingIdentifier() async { + if (!Platform.isIOS) { + return ""; + } + final String uuid = (await messageChannel + .invokeMethod('getAdvertisingIdentifier'))!; + return uuid; + } + + @override + void setConnectTimeout(int connectTimeout) { + messageChannel + .invokeMethod('setConnectTimeout', {'connectTimeout': connectTimeout}); + } + + @override + void setRetryCount(int retryCount) { + messageChannel.invokeMethod('setRetryCount', {'retryCount': retryCount}); + } + + @override + void setRetryInterval(int retryInterval) { + messageChannel + .invokeMethod('setRetryInterval', {'retryInterval': retryInterval}); + } + + @override + void setTimeout(int timeout) { + messageChannel.invokeMethod('setTimeout', {'timeout': timeout}); + } + + @override + Future getLastAttributedTouchData( + {int? attributionWindow}) async { + Map params = {}; + + if (attributionWindow != null) { + params['attributionWindow'] = attributionWindow; + } + + Map response = + await messageChannel.invokeMethod('getLastAttributedTouchData', params); + + if (response['success']) { + return BranchResponse.success(result: response['data']['latd']); + } else { + return BranchResponse.error( + errorCode: response['errorCode'], + errorMessage: response['errorMessage']); + } + } +} diff --git a/lib/src/flutter_branch_sdk_platform_interface.dart b/lib/src/flutter_branch_sdk_platform_interface.dart index 647b1e9a..9f5e7f45 100644 --- a/lib/src/flutter_branch_sdk_platform_interface.dart +++ b/lib/src/flutter_branch_sdk_platform_interface.dart @@ -1,222 +1,198 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'app_tracking_transparency.dart'; -import 'branch_universal_object.dart'; -import 'flutter_branch_sdk_mobile.dart'; - -export 'branch_universal_object.dart'; - -/// The interface that all implementations of flutter_branch_sdk must -/// implement. -abstract class FlutterBranchSdkPlatform extends PlatformInterface { - /// Constructs an instance of [FlutterBranchSdkPlatform]. - FlutterBranchSdkPlatform() : super(token: _token); - - static FlutterBranchSdkPlatform _instance = FlutterBranchSdkMobile(); - - static final Object _token = Object(); - - /// The default instance of [FlutterLocalNotificationsPlatform] to use. - static FlutterBranchSdkPlatform get instance => _instance; - - /// Platform-specific plugins should set this with their own platform-specific - /// class that extends [FlutterBranchSdkPlatform] when they register - /// themselves. - static set instance(FlutterBranchSdkPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - @Deprecated('version 5.0.0') - void initWeb({required String branchKey}) { - throw UnimplementedError('initWeb has not been implemented'); - } - - ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value - void setIdentity(String userId) { - throw UnimplementedError('setIdentity has not been implemented'); - } - - ///Add key value pairs to all requests - void setRequestMetadata(String key, String value) { - throw UnimplementedError('setRequestMetadata has not been implemented'); - } - - ///This method should be called if you know that a different person is about to use the app - void logout() { - throw UnimplementedError('logout has not been implemented'); - } - - ///Returns the last parameters associated with the link that referred the user - Future> getLatestReferringParams() async { - throw UnimplementedError( - 'getLatestReferringParams has not been implemented'); - } - - ///Returns the first parameters associated with the link that referred the user - Future> getFirstReferringParams() async { - throw UnimplementedError( - 'getFirstReferringParams has not been implemented'); - } - - ///Method to change the Tracking state. If disabled SDK will not track any user data or state. - ///SDK will not send any network calls except for deep linking when tracking is disabled - void disableTracking(bool value) async { - throw UnimplementedError('disableTracking has not been implemented'); - } - - ///Initialises a session with the Branch API - ///Listen click em Branch Deeplinks - Stream> initSession() { - throw UnimplementedError('initSession has not been implemented'); - } - - ///Use the SDK integration validator to check that you've added the Branch SDK and - ///handle deep links correctly when you first integrate Branch into your app. - void validateSDKIntegration() { - throw UnimplementedError('validateSDKIntegration has not been implemented'); - } - - ///Creates a short url for the BUO - Future getShortUrl( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties}) async { - throw UnimplementedError('getShortUrl has not been implemented'); - } - - ///Showing a Share Sheet - Future showShareSheet( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties, - required String messageText, - String androidMessageTitle = '', - String androidSharingTitle = ''}) async { - throw UnimplementedError('showShareSheet has not been implemented'); - } - - ///Logs this BranchEvent to Branch for tracking and analytics - void trackContent( - {required List buo, - required BranchEvent branchEvent}) { - throw UnimplementedError('trackContent has not been implemented'); - } - - ///Logs this BranchEvent to Branch for tracking and analytics - void trackContentWithoutBuo({required BranchEvent branchEvent}) { - throw UnimplementedError('trackContentWithoutBuo has not been implemented'); - } - - ///Mark the content referred by this object as viewed. This increment the view count of the contents referred by this object. - void registerView({required BranchUniversalObject buo}) { - throw UnimplementedError('registerView has not been implemented'); - } - - ///For Android: Publish this BUO with Google app indexing so that the contents will be available with google search - ///For iOS: List items on Spotlight - Future listOnSearch( - {required BranchUniversalObject buo, - BranchLinkProperties? linkProperties}) async { - throw UnimplementedError('listOnSearch has not been implemented'); - } - - ///For Android: Remove the BUO from the local indexing if it is added to the local indexing already - /// This will remove the content from Google(Firebase) and other supported Indexing services - ///For iOS: Remove Branch Universal Object from Spotlight if privately indexed - Future removeFromSearch( - {required BranchUniversalObject buo, - BranchLinkProperties? linkProperties}) async { - throw UnimplementedError('removeFromSearch has not been implemented'); - } - - ///Retrieves rewards for the current user/session - @Deprecated('version 4.0.0') - Future loadRewards({String bucket = 'default'}) async { - throw UnimplementedError('loadRewards has not been implemented'); - } - - ///Redeems the specified number of credits. if there are sufficient credits within it. - ///If the number to redeem exceeds the number available in the bucket, all of the - ///available credits will be redeemed instead. - @Deprecated('version 4.0.0') - Future redeemRewards( - {required int count, String bucket = 'default'}) async { - throw UnimplementedError('redeemRewards has not been implemented'); - } - - ///Gets the credit history - @Deprecated('version 4.0.0') - Future getCreditHistory({String bucket = 'default'}) async { - throw UnimplementedError('getCreditHistory has not been implemented'); - } - - ///Set time window for SKAdNetwork callouts in Hours (Only iOS) - ///By default, Branch limits calls to SKAdNetwork to within 72 hours after first install. - void setIOSSKAdNetworkMaxTime(int hours) { - throw UnimplementedError( - 'setIOSSKAdNetworkMaxTime has not been implemented'); - } - - ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. - ///If you call setIdentity, this device will have that identity associated with this user until logout is called. - ///This includes persisting through uninstalls, as we track device id. - Future isUserIdentified() async { - throw UnimplementedError('isUserIdentified has not been implemented'); - } - - /// request AppTracking Autorization and return AppTrackingStatus - /// on Android returns notSupported - Future requestTrackingAuthorization() async { - throw UnimplementedError( - 'requestTrackingAuthorization has not been implemented'); - } - - /// return AppTrackingStatus - /// on Android returns notSupported - Future getTrackingAuthorizationStatus() async { - throw UnimplementedError( - 'getTrackingAuthorizationStatus has not been implemented'); - } - - /// return advertising identifier (ie tracking data). - /// on Android returns empty string - Future getAdvertisingIdentifier() async { - throw UnimplementedError( - 'getAdvertisingIdentifier has not been implemented'); - } - - ///Sets the duration in milliseconds that the system should wait for initializing - ///a network * request. - void setConnectTimeout(int connectTimeout) { - throw UnimplementedError('setConnectTimeout has not been implemented'); - } - - ///Sets the duration in milliseconds that the system should wait for a response - ///before timing out any Branch API. - ///Default 5500 ms. Note that this is the total time allocated for all request - ///retries as set in setRetryCount(int). - void setTimeout(int timeout) { - throw UnimplementedError('setTimeout has not been implemented'); - } - - ///Sets the max number of times to re-attempt a timed-out request to the Branch API, before - /// considering the request to have failed entirely. Default to 3. - /// Note that the the network timeout, as set in setNetworkTimeout(int), - /// together with the retry interval value from setRetryInterval(int) will - /// determine if the max retry count will be attempted. - void setRetryCount(int retryCount) { - throw UnimplementedError('setRetryCount has not been implemented'); - } - - ///Sets the amount of time in milliseconds to wait before re-attempting a - ///timed-out request to the Branch API. Default 1000 ms. - void setRetryInterval(int retryInterval) { - throw UnimplementedError('setRetryInterval has not been implemented'); - } - - ///Gets the available last attributed touch data with a custom set attribution window. - Future getLastAttributedTouchData( - {int? attributionWindow}) async { - throw UnimplementedError( - 'getLastAttributedTouchData has not been implemented'); - } -} +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'flutter_branch_sdk_method_channel.dart'; +import 'objects/app_tracking_transparency.dart'; +import 'objects/branch_universal_object.dart'; + +abstract class FlutterBranchSdkPlatform extends PlatformInterface { + /// Constructs a FlutterBranchSdkPlatform. + FlutterBranchSdkPlatform() : super(token: _token); + + static final Object _token = Object(); + + static FlutterBranchSdkPlatform _instance = FlutterBranchSdkMethodChannel(); + + /// The default instance of [FlutterBranchSdkPlatform] to use. + /// + /// Defaults to [FlutterBranchSdkMethodChannel]. + static FlutterBranchSdkPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [FlutterBranchSdkPlatform] when + /// they register themselves. + static set instance(FlutterBranchSdkPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } + + ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value + void setIdentity(String userId) { + throw UnimplementedError('setIdentity has not been implemented'); + } + + ///Add key value pairs to all requests + void setRequestMetadata(String key, String value) { + throw UnimplementedError('setRequestMetadata has not been implemented'); + } + + ///This method should be called if you know that a different person is about to use the app + void logout() { + throw UnimplementedError('logout has not been implemented'); + } + + ///Returns the last parameters associated with the link that referred the user + Future> getLatestReferringParams() async { + throw UnimplementedError( + 'getLatestReferringParams has not been implemented'); + } + + ///Returns the first parameters associated with the link that referred the user + Future> getFirstReferringParams() async { + throw UnimplementedError( + 'getFirstReferringParams has not been implemented'); + } + + ///Method to change the Tracking state. If disabled SDK will not track any user data or state. + ///SDK will not send any network calls except for deep linking when tracking is disabled + void disableTracking(bool value) async { + throw UnimplementedError('disableTracking has not been implemented'); + } + + ///Initialises a session with the Branch API + ///Listen click em Branch Deeplinks + Stream> initSession() { + throw UnimplementedError('initSession has not been implemented'); + } + + ///Use the SDK integration validator to check that you've added the Branch SDK and + ///handle deep links correctly when you first integrate Branch into your app. + void validateSDKIntegration() { + throw UnimplementedError('validateSDKIntegration has not been implemented'); + } + + ///Creates a short url for the BUO + Future getShortUrl( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties}) async { + throw UnimplementedError('getShortUrl has not been implemented'); + } + + ///Showing a Share Sheet + Future showShareSheet( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required String messageText, + String androidMessageTitle = '', + String androidSharingTitle = ''}) async { + throw UnimplementedError('showShareSheet has not been implemented'); + } + + ///Logs this BranchEvent to Branch for tracking and analytics + void trackContent( + {required List buo, + required BranchEvent branchEvent}) { + throw UnimplementedError('trackContent has not been implemented'); + } + + ///Logs this BranchEvent to Branch for tracking and analytics + void trackContentWithoutBuo({required BranchEvent branchEvent}) { + throw UnimplementedError('trackContentWithoutBuo has not been implemented'); + } + + ///Mark the content referred by this object as viewed. This increment the view count of the contents referred by this object. + void registerView({required BranchUniversalObject buo}) { + throw UnimplementedError('registerView has not been implemented'); + } + + ///For Android: Publish this BUO with Google app indexing so that the contents will be available with google search + ///For iOS: List items on Spotlight + Future listOnSearch( + {required BranchUniversalObject buo, + BranchLinkProperties? linkProperties}) async { + throw UnimplementedError('listOnSearch has not been implemented'); + } + + ///For Android: Remove the BUO from the local indexing if it is added to the local indexing already + /// This will remove the content from Google(Firebase) and other supported Indexing services + ///For iOS: Remove Branch Universal Object from Spotlight if privately indexed + Future removeFromSearch( + {required BranchUniversalObject buo, + BranchLinkProperties? linkProperties}) async { + throw UnimplementedError('removeFromSearch has not been implemented'); + } + + ///Set time window for SKAdNetwork callouts in Hours (Only iOS) + ///By default, Branch limits calls to SKAdNetwork to within 72 hours after first install. + void setIOSSKAdNetworkMaxTime(int hours) { + throw UnimplementedError( + 'setIOSSKAdNetworkMaxTime has not been implemented'); + } + + ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. + ///If you call setIdentity, this device will have that identity associated with this user until logout is called. + ///This includes persisting through uninstalls, as we track device id. + Future isUserIdentified() async { + throw UnimplementedError('isUserIdentified has not been implemented'); + } + + /// request AppTracking Autorization and return AppTrackingStatus + /// on Android returns notSupported + Future requestTrackingAuthorization() async { + throw UnimplementedError( + 'requestTrackingAuthorization has not been implemented'); + } + + /// return AppTrackingStatus + /// on Android returns notSupported + Future getTrackingAuthorizationStatus() async { + throw UnimplementedError( + 'getTrackingAuthorizationStatus has not been implemented'); + } + + /// return advertising identifier (ie tracking data). + /// on Android returns empty string + Future getAdvertisingIdentifier() async { + throw UnimplementedError( + 'getAdvertisingIdentifier has not been implemented'); + } + + ///Sets the duration in milliseconds that the system should wait for initializing + ///a network * request. + void setConnectTimeout(int connectTimeout) { + throw UnimplementedError('setConnectTimeout has not been implemented'); + } + + ///Sets the duration in milliseconds that the system should wait for a response + ///before timing out any Branch API. + ///Default 5500 ms. Note that this is the total time allocated for all request + ///retries as set in setRetryCount(int). + void setTimeout(int timeout) { + throw UnimplementedError('setTimeout has not been implemented'); + } + + ///Sets the max number of times to re-attempt a timed-out request to the Branch API, before + /// considering the request to have failed entirely. Default to 3. + /// Note that the the network timeout, as set in setNetworkTimeout(int), + /// together with the retry interval value from setRetryInterval(int) will + /// determine if the max retry count will be attempted. + void setRetryCount(int retryCount) { + throw UnimplementedError('setRetryCount has not been implemented'); + } + + ///Sets the amount of time in milliseconds to wait before re-attempting a + ///timed-out request to the Branch API. Default 1000 ms. + void setRetryInterval(int retryInterval) { + throw UnimplementedError('setRetryInterval has not been implemented'); + } + + ///Gets the available last attributed touch data with a custom set attribution window. + Future getLastAttributedTouchData( + {int? attributionWindow}) async { + throw UnimplementedError( + 'getLastAttributedTouchData has not been implemented'); + } +} diff --git a/lib/src/flutter_branch_sdk_web.dart b/lib/src/flutter_branch_sdk_web.dart index 4924d43a..c2e963d4 100644 --- a/lib/src/flutter_branch_sdk_web.dart +++ b/lib/src/flutter_branch_sdk_web.dart @@ -1,482 +1,385 @@ -import 'dart:async'; -import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:js'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:js_util'; - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -import 'app_tracking_transparency.dart'; -import 'flutter_branch_sdk_platform_interface.dart'; -import 'web/branch_js.dart'; - -/// A workaround to deep-converting an object from JS to a Dart Object. -dynamic _jsObjectToDartObject(data) => json.decode(jsonStringify(data)); - -dynamic _dartObjectToJsObject(data) => jsonParse(json.encode(data)); -Map _metaData = {}; - -/// A web implementation of the FlutterBranchSdk plugin. -class FlutterBranchSdk extends FlutterBranchSdkPlatform { - static FlutterBranchSdk? _singleton; - - /// Constructs a singleton instance of [MethodChannelFlutterBranchSdk]. - factory FlutterBranchSdk() { - if (_singleton == null) { - _singleton = FlutterBranchSdk._(); - } - return _singleton!; - } - - FlutterBranchSdk._(); - - /// Registers this class as the default instance of [SharePlatform]. - static void registerWith(Registrar registrar) { - FlutterBranchSdkPlatform.instance = FlutterBranchSdk(); - } - - static final StreamController> _initSessionStream = - StreamController>(); - static bool _userIdentified = false; - - @Deprecated('version 5.0.0') - @override - void initWeb({required String branchKey}) {} - - ///Initialises a session with the Branch API - ///Listen click em Branch Deeplinks - @override - Stream> initSession() { - getLatestReferringParams().then((data) { - if (data.isNotEmpty) { - _initSessionStream.sink - .add(data.map((key, value) => MapEntry('$key', value))); - } else { - _initSessionStream.sink.add({}); - } - }); - - return _initSessionStream.stream; - } - - ///Returns the last parameters associated with the link that referred the user, not really applicaple for web though - @override - Future> getLatestReferringParams() { - final Completer> response = Completer(); - - try { - BranchJS.data(allowInterop((err, data) { - if (err == null) { - if (data != null) { - var responseData = - Map.from(_jsObjectToDartObject(data)); - response.complete(responseData['data_parsed'] ?? {}); - } else { - response.complete({}); - } - } else { - response.completeError(err); - } - })); - } catch (e) { - print('getLatestReferringParams() error: $e'); - response.completeError(e); - } - return response.future; - } - - ///Returns the first parameters associated with the link that referred the user - @override - Future> getFirstReferringParams() { - final Completer> response = - Completer>(); - - try { - BranchJS.first(allowInterop((err, data) { - if (err == null) { - if (data != null) { - var responseData = - Map.from(_jsObjectToDartObject(data)); - response.complete(responseData['data_parsed'] ?? {}); - } else { - response.complete({}); - } - } else { - response.completeError(err); - } - })); - } catch (e) { - print('getFirstReferringParams() error: $e'); - response.completeError(e); - } - return response.future; - } - - ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value - @override - void setIdentity(String userId) { - try { - BranchJS.setIdentity(userId, allowInterop((error, data) { - if (error == null) { - _userIdentified = true; - } - })); - } catch (e) { - print('setIdentity() error: $e'); - } - } - - ///This method should be called if you know that a different person is about to use the app - @override - void logout() { - try { - BranchJS.logout(allowInterop((error) { - if (error == null) { - _userIdentified = false; - } - })); - } catch (e) { - print('logout() error: $e'); - } - } - - ///Method to change the Tracking state. If disabled SDK will not track any user data or state. - ///SDK will not send any network calls except for deep linking when tracking is disabled - @override - void disableTracking(bool value) { - try { - BranchJS.disableTracking(value); - } catch (e) { - print('disableTracking() error: $e'); - } - } - - ///Creates a short url for the BUO - @override - Future getShortUrl( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties}) async { - Map data = buo.toMapWeb(); - linkProperties.getControlParams().forEach((key, value) { - data['$key'] = value; - }); - - Map linkData = { - ...linkProperties.toMapWeb(), - 'data': data - }; - - Completer responseCompleter = Completer(); - - try { - BranchJS.link(_dartObjectToJsObject(linkData), allowInterop((err, url) { - if (err == null) { - responseCompleter.complete(BranchResponse.success(result: url)); - } else { - responseCompleter.completeError( - BranchResponse.error(errorCode: '-1', errorMessage: err)); - } - })); - } catch (e) { - print('getShortUrl() error: $e'); - responseCompleter.completeError(BranchResponse.error( - errorCode: '-1', errorMessage: 'getShortUrl() error')); - } - return responseCompleter.future; - } - - ///Showing a Share Sheet - Implemented via navigator share if available, otherwise browser prompt. - @override - Future showShareSheet( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties, - required String messageText, - String androidMessageTitle = '', - String androidSharingTitle = ''}) async { - BranchResponse response = - await getShortUrl(buo: buo, linkProperties: linkProperties); - if (response.success) { - try { - await promiseToFuture(navigatorShare(_dartObjectToJsObject({ - "title": messageText, - "text": buo.title, - "url": response.result - }))); - } catch (e) { - browserPrompt(messageText, response.result); - } - } - return response; - } - - ///Logs this BranchEvent to Branch for tracking and analytics - @override - void trackContent( - {required List buo, - required BranchEvent branchEvent}) { - JsArray contentItems = JsArray(); - - buo.forEach((element) { - contentItems.add(_dartObjectToJsObject(element.toMapWeb())); - }); - - try { - BranchJS.logEvent(branchEvent.eventName, - _dartObjectToJsObject(branchEvent.toMapWeb()), contentItems); - } catch (e) { - print('trackContent() error: $e'); - } - } - - ///Logs this BranchEvent to Branch for tracking and analytics - @override - void trackContentWithoutBuo({required BranchEvent branchEvent}) { - try { - BranchJS.logEvent( - branchEvent.eventName, _dartObjectToJsObject(branchEvent.toMapWeb())); - } catch (e) { - print('trackContentWithoutBuo() error: $e'); - } - } - - ///Mark the content referred by this object as viewed. This increment the view count of the contents referred by this object. - @override - void registerView({required BranchUniversalObject buo}) { - BranchEvent branchEvent = - BranchEvent.standardEvent(BranchStandardEvent.VIEW_ITEM); - - // This might not be exactly the same thing as BUO.registerView, but there's no clear implementation for web sdk - trackContent(buo: [buo], branchEvent: branchEvent); - } - - ///Add key value pairs to all requests - @override - void setRequestMetadata(String key, String value) { - _metaData[key] = value; - } - - ///For Android: Publish this BUO with Google app indexing so that the contents will be available with google search - ///For iOS: List items on Spotlight - @override - Future listOnSearch( - {required BranchUniversalObject buo, - BranchLinkProperties? linkProperties}) async { - throw UnsupportedError('listOnSearch() Not supported by Branch JS SDK'); - } - - ///For Android: Remove the BUO from the local indexing if it is added to the local indexing already - /// This will remove the content from Google(Firebase) and other supported Indexing services - ///For iOS: Remove Branch Universal Object from Spotlight if privately indexed - @override - Future removeFromSearch( - {required BranchUniversalObject buo, - BranchLinkProperties? linkProperties}) async { - throw UnsupportedError('removeFromSearch() Not supported by Branch JS SDK'); - } - - ///Retrieves rewards for the current user/session - @Deprecated('version 4.0.0') - @override - Future loadRewards({String bucket = 'default'}) async { - Completer responseCompleter = Completer(); - - try { - BranchJS.credits(allowInterop((err, data) { - if (err == null) { - var parsedData = Map.from(_jsObjectToDartObject(data)); - if (parsedData.isNotEmpty) { - responseCompleter.complete(BranchResponse.success( - result: parsedData.containsKey(bucket) - ? parsedData[bucket] - : parsedData['default'])); - } else { - responseCompleter.complete(BranchResponse.success(result: 0)); - } - } else { - responseCompleter.complete( - BranchResponse.error(errorCode: '999', errorMessage: err)); - } - })); - } catch (e) { - print('loadRewards() error: $e'); - responseCompleter.complete(BranchResponse.error( - errorCode: '-1', errorMessage: 'loadRewards() error')); - } - return responseCompleter.future; - } - - ///Redeems the specified number of credits. if there are sufficient credits within it. - ///If the number to redeem exceeds the number available in the bucket, all of the - ///available credits will be redeemed instead. - @Deprecated('version 4.0.0') - @override - Future redeemRewards( - {required int count, String bucket = 'default'}) async { - Completer responseCompleter = Completer(); - - try { - BranchJS.redeem(count, bucket, allowInterop((err) { - if (err == null) { - responseCompleter.complete(BranchResponse.success(result: true)); - } else { - responseCompleter.complete(BranchResponse.error( - errorCode: '999', errorMessage: err.toString())); - } - })); - } catch (e) { - print('redeemRewards() error: $e'); - responseCompleter.complete(BranchResponse.error( - errorCode: '-1', errorMessage: 'redeemRewards() error')); - } - return responseCompleter.future; - } - - ///Gets the credit history - @Deprecated('version 4.0.0') - @override - Future getCreditHistory({String bucket = 'default'}) async { - Completer responseCompleter = Completer(); - - try { - BranchJS.creditHistory(_dartObjectToJsObject({'bucket': bucket}), - allowInterop((err, data) { - if (err == null) { - if (data != null) { - responseCompleter.complete( - BranchResponse.success(result: _jsObjectToDartObject(data))); - } else { - responseCompleter.complete(BranchResponse.success(result: {})); - } - } else { - responseCompleter.complete(BranchResponse.error( - errorCode: '999', errorMessage: err.toString())); - } - })); - } catch (e) { - print('getCreditHistory() error: $e'); - responseCompleter.complete(BranchResponse.error( - errorCode: '-1', errorMessage: 'getCreditHistory() error')); - } - - return responseCompleter.future; - } - - ///Set time window for SKAdNetwork callouts in Hours (Only iOS) - ///By default, Branch limits calls to SKAdNetwork to within 72 hours after first install. - @override - void setIOSSKAdNetworkMaxTime(int hours) { - throw UnsupportedError( - 'setIOSSKAdNetworkMaxTime() Not available in Branch JS SDK'); - } - - ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. - ///If you call setIdentity, this device will have that identity associated with this user until logout is called. - ///This includes persisting through uninstalls, as we track device id. - // NOTE: This is not really accurate for persistent checks... - @override - Future isUserIdentified() async { - return Future.value(_userIdentified); - } - - /// request AppTracking Autorization and return AppTrackingStatus - /// on Android returns notSupported - @override - Future requestTrackingAuthorization() async { - throw UnsupportedError( - 'requestTrackingAuthorization() Not available in Branch JS SDK'); - } - - /// return AppTrackingStatus - /// on Android returns notSupported - @override - Future getTrackingAuthorizationStatus() async { - throw UnsupportedError( - 'getTrackingAuthorizationStatus() Not available in Branch JS SDK'); - } - - /// return advertising identifier (ie tracking data). - /// on Android returns empty string - @override - Future getAdvertisingIdentifier() async { - throw UnsupportedError( - 'getAdvertisingIdentifier() Not available in Branch JS SDK'); - } - - ///Use the SDK integration validator to check that you've added the Branch SDK and - ///handle deep links correctly when you first integrate Branch into your app. - @override - void validateSDKIntegration() { - throw UnsupportedError( - 'validateSDKIntegration() not available in Branch JS SDK'); - } - - ///Sets the duration in milliseconds that the system should wait for initializing - ///a network * request. - @override - void setConnectTimeout(int connectTimeout) { - throw UnsupportedError( - 'setConnectTimeout() Not available in Branch JS SDK'); - } - - ///Sets the duration in milliseconds that the system should wait for a response - ///before timing out any Branch API. - ///Default 5500 ms. Note that this is the total time allocated for all request - ///retries as set in setRetryCount(int). - @override - void setTimeout(int timeout) { - throw UnsupportedError('setTimeout() Not available in Branch JS SDK'); - } - - ///Sets the max number of times to re-attempt a timed-out request to the Branch API, before - /// considering the request to have failed entirely. Default to 3. - /// Note that the the network timeout, as set in setNetworkTimeout(int), - /// together with the retry interval value from setRetryInterval(int) will - /// determine if the max retry count will be attempted. - @override - void setRetryCount(int retryCount) { - throw UnsupportedError('setRetryCount() Not available in Branch JS SDK'); - } - - ///Sets the amount of time in milliseconds to wait before re-attempting a - ///timed-out request to the Branch API. Default 1000 ms. - @override - void setRetryInterval(int retryInterval) { - throw UnsupportedError('setRetryInterval() Not available in Branch JS SDK'); - } - - ///Gets the available last attributed touch data with a custom set attribution window. - @override - Future getLastAttributedTouchData( - {int? attributionWindow}) async { - Completer responseCompleter = Completer(); - - try { - BranchJS.lastAttributedTouchData(attributionWindow, - allowInterop((err, data) { - if (err == null) { - if (data != null) { - print(data); - responseCompleter.complete( - BranchResponse.success(result: _jsObjectToDartObject(data))); - } else { - responseCompleter.complete(BranchResponse.success(result: {})); - } - } else { - responseCompleter.complete(BranchResponse.error( - errorCode: '999', errorMessage: err.toString())); - } - })); - } catch (error) { - print('getLastAttributedTouchData() error: $error'); - responseCompleter.complete(BranchResponse.error( - errorCode: '-1', errorMessage: 'getLastAttributedTouchData() error')); - } - - return responseCompleter.future; - } - - void close() { - _initSessionStream.close(); - } -} +// In order to *not* need this ignore, consider extracting the "web" version +// of your plugin as a separate package, instead of inlining it in the same +// package as the core of your plugin. +// ignore: avoid_web_libraries_in_flutter +import 'dart:async'; +import 'dart:convert'; +import 'dart:js'; +import 'dart:js_util'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import 'flutter_branch_sdk_platform_interface.dart'; +import 'objects/app_tracking_transparency.dart'; +import 'objects/branch_universal_object.dart'; +import 'web/branch_js.dart'; + +/// A workaround to deep-converting an object from JS to a Dart Object. +dynamic _jsObjectToDartObject(data) => json.decode(jsonStringify(data)); + +dynamic _dartObjectToJsObject(data) => jsonParse(json.encode(data)); +Map _metaData = {}; + +/// A web implementation of the FlutterBranchSdkPlatform of the FlutterBranchSdk plugin. +class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { + /// Constructs a FlutterBranchSdkWeb + FlutterBranchSdkWeb(); + + static void registerWith(Registrar registrar) { + FlutterBranchSdkPlatform.instance = FlutterBranchSdkWeb(); + } + + static final StreamController> _initSessionStream = + StreamController>(); + static bool _userIdentified = false; + + ///Initialises a session with the Branch API + ///Listen click em Branch Deeplinks + @override + Stream> initSession() { + getLatestReferringParams().then((data) { + if (data.isNotEmpty) { + _initSessionStream.sink + .add(data.map((key, value) => MapEntry('$key', value))); + } else { + _initSessionStream.sink.add({}); + } + }); + + return _initSessionStream.stream; + } + + ///Returns the last parameters associated with the link that referred the user, not really applicaple for web though + @override + Future> getLatestReferringParams() { + final Completer> response = Completer(); + + try { + BranchJS.data(allowInterop((err, data) { + if (err == null) { + if (data != null) { + var responseData = + Map.from(_jsObjectToDartObject(data)); + response.complete(responseData['data_parsed'] ?? {}); + } else { + response.complete({}); + } + } else { + response.completeError(err); + } + })); + } catch (e) { + debugPrint('getLatestReferringParams() error: $e'); + response.completeError(e); + } + return response.future; + } + + ///Returns the first parameters associated with the link that referred the user + @override + Future> getFirstReferringParams() { + final Completer> response = + Completer>(); + + try { + BranchJS.first(allowInterop((err, data) { + if (err == null) { + if (data != null) { + var responseData = + Map.from(_jsObjectToDartObject(data)); + response.complete(responseData['data_parsed'] ?? {}); + } else { + response.complete({}); + } + } else { + response.completeError(err); + } + })); + } catch (e) { + debugPrint('getFirstReferringParams() error: $e'); + response.completeError(e); + } + return response.future; + } + + ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value + @override + void setIdentity(String userId) { + try { + BranchJS.setIdentity(userId, allowInterop((error, data) { + if (error == null) { + _userIdentified = true; + } + })); + } catch (e) { + debugPrint('setIdentity() error: $e'); + } + } + + ///This method should be called if you know that a different person is about to use the app + @override + void logout() { + try { + BranchJS.logout(allowInterop((error) { + if (error == null) { + _userIdentified = false; + } + })); + } catch (e) { + debugPrint('logout() error: $e'); + } + } + + ///Method to change the Tracking state. If disabled SDK will not track any user data or state. + ///SDK will not send any network calls except for deep linking when tracking is disabled + @override + void disableTracking(bool value) { + try { + BranchJS.disableTracking(value); + } catch (e) { + debugPrint('disableTracking() error: $e'); + } + } + + ///Creates a short url for the BUO + @override + Future getShortUrl( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties}) async { + Map data = buo.toMapWeb(); + linkProperties.getControlParams().forEach((key, value) { + data[key] = value; + }); + + Map linkData = { + ...linkProperties.toMapWeb(), + 'data': data + }; + + Completer responseCompleter = Completer(); + + try { + BranchJS.link(_dartObjectToJsObject(linkData), allowInterop((err, url) { + if (err == null) { + responseCompleter.complete(BranchResponse.success(result: url)); + } else { + responseCompleter.completeError( + BranchResponse.error(errorCode: '-1', errorMessage: err)); + } + })); + } catch (e) { + debugPrint('getShortUrl() error: $e'); + responseCompleter.completeError(BranchResponse.error( + errorCode: '-1', errorMessage: 'getShortUrl() error')); + } + return responseCompleter.future; + } + + ///Showing a Share Sheet - Implemented via navigator share if available, otherwise browser prompt. + @override + Future showShareSheet( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required String messageText, + String androidMessageTitle = '', + String androidSharingTitle = ''}) async { + BranchResponse response = + await getShortUrl(buo: buo, linkProperties: linkProperties); + if (response.success) { + try { + await promiseToFuture(navigatorShare(_dartObjectToJsObject({ + "title": messageText, + "text": buo.title, + "url": response.result + }))); + } catch (e) { + browserPrompt(messageText, response.result); + } + } + return response; + } + + ///Logs this BranchEvent to Branch for tracking and analytics + @override + void trackContent( + {required List buo, + required BranchEvent branchEvent}) { + JsArray contentItems = JsArray(); + + for (var element in buo) { + contentItems.add(_dartObjectToJsObject(element.toMapWeb())); + } + + try { + BranchJS.logEvent(branchEvent.eventName, + _dartObjectToJsObject(branchEvent.toMapWeb()), contentItems); + } catch (e) { + debugPrint('trackContent() error: $e'); + } + } + + ///Logs this BranchEvent to Branch for tracking and analytics + @override + void trackContentWithoutBuo({required BranchEvent branchEvent}) { + try { + BranchJS.logEvent( + branchEvent.eventName, _dartObjectToJsObject(branchEvent.toMapWeb())); + } catch (e) { + debugPrint('trackContentWithoutBuo() error: $e'); + } + } + + ///Mark the content referred by this object as viewed. This increment the view count of the contents referred by this object. + @override + void registerView({required BranchUniversalObject buo}) { + BranchEvent branchEvent = + BranchEvent.standardEvent(BranchStandardEvent.VIEW_ITEM); + + // This might not be exactly the same thing as BUO.registerView, but there's no clear implementation for web sdk + trackContent(buo: [buo], branchEvent: branchEvent); + } + + ///Add key value pairs to all requests + @override + void setRequestMetadata(String key, String value) { + _metaData[key] = value; + } + + ///For Android: Publish this BUO with Google app indexing so that the contents will be available with google search + ///For iOS: List items on Spotlight + @override + Future listOnSearch( + {required BranchUniversalObject buo, + BranchLinkProperties? linkProperties}) async { + throw UnsupportedError('listOnSearch() Not supported by Branch JS SDK'); + } + + ///For Android: Remove the BUO from the local indexing if it is added to the local indexing already + /// This will remove the content from Google(Firebase) and other supported Indexing services + ///For iOS: Remove Branch Universal Object from Spotlight if privately indexed + @override + Future removeFromSearch( + {required BranchUniversalObject buo, + BranchLinkProperties? linkProperties}) async { + throw UnsupportedError('removeFromSearch() Not supported by Branch JS SDK'); + } + + ///Set time window for SKAdNetwork callouts in Hours (Only iOS) + ///By default, Branch limits calls to SKAdNetwork to within 72 hours after first install. + @override + void setIOSSKAdNetworkMaxTime(int hours) { + throw UnsupportedError( + 'setIOSSKAdNetworkMaxTime() Not available in Branch JS SDK'); + } + + ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. + ///If you call setIdentity, this device will have that identity associated with this user until logout is called. + ///This includes persisting through uninstalls, as we track device id. + // NOTE: This is not really accurate for persistent checks... + @override + Future isUserIdentified() async { + return Future.value(_userIdentified); + } + + /// request AppTracking Autorization and return AppTrackingStatus + /// on Android returns notSupported + @override + Future requestTrackingAuthorization() async { + throw UnsupportedError( + 'requestTrackingAuthorization() Not available in Branch JS SDK'); + } + + /// return AppTrackingStatus + /// on Android returns notSupported + @override + Future getTrackingAuthorizationStatus() async { + throw UnsupportedError( + 'getTrackingAuthorizationStatus() Not available in Branch JS SDK'); + } + + /// return advertising identifier (ie tracking data). + /// on Android returns empty string + @override + Future getAdvertisingIdentifier() async { + throw UnsupportedError( + 'getAdvertisingIdentifier() Not available in Branch JS SDK'); + } + + ///Use the SDK integration validator to check that you've added the Branch SDK and + ///handle deep links correctly when you first integrate Branch into your app. + @override + void validateSDKIntegration() { + throw UnsupportedError( + 'validateSDKIntegration() not available in Branch JS SDK'); + } + + ///Sets the duration in milliseconds that the system should wait for initializing + ///a network * request. + @override + void setConnectTimeout(int connectTimeout) { + throw UnsupportedError( + 'setConnectTimeout() Not available in Branch JS SDK'); + } + + ///Sets the duration in milliseconds that the system should wait for a response + ///before timing out any Branch API. + ///Default 5500 ms. Note that this is the total time allocated for all request + ///retries as set in setRetryCount(int). + @override + void setTimeout(int timeout) { + throw UnsupportedError('setTimeout() Not available in Branch JS SDK'); + } + + ///Sets the max number of times to re-attempt a timed-out request to the Branch API, before + /// considering the request to have failed entirely. Default to 3. + /// Note that the the network timeout, as set in setNetworkTimeout(int), + /// together with the retry interval value from setRetryInterval(int) will + /// determine if the max retry count will be attempted. + @override + void setRetryCount(int retryCount) { + throw UnsupportedError('setRetryCount() Not available in Branch JS SDK'); + } + + ///Sets the amount of time in milliseconds to wait before re-attempting a + ///timed-out request to the Branch API. Default 1000 ms. + @override + void setRetryInterval(int retryInterval) { + throw UnsupportedError('setRetryInterval() Not available in Branch JS SDK'); + } + + ///Gets the available last attributed touch data with a custom set attribution window. + @override + Future getLastAttributedTouchData( + {int? attributionWindow}) async { + Completer responseCompleter = Completer(); + + try { + BranchJS.lastAttributedTouchData(attributionWindow, + allowInterop((err, data) { + if (err == null) { + if (data != null) { + debugPrint(data); + responseCompleter.complete( + BranchResponse.success(result: _jsObjectToDartObject(data))); + } else { + responseCompleter.complete(BranchResponse.success(result: {})); + } + } else { + responseCompleter.complete(BranchResponse.error( + errorCode: '999', errorMessage: err.toString())); + } + })); + } catch (error) { + debugPrint('getLastAttributedTouchData() error: $error'); + responseCompleter.complete(BranchResponse.error( + errorCode: '-1', errorMessage: 'getLastAttributedTouchData() error')); + } + + return responseCompleter.future; + } + + void close() { + _initSessionStream.close(); + } +} diff --git a/lib/src/app_tracking_transparency.dart b/lib/src/objects/app_tracking_transparency.dart similarity index 96% rename from lib/src/app_tracking_transparency.dart rename to lib/src/objects/app_tracking_transparency.dart index d8bb0bdd..f615440a 100644 --- a/lib/src/app_tracking_transparency.dart +++ b/lib/src/objects/app_tracking_transparency.dart @@ -1,16 +1,16 @@ -enum AppTrackingStatus { - /// The user has not yet received an authorization request dialog - notDetermined, - - /// The device is restricted, tracking is disabled and the system can't show a request dialog - restricted, - - /// The user denies authorization for tracking - denied, - - /// The user authorizes access to tracking - authorized, - - /// The platform is not iOS or the iOS version is below 14.0 - notSupported, -} +enum AppTrackingStatus { + /// The user has not yet received an authorization request dialog + notDetermined, + + /// The device is restricted, tracking is disabled and the system can't show a request dialog + restricted, + + /// The user denies authorization for tracking + denied, + + /// The user authorizes access to tracking + authorized, + + /// The platform is not iOS or the iOS version is below 14.0 + notSupported, +} diff --git a/lib/src/objects/branch_event.dart b/lib/src/objects/branch_event.dart new file mode 100644 index 00000000..4caffa00 --- /dev/null +++ b/lib/src/objects/branch_event.dart @@ -0,0 +1,131 @@ +part of flutter_branch_sdk_objects; +/* +* Enum for creating Branch events for tracking and analytical purpose. +* Enum class represent a standard or custom BranchEvents. Standard Branch events are defined with BRANCH_STANDARD_EVENT}. +* Please use #logEvent() method to log the events for tracking. +*/ + +enum BranchStandardEvent { + // Commerce events + ADD_TO_CART, + ADD_TO_WISHLIST, + VIEW_CART, + INITIATE_PURCHASE, + ADD_PAYMENT_INFO, + PURCHASE, + SPEND_CREDITS, + // Content Events + SEARCH, + VIEW_ITEM, + VIEW_ITEMS, + RATE, + SHARE, + // User Lifecycle Events + COMPLETE_REGISTRATION, + COMPLETE_TUTORIAL, + ACHIEVE_LEVEL, + UNLOCK_ACHIEVEMENT +} + +String getBranchStandardEventString(BranchStandardEvent branchStandardEvent) { + return branchStandardEvent.toString().split('.').last; +} + +enum BranchEventAdType { BANNER, INTERSTITIAL, REWARDED_VIDEO, NATIVE } + +String getBranchEventAdTypeString(BranchEventAdType branchEventAdType) { + return branchEventAdType.toString().split('.').last; +} + +class BranchEvent { + String _eventName = ''; + bool _isStandardEvent = true; + String transactionID = ''; + BranchCurrencyType? currency; + double revenue = -1; + double shipping = -1; + double tax = -1; + String coupon = ''; + String affiliation = ''; + String eventDescription = ''; + String searchQuery = ''; + BranchEventAdType? adType; + final Map _customData = {}; + + BranchEvent.standardEvent(BranchStandardEvent branchStandardEvent) { + _eventName = getBranchStandardEventString(branchStandardEvent); + _isStandardEvent = true; + } + + BranchEvent.customEvent(this._eventName) { + _isStandardEvent = false; + } + + String get eventName => _eventName; + bool get isStandardEvent => _isStandardEvent; + + void addCustomData(String key, dynamic value) { + _customData[key] = value; + } + + void removeCustomData(String key) { + _customData.remove(key); + } + + Map toMap() { + Map ret = {}; + + ret["eventName"] = _eventName; + ret["isStandardEvent"] = _isStandardEvent; + if (transactionID.isNotEmpty) { + ret["transactionID"] = transactionID; + } + if (currency != null) { + ret["currency"] = getCurrencyTypeString(currency!); + } + if (revenue != -1) ret["revenue"] = revenue; + if (shipping != -1) ret["shipping"] = shipping; + if (tax != -1) ret["tax"] = tax; + if (coupon.isNotEmpty) ret["coupon"] = coupon; + if (affiliation.isNotEmpty) ret["affiliation"] = affiliation; + if (eventDescription.isNotEmpty) { + ret["eventDescription"] = eventDescription; + } + if (searchQuery.isNotEmpty) ret["searchQuery"] = searchQuery; + if (adType != null) { + ret["adType"] = getBranchEventAdTypeString(adType!); + } + if (_customData.isNotEmpty) ret["customData"] = _customData; + return ret; + } + + Map toMapWeb() { + Map ret = {}; + if (_isStandardEvent) { + if (transactionID.isNotEmpty) { + ret["transactionID"] = transactionID; + } + if (currency != null) { + ret["currency"] = getCurrencyTypeString(currency!); + } + if (revenue != -1) ret["revenue"] = revenue; + if (shipping != -1) ret["shipping"] = shipping; + if (tax != -1) ret["tax"] = tax; + if (coupon.isNotEmpty) ret["coupon"] = coupon; + if (affiliation.isNotEmpty) ret["affiliation"] = affiliation; + if (eventDescription.isNotEmpty) { + ret["eventDescription"] = eventDescription; + } + if (searchQuery.isNotEmpty) { + ret["searchQuery"] = searchQuery; + } + if (adType != null) { + ret["adType"] = getBranchEventAdTypeString(adType!); + } + } + _customData.forEach((key, value) { + ret[key] = value; + }); + return ret; + } +} diff --git a/lib/src/branch_response.dart b/lib/src/objects/branch_response.dart similarity index 87% rename from lib/src/branch_response.dart rename to lib/src/objects/branch_response.dart index 3fe43ec1..215ccbad 100644 --- a/lib/src/branch_response.dart +++ b/lib/src/objects/branch_response.dart @@ -1,20 +1,20 @@ -part of flutter_branch_sdk_objects; - -class BranchResponse { - bool success = true; - T? result; - String errorCode = ''; - String errorMessage = ''; - - BranchResponse.success({required this.result}) { - this.success = true; - } - BranchResponse.error({required this.errorCode, required this.errorMessage}) { - this.success = false; - } - - @override - String toString() { - return ('success: $success, errorCode: $errorCode, errorMessage: $errorMessage}'); - } -} +part of flutter_branch_sdk_objects; + +class BranchResponse { + bool success = true; + T? result; + String errorCode = ''; + String errorMessage = ''; + + BranchResponse.success({required this.result}) { + success = true; + } + BranchResponse.error({required this.errorCode, required this.errorMessage}) { + success = false; + } + + @override + String toString() { + return ('success: $success, errorCode: $errorCode, errorMessage: $errorMessage}'); + } +} diff --git a/lib/src/branch_universal_object.dart b/lib/src/objects/branch_universal_object.dart similarity index 63% rename from lib/src/branch_universal_object.dart rename to lib/src/objects/branch_universal_object.dart index 9def5a10..315825b0 100644 --- a/lib/src/branch_universal_object.dart +++ b/lib/src/objects/branch_universal_object.dart @@ -1,155 +1,161 @@ -library flutter_branch_sdk_objects; - -part 'branch_event.dart'; -part 'branch_response.dart'; -part 'content_meta_data.dart'; -part 'content_schema.dart'; -part 'link_properties.dart'; - -/* - * Class represents a single piece of content within your app, as well as any associated metadata. - * It provides convenient methods for sharing, deep linking, and tracking how often that content is viewed. This information is then used to provide you with powerful content analytics - * and deep linking. - */ -class BranchUniversalObject { - /* Canonical identifier for the content referred. */ - final String canonicalIdentifier; - - /* Canonical url for the content referred. This would be the corresponding website URL */ - String canonicalUrl = ''; - - /* Title for the content referred by BranchUniversalObject */ - String title = ''; - - /* Description for the content referred */ - String contentDescription = ''; - - /* An image url associated with the content referred */ - String imageUrl = ''; - - /* Meta data provided for the content. {@link ContentMetadata} object holds the metadata for this content */ - BranchContentMetaData? contentMetadata; - - /* Content index mode */ - bool publiclyIndex = true; - - /* Any keyword associated with the content. Used for indexing */ - List keywords; - - /* Expiry date for the content and any associated links. Represented as epoch milli second */ - int expirationDateInMilliSec = 0; - - /* Index mode for local content indexing */ - bool locallyIndex = true; - int _creationDateTimeStamp = DateTime.now().millisecondsSinceEpoch; - - ///Create a BranchUniversalObject with the given content. - BranchUniversalObject( - {required this.canonicalIdentifier, - this.canonicalUrl = '', - this.title = '', - this.contentDescription = '', - this.imageUrl = '', - this.contentMetadata, - this.keywords = const [], - this.publiclyIndex = true, - this.locallyIndex = true, - this.expirationDateInMilliSec = 0}); - - ///Adds any keywords associated with the content referred - void addKeyWords(List keywords) { - this.keywords.addAll(keywords); - } - - ///Add a keyword associated with the content referred - void addKeyWord(String keyword) { - this.keywords.add(keyword); - } - - ///Remove a keyword associated with the content referred - void removeKeyWord(String keyword) { - this.keywords.remove(keyword); - } - - ///Get the keywords associated with this BranchUniversalObject - List getKeywords() { - return this.keywords; - } - - Map toMap() { - Map ret = {}; - if (this.canonicalIdentifier.isNotEmpty) - ret["canonicalIdentifier"] = this.canonicalIdentifier; - - if (this.canonicalUrl.isNotEmpty) ret["canonicalUrl"] = this.canonicalUrl; - - if (this.title.isNotEmpty) ret["title"] = this.title; - - if (this.contentDescription.isNotEmpty) - ret["contentDescription"] = this.contentDescription; - - if (this.imageUrl.isNotEmpty) ret["imageUrl"] = this.imageUrl; - - if (this.keywords.isNotEmpty) ret["keywords"] = this.keywords; - - ret["creationDate"] = this._creationDateTimeStamp; - - if (this.expirationDateInMilliSec > 0) - ret["expirationDate"] = this.expirationDateInMilliSec; - - ret["locallyIndex"] = this.locallyIndex; - ret["publiclyIndex"] = this.publiclyIndex; - - if (this.contentMetadata != null && - this.contentMetadata!.toMap().isNotEmpty) - ret["contentMetadata"] = this.contentMetadata!.toMap(); - - if (ret.isEmpty) { - throw ArgumentError('Branch Universal Object is required'); - } - return ret; - } - - Map toMapWeb() { - Map ret = {}; - if (this.canonicalIdentifier.isNotEmpty) - ret["\$canonical_identifier"] = this.canonicalIdentifier; - - if (this.canonicalUrl.isNotEmpty) ret["\$canonicalUrl"] = this.canonicalUrl; - - if (this.title.isNotEmpty) ret["\$og_title"] = this.title; - - if (this.contentDescription.isNotEmpty) - ret["\$og_description"] = this.contentDescription; - - if (this.imageUrl.isNotEmpty) ret["\$og_image_url"] = this.imageUrl; - - if (this.keywords.isNotEmpty) ret["\$keywords"] = this.keywords; - - ret["\$creation_timestamp"] = this._creationDateTimeStamp; - - if (this.expirationDateInMilliSec > 0) - ret["\$exp_date"] = this.expirationDateInMilliSec; - - ret["\$locally_indexable"] = this.locallyIndex; - ret["\$publicly_indexable"] = this.publiclyIndex; - - Map contentMetadata = { - if (this.contentMetadata != null) ...this.contentMetadata!.toMapWeb() - }; - - if (contentMetadata.containsKey('customMetadata')) { - var customMetadata = contentMetadata['customMetadata']; - contentMetadata.remove('customMetadata'); - contentMetadata.addAll(customMetadata); - ret.addAll(contentMetadata); - } else { - ret.addAll(contentMetadata); - } - - if (ret.isEmpty) { - throw ArgumentError('Branch Universal Object is required'); - } - return ret; - } -} +library flutter_branch_sdk_objects; + +part 'branch_event.dart'; +part 'branch_response.dart'; +part 'content_meta_data.dart'; +part 'content_schema.dart'; +part 'link_properties.dart'; + +/* + * Class represents a single piece of content within your app, as well as any associated metadata. + * It provides convenient methods for sharing, deep linking, and tracking how often that content is viewed. This information is then used to provide you with powerful content analytics + * and deep linking. + */ +class BranchUniversalObject { + /* Canonical identifier for the content referred. */ + final String canonicalIdentifier; + + /* Canonical url for the content referred. This would be the corresponding website URL */ + String canonicalUrl = ''; + + /* Title for the content referred by BranchUniversalObject */ + String title = ''; + + /* Description for the content referred */ + String contentDescription = ''; + + /* An image url associated with the content referred */ + String imageUrl = ''; + + /* Meta data provided for the content. {@link ContentMetadata} object holds the metadata for this content */ + BranchContentMetaData? contentMetadata; + + /* Content index mode */ + bool publiclyIndex = true; + + /* Any keyword associated with the content. Used for indexing */ + List keywords; + + /* Expiry date for the content and any associated links. Represented as epoch milli second */ + int expirationDateInMilliSec = 0; + + /* Index mode for local content indexing */ + bool locallyIndex = true; + final int _creationDateTimeStamp = DateTime.now().millisecondsSinceEpoch; + + ///Create a BranchUniversalObject with the given content. + BranchUniversalObject( + {required this.canonicalIdentifier, + this.canonicalUrl = '', + this.title = '', + this.contentDescription = '', + this.imageUrl = '', + this.contentMetadata, + this.keywords = const [], + this.publiclyIndex = true, + this.locallyIndex = true, + this.expirationDateInMilliSec = 0}); + + ///Adds any keywords associated with the content referred + void addKeyWords(List keywords) { + keywords.addAll(keywords); + } + + ///Add a keyword associated with the content referred + void addKeyWord(String keyword) { + keywords.add(keyword); + } + + ///Remove a keyword associated with the content referred + void removeKeyWord(String keyword) { + keywords.remove(keyword); + } + + ///Get the keywords associated with this BranchUniversalObject + List getKeywords() { + return keywords; + } + + Map toMap() { + Map ret = {}; + if (canonicalIdentifier.isNotEmpty) { + ret["canonicalIdentifier"] = canonicalIdentifier; + } + + if (canonicalUrl.isNotEmpty) ret["canonicalUrl"] = canonicalUrl; + + if (title.isNotEmpty) ret["title"] = title; + + if (contentDescription.isNotEmpty) { + ret["contentDescription"] = contentDescription; + } + + if (imageUrl.isNotEmpty) ret["imageUrl"] = imageUrl; + + if (keywords.isNotEmpty) ret["keywords"] = keywords; + + ret["creationDate"] = _creationDateTimeStamp; + + if (expirationDateInMilliSec > 0) { + ret["expirationDate"] = expirationDateInMilliSec; + } + + ret["locallyIndex"] = locallyIndex; + ret["publiclyIndex"] = publiclyIndex; + + if (contentMetadata != null && contentMetadata!.toMap().isNotEmpty) { + ret["contentMetadata"] = contentMetadata!.toMap(); + } + + if (ret.isEmpty) { + throw ArgumentError('Branch Universal Object is required'); + } + return ret; + } + + Map toMapWeb() { + Map ret = {}; + if (canonicalIdentifier.isNotEmpty) { + ret["\$canonical_identifier"] = canonicalIdentifier; + } + + if (canonicalUrl.isNotEmpty) ret["\$canonicalUrl"] = canonicalUrl; + + if (title.isNotEmpty) ret["\$og_title"] = title; + + if (contentDescription.isNotEmpty) { + ret["\$og_description"] = contentDescription; + } + + if (imageUrl.isNotEmpty) ret["\$og_image_url"] = imageUrl; + + if (keywords.isNotEmpty) ret["\$keywords"] = keywords; + + ret["\$creation_timestamp"] = _creationDateTimeStamp; + + if (expirationDateInMilliSec > 0) { + ret["\$exp_date"] = expirationDateInMilliSec; + } + + ret["\$locally_indexable"] = locallyIndex; + ret["\$publicly_indexable"] = publiclyIndex; + + Map contentMetadata = { + if (this.contentMetadata != null) ...this.contentMetadata!.toMapWeb() + }; + + if (contentMetadata.containsKey('customMetadata')) { + var customMetadata = contentMetadata['customMetadata']; + contentMetadata.remove('customMetadata'); + contentMetadata.addAll(customMetadata); + ret.addAll(contentMetadata); + } else { + ret.addAll(contentMetadata); + } + + if (ret.isEmpty) { + throw ArgumentError('Branch Universal Object is required'); + } + return ret; + } +} diff --git a/lib/src/content_meta_data.dart b/lib/src/objects/content_meta_data.dart similarity index 61% rename from lib/src/content_meta_data.dart rename to lib/src/objects/content_meta_data.dart index 26955186..b232c0a6 100644 --- a/lib/src/content_meta_data.dart +++ b/lib/src/objects/content_meta_data.dart @@ -1,459 +1,460 @@ -part of flutter_branch_sdk_objects; - -enum BranchCondition { - OTHER, - NEW, - GOOD, - FAIR, - POOR, - USED, - REFURBISHED, - EXCELLENT -} - -enum BranchCurrencyType { - AED, - AFN, - ALL, - AMD, - ANG, - AOA, - ARS, - AUD, - AWG, - AZN, - BAM, - BBD, - BDT, - BGN, - BHD, - BIF, - BMD, - BND, - BOB, - BOV, - BRL, - BSD, - BTN, - BWP, - BYN, - BYR, - BZD, - CAD, - CDF, - CHE, - CHF, - CHW, - CLF, - CLP, - CNY, - COP, - COU, - CRC, - CUC, - CUP, - CVE, - CZK, - DJF, - DKK, - DOP, - DZD, - EGP, - ERN, - ETB, - EUR, - FJD, - FKP, - GBP, - GEL, - GHS, - GIP, - GMD, - GNF, - GTQ, - GYD, - HKD, - HNL, - HRK, - HTG, - HUF, - IDR, - ILS, - INR, - IQD, - IRR, - ISK, - JMD, - JOD, - JPY, - KES, - KGS, - KHR, - KMF, - KPW, - KRW, - KWD, - KYD, - KZT, - LAK, - LBP, - LKR, - LRD, - LSL, - LYD, - MAD, - MDL, - MGA, - MKD, - MMK, - MNT, - MOP, - MRO, - MUR, - MVR, - MWK, - MXN, - MXV, - MYR, - MZN, - NAD, - NGN, - NIO, - NOK, - NPR, - NZD, - OMR, - PAB, - PEN, - PGK, - PHP, - PKR, - PLN, - PYG, - QAR, - RON, - RSD, - RUB, - RWF, - SAR, - SBD, - SCR, - SDG, - SEK, - SGD, - SHP, - SLL, - SOS, - SRD, - SSP, - STD, - SYP, - SZL, - THB, - TJS, - TMT, - TND, - TOP, - TRY, - TTD, - TWD, - TZS, - UAH, - UGX, - USD, - USN, - UYI, - UYU, - UZS, - VEF, - VND, - VUV, - WST, - XAF, - XAG, - XAU, - XBA, - XBB, - XBC, - XBD, - XCD, - XDR, - XFU, - XOF, - XPD, - XPF, - XPT, - XSU, - XTS, - XUA, - XXX, - YER, - ZAR, - ZMW -} - -String getCurrencyTypeString(BranchCurrencyType currencyType) { - return currencyType.toString().split('.').last; -} - -enum BranchProductCategory { - ANIMALS_AND_PET_SUPPLIES, - APPAREL_AND_ACCESSORIES, - ARTS_AND_ENTERTAINMENT, - BABY_AND_TODDLER, - BUSINESS_AND_INDUSTRIAL, - CAMERAS_AND_OPTICS, - ELECTRONICS, - FOOD_BEVERAGES_AND_TOBACCO, - FURNITURE, - HARDWARE, - HEALTH_AND_BEAUTY, - HOME_AND_GARDEN, - LUGGAGE_AND_BAGS, - MATURE, - MEDIA, - OFFICE_SUPPLIES, - RELIGIOUS_AND_CEREMONIAL, - SOFTWARE, - SPORTING_GOODS, - TOYS_AND_GAMES, - VEHICLES_AND_PARTS, -} - -/* -Class for describing metadata for a piece of content represented by a FlutterBranchUniversalObject -*/ -class BranchContentMetaData { - /// Schema for the qualifying content item. Please see [BranchContentSchema] - BranchContentSchema? contentSchema; - - /// Quantity of the thing associated with the qualifying content item - double quantity = 0; - - /// Any price associated with the qualifying content item - double price = 0; - - /// Currency type associated with the price - BranchCurrencyType? currencyType; - - /// Holds any associated store keeping unit - String sku = ''; - - /// Name of any product specified by this metadata - String productName = ''; - - /// Any brand name associated with this metadata - String productBrand = ''; - - /// Category of product if this metadata is for a product - /// Value should be one of the enumeration from {@link ProductCategory} - BranchProductCategory? productCategory; - - /// Condition of the product item. Value is one of the enum constants from {@link CONDITION} - BranchCondition? condition; - - /// Variant of product if this metadata is for a product - String productVariant = ''; - - /// Rating for the qualifying content item - double rating = 0; - - /// Average rating for the qualifying content item - double ratingAverage = 0; - - /// Total number of ratings for the qualifying content item - int ratingCount = 0; - - ///Maximum ratings for the qualifying content item - double ratingMax = 0; - - /// Street address associated with the qualifying content item - String _addressStreet = ''; - - /// City name associated with the qualifying content item - String _addressCity = ''; - - /// Region or province name associated with the qualifying content item - String _addressRegion = ''; - - /// Country name associated with the qualifying content item - String _addressCountry = ''; - - /// Postal code associated with the qualifying content item - String _addressPostalCode = ''; - - /// Latitude value associated with the qualifying content item - double? _latitude; - - /// Latitude value associated with the qualifying content item - double? _longitude; - - List _imageCaptions = const []; - Map _customMetadata = {}; - - String? _getProductConditionString(BranchCondition? productCondition) { - if (productCondition == null) return null; - return productCondition.toString().split('.').last; - } - - String? _getProductCategoryString(BranchProductCategory? productCategory) { - if (productCategory == null) return null; - switch (productCategory) { - case BranchProductCategory.ANIMALS_AND_PET_SUPPLIES: - return "Animals & Pet Supplies"; - case BranchProductCategory.APPAREL_AND_ACCESSORIES: - return "Apparel & Accessories"; - case BranchProductCategory.ARTS_AND_ENTERTAINMENT: - return "Arts & Entertainment"; - case BranchProductCategory.BABY_AND_TODDLER: - return "Baby & Toddler"; - case BranchProductCategory.BUSINESS_AND_INDUSTRIAL: - return "Business & Industrial"; - case BranchProductCategory.CAMERAS_AND_OPTICS: - return "Cameras & Optics"; - case BranchProductCategory.ELECTRONICS: - return "Electronics"; - case BranchProductCategory.FOOD_BEVERAGES_AND_TOBACCO: - return "Food, Beverages & Tobacco"; - case BranchProductCategory.FURNITURE: - return "Furniture"; - case BranchProductCategory.HARDWARE: - return "Hardware"; - case BranchProductCategory.HEALTH_AND_BEAUTY: - return "Health & Beauty"; - case BranchProductCategory.HOME_AND_GARDEN: - return "Home & Garden"; - case BranchProductCategory.LUGGAGE_AND_BAGS: - return "Luggage & Bags"; - case BranchProductCategory.MATURE: - return "Mature"; - case BranchProductCategory.MEDIA: - return "Media"; - case BranchProductCategory.OFFICE_SUPPLIES: - return "Office Supplies"; - case BranchProductCategory.RELIGIOUS_AND_CEREMONIAL: - return "Religious & Ceremonial"; - case BranchProductCategory.SOFTWARE: - return "Software"; - case BranchProductCategory.SPORTING_GOODS: - return "Sporting Goods"; - case BranchProductCategory.TOYS_AND_GAMES: - return "Toys & Games"; - case BranchProductCategory.VEHICLES_AND_PARTS: - return "Vehicles & Parts"; - default: - return "Home & Garden"; - } - } - - BranchContentMetaData addImageCaptions(List captions) { - this._imageCaptions = captions; - return this; - } - - BranchContentMetaData addCustomMetadata(String key, dynamic value) { - this._customMetadata[key] = value; - return this; - } - - BranchContentMetaData setAddress( - {String? street, - String? city, - String? region, - String? country, - String? postalCode}) { - if (street != null) this._addressStreet = street; - if (city != null) this._addressCity = city; - if (region != null) this._addressRegion = region; - if (country != null) this._addressCountry = country; - if (postalCode != null) this._addressPostalCode = postalCode; - return this; - } - - BranchContentMetaData setLocation(double latitude, double longitude) { - this._latitude = latitude; - this._longitude = longitude; - return this; - } - - Map toMap() { - Map ret = Map(); - if (this.contentSchema != null) - ret["content_schema"] = getContentSchemaString(this.contentSchema); - if (this.quantity > 0) ret["quantity"] = this.quantity; - if (this.price > 0) ret["price"] = this.price; - if (this.currencyType != null) - ret["currency"] = getCurrencyTypeString(this.currencyType!); - if (this.sku.isNotEmpty) ret["sku"] = this.sku; - if (this.productName.isNotEmpty) ret["product_name"] = this.productName; - if (this.productBrand.isNotEmpty) ret["product_brand"] = this.productBrand; - if (this.productCategory != null) - ret["product_category"] = _getProductCategoryString(this.productCategory); - if (this.productVariant.isNotEmpty) - ret["product_variant"] = this.productVariant; - if (this.condition != null) - ret["condition"] = _getProductConditionString(this.condition); - if (this.ratingAverage > 0) ret["rating_average"] = this.ratingAverage; - if (this.ratingCount > 0) ret["rating_count"] = this.ratingCount; - if (this.ratingMax > 0) ret["rating_max"] = this.ratingMax; - if (this.rating > 0) ret["rating"] = this.rating; - if (this._addressStreet.isNotEmpty) - ret["address_street"] = this._addressStreet; - if (this._addressCity.isNotEmpty) ret["address_city"] = this._addressCity; - if (this._addressRegion.isNotEmpty) - ret["address_region"] = this._addressRegion; - if (this._addressCountry.isNotEmpty) - ret["address_country"] = this._addressCountry; - if (this._addressPostalCode.isNotEmpty) - ret["address_postal_code"] = this._addressPostalCode; - if (this._latitude != null) ret["latitude"] = this._latitude; - if (this._longitude != null) ret["longitude"] = this._longitude; - if (_imageCaptions.isNotEmpty) ret["image_captions"] = this._imageCaptions; - if (this._customMetadata.isNotEmpty) { - ret["customMetadata"] = this._customMetadata; - } - return ret; - } - - Map toMapWeb() { - Map ret = Map(); - if (this.contentSchema != null) - ret["\$content_schema"] = getContentSchemaString(this.contentSchema); - if (this.quantity > 0) ret["\$quantity"] = this.quantity; - if (this.price > 0) ret["\$price"] = this.price; - if (this.currencyType != null) - ret["\$currency"] = getCurrencyTypeString(this.currencyType!); - if (this.sku.isNotEmpty) ret["\$sku"] = this.sku; - if (this.productName.isNotEmpty) ret["\$product_name"] = this.productName; - if (this.productBrand.isNotEmpty) - ret["\$product_brand"] = this.productBrand; - if (this.productCategory != null) - ret["\$product_category"] = - this.productCategory.toString().split('.').last; - if (this.productVariant.isNotEmpty) - ret["\$product_variant"] = this.productVariant; - if (this.condition != null) - ret["\$condition"] = _getProductConditionString(this.condition); - if (this.ratingAverage > 0) ret["\$rating_average"] = this.ratingAverage; - if (this.ratingCount > 0) ret["\$rating_count"] = this.ratingCount; - if (this.ratingMax > 0) ret["\$rating_max"] = this.ratingMax; - if (this.rating > 0) ret["\$rating"] = this.rating; - if (this._addressStreet.isNotEmpty) - ret["\$address_street"] = this._addressStreet; - if (this._addressCity.isNotEmpty) ret["\$address_city"] = this._addressCity; - if (this._addressRegion.isNotEmpty) - ret["\$address_region"] = this._addressRegion; - if (this._addressCountry.isNotEmpty) - ret["\$address_country"] = this._addressCountry; - if (this._addressPostalCode.isNotEmpty) - ret["\$address_postal_code"] = this._addressPostalCode; - if (this._latitude != null) ret["\$latitude"] = this._latitude; - if (this._longitude != null) ret["\$longitude"] = this._longitude; - if (_imageCaptions.isNotEmpty) - ret["\$image_captions"] = this._imageCaptions; - this._customMetadata.forEach((key, value) { - ret['$key'] = value; - }); - return ret; - } -} +part of flutter_branch_sdk_objects; + +enum BranchCondition { + OTHER, + NEW, + GOOD, + FAIR, + POOR, + USED, + REFURBISHED, + EXCELLENT +} + +enum BranchCurrencyType { + AED, + AFN, + ALL, + AMD, + ANG, + AOA, + ARS, + AUD, + AWG, + AZN, + BAM, + BBD, + BDT, + BGN, + BHD, + BIF, + BMD, + BND, + BOB, + BOV, + BRL, + BSD, + BTN, + BWP, + BYN, + BYR, + BZD, + CAD, + CDF, + CHE, + CHF, + CHW, + CLF, + CLP, + CNY, + COP, + COU, + CRC, + CUC, + CUP, + CVE, + CZK, + DJF, + DKK, + DOP, + DZD, + EGP, + ERN, + ETB, + EUR, + FJD, + FKP, + GBP, + GEL, + GHS, + GIP, + GMD, + GNF, + GTQ, + GYD, + HKD, + HNL, + HRK, + HTG, + HUF, + IDR, + ILS, + INR, + IQD, + IRR, + ISK, + JMD, + JOD, + JPY, + KES, + KGS, + KHR, + KMF, + KPW, + KRW, + KWD, + KYD, + KZT, + LAK, + LBP, + LKR, + LRD, + LSL, + LYD, + MAD, + MDL, + MGA, + MKD, + MMK, + MNT, + MOP, + MRO, + MUR, + MVR, + MWK, + MXN, + MXV, + MYR, + MZN, + NAD, + NGN, + NIO, + NOK, + NPR, + NZD, + OMR, + PAB, + PEN, + PGK, + PHP, + PKR, + PLN, + PYG, + QAR, + RON, + RSD, + RUB, + RWF, + SAR, + SBD, + SCR, + SDG, + SEK, + SGD, + SHP, + SLL, + SOS, + SRD, + SSP, + STD, + SYP, + SZL, + THB, + TJS, + TMT, + TND, + TOP, + TRY, + TTD, + TWD, + TZS, + UAH, + UGX, + USD, + USN, + UYI, + UYU, + UZS, + VEF, + VND, + VUV, + WST, + XAF, + XAG, + XAU, + XBA, + XBB, + XBC, + XBD, + XCD, + XDR, + XFU, + XOF, + XPD, + XPF, + XPT, + XSU, + XTS, + XUA, + XXX, + YER, + ZAR, + ZMW +} + +String getCurrencyTypeString(BranchCurrencyType currencyType) { + return currencyType.toString().split('.').last; +} + +enum BranchProductCategory { + ANIMALS_AND_PET_SUPPLIES, + APPAREL_AND_ACCESSORIES, + ARTS_AND_ENTERTAINMENT, + BABY_AND_TODDLER, + BUSINESS_AND_INDUSTRIAL, + CAMERAS_AND_OPTICS, + ELECTRONICS, + FOOD_BEVERAGES_AND_TOBACCO, + FURNITURE, + HARDWARE, + HEALTH_AND_BEAUTY, + HOME_AND_GARDEN, + LUGGAGE_AND_BAGS, + MATURE, + MEDIA, + OFFICE_SUPPLIES, + RELIGIOUS_AND_CEREMONIAL, + SOFTWARE, + SPORTING_GOODS, + TOYS_AND_GAMES, + VEHICLES_AND_PARTS, +} + +/* +Class for describing metadata for a piece of content represented by a FlutterBranchUniversalObject +*/ +class BranchContentMetaData { + /// Schema for the qualifying content item. Please see [BranchContentSchema] + BranchContentSchema? contentSchema; + + /// Quantity of the thing associated with the qualifying content item + double quantity = 0; + + /// Any price associated with the qualifying content item + double price = 0; + + /// Currency type associated with the price + BranchCurrencyType? currencyType; + + /// Holds any associated store keeping unit + String sku = ''; + + /// Name of any product specified by this metadata + String productName = ''; + + /// Any brand name associated with this metadata + String productBrand = ''; + + /// Category of product if this metadata is for a product + /// Value should be one of the enumeration from {@link ProductCategory} + BranchProductCategory? productCategory; + + /// Condition of the product item. Value is one of the enum constants from {@link CONDITION} + BranchCondition? condition; + + /// Variant of product if this metadata is for a product + String productVariant = ''; + + /// Rating for the qualifying content item + double rating = 0; + + /// Average rating for the qualifying content item + double ratingAverage = 0; + + /// Total number of ratings for the qualifying content item + int ratingCount = 0; + + ///Maximum ratings for the qualifying content item + double ratingMax = 0; + + /// Street address associated with the qualifying content item + String _addressStreet = ''; + + /// City name associated with the qualifying content item + String _addressCity = ''; + + /// Region or province name associated with the qualifying content item + String _addressRegion = ''; + + /// Country name associated with the qualifying content item + String _addressCountry = ''; + + /// Postal code associated with the qualifying content item + String _addressPostalCode = ''; + + /// Latitude value associated with the qualifying content item + double? _latitude; + + /// Latitude value associated with the qualifying content item + double? _longitude; + + List _imageCaptions = const []; + final Map _customMetadata = {}; + + String? _getProductConditionString(BranchCondition? productCondition) { + if (productCondition == null) return null; + return productCondition.toString().split('.').last; + } + + String? _getProductCategoryString(BranchProductCategory? productCategory) { + if (productCategory == null) return null; + switch (productCategory) { + case BranchProductCategory.ANIMALS_AND_PET_SUPPLIES: + return "Animals & Pet Supplies"; + case BranchProductCategory.APPAREL_AND_ACCESSORIES: + return "Apparel & Accessories"; + case BranchProductCategory.ARTS_AND_ENTERTAINMENT: + return "Arts & Entertainment"; + case BranchProductCategory.BABY_AND_TODDLER: + return "Baby & Toddler"; + case BranchProductCategory.BUSINESS_AND_INDUSTRIAL: + return "Business & Industrial"; + case BranchProductCategory.CAMERAS_AND_OPTICS: + return "Cameras & Optics"; + case BranchProductCategory.ELECTRONICS: + return "Electronics"; + case BranchProductCategory.FOOD_BEVERAGES_AND_TOBACCO: + return "Food, Beverages & Tobacco"; + case BranchProductCategory.FURNITURE: + return "Furniture"; + case BranchProductCategory.HARDWARE: + return "Hardware"; + case BranchProductCategory.HEALTH_AND_BEAUTY: + return "Health & Beauty"; + case BranchProductCategory.HOME_AND_GARDEN: + return "Home & Garden"; + case BranchProductCategory.LUGGAGE_AND_BAGS: + return "Luggage & Bags"; + case BranchProductCategory.MATURE: + return "Mature"; + case BranchProductCategory.MEDIA: + return "Media"; + case BranchProductCategory.OFFICE_SUPPLIES: + return "Office Supplies"; + case BranchProductCategory.RELIGIOUS_AND_CEREMONIAL: + return "Religious & Ceremonial"; + case BranchProductCategory.SOFTWARE: + return "Software"; + case BranchProductCategory.SPORTING_GOODS: + return "Sporting Goods"; + case BranchProductCategory.TOYS_AND_GAMES: + return "Toys & Games"; + case BranchProductCategory.VEHICLES_AND_PARTS: + return "Vehicles & Parts"; + default: + return "Home & Garden"; + } + } + + BranchContentMetaData addImageCaptions(List captions) { + _imageCaptions = captions; + return this; + } + + BranchContentMetaData addCustomMetadata(String key, dynamic value) { + _customMetadata[key] = value; + return this; + } + + BranchContentMetaData setAddress( + {String? street, + String? city, + String? region, + String? country, + String? postalCode}) { + if (street != null) _addressStreet = street; + if (city != null) _addressCity = city; + if (region != null) _addressRegion = region; + if (country != null) _addressCountry = country; + if (postalCode != null) _addressPostalCode = postalCode; + return this; + } + + BranchContentMetaData setLocation(double latitude, double longitude) { + _latitude = latitude; + _longitude = longitude; + return this; + } + + Map toMap() { + Map ret = {}; + if (contentSchema != null) { + ret["content_schema"] = getContentSchemaString(contentSchema); + } + if (quantity > 0) ret["quantity"] = quantity; + if (price > 0) ret["price"] = price; + if (currencyType != null) { + ret["currency"] = getCurrencyTypeString(currencyType!); + } + if (sku.isNotEmpty) ret["sku"] = sku; + if (productName.isNotEmpty) ret["product_name"] = productName; + if (productBrand.isNotEmpty) ret["product_brand"] = productBrand; + if (productCategory != null) { + ret["product_category"] = _getProductCategoryString(productCategory); + } + if (productVariant.isNotEmpty) ret["product_variant"] = productVariant; + if (condition != null) { + ret["condition"] = _getProductConditionString(condition); + } + if (ratingAverage > 0) ret["rating_average"] = ratingAverage; + if (ratingCount > 0) ret["rating_count"] = ratingCount; + if (ratingMax > 0) ret["rating_max"] = ratingMax; + if (rating > 0) ret["rating"] = rating; + if (_addressStreet.isNotEmpty) ret["address_street"] = _addressStreet; + if (_addressCity.isNotEmpty) ret["address_city"] = _addressCity; + if (_addressRegion.isNotEmpty) ret["address_region"] = _addressRegion; + if (_addressCountry.isNotEmpty) ret["address_country"] = _addressCountry; + if (_addressPostalCode.isNotEmpty) { + ret["address_postal_code"] = _addressPostalCode; + } + if (_latitude != null) ret["latitude"] = _latitude; + if (_longitude != null) ret["longitude"] = _longitude; + if (_imageCaptions.isNotEmpty) ret["image_captions"] = _imageCaptions; + if (_customMetadata.isNotEmpty) { + ret["customMetadata"] = _customMetadata; + } + return ret; + } + + Map toMapWeb() { + Map ret = {}; + if (contentSchema != null) { + ret["\$content_schema"] = getContentSchemaString(contentSchema); + } + if (quantity > 0) ret["\$quantity"] = quantity; + if (price > 0) ret["\$price"] = price; + if (currencyType != null) { + ret["\$currency"] = getCurrencyTypeString(currencyType!); + } + if (sku.isNotEmpty) ret["\$sku"] = sku; + if (productName.isNotEmpty) ret["\$product_name"] = productName; + if (productBrand.isNotEmpty) ret["\$product_brand"] = productBrand; + if (productCategory != null) { + ret["\$product_category"] = productCategory.toString().split('.').last; + } + if (productVariant.isNotEmpty) ret["\$product_variant"] = productVariant; + if (condition != null) { + ret["\$condition"] = _getProductConditionString(condition); + } + if (ratingAverage > 0) ret["\$rating_average"] = ratingAverage; + if (ratingCount > 0) ret["\$rating_count"] = ratingCount; + if (ratingMax > 0) ret["\$rating_max"] = ratingMax; + if (rating > 0) ret["\$rating"] = rating; + if (_addressStreet.isNotEmpty) ret["\$address_street"] = _addressStreet; + if (_addressCity.isNotEmpty) ret["\$address_city"] = _addressCity; + if (_addressRegion.isNotEmpty) ret["\$address_region"] = _addressRegion; + if (_addressCountry.isNotEmpty) ret["\$address_country"] = _addressCountry; + if (_addressPostalCode.isNotEmpty) { + ret["\$address_postal_code"] = _addressPostalCode; + } + if (_latitude != null) ret["\$latitude"] = _latitude; + if (_longitude != null) ret["\$longitude"] = _longitude; + if (_imageCaptions.isNotEmpty) { + ret["\$image_captions"] = _imageCaptions; + } + _customMetadata.forEach((key, value) { + ret[key] = value; + }); + return ret; + } +} diff --git a/lib/src/content_schema.dart b/lib/src/objects/content_schema.dart similarity index 95% rename from lib/src/content_schema.dart rename to lib/src/objects/content_schema.dart index d3e747f5..1c536289 100644 --- a/lib/src/content_schema.dart +++ b/lib/src/objects/content_schema.dart @@ -1,44 +1,44 @@ -part of flutter_branch_sdk_objects; - -enum BranchContentSchema { - COMMERCE_AUCTION, - COMMERCE_BUSINESS, - COMMERCE_OTHER, - COMMERCE_PRODUCT, - COMMERCE_RESTAURANT, - COMMERCE_SERVICE, - COMMERCE_TRAVEL_FLIGHT, - COMMERCE_TRAVEL_HOTEL, - COMMERCE_TRAVEL_OTHER, - GAME_STATE, - MEDIA_IMAGE, - MEDIA_MIXED, - MEDIA_MUSIC, - MEDIA_OTHER, - MEDIA_VIDEO, - OTHER, - TEXT_ARTICLE, - TEXT_BLOG, - TEXT_OTHER, - TEXT_RECIPE, - TEXT_REVIEW, - TEXT_SEARCH_RESULTS, - TEXT_STORY, - TEXT_TECHNICAL_DOC -} - -BranchContentSchema getValueContentSchema(String name) { - BranchContentSchema? schema; - for (BranchContentSchema contentSchema in BranchContentSchema.values) { - if (contentSchema.toString() == name) { - schema = contentSchema; - break; - } - } - return schema!; -} - -String? getContentSchemaString(BranchContentSchema? contentSchema) { - if (contentSchema == null) return null; - return contentSchema.toString().split('.').last; -} +part of flutter_branch_sdk_objects; + +enum BranchContentSchema { + COMMERCE_AUCTION, + COMMERCE_BUSINESS, + COMMERCE_OTHER, + COMMERCE_PRODUCT, + COMMERCE_RESTAURANT, + COMMERCE_SERVICE, + COMMERCE_TRAVEL_FLIGHT, + COMMERCE_TRAVEL_HOTEL, + COMMERCE_TRAVEL_OTHER, + GAME_STATE, + MEDIA_IMAGE, + MEDIA_MIXED, + MEDIA_MUSIC, + MEDIA_OTHER, + MEDIA_VIDEO, + OTHER, + TEXT_ARTICLE, + TEXT_BLOG, + TEXT_OTHER, + TEXT_RECIPE, + TEXT_REVIEW, + TEXT_SEARCH_RESULTS, + TEXT_STORY, + TEXT_TECHNICAL_DOC +} + +BranchContentSchema getValueContentSchema(String name) { + BranchContentSchema? schema; + for (BranchContentSchema contentSchema in BranchContentSchema.values) { + if (contentSchema.toString() == name) { + schema = contentSchema; + break; + } + } + return schema!; +} + +String? getContentSchemaString(BranchContentSchema? contentSchema) { + if (contentSchema == null) return null; + return contentSchema.toString().split('.').last; +} diff --git a/lib/src/link_properties.dart b/lib/src/objects/link_properties.dart similarity index 87% rename from lib/src/link_properties.dart rename to lib/src/objects/link_properties.dart index d8f01b63..717db1f9 100644 --- a/lib/src/link_properties.dart +++ b/lib/src/objects/link_properties.dart @@ -1,76 +1,76 @@ -part of flutter_branch_sdk_objects; -/* -* Class for representing any additional information that is specific to the link. -* Use this class to specify the properties of a deep link such as channel, feature etc and any control params associated with the link. -* -*/ - -class BranchLinkProperties { - List tags = const []; - final String feature; - final String alias; - final String stage; - final int matchDuration; - Map _controlParams = {}; - final String channel; - final String campaign; - - BranchLinkProperties( - {this.channel = '', - this.feature = '', - this.alias = '', - this.matchDuration = 0, - this.stage = '', - this.tags = const [], - this.campaign = ''}); - - void addTags(String tag) { - tags.add(tag); - } - - List getTags() { - return this.tags; - } - - Map getControlParams() { - return this._controlParams; - } - - BranchLinkProperties addControlParam(String key, dynamic value) { - this._controlParams[key] = value; - return this; - } - - Map toMap() { - Map ret = {}; - - if (tags.length > 0) ret['tags'] = tags; - if (feature.isNotEmpty) ret['feature'] = feature; - if (alias.isNotEmpty) ret['alias'] = alias; - if (stage.isNotEmpty) ret['stage'] = stage; - if (matchDuration > 0) ret['matchDuration'] = matchDuration; - if (_controlParams.isNotEmpty) ret['controlParams'] = _controlParams; - if (channel.isNotEmpty) ret['channel'] = channel; - if (campaign.isNotEmpty) ret['campaign'] = campaign; - if (ret.isEmpty) { - throw ArgumentError('Link Properties is required'); - } - return ret; - } - - Map toMapWeb() { - Map ret = {}; - - if (tags.length > 0) ret['tags'] = tags; - if (feature.isNotEmpty) ret['feature'] = feature; - if (alias.isNotEmpty) ret['alias'] = alias; - if (stage.isNotEmpty) ret['stage'] = stage; - if (matchDuration > 0) ret['matchDuration'] = matchDuration; - if (channel.isNotEmpty) ret['channel'] = channel; - if (campaign.isNotEmpty) ret['campaign'] = campaign; - if (ret.isEmpty) { - throw ArgumentError('Link Properties is required'); - } - return ret; - } -} +part of flutter_branch_sdk_objects; +/* +* Class for representing any additional information that is specific to the link. +* Use this class to specify the properties of a deep link such as channel, feature etc and any control params associated with the link. +* +*/ + +class BranchLinkProperties { + List tags = const []; + final String feature; + final String alias; + final String stage; + final int matchDuration; + final Map _controlParams = {}; + final String channel; + final String campaign; + + BranchLinkProperties( + {this.channel = '', + this.feature = '', + this.alias = '', + this.matchDuration = 0, + this.stage = '', + this.tags = const [], + this.campaign = ''}); + + void addTags(String tag) { + tags.add(tag); + } + + List getTags() { + return tags; + } + + Map getControlParams() { + return _controlParams; + } + + BranchLinkProperties addControlParam(String key, dynamic value) { + _controlParams[key] = value; + return this; + } + + Map toMap() { + Map ret = {}; + + if (tags.isNotEmpty) ret['tags'] = tags; + if (feature.isNotEmpty) ret['feature'] = feature; + if (alias.isNotEmpty) ret['alias'] = alias; + if (stage.isNotEmpty) ret['stage'] = stage; + if (matchDuration > 0) ret['matchDuration'] = matchDuration; + if (_controlParams.isNotEmpty) ret['controlParams'] = _controlParams; + if (channel.isNotEmpty) ret['channel'] = channel; + if (campaign.isNotEmpty) ret['campaign'] = campaign; + if (ret.isEmpty) { + throw ArgumentError('Link Properties is required'); + } + return ret; + } + + Map toMapWeb() { + Map ret = {}; + + if (tags.isNotEmpty) ret['tags'] = tags; + if (feature.isNotEmpty) ret['feature'] = feature; + if (alias.isNotEmpty) ret['alias'] = alias; + if (stage.isNotEmpty) ret['stage'] = stage; + if (matchDuration > 0) ret['matchDuration'] = matchDuration; + if (channel.isNotEmpty) ret['channel'] = channel; + if (campaign.isNotEmpty) ret['campaign'] = campaign; + if (ret.isEmpty) { + throw ArgumentError('Link Properties is required'); + } + return ret; + } +} diff --git a/lib/src/web/branch_js.dart b/lib/src/web/branch_js.dart index 4acafc72..bf6a0b03 100644 --- a/lib/src/web/branch_js.dart +++ b/lib/src/web/branch_js.dart @@ -1,956 +1,956 @@ -@JS() -library branchjs; - -// ignore: avoid_web_libraries_in_flutter -import 'dart:js'; - -import 'package:js/js.dart'; - -@JS('JSON.stringify') -external String jsonStringify(Object obj); - -@JS('JSON.parse') -external dynamic jsonParse(String str); - -@JS('navigator.share') -external dynamic navigatorShare(Object data); - -@JS('prompt') -external dynamic browserPrompt(String message, [String data]); - -@JS('branch') -class BranchJS { - /// addListener(event, listener) - /// Parameters - /// - /// event: String, optional - Specify which events you would like to listen for. If - /// not defined, the observer will recieve all events. - /// - /// listener: function, required - Listening function that will recieves an - /// event as a string and optional data as an object. - /// - /// The Branch Web SDK includes a simple event listener, that currently only publishes events for - /// Journeys events. - /// Future development will include the ability to subscribe to events related to all other Web - /// SDK functionality. - /// - /// Example - /// - /// var listener = function(event, data) { console.log(event, data); } - /// - /// // Specify an event to listen for - /// branch.addListener('willShowJourney', listener); - /// - /// // Listen for all events - /// branch.addListener(listener); - /// Available Journey Events: - /// - /// willShowJourney: Journey is about to be shown. - /// didShowJourney: Journey's entrance animation has completed and it is being shown to the user. - /// willNotShowJourney: Journey will not be shown and no other events will be emitted. - /// didClickJourneyCTA: User clicked on Journey's CTA button. - /// didClickJourneyClose: User clicked on Journey's close button. - /// willCloseJourney: Journey close animation has started. - /// didCloseJourney: Journey's close animation has completed and it is no longer visible to the user. - /// didCallJourneyClose: Emitted when developer calls branch.closeJourney() to dismiss Journey. - @JS('addListener') - external static void addListener([String event, Function listener]); - - // Some internal method not documented - // @JS('applyCode') - // external static void applyCode(); - - /// autoAppIndex(data, callback) - /// Parameters - /// - /// data: Object, optional - Information on how to build your App Indexing tags for your webpage - /// - /// callback: function, optional - Returns an error string if unsuccessful - /// - /// This function generates and inserts Firebase App Indexing tags between the section of your webpage. - /// Once inserted, these tags will help Google index and surface content from your App in Google Search. - /// - /// Listed below are optional parameters which can be used to build your page's App Indexing Tags: - /// - /// Key Value - /// "androidPackageName" Android App's package name - /// "androidURL" A custom scheme for your Android App such as: example/home/cupertino/12345 where example is the App's URI scheme and home/cupertino/12345 routes to unique content in the App - /// "iosAppId" iTunes App Store ID for your iOS App - /// "iosURL" A custom scheme for your iOS App such as: example/home/cupertino/12345 - /// "data" Any additional deep link data that you would like to pass to your App - /// Resultant Firebase App Indexing tags will have the following format: - /// - /// Text - /// Text - /// JavaScript - /// - /// - /// Example - /// - /// branch.autoAppIndex({ - /// iosAppId:'123456789', - /// iosURL:'example/home/cupertino/12345', - /// androidPackageName:'com.somecompany.app', - /// androidURL:'example/home/cupertino/12345', - /// data:{"walkScore":65, "transitScore":50} - /// }, function(err) { console.log(err); }); - @JS('autoAppIndex') - external static void autoAppIndex([data, Function callback]); - - // No documentation in full reference - // @JS('banner') - // external static void banner(); - - // No documentation in full reference - // @JS('closeBanner') - // external static void closeBanner(); - - /// closeJourney(callback) - /// Parameters - /// - /// callback: function, optional - /// - /// Journeys include a close button the user can click, but you may want to close the - /// Journey with a timeout, or via some other user interaction with your web app. In this case, - /// closing the Journey is very simple by calling Branch.closeJourney(). - /// - /// Usage - /// - /// branch.closeJourney(function(err) { console.log(err); }); - @JS('closeJourney') - external static void closeJourney([Function callback]); - - /// creditHistory(options, callback) - /// Parameters - /// - /// options: Object, optional - options controlling the returned history - /// - /// callback: function, required - returns an array with credit history - /// data - /// - /// This call will retrieve the entire history of credits and redemptions from the individual user. - /// Properties available in the options object: - /// - /// Key Value - /// bucket optional (max 63 characters) - The bucket from which to retrieve credit transactions. - /// begin_after_id optional - The credit transaction id of the last item in the previous retrieval. Retrieval will start from the transaction next to it. If none is specified, retrieval starts from the very beginning in the transaction history, depending on the order. - /// length optional - The number of credit transactions to retrieve. If none is specified, up to 100 credit transactions will be retrieved. - /// direction DEPRECATED - The order of credit transactions to retrieve. If direction is 1, retrieval is in least recent first order; If direction is 0, or if none is specified, retrieval is in most recent first order. No longer supported. - /// Usage - /// - /// branch.creditHistory( - /// options, - /// callback(err, data) - /// ); - /// Example - /// - /// branch.creditHistory( - /// { - /// "length":50, - /// "direction":0, // no longer supported. - /// "begin_after_id":"123456789012345", - /// "bucket":"default" - /// } - /// callback (err, data) - /// ); - /// Callback Format - /// - /// callback( - /// "Error message", - /// [ - /// { - /// "transaction": { - /// "date": "2014-10-14T01:54:40.425Z", - /// "id": "50388077461373184", - /// "bucket": "default", - /// "type": 0, - /// "amount": 5 - /// }, - /// "referrer": "12345678", - /// "referree": null - /// }, - /// { - /// "transaction": { - /// "date": "2014-10-14T01:55:09.474Z", - /// "id": "50388199301710081", - /// "bucket": "default", - /// "type": 2, - /// "amount": -3 - /// }, - /// "referrer": null, - /// "referree": "12345678" - /// } - /// ] - /// ); - @JS('creditHistory') - external static void creditHistory([options, Function callback]); - - /// credits(callback) - /// Parameters - /// - /// callback: function, required - returns an object with credit data. - /// - /// Formerly showCredits()] - /// - /// This call will retrieve the entire history of credits and redemptions from the individual user. - /// - /// Usage - /// - /// branch.credits( - /// callback (err, data) - /// ); - /// Callback Format - /// - /// callback( - /// "Error message", - /// { - /// 'default': 15, - /// 'other bucket': 9 - /// } - /// ); - @JS('credits') - external static void credits(Function callback); - - /// data(callback) - /// Parameters - /// - /// callback: function, optional - callback to read the - /// session data. - /// - /// Returns the same session information and any referring data, as - /// Branch.init, but does not require the app_id. This is meant to be called - /// after Branch.init has been called if you need the session information at a - /// later point. - /// If the Branch session has already been initialized, the callback will return - /// immediately, otherwise, it will return once Branch has been initialized. - @JS('data') - external static void data([Function callback]); - - /// deepview(data, options, callback) - /// Parameters - /// - /// data: Object, required - object of all link data, same as branch.link(). - /// - /// options: Object, optional - { make_new_link: whether to create a new link even if - /// one already exists. open_app, whether to try to open the app passively (as opposed to - /// opening it upon user clicking); defaults to true - /// }. - /// - /// callback: function, optional - returns an error if the API call is unsuccessful - /// - /// Turns the current page into a "deepview" – a preview of app content. This gives the page two - /// special behaviors: - /// - /// When the page is viewed on a mobile browser, if the user has the app - /// installed on their phone, we will try to open the app automaticaly and deeplink them to this content (this can be toggled off by turning open_app to false, but this is not recommended). - /// Provides a callback to open the app directly, accessible as branch.deepviewCta(); - /// you'll want to have a button on your web page that says something like "View in app", which calls this function. - /// See this tutorial for a full - /// guide on how to use the deepview functionality of the Web SDK. - /// - /// Usage - /// - /// branch.deepview( - /// data, - /// options, - /// callback (err) - /// ); - /// Example - /// - /// branch.deepview( - /// { - /// channel: 'facebook', - /// data: { - /// mydata: 'content of my data', - /// foo: 'bar', - /// '$deeplink_path': 'item_id=12345' - /// }, - /// feature: 'dashboard', - /// stage: 'new user', - /// tags: [ 'tag1', 'tag2' ], - /// }, - /// { - /// make_new_link: true, - /// open_app: true - /// }, - /// function(err) { - /// console.log(err || 'no error'); - /// } - /// ); - /// Callback Format - /// - /// callback( - /// "Error message" - /// ); - @JS('deepview') - external static void deepview(Object data, - [Object options, Function callback]); - - /// deepviewCta() - /// Perform the branch deepview CTA (call to action) on mobile after branch.deepview() call is - /// finished. If the branch.deepview() call is finished with no error, when branch.deepviewCta() is called, - /// an attempt is made to open the app and deeplink the end user into it; if the end user does not - /// have the app installed, they will be redirected to the platform-appropriate app stores. If on the - /// other hand, branch.deepview() returns with an error, branch.deepviewCta() will fall back to - /// redirect the user using - /// Branch dynamic links. - /// - /// If branch.deepview() has not been called, an error will arise with a reminder to call - /// branch.deepview() first. - /// - /// Usage - /// - /// $('a.deepview-cta').click(branch.deepviewCta); // If you are using jQuery - /// - /// document.getElementById('my-elem').onClick = branch.deepviewCta; // Or generally - /// - /// // In HTML - /// - /// // We recommend to assign deepviewCta in deepview callback: - /// branch.deepview(data, option, function(err) { - /// if (err) { - /// throw err; - /// } - /// $('a.deepview-cta').click(branch.deepviewCta); - /// }); - /// - /// // You can call this function any time after branch.deepview() is finished by simply: - /// branch.deepviewCta(); - /// - /// When debugging, please call branch.deepviewCta() with an error callback like so: - /// - /// branch.deepviewCta(function(err) { - /// if (err) { - /// console.log(err); - /// } - /// }); - /// Referral System Rewarding Functionality - /// In a standard referral system, you have 2 parties: the original user and the invitee. Our system - /// is flexible enough to handle rewards for all users for any actions. Here are a couple example - /// scenarios: - /// - /// Reward the original user for taking action (eg. inviting, purchasing, etc) - /// Reward the invitee for installing the app from the original user's referral link - /// Reward the original user when the invitee takes action (eg. give the original user credit when - /// their the invitee buys something) - /// These reward definitions are created on the dashboard, under the 'Reward Rules' section in the - /// 'Referrals' tab on the dashboard. - /// - /// Warning: For a referral program, you should not use unique awards for custom events and redeem - /// pre-identify call. This can allow users to cheat the system. - @JS('deepviewCta') - external static void deepviewCta([Function errorCallback]); - - /// first(callback) - /// Parameters - /// - /// callback: function, optional - callback to read the - /// session data. - /// - /// Returns the same session information and any referring data, as - /// Branch.init did when the app was first installed. This is meant to be called - /// after Branch.init has been called if you need the first session information at a - /// later point. - /// If the Branch session has already been initialized, the callback will return - /// immediately, otherwise, it will return once Branch has been initialized. - @JS('first') - external static void first([Function callback]); - - // No documentation on reference - // @JS('getCode') - // external static void getCode(); - - /// init(branch_key, options, callback) - /// Parameters - /// - /// branch_key: string, required - Your Branch live key, or (deprecated) your app id. - /// - /// options: Object, optional - { }. - /// - /// callback: function, optional - callback to read the - /// session data. - /// - /// Adding the Branch script to your page automatically creates a window.branch - /// object with all the external methods described below. All calls made to - /// Branch methods are stored in a queue, so even if the SDK is not fully - /// instantiated, calls made to it will be queued in the order they were - /// originally called. - /// If the session was opened from a referring link, data() will also return the referring link - /// click as referring_link, which gives you the ability to continue the click flow. - /// - /// The init function on the Branch object initiates the Branch session and - /// creates a new user session, if it doesn't already exist, in - /// sessionStorage. - /// - /// Useful Tip: The init function returns a data object where you can read - /// the link the user was referred by. - /// - /// Properties available in the options object: - /// - /// Key Value - /// branch_match_id optional - string. The current user's browser-fingerprint-id. The value of this parameter should be the same as the value of ?branch_match_id (automatically appended by Branch after a link click). _Only necessary if ?_branch_match_id is lost due to multiple redirects in your flow. - /// branch_view_id optional - string. If you would like to test how Journeys render on your page before activating them, you can set the value of this parameter to the id of the view you are testing. Only necessary when testing a view related to a Journey. - /// no_journeys optional - boolean. When true, prevents Journeys from appearing on current page. - /// disable_entry_animation optional - boolean. When true, prevents a Journeys entry animation. - /// disable_exit_animation optional - boolean. When true, prevents a Journeys exit animation. - /// retries optional - integer. Value specifying the number of times that a Branch API call can be re-attempted. Default 2. - /// retry_delay optional - integer . Amount of time in milliseconds to wait before re-attempting a timed-out request to the Branch API. Default 200 ms. - /// timeout optional - integer. Duration in milliseconds that the system should wait for a response before considering any Branch API call to have timed out. Default 5000 ms. - /// metadata optional - object. Key-value pairs used to target Journeys users via the "is viewing a page with metadata key" filter. - /// nonce optional - string. A nonce value that will be added to branch-journey-cta injected script. Used to allow that script from a Content Security Policy. - /// tracking_disabled optional - boolean. true disables tracking - /// Usage - /// - /// branch.init( - /// branch_key, - /// options, - /// callback (err, data), - /// ); - /// Callback Format - /// - /// callback( - /// "Error message", - /// { - /// data_parsed: { }, // If the user was referred from a link, and the link has associated data, the data is passed in here. - /// referring_identity: '12345', // If the user was referred from a link, and the link was created by a user with an identity, that identity is here. - /// has_app: true, // Does the user have the app installed already? - /// identity: 'BranchUser', // Unique string that identifies the user - /// ~referring_link: 'https://bnc.lt/c/jgg75-Gjd3' // The referring link click, if available. - /// } - /// ); - /// Note: Branch.init must be called prior to calling any other Branch functions. - @JS('init') - external static void init(String branchKey, - [Object? options, Function? callback]); - - /// link(data, callback) - /// Parameters - /// - /// data: Object, required - link data and metadata. - /// - /// callback: function, required - returns a string of the Branch deep - /// linking URL. - /// - /// Formerly createLink() - /// - /// Creates and returns a deep linking URL. The data parameter can include an - /// object with optional data you would like to store, including Facebook - /// Open Graph data. - /// - /// data The dictionary to embed with the link. Accessed as session or install parameters from - /// the SDK. - /// - /// Note - /// You can customize the Facebook OG tags of each URL if you want to dynamically share content by - /// using the following optional keys in the data dictionary. Please use this - /// Facebook tool to debug your OG tags! - /// - /// Key Value - /// "$og_title" The title you'd like to appear for the link in social media - /// "$og_description" The description you'd like to appear for the link in social media - /// "$og_image_url" The URL for the image you'd like to appear for the link in social media - /// "$og_video" The URL for the video - /// "$og_url" The URL you'd like to appear - /// "$og_redirect" If you want to bypass our OG tags and use your own, use this key with the URL that contains your site's metadata. - /// Also, you can set custom redirection by inserting the following optional keys in the dictionary: - /// - /// Key Value - /// "$desktop_url" Where to send the user on a desktop or laptop. By default it is the Branch-hosted text-me service - /// "$android_url" The replacement URL for the Play Store to send the user if they don't have the app. Only necessary if you want a mobile web splash - /// "$ios_url" The replacement URL for the App Store to send the user if they don't have the app. Only necessary if you want a mobile web splash - /// "$ipad_url" Same as above but for iPad Store - /// "$fire_url" Same as above but for Amazon Fire Store - /// "$blackberry_url" Same as above but for Blackberry Store - /// "$windows_phone_url" Same as above but for Windows Store - /// "$after_click_url" When a user returns to the browser after going to the app, take them to this URL. iOS only; Android coming soon - /// You have the ability to control the direct deep linking of each link as well: - /// - /// Key Value - /// "$deeplink_path" The value of the deep link path that you'd like us to append to your URI. For example, you could specify "$deeplink_path": "radio/station/456" and we'll open the app with the URI "yourapp://radio/station/456?link_click_id=branch-identifier". This is primarily for supporting legacy deep linking infrastructure. - /// "$always_deeplink" true or false. (default is not to deep link first) This key can be specified to have our linking service force try to open the app, even if we're not sure the user has the app installed. If the app is not installed, we fall back to the respective app store or $platform_url key. By default, we only open the app if we've seen a user initiate a session in your app from a Branch link (has been cookied and deep linked by Branch). - /// Usage - /// - /// branch.link( - /// data, - /// callback (err, link) - /// ); - /// Example - /// - /// branch.link({ - /// tags: [ 'tag1', 'tag2' ], - /// channel: 'facebook', - /// feature: 'dashboard', - /// stage: 'new user', - /// data: { - /// mydata: 'something', - /// foo: 'bar', - /// '$desktop_url': 'http://myappwebsite.com', - /// '$ios_url': 'http://myappwebsite.com/ios', - /// '$ipad_url': 'http://myappwebsite.com/ipad', - /// '$android_url': 'http://myappwebsite.com/android', - /// '$og_app_id': '12345', - /// '$og_title': 'My App', - /// '$og_description': 'My app\'s description.', - /// '$og_image_url': 'http://myappwebsite.com/image.png' - /// } - /// }, function(err, link) { - /// console.log(err, link); - /// }); - /// Callback Format - /// - /// callback( - /// "Error message", - /// 'https://bnc.lt/l/3HZMytU-BW' // Branch deep linking URL - /// ); - @JS('link') - external static void link(Object data, Function callback); - - /// logout(callback) - /// Parameters - /// - /// callback: function, optional - /// - /// Logs out the current session, replaces session IDs and identity IDs. - /// - /// Usage - /// - /// branch.logout( - /// callback (err) - /// ); - /// Callback Format - /// - /// callback( - /// "Error message" - /// ); - @JS('logout') - external static void logout([Function callback]); - - /// redeem(amount, bucket, callback) - /// Parameters - /// - /// amount: number, required - an amount (int) of number of credits to redeem - /// - /// bucket: string, required - the name of the bucket (string) of which bucket to redeem the credits from - /// - /// callback: function, optional - returns an error if unsuccessful - /// - /// Formerly redeemCredits()] - /// - /// Credits are stored in buckets, which you can define as points, currency, whatever makes sense - /// for your app. When you want to redeem credits, call this method with the number of points to be - /// redeemed, and the bucket to redeem them from. - /// - /// branch.redeem( - /// amount, // Amount of credits to be redeemed - /// bucket, // String of bucket name to redeem credits from - /// callback (err) - /// ); - /// Example - /// - /// branch.redeem( - /// 5, - /// "Rubies", - /// function(err) { - /// console.log(err); - /// } - /// ); - /// Callback Format - /// - /// callback("Error message"); - @JS('redeem') - external static void redeem(int amount, String bucket, [Function callback]); - - // No documentation on reference - // @JS('referrals') - // external static void referrals(); - - /// removeListener(listener) - /// Parameters - /// - /// listener: function, required - Reference to the listening function you - /// would like to remove. note: this must be the same reference that was passed to - /// branch.addListener(), not an identical clone of the function. - /// - /// Remove the listener from observations, if it is present. Not that this function must be - /// passed a referrence to the same function that was passed to branch.addListener(), not - /// just an identical clone of the function. - @JS('removeListener') - external static void removeListener(Function listener); - - /// sendSMS(phone, linkData, options, callback) - /// Parameters - /// - /// phone: string, required - phone number to send SMS to - /// - /// linkData: Object, required - object of link data - /// - /// options: Object, optional - options: make_new_link, which forces the creation of a - /// new link even if one already exists - /// - /// callback: function, optional - Returns an error if unsuccessful - /// - /// Formerly SMSLink() - /// - /// A robust function to give your users the ability to share links via SMS. If - /// the user navigated to this page via a Branch link, sendSMS will send that - /// same link. Otherwise, it will create a new link with the data provided in - /// the params argument. sendSMS also registers a click event with the - /// channel pre-filled with 'sms' before sending an sms to the provided - /// phone parameter. This way the entire link click event is recorded starting - /// with the user sending an sms. - /// - /// Note: sendSMS will automatically send a previously generated link click, - /// along with the data object in the original link. Therefore, it is unneccessary for the - /// data() method to be called to check for an already existing link. If a link already - /// exists, sendSMS will simply ignore the data object passed to it, and send the existing link. - /// If this behavior is not desired, set make_new_link: true in the options object argument - /// of sendSMS, and sendSMS will always make a new link. - /// - /// Supports international SMS. - /// - /// Please note that the destination phone number needs to be from the same country the SMS is being sent from. - /// - /// Usage - /// - /// branch.sendSMS( - /// phone, - /// linkData, - /// options, - /// callback (err, data) - /// ); - /// Example - /// - /// var linkData = { - /// tags: ['tag1', 'tag2'], - /// channel: 'Website', - /// feature: 'TextMeTheApp', - /// data: { - /// // here's how to define the deeplink_path and the custom text on the front end - /// $deeplink_path: `custom_deeplink_path`, - /// //for the custom text, use {{link}} as a macro for link location - /// $custom_sms_text: `Here's my custom text, and here is the {{ link }}`, - /// mydata: 'something', - /// foo: 'bar', - /// '$desktop_url': 'http://myappwebsite.com', - /// '$ios_url': 'http://myappwebsite.com/ios', - /// '$ipad_url': 'http://myappwebsite.com/ipad', - /// '$android_url': 'http://myappwebsite.com/android', - /// '$og_app_id': '12345', - /// '$og_title': 'My App', - /// '$og_description': 'My app\'s description.', - /// '$og_image_url': 'http://myappwebsite.com/image.png' - /// } - /// } - /// branch.sendSMS( - /// '9999999999', - /// linkData, - /// { make_new_link: false }, // Default: false. If set to true, sendSMS will generate a new link even if one already exists. - /// function(err) { console.log(err); } - /// ); - /// Callback Format - /// - /// callback("Error message"); - @JS('sendSMS') - external static void sendSMS(String phone, Object linkData, - [Object options, Function callback]); - - /// setBranchViewData(data) - /// Parameters - /// - /// data: Object, required - object of all link data, same as Branch.link() - /// - /// This function lets you set the deep link data dynamically for a given mobile web Journey. For - /// example, if you desgin a full page interstitial, and want the deep link data to be custom for each - /// page, you'd need to use this function to dynamically set the deep link params on page load. Then, - /// any Journey loaded on that page will inherit these deep link params. - /// - /// Usage - /// - /// branch.setBranchViewData( - /// data // Data for link, same as Branch.link() - /// ); - /// Example - /// - /// branch.setBranchViewData({ - /// tags: ['tag1', 'tag2'], - /// data: { - /// mydata: 'something', - /// foo: 'bar', - /// '$deeplink_path': 'open/item/1234' - /// } - /// }); - @JS('setBranchViewData') - external static void setBranchViewData(Object data); - - /// setIdentity(identity, callback) - /// Parameters - /// - /// identity: string, required - a string uniquely identifying the user - often a user ID - /// or email address. - /// - /// callback: function, optional - callback that returns the user's - /// Branch identity id and unique link. - /// - /// Formerly identify() - /// - /// Sets the identity of a user and returns the data. To use this function, pass - /// a unique string that identifies the user - this could be an email address, - /// UUID, Facebook ID, etc. - /// - /// Usage - /// - /// branch.setIdentity( - /// identity, - /// callback (err, data) - /// ); - /// Callback Format - /// - /// callback( - /// "Error message", - /// { - /// identity_id: '12345', // Server-generated ID of the user identity, stored in `sessionStorage`. - /// link: 'url', // New link to use (replaces old stored link), stored in `sessionStorage`. - /// referring_data_parsed: { }, // Returns the initial referring data for this identity, if exists, as a parsed object. - /// referring_identity: '12345' // Returns the initial referring identity for this identity, if exists. - /// } - /// ); - @JS('setIdentity') - external static void setIdentity(String identity, [Function callback]); - - /// track(event, metadata, callback) - /// Parameters - /// - /// event: string, required - name of the event to be tracked. - /// - /// metadata: Object, optional - object of event metadata. - /// - /// callback: function, optional - /// - /// This function allows you to track any event with supporting metadata. - /// The metadata parameter is a formatted JSON object that can contain - /// any data and has limitless hierarchy - /// - /// Usage - /// - /// branch.track( - /// event, - /// metadata, - /// callback (err) - /// ); - /// Callback Format - /// - /// callback("Error message"); - @JS('track') - external static void track(String event, - [Object metadata, Function callback]); - - // No documentation in reference - // @JS('validateCode') - // external static void validateCode(); - - /// trackCommerceEvent(event, commerce_data, metadata, callback) - /// Parameters - /// - /// event: String, required - Name of the commerce event to be tracked. We currently support 'purchase' events - /// - /// commerce_data: Object, required - Data that describes the commerce event - /// - /// metadata: Object, optional - metadata you may want add to the event - /// - /// callback: function, optional - Returns an error if unsuccessful - /// - /// Sends a user commerce event to the server - /// - /// Use commerce events to track when a user purchases an item in your online store, - /// makes an in-app purchase, or buys a subscription. The commerce events are tracked in - /// the Branch dashboard along with your other events so you can judge the effectiveness of - /// campaigns and other analytics. - /// - /// Usage - /// - /// branch.trackCommerceEvent( - /// event, - /// commerce_data, - /// metadata, - /// callback (err) - /// ); - /// Example - /// - /// var commerce_data = { - /// "revenue": 50.0, - /// "currency": "USD", - /// "transaction_id": "foo-transaction-id", - /// "shipping": 0.0, - /// "tax": 5.0, - /// "affiliation": "foo-affiliation", - /// "products": [ - /// { "sku": "foo-sku-1", "name": "foo-item-1", "price": 45.00, "quantity": 1, "brand": "foo-brand", - /// "category": "Electronics", "variant": "foo-variant-1"}, - /// { "sku": "foo-sku-2", "price": 2.50, "quantity": 2} - /// ], - /// }; - /// - /// var metadata = { "foo": "bar" }; - /// - /// branch.trackCommerceEvent('purchase', commerce_data, metadata, function(err) { - /// if(err) { - /// throw err; - /// } - /// }); - @JS('trackCommerceEvent') - external static void trackCommerceEvent(String name, Object commerceData, - [Object metadata, Function callback]); - - /// logEvent(event, event_data_and_custom_data, content_items, customer_event_alias, callback) - /// Parameters - /// - /// event: String, required - /// - /// event_data_and_custom_data: Object, optional - /// - /// content_items: Array, optional - /// - /// customer_event_alias: String, optional - /// - /// callback: function, optional - /// - /// Register commerce events, content events, user lifecycle events and custom events via logEvent() - /// - /// NOTE: If this is the first time you are integrating our new event tracking feature via logEvent(), please use the latest Branch WebSDK snippet from the Installation section. This has been updated in v2.30.0 of our SDK. - /// - /// The guides below provide information about what keys can be sent when triggering these event types: - /// - /// Logging Commerce Events - /// Logging Content Events - /// Logging User Lifecycle - /// Logging Custom Events - /// Usage for Commerce, Content & User Lifecycle "Standard Events" - /// - /// branch.logEvent( - /// event, - /// event_data_and_custom_data, - /// content_items, - /// customer_event_alias, - /// callback (err) - /// ); - /// Usage for "Custom Events" - /// - /// JavaScript - /// JavaScript - /// branch.logEvent( - /// event, - /// custom_data, - /// callback (err) - /// ); - /// `` - /// **Notes**: - /// - logEvent() sends user_data automatically - /// - When firing Standard Events, send custom and event data as part of the same object - /// - Custom Events do not contain content items and event data - /// - /// ### Example -- How to log a Commerce Event - /// var event_and_custom_data = { - /// "transaction_id": "tras_Id_1232343434", - /// "currency": "USD", - /// "revenue": 180.2, - /// "shipping": 10.5, - /// "tax": 13.5, - /// "coupon": "promo-1234", - /// "affiliation": "high_fi", - /// "description": "Preferred purchase", - /// "purchase_loc": "Palo Alto", - /// "store_pickup": "unavailable" - /// }; - /// var content_items = [ - /// { - /// "$content_schema": "COMMERCE_PRODUCT", - /// "$og_title": "Nike Shoe", - /// "$og_description": "Start loving your steps", - /// "$og_image_url": "http:///example.com/img1.jpg", - /// "$canonical_identifier": "nike/1234", - /// "$publicly_indexable": false, - /// "$price": 101.2, - /// "$locally_indexable": true, - /// "$quantity": 1, - /// "$sku": "1101123445", - /// "$product_name": "Runner", - /// "$product_brand": "Nike", - /// "$product_category": "Sporting Goods", - /// "$product_variant": "XL", - /// "$rating_average": 4.2, - /// "$rating_count": 5, - /// "$rating_max": 2.2, - /// "$creation_timestamp": 1499892854966, - /// "$exp_date": 1499892854966, - /// "$keywords": [ "sneakers", "shoes" ], - /// "$address_street": "230 South LaSalle Street", - /// "$address_city": "Chicago", - /// "$address_region": "IL", - /// "$address_country": "US", - /// "$address_postal_code": "60604", - /// "$latitude": 12.07, - /// "$longitude": -97.5, - /// "$image_captions": [ "my_img_caption1", "my_img_caption_2" ], - /// "$condition": "NEW", - /// "$custom_fields": {"foo1":"bar1","foo2":"bar2"} - /// }, - /// { - /// "$og_title": "Nike Woolen Sox", - /// "$canonical_identifier": "nike/5324", - /// "$og_description": "Fine combed woolen sox for those who love your foot", - /// "$publicly_indexable": false, - /// "$price": 80.2, - /// "$locally_indexable": true, - /// "$quantity": 5, - /// "$sku": "110112467", - /// "$product_name": "Woolen Sox", - /// "$product_brand": "Nike", - /// "$product_category": "Apparel & Accessories", - /// "$product_variant": "Xl", - /// "$rating_average": 3.3, - /// "$rating_count": 5, - /// "$rating_max": 2.8, - /// "$creation_timestamp": 1499892854966 - /// }]; - /// var customer_event_alias = "event alias"; - /// branch.logEvent( - /// "PURCHASE", - /// event_and_custom_data, - /// content_items, - /// customer_event_alias, - /// function(err) { console.log(err); } - /// ); - - @JS('logEvent') - external static void logEvent(String event, - [Object eventDataAndCustomData, - JsArray contentItems, - String customerEventAlias, - Function callback]); - - /// disableTracking(disableTracking) - /// Parameters - /// - /// disableTracking: Boolean, optional - true disables tracking and false re-enables tracking. - /// - /// Notes: - /// - /// disableTracking() without a parameter is a shorthand for disableTracking(true). - /// If a call to disableTracking(false) is made, the WebSDK will re-initialize. Additionally, if tracking_disabled: true is passed - /// as an option to init(), it will be removed during the reinitialization process. - /// Allows User to Remain Private - /// - /// This will prevent any Branch requests from being sent across the network, except for the case of deep linking. - /// If someone clicks a Branch link, but has expressed not to be tracked, we will return deep linking data back to the - /// client but without tracking information. - /// - /// In do-not-track mode, you will still be able to create links and display Journeys however, they will not have identifiable - /// information associated to them. You can change this behavior at any time, by calling the aforementioned function. - /// The do-not-track mode state is persistent: it is saved for the user across browser sessions for the web site. - @JS('disableTracking') - external static void disableTracking([bool disableTracking]); - - @JS('lastAttributedTouchData') - external static void lastAttributedTouchData(attributionWindow, - [Function callback]); -} +@JS() +library branchjs; + +// ignore: avoid_web_libraries_in_flutter +import 'dart:js'; + +import 'package:js/js.dart'; + +@JS('JSON.stringify') +external String jsonStringify(Object obj); + +@JS('JSON.parse') +external dynamic jsonParse(String str); + +@JS('navigator.share') +external dynamic navigatorShare(Object data); + +@JS('prompt') +external dynamic browserPrompt(String message, [String data]); + +@JS('branch') +class BranchJS { + /// addListener(event, listener) + /// Parameters + /// + /// event: String, optional - Specify which events you would like to listen for. If + /// not defined, the observer will recieve all events. + /// + /// listener: function, required - Listening function that will recieves an + /// event as a string and optional data as an object. + /// + /// The Branch Web SDK includes a simple event listener, that currently only publishes events for + /// Journeys events. + /// Future development will include the ability to subscribe to events related to all other Web + /// SDK functionality. + /// + /// Example + /// + /// var listener = function(event, data) { console.log(event, data); } + /// + /// // Specify an event to listen for + /// branch.addListener('willShowJourney', listener); + /// + /// // Listen for all events + /// branch.addListener(listener); + /// Available Journey Events: + /// + /// willShowJourney: Journey is about to be shown. + /// didShowJourney: Journey's entrance animation has completed and it is being shown to the user. + /// willNotShowJourney: Journey will not be shown and no other events will be emitted. + /// didClickJourneyCTA: User clicked on Journey's CTA button. + /// didClickJourneyClose: User clicked on Journey's close button. + /// willCloseJourney: Journey close animation has started. + /// didCloseJourney: Journey's close animation has completed and it is no longer visible to the user. + /// didCallJourneyClose: Emitted when developer calls branch.closeJourney() to dismiss Journey. + @JS('addListener') + external static void addListener([String event, Function listener]); + + // Some internal method not documented + // @JS('applyCode') + // external static void applyCode(); + + /// autoAppIndex(data, callback) + /// Parameters + /// + /// data: Object, optional - Information on how to build your App Indexing tags for your webpage + /// + /// callback: function, optional - Returns an error string if unsuccessful + /// + /// This function generates and inserts Firebase App Indexing tags between the section of your webpage. + /// Once inserted, these tags will help Google index and surface content from your App in Google Search. + /// + /// Listed below are optional parameters which can be used to build your page's App Indexing Tags: + /// + /// Key Value + /// "androidPackageName" Android App's package name + /// "androidURL" A custom scheme for your Android App such as: example/home/cupertino/12345 where example is the App's URI scheme and home/cupertino/12345 routes to unique content in the App + /// "iosAppId" iTunes App Store ID for your iOS App + /// "iosURL" A custom scheme for your iOS App such as: example/home/cupertino/12345 + /// "data" Any additional deep link data that you would like to pass to your App + /// Resultant Firebase App Indexing tags will have the following format: + /// + /// Text + /// Text + /// JavaScript + /// + /// + /// Example + /// + /// branch.autoAppIndex({ + /// iosAppId:'123456789', + /// iosURL:'example/home/cupertino/12345', + /// androidPackageName:'com.somecompany.app', + /// androidURL:'example/home/cupertino/12345', + /// data:{"walkScore":65, "transitScore":50} + /// }, function(err) { console.log(err); }); + @JS('autoAppIndex') + external static void autoAppIndex([data, Function callback]); + + // No documentation in full reference + // @JS('banner') + // external static void banner(); + + // No documentation in full reference + // @JS('closeBanner') + // external static void closeBanner(); + + /// closeJourney(callback) + /// Parameters + /// + /// callback: function, optional + /// + /// Journeys include a close button the user can click, but you may want to close the + /// Journey with a timeout, or via some other user interaction with your web app. In this case, + /// closing the Journey is very simple by calling Branch.closeJourney(). + /// + /// Usage + /// + /// branch.closeJourney(function(err) { console.log(err); }); + @JS('closeJourney') + external static void closeJourney([Function callback]); + + /// creditHistory(options, callback) + /// Parameters + /// + /// options: Object, optional - options controlling the returned history + /// + /// callback: function, required - returns an array with credit history + /// data + /// + /// This call will retrieve the entire history of credits and redemptions from the individual user. + /// Properties available in the options object: + /// + /// Key Value + /// bucket optional (max 63 characters) - The bucket from which to retrieve credit transactions. + /// begin_after_id optional - The credit transaction id of the last item in the previous retrieval. Retrieval will start from the transaction next to it. If none is specified, retrieval starts from the very beginning in the transaction history, depending on the order. + /// length optional - The number of credit transactions to retrieve. If none is specified, up to 100 credit transactions will be retrieved. + /// direction DEPRECATED - The order of credit transactions to retrieve. If direction is 1, retrieval is in least recent first order; If direction is 0, or if none is specified, retrieval is in most recent first order. No longer supported. + /// Usage + /// + /// branch.creditHistory( + /// options, + /// callback(err, data) + /// ); + /// Example + /// + /// branch.creditHistory( + /// { + /// "length":50, + /// "direction":0, // no longer supported. + /// "begin_after_id":"123456789012345", + /// "bucket":"default" + /// } + /// callback (err, data) + /// ); + /// Callback Format + /// + /// callback( + /// "Error message", + /// [ + /// { + /// "transaction": { + /// "date": "2014-10-14T01:54:40.425Z", + /// "id": "50388077461373184", + /// "bucket": "default", + /// "type": 0, + /// "amount": 5 + /// }, + /// "referrer": "12345678", + /// "referree": null + /// }, + /// { + /// "transaction": { + /// "date": "2014-10-14T01:55:09.474Z", + /// "id": "50388199301710081", + /// "bucket": "default", + /// "type": 2, + /// "amount": -3 + /// }, + /// "referrer": null, + /// "referree": "12345678" + /// } + /// ] + /// ); + @JS('creditHistory') + external static void creditHistory([options, Function callback]); + + /// credits(callback) + /// Parameters + /// + /// callback: function, required - returns an object with credit data. + /// + /// Formerly showCredits()] + /// + /// This call will retrieve the entire history of credits and redemptions from the individual user. + /// + /// Usage + /// + /// branch.credits( + /// callback (err, data) + /// ); + /// Callback Format + /// + /// callback( + /// "Error message", + /// { + /// 'default': 15, + /// 'other bucket': 9 + /// } + /// ); + @JS('credits') + external static void credits(Function callback); + + /// data(callback) + /// Parameters + /// + /// callback: function, optional - callback to read the + /// session data. + /// + /// Returns the same session information and any referring data, as + /// Branch.init, but does not require the app_id. This is meant to be called + /// after Branch.init has been called if you need the session information at a + /// later point. + /// If the Branch session has already been initialized, the callback will return + /// immediately, otherwise, it will return once Branch has been initialized. + @JS('data') + external static void data([Function callback]); + + /// deepview(data, options, callback) + /// Parameters + /// + /// data: Object, required - object of all link data, same as branch.link(). + /// + /// options: Object, optional - { make_new_link: whether to create a new link even if + /// one already exists. open_app, whether to try to open the app passively (as opposed to + /// opening it upon user clicking); defaults to true + /// }. + /// + /// callback: function, optional - returns an error if the API call is unsuccessful + /// + /// Turns the current page into a "deepview" – a preview of app content. This gives the page two + /// special behaviors: + /// + /// When the page is viewed on a mobile browser, if the user has the app + /// installed on their phone, we will try to open the app automaticaly and deeplink them to this content (this can be toggled off by turning open_app to false, but this is not recommended). + /// Provides a callback to open the app directly, accessible as branch.deepviewCta(); + /// you'll want to have a button on your web page that says something like "View in app", which calls this function. + /// See this tutorial for a full + /// guide on how to use the deepview functionality of the Web SDK. + /// + /// Usage + /// + /// branch.deepview( + /// data, + /// options, + /// callback (err) + /// ); + /// Example + /// + /// branch.deepview( + /// { + /// channel: 'facebook', + /// data: { + /// mydata: 'content of my data', + /// foo: 'bar', + /// '$deeplink_path': 'item_id=12345' + /// }, + /// feature: 'dashboard', + /// stage: 'new user', + /// tags: [ 'tag1', 'tag2' ], + /// }, + /// { + /// make_new_link: true, + /// open_app: true + /// }, + /// function(err) { + /// console.log(err || 'no error'); + /// } + /// ); + /// Callback Format + /// + /// callback( + /// "Error message" + /// ); + @JS('deepview') + external static void deepview(Object data, + [Object options, Function callback]); + + /// deepviewCta() + /// Perform the branch deepview CTA (call to action) on mobile after branch.deepview() call is + /// finished. If the branch.deepview() call is finished with no error, when branch.deepviewCta() is called, + /// an attempt is made to open the app and deeplink the end user into it; if the end user does not + /// have the app installed, they will be redirected to the platform-appropriate app stores. If on the + /// other hand, branch.deepview() returns with an error, branch.deepviewCta() will fall back to + /// redirect the user using + /// Branch dynamic links. + /// + /// If branch.deepview() has not been called, an error will arise with a reminder to call + /// branch.deepview() first. + /// + /// Usage + /// + /// $('a.deepview-cta').click(branch.deepviewCta); // If you are using jQuery + /// + /// document.getElementById('my-elem').onClick = branch.deepviewCta; // Or generally + /// + /// // In HTML + /// + /// // We recommend to assign deepviewCta in deepview callback: + /// branch.deepview(data, option, function(err) { + /// if (err) { + /// throw err; + /// } + /// $('a.deepview-cta').click(branch.deepviewCta); + /// }); + /// + /// // You can call this function any time after branch.deepview() is finished by simply: + /// branch.deepviewCta(); + /// + /// When debugging, please call branch.deepviewCta() with an error callback like so: + /// + /// branch.deepviewCta(function(err) { + /// if (err) { + /// console.log(err); + /// } + /// }); + /// Referral System Rewarding Functionality + /// In a standard referral system, you have 2 parties: the original user and the invitee. Our system + /// is flexible enough to handle rewards for all users for any actions. Here are a couple example + /// scenarios: + /// + /// Reward the original user for taking action (eg. inviting, purchasing, etc) + /// Reward the invitee for installing the app from the original user's referral link + /// Reward the original user when the invitee takes action (eg. give the original user credit when + /// their the invitee buys something) + /// These reward definitions are created on the dashboard, under the 'Reward Rules' section in the + /// 'Referrals' tab on the dashboard. + /// + /// Warning: For a referral program, you should not use unique awards for custom events and redeem + /// pre-identify call. This can allow users to cheat the system. + @JS('deepviewCta') + external static void deepviewCta([Function errorCallback]); + + /// first(callback) + /// Parameters + /// + /// callback: function, optional - callback to read the + /// session data. + /// + /// Returns the same session information and any referring data, as + /// Branch.init did when the app was first installed. This is meant to be called + /// after Branch.init has been called if you need the first session information at a + /// later point. + /// If the Branch session has already been initialized, the callback will return + /// immediately, otherwise, it will return once Branch has been initialized. + @JS('first') + external static void first([Function callback]); + + // No documentation on reference + // @JS('getCode') + // external static void getCode(); + + /// init(branch_key, options, callback) + /// Parameters + /// + /// branch_key: string, required - Your Branch live key, or (deprecated) your app id. + /// + /// options: Object, optional - { }. + /// + /// callback: function, optional - callback to read the + /// session data. + /// + /// Adding the Branch script to your page automatically creates a window.branch + /// object with all the external methods described below. All calls made to + /// Branch methods are stored in a queue, so even if the SDK is not fully + /// instantiated, calls made to it will be queued in the order they were + /// originally called. + /// If the session was opened from a referring link, data() will also return the referring link + /// click as referring_link, which gives you the ability to continue the click flow. + /// + /// The init function on the Branch object initiates the Branch session and + /// creates a new user session, if it doesn't already exist, in + /// sessionStorage. + /// + /// Useful Tip: The init function returns a data object where you can read + /// the link the user was referred by. + /// + /// Properties available in the options object: + /// + /// Key Value + /// branch_match_id optional - string. The current user's browser-fingerprint-id. The value of this parameter should be the same as the value of ?branch_match_id (automatically appended by Branch after a link click). _Only necessary if ?_branch_match_id is lost due to multiple redirects in your flow. + /// branch_view_id optional - string. If you would like to test how Journeys render on your page before activating them, you can set the value of this parameter to the id of the view you are testing. Only necessary when testing a view related to a Journey. + /// no_journeys optional - boolean. When true, prevents Journeys from appearing on current page. + /// disable_entry_animation optional - boolean. When true, prevents a Journeys entry animation. + /// disable_exit_animation optional - boolean. When true, prevents a Journeys exit animation. + /// retries optional - integer. Value specifying the number of times that a Branch API call can be re-attempted. Default 2. + /// retry_delay optional - integer . Amount of time in milliseconds to wait before re-attempting a timed-out request to the Branch API. Default 200 ms. + /// timeout optional - integer. Duration in milliseconds that the system should wait for a response before considering any Branch API call to have timed out. Default 5000 ms. + /// metadata optional - object. Key-value pairs used to target Journeys users via the "is viewing a page with metadata key" filter. + /// nonce optional - string. A nonce value that will be added to branch-journey-cta injected script. Used to allow that script from a Content Security Policy. + /// tracking_disabled optional - boolean. true disables tracking + /// Usage + /// + /// branch.init( + /// branch_key, + /// options, + /// callback (err, data), + /// ); + /// Callback Format + /// + /// callback( + /// "Error message", + /// { + /// data_parsed: { }, // If the user was referred from a link, and the link has associated data, the data is passed in here. + /// referring_identity: '12345', // If the user was referred from a link, and the link was created by a user with an identity, that identity is here. + /// has_app: true, // Does the user have the app installed already? + /// identity: 'BranchUser', // Unique string that identifies the user + /// ~referring_link: 'https://bnc.lt/c/jgg75-Gjd3' // The referring link click, if available. + /// } + /// ); + /// Note: Branch.init must be called prior to calling any other Branch functions. + @JS('init') + external static void init(String branchKey, + [Object? options, Function? callback]); + + /// link(data, callback) + /// Parameters + /// + /// data: Object, required - link data and metadata. + /// + /// callback: function, required - returns a string of the Branch deep + /// linking URL. + /// + /// Formerly createLink() + /// + /// Creates and returns a deep linking URL. The data parameter can include an + /// object with optional data you would like to store, including Facebook + /// Open Graph data. + /// + /// data The dictionary to embed with the link. Accessed as session or install parameters from + /// the SDK. + /// + /// Note + /// You can customize the Facebook OG tags of each URL if you want to dynamically share content by + /// using the following optional keys in the data dictionary. Please use this + /// Facebook tool to debug your OG tags! + /// + /// Key Value + /// "$og_title" The title you'd like to appear for the link in social media + /// "$og_description" The description you'd like to appear for the link in social media + /// "$og_image_url" The URL for the image you'd like to appear for the link in social media + /// "$og_video" The URL for the video + /// "$og_url" The URL you'd like to appear + /// "$og_redirect" If you want to bypass our OG tags and use your own, use this key with the URL that contains your site's metadata. + /// Also, you can set custom redirection by inserting the following optional keys in the dictionary: + /// + /// Key Value + /// "$desktop_url" Where to send the user on a desktop or laptop. By default it is the Branch-hosted text-me service + /// "$android_url" The replacement URL for the Play Store to send the user if they don't have the app. Only necessary if you want a mobile web splash + /// "$ios_url" The replacement URL for the App Store to send the user if they don't have the app. Only necessary if you want a mobile web splash + /// "$ipad_url" Same as above but for iPad Store + /// "$fire_url" Same as above but for Amazon Fire Store + /// "$blackberry_url" Same as above but for Blackberry Store + /// "$windows_phone_url" Same as above but for Windows Store + /// "$after_click_url" When a user returns to the browser after going to the app, take them to this URL. iOS only; Android coming soon + /// You have the ability to control the direct deep linking of each link as well: + /// + /// Key Value + /// "$deeplink_path" The value of the deep link path that you'd like us to append to your URI. For example, you could specify "$deeplink_path": "radio/station/456" and we'll open the app with the URI "yourapp://radio/station/456?link_click_id=branch-identifier". This is primarily for supporting legacy deep linking infrastructure. + /// "$always_deeplink" true or false. (default is not to deep link first) This key can be specified to have our linking service force try to open the app, even if we're not sure the user has the app installed. If the app is not installed, we fall back to the respective app store or $platform_url key. By default, we only open the app if we've seen a user initiate a session in your app from a Branch link (has been cookied and deep linked by Branch). + /// Usage + /// + /// branch.link( + /// data, + /// callback (err, link) + /// ); + /// Example + /// + /// branch.link({ + /// tags: [ 'tag1', 'tag2' ], + /// channel: 'facebook', + /// feature: 'dashboard', + /// stage: 'new user', + /// data: { + /// mydata: 'something', + /// foo: 'bar', + /// '$desktop_url': 'http://myappwebsite.com', + /// '$ios_url': 'http://myappwebsite.com/ios', + /// '$ipad_url': 'http://myappwebsite.com/ipad', + /// '$android_url': 'http://myappwebsite.com/android', + /// '$og_app_id': '12345', + /// '$og_title': 'My App', + /// '$og_description': 'My app\'s description.', + /// '$og_image_url': 'http://myappwebsite.com/image.png' + /// } + /// }, function(err, link) { + /// console.log(err, link); + /// }); + /// Callback Format + /// + /// callback( + /// "Error message", + /// 'https://bnc.lt/l/3HZMytU-BW' // Branch deep linking URL + /// ); + @JS('link') + external static void link(Object data, Function callback); + + /// logout(callback) + /// Parameters + /// + /// callback: function, optional + /// + /// Logs out the current session, replaces session IDs and identity IDs. + /// + /// Usage + /// + /// branch.logout( + /// callback (err) + /// ); + /// Callback Format + /// + /// callback( + /// "Error message" + /// ); + @JS('logout') + external static void logout([Function callback]); + + /// redeem(amount, bucket, callback) + /// Parameters + /// + /// amount: number, required - an amount (int) of number of credits to redeem + /// + /// bucket: string, required - the name of the bucket (string) of which bucket to redeem the credits from + /// + /// callback: function, optional - returns an error if unsuccessful + /// + /// Formerly redeemCredits()] + /// + /// Credits are stored in buckets, which you can define as points, currency, whatever makes sense + /// for your app. When you want to redeem credits, call this method with the number of points to be + /// redeemed, and the bucket to redeem them from. + /// + /// branch.redeem( + /// amount, // Amount of credits to be redeemed + /// bucket, // String of bucket name to redeem credits from + /// callback (err) + /// ); + /// Example + /// + /// branch.redeem( + /// 5, + /// "Rubies", + /// function(err) { + /// console.log(err); + /// } + /// ); + /// Callback Format + /// + /// callback("Error message"); + @JS('redeem') + external static void redeem(int amount, String bucket, [Function callback]); + + // No documentation on reference + // @JS('referrals') + // external static void referrals(); + + /// removeListener(listener) + /// Parameters + /// + /// listener: function, required - Reference to the listening function you + /// would like to remove. note: this must be the same reference that was passed to + /// branch.addListener(), not an identical clone of the function. + /// + /// Remove the listener from observations, if it is present. Not that this function must be + /// passed a referrence to the same function that was passed to branch.addListener(), not + /// just an identical clone of the function. + @JS('removeListener') + external static void removeListener(Function listener); + + /// sendSMS(phone, linkData, options, callback) + /// Parameters + /// + /// phone: string, required - phone number to send SMS to + /// + /// linkData: Object, required - object of link data + /// + /// options: Object, optional - options: make_new_link, which forces the creation of a + /// new link even if one already exists + /// + /// callback: function, optional - Returns an error if unsuccessful + /// + /// Formerly SMSLink() + /// + /// A robust function to give your users the ability to share links via SMS. If + /// the user navigated to this page via a Branch link, sendSMS will send that + /// same link. Otherwise, it will create a new link with the data provided in + /// the params argument. sendSMS also registers a click event with the + /// channel pre-filled with 'sms' before sending an sms to the provided + /// phone parameter. This way the entire link click event is recorded starting + /// with the user sending an sms. + /// + /// Note: sendSMS will automatically send a previously generated link click, + /// along with the data object in the original link. Therefore, it is unneccessary for the + /// data() method to be called to check for an already existing link. If a link already + /// exists, sendSMS will simply ignore the data object passed to it, and send the existing link. + /// If this behavior is not desired, set make_new_link: true in the options object argument + /// of sendSMS, and sendSMS will always make a new link. + /// + /// Supports international SMS. + /// + /// Please note that the destination phone number needs to be from the same country the SMS is being sent from. + /// + /// Usage + /// + /// branch.sendSMS( + /// phone, + /// linkData, + /// options, + /// callback (err, data) + /// ); + /// Example + /// + /// var linkData = { + /// tags: ['tag1', 'tag2'], + /// channel: 'Website', + /// feature: 'TextMeTheApp', + /// data: { + /// // here's how to define the deeplink_path and the custom text on the front end + /// $deeplink_path: `custom_deeplink_path`, + /// //for the custom text, use {{link}} as a macro for link location + /// $custom_sms_text: `Here's my custom text, and here is the {{ link }}`, + /// mydata: 'something', + /// foo: 'bar', + /// '$desktop_url': 'http://myappwebsite.com', + /// '$ios_url': 'http://myappwebsite.com/ios', + /// '$ipad_url': 'http://myappwebsite.com/ipad', + /// '$android_url': 'http://myappwebsite.com/android', + /// '$og_app_id': '12345', + /// '$og_title': 'My App', + /// '$og_description': 'My app\'s description.', + /// '$og_image_url': 'http://myappwebsite.com/image.png' + /// } + /// } + /// branch.sendSMS( + /// '9999999999', + /// linkData, + /// { make_new_link: false }, // Default: false. If set to true, sendSMS will generate a new link even if one already exists. + /// function(err) { console.log(err); } + /// ); + /// Callback Format + /// + /// callback("Error message"); + @JS('sendSMS') + external static void sendSMS(String phone, Object linkData, + [Object options, Function callback]); + + /// setBranchViewData(data) + /// Parameters + /// + /// data: Object, required - object of all link data, same as Branch.link() + /// + /// This function lets you set the deep link data dynamically for a given mobile web Journey. For + /// example, if you desgin a full page interstitial, and want the deep link data to be custom for each + /// page, you'd need to use this function to dynamically set the deep link params on page load. Then, + /// any Journey loaded on that page will inherit these deep link params. + /// + /// Usage + /// + /// branch.setBranchViewData( + /// data // Data for link, same as Branch.link() + /// ); + /// Example + /// + /// branch.setBranchViewData({ + /// tags: ['tag1', 'tag2'], + /// data: { + /// mydata: 'something', + /// foo: 'bar', + /// '$deeplink_path': 'open/item/1234' + /// } + /// }); + @JS('setBranchViewData') + external static void setBranchViewData(Object data); + + /// setIdentity(identity, callback) + /// Parameters + /// + /// identity: string, required - a string uniquely identifying the user - often a user ID + /// or email address. + /// + /// callback: function, optional - callback that returns the user's + /// Branch identity id and unique link. + /// + /// Formerly identify() + /// + /// Sets the identity of a user and returns the data. To use this function, pass + /// a unique string that identifies the user - this could be an email address, + /// UUID, Facebook ID, etc. + /// + /// Usage + /// + /// branch.setIdentity( + /// identity, + /// callback (err, data) + /// ); + /// Callback Format + /// + /// callback( + /// "Error message", + /// { + /// identity_id: '12345', // Server-generated ID of the user identity, stored in `sessionStorage`. + /// link: 'url', // New link to use (replaces old stored link), stored in `sessionStorage`. + /// referring_data_parsed: { }, // Returns the initial referring data for this identity, if exists, as a parsed object. + /// referring_identity: '12345' // Returns the initial referring identity for this identity, if exists. + /// } + /// ); + @JS('setIdentity') + external static void setIdentity(String identity, [Function callback]); + + /// track(event, metadata, callback) + /// Parameters + /// + /// event: string, required - name of the event to be tracked. + /// + /// metadata: Object, optional - object of event metadata. + /// + /// callback: function, optional + /// + /// This function allows you to track any event with supporting metadata. + /// The metadata parameter is a formatted JSON object that can contain + /// any data and has limitless hierarchy + /// + /// Usage + /// + /// branch.track( + /// event, + /// metadata, + /// callback (err) + /// ); + /// Callback Format + /// + /// callback("Error message"); + @JS('track') + external static void track(String event, + [Object metadata, Function callback]); + + // No documentation in reference + // @JS('validateCode') + // external static void validateCode(); + + /// trackCommerceEvent(event, commerce_data, metadata, callback) + /// Parameters + /// + /// event: String, required - Name of the commerce event to be tracked. We currently support 'purchase' events + /// + /// commerce_data: Object, required - Data that describes the commerce event + /// + /// metadata: Object, optional - metadata you may want add to the event + /// + /// callback: function, optional - Returns an error if unsuccessful + /// + /// Sends a user commerce event to the server + /// + /// Use commerce events to track when a user purchases an item in your online store, + /// makes an in-app purchase, or buys a subscription. The commerce events are tracked in + /// the Branch dashboard along with your other events so you can judge the effectiveness of + /// campaigns and other analytics. + /// + /// Usage + /// + /// branch.trackCommerceEvent( + /// event, + /// commerce_data, + /// metadata, + /// callback (err) + /// ); + /// Example + /// + /// var commerce_data = { + /// "revenue": 50.0, + /// "currency": "USD", + /// "transaction_id": "foo-transaction-id", + /// "shipping": 0.0, + /// "tax": 5.0, + /// "affiliation": "foo-affiliation", + /// "products": [ + /// { "sku": "foo-sku-1", "name": "foo-item-1", "price": 45.00, "quantity": 1, "brand": "foo-brand", + /// "category": "Electronics", "variant": "foo-variant-1"}, + /// { "sku": "foo-sku-2", "price": 2.50, "quantity": 2} + /// ], + /// }; + /// + /// var metadata = { "foo": "bar" }; + /// + /// branch.trackCommerceEvent('purchase', commerce_data, metadata, function(err) { + /// if(err) { + /// throw err; + /// } + /// }); + @JS('trackCommerceEvent') + external static void trackCommerceEvent(String name, Object commerceData, + [Object metadata, Function callback]); + + /// logEvent(event, event_data_and_custom_data, content_items, customer_event_alias, callback) + /// Parameters + /// + /// event: String, required + /// + /// event_data_and_custom_data: Object, optional + /// + /// content_items: Array, optional + /// + /// customer_event_alias: String, optional + /// + /// callback: function, optional + /// + /// Register commerce events, content events, user lifecycle events and custom events via logEvent() + /// + /// NOTE: If this is the first time you are integrating our new event tracking feature via logEvent(), please use the latest Branch WebSDK snippet from the Installation section. This has been updated in v2.30.0 of our SDK. + /// + /// The guides below provide information about what keys can be sent when triggering these event types: + /// + /// Logging Commerce Events + /// Logging Content Events + /// Logging User Lifecycle + /// Logging Custom Events + /// Usage for Commerce, Content & User Lifecycle "Standard Events" + /// + /// branch.logEvent( + /// event, + /// event_data_and_custom_data, + /// content_items, + /// customer_event_alias, + /// callback (err) + /// ); + /// Usage for "Custom Events" + /// + /// JavaScript + /// JavaScript + /// branch.logEvent( + /// event, + /// custom_data, + /// callback (err) + /// ); + /// `` + /// **Notes**: + /// - logEvent() sends user_data automatically + /// - When firing Standard Events, send custom and event data as part of the same object + /// - Custom Events do not contain content items and event data + /// + /// ### Example -- How to log a Commerce Event + /// var event_and_custom_data = { + /// "transaction_id": "tras_Id_1232343434", + /// "currency": "USD", + /// "revenue": 180.2, + /// "shipping": 10.5, + /// "tax": 13.5, + /// "coupon": "promo-1234", + /// "affiliation": "high_fi", + /// "description": "Preferred purchase", + /// "purchase_loc": "Palo Alto", + /// "store_pickup": "unavailable" + /// }; + /// var content_items = [ + /// { + /// "$content_schema": "COMMERCE_PRODUCT", + /// "$og_title": "Nike Shoe", + /// "$og_description": "Start loving your steps", + /// "$og_image_url": "http:///example.com/img1.jpg", + /// "$canonical_identifier": "nike/1234", + /// "$publicly_indexable": false, + /// "$price": 101.2, + /// "$locally_indexable": true, + /// "$quantity": 1, + /// "$sku": "1101123445", + /// "$product_name": "Runner", + /// "$product_brand": "Nike", + /// "$product_category": "Sporting Goods", + /// "$product_variant": "XL", + /// "$rating_average": 4.2, + /// "$rating_count": 5, + /// "$rating_max": 2.2, + /// "$creation_timestamp": 1499892854966, + /// "$exp_date": 1499892854966, + /// "$keywords": [ "sneakers", "shoes" ], + /// "$address_street": "230 South LaSalle Street", + /// "$address_city": "Chicago", + /// "$address_region": "IL", + /// "$address_country": "US", + /// "$address_postal_code": "60604", + /// "$latitude": 12.07, + /// "$longitude": -97.5, + /// "$image_captions": [ "my_img_caption1", "my_img_caption_2" ], + /// "$condition": "NEW", + /// "$custom_fields": {"foo1":"bar1","foo2":"bar2"} + /// }, + /// { + /// "$og_title": "Nike Woolen Sox", + /// "$canonical_identifier": "nike/5324", + /// "$og_description": "Fine combed woolen sox for those who love your foot", + /// "$publicly_indexable": false, + /// "$price": 80.2, + /// "$locally_indexable": true, + /// "$quantity": 5, + /// "$sku": "110112467", + /// "$product_name": "Woolen Sox", + /// "$product_brand": "Nike", + /// "$product_category": "Apparel & Accessories", + /// "$product_variant": "Xl", + /// "$rating_average": 3.3, + /// "$rating_count": 5, + /// "$rating_max": 2.8, + /// "$creation_timestamp": 1499892854966 + /// }]; + /// var customer_event_alias = "event alias"; + /// branch.logEvent( + /// "PURCHASE", + /// event_and_custom_data, + /// content_items, + /// customer_event_alias, + /// function(err) { console.log(err); } + /// ); + + @JS('logEvent') + external static void logEvent(String event, + [Object eventDataAndCustomData, + JsArray contentItems, + String customerEventAlias, + Function callback]); + + /// disableTracking(disableTracking) + /// Parameters + /// + /// disableTracking: Boolean, optional - true disables tracking and false re-enables tracking. + /// + /// Notes: + /// + /// disableTracking() without a parameter is a shorthand for disableTracking(true). + /// If a call to disableTracking(false) is made, the WebSDK will re-initialize. Additionally, if tracking_disabled: true is passed + /// as an option to init(), it will be removed during the reinitialization process. + /// Allows User to Remain Private + /// + /// This will prevent any Branch requests from being sent across the network, except for the case of deep linking. + /// If someone clicks a Branch link, but has expressed not to be tracked, we will return deep linking data back to the + /// client but without tracking information. + /// + /// In do-not-track mode, you will still be able to create links and display Journeys however, they will not have identifiable + /// information associated to them. You can change this behavior at any time, by calling the aforementioned function. + /// The do-not-track mode state is persistent: it is saved for the user across browser sessions for the web site. + @JS('disableTracking') + external static void disableTracking([bool disableTracking]); + + @JS('lastAttributedTouchData') + external static void lastAttributedTouchData(attributionWindow, + [Function callback]); +} diff --git a/pubspec.lock b/pubspec.lock index 0fd4fb87..2feb16f3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -56,12 +56,12 @@ packages: source: sdk version: "0.0.0" flutter_lints: - dependency: "direct main" + dependency: "direct dev" description: name: flutter_lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -85,7 +85,7 @@ packages: name: lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" matcher: dependency: transitive description: @@ -120,7 +120,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.2" sky_engine: dependency: transitive description: flutter @@ -176,5 +176,5 @@ packages: source: hosted version: "2.1.2" sdks: - dart: ">=2.17.0-0 <3.0.0" - flutter: ">=1.22.0" + dart: ">=2.17.3 <3.0.0" + flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index e9879092..3e827ef2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,32 +1,47 @@ -name: flutter_branch_sdk -description: Flutter Plugin for create deep link using Brach SDK (https://branch.io). This plugin provides a cross-platform (iOS, Android). -version: 5.1.1 -homepage: https://github.com/RodrigoSMarques/flutter_branch_sdk - -environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=1.22.0" - -flutter: - plugin: - platforms: - android: - package: br.com.rsmarques.flutter_branch_sdk - pluginClass: FlutterBranchSdkPlugin - ios: - pluginClass: FlutterBranchSdkPlugin - web: - fileName: src/flutter_branch_sdk_web.dart - pluginClass: FlutterBranchSdk - -dependencies: - flutter: - sdk: flutter - flutter_web_plugins: - sdk: flutter - plugin_platform_interface: ">=1.0.0 <3.0.0" - js: ^0.6.4 - -dev_dependencies: - flutter_test: - sdk: flutter +name: flutter_branch_sdk +description: Flutter Plugin for create deep link using Brach SDK (https://branch.io). This plugin provides a cross-platform (iOS, Android, Web). +version: 6.0.0 +homepage: https://github.com/RodrigoSMarques/flutter_branch_sdk + +environment: + sdk: ">=2.17.3 <3.0.0" + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + plugin_platform_interface: ^2.1.2 + js: ^0.6.4 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: br.com.rsmarques.flutter_branch_sdk + pluginClass: FlutterBranchSdkPlugin + ios: + pluginClass: FlutterBranchSdkPlugin + web: + pluginClass: FlutterBranchSdkWeb + fileName: src/flutter_branch_sdk_web.dart From d54234688c39f89839032012f037732290ff6d4c Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 16 Jun 2022 16:09:05 -0300 Subject: [PATCH 003/125] Update LICENSE --- LICENSE | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 97cfb48e..928edc82 100644 --- a/LICENSE +++ b/LICENSE @@ -1 +1,21 @@ -TODO: Add your license here. +MIT License + +Copyright (c) 2019 Rodrigo de Souza Marques + +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. \ No newline at end of file From 6b8e63f012f060cea045506c1a29120f1ac92eea Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 16 Jun 2022 16:14:37 -0300 Subject: [PATCH 004/125] Update README.md --- example/README.md | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/example/README.md b/example/README.md index 6cab3168..293d9766 100644 --- a/example/README.md +++ b/example/README.md @@ -4,13 +4,6 @@ Demonstrates how to use the flutter_branch_sdk plugin. ## Getting Started -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +See the `example` directory for a complete sample app using Branch SDK. + +![Example app](https://user-images.githubusercontent.com/17687286/70445281-0b87c180-1a7a-11ea-8611-7217d46c75a7.png) \ No newline at end of file From ff10e0cc8e7a12ce40d39d262e3dd5c5f982447c Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Sat, 18 Jun 2022 14:13:30 -0300 Subject: [PATCH 005/125] ios SDK version update --- CHANGELOG.md | 3 ++- README.md | 4 ++-- example/ios/Podfile.lock | 8 ++++---- ios/flutter_branch_sdk.podspec | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1054d21..66f9764f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## 6.0.0 * Updated Native `Android` SDK: * Android Native SDK Update 5.2.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) - + * iOS Native SDK Update 1.43.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) + ###BREAKING CHANGE * Minimum required Dart SDK version to 2.17 (Flutter 3.0) diff --git a/README.md b/README.md index e7fba02d..114eb24c 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ Branch.io helps mobile apps grow with deep links that power referral systems, sh Supports Android, iOS and Web. -* Android - Branch SDK Version >= 5.1.5 [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) -* iOS - Branch SDK Version >= 1.42.0 [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) +* Android - Branch SDK Version >= 5.2.0 [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) +* iOS - Branch SDK Version >= 1.43.0 [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) Implemented functions in plugin: diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 45d125bb..b46a082b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,8 +1,8 @@ PODS: - - Branch (1.42.0) + - Branch (1.43.0) - Flutter (1.0.0) - flutter_branch_sdk (3.0.0): - - Branch (~> 1.42.0) + - Branch (~> 1.43.0) - Flutter DEPENDENCIES: @@ -20,9 +20,9 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_branch_sdk/ios" SPEC CHECKSUMS: - Branch: a6f1d597dc7c027360f386d05e8d109043b207d8 + Branch: 0f64bfa5979d8874ac1ad399657d8ab8ef6e2cde Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a - flutter_branch_sdk: c7161eca896193a81bbb452f2cb492d5d89f0d91 + flutter_branch_sdk: dcf38505c8dcb3249841e2acaf323f4a39f30e2b PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c diff --git a/ios/flutter_branch_sdk.podspec b/ios/flutter_branch_sdk.podspec index 7882649d..cbb7b684 100644 --- a/ios/flutter_branch_sdk.podspec +++ b/ios/flutter_branch_sdk.podspec @@ -15,7 +15,7 @@ Flutter Plugin for create deep link using Brach SDK (https://branch.io). This pl s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'Branch', '~> 1.42.0' + s.dependency 'Branch', '~> 1.43.0' s.platform = :ios, '9.0' # Flutter.framework does not contain a i386 slice. From 1a454d35842fef739ffdea5a4edb0e6b3cc5e0cc Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Tue, 21 Jun 2022 02:09:31 -0300 Subject: [PATCH 006/125] new methods getQRCodeAsData / getQRCodeAsImage --- .../FlutterBranchSdkHelper.java | 29 +++ .../FlutterBranchSdkInit.java | 2 +- .../FlutterBranchSdkPlugin.java | 177 ++++++++---------- .../MainThreadEventSink.java | 64 +++++++ .../MethodResultWrapper.java | 63 +++++++ example/lib/main.dart | 14 ++ ios/Classes/FlutterBranchIoSdkHelper.swift | 61 +++++- ios/Classes/SwiftFlutterBranchSdkPlugin.swift | 55 +++++- lib/src/flutter_branch_sdk.dart | 18 ++ .../flutter_branch_sdk_method_channel.dart | 48 ++++- ...flutter_branch_sdk_platform_interface.dart | 20 +- lib/src/flutter_branch_sdk_web.dart | 52 +++++ lib/src/objects/branch_qrcode.dart | 83 ++++++++ lib/src/objects/branch_universal_object.dart | 3 + lib/src/web/branch_js.dart | 4 + 15 files changed, 582 insertions(+), 111 deletions(-) create mode 100644 android/src/main/java/br/com/rsmarques/flutter_branch_sdk/MainThreadEventSink.java create mode 100644 android/src/main/java/br/com/rsmarques/flutter_branch_sdk/MethodResultWrapper.java create mode 100644 lib/src/objects/branch_qrcode.dart diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkHelper.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkHelper.java index a3d8bab8..f5be7455 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkHelper.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkHelper.java @@ -14,6 +14,7 @@ import java.util.Map; import io.branch.indexing.BranchUniversalObject; +import io.branch.referral.QRCode.BranchQRCode; import io.branch.referral.util.AdType; import io.branch.referral.util.BRANCH_STANDARD_EVENT; import io.branch.referral.util.BranchContentSchema; @@ -204,6 +205,34 @@ BranchEvent convertToEvent(HashMap eventMap) { return event; } + BranchQRCode convertToQRCode(HashMap qrCodeMap) { + BranchQRCode branchQRCode = new BranchQRCode(); + if (qrCodeMap.containsKey("width")) { + branchQRCode.setWidth((int) qrCodeMap.get("width")); + } + if (qrCodeMap.containsKey("margin")) { + branchQRCode.setMargin((int) qrCodeMap.get("margin")); + } + if (qrCodeMap.containsKey("codeColor")) { + branchQRCode.setCodeColor((String) qrCodeMap.get("codeColor")); + } + if (qrCodeMap.containsKey("backgroundColor")) { + branchQRCode.setBackgroundColor((String) qrCodeMap.get("backgroundColor")); + } + if (qrCodeMap.containsKey("imageFormat")) { + final String imageFormat = (String) qrCodeMap.get("imageFormat"); + if (imageFormat.equals("JPEG")) { + branchQRCode.setImageFormat(BranchQRCode.BranchImageFormat.JPEG); + } else { + branchQRCode.setImageFormat(BranchQRCode.BranchImageFormat.PNG); + } + } + if (qrCodeMap.containsKey("centerLogoUrl")) { + branchQRCode.setCenterLogo((String) qrCodeMap.get("centerLogoUrl")); + } + return branchQRCode; + } + AdType convertToAdType(String adType) { switch (adType) { case "BANNER": diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkInit.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkInit.java index 009cb7fc..3d3d1e59 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkInit.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkInit.java @@ -8,7 +8,7 @@ public class FlutterBranchSdkInit { private static final String DEBUG_NAME = "FlutterBranchSDK"; private static final String PLUGIN_NAME = "Flutter"; - private static final String PLUGIN_VERSION = "5.1.1"; + private static final String PLUGIN_VERSION = "6.0.0"; public static void init(Context context) { ApplicationInfoHelper applicationInfoHelper = new ApplicationInfoHelper(context); diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java index 1ae62679..1971ad6c 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java @@ -4,6 +4,7 @@ import android.app.Application; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -12,6 +13,10 @@ import org.json.JSONException; import org.json.JSONObject; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -20,6 +25,7 @@ import io.branch.indexing.BranchUniversalObject; import io.branch.referral.Branch; import io.branch.referral.BranchError; +import io.branch.referral.QRCode.BranchQRCode; import io.branch.referral.ServerRequestGetLATD; import io.branch.referral.util.BranchEvent; import io.branch.referral.util.LinkProperties; @@ -292,6 +298,12 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result rawResult) { case "getLastAttributedTouchData": getLastAttributedTouchData(call, result); break; + case "getQRCodeAsData": + getQRCode("D", call, result); + break; + case "getQRCodeAsImage": + getQRCode("I", call, result); + break; default: result.notImplemented(); break; @@ -707,118 +719,77 @@ public void onDataFetched(JSONObject jsonObject, BranchError error) { } } - // MethodChannel.Result wrapper that responds on the platform thread. - private static class MethodResultWrapper implements Result { - private final Result methodResult; - private final Handler handler; - - MethodResultWrapper(Result result) { - methodResult = result; - handler = new Handler(Looper.getMainLooper()); - } - - @Override - public void success(final Object result) { - handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.success(result); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } - - @Override - public void error( - final String errorCode, final String errorMessage, final Object errorDetails) { - handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.error(errorCode, errorMessage, errorDetails); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - } + private void getQRCode(final String type, final MethodCall call, final Result result) { - @Override - public void notImplemented() { - handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.notImplemented(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); + LogUtils.debug(DEBUG_NAME, "getQRCodeAsData call"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); } - } - - private static class MainThreadEventSink implements EventChannel.EventSink { - private final EventChannel.EventSink eventSink; - private final Handler handler; + HashMap argsMap = (HashMap) call.arguments; - MainThreadEventSink(EventChannel.EventSink eventSink) { - this.eventSink = eventSink; - handler = new Handler(Looper.getMainLooper()); - } + final BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) argsMap.get("buo")); + final LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); + final BranchQRCode branchQRCode = branchSdkHelper.convertToQRCode((HashMap) argsMap.get("qrCodeSettings")); + final Map response = new HashMap<>(); - @Override - public void success(final Object o) { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (eventSink != null) { - eventSink.success(o); - } - } catch (Exception e) { - e.printStackTrace(); + if (type.equals("D")) { + try { + branchQRCode.getQRCodeAsData(context, buo, linkProperties, new BranchQRCode.BranchQRCodeDataHandler() { + @Override + public void onSuccess(byte[] qrCodeData) { + response.put("success", Boolean.TRUE); + response.put("data", qrCodeData); + result.success(response); } - } - }); - } - - @Override - public void error(final String s, final String s1, final Object o) { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (eventSink != null) { - eventSink.error(s, s1, o); + @Override + public void onFailure(Exception error) { + response.put("success", Boolean.FALSE); + response.put("errorCode", "-1"); + response.put("errorMessage", error.getMessage()); + result.success(response); + } + }); + } catch (IOException e) { + response.put("success", Boolean.FALSE); + response.put("errorCode", "-1"); + response.put("errorMessage", e.getMessage()); + result.success(response); + } + } else { + //https://gist.github.com/nikartm/79932c0a4f0a644f7ce020143146db98 + //https://github.com/flutter/plugins/blob/main/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java + //https://stackoverflow.com/questions/39538073/android-store-image-on-a-temp-file-cross-activity + //https://github.com/flutter/plugins/blob/main/packages/image_picker/image_picker_android/android/src/main/AndroidManifest.xml + //https://github.com/flutter/plugins/blob/main/packages/image_picker/image_picker_android/android/src/main/res/xml/flutter_image_picker_file_paths.xml + + try { + branchQRCode.getQRCodeAsImage(activity, buo, linkProperties, new BranchQRCode.BranchQRCodeImageHandler() { + @Override + public void onSuccess(Bitmap qrCodeImage) { + FileOutputStream outStream = null; + try { + outStream = new FileOutputStream(new File(context.getCacheDir(), "tempBMP")); + } catch (FileNotFoundException e) { + e.printStackTrace(); } - } catch (Exception e) { - e.printStackTrace(); + /* + final Bitmap myBitmap = new Bitmap(); + myBitmap.compress(Bitmap.CompressFormat.JPEG, 75, outStream); + outStream.close(); + */ } - } - }); - } - @Override - public void endOfStream() { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (eventSink != null) { - eventSink.endOfStream(); - } - } catch (Exception e) { - e.printStackTrace(); + @Override + public void onFailure(Exception e) { + } - } - }); + }); + } catch (IOException e) { + response.put("success", Boolean.FALSE); + response.put("errorCode", "-1"); + response.put("errorMessage", e.getMessage()); + result.success(response); + } } } } diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/MainThreadEventSink.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/MainThreadEventSink.java new file mode 100644 index 00000000..2709b401 --- /dev/null +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/MainThreadEventSink.java @@ -0,0 +1,64 @@ +package br.com.rsmarques.flutter_branch_sdk; + +import android.os.Handler; +import android.os.Looper; + +import io.flutter.plugin.common.EventChannel; + +public class MainThreadEventSink implements EventChannel.EventSink { + private final EventChannel.EventSink eventSink; + private final Handler handler; + + MainThreadEventSink(EventChannel.EventSink eventSink) { + this.eventSink = eventSink; + handler = new Handler(Looper.getMainLooper()); + } + + @Override + public void success(final Object o) { + handler.post(new Runnable() { + @Override + public void run() { + try { + if (eventSink != null) { + eventSink.success(o); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void error(final String s, final String s1, final Object o) { + handler.post(new Runnable() { + @Override + public void run() { + try { + if (eventSink != null) { + eventSink.error(s, s1, o); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void endOfStream() { + handler.post(new Runnable() { + @Override + public void run() { + try { + if (eventSink != null) { + eventSink.endOfStream(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } +} diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/MethodResultWrapper.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/MethodResultWrapper.java new file mode 100644 index 00000000..3bc513ce --- /dev/null +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/MethodResultWrapper.java @@ -0,0 +1,63 @@ +package br.com.rsmarques.flutter_branch_sdk; + +import android.os.Handler; +import android.os.Looper; + +import io.flutter.plugin.common.MethodChannel; + +// MethodChannel.Result wrapper that responds on the platform thread. +public class MethodResultWrapper implements MethodChannel.Result { + private final MethodChannel.Result methodResult; + private final Handler handler; + + MethodResultWrapper(MethodChannel.Result result) { + methodResult = result; + handler = new Handler(Looper.getMainLooper()); + } + + @Override + public void success(final Object result) { + handler.post( + new Runnable() { + @Override + public void run() { + try { + methodResult.success(result); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void error( + final String errorCode, final String errorMessage, final Object errorDetails) { + handler.post( + new Runnable() { + @Override + public void run() { + try { + methodResult.error(errorCode, errorMessage, errorDetails); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void notImplemented() { + handler.post( + new Runnable() { + @Override + public void run() { + try { + methodResult.notImplemented(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 08928a05..e5ae9dca 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -545,6 +545,20 @@ class _HomePageState extends State { controllerUrl.sink .add('Error : ${response.errorCode} - ${response.errorMessage}'); } + + BranchResponse responseQrCode = await FlutterBranchSdk.getQRCodeAsData( + buo: buo!, + linkProperties: lp, + qrCode: BranchQrCode( + primaryColor: Colors.blue, + backgroundColor: const Color(0xff443a49), + imageFormat: BranchImageFormat.PNG)); + if (responseQrCode.success) { + print(responseQrCode.result); + } else { + print( + 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); + } } void shareLink() async { diff --git a/ios/Classes/FlutterBranchIoSdkHelper.swift b/ios/Classes/FlutterBranchIoSdkHelper.swift index e490279a..91f8fc2c 100644 --- a/ios/Classes/FlutterBranchIoSdkHelper.swift +++ b/ios/Classes/FlutterBranchIoSdkHelper.swift @@ -196,6 +196,7 @@ func convertToEvent(dict: [String: Any?]) -> BranchEvent? { } return event } + func convertToAdType(adType: String) -> BranchEventAdType { switch adType { case "BANNER": @@ -210,6 +211,35 @@ func convertToAdType(adType: String) -> BranchEventAdType { return BranchEventAdType.none } } + +func convertToQRCode(dict: [String: Any?]) -> BranchQRCode { + let qrCode : BranchQRCode = BranchQRCode() + + if let width = dict["width"] as? Int { + qrCode.width = NSNumber(value: width) + } + if let margin = dict["margin"] as? Int { + qrCode.margin = NSNumber(value: margin) + } + if let codeColor = dict["codeColor"] as? String { + qrCode.codeColor = UIColor.init(hexString: codeColor) + } + if let backgroundColor = dict["backgroundColor"] as? String { + qrCode.backgroundColor = UIColor.init(hexString: backgroundColor) + } + if let imageFormat = dict["imageFormat"] as? String { + if (imageFormat == "JPEG") { + qrCode.imageFormat = BranchQRCodeImageFormat.JPEG + } else { + qrCode.imageFormat = BranchQRCodeImageFormat.PNG + } + } + if let centerLogoUrl = dict["centerLogoUrl"] as? String { + qrCode.centerLogo = centerLogoUrl + } + return qrCode +} + //--------------------------------------------------------------------------------------------- // Extension // -------------------------------------------------------------------------------------------- @@ -227,8 +257,37 @@ extension Date { extension Bundle { static func infoPlistValue(forKey key: String) -> Any? { guard let value = Bundle.main.object(forInfoDictionaryKey: key) else { - return nil + return nil } return value } } + +extension UIColor { + convenience init(hexString: String, alpha: CGFloat = 1.0) { + let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let scanner = Scanner(string: hexString) + if (hexString.hasPrefix("#")) { + scanner.scanLocation = 1 + } + var color: UInt32 = 0 + scanner.scanHexInt32(&color) + let mask = 0x000000FF + let r = Int(color >> 16) & mask + let g = Int(color >> 8) & mask + let b = Int(color) & mask + let red = CGFloat(r) / 255.0 + let green = CGFloat(g) / 255.0 + let blue = CGFloat(b) / 255.0 + self.init(red:red, green:green, blue:blue, alpha:alpha) + } + func toHexString() -> String { + var r:CGFloat = 0 + var g:CGFloat = 0 + var b:CGFloat = 0 + var a:CGFloat = 0 + getRed(&r, green: &g, blue: &b, alpha: &a) + let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 + return String(format:"#%06x", rgb) + } +} diff --git a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift index 71cfc554..d1f12f04 100644 --- a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift +++ b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift @@ -10,7 +10,7 @@ let MESSAGE_CHANNEL = "flutter_branch_sdk/message"; let EVENT_CHANNEL = "flutter_branch_sdk/event"; let ERROR_CODE = "FLUTTER_BRANCH_SDK_ERROR"; let PLUGIN_NAME = "Flutter"; -let PLUGIN_VERSION = "5.1.1" +let PLUGIN_VERSION = "6.0.0" public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStreamHandler { var eventSink: FlutterEventSink? @@ -221,6 +221,12 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream case "getLastAttributedTouchData": getLastAttributedTouchData(call: call, result: result) break + case "getQRCodeAsData": + getQRCode(type: "D", call: call, result: result) + break + case "getQRCodeAsImage": + getQRCode(type: "I", call: call, result: result) + break default: result(FlutterMethodNotImplemented) break @@ -548,4 +554,51 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream Branch.getInstance().setRetryInterval(TimeInterval(retryInterval)) } } + + private func getQRCode(type: String, call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments as! [String: Any?] + let buoDict = args["buo"] as! [String: Any?] + let lpDict = args["lp"] as! [String: Any?] + let qrCodeDict = args["qrCodeSettings"] as! [String: Any?] + + let buo: BranchUniversalObject? = convertToBUO(dict: buoDict) + let lp : BranchLinkProperties? = convertToLp(dict: lpDict ) + let qrCode : BranchQRCode? = convertToQRCode(dict: qrCodeDict) + + let response : NSMutableDictionary! = [:] + + if (type == "D") { + qrCode?.getAsData(buo, linkProperties: lp, completion: { data, error in + if (error == nil) { + response["success"] = NSNumber(value: true) + response["data"] = FlutterStandardTypedData(bytes: data!) + } else { + response["success"] = NSNumber(value: false) + if let err = (error as NSError?) { + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } + } + DispatchQueue.main.async { + result(response) + } + + }) + } else { + qrCode?.getAsImage(buo, linkProperties: lp, completion: { image, error in + if (error == nil) { + + } else { + response["success"] = NSNumber(value: false) + if let err = (error as NSError?) { + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } + } + DispatchQueue.main.async { + result(response) + } + }) + } + } } diff --git a/lib/src/flutter_branch_sdk.dart b/lib/src/flutter_branch_sdk.dart index 68b7bf95..87620601 100644 --- a/lib/src/flutter_branch_sdk.dart +++ b/lib/src/flutter_branch_sdk.dart @@ -171,4 +171,22 @@ class FlutterBranchSdk { return FlutterBranchSdkPlatform.instance .getLastAttributedTouchData(attributionWindow: attributionWindow); } + + ///Creates a Branch QR Code image. Returns the QR code as Data (base64). + static Future getQRCodeAsData( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required BranchQrCode qrCode}) async { + return FlutterBranchSdkPlatform.instance.getQRCodeAsData( + buo: buo, linkProperties: linkProperties, qrCodeSettings: qrCode); + } + + ///Creates a Branch QR Code image. Returns the QR code as a Image. + static Future getQRCodeAsImage( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required BranchQrCode qrCode}) async { + return FlutterBranchSdkPlatform.instance.getQRCodeAsImage( + buo: buo, linkProperties: linkProperties, qrCodeSettings: qrCode); + } } diff --git a/lib/src/flutter_branch_sdk_method_channel.dart b/lib/src/flutter_branch_sdk_method_channel.dart index dcc0c953..e4fa8153 100644 --- a/lib/src/flutter_branch_sdk_method_channel.dart +++ b/lib/src/flutter_branch_sdk_method_channel.dart @@ -7,7 +7,7 @@ import 'objects/app_tracking_transparency.dart'; import 'objects/branch_universal_object.dart'; /// An implementation of [FlutterBranchSdkPlatform] that uses method channels. -class FlutterBranchSdkMethodChannel extends FlutterBranchSdkPlatform { +class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { static const MESSAGE_CHANNEL = 'flutter_branch_sdk/message'; static const EVENT_CHANNEL = 'flutter_branch_sdk/event'; @@ -272,4 +272,50 @@ class FlutterBranchSdkMethodChannel extends FlutterBranchSdkPlatform { errorMessage: response['errorMessage']); } } + + ///Creates a Branch QR Code image. Returns the QR code as Data (base64). + @override + Future getQRCodeAsData( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required BranchQrCode qrCodeSettings}) async { + Map params = {}; + params['buo'] = buo.toMap(); + params['lp'] = linkProperties.toMap(); + params['qrCodeSettings'] = qrCodeSettings.toMap(); + + Map response = + await messageChannel.invokeMethod('getQRCodeAsData', params); + + if (response['success']) { + return BranchResponse.success(result: response['result']); + } else { + return BranchResponse.error( + errorCode: response['errorCode'], + errorMessage: response['errorMessage']); + } + } + + ///Creates a Branch QR Code image. Returns the QR code as a Image. + @override + Future getQRCodeAsImage( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required BranchQrCode qrCodeSettings}) async { + Map params = {}; + params['buo'] = buo.toMap(); + params['lp'] = linkProperties.toMap(); + params['qrCodeSettings'] = qrCodeSettings.toMap(); + + Map response = + await messageChannel.invokeMethod('getQRCodeAsImage', params); + + if (response['success']) { + return BranchResponse.success(result: response['result']); + } else { + return BranchResponse.error( + errorCode: response['errorCode'], + errorMessage: response['errorMessage']); + } + } } diff --git a/lib/src/flutter_branch_sdk_platform_interface.dart b/lib/src/flutter_branch_sdk_platform_interface.dart index 9f5e7f45..fc93bd4c 100644 --- a/lib/src/flutter_branch_sdk_platform_interface.dart +++ b/lib/src/flutter_branch_sdk_platform_interface.dart @@ -25,10 +25,6 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { _instance = instance; } - Future getPlatformVersion() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } - ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value void setIdentity(String userId) { throw UnimplementedError('setIdentity has not been implemented'); @@ -195,4 +191,20 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { throw UnimplementedError( 'getLastAttributedTouchData has not been implemented'); } + + ///Creates a Branch QR Code image. Returns the QR code as Data (base64). + Future getQRCodeAsData( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required BranchQrCode qrCodeSettings}) async { + throw UnimplementedError('getQRCodeAsData has not been implemented'); + } + + ///Creates a Branch QR Code image. Returns the QR code as a Image. + Future getQRCodeAsImage( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required BranchQrCode qrCodeSettings}) async { + throw UnimplementedError('getQRCodeAsImage has not been implemented'); + } } diff --git a/lib/src/flutter_branch_sdk_web.dart b/lib/src/flutter_branch_sdk_web.dart index c2e963d4..56885de8 100644 --- a/lib/src/flutter_branch_sdk_web.dart +++ b/lib/src/flutter_branch_sdk_web.dart @@ -375,10 +375,62 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { responseCompleter.complete(BranchResponse.error( errorCode: '-1', errorMessage: 'getLastAttributedTouchData() error')); } + return responseCompleter.future; + } + + ///Creates a Branch QR Code image. Returns the QR code as Data (base64). + @override + Future getQRCodeAsData( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required BranchQrCode qrCodeSettings}) async { + // TODO: implement getQRCodeAsData + Completer responseCompleter = Completer(); + + Map data = buo.toMapWeb(); + linkProperties.getControlParams().forEach((key, value) { + data[key] = value; + }); + + Map linkData = { + ...linkProperties.toMapWeb(), + 'data': data + }; + try { + BranchJS.qrCode(linkData, qrCodeSettings.toMapWeb(), + allowInterop((err, data) { + if (err == null) { + if (data != null) { + debugPrint(data); + responseCompleter.complete( + BranchResponse.success(result: _jsObjectToDartObject(data))); + } else { + responseCompleter.complete(BranchResponse.success(result: {})); + } + } else { + responseCompleter.complete(BranchResponse.error( + errorCode: '999', errorMessage: err.toString())); + } + })); + } catch (error) { + debugPrint('qrCode() error: $error'); + responseCompleter.complete(BranchResponse.error( + errorCode: '-1', errorMessage: 'qrCode() error')); + } return responseCompleter.future; } + ///Creates a Branch QR Code image. Returns the QR code as a Image. + @override + Future getQRCodeAsImage( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required BranchQrCode qrCodeSettings}) { + // TODO: implement getQRCodeAsImage + throw UnimplementedError(); + } + void close() { _initSessionStream.close(); } diff --git a/lib/src/objects/branch_qrcode.dart b/lib/src/objects/branch_qrcode.dart new file mode 100644 index 00000000..267e0f7c --- /dev/null +++ b/lib/src/objects/branch_qrcode.dart @@ -0,0 +1,83 @@ +part of flutter_branch_sdk_objects; + +enum BranchImageFormat { + JPEG, + /* QR code is returned as a JPEG */ + PNG + /*QR code is returned as a PNG */ +} + +class BranchQrCode { + /* Primary color of the generated QR code itself. */ + Color? primaryColor; + /* Secondary color used as the QR Code background. */ + Color? backgroundColor; + /* The number of pixels for the QR code's border. Min 1px. Max 20px. */ + int? margin; + /* Output size of QR Code image. Min 300px. Max 2000px. */ + int? width; + /* A URL of an image that will be added to the center of the QR code. Must be a PNG or JPEG. */ + String centerLogoUrl; + /* Image Format of the returned QR code. Can be a JPEG or PNG. */ + BranchImageFormat imageFormat; + + BranchQrCode( + {this.primaryColor, + this.backgroundColor, + this.margin, + this.width, + this.imageFormat = BranchImageFormat.PNG, + this.centerLogoUrl = ''}) { + if (centerLogoUrl.isNotEmpty) { + assert(Uri.parse(centerLogoUrl).isAbsolute == true, 'Invalid URL'); + } + } + + Map toMap() { + Map ret = {}; + + if (primaryColor != null) { + ret["codeColor"] = _colorToHex(primaryColor!); + } + if (backgroundColor != null) { + ret["backgroundColor"] = _colorToHex(backgroundColor!); + } + if (margin != null) { + ret["margin"] = margin; + } + if (width != null) { + ret["width"] = width; + } + ret["imageFormat"] = imageFormat.name.toUpperCase(); + if (centerLogoUrl.isNotEmpty) { + ret["centerLogoUrl"] = centerLogoUrl; + } + return ret; + } + + Map toMapWeb() { + Map ret = {}; + + if (primaryColor != null) { + ret["code_color"] = _colorToHex(primaryColor!); + } + if (backgroundColor != null) { + ret["background_color"] = _colorToHex(backgroundColor!); + } + if (margin != null) { + ret["margin"] = margin; + } + if (width != null) { + ret["width"] = width; + } + ret["image_format"] = imageFormat.name.toLowerCase(); + if (centerLogoUrl.isNotEmpty) { + ret["centerLogoUrl"] = centerLogoUrl; + } + return ret; + } + + String _colorToHex(Color color) { + return '#${color.value.toRadixString(16).substring(2, 8)}'; + } +} diff --git a/lib/src/objects/branch_universal_object.dart b/lib/src/objects/branch_universal_object.dart index 315825b0..31916703 100644 --- a/lib/src/objects/branch_universal_object.dart +++ b/lib/src/objects/branch_universal_object.dart @@ -1,6 +1,9 @@ library flutter_branch_sdk_objects; +import 'package:flutter/material.dart'; + part 'branch_event.dart'; +part 'branch_qrcode.dart'; part 'branch_response.dart'; part 'content_meta_data.dart'; part 'content_schema.dart'; diff --git a/lib/src/web/branch_js.dart b/lib/src/web/branch_js.dart index bf6a0b03..dcabca5c 100644 --- a/lib/src/web/branch_js.dart +++ b/lib/src/web/branch_js.dart @@ -953,4 +953,8 @@ class BranchJS { @JS('lastAttributedTouchData') external static void lastAttributedTouchData(attributionWindow, [Function callback]); + + @JS('qrCode') + external static void qrCode(qrCodeLinkData, qrCodeSettings, + [Function callback]); } From d169d8cb3dd205578532390449664f35451cf9ea Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Wed, 22 Jun 2022 02:04:57 -0300 Subject: [PATCH 007/125] Refactor methods getQRCode --- CHANGELOG.md | 2 +- .../FlutterBranchSdkPlugin.java | 48 ++------------- example/lib/main.dart | 24 +++++++- ios/Classes/SwiftFlutterBranchSdkPlugin.swift | 58 ++++++------------- .../flutter_branch_sdk_method_channel.dart | 27 ++++----- lib/src/flutter_branch_sdk_web.dart | 27 +++++---- lib/src/web/branch_js.dart | 2 +- 7 files changed, 77 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f9764f..a2ff1c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## 6.0.0 -* Updated Native `Android` SDK: +* Updated Native `Android` and `iOS` SDKs: * Android Native SDK Update 5.2.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) * iOS Native SDK Update 1.43.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java index 1971ad6c..9958c109 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java @@ -298,11 +298,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result rawResult) { case "getLastAttributedTouchData": getLastAttributedTouchData(call, result); break; - case "getQRCodeAsData": - getQRCode("D", call, result); - break; - case "getQRCodeAsImage": - getQRCode("I", call, result); + case "getQRCode": + getQRCode(call, result); break; default: result.notImplemented(); @@ -719,7 +716,7 @@ public void onDataFetched(JSONObject jsonObject, BranchError error) { } } - private void getQRCode(final String type, final MethodCall call, final Result result) { + private void getQRCode(final MethodCall call, final Result result) { LogUtils.debug(DEBUG_NAME, "getQRCodeAsData call"); if (!(call.arguments instanceof Map)) { @@ -732,13 +729,12 @@ private void getQRCode(final String type, final MethodCall call, final Result re final BranchQRCode branchQRCode = branchSdkHelper.convertToQRCode((HashMap) argsMap.get("qrCodeSettings")); final Map response = new HashMap<>(); - if (type.equals("D")) { try { branchQRCode.getQRCodeAsData(context, buo, linkProperties, new BranchQRCode.BranchQRCodeDataHandler() { @Override public void onSuccess(byte[] qrCodeData) { response.put("success", Boolean.TRUE); - response.put("data", qrCodeData); + response.put("result", qrCodeData); result.success(response); } @Override @@ -755,43 +751,7 @@ public void onFailure(Exception error) { response.put("errorMessage", e.getMessage()); result.success(response); } - } else { - //https://gist.github.com/nikartm/79932c0a4f0a644f7ce020143146db98 - //https://github.com/flutter/plugins/blob/main/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java - //https://stackoverflow.com/questions/39538073/android-store-image-on-a-temp-file-cross-activity - //https://github.com/flutter/plugins/blob/main/packages/image_picker/image_picker_android/android/src/main/AndroidManifest.xml - //https://github.com/flutter/plugins/blob/main/packages/image_picker/image_picker_android/android/src/main/res/xml/flutter_image_picker_file_paths.xml - - try { - branchQRCode.getQRCodeAsImage(activity, buo, linkProperties, new BranchQRCode.BranchQRCodeImageHandler() { - @Override - public void onSuccess(Bitmap qrCodeImage) { - FileOutputStream outStream = null; - try { - outStream = new FileOutputStream(new File(context.getCacheDir(), "tempBMP")); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - /* - final Bitmap myBitmap = new Bitmap(); - myBitmap.compress(Bitmap.CompressFormat.JPEG, 75, outStream); - outStream.close(); - */ - } - - @Override - public void onFailure(Exception e) { - - } - }); - } catch (IOException e) { - response.put("success", Boolean.FALSE); - response.put("errorCode", "-1"); - response.put("errorMessage", e.getMessage()); - result.success(response); - } } - } } diff --git a/example/lib/main.dart b/example/lib/main.dart index e5ae9dca..2800db04 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -153,7 +153,7 @@ class _HomePageState extends State { ..addCustomMetadata('custom_list_number', [1, 2, 3, 4, 5]) ..addCustomMetadata('custom_list_string', ['a', 'b', 'c']), */ - contentMetadata: metadata, + //contentMetadata: metadata, keywords: ['Plugin', 'Branch', 'Flutter'], publiclyIndex: true, locallyIndex: true, @@ -550,8 +550,9 @@ class _HomePageState extends State { buo: buo!, linkProperties: lp, qrCode: BranchQrCode( - primaryColor: Colors.blue, - backgroundColor: const Color(0xff443a49), + primaryColor: Colors.black, + //backgroundColor: const Color(0xff443a49), + backgroundColor: Colors.white, imageFormat: BranchImageFormat.PNG)); if (responseQrCode.success) { print(responseQrCode.result); @@ -559,6 +560,23 @@ class _HomePageState extends State { print( 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); } + + BranchResponse responseQrCode2 = await FlutterBranchSdk.getQRCodeAsImage( + buo: buo!, + linkProperties: lp, + qrCode: BranchQrCode( + primaryColor: Colors.black, + //backgroundColor: const Color(0xff443a49), + backgroundColor: Colors.white, + imageFormat: BranchImageFormat.PNG)); + if (responseQrCode2.success) { + print((responseQrCode2.result as Image).width); + print((responseQrCode2.result as Image).height); + print('done'); + } else { + print( + 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); + } } void shareLink() async { diff --git a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift index d1f12f04..94c43c6c 100644 --- a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift +++ b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift @@ -221,11 +221,8 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream case "getLastAttributedTouchData": getLastAttributedTouchData(call: call, result: result) break - case "getQRCodeAsData": - getQRCode(type: "D", call: call, result: result) - break - case "getQRCodeAsImage": - getQRCode(type: "I", call: call, result: result) + case "getQRCode": + getQRCode(call: call, result: result) break default: result(FlutterMethodNotImplemented) @@ -555,50 +552,33 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream } } - private func getQRCode(type: String, call: FlutterMethodCall, result: @escaping FlutterResult) { + private func getQRCode(call: FlutterMethodCall, result: @escaping FlutterResult) { let args = call.arguments as! [String: Any?] let buoDict = args["buo"] as! [String: Any?] let lpDict = args["lp"] as! [String: Any?] let qrCodeDict = args["qrCodeSettings"] as! [String: Any?] - + let buo: BranchUniversalObject? = convertToBUO(dict: buoDict) let lp : BranchLinkProperties? = convertToLp(dict: lpDict ) let qrCode : BranchQRCode? = convertToQRCode(dict: qrCodeDict) let response : NSMutableDictionary! = [:] - if (type == "D") { - qrCode?.getAsData(buo, linkProperties: lp, completion: { data, error in - if (error == nil) { - response["success"] = NSNumber(value: true) - response["data"] = FlutterStandardTypedData(bytes: data!) - } else { - response["success"] = NSNumber(value: false) - if let err = (error as NSError?) { - response["errorCode"] = String(err.code) - response["errorMessage"] = err.localizedDescription - } - } - DispatchQueue.main.async { - result(response) - } - - }) - } else { - qrCode?.getAsImage(buo, linkProperties: lp, completion: { image, error in - if (error == nil) { - - } else { - response["success"] = NSNumber(value: false) - if let err = (error as NSError?) { - response["errorCode"] = String(err.code) - response["errorMessage"] = err.localizedDescription - } - } - DispatchQueue.main.async { - result(response) + qrCode?.getAsData(buo, linkProperties: lp, completion: { data, error in + if (error == nil) { + response["success"] = NSNumber(value: true) + response["result"] = FlutterStandardTypedData(bytes: data!) + } else { + response["success"] = NSNumber(value: false) + if let err = (error as NSError?) { + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription } - }) - } + } + DispatchQueue.main.async { + result(response) + } + + }) } } diff --git a/lib/src/flutter_branch_sdk_method_channel.dart b/lib/src/flutter_branch_sdk_method_channel.dart index e4fa8153..b101285a 100644 --- a/lib/src/flutter_branch_sdk_method_channel.dart +++ b/lib/src/flutter_branch_sdk_method_channel.dart @@ -1,6 +1,8 @@ +import 'dart:convert'; import 'dart:io'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'flutter_branch_sdk_platform_interface.dart'; import 'objects/app_tracking_transparency.dart'; @@ -279,13 +281,12 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required BranchQrCode qrCodeSettings}) async { - Map params = {}; - params['buo'] = buo.toMap(); - params['lp'] = linkProperties.toMap(); - params['qrCodeSettings'] = qrCodeSettings.toMap(); - Map response = - await messageChannel.invokeMethod('getQRCodeAsData', params); + await messageChannel.invokeMethod('getQRCode', { + 'buo': buo.toMap(), + 'lp': linkProperties.toMap(), + 'qrCodeSettings': qrCodeSettings.toMap() + }); if (response['success']) { return BranchResponse.success(result: response['result']); @@ -302,16 +303,16 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required BranchQrCode qrCodeSettings}) async { - Map params = {}; - params['buo'] = buo.toMap(); - params['lp'] = linkProperties.toMap(); - params['qrCodeSettings'] = qrCodeSettings.toMap(); - Map response = - await messageChannel.invokeMethod('getQRCodeAsImage', params); + await messageChannel.invokeMethod('getQRCode', { + 'buo': buo.toMap(), + 'lp': linkProperties.toMap(), + 'qrCodeSettings': qrCodeSettings.toMap() + }); if (response['success']) { - return BranchResponse.success(result: response['result']); + return BranchResponse.success( + result: Image.memory(base64Decode(response['result']))); } else { return BranchResponse.error( errorCode: response['errorCode'], diff --git a/lib/src/flutter_branch_sdk_web.dart b/lib/src/flutter_branch_sdk_web.dart index 56885de8..31a85ed1 100644 --- a/lib/src/flutter_branch_sdk_web.dart +++ b/lib/src/flutter_branch_sdk_web.dart @@ -8,6 +8,7 @@ import 'dart:js'; import 'dart:js_util'; import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'flutter_branch_sdk_platform_interface.dart'; @@ -384,7 +385,6 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required BranchQrCode qrCodeSettings}) async { - // TODO: implement getQRCodeAsData Completer responseCompleter = Completer(); Map data = buo.toMapWeb(); @@ -398,13 +398,13 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { }; try { - BranchJS.qrCode(linkData, qrCodeSettings.toMapWeb(), - allowInterop((err, data) { + BranchJS.qrCode(_dartObjectToJsObject(linkData), + _dartObjectToJsObject(qrCodeSettings.toMapWeb()), + allowInterop((err, qrCode) { if (err == null) { - if (data != null) { - debugPrint(data); - responseCompleter.complete( - BranchResponse.success(result: _jsObjectToDartObject(data))); + if (qrCode != null) { + responseCompleter + .complete(BranchResponse.success(result: qrCode.base64())); } else { responseCompleter.complete(BranchResponse.success(result: {})); } @@ -426,9 +426,16 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { Future getQRCodeAsImage( {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, - required BranchQrCode qrCodeSettings}) { - // TODO: implement getQRCodeAsImage - throw UnimplementedError(); + required BranchQrCode qrCodeSettings}) async { + BranchResponse response = await getQRCodeAsData( + buo: buo, + linkProperties: linkProperties, + qrCodeSettings: qrCodeSettings); + if (response.success) { + return BranchResponse.success( + result: Image.memory(base64Decode(response.result))); + } + return response; } void close() { diff --git a/lib/src/web/branch_js.dart b/lib/src/web/branch_js.dart index dcabca5c..a5ac5426 100644 --- a/lib/src/web/branch_js.dart +++ b/lib/src/web/branch_js.dart @@ -955,6 +955,6 @@ class BranchJS { [Function callback]); @JS('qrCode') - external static void qrCode(qrCodeLinkData, qrCodeSettings, + external static void qrCode(Object qrCodeLinkData, Object qrCodeSettings, [Function callback]); } From be2a0f64ee19e424e3d7a976e24a3f1e72346f72 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 23 Jun 2022 01:21:39 -0300 Subject: [PATCH 008/125] Little adjustments Little adjustments --- .../FlutterBranchSdkPlugin.java | 6 +- example/lib/main.dart | 561 ++++++++++-------- .../flutter_branch_sdk_method_channel.dart | 4 +- lib/src/flutter_branch_sdk_web.dart | 10 +- 4 files changed, 309 insertions(+), 272 deletions(-) diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java index 9958c109..569c924c 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java @@ -4,7 +4,6 @@ import android.app.Application; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -13,9 +12,6 @@ import org.json.JSONException; import org.json.JSONObject; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -729,10 +725,12 @@ private void getQRCode(final MethodCall call, final Result result) { final BranchQRCode branchQRCode = branchSdkHelper.convertToQRCode((HashMap) argsMap.get("qrCodeSettings")); final Map response = new HashMap<>(); + try { branchQRCode.getQRCodeAsData(context, buo, linkProperties, new BranchQRCode.BranchQRCodeDataHandler() { @Override public void onSuccess(byte[] qrCodeData) { + response.put("success", Boolean.TRUE); response.put("result", qrCodeData); result.success(response); diff --git a/example/lib/main.dart b/example/lib/main.dart index 2800db04..470169f7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -46,7 +46,6 @@ class _HomePageState extends State { StreamSubscription? streamSubscription; StreamController controllerData = StreamController(); StreamController controllerInitSession = StreamController(); - StreamController controllerUrl = StreamController(); @override void initState() { @@ -56,7 +55,7 @@ class _HomePageState extends State { initDeepLinkData(); - FlutterBranchSdk.setIdentity('branch_user_test'); + identifyUser(); //requestATTTracking(); } @@ -212,6 +211,257 @@ class _HomePageState extends State { ); } + void validSdkIntegration() { + if (kIsWeb) { + showSnackBar( + context: context, + message: 'validateSDKIntegration() not available in Flutter Web'); + return; + } + + FlutterBranchSdk.validateSDKIntegration(); + if (Platform.isAndroid) { + showSnackBar( + context: context, message: 'Check messages in run log or logcat'); + } + } + + void enableTracking() { + FlutterBranchSdk.disableTracking(false); + showSnackBar(context: context, message: 'Tracking enabled'); + } + + void disableTracking() { + FlutterBranchSdk.disableTracking(true); + showSnackBar(context: context, message: 'Tracking disabled'); + } + + void identifyUser() { + FlutterBranchSdk.setIdentity('branch_user_test'); + showSnackBar(context: context, message: 'User branch_user_test identfied'); + } + + void userLogout() { + FlutterBranchSdk.logout(); + showSnackBar(context: context, message: 'User branch_user_test logout'); + } + + void registerView() { + FlutterBranchSdk.registerView(buo: buo!); + showSnackBar(context: context, message: 'Event Registered'); + } + + void trackContent() { + FlutterBranchSdk.trackContent(buo: [buo!], branchEvent: eventStandart!); + + FlutterBranchSdk.trackContent(buo: [buo!], branchEvent: eventCustom!); + + FlutterBranchSdk.trackContentWithoutBuo(branchEvent: eventStandart!); + + FlutterBranchSdk.trackContentWithoutBuo(branchEvent: eventCustom!); + + showSnackBar(context: context, message: 'Tracked content'); + } + + void getFirstParameters() async { + Map params = + await FlutterBranchSdk.getFirstReferringParams(); + controllerData.sink.add(params.toString()); + showSnackBar(context: context, message: 'First Parameters recovered'); + } + + void getLastParameters() async { + Map params = + await FlutterBranchSdk.getLatestReferringParams(); + controllerData.sink.add(params.toString()); + showSnackBar(context: context, message: 'Last Parameters recovered'); + } + + void getLastAttributed() async { + BranchResponse response = + await FlutterBranchSdk.getLastAttributedTouchData(); + if (response.success) { + controllerData.sink.add(response.result.toString()); + showSnackBar( + context: context, message: 'Last Attributed TouchData recovered'); + } else { + showSnackBar( + context: context, + message: + 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', + duration: 5); + } + } + + void listOnSearch() async { + if (kIsWeb) { + showSnackBar( + context: context, + message: 'listOnSearch() not available in Flutter Web'); + return; + } + //Buo without Link Properties + bool success = await FlutterBranchSdk.listOnSearch(buo: buo!); + + //Buo with Link Properties + success = + await FlutterBranchSdk.listOnSearch(buo: buo!, linkProperties: lp); + + if (success) { + showSnackBar(context: context, message: 'Listed on Search'); + } + } + + void removeFromSearch() async { + if (kIsWeb) { + showSnackBar( + context: context, + message: 'removeFromSearch() not available in Flutter Web'); + return; + } + bool success = await FlutterBranchSdk.removeFromSearch(buo: buo!); + success = + await FlutterBranchSdk.removeFromSearch(buo: buo!, linkProperties: lp); + if (success) { + showSnackBar(context: context, message: 'Removed from Search'); + } + } + + void generateLink() async { + BranchResponse response = + await FlutterBranchSdk.getShortUrl(buo: buo!, linkProperties: lp); + if (response.success) { + showGeneratedLink(response.result); + } else { + showSnackBar( + context: context, + message: 'Error : ${response.errorCode} - ${response.errorMessage}'); + } + } + + void generateQrCode() async { + /* + BranchResponse responseQrCode = await FlutterBranchSdk.getQRCodeAsData( + buo: buo!, + linkProperties: lp, + qrCode: BranchQrCode( + primaryColor: Colors.black, + //backgroundColor: const Color(0xff443a49), + backgroundColor: Colors.white, + imageFormat: BranchImageFormat.PNG)); + if (responseQrCode.success) { + print(responseQrCode.result); + } else { + print( + 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); + } + */ + + BranchResponse responseQrCode = await FlutterBranchSdk.getQRCodeAsImage( + buo: buo!, + linkProperties: lp, + qrCode: BranchQrCode( + primaryColor: Colors.black, + //backgroundColor: const Color(0xff443a49), + backgroundColor: Colors.white, + imageFormat: BranchImageFormat.PNG)); + if (responseQrCode.success) { + showQrCode(responseQrCode.result); + } else { + showSnackBar( + context: context, + message: + 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); + } + } + + void showGeneratedLink(String url) async { + showModalBottomSheet( + isDismissible: true, + isScrollControlled: true, + context: context, + builder: (_) { + return Container( + padding: const EdgeInsets.all(12), + height: 150, + child: Column( + children: [ + const Center( + child: Text( + 'Link created', + style: TextStyle( + color: Colors.blue, fontWeight: FontWeight.bold), + )), + const SizedBox( + height: 10, + ), + Text(url), + const SizedBox( + height: 10, + ), + CustomButton( + onPressed: () async { + await Clipboard.setData(ClipboardData(text: url)); + Navigator.pop(this.context); + }, + child: const Center(child: Text('Copy link'))), + ], + ), + ); + }); + } + + void showQrCode(Image image) async { + showModalBottomSheet( + isDismissible: true, + isScrollControlled: true, + context: context, + builder: (_) { + return Container( + padding: const EdgeInsets.all(12), + height: 370, + child: Column( + children: [ + const Center( + child: Text( + 'Qr Code', + style: TextStyle( + color: Colors.blue, fontWeight: FontWeight.bold), + )), + const SizedBox( + height: 10, + ), + Image( + image: image.image, + height: 300, + width: 300, + ), + ], + ), + ); + }); + } + + void shareLink() async { + BranchResponse response = await FlutterBranchSdk.showShareSheet( + buo: buo!, + linkProperties: lp, + messageText: 'My Share text', + androidMessageTitle: 'My Message Title', + androidSharingTitle: 'My Share with'); + + if (response.success) { + showSnackBar( + context: context, message: 'showShareSheet Success', duration: 5); + } else { + showSnackBar( + context: context, + message: + 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', + duration: 5); + } + } + @override Widget build(BuildContext context) { return ScaffoldMessenger( @@ -251,251 +501,121 @@ class _HomePageState extends State { }, ), CustomButton( + onPressed: validSdkIntegration, child: const Text('Validate SDK Integration'), - onPressed: () { - if (kIsWeb) { - showSnackBar( - context: context, - message: - 'validateSDKIntegration() not available in Flutter Web'); - return; - } - - FlutterBranchSdk.validateSDKIntegration(); - if (Platform.isAndroid) { - showSnackBar( - context: context, - message: 'Check messages in run log or logcat'); - } - }, ), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: CustomButton( + onPressed: enableTracking, child: const Text('Enable tracking'), - onPressed: () { - FlutterBranchSdk.disableTracking(false); - showSnackBar( - context: context, message: 'Tracking enabled'); - }, ), ), - const SizedBox( - width: 10, - ), Expanded( child: CustomButton( + onPressed: disableTracking, child: const Text('Disable tracking'), - onPressed: () { - FlutterBranchSdk.disableTracking(true); - showSnackBar( - context: context, message: 'Tracking disabled'); - }, ), ), ], ), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: CustomButton( + onPressed: identifyUser, child: const Text('Identify user'), - onPressed: () { - FlutterBranchSdk.setIdentity('branch_user_test'); - showSnackBar( - context: context, - message: 'User branch_user_test identfied'); - }, ), ), - const SizedBox( - width: 10, - ), Expanded( child: CustomButton( + onPressed: userLogout, child: const Text('User logout'), - onPressed: () { - FlutterBranchSdk.logout(); - showSnackBar( - context: context, - message: 'User branch_user_test logout'); - }, ), ), ], ), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: CustomButton( + onPressed: registerView, child: const Text('Register view'), - onPressed: () { - FlutterBranchSdk.registerView(buo: buo!); - - showSnackBar( - context: context, message: 'Event Registered'); - }, ), ), - const SizedBox( - width: 10, - ), Expanded( child: CustomButton( + onPressed: trackContent, child: const Text('Track content'), - onPressed: () { - //FlutterBranchSdk.trackContent( - // buo: [buo!], branchEvent: eventStandart!); - - FlutterBranchSdk.trackContent( - buo: [buo!], branchEvent: eventCustom!); - - FlutterBranchSdk.trackContentWithoutBuo( - branchEvent: eventStandart!); - FlutterBranchSdk.trackContentWithoutBuo( - branchEvent: eventCustom!); - - showSnackBar( - context: context, message: 'Tracked content'); - }, ), ), ], ), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: CustomButton( - child: const Text('Get First Parameters'), - onPressed: () async { - Map params = - await FlutterBranchSdk.getFirstReferringParams(); - controllerData.sink.add(params.toString()); - showSnackBar( - context: context, - message: 'First Parameters recovered'); - }, + onPressed: getFirstParameters, + child: const Text('Get First Parameters', + textAlign: TextAlign.center), ), ), - const SizedBox( - width: 10, - ), Expanded( child: CustomButton( - child: const Text('Get Last Parameters'), - onPressed: () async { - Map params = - await FlutterBranchSdk.getLatestReferringParams(); - controllerData.sink.add(params.toString()); - showSnackBar( - context: context, - message: 'Last Parameters recovered'); - }, + onPressed: getLastParameters, + child: const Text('Get Last Parameters', + textAlign: TextAlign.center), ), ), + Expanded( + child: CustomButton( + onPressed: getLastAttributed, + child: const Text('Get Last Attributed', + textAlign: TextAlign.center), + ), + ) ], ), - CustomButton( - onPressed: getLastAttributed, - child: const Text('Get last Attributed'), - ), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: CustomButton( - child: const Text('List on Search'), - onPressed: () async { - if (kIsWeb) { - showSnackBar( - context: context, - message: - 'listOnSearch() not available in Flutter Web'); - return; - } - bool success = - await FlutterBranchSdk.listOnSearch(buo: buo!); - - success = await FlutterBranchSdk.listOnSearch( - buo: buo!, linkProperties: lp); - - if (success) { - showSnackBar( - context: context, message: 'Listed on Search'); - } - }, + onPressed: listOnSearch, + child: const Text('List on Search', + textAlign: TextAlign.center), ), ), - const SizedBox( - width: 10, - ), Expanded( child: CustomButton( - child: const Text('Remove from Search'), - onPressed: () async { - if (kIsWeb) { - showSnackBar( - context: context, - message: - 'removeFromSearch() not available in Flutter Web'); - return; - } - bool success = - await FlutterBranchSdk.removeFromSearch( - buo: buo!); - success = await FlutterBranchSdk.removeFromSearch( - buo: buo!, linkProperties: lp); - if (success) { - showSnackBar( - context: context, - message: 'Removed from Search'); - } - }, + onPressed: removeFromSearch, + child: const Text('Remove from Search', + textAlign: TextAlign.center), ), ), ], ), - CustomButton( - onPressed: generateLink, - child: const Text('Generate Link'), - ), - StreamBuilder( - stream: controllerUrl.stream, - initialData: '', - builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data!.isNotEmpty) { - return Column( - children: [ - const Center( - child: Text( - 'Link build', - style: TextStyle( - color: Colors.blue, - fontWeight: FontWeight.bold), - )), - Center( - child: GestureDetector( - onTap: () async { - await Clipboard.setData( - ClipboardData(text: snapshot.data!)); - showSnackBar( - context: context, - message: 'Copied to Clipboard', - duration: 2); - }, - child: Text(snapshot.data!), - )) - ], - ); - } else { - return Container(); - } - }, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: CustomButton( + onPressed: () => generateLink, + child: const Text('Generate Link'), + ), + ), + Expanded( + child: CustomButton( + onPressed: () => generateQrCode, + child: const Text('Generate QrCode'), + ), + ), + ], ), CustomButton( onPressed: shareLink, @@ -507,7 +627,7 @@ class _HomePageState extends State { const Divider(), const Center( child: Text( - 'Deep Link data', + 'Data', style: TextStyle( color: Colors.blue, fontWeight: FontWeight.bold), ), @@ -515,7 +635,7 @@ class _HomePageState extends State { const Divider(), StreamBuilder( stream: controllerData.stream, - initialData: '', + initialData: null, builder: (context, snapshot) { if (snapshot.hasData && snapshot.data!.isNotEmpty) { return Column( @@ -536,89 +656,10 @@ class _HomePageState extends State { ); } - void generateLink() async { - BranchResponse response = - await FlutterBranchSdk.getShortUrl(buo: buo!, linkProperties: lp); - if (response.success) { - controllerUrl.sink.add('${response.result}'); - } else { - controllerUrl.sink - .add('Error : ${response.errorCode} - ${response.errorMessage}'); - } - - BranchResponse responseQrCode = await FlutterBranchSdk.getQRCodeAsData( - buo: buo!, - linkProperties: lp, - qrCode: BranchQrCode( - primaryColor: Colors.black, - //backgroundColor: const Color(0xff443a49), - backgroundColor: Colors.white, - imageFormat: BranchImageFormat.PNG)); - if (responseQrCode.success) { - print(responseQrCode.result); - } else { - print( - 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); - } - - BranchResponse responseQrCode2 = await FlutterBranchSdk.getQRCodeAsImage( - buo: buo!, - linkProperties: lp, - qrCode: BranchQrCode( - primaryColor: Colors.black, - //backgroundColor: const Color(0xff443a49), - backgroundColor: Colors.white, - imageFormat: BranchImageFormat.PNG)); - if (responseQrCode2.success) { - print((responseQrCode2.result as Image).width); - print((responseQrCode2.result as Image).height); - print('done'); - } else { - print( - 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); - } - } - - void shareLink() async { - BranchResponse response = await FlutterBranchSdk.showShareSheet( - buo: buo!, - linkProperties: lp, - messageText: 'My Share text', - androidMessageTitle: 'My Message Title', - androidSharingTitle: 'My Share with'); - - if (response.success) { - showSnackBar( - context: context, message: 'showShareSheet Success', duration: 5); - } else { - showSnackBar( - context: context, - message: - 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', - duration: 5); - } - } - - void getLastAttributed() async { - BranchResponse response = - await FlutterBranchSdk.getLastAttributedTouchData(); - if (response.success) { - showSnackBar( - context: context, message: response.result.toString(), duration: 5); - } else { - showSnackBar( - context: context, - message: - 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', - duration: 5); - } - } - @override void dispose() { super.dispose(); controllerData.close(); - controllerUrl.close(); controllerInitSession.close(); streamSubscription?.cancel(); } diff --git a/lib/src/flutter_branch_sdk_method_channel.dart b/lib/src/flutter_branch_sdk_method_channel.dart index b101285a..588d19fb 100644 --- a/lib/src/flutter_branch_sdk_method_channel.dart +++ b/lib/src/flutter_branch_sdk_method_channel.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:flutter/services.dart'; @@ -311,8 +310,7 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { }); if (response['success']) { - return BranchResponse.success( - result: Image.memory(base64Decode(response['result']))); + return BranchResponse.success(result: Image.memory(response['result'])); } else { return BranchResponse.error( errorCode: response['errorCode'], diff --git a/lib/src/flutter_branch_sdk_web.dart b/lib/src/flutter_branch_sdk_web.dart index 31a85ed1..c539a104 100644 --- a/lib/src/flutter_branch_sdk_web.dart +++ b/lib/src/flutter_branch_sdk_web.dart @@ -7,7 +7,6 @@ import 'dart:convert'; import 'dart:js'; import 'dart:js_util'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; @@ -360,7 +359,6 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { allowInterop((err, data) { if (err == null) { if (data != null) { - debugPrint(data); responseCompleter.complete( BranchResponse.success(result: _jsObjectToDartObject(data))); } else { @@ -403,8 +401,8 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { allowInterop((err, qrCode) { if (err == null) { if (qrCode != null) { - responseCompleter - .complete(BranchResponse.success(result: qrCode.base64())); + responseCompleter.complete( + BranchResponse.success(result: qrCode.rawBuffer.asUint8List())); } else { responseCompleter.complete(BranchResponse.success(result: {})); } @@ -433,7 +431,9 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { qrCodeSettings: qrCodeSettings); if (response.success) { return BranchResponse.success( - result: Image.memory(base64Decode(response.result))); + result: Image.memory( + response.result, + )); } return response; } From 28e28f3d0169a0571f699d590a9b72fad18dd18a Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 23 Jun 2022 01:50:35 -0300 Subject: [PATCH 009/125] Update main.dart Refactor Example App --- example/lib/main.dart | 113 +++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 63 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 470169f7..3ab26bed 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -55,7 +55,7 @@ class _HomePageState extends State { initDeepLinkData(); - identifyUser(); + FlutterBranchSdk.setIdentity('branch_user_test'); //requestATTTracking(); } @@ -93,7 +93,6 @@ class _HomePageState extends State { print( '------------------------------------------------------------------------------------------------'); showSnackBar( - context: context, message: 'Link clicked: Custom string - ${data['custom_string']}', duration: 10); } @@ -169,10 +168,10 @@ class _HomePageState extends State { // Aliases are enforced to be unique** and immutable per domain, and per link - they cannot be reused unless deleted. //alias: 'https://branch.io' //define link url, stage: 'new share', - campaign: 'xxxxx', + campaign: 'campaign', tags: ['one', 'two', 'three']) ..addControlParam('\$uri_redirect_mode', '1') - ..addControlParam('referring_user_id', 'asdf'); + ..addControlParam('referring_user_id', 'user_id'); eventStandart = BranchEvent.standardEvent(BranchStandardEvent.ADD_TO_CART) //--optional Event data @@ -198,10 +197,7 @@ class _HomePageState extends State { 'Custom_Event_Property_Key2', 'Custom_Event_Property_val2'); } - void showSnackBar( - {required BuildContext context, - required String message, - int duration = 1}) { + void showSnackBar({required String message, int duration = 1}) { scaffoldMessengerKey.currentState!.removeCurrentSnackBar(); scaffoldMessengerKey.currentState!.showSnackBar( SnackBar( @@ -214,41 +210,39 @@ class _HomePageState extends State { void validSdkIntegration() { if (kIsWeb) { showSnackBar( - context: context, message: 'validateSDKIntegration() not available in Flutter Web'); return; } FlutterBranchSdk.validateSDKIntegration(); if (Platform.isAndroid) { - showSnackBar( - context: context, message: 'Check messages in run log or logcat'); + showSnackBar(message: 'Check messages in run log or logcat'); } } void enableTracking() { FlutterBranchSdk.disableTracking(false); - showSnackBar(context: context, message: 'Tracking enabled'); + showSnackBar(message: 'Tracking enabled'); } void disableTracking() { FlutterBranchSdk.disableTracking(true); - showSnackBar(context: context, message: 'Tracking disabled'); + showSnackBar(message: 'Tracking disabled'); } void identifyUser() { FlutterBranchSdk.setIdentity('branch_user_test'); - showSnackBar(context: context, message: 'User branch_user_test identfied'); + showSnackBar(message: 'User branch_user_test identfied'); } void userLogout() { FlutterBranchSdk.logout(); - showSnackBar(context: context, message: 'User branch_user_test logout'); + showSnackBar(message: 'User branch_user_test logout'); } void registerView() { FlutterBranchSdk.registerView(buo: buo!); - showSnackBar(context: context, message: 'Event Registered'); + showSnackBar(message: 'Event Registered'); } void trackContent() { @@ -260,21 +254,21 @@ class _HomePageState extends State { FlutterBranchSdk.trackContentWithoutBuo(branchEvent: eventCustom!); - showSnackBar(context: context, message: 'Tracked content'); + showSnackBar(message: 'Tracked content'); } void getFirstParameters() async { Map params = await FlutterBranchSdk.getFirstReferringParams(); controllerData.sink.add(params.toString()); - showSnackBar(context: context, message: 'First Parameters recovered'); + showSnackBar(message: 'First Parameters recovered'); } void getLastParameters() async { Map params = await FlutterBranchSdk.getLatestReferringParams(); controllerData.sink.add(params.toString()); - showSnackBar(context: context, message: 'Last Parameters recovered'); + showSnackBar(message: 'Last Parameters recovered'); } void getLastAttributed() async { @@ -282,11 +276,9 @@ class _HomePageState extends State { await FlutterBranchSdk.getLastAttributedTouchData(); if (response.success) { controllerData.sink.add(response.result.toString()); - showSnackBar( - context: context, message: 'Last Attributed TouchData recovered'); + showSnackBar(message: 'Last Attributed TouchData recovered'); } else { showSnackBar( - context: context, message: 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', duration: 5); @@ -295,9 +287,7 @@ class _HomePageState extends State { void listOnSearch() async { if (kIsWeb) { - showSnackBar( - context: context, - message: 'listOnSearch() not available in Flutter Web'); + showSnackBar(message: 'listOnSearch() not available in Flutter Web'); return; } //Buo without Link Properties @@ -308,40 +298,39 @@ class _HomePageState extends State { await FlutterBranchSdk.listOnSearch(buo: buo!, linkProperties: lp); if (success) { - showSnackBar(context: context, message: 'Listed on Search'); + showSnackBar(message: 'Listed on Search'); } } void removeFromSearch() async { if (kIsWeb) { - showSnackBar( - context: context, - message: 'removeFromSearch() not available in Flutter Web'); + showSnackBar(message: 'removeFromSearch() not available in Flutter Web'); return; } bool success = await FlutterBranchSdk.removeFromSearch(buo: buo!); success = await FlutterBranchSdk.removeFromSearch(buo: buo!, linkProperties: lp); if (success) { - showSnackBar(context: context, message: 'Removed from Search'); + showSnackBar(message: 'Removed from Search'); } } - void generateLink() async { + void generateLink(BuildContext context) async { BranchResponse response = await FlutterBranchSdk.getShortUrl(buo: buo!, linkProperties: lp); if (response.success) { - showGeneratedLink(response.result); + showGeneratedLink(this.context, response.result); } else { showSnackBar( - context: context, message: 'Error : ${response.errorCode} - ${response.errorMessage}'); } } - void generateQrCode() async { + void generateQrCode( + BuildContext context, + ) async { /* - BranchResponse responseQrCode = await FlutterBranchSdk.getQRCodeAsData( + BranchResponse responseQrCodeData = await FlutterBranchSdk.getQRCodeAsData( buo: buo!, linkProperties: lp, qrCode: BranchQrCode( @@ -349,33 +338,33 @@ class _HomePageState extends State { //backgroundColor: const Color(0xff443a49), backgroundColor: Colors.white, imageFormat: BranchImageFormat.PNG)); - if (responseQrCode.success) { - print(responseQrCode.result); + if (responseQrCodeData.success) { + print(responseQrCodeData.result); } else { print( - 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); + 'Error : ${responseQrCodeData.errorCode} - ${responseQrCodeData.errorMessage}'); } */ - BranchResponse responseQrCode = await FlutterBranchSdk.getQRCodeAsImage( - buo: buo!, - linkProperties: lp, - qrCode: BranchQrCode( - primaryColor: Colors.black, - //backgroundColor: const Color(0xff443a49), - backgroundColor: Colors.white, - imageFormat: BranchImageFormat.PNG)); - if (responseQrCode.success) { - showQrCode(responseQrCode.result); + BranchResponse responseQrCodeImage = + await FlutterBranchSdk.getQRCodeAsImage( + buo: buo!, + linkProperties: lp, + qrCode: BranchQrCode( + primaryColor: Colors.black, + //backgroundColor: const Color(0xff443a49), + backgroundColor: Colors.white, + imageFormat: BranchImageFormat.PNG)); + if (responseQrCodeImage.success) { + showQrCode(this.context, responseQrCodeImage.result); } else { showSnackBar( - context: context, message: - 'Error : ${responseQrCode.errorCode} - ${responseQrCode.errorMessage}'); + 'Error : ${responseQrCodeImage.errorCode} - ${responseQrCodeImage.errorMessage}'); } } - void showGeneratedLink(String url) async { + void showGeneratedLink(BuildContext context, String url) async { showModalBottomSheet( isDismissible: true, isScrollControlled: true, @@ -411,7 +400,7 @@ class _HomePageState extends State { }); } - void showQrCode(Image image) async { + void showQrCode(BuildContext context, Image image) async { showModalBottomSheet( isDismissible: true, isScrollControlled: true, @@ -419,7 +408,7 @@ class _HomePageState extends State { builder: (_) { return Container( padding: const EdgeInsets.all(12), - height: 370, + height: 350, child: Column( children: [ const Center( @@ -433,9 +422,12 @@ class _HomePageState extends State { ), Image( image: image.image, - height: 300, - width: 300, + height: 250, + width: 250, ), + CustomButton( + onPressed: () => Navigator.pop(this.context), + child: const Center(child: Text('Close'))), ], ), ); @@ -451,11 +443,9 @@ class _HomePageState extends State { androidSharingTitle: 'My Share with'); if (response.success) { - showSnackBar( - context: context, message: 'showShareSheet Success', duration: 5); + showSnackBar(message: 'showShareSheet Success', duration: 5); } else { showSnackBar( - context: context, message: 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', duration: 5); @@ -605,13 +595,13 @@ class _HomePageState extends State { children: [ Expanded( child: CustomButton( - onPressed: () => generateLink, + onPressed: () => generateLink(context), child: const Text('Generate Link'), ), ), Expanded( child: CustomButton( - onPressed: () => generateQrCode, + onPressed: () => generateQrCode(context), child: const Text('Generate QrCode'), ), ), @@ -621,9 +611,6 @@ class _HomePageState extends State { onPressed: shareLink, child: const Text('Share Link'), ), - const SizedBox( - height: 10, - ), const Divider(), const Center( child: Text( From d171770c29aa4501988c1d06432ba1ccfdb3846d Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 23 Jun 2022 01:52:35 -0300 Subject: [PATCH 010/125] Create branch_logo.png --- assets/branch_logo.png | Bin 0 -> 4102 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/branch_logo.png diff --git a/assets/branch_logo.png b/assets/branch_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..15ffe9d47654f426f2901e81f40d904229821c39 GIT binary patch literal 4102 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 literal 0 HcmV?d00001 From 357f0bdad371c2e4bb5053252581550716b4f871 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 23 Jun 2022 02:04:18 -0300 Subject: [PATCH 011/125] New assets --- assets/branch_logo.png | Bin 4102 -> 0 bytes assets/branch_logo_qrcode.jpeg | Bin 0 -> 2370 bytes example/lib/main.dart | 2 ++ 3 files changed, 2 insertions(+) delete mode 100644 assets/branch_logo.png create mode 100644 assets/branch_logo_qrcode.jpeg diff --git a/assets/branch_logo.png b/assets/branch_logo.png deleted file mode 100644 index 15ffe9d47654f426f2901e81f40d904229821c39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4102 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 diff --git a/assets/branch_logo_qrcode.jpeg b/assets/branch_logo_qrcode.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..14e6d995fd3daee03817a5bf202847b00660f00e GIT binary patch literal 2370 zcmbW1dofoL5!&>nT&g52D!)N9&c(zF{v0yE>WmMIZRC6(A#m3 z!I7f~DN$}ABIJIE+$z^{iSawbX`R-3|9aQ^?6vmu-D^M3v-fAOXKlC#9tPr;rWU3E zf&eh*4*-vVSd*YLo&Z=|gFOHMAs_%r03=^Q`~e^tAh4wYu!Lm)(RL8=dkg}A=raKI zJ;s&qH;rF;GyA(orb9m}rX&7|wn|6-(3@%SXP|S0>3h-FpXtj|&`?tcIz|@OsLkqp z+tMmqQoK_xw)Ylzh?)u)PX4rFbOo*kXdz$)tdNirK%gNc8iH#9o_`AgXv?xq$%fl@*q#(r@{>Fek&q#@%c!_U z%C3D*S;PH8q_7BPCstZUg`i5@y@#TyrLCi@XMD)S)Xdz%(%!+*$@!>@tH-I+o?gr| z-u@Q@E(Hb!hg`e%mGb{UkPA)eu|KX#O(#K`x6;EEgtgWkW zXnfVw@wT(8`(00O-^lyXu@B=DlT*|43yWXAE-kODe%s{Yzvtgr{P}OP|KdXPxezE6 z5+%6F1t9`A!O2?=@ay~-pSg{GxVqiECg^$ya= zTACCZnMTv38W>QhT66;~9V&%P)}m3h_)98{OrcV!@F2e}1>im)iiG&hghT@xSaK!i zSntWasN-f+X>6_C3T8sLuuiDyy2s0 z862w?b8tAYK+o$%?e`|y?8C!xFbIP_)8)1Yt=YgJE!$ukd#gp%@indP zlKpSRZbq+pgTvaq1=d*u8fq%Ie~IZ5?N<9}LsvIHB<*=D#A(;qax!y#t!Vp4ns6wR6e`XqF zbzu-5dG$W)xe#T+BXz;%^j%ZwB_{%-)sDX*p9u9%$|yS}?6p+`ACh?!KA##YXt{Q7^o1#uO7P>o3I2a6ggXgfplGxm=&m90%suk;dvMCuvbJ6q+ti;xgaVAQ>2s zyP9gZqx732;;pV?7!!AwsF)AgWl?6bZlk6rC+iGpg+a5?7n!HJvPvdI-u`yE`?mM_3kA3@=~E^Z zO+1_SsGuU^yc6e}W7CN~QF;r;JX%Cs-kDdwfuE_;$2p}%hnfURwP-Ed?VWG6%&tV} znM*##?njA>`C86s#_6gl+}SDZBA&NyhPr(1+KPjvnp#WWj@Z%HJJWdwc&ahFS-NZ! z(cGTc#Yyk3;I^8z@r9Xv%4>6$HdJj5!3JKbh1i>zFWx42xc!URjfM@CcgH88ZyH2e zMoy^ii>W)gIUWOyN{lBhMe}OC7U439n>0(=U&b3g@SH2vwoa^aDVfKwF9sS>rPwPl zm~W7Tl2qgNKM>}$EBq@oK9%Xd5bk#`5`kpveSOgUB4p*+wU++kt`z0N6Q^QW)$*em z^7ZB9k>)kVfH7jgNO&Q8QR-5jF2ckosLl4?`$v>P&ts&cBAT$~* z)OctwecFiMbe_MJnidj%Z&iI+B~h_0WvsQU= z(j9@n7(|nX6UtmWJHv*8U=TsNsw=x#bhwDT;+Qg1y1i{Aq1ygqMXDhT;#(Z2x>73O zjwSo!*5_$U2DwVlo4x!SeMjgv#&~OX9b;{8bZN+IkCm*K?L#lzZ%68y;X}0RA^B%4 zGnRS!PXP)GdJa3g$t`g>Zjo$&lk~Qy&rRm#S~>4KR;AOYBx8j8veu$k-r{SP-wn7w z+kd$0WI27ViL*aH-tUvyk+3)rx|%asSU7D}k{C5^T!K%|FW~~^1-}T++59x8&eAAy zbq?v=@MjedQu3(nrEm~#g3ueL~tEUumI$z zUx^GqIP>So=K=3s>{V3Ig!+0DV@P{O2n)tAMdP{RwSiP^UBTmBE68BX$+ z3AN8)GrJE_9jkYJot-^~$(nnxZ|HPk?c>>gdgdd8Txpf2?$zP-}lt)=Op!+ zpUMqz>gOf`^()#mb73ITG48*S!aexwpaZ#WWS1li8c1)s>7sbC71NHk { qrCode: BranchQrCode( primaryColor: Colors.black, //backgroundColor: const Color(0xff443a49), + centerLogoUrl: + 'https://raw.githubusercontent.com/RodrigoSMarques/flutter_branch_sdk/dev/assets/branch_logo.png', backgroundColor: Colors.white, imageFormat: BranchImageFormat.PNG)); if (responseQrCodeImage.success) { From c7428abe4f4fa51925a2b46f13ec5780148a5a55 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 23 Jun 2022 02:05:27 -0300 Subject: [PATCH 012/125] Update main.dart --- example/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index a6e4023f..0e1e7f7b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -354,7 +354,7 @@ class _HomePageState extends State { primaryColor: Colors.black, //backgroundColor: const Color(0xff443a49), centerLogoUrl: - 'https://raw.githubusercontent.com/RodrigoSMarques/flutter_branch_sdk/dev/assets/branch_logo.png', + 'https://raw.githubusercontent.com/RodrigoSMarques/flutter_branch_sdk/dev/assets/branch_logo_qrcode.jpeg', backgroundColor: Colors.white, imageFormat: BranchImageFormat.PNG)); if (responseQrCodeImage.success) { From 89953467b5de1042af52fa8cc93ba241005ae64d Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 23 Jun 2022 02:18:20 -0300 Subject: [PATCH 013/125] Fix center_logo_url --- example/lib/main.dart | 2 +- lib/src/objects/branch_qrcode.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 0e1e7f7b..d43a479a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -352,7 +352,7 @@ class _HomePageState extends State { linkProperties: lp, qrCode: BranchQrCode( primaryColor: Colors.black, - //backgroundColor: const Color(0xff443a49), + //primaryColor: const Color(0xff443a49), centerLogoUrl: 'https://raw.githubusercontent.com/RodrigoSMarques/flutter_branch_sdk/dev/assets/branch_logo_qrcode.jpeg', backgroundColor: Colors.white, diff --git a/lib/src/objects/branch_qrcode.dart b/lib/src/objects/branch_qrcode.dart index 267e0f7c..4fdb2cc0 100644 --- a/lib/src/objects/branch_qrcode.dart +++ b/lib/src/objects/branch_qrcode.dart @@ -72,7 +72,7 @@ class BranchQrCode { } ret["image_format"] = imageFormat.name.toLowerCase(); if (centerLogoUrl.isNotEmpty) { - ret["centerLogoUrl"] = centerLogoUrl; + ret["center_logo_url"] = centerLogoUrl; } return ret; } From c95a1f3c5cb2b8ad4385a79b679860c81fd4fef1 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Thu, 23 Jun 2022 09:14:41 -0300 Subject: [PATCH 014/125] Update main.dart --- example/lib/main.dart | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d43a479a..0a046a27 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -390,12 +390,15 @@ class _HomePageState extends State { const SizedBox( height: 10, ), - CustomButton( - onPressed: () async { - await Clipboard.setData(ClipboardData(text: url)); - Navigator.pop(this.context); - }, - child: const Center(child: Text('Copy link'))), + IntrinsicWidth( + stepWidth: 300, + child: CustomButton( + onPressed: () async { + await Clipboard.setData(ClipboardData(text: url)); + Navigator.pop(this.context); + }, + child: const Center(child: Text('Copy link'))), + ), ], ), ); @@ -410,7 +413,7 @@ class _HomePageState extends State { builder: (_) { return Container( padding: const EdgeInsets.all(12), - height: 350, + height: 370, child: Column( children: [ const Center( @@ -427,9 +430,12 @@ class _HomePageState extends State { height: 250, width: 250, ), - CustomButton( - onPressed: () => Navigator.pop(this.context), - child: const Center(child: Text('Close'))), + IntrinsicWidth( + stepWidth: 300, + child: CustomButton( + onPressed: () => Navigator.pop(this.context), + child: const Center(child: Text('Close'))), + ), ], ), ); From c473937197265cda873a09c12d72ff4766de187c Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Fri, 24 Jun 2022 08:16:29 -0300 Subject: [PATCH 015/125] new method shareWithLPLinkMetadata --- assets/branch.png | Bin 0 -> 13952 bytes example/lib/main.dart | 14 +- ios/Classes/FlutterBranchIoSdkHelper.swift | 26 ++++ ios/Classes/SwiftFlutterBranchSdkPlugin.swift | 139 +++++++++++------- lib/flutter_branch_sdk.dart | 2 + lib/src/flutter_branch_sdk.dart | 14 ++ .../flutter_branch_sdk_method_channel.dart | 29 +++- ...flutter_branch_sdk_platform_interface.dart | 9 ++ 8 files changed, 166 insertions(+), 67 deletions(-) create mode 100644 assets/branch.png diff --git a/assets/branch.png b/assets/branch.png new file mode 100644 index 0000000000000000000000000000000000000000..7ae2d2f5066fe26f6ea503b9c84d006365db6535 GIT binary patch literal 13952 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 literal 0 HcmV?d00001 diff --git a/example/lib/main.dart b/example/lib/main.dart index 0a046a27..efcfa958 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -47,6 +47,8 @@ class _HomePageState extends State { StreamController controllerData = StreamController(); StreamController controllerInitSession = StreamController(); + static const imageURL = + 'https://raw.githubusercontent.com/RodrigoSMarques/flutter_branch_sdk/dev/assets/branch_logo_qrcode.jpeg'; @override void initState() { super.initState(); @@ -140,8 +142,7 @@ class _HomePageState extends State { // even if the user goes to the app instead of your website! This will help your SEO efforts. canonicalUrl: 'https://flutter.dev', title: 'Flutter Branch Plugin', - imageUrl: - 'https://flutter.dev/assets/flutter-lockup-4cb0ee072ab312e59784d9fbf4fb7ad42688a7fdaea1270ccf6bbf4f34b7e03f.svg', + imageUrl: imageURL, contentDescription: 'Flutter Branch Description', /* contentMetadata: BranchContentMetaData() @@ -353,8 +354,7 @@ class _HomePageState extends State { qrCode: BranchQrCode( primaryColor: Colors.black, //primaryColor: const Color(0xff443a49), - centerLogoUrl: - 'https://raw.githubusercontent.com/RodrigoSMarques/flutter_branch_sdk/dev/assets/branch_logo_qrcode.jpeg', + centerLogoUrl: imageURL, backgroundColor: Colors.white, imageFormat: BranchImageFormat.PNG)); if (responseQrCodeImage.success) { @@ -458,6 +458,12 @@ class _HomePageState extends State { 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', duration: 5); } + + FlutterBranchSdk.shareWithLPLinkMetadata( + buo: buo!, + linkProperties: lp, + title: "Teste shareWithLPLinkMetadata", + icon: Image.network(imageURL)); } @override diff --git a/ios/Classes/FlutterBranchIoSdkHelper.swift b/ios/Classes/FlutterBranchIoSdkHelper.swift index 91f8fc2c..12854000 100644 --- a/ios/Classes/FlutterBranchIoSdkHelper.swift +++ b/ios/Classes/FlutterBranchIoSdkHelper.swift @@ -261,6 +261,15 @@ extension Bundle { } return value } + public var icon: UIImage? { + if let icons = infoDictionary?["CFBundleIcons"] as? [String: Any], + let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], + let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], + let lastIcon = iconFiles.last { + return UIImage(named: lastIcon) + } + return nil + } } extension UIColor { @@ -291,3 +300,20 @@ extension UIColor { return String(format:"#%06x", rgb) } } + +extension UIImage { + public static func loadFrom(url: URL, completion: @escaping (_ image: UIImage?) -> ()) { + DispatchQueue.global().async { + if let data = try? Data(contentsOf: url) { + DispatchQueue.main.async { + completion(UIImage(data: data)) + } + } else { + DispatchQueue.main.async { + completion(nil) + } + } + } + } + +} diff --git a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift index 94c43c6c..3a7625db 100644 --- a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift +++ b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift @@ -224,6 +224,8 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream case "getQRCode": getQRCode(call: call, result: result) break + case"shareWithLPLinkMetadata": + shareWithLPLinkMetadata(call: call, result: result) default: result(FlutterMethodNotImplemented) break @@ -472,60 +474,7 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream result(Branch.getInstance().isUserIdentified()) } } - - /* - https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager - - ATTrackingManager.AuthorizationStatus: - - authorized = 3 - - denied = 2 - - notDetermined = 0 - - restricted = 1 - */ - - private func requestTrackingAuthorization(result: @escaping FlutterResult) { - if #available(iOS 14, *) { - ATTrackingManager.requestTrackingAuthorization { (status) in - Branch.getInstance().handleATTAuthorizationStatus(status.rawValue) - - DispatchQueue.main.async { - result(Int(status.rawValue)) - } - } - } else { - DispatchQueue.main.async { - result(Int(4)) // return notSupported - } - } - } - - private func getTrackingAuthorizationStatus(result: @escaping FlutterResult) { - if #available(iOS 14, *) { - DispatchQueue.main.async { - result(Int(ATTrackingManager.trackingAuthorizationStatus.rawValue)) - } - } else { - DispatchQueue.main.async { - result(Int(4)) // return notSupported - } - } - } - - private func getAdvertisingIdentifier(result: @escaping FlutterResult) { - if #available(iOS 14, *) { - let status = ATTrackingManager.trackingAuthorizationStatus - if status == .authorized { - result(String(ASIdentifierManager.shared().advertisingIdentifier.uuidString)) - } else { - result(String("")) // return notSupported - } - } else { - DispatchQueue.main.async { - result(String("")) // return notSupported - } - } - } - + private func setTimeout(call: FlutterMethodCall) { let args = call.arguments as! [String: Any?] let _ = args["timeout"] as? Int ?? 0 @@ -581,4 +530,84 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream }) } -} + + private func shareWithLPLinkMetadata(call: FlutterMethodCall, result: @escaping FlutterResult) { + + let args = call.arguments as! [String: Any?] + let buoDict = args["buo"] as! [String: Any?] + let lpDict = args["lp"] as! [String: Any?] + let title = args["title"] as! String + let buo: BranchUniversalObject? = convertToBUO(dict: buoDict) + let lp : BranchLinkProperties? = convertToLp(dict: lpDict ) + var iconImage : UIImage? + + if let buoURL = buo?.imageUrl { + let url = URL(string: buoURL)! + UIImage.loadFrom(url: url) { image in + iconImage = image + } + } else { + iconImage = Bundle.main.icon + } + + let bsl = BranchShareLink(universalObject: buo!, linkProperties: lp!) + if #available(iOS 13.0, *) { + bsl.addLPLinkMetadata(title, icon: iconImage) + let controller = UIApplication.shared.keyWindow!.rootViewController + bsl.presentActivityViewController(from: controller, anchor: nil) + } + } + + /* + https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager + + ATTrackingManager.AuthorizationStatus: + - authorized = 3 + - denied = 2 + - notDetermined = 0 + - restricted = 1 + */ + + private func requestTrackingAuthorization(result: @escaping FlutterResult) { + if #available(iOS 14, *) { + ATTrackingManager.requestTrackingAuthorization { (status) in + Branch.getInstance().handleATTAuthorizationStatus(status.rawValue) + + DispatchQueue.main.async { + result(Int(status.rawValue)) + } + } + } else { + DispatchQueue.main.async { + result(Int(4)) // return notSupported + } + } + } + + private func getTrackingAuthorizationStatus(result: @escaping FlutterResult) { + if #available(iOS 14, *) { + DispatchQueue.main.async { + result(Int(ATTrackingManager.trackingAuthorizationStatus.rawValue)) + } + } else { + DispatchQueue.main.async { + result(Int(4)) // return notSupported + } + } + } + + private func getAdvertisingIdentifier(result: @escaping FlutterResult) { + if #available(iOS 14, *) { + let status = ATTrackingManager.trackingAuthorizationStatus + if status == .authorized { + result(String(ASIdentifierManager.shared().advertisingIdentifier.uuidString)) + } else { + result(String("")) // return notSupported + } + } else { + DispatchQueue.main.async { + result(String("")) // return notSupported + } + } + } + } diff --git a/lib/flutter_branch_sdk.dart b/lib/flutter_branch_sdk.dart index 224e4fb8..c73ca132 100644 --- a/lib/flutter_branch_sdk.dart +++ b/lib/flutter_branch_sdk.dart @@ -1,5 +1,7 @@ library flutter_branch_sdk; +import 'package:flutter/widgets.dart'; + import 'src/flutter_branch_sdk_platform_interface.dart'; import 'src/objects/app_tracking_transparency.dart'; import 'src/objects/branch_universal_object.dart'; diff --git a/lib/src/flutter_branch_sdk.dart b/lib/src/flutter_branch_sdk.dart index 87620601..cca22b1a 100644 --- a/lib/src/flutter_branch_sdk.dart +++ b/lib/src/flutter_branch_sdk.dart @@ -189,4 +189,18 @@ class FlutterBranchSdk { return FlutterBranchSdkPlatform.instance.getQRCodeAsImage( buo: buo, linkProperties: linkProperties, qrCodeSettings: qrCode); } + + static void shareWithLPLinkMetadata( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required Image icon, + required String title}) { + Map params = {}; + params['buo'] = buo.toMap(); + params['lp'] = linkProperties.toMap(); + params['title'] = title; + + FlutterBranchSdkPlatform.instance.shareWithLPLinkMetadata( + buo: buo, linkProperties: linkProperties, icon: icon, title: title); + } } diff --git a/lib/src/flutter_branch_sdk_method_channel.dart b/lib/src/flutter_branch_sdk_method_channel.dart index 588d19fb..71ba5dea 100644 --- a/lib/src/flutter_branch_sdk_method_channel.dart +++ b/lib/src/flutter_branch_sdk_method_channel.dart @@ -98,15 +98,14 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { required String messageText, String androidMessageTitle = '', String androidSharingTitle = ''}) async { - Map params = {}; - params['buo'] = buo.toMap(); - params['lp'] = linkProperties.toMap(); - params['messageText'] = messageText; - params['messageTitle'] = androidMessageTitle; - params['sharingTitle'] = androidSharingTitle; - Map response = - await messageChannel.invokeMethod('showShareSheet', params); + await messageChannel.invokeMethod('showShareSheet', { + 'buo': buo.toMap(), + 'lp': linkProperties.toMap(), + 'messageText': messageText, + 'messageTitle': androidMessageTitle, + 'sharingTitle': androidSharingTitle + }); if (response['success']) { return BranchResponse.success(result: response['url']); @@ -317,4 +316,18 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { errorMessage: response['errorMessage']); } } + + @override + void shareWithLPLinkMetadata( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required Image icon, + required String title}) async { + Map params = {}; + params['buo'] = buo.toMap(); + params['lp'] = linkProperties.toMap(); + params['title'] = title; + + messageChannel.invokeMethod('shareWithLPLinkMetadata', params); + } } diff --git a/lib/src/flutter_branch_sdk_platform_interface.dart b/lib/src/flutter_branch_sdk_platform_interface.dart index fc93bd4c..82e10e59 100644 --- a/lib/src/flutter_branch_sdk_platform_interface.dart +++ b/lib/src/flutter_branch_sdk_platform_interface.dart @@ -1,3 +1,4 @@ +import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'flutter_branch_sdk_method_channel.dart'; @@ -207,4 +208,12 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { required BranchQrCode qrCodeSettings}) async { throw UnimplementedError('getQRCodeAsImage has not been implemented'); } + + void shareWithLPLinkMetadata( + {required BranchUniversalObject buo, + required BranchLinkProperties linkProperties, + required Image icon, + required String title}) { + throw UnimplementedError('getQRCodeAsImage has not been implemented'); + } } From 0c7e2f72277a5e464b0f122f9e44680dedb4bf56 Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Fri, 24 Jun 2022 08:38:23 -0300 Subject: [PATCH 016/125] Refactory web method toMapWeb to toMap --- example/lib/main.dart | 12 +- ...flutter_branch_sdk_platform_interface.dart | 3 +- lib/src/flutter_branch_sdk_web.dart | 22 ++-- lib/src/objects/branch_event.dart | 60 +++++----- lib/src/objects/branch_qrcode.dart | 71 ++++++------ lib/src/objects/branch_universal_object.dart | 103 +++++++++--------- lib/src/objects/link_properties.dart | 16 --- 7 files changed, 130 insertions(+), 157 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index efcfa958..419f98b6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -459,11 +459,13 @@ class _HomePageState extends State { duration: 5); } - FlutterBranchSdk.shareWithLPLinkMetadata( - buo: buo!, - linkProperties: lp, - title: "Teste shareWithLPLinkMetadata", - icon: Image.network(imageURL)); + if (Platform.isIOS) { + FlutterBranchSdk.shareWithLPLinkMetadata( + buo: buo!, + linkProperties: lp, + title: "ShareWithLPLinkMetadata test", + icon: Image.network(imageURL)); + } } @override diff --git a/lib/src/flutter_branch_sdk_platform_interface.dart b/lib/src/flutter_branch_sdk_platform_interface.dart index 82e10e59..268969c9 100644 --- a/lib/src/flutter_branch_sdk_platform_interface.dart +++ b/lib/src/flutter_branch_sdk_platform_interface.dart @@ -214,6 +214,7 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { required BranchLinkProperties linkProperties, required Image icon, required String title}) { - throw UnimplementedError('getQRCodeAsImage has not been implemented'); + throw UnimplementedError( + 'shareWithLPLinkMetadata has not been implemented'); } } diff --git a/lib/src/flutter_branch_sdk_web.dart b/lib/src/flutter_branch_sdk_web.dart index c539a104..ea0a3a06 100644 --- a/lib/src/flutter_branch_sdk_web.dart +++ b/lib/src/flutter_branch_sdk_web.dart @@ -147,15 +147,12 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { Future getShortUrl( {required BranchUniversalObject buo, required BranchLinkProperties linkProperties}) async { - Map data = buo.toMapWeb(); + Map data = buo.toMap(); linkProperties.getControlParams().forEach((key, value) { data[key] = value; }); - Map linkData = { - ...linkProperties.toMapWeb(), - 'data': data - }; + Map linkData = {...linkProperties.toMap(), 'data': data}; Completer responseCompleter = Completer(); @@ -208,12 +205,12 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { JsArray contentItems = JsArray(); for (var element in buo) { - contentItems.add(_dartObjectToJsObject(element.toMapWeb())); + contentItems.add(_dartObjectToJsObject(element.toMap())); } try { BranchJS.logEvent(branchEvent.eventName, - _dartObjectToJsObject(branchEvent.toMapWeb()), contentItems); + _dartObjectToJsObject(branchEvent.toMap()), contentItems); } catch (e) { debugPrint('trackContent() error: $e'); } @@ -224,7 +221,7 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { void trackContentWithoutBuo({required BranchEvent branchEvent}) { try { BranchJS.logEvent( - branchEvent.eventName, _dartObjectToJsObject(branchEvent.toMapWeb())); + branchEvent.eventName, _dartObjectToJsObject(branchEvent.toMap())); } catch (e) { debugPrint('trackContentWithoutBuo() error: $e'); } @@ -385,19 +382,16 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { required BranchQrCode qrCodeSettings}) async { Completer responseCompleter = Completer(); - Map data = buo.toMapWeb(); + Map data = buo.toMap(); linkProperties.getControlParams().forEach((key, value) { data[key] = value; }); - Map linkData = { - ...linkProperties.toMapWeb(), - 'data': data - }; + Map linkData = {...linkProperties.toMap(), 'data': data}; try { BranchJS.qrCode(_dartObjectToJsObject(linkData), - _dartObjectToJsObject(qrCodeSettings.toMapWeb()), + _dartObjectToJsObject(qrCodeSettings.toMap()), allowInterop((err, qrCode) { if (err == null) { if (qrCode != null) { diff --git a/lib/src/objects/branch_event.dart b/lib/src/objects/branch_event.dart index 4caffa00..f9031307 100644 --- a/lib/src/objects/branch_event.dart +++ b/lib/src/objects/branch_event.dart @@ -75,33 +75,9 @@ class BranchEvent { Map toMap() { Map ret = {}; - ret["eventName"] = _eventName; - ret["isStandardEvent"] = _isStandardEvent; - if (transactionID.isNotEmpty) { - ret["transactionID"] = transactionID; - } - if (currency != null) { - ret["currency"] = getCurrencyTypeString(currency!); - } - if (revenue != -1) ret["revenue"] = revenue; - if (shipping != -1) ret["shipping"] = shipping; - if (tax != -1) ret["tax"] = tax; - if (coupon.isNotEmpty) ret["coupon"] = coupon; - if (affiliation.isNotEmpty) ret["affiliation"] = affiliation; - if (eventDescription.isNotEmpty) { - ret["eventDescription"] = eventDescription; - } - if (searchQuery.isNotEmpty) ret["searchQuery"] = searchQuery; - if (adType != null) { - ret["adType"] = getBranchEventAdTypeString(adType!); - } - if (_customData.isNotEmpty) ret["customData"] = _customData; - return ret; - } - - Map toMapWeb() { - Map ret = {}; - if (_isStandardEvent) { + if (!kIsWeb) { + ret["eventName"] = _eventName; + ret["isStandardEvent"] = _isStandardEvent; if (transactionID.isNotEmpty) { ret["transactionID"] = transactionID; } @@ -122,10 +98,34 @@ class BranchEvent { if (adType != null) { ret["adType"] = getBranchEventAdTypeString(adType!); } + if (_customData.isNotEmpty) ret["customData"] = _customData; + } else { + if (_isStandardEvent) { + if (transactionID.isNotEmpty) { + ret["transactionID"] = transactionID; + } + if (currency != null) { + ret["currency"] = getCurrencyTypeString(currency!); + } + if (revenue != -1) ret["revenue"] = revenue; + if (shipping != -1) ret["shipping"] = shipping; + if (tax != -1) ret["tax"] = tax; + if (coupon.isNotEmpty) ret["coupon"] = coupon; + if (affiliation.isNotEmpty) ret["affiliation"] = affiliation; + if (eventDescription.isNotEmpty) { + ret["eventDescription"] = eventDescription; + } + if (searchQuery.isNotEmpty) { + ret["searchQuery"] = searchQuery; + } + if (adType != null) { + ret["adType"] = getBranchEventAdTypeString(adType!); + } + } + _customData.forEach((key, value) { + ret[key] = value; + }); } - _customData.forEach((key, value) { - ret[key] = value; - }); return ret; } } diff --git a/lib/src/objects/branch_qrcode.dart b/lib/src/objects/branch_qrcode.dart index 4fdb2cc0..511bc187 100644 --- a/lib/src/objects/branch_qrcode.dart +++ b/lib/src/objects/branch_qrcode.dart @@ -36,43 +36,40 @@ class BranchQrCode { Map toMap() { Map ret = {}; - if (primaryColor != null) { - ret["codeColor"] = _colorToHex(primaryColor!); - } - if (backgroundColor != null) { - ret["backgroundColor"] = _colorToHex(backgroundColor!); - } - if (margin != null) { - ret["margin"] = margin; - } - if (width != null) { - ret["width"] = width; - } - ret["imageFormat"] = imageFormat.name.toUpperCase(); - if (centerLogoUrl.isNotEmpty) { - ret["centerLogoUrl"] = centerLogoUrl; - } - return ret; - } - - Map toMapWeb() { - Map ret = {}; - - if (primaryColor != null) { - ret["code_color"] = _colorToHex(primaryColor!); - } - if (backgroundColor != null) { - ret["background_color"] = _colorToHex(backgroundColor!); - } - if (margin != null) { - ret["margin"] = margin; - } - if (width != null) { - ret["width"] = width; - } - ret["image_format"] = imageFormat.name.toLowerCase(); - if (centerLogoUrl.isNotEmpty) { - ret["center_logo_url"] = centerLogoUrl; + if (!kIsWeb) { + if (primaryColor != null) { + ret["codeColor"] = _colorToHex(primaryColor!); + } + if (backgroundColor != null) { + ret["backgroundColor"] = _colorToHex(backgroundColor!); + } + if (margin != null) { + ret["margin"] = margin; + } + if (width != null) { + ret["width"] = width; + } + ret["imageFormat"] = imageFormat.name.toUpperCase(); + if (centerLogoUrl.isNotEmpty) { + ret["centerLogoUrl"] = centerLogoUrl; + } + } else { + if (primaryColor != null) { + ret["code_color"] = _colorToHex(primaryColor!); + } + if (backgroundColor != null) { + ret["background_color"] = _colorToHex(backgroundColor!); + } + if (margin != null) { + ret["margin"] = margin; + } + if (width != null) { + ret["width"] = width; + } + ret["image_format"] = imageFormat.name.toLowerCase(); + if (centerLogoUrl.isNotEmpty) { + ret["center_logo_url"] = centerLogoUrl; + } } return ret; } diff --git a/lib/src/objects/branch_universal_object.dart b/lib/src/objects/branch_universal_object.dart index 31916703..a63beed4 100644 --- a/lib/src/objects/branch_universal_object.dart +++ b/lib/src/objects/branch_universal_object.dart @@ -1,5 +1,6 @@ library flutter_branch_sdk_objects; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; part 'branch_event.dart'; @@ -81,79 +82,73 @@ class BranchUniversalObject { Map toMap() { Map ret = {}; - if (canonicalIdentifier.isNotEmpty) { - ret["canonicalIdentifier"] = canonicalIdentifier; - } + if (!kIsWeb) { + if (canonicalIdentifier.isNotEmpty) { + ret["canonicalIdentifier"] = canonicalIdentifier; + } - if (canonicalUrl.isNotEmpty) ret["canonicalUrl"] = canonicalUrl; + if (canonicalUrl.isNotEmpty) ret["canonicalUrl"] = canonicalUrl; - if (title.isNotEmpty) ret["title"] = title; + if (title.isNotEmpty) ret["title"] = title; - if (contentDescription.isNotEmpty) { - ret["contentDescription"] = contentDescription; - } + if (contentDescription.isNotEmpty) { + ret["contentDescription"] = contentDescription; + } - if (imageUrl.isNotEmpty) ret["imageUrl"] = imageUrl; + if (imageUrl.isNotEmpty) ret["imageUrl"] = imageUrl; - if (keywords.isNotEmpty) ret["keywords"] = keywords; + if (keywords.isNotEmpty) ret["keywords"] = keywords; - ret["creationDate"] = _creationDateTimeStamp; + ret["creationDate"] = _creationDateTimeStamp; - if (expirationDateInMilliSec > 0) { - ret["expirationDate"] = expirationDateInMilliSec; - } + if (expirationDateInMilliSec > 0) { + ret["expirationDate"] = expirationDateInMilliSec; + } - ret["locallyIndex"] = locallyIndex; - ret["publiclyIndex"] = publiclyIndex; - - if (contentMetadata != null && contentMetadata!.toMap().isNotEmpty) { - ret["contentMetadata"] = contentMetadata!.toMap(); - } + ret["locallyIndex"] = locallyIndex; + ret["publiclyIndex"] = publiclyIndex; - if (ret.isEmpty) { - throw ArgumentError('Branch Universal Object is required'); - } - return ret; - } - - Map toMapWeb() { - Map ret = {}; - if (canonicalIdentifier.isNotEmpty) { - ret["\$canonical_identifier"] = canonicalIdentifier; - } + if (contentMetadata != null && contentMetadata!.toMap().isNotEmpty) { + ret["contentMetadata"] = contentMetadata!.toMap(); + } + } else { + if (canonicalIdentifier.isNotEmpty) { + ret["\$canonical_identifier"] = canonicalIdentifier; + } - if (canonicalUrl.isNotEmpty) ret["\$canonicalUrl"] = canonicalUrl; + if (canonicalUrl.isNotEmpty) ret["\$canonicalUrl"] = canonicalUrl; - if (title.isNotEmpty) ret["\$og_title"] = title; + if (title.isNotEmpty) ret["\$og_title"] = title; - if (contentDescription.isNotEmpty) { - ret["\$og_description"] = contentDescription; - } + if (contentDescription.isNotEmpty) { + ret["\$og_description"] = contentDescription; + } - if (imageUrl.isNotEmpty) ret["\$og_image_url"] = imageUrl; + if (imageUrl.isNotEmpty) ret["\$og_image_url"] = imageUrl; - if (keywords.isNotEmpty) ret["\$keywords"] = keywords; + if (keywords.isNotEmpty) ret["\$keywords"] = keywords; - ret["\$creation_timestamp"] = _creationDateTimeStamp; + ret["\$creation_timestamp"] = _creationDateTimeStamp; - if (expirationDateInMilliSec > 0) { - ret["\$exp_date"] = expirationDateInMilliSec; - } + if (expirationDateInMilliSec > 0) { + ret["\$exp_date"] = expirationDateInMilliSec; + } - ret["\$locally_indexable"] = locallyIndex; - ret["\$publicly_indexable"] = publiclyIndex; + ret["\$locally_indexable"] = locallyIndex; + ret["\$publicly_indexable"] = publiclyIndex; - Map contentMetadata = { - if (this.contentMetadata != null) ...this.contentMetadata!.toMapWeb() - }; + Map contentMetadata = { + if (this.contentMetadata != null) ...this.contentMetadata!.toMapWeb() + }; - if (contentMetadata.containsKey('customMetadata')) { - var customMetadata = contentMetadata['customMetadata']; - contentMetadata.remove('customMetadata'); - contentMetadata.addAll(customMetadata); - ret.addAll(contentMetadata); - } else { - ret.addAll(contentMetadata); + if (contentMetadata.containsKey('customMetadata')) { + var customMetadata = contentMetadata['customMetadata']; + contentMetadata.remove('customMetadata'); + contentMetadata.addAll(customMetadata); + ret.addAll(contentMetadata); + } else { + ret.addAll(contentMetadata); + } } if (ret.isEmpty) { diff --git a/lib/src/objects/link_properties.dart b/lib/src/objects/link_properties.dart index 717db1f9..b84faf05 100644 --- a/lib/src/objects/link_properties.dart +++ b/lib/src/objects/link_properties.dart @@ -57,20 +57,4 @@ class BranchLinkProperties { } return ret; } - - Map toMapWeb() { - Map ret = {}; - - if (tags.isNotEmpty) ret['tags'] = tags; - if (feature.isNotEmpty) ret['feature'] = feature; - if (alias.isNotEmpty) ret['alias'] = alias; - if (stage.isNotEmpty) ret['stage'] = stage; - if (matchDuration > 0) ret['matchDuration'] = matchDuration; - if (channel.isNotEmpty) ret['channel'] = channel; - if (campaign.isNotEmpty) ret['campaign'] = campaign; - if (ret.isEmpty) { - throw ArgumentError('Link Properties is required'); - } - return ret; - } } From f59bc771bf5acd5e93b7a1ab0705f20319defb6c Mon Sep 17 00:00:00 2001 From: Rodrigo de Souza Marques Date: Fri, 24 Jun 2022 20:40:49 -0300 Subject: [PATCH 017/125] Adjust Title Sample App --- example/lib/main.dart | 2 +- example/web/index.html | 4 ++-- example/web/manifest.json | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 419f98b6..10357589 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -474,7 +474,7 @@ class _HomePageState extends State { key: scaffoldMessengerKey, child: Scaffold( appBar: AppBar( - title: const Text('Branch.io Plugin Example App'), + title: const Text('Flutter Branch SDK Example'), ), body: Scrollbar( thumbVisibility: true, diff --git a/example/web/index.html b/example/web/index.html index c41563fc..f5b3622b 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -23,13 +23,13 @@ - + - flutter_branch_sdk_example + Flutter Branch SDK Example