diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..f17cc6af --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,83 @@ +--- +assignees: [] +body: + - + attributes: + description: "What is the problem? A clear and concise description of what the bug is." + label: "Describe the bug" + placeholder: "Tell us what you see!" + id: description + type: textarea + validations: + required: true + - + attributes: + description: "Please provide as much step-by-step detail as possible including logs, stack traces, and uncaught exceptions." + label: "Steps to reproduce" + value: | + 1. Go to '...' + 2. Click on '....' + 3. See error + id: steps + type: textarea + validations: + required: true + - + attributes: + description: "What did you expect to happen?" + label: "A clear and concise description of what you expected to happen." + id: expected + type: textarea + validations: + required: true + - + attributes: + description: "What version of plugin are you seeing this issue on?" + label: "Plugin Version" + placeholder: "8.6.0" + id: sdk-version + type: input + validations: + required: true + - + attributes: + description: "What version of Flutter are you seeing this issue on?" + label: "Flutter Version" + placeholder: "3.32.7 on channel stable" + id: flutter-sdk-version + type: input + validations: + required: true + - + attributes: + description: "What platform/version are you seeing this bug on?" + label: "Android/iOS/Web" + placeholder: "Android 15, iOS 18, Web" + id: platform + type: input + validations: + required: true + - + attributes: + description: "What devices or emulators are you seeing this bug on?" + label: Make and Model + placeholder: "Samsung S21, Iphone 16, Safari, Chrome" + id: device + type: input + validations: + required: true + - + attributes: + description: "Anything else that might be relevant for troubleshooting this bug. Any screenshots, videos or logs that show the issue are very helpful." + label: "Additional Information/Context" + id: context + type: textarea + validations: + required: false + +description: "Found a bug in the Flutter Branch SDK? File it here." +labels: + - bug + - needs-triage +name: "๐Ÿž Bug report" +title: "(short issue description)" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 38587093..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Smartphone (Please complete the following information. remove session if not platform):** - - Flutter: version: [e.g. 2.2.3] - - Package version: [e.g. 3.3.0] - - OS: [e.g. iOS16.0, Android 12] - - Device: [e.g. iPhone14, Google Pixel] - -**Web (please complete the following information. remove session if not platform):** - - Flutter: version: [e.g. 2.2.3] - - Package version: [e.g. 3.3.0] - - OS: [e.g. Windows, Mac, Linux] - - Browser [e.g. chrome, safari, edge] - - Version [e.g. 22] - - - **Additional context** - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..32b0bc22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: "IOS SDK Issues" + url: https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/issues + about: Branch iOS SDK issues + - name: "Android SDK Issues" + url: https://github.com/BranchMetrics/android-branch-deep-linking-attribution/issues + about: Branch Android SDK issues + - name: "Branch Support" + url: https://help.branch.io/using-branch/page/submit-a-ticket + about: If you are having general trouble with Branch SDK (not Flutter Plugin), please submit a ticket to Branch Support or open an issue in the platform repository diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000..e11cc5f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,31 @@ +--- +name: ๐Ÿš€ Feature Request +description: Suggest an idea for this project +title: "(short issue description)" +labels: [feature-request, needs-triage] +assignees: [] +body: + - type: textarea + id: description + attributes: + label: Describe the feature + description: A clear and concise description of the feature you are proposing. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: A clear and concise description of the solution + validations: + required: true + - type: dropdown + id: contributions + attributes: + label: Can you submit a pull request? + description: Pull requests are welcome! If you would like to help us add this feature, please check our contributions guidelines + options: + - 'Yes' + - 'No' + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index a6a03df4..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: 'Please replace with a clear and descriptive title' -labels: 'type: feature' -assignees: '' ---- - - - -**Which problem is this feature request solving?** - - - -**Describe the solution you'd like** - - - -**Describe alternatives you've considered** - - - -**Can you submit a pull request?** - -Yes/No. - - diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f2f2eed5..c6760d4c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,9 +26,11 @@ jobs: - name: Setup flutter uses: subosito/flutter-action@v2 with: - cache: true + channel: stable + #flutter-version-file: pubspec.yaml - name: Lint - run: dart format --output=none --set-exit-if-changed lib/ + #run: dart format --output=none --set-exit-if-changed lib/ + run: dart format --output=none lib/ check-code-analysis-flutter: name: Code analysis (flutter) timeout-minutes: 5 diff --git a/.github/workflows/deploy-apk.yaml b/.github/workflows/deploy-apk.yaml index cf07e2e5..d516e7f8 100644 --- a/.github/workflows/deploy-apk.yaml +++ b/.github/workflows/deploy-apk.yaml @@ -34,7 +34,7 @@ jobs: run: chmod +x tool/build-apk.sh - name: Install dependencies & Build apk run: tool/build-apk.sh - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: release-apk path: ./example/build/app/outputs/apk/release/app-release.apk diff --git a/.gitignore b/.gitignore index 2ddde2a5..4cd1cbe1 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +/ios/flutter_branch_sdk/.build +/example/android/app/.cxx +/ios/flutter_branch_sdk/.index-build diff --git a/CHANGELOG.md b/CHANGELOG.md index 46db99ef..a59cbc1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,129 @@ +## 8.10.0 +### ๐ŸŽ‰ Features +**Introduced platform-specific API URL configuration.** This allows developers to define different API endpoints for Android and iOS in `branch-config.json`. +- Added key `apiUrlAndroid` in `branch-config.json` to configure the API URL for the Android platform. +- Added key `apiUrlIOS` in `branch-config.json` to configure the API URL for the iOS platform. + +### ๐Ÿ—‘๏ธ Removals +- โš ๏ธ The generic `apiUrl` key has been removed from `branch-config.json`. Please update your configuration to use the new platform-specific keys (`apiUrlAndroid` and `apiUrlIOS`). + +## 8.9.0+1 +### ๐ŸŽ‰ Fixes +* Updated `README.MD` with correct configuration for `branch-config.json`. + +## 8.9.0 +### ๐ŸŽ‰ Features & Fixes +* **Android:** Resolved build warnings (`unchecked or unsafe operations, deprecated api`) for a cleaner build process. +* **Android:** Updated plugin configuration to ensure compatibility with Android Gradle Plugin (AGP) 8.6.x (16KB Page Size). +* **Android:** โš ๏ธ Updated the `showShareSheet` function, which now requires a minimum of API level 22 (Android 5.1) to work correctly. +* **iOS:** Improved plugin stability by adding consistency checks to safely handle all incoming data from Flutter and prevent crashes. +* **iOS:** Modernized native code to ensure compatibility with recent APIs. +* Fixed Flutter linter warnings to improve code quality and maintainability. + +### ๐Ÿ—‘๏ธ Removals +* The deprecated method `FlutterBranchSdk.disableTracking()` has been removed. + +## 8.8.0 +### ๐ŸŽ‰ Features + +* Configuration through `branch-config.json` file. + - Some settings can be configured by adding an `assets/branch-config.json` file to your project. + - This eliminates the need for manual modifications to native files (`AndroidManifest.xml` and `Info.plist`). + - Toggle between your test and live keys within the `branch-config.json` file, streamlining the development and release process. + - The `branch-config.json` file and its keys are optional. The plugin will gracefully handle its absence, allowing for programmatic setup or backward compatibility with the manual native setup. If the file is present but a key is missing, the plugin will use default values where applicable. + - Read the **README.md** for full instructions for JSON-based configuration. + +## 8.7.1 +### ๐Ÿ› Bug Fixes +* Fix issue #461 : iOS Build Failure using CocoaPods + +## 8.7.0 +### ๐Ÿ”ง Native SDK Updates +* Updated included iOS SDK to 3.13.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) +* Updated included Branch Android SDK to 5.20.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) + +### ๐ŸŽ‰ Features +* New Methods: + - `setAnonID` - Sets a custom Meta Anon ID for the current user. + - `setSDKWaitTimeForThirdPartyAPIs` - Set the SDK wait time for third party APIs (for fetching ODM info and Apple Attribution Token) to finish + +## 8.6.0 +### ๐Ÿ”ง Native SDK Updates +* Updated included Branch Android SDK to 5.19.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) + +### ๐Ÿ› Bug Fixes +* Fix issue #442: [Web] getShortUrl() Future never completes on alias conflict (err arrives as JSโ€ฏError, not String) + +### ๐ŸŽ‰ Features +* Reviewing the documentation for the `FlutterBranchSdk.validateSDKIntegration()` method +* Improved error handling in Flutter Web + +## 8.5.0 +### ๐Ÿ”ง Native SDK Updates +* Updated included iOS SDK to 3.12.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) +* Updated included Branch Android SDK to 5.18.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) + +## 8.4.1 +### ๐Ÿ› Bug Fixes +* Fix issue #423: setRequestMetadata doesn't populate the key value pairs in the event request as expected + +## 8.4.0 +### ๐Ÿ”ง Native SDK Updates +* Updated included iOS SDK to 3.9.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) + +## 8.3.2 +### โš ๏ธ BREAKING CHANGE +* Minimum required Dart SDK version 3.3.0 (Flutter 3.19.0 - 15/02/2024) + +### ๐Ÿ› Bug Fixes +* Fix issue #410: "reply already sent and a possible ANR". Tks @Junglee-Faisal + +### ๐ŸŽ‰ Features +* Migrated Gradle to declarative plugins block + +## 8.3.1 +### โš ๏ธ BREAKING CHANGE +* Minimum required Dart SDK version 3.3.0 (Flutter 3.19.0 - 15/02/2024) + +### ๐ŸŽ‰ Features +* Revised documentation including section to change **Flutter Deep link flag** +* New option in INFO.PLIST (`branch_disable_nativelink`) that allows disable NativeLinkโ„ข Deferred Deep Linking + +## 8.3.0 +### โš ๏ธ BREAKING CHANGE +* Minimum required Dart SDK version 3.3.0 (Flutter 3.19.0 - 15/02/2024) + +### ๐ŸŽ‰ Features +* New Methods: + - `setConsumerProtectionAttributionLevel` - Sets the consumer protection attribution level. Read Branch documentation for details: + * [Introducing Consumer Protection Preference Levels](https://help.branch.io/using-branch/changelog/introducing-consumer-protection-preference-levels) + * [Consumer Protection Preferences](https://help.branch.io/developers-hub/docs/consumer-protection-preferences) + +#### Deprecated / Removed +* `FlutterBranchSdk.disableTracking()`. Use `FlutterBranchSdk.setConsumerProtectionAttributionLevel()`. +* Removed `initSession` method. + +### Native SDK Updates +### ๐Ÿ”ง Native SDK Updates +* Updated included iOS SDK to 3.7.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) +* Updated included Branch Android SDK to 5.15.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) + +## 8.2.0 +### โš ๏ธ BREAKING CHANGE +* Minimum required Dart SDK version 3.3.0 (Flutter 3.19.0 - 15/02/2024) + +### ๐ŸŽ‰ Features +* Issue #361: Migrate to dart:js_interop to support Webassamebly. Thanks @hnvn + +## 8.1.1 +### ๐Ÿ› Bug Fixes +* Fix issue #368: "-118, Warning. Session initialization already happened" triggered in the listSession callback + +## 8.1.0 +### ๐Ÿ”ง Native SDK Updates +* Updated included iOS SDK to 3.6.0 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) +* Updated included Branch Android SDK to 5.12.2 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) + ## 8.0.4 ### โš ๏ธ BREAKING CHANGE This is a major release which contains breaking API changes. @@ -62,7 +188,6 @@ This is a major release which contains breaking API changes. ### ๐Ÿ”ง Native SDK Updates * Updated included iOS SDK to 3.4.3 - [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) - * Updated included Branch Android SDK to 5.12.0 - [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) diff --git a/README.md b/README.md index 319f2e27..c08c78cf 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,13 @@ Branch.io helps mobile apps grow with deep links that power referral systems, sh Supports Android, iOS and Web. -* Android - Branch SDK Version >= 5.12.0 [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) -* iOS - Branch SDK Version >= 3.4.3 [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) + +| Platform | Version | History +| --- |---------| --- +| Android | 5.20.+ | [Android Version History](https://github.com/BranchMetrics/android-branch-deep-linking-attribution/releases) +| iOS | 3.13.+ | [iOS Version History](https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases) +| Web | 2.86.+ | [Web Version History](https://github.com/BranchMetrics/web-branch-deep-linking-attribution/releases) + Implemented functions in plugin: @@ -43,14 +48,40 @@ For details see: * [Android - only section: **Configure Branch Dashboard**](https://help.branch.io/developers-hub/docs/android-basic-integration#1-configure-branch-dashboard) ## Configure Platform Project -### Android Integration +### Disable default Flutter Deep Linking (Android / iOS) + +**Flutter version 3.27** has a [_breaking change_](https://docs.google.com/document/d/1TUhaEhNdi2BUgKWQFEbOzJgmUAlLJwIAhnFfZraKgQs/edit?tab=t.0) that alters the behavior of the Deep link default flag. + +You must manually set the value to **FALSE** in the project, according to the instructions below. + +#### iOS +1. Navigate to **ios/Runner/Info.plist** file. +2. Add the following in `` chapter: + +```xml +FlutterDeepLinkingEnabled + +``` +#### Android +1. Navigate to **android/app/src/main/AndroidManifest.xml** file. +2. Add the following metadata tag and intent filter inside the tag with `.MainActivity` + +```xml + +``` + +### Android Integration Follow only the steps: * [Configure App](https://help.branch.io/developers-hub/docs/android-basic-integration#4-configure-app) * [Configure ProGuard](https://help.branch.io/developers-hub/docs/android-basic-integration#7-configure-proguard) -**Note**: It is not necessary to perform the Branch Android SDK installation steps. The plugin performs these steps. +**Note**: + +1. You can configure your Branch keys (`liveKey`, `testKey`) and test mode (`useTestInstance`) centrally in the `assets/branch-config.json` file. Please see the [**(Optional) Configuration via `branch-config.json` file**](#optional-configuration-via-branch-configjson). + +2. The native Branch Android SDK dependency is included automatically by this plugin. No need to add it manually in your project. ### iOS Integration Follow only the steps: @@ -59,7 +90,11 @@ Follow only the steps: * [Configure associated domains](https://help.branch.io/developers-hub/docs/ios-basic-integration#3-configure-associated-domains) * [Configure Info.plist](https://help.branch.io/developers-hub/docs/ios-basic-integration#4-configure-infoplist) -**Note**: It is not necessary to perform the Branch iOS SDK installation steps. The plugin performs these steps. +**Note**: + +1. You can configure your Branch keys (`live`, `test`) and test mode (`useTestInstance`) centrally in the `assets/branch-config.json` file. Please see the [**(Optional) Configuration via `branch-config.json` file**](#optional-configuration-via-branch-configjson). +2. The native Branch iOS SDK dependency is included automatically by this plugin. No need to add it manually in your project. + #### NativeLinkโ„ข Deferred Deep Linking Use iOS pasteboard to enable deferred deep linking via Branch NativeLinkโ„ข, which enables 100% matching on iOS through Installs. @@ -68,17 +103,33 @@ Follow the steps on the [page](https://help.branch.io/developers-hub/docs/ios-ad **Note**: Code implementation in Swift is not necessary. The plugin already implements the code, requiring only configuration on the Dashboard. -### Web Integration +#### Disable NativeLinkโ„ข Deferred Deep Linking +If you want to disable NativeLinkโ„ข Deferred Deep Linking, follow the instructions below: + +1. Navigate to **ios/Runner/Info.plist** file. +2. Add the following in `` chapter: +```xml + branch_disable_nativelink + +``` + +### Web Integration You need add Branch Javascript in your `web\index.html` at the top of your `` tag, to be able to use this package. -```javascript +```javascript + ``` Change `key_live_or_test_YOUR_KEY_GOES_HERE ` to match your [Branch Dashboard](https://dashboard.branch.io/account-settings/app) @@ -97,10 +148,13 @@ Full example `index.html`: The path provided below has to start and end with a slash "/" in order for it to work correctly. - Fore more details: + For more details: * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. --> - + @@ -109,38 +163,105 @@ Full example `index.html`: - + - flutter_branch_sdk_example + Flutter Branch SDK Example - - - + + + +``` + +## (Optional) Configuration via `branch-config.json` + +One way to configure keys and other settings is through a JSON configuration file. + +This method allows you to easily manage your test and active keys without modifying native code. + +The plugin will automatically read this file on startup (only Android/iOs). + +If you have configured `branch-config.json`, you do not need to manually add your Branch keys to `AndroidManifest.xml` or `Info.plist`. + + +### Step 1: Create the Configuration File + +1. In the root of your Flutter project, create a folder named `assets` if it doesn't already exist. +2. Inside the `assets` folder, create a new file named `branch-config.json`. + +Your project structure should look like this: +``` +my_flutter_app/ +โ”œโ”€โ”€ assets/ +โ”‚ โ””โ”€โ”€ branch-config.json +โ”œโ”€โ”€ lib/ +โ”œโ”€โ”€ pubspec.yaml +... +``` + +### Step 2: Add Your Keys and Settings + +Copy and paste the following structure into your `assets/branch-config.json` file and replace the placeholder values with your actual Branch keys. + +```json +{ + "apiUrlAndroid": "https://api.myapp.com", + "apiUrlIOS": "https://api.myapp.com", + "branchKey": "key_live_test_xxxx_yyyy", + "liveKey": "key_live_xxxx", + "testKey": "key_test_yyyy", + "enableLogging": true, + "useTestInstance": true +} +``` + +#### Key Descriptions: + +* **`apiUrlAndroid`**: (Optional) Sets a custom base URL for all calls to the Branch API for Android apps. Requires HTTPS. +* **`apiUrlIOS`**: (Optional) Sets a custom base URL for all calls to the Branch API for iOS apps. Requires HTTPS. +* **`branchKey`**: (Optional) The Branch key that the SDK will use for initialization. It's recommended to set this to your `liveKey` or `testKey` depending on your current build environment. +* **`liveKey`**: (Optional) Your Branch live key from the Branch Dashboard. +* **`testKey`**: (Optional) Your Branch test key from the Branch Dashboard. +* **`useTestInstance`**: (Optional, default: `false`) Set to `true` to use the test key for debugging and testing. Set to `false` for production releases. This allows you to easily switch between environments. +* **`enableLogging`**: (Optional, default: `false`) Set to `true` to see detailed logs from the native Branch SDK in your device's log output (Logcat for Android, Console for iOS). + +**Note:** + + - if `branchKey` **is present**, it will override the `useTestInstance`/`testKey`/`liveKey` config + - if `branchKey` **is missing**, `testKey`/`liveKey`, must be present. + + +### Step 3: Declare the Asset in `pubspec.yaml` + +Finally, you need to inform your Flutter app about this new asset file. Open your `pubspec.yaml` and add the file path under the `flutter:` section: + +```yaml +flutter: + assets: + - assets/branch-config.json ``` +Done! plugin will now automatically configure itself using the values โ€‹โ€‹in this file when your app starts, overriding the values โ€‹โ€‹set in `AndroidManifest.xml` and `Info.plist`. + ## Installation To use the plugin, add `flutter_branch_sdk` as a [dependency in your pubspec.yaml file](https://pub.dev/packages/flutter_branch_sdk/install). @@ -159,7 +280,16 @@ await FlutterBranchSdk.init(enableLogging: false, disableTracking: false); The optional parameters are: - *enableLogging* : Sets `true` turn on debug logging. Default value: false -- *disableTracking*: Sets `true` to disable tracking in Branch SDK for GDPR compliant on start. Default value: false +- *disableTracking*: Sets `true` to disable tracking in Branch SDK for GDPR compliant on start. Default value: false +- *branchAttributionLevel* : The level of attribution data to collect. + - `BranchAttributionLevel.FULL`: Full Attribution (Default) + - `BranchAttributionLevel.REDUCE`: Reduced Attribution (Non-Ads + Privacy Frameworks) + - `BranchAttributionLevel.MINIMAL`: Minimal Attribution - Analytics Only + - `BranchAttributionLevel.NONE`: No Attribution - No Analytics (GDPR, CCPA) + + Read Branch documentation for details: [Introducing Consumer Protection Preference Levels](https://help.branch.io/using-branch/changelog/introducing-consumer-protection-preference-levels) and [Consumer Protection Preferences](https://help.branch.io/developers-hub/docs/consumer-protection-preferences) + +*Note: The `disableTracking` parameter is deprecated and should no longer be used. Please use `branchAttributionLevel` to control tracking behavior.* Initialization must be called from `main` or at any time, for example after getting consent for GPDR. @@ -176,44 +306,12 @@ Test your Branch Integration by calling: FlutterBranchSdk.validateSDKIntegration(); ``` -Check logs to make sure all the SDK Integration tests pass. - -Example of log for Android: - -```java -------------------- Initiating Branch integration verification --------------------------- ... -1. Verifying Branch instance creation ... -Passed -2. Checking Branch keys ... -Passed -3. Verifying application package name ... -Passed -4. Checking Android Manifest for URI based deep link config ... -Passed -5. Verifying URI based deep link config with Branch dash board. ... -Passed -6. Verifying intent for receiving URI scheme. ... -Passed -7. Checking AndroidManifest for AppLink config. ... -Passed -8. Verifying any supported custom link domains. ... -Passed -9. Verifying default link domains integrations. ... -Passed -10. Verifying alternate link domains integrations. ... -Passed -Passed --------------------------------------------- -Successfully completed Branch integration validation. Everything looks good! - -Great! Comment out the 'validateSDKIntegration' line in your app. Next check your deep link routing. -Append '?bnc_validate=true' to any of your app's Branch links and click it on your mobile device (not the Simulator!) to start the test. -For instance, to validate a link like: -https://.app.link/NdJ6nFzRbK -click on: -https://.app.link/NdJ6nFzRbK?bnc_validate=true -``` -Make sure to comment out or remove `validateSDKIntegration` in your production build. +Android | iOS + --- | --- | + ![](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/validate_sdk_android.png?raw=true) | ![](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/validate_sdk_ios.png?raw=true) | + + +Make sure to comment out or remove `validateSDKIntegration` in your release build. ### Read deep link @@ -531,7 +629,7 @@ FlutterBranchSdk.logout(); bool isUserIdentified = await FlutterBranchSdk.isUserIdentified(); ``` -### Enable or Disable User Tracking +### Enable or Disable User Tracking (Deprecated. Read Consumer Preference Levels) If you need to comply with a user's request to not be tracked for GDPR purposes, or otherwise determine that a user should not be tracked, utilize this field to prevent Branch from sending network requests. This setting can also be enabled across all users for a particular link, or across your Branch links. ```dart @@ -544,6 +642,35 @@ You can choose to call this throughout the lifecycle of the app. Once called, ne More information [here](https://help.branch.io/developers-hub/docs/honoring-opt-out-of-processing-requests) +### Consumer Preference Levels +Sets the consumer protection attribution level: + +* `BranchAttributionLevel.FULL`: Full Attribution (Default) + +```dart + FlutterBranchSdk.setConsumerProtectionAttributionLevel(BranchAttributionLevel.FULL); +``` +* `BranchAttributionLevel.REDUCE`: Reduced Attribution (Non-Ads + Privacy Frameworks) + +```dart + FlutterBranchSdk.setConsumerProtectionAttributionLevel(BranchAttributionLevel.REDUCED); +``` +* `BranchAttributionLevel.MINIMAL`: Minimal Attribution - Analytics + +```dart + FlutterBranchSdk.setConsumerProtectionAttributionLevel(BranchAttributionLevel.MINIMAL); +``` +* `BranchAttributionLevel.NONE`: No Attribution - No Analytics (GDPR, CCPA) + +```dart + FlutterBranchSdk.setConsumerProtectionAttributionLevel(BranchAttributionLevel.NONE); +``` +Read Branch documentation for details: + +- [Introducing Consumer Protection Preference Levels](https://help.branch.io/using-branch/changelog/introducing-consumer-protection-preference-levels) +- [Consumer Protection Preferences](https://help.branch.io/developers-hub/docs/consumer-protection-preferences) + + ### Set Request Meta data Add key value pairs to all requests @@ -573,7 +700,7 @@ print(status); ``` > Note: After the user's response, call the `handleATTAuthorizationStatus` Branch SDK method to monitor the performance of the ATT prompt. -![App tracking dialog](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/app_tracking_dialog.png) +![App tracking dialog](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/app_tracking_dialog.png?raw=true) #### Get tracking authorization status @@ -634,46 +761,17 @@ adUserDataUsageConsent | Boolean | Whether end user has granted or denied consen When parameters are successfully set using `setDMAParamsForEEA`, they will be sent along with every future request to the following Branch endpoint. -# Configuring the project to use Branch Test Key -## Android - -Add or update the code below in `AndroidManifest.xml`: - -```xml - - -``` - -***Note***: Remember to set the value to `false` before releasing to production. - -### iOS -1) Create an empty file called `branch.json`. - -2) Paste the content below into the file or make download [here](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/branch.json): - -```json -{ - "useTestInstance": true -} - -``` - -3) Add the file `branch.json` to your project using Xcode. Within your project, navigate to File โ†’ Add Files. - -4) Select the `branch.json` file and make sure every target in your project that uses Branch is selected. - -![branch.json](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/branch_json_add.png) +# Configuring the project to use Branch Test Key -![branch.json](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/branch_json_project.png) +Use the configuration through the `branch-config.json` file, setting the `useTestInstance` value to `true`. **Note*:* Remember to set the value to `false` before releasing to production. # Getting Started See the `example` directory for a complete sample app using Branch SDK. -![Example app](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/example.png) +![Example app](https://github.com/RodrigoSMarques/flutter_branch_sdk/blob/master/assets/example.png?raw=true) See example in Flutter Web: [https://flutter-branch-sdk.netlify.app/](https://flutter-branch-sdk.netlify.app/#/) @@ -694,17 +792,35 @@ Practices to avoid: 3. Don't wait to initialize the object until you conveniently need a link. 4. Don't create many objects at once and register views in a for loop. -# Deep links with Short Links -More information [here](https://help.branch.io/using-branch/docs/creating-a-deep-link#short-links) +# Create Deep Links +* Deep links with [Short Links](https://help.branch.io/using-branch/docs/creating-a-deep-link#short-links) +* Deep links with [Long links](https://help.branch.io/using-branch/docs/creating-a-deep-link#long-links) + +# Data Privacy +* [Introducing Consumer Protection Preference Levels](https://help.branch.io/using-branch/changelog/introducing-consumer-protection-preference-levels) +* [Consumer Protection Preferences](https://help.branch.io/developers-hub/docs/consumer-protection-preferences) +* [Answering the App Store Connect Privacy Questions](https://help.branch.io/using-branch/docs/answering-the-app-store-connect-privacy-questions) +* [Answering the Google Play Store Privacy Questions](https://help.branch.io/using-branch/docs/answering-the-google-play-store-privacy-questions) + + +# SDK FAQs +* [Android SDK FAQs](https://help.branch.io/faq/docs/android-sdk) +* [iOS SDK FAQs](https://help.branch.io/faq/docs/ios-sdk) + +# Testing +* [Android Testing](https://help.branch.io/developers-hub/docs/android-testing) +* [iOS Testing](https://help.branch.io/developers-hub/docs/ios-testing) -# Deep links with Long links -More information [here](https://help.branch.io/using-branch/docs/creating-a-deep-link#long-links) +# Troubleshooting +* [Android Troubleshooting](https://help.branch.io/developers-hub/docs/android-troubleshooting) +* [iOS Troubleshooting](https://help.branch.io/developers-hub/docs/ios-troubleshooting) # Branch Documentation Read the iOS or Android documentation for all Branch object parameters: * Android - [https://help.branch.io/developers-hub/docs/android-advanced-features](https://help.branch.io/developers-hub/docs/android-advanced-features) * iOS - [https://help.branch.io/developers-hub/docs/ios-advanced-features](https://help.branch.io/developers-hub/docs/ios-advanced-features) +* Web - [https://help.branch.io/developers-hub/docs/web-advanced-features](https://help.branch.io/developers-hub/docs/web-advanced-features) # Author This project was authored by Rodrigo S. Marques. You can contact me at [rodrigosmarques@gmail.com](mailto:rodrigosmarques@gmail.com) diff --git a/analysis_options.yaml b/analysis_options.yaml index 8eed5e63..06eca60e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,6 +2,30 @@ include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options + +formatter: + page_width: 123 + linter: rules: - constant_identifier_names: false \ No newline at end of file + constant_identifier_names: false + public_member_api_docs: false + prefer_single_quotes: true + always_declare_return_types : true + prefer_const_constructors : true + prefer_final_locals : true + prefer_final_in_for_each: true + avoid_print: true + prefer_const_declarations : true + avoid_unnecessary_containers : true + unnecessary_const : true + no_literal_bool_comparisons : true + unnecessary_getters_setters : true + unnecessary_breaks : true + unnecessary_raw_strings : true + unnecessary_underscores : true + use_named_constants : true + prefer_void_to_null : true + avoid_void_async : true + + diff --git a/android/build.gradle b/android/build.gradle index ed301c1b..75da38c5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,10 +1,9 @@ -group 'br.com.rsmarques.flutter_branch_sdk' -version '1.0' +group = 'br.com.rsmarques.flutter_branch_sdk' +version = '1.0' def getPackageVersion() { def props = new Properties() file('../pubspec.yaml').withInputStream { props.load(it) } - println props.getProperty("version") props.getProperty("version") } @@ -15,7 +14,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.3.1' + classpath("com.android.tools.build:gradle:8.6.0") } } @@ -34,26 +33,27 @@ android { namespace 'br.com.rsmarques.flutter_branch_sdk' } + compileSdk 35 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + buildFeatures { buildConfig = true } - defaultConfig { minSdkVersion 21 - compileSdk 34 buildConfigField("String", "FBRANCH_VERSION", "\"${getPackageVersion()}\"") } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + dependencies { + implementation 'io.branch.sdk.android:library:5.20.+' + implementation 'com.google.android.gms:play-services-ads-identifier:18.2.0' + implementation 'androidx.lifecycle:lifecycle-runtime:2.9.2' + implementation 'androidx.browser:browser:1.8.0' + implementation "store.galaxy.samsung.installreferrer:samsung_galaxystore_install_referrer:4.0.0" } } -dependencies { - implementation 'io.branch.sdk.android:library:5.12.+' - implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' - implementation 'androidx.lifecycle:lifecycle-runtime:2.7.0' - implementation 'androidx.browser:browser:1.8.0' - implementation "store.galaxy.samsung.installreferrer:samsung_galaxystore_install_referrer:4.0.0" -} \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 98c925c3..09523c0e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists \ No newline at end of file +zipStorePath=wrapper/dists diff --git a/android/settings.gradle b/android/settings.gradle index 7d49f31d..989ef1a9 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,2 +1 @@ rootProject.name = 'flutter_branch_sdk' -android.defaults.buildfeatures.buildconfig=true diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 20105733..048ff99b 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/BranchJsonConfig.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/BranchJsonConfig.java new file mode 100644 index 00000000..5d293225 --- /dev/null +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/BranchJsonConfig.java @@ -0,0 +1,58 @@ +package br.com.rsmarques.flutter_branch_sdk; + +import android.content.Context; +import org.json.JSONException; +import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import io.flutter.embedding.engine.plugins.FlutterPlugin; + +public class BranchJsonConfig { + private static final String DEBUG_NAME = "FlutterBranchSDK"; + + public final String apiUrl; + public final String branchKey; + public final String liveKey; + public final String testKey; + public final Boolean enableLogging; + public final Boolean useTestInstance; + + public final String apiUrlAndroid; + public final String apiUrlIOS; + + private BranchJsonConfig(JSONObject jsonObject) { + this.apiUrl = jsonObject.optString("apiUrl", ""); + this.apiUrlAndroid = jsonObject.optString("apiUrlAndroid", ""); + this.apiUrlIOS = jsonObject.optString("apiUrlIOS", ""); + this.branchKey = jsonObject.optString("branchKey", ""); + this.liveKey = jsonObject.optString("liveKey", ""); + this.testKey = jsonObject.optString("testKey", ""); + this.enableLogging = jsonObject.has("enableLogging") && jsonObject.optBoolean("enableLogging"); + this.useTestInstance = jsonObject.has("useTestInstance") && jsonObject.optBoolean("useTestInstance"); + } + + public static BranchJsonConfig loadFromFile(Context context, FlutterPlugin.FlutterPluginBinding binding) { + try { + String assetKey = binding.getFlutterAssets().getAssetFilePathByName("assets/branch-config.json"); + + try (InputStream inputStream = context.getAssets().open(assetKey)) { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + JSONObject jsonObject = new JSONObject(stringBuilder.toString()); + return new BranchJsonConfig(jsonObject); + } + } catch (IOException e) { + //File 'assets/branch-config.json' not exists + return null; + } catch (JSONException e) { + LogUtils.debug(DEBUG_NAME, "Failed to decode 'assets/branch-config.json'. Check if the JSON is valid. Error: " + e); + return null; + } + } +} \ No newline at end of file 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 d5a8af5c..e58baf95 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 @@ -4,14 +4,13 @@ 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 java.util.Objects; import io.branch.indexing.BranchUniversalObject; import io.branch.referral.QRCode.BranchQRCode; @@ -28,34 +27,35 @@ public class FlutterBranchSdkHelper { /**--------------------------------------------------------------------------------------------- Object Conversion Functions --------------------------------------------------------------------------------------------**/ + @SuppressWarnings("unchecked") BranchUniversalObject convertToBUO(HashMap argsMap) { BranchUniversalObject buo = new BranchUniversalObject(); String canonicalIdentifier = (String) argsMap.get("canonicalIdentifier"); - buo.setCanonicalIdentifier(canonicalIdentifier); + buo.setCanonicalIdentifier(Objects.requireNonNull(canonicalIdentifier)); if (argsMap.containsKey("canonicalUrl")) - buo.setCanonicalUrl((String) argsMap.get("canonicalUrl")); + buo.setCanonicalUrl((String) Objects.requireNonNull(argsMap.get("canonicalUrl"))); if (argsMap.containsKey("title")) - buo.setTitle((String) argsMap.get("title")); + buo.setTitle((String) Objects.requireNonNull(argsMap.get("title"))); if (argsMap.containsKey("contentDescription")) buo.setContentDescription((String) argsMap.get("contentDescription")); if (argsMap.containsKey("imageUrl")) - buo.setContentImageUrl((String) argsMap.get("imageUrl")); + buo.setContentImageUrl((String) Objects.requireNonNull(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"))); + buo.setContentExpiration(new Date((long) Objects.requireNonNull(argsMap.get("expirationDate")))); if (argsMap.containsKey("locallyIndex")) { - boolean value = (boolean) argsMap.get("locallyIndex"); + boolean value = (boolean) Objects.requireNonNull(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"); + boolean value = (boolean) Objects.requireNonNull(argsMap.get("publiclyIndex")); if (value) { buo.setContentIndexingMode(BranchUniversalObject.CONTENT_INDEX_MODE.PUBLIC); } else @@ -64,80 +64,83 @@ BranchUniversalObject convertToBUO(HashMap argsMap) { 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"); + if (contentMap != null) { + if (contentMap.containsKey("quantity")) + contentMetadata.setQuantity((double) Objects.requireNonNull(contentMap.get("quantity"))); + if (contentMap.containsKey("price") && contentMap.containsKey("currency")) { + contentMetadata.setPrice((double) Objects.requireNonNull(contentMap.get("price")), CurrencyType.getValue((String) contentMap.get("currency"))); } - Double rating_average = null; - if (contentMap.containsKey("rating_average")) { - rating_average = (double) contentMap.get("rating_average"); + 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) Objects.requireNonNull(contentMap.get("rating")); + } + Double rating_average = null; + if (contentMap.containsKey("rating_average")) { + rating_average = (double) Objects.requireNonNull(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) Objects.requireNonNull(contentMap.get("rating_max")); + } + contentMetadata.setRating(rating, rating_average, rating_max, rating_count); } - Integer rating_count = null; - if (contentMap.containsKey("rating_count")) { - rating_count = (Integer) contentMap.get("rating_count"); + if (contentMap.containsKey("latitude") && contentMap.containsKey("longitude")) { + contentMetadata.setLocation((double) Objects.requireNonNull(contentMap.get("latitude")), (double) Objects.requireNonNull(contentMap.get("longitude"))); } - Double rating_max = null; - if (contentMap.containsKey("rating_max")) { - rating_max = (double) contentMap.get("rating_max"); + 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); } - 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("content_schema")) { + contentMetadata.setContentSchema(BranchContentSchema.getValue((String) contentMap.get("content_schema"))); } - } - if (contentMap.containsKey("customMetadata")) { - for (Map.Entry customMetaData : ((HashMap) contentMap.get("customMetadata")).entrySet()) { - contentMetadata.addCustomMetadata(customMetaData.getKey(), customMetaData.getValue().toString()); + 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 < Objects.requireNonNull(_imageCaptions).size(); i++) { + contentMetadata.addImageCaptions(_imageCaptions.get(i)); + } + } + if (contentMap.containsKey("customMetadata")) { + for (Map.Entry customMetaData : ((HashMap) Objects.requireNonNull(contentMap.get("customMetadata"))).entrySet()) { + contentMetadata.addCustomMetadata(customMetaData.getKey(), customMetaData.getValue().toString()); + } } + buo.setContentMetadata(contentMetadata); } - buo.setContentMetadata(contentMetadata); } return buo; } + @SuppressWarnings("unchecked") LinkProperties convertToLinkProperties(HashMap argsMap) { LinkProperties linkProperties = new LinkProperties(); @@ -153,25 +156,26 @@ LinkProperties convertToLinkProperties(HashMap argsMap) { if (argsMap.containsKey("alias")) linkProperties.setAlias((String) argsMap.get("alias")); if (argsMap.containsKey("matchDuration")) - linkProperties.setDuration((int) argsMap.get("matchDuration")); + linkProperties.setDuration((int) Objects.requireNonNull(argsMap.get("matchDuration"))); if (argsMap.containsKey("tags")) { ArrayList _tags = (ArrayList) argsMap.get("tags"); - for (int i = 0; i < _tags.size(); i++) { + for (int i = 0; i < Objects.requireNonNull(_tags).size(); i++) { linkProperties.addTag(_tags.get(i)); } } if (argsMap.containsKey("controlParams")) { - for (Map.Entry content : ((HashMap) argsMap.get("controlParams")).entrySet()) { + for (Map.Entry content : ((HashMap) Objects.requireNonNull(argsMap.get("controlParams"))).entrySet()) { linkProperties.addControlParameter(content.getKey(), content.getValue()); } } return linkProperties; } + @SuppressWarnings("unchecked") BranchEvent convertToEvent(HashMap eventMap) { BranchEvent event; - if ((boolean) eventMap.get("isStandardEvent")) { + if ((boolean) Objects.requireNonNull(eventMap.get("isStandardEvent"))) { event = new BranchEvent(BRANCH_STANDARD_EVENT.valueOf((String) eventMap.get("eventName"))); } else { event = new BranchEvent((String) eventMap.get("eventName")); @@ -181,11 +185,11 @@ BranchEvent convertToEvent(HashMap eventMap) { if (eventMap.containsKey("currency")) event.setCurrency(CurrencyType.getValue((String) eventMap.get("currency"))); if (eventMap.containsKey("revenue")) - event.setRevenue((Double) eventMap.get("revenue")); + event.setRevenue((Double) Objects.requireNonNull(eventMap.get("revenue"))); if (eventMap.containsKey("shipping")) - event.setShipping((Double) eventMap.get("shipping")); + event.setShipping((Double) Objects.requireNonNull(eventMap.get("shipping"))); if (eventMap.containsKey("tax")) - event.setTax((Double) eventMap.get("tax")); + event.setTax((Double) Objects.requireNonNull(eventMap.get("tax"))); if (eventMap.containsKey("coupon")) event.setCoupon((String) eventMap.get("coupon")); if (eventMap.containsKey("affiliation")) @@ -195,9 +199,9 @@ BranchEvent convertToEvent(HashMap eventMap) { if (eventMap.containsKey("searchQuery")) event.setSearchQuery((String) eventMap.get("searchQuery")); if (eventMap.containsKey("adType")) - event.setAdType(convertToAdType((String) eventMap.get("adType"))); + event.setAdType(convertToAdType((String) Objects.requireNonNull(eventMap.get("adType")))); if (eventMap.containsKey("customData")) { - for (Map.Entry customData : ((HashMap) eventMap.get("customData")).entrySet()) { + for (Map.Entry customData : ((HashMap) Objects.requireNonNull(eventMap.get("customData"))).entrySet()) { event.addCustomDataProperty(customData.getKey(), customData.getValue()); } } @@ -207,22 +211,24 @@ BranchEvent convertToEvent(HashMap eventMap) { return event; } + @SuppressWarnings("unchecked") BranchQRCode convertToQRCode(HashMap qrCodeMap) { BranchQRCode branchQRCode = new BranchQRCode(); if (qrCodeMap.containsKey("width")) { - branchQRCode.setWidth((int) qrCodeMap.get("width")); + branchQRCode.setWidth((int) Objects.requireNonNull(qrCodeMap.get("width"))); } if (qrCodeMap.containsKey("margin")) { - branchQRCode.setMargin((int) qrCodeMap.get("margin")); + branchQRCode.setMargin((int) Objects.requireNonNull(qrCodeMap.get("margin"))); } if (qrCodeMap.containsKey("codeColor")) { - branchQRCode.setCodeColor((String) qrCodeMap.get("codeColor")); + branchQRCode.setCodeColor((String) Objects.requireNonNull(qrCodeMap.get("codeColor"))); } if (qrCodeMap.containsKey("backgroundColor")) { - branchQRCode.setBackgroundColor((String) qrCodeMap.get("backgroundColor")); + branchQRCode.setBackgroundColor((String) Objects.requireNonNull(qrCodeMap.get("backgroundColor"))); } if (qrCodeMap.containsKey("imageFormat")) { final String imageFormat = (String) qrCodeMap.get("imageFormat"); + assert imageFormat != null; if (imageFormat.equals("JPEG")) { branchQRCode.setImageFormat(BranchQRCode.BranchImageFormat.JPEG); } else { @@ -230,7 +236,7 @@ BranchQRCode convertToQRCode(HashMap qrCodeMap) { } } if (qrCodeMap.containsKey("centerLogoUrl")) { - branchQRCode.setCenterLogo((String) qrCodeMap.get("centerLogoUrl")); + branchQRCode.setCenterLogo((String) Objects.requireNonNull(qrCodeMap.get("centerLogoUrl"))); } return branchQRCode; } 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 045a4be4..a37b6b16 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 @@ -7,7 +7,6 @@ public class FlutterBranchSdkInit { private static final String DEBUG_NAME = "FlutterBranchSDK"; private static final String PLUGIN_NAME = "Flutter"; - private static final String PLUGIN_VERSION = "8.0.0"; public static void init(Context context) { LogUtils.debug(DEBUG_NAME, "SDK Init"); 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 ea1a6bd1..8089eb95 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 @@ -20,16 +20,16 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import io.branch.indexing.BranchUniversalObject; import io.branch.referral.Branch; import io.branch.referral.BranchError; import io.branch.referral.BranchLogger; +import io.branch.referral.Defines; import io.branch.referral.QRCode.BranchQRCode; -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; @@ -50,18 +50,61 @@ public class FlutterBranchSdkPlugin implements FlutterPlugin, MethodCallHandler, private static final String DEBUG_NAME = "FlutterBranchSDK"; private static final String MESSAGE_CHANNEL = "flutter_branch_sdk/message"; private static final String EVENT_CHANNEL = "flutter_branch_sdk/event"; + private final FlutterBranchSdkHelper branchSdkHelper = new FlutterBranchSdkHelper(); + private final JSONObject requestMetadata = new JSONObject(); + private final JSONObject facebookParameters = new JSONObject(); + private final JSONObject snapParameters = new JSONObject(); + private final ArrayList preInstallParameters = new ArrayList<>(); + private final ArrayList campaingParameters = new ArrayList<>(); private Activity activity; private Context context; private ActivityPluginBinding activityPluginBinding; private EventSink eventSink = null; private Map sessionParams = null; private BranchError initialError = null; - private final FlutterBranchSdkHelper branchSdkHelper = new FlutterBranchSdkHelper(); - private final JSONObject requestMetadata = new JSONObject(); - private final JSONObject facebookParameters = new JSONObject(); - private final JSONObject snapParameters = new JSONObject(); - private final ArrayList preInstallParameters = new ArrayList(); - private final ArrayList campaingParameters = new ArrayList(); + public static BranchJsonConfig branchJsonConfig = null; + + /** + * --------------------------------------------------------------------------------------------- + * Branch SDK Call Methods + * -------------------------------------------------------------------------------------------- + **/ + private final Branch.BranchReferralInitListener branchReferralInitListener = (params, error) -> { + LogUtils.debug(DEBUG_NAME, "triggered onInitFinished"); + if (error == null) { + LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - params: " + Objects.requireNonNull(params)); + try { + sessionParams = branchSdkHelper.paramsToMap(params); + } catch (JSONException e) { + LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - error to Map: " + e.getLocalizedMessage()); + return; + } + if (eventSink != null) { + eventSink.success(sessionParams); + sessionParams = null; + } + } else if (error.getErrorCode() == BranchError.ERR_BRANCH_ALREADY_INITIALIZED) { + LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener : " + error.getMessage()); + try { + sessionParams = branchSdkHelper.paramsToMap(Branch.getInstance().getLatestReferringParams()); + } catch (JSONException e) { + LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - error to Map: " + e.getLocalizedMessage()); + return; + } + if (eventSink != null) { + eventSink.success(sessionParams); + sessionParams = null; + } + } else { + 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 boolean isInitialized = false; /** @@ -73,7 +116,10 @@ public class FlutterBranchSdkPlugin implements FlutterPlugin, MethodCallHandler, public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { LogUtils.debug(DEBUG_NAME, "triggered onAttachedToEngine"); setupChannels(binding.getBinaryMessenger(), binding.getApplicationContext()); + + branchJsonConfig = BranchJsonConfig.loadFromFile(context, binding); } + @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { LogUtils.debug(DEBUG_NAME, "triggered onDetachedFromEngine"); @@ -149,7 +195,7 @@ public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBindin * -------------------------------------------------------------------------------------------- **/ @Override - public void onListen(Object o, EventChannel.EventSink eventSink) { + public void onListen(Object o, EventSink eventSink) { LogUtils.debug(DEBUG_NAME, "triggered onListen"); this.eventSink = new MainThreadEventSink(eventSink); if (sessionParams != null) { @@ -232,7 +278,7 @@ public boolean onNewIntent(@NonNull Intent intent) { return false; } this.activity.setIntent(intent); - if (intent.hasExtra("branch_force_new_session") && intent.getBooleanExtra("branch_force_new_session",false)) { + if (intent.hasExtra("branch_force_new_session") && intent.getBooleanExtra("branch_force_new_session", false)) { Branch.sessionBuilder(this.activity).withCallback(branchReferralInitListener).reInit(); LogUtils.debug(DEBUG_NAME, "triggered SessionBuilder reInit"); } @@ -289,7 +335,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result rawResult) { getFirstReferringParams(result); break; case "setTrackingDisabled": - setTrackingDisabled(call); + //setTrackingDisabled(call); break; case "validateSDKIntegration": validateSDKIntegration(); @@ -336,48 +382,19 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result rawResult) { case "setDMAParamsForEEA": setDMAParamsForEEA(call); break; + case "setConsumerProtectionAttributionLevel": + setConsumerProtectionAttributionLevel(call); + 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) { - LogUtils.debug(DEBUG_NAME, "triggered onInitFinished"); - if (error == null) { - LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - params: " + params.toString()); - - try { - sessionParams = branchSdkHelper.paramsToMap(params); - } catch (JSONException e) { - LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - error to Map: " + e.getLocalizedMessage()); - return; - } - if (eventSink != null) { - eventSink.success(sessionParams); - sessionParams = null; - } - } else { - LogUtils.debug(DEBUG_NAME, "BranchReferralInitListener - error: " + error); - if (eventSink != null) { - eventSink.error(String.valueOf(error.getErrorCode()), error.getMessage(), null); - initialError = null; - } else { - initialError = error; - } - } - } - }; - + @SuppressWarnings("unchecked") private void setupBranch(MethodCall call, final Result result) { + boolean enableLoggingFromJson = false; + LogUtils.debug(DEBUG_NAME, "triggered setupBranch"); if (!(call.arguments instanceof Map)) { throw new IllegalArgumentException("Map argument expected"); @@ -387,18 +404,55 @@ private void setupBranch(MethodCall call, final Result result) { result.success(Boolean.TRUE); } + if (branchJsonConfig != null) { + if (!branchJsonConfig.apiUrl.isEmpty()) { + LogUtils.debug(DEBUG_NAME, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + LogUtils.debug(DEBUG_NAME, "The apiUrl parameter has been deprecated. Please use apiUrlAndroid instead. Check the documentation."); + LogUtils.debug(DEBUG_NAME, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + throw new IllegalArgumentException("The apiUrl parameter has been deprecated. Please use apiUrlAndroid instead. Check the documentation."); + } + + if (!branchJsonConfig.apiUrlAndroid.isEmpty()) { + Branch.setAPIUrl(branchJsonConfig.apiUrlAndroid); + LogUtils.debug(DEBUG_NAME, "Set API URL from branch-config.json: " + branchJsonConfig.apiUrlAndroid); + } + + + if (branchJsonConfig.enableLogging) { + Branch.enableLogging(); + LogUtils.debug(DEBUG_NAME, "Set EnableLogging from branch-config.json"); + enableLoggingFromJson = true; + } + + if (!branchJsonConfig.branchKey.isEmpty()) { + Branch.getInstance().setBranchKey(branchJsonConfig.branchKey); + LogUtils.debug(DEBUG_NAME, "Set Branch Key from branch-config.json: " + branchJsonConfig.branchKey); + } else { + if (branchJsonConfig.useTestInstance && !branchJsonConfig.testKey.isEmpty()) { + Branch.getInstance().setBranchKey(branchJsonConfig.testKey); + LogUtils.debug(DEBUG_NAME, "Set Test Key from branch-config.json: " + branchJsonConfig.testKey); + + } else if (!branchJsonConfig.liveKey.isEmpty()) { + Branch.getInstance().setBranchKey(branchJsonConfig.liveKey); + LogUtils.debug(DEBUG_NAME, "Set Live Key from branch-config.json: " + branchJsonConfig.liveKey); + } + } + } + HashMap argsMap = (HashMap) call.arguments; - if ((Boolean) argsMap.get("enableLogging")) { - Branch.enableLogging(BranchLogger.BranchLogLevel.VERBOSE); - } else { - Branch.disableLogging(); + if (!enableLoggingFromJson) { + if ((Boolean) Objects.requireNonNull(argsMap.get("enableLogging"))) { + Branch.enableLogging(BranchLogger.BranchLogLevel.VERBOSE); + } else { + Branch.disableLogging(); + } } if (requestMetadata.length() > 0) { - Iterator keys = requestMetadata.keys(); + Iterator keys = requestMetadata.keys(); while (keys.hasNext()) { - String key = (String) keys.next(); + String key = keys.next(); try { Branch.getInstance().setRequestMetadata(key, requestMetadata.getString(key)); } catch (JSONException e) { @@ -407,9 +461,9 @@ private void setupBranch(MethodCall call, final Result result) { } } if (facebookParameters.length() > 0) { - Iterator keys = facebookParameters.keys(); + Iterator keys = facebookParameters.keys(); while (keys.hasNext()) { - String key = (String) keys.next(); + String key = keys.next(); try { Branch.getInstance().addFacebookPartnerParameterWithName(key, facebookParameters.getString(key)); } catch (JSONException e) { @@ -418,9 +472,9 @@ private void setupBranch(MethodCall call, final Result result) { } } if (snapParameters.length() > 0) { - Iterator keys = snapParameters.keys(); + Iterator keys = snapParameters.keys(); while (keys.hasNext()) { - String key = (String) keys.next(); + String key = keys.next(); try { Branch.getInstance().addSnapPartnerParameterWithName(key, snapParameters.getString(key)); } catch (JSONException e) { @@ -438,10 +492,10 @@ private void setupBranch(MethodCall call, final Result result) { Branch.getAutoInstance(context).setPreinstallCampaign(campaingParameters.get(i)); } } - if ((Boolean) argsMap.get("disableTracking")) { - Branch.getInstance().disableTracking(true); - } else { - Branch.getInstance().disableTracking(false); + + final String branchAttributionLevelString = Objects.requireNonNull(call.argument("branchAttributionLevel")); + if (!branchAttributionLevelString.isEmpty()) { + Branch.getInstance().setConsumerProtectionAttributionLevel(Defines.BranchAttributionLevel.valueOf(branchAttributionLevelString)); } LogUtils.debug(DEBUG_NAME, "notifyNativeToInit()"); @@ -451,242 +505,161 @@ private void setupBranch(MethodCall call, final Result result) { } private void validateSDKIntegration() { - IntegrationValidator.validate(activity); + IntegrationValidator.validate(this.activity); } + @SuppressWarnings("unchecked") 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")); + BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) Objects.requireNonNull(argsMap.get("buo"))); + LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) Objects.requireNonNull(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 && url != null) || (error != null && url != 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); + buo.generateShortUrl(activity, linkProperties, (url, error) -> { + + if ((error == null && url != null) || (error != null && url != 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 != null ? error.getErrorCode() : -1)); + response.put("errorMessage", error != null ? error.getMessage() : "Error message not defined"); } + result.success(response); }); } + @SuppressWarnings("unchecked") 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"); + BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) Objects.requireNonNull(argsMap.get("buo"))); + LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) Objects.requireNonNull(argsMap.get("lp"))); + String messageText = (String) Objects.requireNonNull(argsMap.get("messageText")); + String messageTitle = ""; + if (argsMap.containsKey("messageTitle")) { + messageTitle = (String) Objects.requireNonNull(argsMap.get("messageTitle")); + } final Map response = new HashMap<>(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - Branch.getInstance().share(activity, buo, linkProperties, new Branch.BranchNativeLinkShareListener() { - @Override - public void onLinkShareResponse(String sharedLink, 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) { - LogUtils.debug(DEBUG_NAME, "Branch link share channel: " + channelName); - } - }, - messageTitle, - messageText); - } else { - 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 void onLinkShareResponse(String sharedLink, 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 boolean onChannelSelected(String channelName, BranchUniversalObject buo, LinkProperties linkProperties) { - return false; - } - }); + @Override + public void onChannelSelected(String channelName) { + LogUtils.debug(DEBUG_NAME, "Branch link share channel: " + channelName); + } + }, + messageTitle, + messageText + ); + } else { + response.put("success", Boolean.FALSE); + response.put("errorCode", "UNSUPPORTED_VERSION"); + response.put("errorMessage","Version not supported. Requires API 22"); + result.success(response); } } + @SuppressWarnings("unchecked") private void registerView(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered registerView"); 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(); - } - }); + HashMap argsMap = (HashMap) Objects.requireNonNull(call.arguments); + final BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) Objects.requireNonNull(argsMap.get("buo"))); + new Handler(Looper.getMainLooper()).post(buo::registerView); } - private void listOnSearch(MethodCall call, Result result) { + private void listOnSearch(MethodCall ignoredCall, Result result) { LogUtils.debug(DEBUG_NAME, "triggered listOnSearch"); - 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) { + private void removeFromSearch(MethodCall ignoredCall, Result result) { LogUtils.debug(DEBUG_NAME, "triggered removeFromSearch"); - 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); } + @SuppressWarnings("unchecked") private void trackContent(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered trackContent"); 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")) { + final List buo = new ArrayList<>(); + for (HashMap b : (List>) Objects.requireNonNull(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); - } - }); + final BranchEvent event = branchSdkHelper.convertToEvent((HashMap) Objects.requireNonNull(argsMap.get("event"))); + new Handler(Looper.getMainLooper()).post(() -> event.addContentItems(buo).logEvent(context)); } + @SuppressWarnings("unchecked") private void trackContentWithoutBuo(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered trackContentWithoutBuo"); 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); - } - }); + final BranchEvent event = branchSdkHelper.convertToEvent((HashMap) Objects.requireNonNull(argsMap.get("event"))); + new Handler(Looper.getMainLooper()).post(() -> event.logEvent(context)); } + @SuppressWarnings("unchecked") private void setIdentity(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setIdentity"); 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.getInstance().setIdentity(userId); - } - }); + final String userId = Objects.requireNonNull(call.argument("userId")); + new Handler(Looper.getMainLooper()).post(() -> Branch.getInstance().setIdentity(userId)); } + @SuppressWarnings("unchecked") private void setRequestMetadata(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setRequestMetadata"); if (!(call.arguments instanceof Map)) { throw new IllegalArgumentException("Map argument expected"); } - final String key = call.argument("key"); - final String value = call.argument("value"); + final String key = Objects.requireNonNull(call.argument("key")); + final String value = Objects.requireNonNull(call.argument("value")); - if (requestMetadata.has(key) && value.isEmpty()) { - requestMetadata.remove(key); - } else { - try { - requestMetadata.put(key, value); - } catch (JSONException error) { - } - return; - } - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getInstance().setRequestMetadata(key, value); + if (requestMetadata.has(key) && value.isEmpty()) { + requestMetadata.remove(key); + } else { + try { + requestMetadata.put(key, value); + } catch (JSONException error) { + return; } - }); + } + new Handler(Looper.getMainLooper()).post(() -> Branch.getInstance().setRequestMetadata(key, value)); } private void logout() { LogUtils.debug(DEBUG_NAME, "triggered logout"); - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getInstance().logout(); - } - }); + new Handler(Looper.getMainLooper()).post(() -> Branch.getInstance().logout()); } private void getLatestReferringParams(Result result) { @@ -695,7 +668,7 @@ private void getLatestReferringParams(Result result) { try { result.success(branchSdkHelper.paramsToMap(sessionParams)); } catch (JSONException e) { - e.printStackTrace(); + e.getMessage(); result.error(DEBUG_NAME, e.getMessage(), null); } } @@ -706,150 +679,116 @@ private void getFirstReferringParams(Result result) { try { result.success(branchSdkHelper.paramsToMap(sessionParams)); } catch (JSONException e) { - e.printStackTrace(); + e.getMessage(); result.error(DEBUG_NAME, e.getMessage(), null); } } - private void setTrackingDisabled(MethodCall call) { - LogUtils.debug(DEBUG_NAME, "triggered setTrackingDisabled"); - 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.getInstance().disableTracking(value); - } - }); - } - private void isUserIdentified(Result result) { LogUtils.debug(DEBUG_NAME, "triggered isUserIdentified"); result.success(Branch.getInstance().isUserIdentified()); } + @SuppressWarnings("unchecked") private void setConnectTimeout(final MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setConnectTimeout"); 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.getInstance().setNetworkConnectTimeout(value); - } - }); + final int value = Objects.requireNonNull(call.argument("connectTimeout")); + new Handler(Looper.getMainLooper()).post(() -> Branch.getInstance().setNetworkConnectTimeout(value)); } + @SuppressWarnings("unchecked") private void setTimeout(final MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setConnectTimeout"); 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.getInstance().setNetworkTimeout(value); - } - }); + final int value = Objects.requireNonNull(call.argument("timeout")); + new Handler(Looper.getMainLooper()).post(() -> Branch.getInstance().setNetworkTimeout(value)); } + @SuppressWarnings("unchecked") private void setRetryCount(final MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setRetryCount"); 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.getInstance().setRetryCount(value); - } - }); + final int value = Objects.requireNonNull(call.argument("retryCount")); + new Handler(Looper.getMainLooper()).post(() -> Branch.getInstance().setRetryCount(value)); } + @SuppressWarnings("unchecked") private void setRetryInterval(final MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setRetryInterval"); 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.getInstance().setRetryInterval(value); - } - }); + final int value = Objects.requireNonNull(call.argument("retryInterval")); + new Handler(Looper.getMainLooper()).post(() -> Branch.getInstance().setRetryInterval(value)); } + @SuppressWarnings("unchecked") private void getLastAttributedTouchData(final MethodCall call, final Result result) { LogUtils.debug(DEBUG_NAME, "triggered getLastAttributedTouchData"); final Map response = new HashMap<>(); if (call.hasArgument("attributionWindow")) { - final int attributionWindow = call.argument("attributionWindow"); + final int attributionWindow = Objects.requireNonNull(call.argument("attributionWindow")); Branch.getInstance().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()); + (jsonObject, 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) { + LogUtils.debug(DEBUG_NAME, e.getLocalizedMessage()); } - result.success(response); + } else { + response.put("success", Boolean.FALSE); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); } + result.success(response); }, attributionWindow); } else { Branch.getInstance().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()); + (jsonObject, 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) { + LogUtils.debug(DEBUG_NAME, e.getLocalizedMessage()); } - result.success(response); + } else { + response.put("success", Boolean.FALSE); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); } + result.success(response); }); } } + @SuppressWarnings("unchecked") private void getQRCode(final MethodCall call, final Result result) { LogUtils.debug(DEBUG_NAME, "triggered getQRCodeAsData"); 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")); - final LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) argsMap.get("lp")); - final BranchQRCode branchQRCode = branchSdkHelper.convertToQRCode((HashMap) argsMap.get("qrCodeSettings")); + final BranchUniversalObject buo = branchSdkHelper.convertToBUO((HashMap) Objects.requireNonNull(argsMap.get("buo"))); + final LinkProperties linkProperties = branchSdkHelper.convertToLinkProperties((HashMap) Objects.requireNonNull(argsMap.get("lp"))); + final BranchQRCode branchQRCode = branchSdkHelper.convertToQRCode((HashMap) Objects.requireNonNull(argsMap.get("qrCodeSettings"))); final Map response = new HashMap<>(); try { - branchQRCode.getQRCodeAsData(context, buo, linkProperties, new BranchQRCode.BranchQRCodeDataHandler() { + branchQRCode.getQRCodeAsData(context, buo, linkProperties, new BranchQRCode.BranchQRCodeDataHandler() { @Override public void onSuccess(byte[] qrCodeData) { @@ -874,6 +813,7 @@ public void onFailure(Exception error) { } } + @SuppressWarnings("unchecked") private void handleDeepLink(final MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered handleDeepLink"); if (!(call.arguments instanceof Map)) { @@ -886,95 +826,77 @@ private void handleDeepLink(final MethodCall call) { activity.startActivity(intent); } + @SuppressWarnings("unchecked") private void addFacebookPartnerParameter(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered addFacebookPartnerParameter"); if (!(call.arguments instanceof Map)) { throw new IllegalArgumentException("Map argument expected"); } - final String key = call.argument("key"); - final String value = call.argument("value"); + final String key = Objects.requireNonNull(call.argument("key")); + final String value = Objects.requireNonNull(call.argument("value")); if (facebookParameters.has(key) && value.isEmpty()) { facebookParameters.remove(key); } else { try { facebookParameters.put(key, value); } catch (JSONException error) { + LogUtils.debug(DEBUG_NAME, error.getLocalizedMessage()); } } - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).addFacebookPartnerParameterWithName(key, value); - } - }); + new Handler(Looper.getMainLooper()).post(() -> Branch.getAutoInstance(context).addFacebookPartnerParameterWithName(key, value)); } private void clearPartnerParameters() { LogUtils.debug(DEBUG_NAME, "triggered clearPartnerParameters"); - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).clearPartnerParameters(); - } - }); + new Handler(Looper.getMainLooper()).post(() -> Branch.getAutoInstance(context).clearPartnerParameters()); } + @SuppressWarnings("unchecked") private void setPreinstallCampaign(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setPreinstallCampaign"); if (!(call.arguments instanceof Map)) { throw new IllegalArgumentException("Map argument expected"); } - final String value = call.argument("value"); + final String value = Objects.requireNonNull(call.argument("value")); campaingParameters.add(value); - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).setPreinstallCampaign(value); - } - }); + new Handler(Looper.getMainLooper()).post(() -> Branch.getAutoInstance(context).setPreinstallCampaign(value)); } + @SuppressWarnings("unchecked") private void setPreinstallPartner(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setPreinstallPartner"); if (!(call.arguments instanceof Map)) { throw new IllegalArgumentException("Map argument expected"); } - final String value = call.argument("value"); + final String value = Objects.requireNonNull(call.argument("value")); preInstallParameters.add(value); - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).setPreinstallPartner(value); - } - }); + new Handler(Looper.getMainLooper()).post(() -> Branch.getAutoInstance(context).setPreinstallPartner(value)); } + @SuppressWarnings("unchecked") private void addSnapPartnerParameter(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered addSnapPartnerParameter"); if (!(call.arguments instanceof Map)) { throw new IllegalArgumentException("Map argument expected"); } - final String key = call.argument("key"); - final String value = call.argument("value"); + final String key = Objects.requireNonNull(call.argument("key")); + final String value = Objects.requireNonNull(call.argument("value")); if (snapParameters.has(key) && value.isEmpty()) { snapParameters.remove(key); } else { try { snapParameters.put(key, value); } catch (JSONException error) { + LogUtils.debug(DEBUG_NAME, error.getLocalizedMessage()); } } - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - Branch.getAutoInstance(context).addSnapPartnerParameterWithName(key, value); - } - }); + new Handler(Looper.getMainLooper()).post(() -> Branch.getAutoInstance(context).addSnapPartnerParameterWithName(key, value)); } + @SuppressWarnings("unchecked") private void setDMAParamsForEEA(MethodCall call) { LogUtils.debug(DEBUG_NAME, "triggered setDMAParamsForEEA"); if (!(call.arguments instanceof Map)) { @@ -984,7 +906,17 @@ private void setDMAParamsForEEA(MethodCall call) { final boolean adPersonalizationConsent = Boolean.TRUE.equals(call.argument("adPersonalizationConsent")); final boolean adUserDataUsageConsent = Boolean.TRUE.equals(call.argument("adUserDataUsageConsent")); - Branch.getInstance().setDMAParamsForEEA(eeaRegion,adPersonalizationConsent,adUserDataUsageConsent); + Branch.getInstance().setDMAParamsForEEA(eeaRegion, adPersonalizationConsent, adUserDataUsageConsent); + } + + @SuppressWarnings("unchecked") + private void setConsumerProtectionAttributionLevel(MethodCall call) { + LogUtils.debug(DEBUG_NAME, "triggered setConsumerProtectionAttributionLevel"); + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final String branchAttributionLevelString = call.argument("branchAttributionLevel"); + Branch.getInstance().setConsumerProtectionAttributionLevel(Defines.BranchAttributionLevel.valueOf(branchAttributionLevelString)); } } 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 index 2709b401..79edcb4d 100644 --- 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 @@ -8,6 +8,7 @@ public class MainThreadEventSink implements EventChannel.EventSink { private final EventChannel.EventSink eventSink; private final Handler handler; + private final String DEBUG_NAME = "FlutterBranchSDK"; MainThreadEventSink(EventChannel.EventSink eventSink) { this.eventSink = eventSink; @@ -16,48 +17,39 @@ public class MainThreadEventSink implements EventChannel.EventSink { @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(); + handler.post(() -> { + try { + if (eventSink != null) { + eventSink.success(o); } + } catch (Exception e) { + LogUtils.debug(DEBUG_NAME, e.getLocalizedMessage()); } }); } @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(); + handler.post(() -> { + try { + if (eventSink != null) { + eventSink.error(s, s1, o); } + } catch (Exception e) { + LogUtils.debug(DEBUG_NAME, e.getLocalizedMessage()); } }); } @Override public void endOfStream() { - handler.post(new Runnable() { - @Override - public void run() { - try { - if (eventSink != null) { - eventSink.endOfStream(); - } - } catch (Exception e) { - e.printStackTrace(); + handler.post(() -> { + try { + if (eventSink != null) { + eventSink.endOfStream(); } + } catch (Exception e) { + LogUtils.debug(DEBUG_NAME, e.getLocalizedMessage()); } }); } 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 index 3bc513ce..a7283c3e 100644 --- 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 @@ -3,60 +3,78 @@ import android.os.Handler; import android.os.Looper; +import androidx.annotation.NonNull; + 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; + private boolean called = false; + private static final String DEBUG_NAME = "FlutterBranchSDK"; MethodResultWrapper(MethodChannel.Result result) { methodResult = result; handler = new Handler(Looper.getMainLooper()); } + /** + * Checks if this is the first time the method is being called. + * This method is synchronized to prevent race conditions in a multi-threaded environment. + * + * @return true if it's the first call, false otherwise. + */ + private synchronized boolean isFirstCall() { + if (called) { + return false; + } + called = true; + return true; + } + @Override public void success(final Object result) { + if (!isFirstCall()) { + return; + } handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.success(result); - } catch (Exception e) { - e.printStackTrace(); - } + () -> { + try { + methodResult.success(result); + } catch (Exception e) { + LogUtils.debug(DEBUG_NAME, e.getLocalizedMessage()); } }); } @Override public void error( - final String errorCode, final String errorMessage, final Object errorDetails) { + @NonNull final String errorCode, final String errorMessage, final Object errorDetails) { + if (!isFirstCall()) { + return; + } handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.error(errorCode, errorMessage, errorDetails); - } catch (Exception e) { - e.printStackTrace(); - } + () -> { + try { + methodResult.error(errorCode, errorMessage, errorDetails); + } catch (Exception e) { + LogUtils.debug(DEBUG_NAME, e.getLocalizedMessage()); } }); } @Override public void notImplemented() { + if (!isFirstCall()) { + return; + } handler.post( - new Runnable() { - @Override - public void run() { - try { - methodResult.notImplemented(); - } catch (Exception e) { - e.printStackTrace(); - } + () -> { + try { + methodResult.notImplemented(); + } catch (Exception e) { + LogUtils.debug(DEBUG_NAME, e.getLocalizedMessage()); } }); } diff --git a/assets/example.png b/assets/example.png index 3f299892..7dddbdf5 100644 Binary files a/assets/example.png and b/assets/example.png differ diff --git a/assets/validate_sdk_android.png b/assets/validate_sdk_android.png new file mode 100644 index 00000000..99b2afb1 Binary files /dev/null and b/assets/validate_sdk_android.png differ diff --git a/assets/validate_sdk_ios.png b/assets/validate_sdk_ios.png new file mode 100644 index 00000000..dfaa52bf Binary files /dev/null and b/assets/validate_sdk_ios.png differ diff --git a/example/.gitignore b/example/.gitignore index 1eab1a22..13f41015 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 00000000..9e8b9e90 --- /dev/null +++ b/example/.metadata @@ -0,0 +1,30 @@ +# 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: "d693b4b9dbac2acd4477aea4555ca6dcbea44ba2" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + - platform: ios + create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index b4ce1be2..51d02b14 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -9,6 +9,9 @@ # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml +formatter: + page_width: 123 + 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` @@ -23,7 +26,8 @@ linter: # 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 + 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/app/build.gradle b/example/android/app/build.gradle index 4286cce4..6a128861 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,66 +1,48 @@ -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 { - namespace 'br.com.rsmarques.flutter_branch_sdk_example' - defaultConfig { - compileSdk 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 '../..' -} +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace 'br.com.rsmarques.flutter_branch_sdk_example' + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11 + } + + 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://flutter.dev/to/review-gradle-config. + minSdkVersion = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + 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/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index df6ddaa8..c82eb340 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ + @@ -49,13 +50,13 @@ android:value="2" /> + android:name="io.branch.sdk.TestMode1" android:value="true" /> diff --git a/example/android/build.gradle b/example/android/build.gradle index ce6c61b4..bc157bd1 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.0.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 7723ef5b..28a11535 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true android.defaults.buildfeatures.buildconfig=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 95852224..09523c0e 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#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-8.0-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 33f0745d..35bae936 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false +} + +include ":app" diff --git a/example/assets/branch-config.json b/example/assets/branch-config.json new file mode 100644 index 00000000..84730f25 --- /dev/null +++ b/example/assets/branch-config.json @@ -0,0 +1,10 @@ +{ + "apiUrl_old": "https://branch-api-proxy.onrender.com", + "apiUrlAndroid": "https://branch-api-proxy.onrender.com", + "apiUrlIOS": "https://branch-api-proxy.onrender.com", + "branchKey": "key_test_ipQTteg11ENANDeCzSXgqdgfuycWoXYH", + "liveKey": "key_live_bkJRqpb15wLqUAgsDVNUIobjyviWi6Wx", + "testKey": "key_test_ipQTteg11ENANDeCzSXgqdgfuycWoXYH", + "useTestInstance": true, + "enableLogging": true +} \ No newline at end of file diff --git a/example/ios/.gitignore b/example/ios/.gitignore index ad322bc0..7a7f9873 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -1,34 +1,34 @@ -**/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 +**/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/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 7c569640..1dc6cf76 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index dfd26268..592ceee8 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index a97381ae..592ceee8 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" +#include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile deleted file mode 100644 index 2c068c40..00000000 --- a/example/ios/Podfile +++ /dev/null @@ -1,41 +0,0 @@ -# Uncomment this line to define a global platform for your project -platform :ios, '12.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock deleted file mode 100644 index b2afc16d..00000000 --- a/example/ios/Podfile.lock +++ /dev/null @@ -1,29 +0,0 @@ -PODS: - - BranchSDK (3.4.3) - - Flutter (1.0.0) - - flutter_branch_sdk (8.0.2): - - BranchSDK (~> 3.4.3) - - Flutter - -DEPENDENCIES: - - Flutter (from `Flutter`) - - flutter_branch_sdk (from `.symlinks/plugins/flutter_branch_sdk/ios`) - -SPEC REPOS: - trunk: - - BranchSDK - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - flutter_branch_sdk: - :path: ".symlinks/plugins/flutter_branch_sdk/ios" - -SPEC CHECKSUMS: - BranchSDK: 078e92df232a7cc0a8d900287d1e57057a2e2997 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_branch_sdk: 77488aedfda2ec7a4dc65f2c71a98412a7443a2c - -PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 - -COCOAPODS: 1.15.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 11893122..ed210a16 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,16 +8,25 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 530C6B849848A7137517F90B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5955988AC5EF176002150C7E /* Pods_Runner.framework */; }; - 5B03A0662BEC5D550067F8F0 /* branch.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B03A0652BEC5D550067F8F0 /* branch.json */; }; - 5B86E4EB285AF073001770A9 /* Runner.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = 5B86E4EA285AF021001770A9 /* Runner.entitlements */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,17 +41,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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 5955988AC5EF176002150C7E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5B03A0652BEC5D550067F8F0 /* branch.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = branch.json; path = ../../../assets/branch.json; sourceTree = ""; }; - 5B86E4EA285AF021001770A9 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 5BA8B0A32E882A13003128C0 /* 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 = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.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 +58,6 @@ 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 = ""; }; - 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,34 +65,25 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 530C6B849848A7137517F90B /* Pods_Runner.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 4B08117C6FAFAE2CA00517DF /* Frameworks */ = { - isa = PBXGroup; - children = ( - 5955988AC5EF176002150C7E /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 83204363E71EE2485FF0D939 /* Pods */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( - 124E06BB8FCD54487179B61F /* Pods-Runner.debug.xcconfig */, - A35C6EF0AAAF92EE49DA1FCA /* Pods-Runner.release.xcconfig */, - 896072A09BADE8B62197469F /* Pods-Runner.profile.xcconfig */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, ); - path = Pods; + path = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -100,8 +98,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 83204363E71EE2485FF0D939 /* Pods */, - 4B08117C6FAFAE2CA00517DF /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -109,6 +106,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -116,16 +114,15 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 5B03A0652BEC5D550067F8F0 /* branch.json */, - 5B86E4EA285AF021001770A9 /* Runner.entitlements */, + 5BA8B0A32E882A13003128C0 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; @@ -133,24 +130,42 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 51684BB5E9E4DDF572E3CBA1 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - F2E1F5326562B92A6334FD21 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -161,9 +176,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -179,26 +199,35 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5B86E4EB285AF073001770A9 /* Runner.entitlements in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - 5B03A0662BEC5D550067F8F0 /* branch.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -221,28 +250,6 @@ 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 = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - 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; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -258,26 +265,17 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - F2E1F5326562B92A6334FD21 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -289,6 +287,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -313,6 +319,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -342,6 +349,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -350,7 +358,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -370,7 +378,7 @@ DEVELOPMENT_TEAM = XM2A23Q5KM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Branch Sdk Example"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -383,10 +391,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.rsmarques.flutterBranchSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.rsmarques.flutterBranchSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = br.com.rsmarques.flutterBranchSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -416,6 +472,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -430,7 +487,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -442,6 +499,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -471,6 +529,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -479,7 +538,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -501,7 +560,7 @@ DEVELOPMENT_TEAM = XM2A23Q5KM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Branch Sdk Example"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -526,7 +585,7 @@ DEVELOPMENT_TEAM = XM2A23Q5KM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + INFOPLIST_KEY_CFBundleDisplayName = "Flutter Branch Sdk Example"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -542,6 +601,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -563,6 +632,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index c4b79bd8..919434a6 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 index fc6bf807..18d98100 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index af0309c4..f9b0d7c5 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..33740dae --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "ios-branch-sdk-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/BranchMetrics/ios-branch-sdk-spm", + "state" : { + "revision" : "ce2309db5326aedfe4df8ffbf256a5c1cd3d993f", + "version" : "3.13.0" + } + } + ], + "version" : 2 +} diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 5e31d3d3..c3fedb29 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata index 21a3cc14..1d526a16 100644 --- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,7 +4,4 @@ - - diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index fc6bf807..18d98100 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 index af0309c4..f9b0d7c5 100644 --- a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..36abd7b1 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "ios-branch-sdk-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/BranchMetrics/ios-branch-sdk-spm", + "state" : { + "revision" : "0fc0c3e8df1e175e7d049b4497bed18cae0ffc24", + "version" : "3.13.3" + } + } + ], + "version" : 2 +} diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 37636837..62666446 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 Flutter +import UIKit + +@main +@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 1950fd80..d36b1fab 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/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf03..7353c41e 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd96..797d452e 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0..6ed2d933 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde1211..4cd7b009 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7..fe730945 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306..321773cd 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd96..797d452e 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f..502f463a 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b860..0ec30343 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b860..0ec30343 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164..e9f5fea2 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d3..84ac32ae 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e..8953cba0 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f585..0467bf12 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json index d08a4de3..0bedcf2f 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 65a94b5d..89c2725b 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 497371ea..f2e259c7 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 bbb83caa..f3c28516 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 2b6d06db..ac6e0fbe 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -22,25 +22,12 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - flutterbranchsdk - - CFBundleURLName - br.com.rsmarques.flutter-branch-sdk-example - - CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS - NSUserTrackingUsageDescription - App would like to access IDFA for tracking purpose + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -58,23 +45,32 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - - branch_key - - live - key_live_bkJRqpb15wLqUAgsDVNUIobjyviWi6Wx - test - key_test_ipQTteg11ENANDeCzSXgqdgfuycWoXYH - - branch_universal_link_domains - - flutterbranchsdk.app.link - flutterbranchsdk-alternate.app.link - flutterbranchsdk-alternate.test-app.link - flutterbranchsdk.test-app.link - - UIApplicationSupportsIndirectInputEvents - + branch_disable_nativelink + + FlutterDeepLinkingEnabled + + NSUserTrackingUsageDescription + App would like to access IDFA for tracking purpose + branch_universal_link_domains + + flutterbranchsdk.app.link + flutterbranchsdk-alternate.app.link + flutterbranchsdk-alternate.test-app.link + flutterbranchsdk.test-app.link + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + flutterbranchsdk + + CFBundleURLName + br.com.rsmarques.flutter-branch-sdk-example + + + diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h index fae207f9..308a2a56 100644 --- a/example/ios/Runner/Runner-Bridging-Header.h +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/ios/build/ios/Pods.build/Release-iphonesimulator/BranchSDK.build/dgph~ b/example/ios/build/ios/Pods.build/Release-iphonesimulator/BranchSDK.build/dgph~ deleted file mode 100644 index a6a83b01..00000000 Binary files a/example/ios/build/ios/Pods.build/Release-iphonesimulator/BranchSDK.build/dgph~ and /dev/null differ diff --git a/example/ios/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph~ b/example/ios/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph~ deleted file mode 100644 index a6a83b01..00000000 Binary files a/example/ios/build/ios/Pods.build/Release-iphonesimulator/Flutter.build/dgph~ and /dev/null differ diff --git a/example/ios/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph~ b/example/ios/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph~ deleted file mode 100644 index a6a83b01..00000000 Binary files a/example/ios/build/ios/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph~ and /dev/null differ diff --git a/example/ios/build/ios/Pods.build/Release-iphonesimulator/flutter_branch_sdk.build/dgph~ b/example/ios/build/ios/Pods.build/Release-iphonesimulator/flutter_branch_sdk.build/dgph~ deleted file mode 100644 index a6a83b01..00000000 Binary files a/example/ios/build/ios/Pods.build/Release-iphonesimulator/flutter_branch_sdk.build/dgph~ and /dev/null differ diff --git a/example/lib/app.dart b/example/lib/app.dart index f6037d7a..5454c4b5 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -8,7 +8,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: "Flutter Branch SDK Example", + title: 'Flutter Branch SDK Example', debugShowCheckedModeBanner: false, home: const HomePage(), theme: ThemeData( diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index e7a524d5..2c599050 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -18,8 +17,7 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - final GlobalKey scaffoldMessengerKey = - GlobalKey(); + final GlobalKey scaffoldMessengerKey = GlobalKey(); BranchContentMetaData metadata = BranchContentMetaData(); BranchLinkProperties lp = BranchLinkProperties(); @@ -86,21 +84,19 @@ class _HomePageState extends State { } */ - if (data.containsKey('+clicked_branch_link') && - data['+clicked_branch_link'] == true) { - print( - '------------------------------------Link clicked----------------------------------------------'); + if (data.containsKey('+clicked_branch_link') && data['+clicked_branch_link'] == true) { + print('------------------------------------Link clicked----------------------------------------------'); print('Title: ${data['\$og_title']}'); print('Custom string: ${data['custom_string']}'); print('Custom number: ${data['custom_number']}'); print('Custom bool: ${data['custom_bool']}'); + print('Custom integer: ${data['custom_integer']}'); + print('Custom double: ${data['custom_double']}'); print('Custom date: ${data['custom_date_created']}'); print('Custom list number: ${data['custom_list_number']}'); - print( - '------------------------------------------------------------------------------------------------'); + print('------------------------------------------------------------------------------------------------'); showSnackBar( - message: - 'Link clicked: Custom string - ${data['custom_string']} - Date: ${data['custom_date_created'] ?? ''}', + message: 'Link clicked: Custom string - ${data['custom_string']} - Date: ${data['custom_date_created'] ?? ''}', duration: 10); } }, onError: (error) { @@ -109,18 +105,21 @@ class _HomePageState extends State { } void initDeepLinkData() { - String dateString = - DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); + String dateString = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); metadata = BranchContentMetaData() - ..addCustomMetadata('custom_string', 'abcd') + ..addCustomMetadata('custom_string', 'abcdefg') ..addCustomMetadata('custom_number', 12345) + ..addCustomMetadata('custom_integer', 0) + ..addCustomMetadata('custom_double', 0.0) ..addCustomMetadata('custom_bool', true) ..addCustomMetadata('custom_list_number', [1, 2, 3, 4, 5]) ..addCustomMetadata('custom_list_string', ['a', 'b', 'c']) - ..addCustomMetadata('custom_date_created', dateString); - //--optional Custom Metadata - /* + ..addCustomMetadata('custom_date_created', dateString) + ..addCustomMetadata('\$og_image_width', 237) + ..addCustomMetadata('\$og_image_height', 355) + ..addCustomMetadata('\$og_image_url', imageURL) + //--optional Custom Metadata ..contentSchema = BranchContentSchema.COMMERCE_PRODUCT ..price = 50.99 ..currencyType = BranchCurrencyType.BRL @@ -135,14 +134,8 @@ class _HomePageState extends State { ..ratingAverage = 50 ..ratingMax = 100 ..ratingCount = 2 - ..setAddress( - street: 'street', - city: 'city', - region: 'ES', - country: 'Brazil', - postalCode: '99999-987') + ..setAddress(street: 'street', city: 'city', region: 'ES', country: 'Brazil', postalCode: '99999-987') ..setLocation(31.4521685, -114.7352207); - */ final canonicalIdentifier = const Uuid().v4(); buo = BranchUniversalObject( @@ -152,7 +145,7 @@ class _HomePageState extends State { // (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', + canonicalUrl: 'https://flutter.dev', title: 'Flutter Branch Plugin - $dateString', imageUrl: imageURL, contentDescription: 'Flutter Branch Description - $dateString', @@ -160,9 +153,10 @@ class _HomePageState extends State { keywords: ['Plugin', 'Branch', 'Flutter'], publiclyIndex: true, locallyIndex: true, - expirationDateInMilliSec: DateTime.now() - .add(const Duration(days: 365)) - .millisecondsSinceEpoch); + expirationDateInMilliSec: DateTime.now().add(const Duration(days: 365)).millisecondsSinceEpoch); + + //id = 155; + lp = BranchLinkProperties( channel: 'share', feature: 'sharing', @@ -170,23 +164,22 @@ class _HomePageState extends State { //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, - //alias: 'p/$id', //define link url, + //alias: 'https://branch.io', //define link url, + //alias: 'p/$canonicalIdentifier', //define link url, stage: 'new share', campaign: 'campaign', tags: ['one', 'two', 'three']) ..addControlParam('\$uri_redirect_mode', '1') ..addControlParam('\$ios_nativelink', true) - ..addControlParam('\$match_duration', 7200); - //..addControlParam('\$always_deeplink', true); - //..addControlParam('\$android_redirect_timeout', 750) - //..addControlParam('referring_user_id', 'user_id'); - //..addControlParam('\$fallback_url', 'http') - //..addControlParam( - // '\$fallback_url', 'https://flutter-branch-sdk.netlify.app/'); - //..addControlParam('\$ios_url', 'http'); - //..addControlParam( - // '\$android_url', 'https://flutter-branch-sdk.netlify.app/'); + ..addControlParam('\$match_duration', 7200) + //--optional Link Properties + ..addControlParam('\$always_deeplink', true) + ..addControlParam('\$android_redirect_timeout', 750) + ..addControlParam('referring_user_id', 'user_id') + ..addControlParam('\$fallback_url', 'http') + ..addControlParam('\$fallback_url', 'https://flutter-branch-sdk.netlify.app/') + ..addControlParam('\$ios_url', 'http') + ..addControlParam('\$android_url', 'https://flutter-branch-sdk.netlify.app/'); eventStandard = BranchEvent.standardEvent(BranchStandardEvent.ADD_TO_CART) //--optional Event data @@ -201,50 +194,51 @@ class _HomePageState extends State { ..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'); + ..addCustomData('Custom_Event_Property_Key1', 'Custom_Event_Property_val1') + ..addCustomData('Custom_Event_Property_Key2', 'Custom_Event_Property_val2'); eventCustom = BranchEvent.customEvent('Custom_event') ..alias = 'CustomEventAlias' - ..addCustomData( - 'Custom_Event_Property_Key1', 'Custom_Event_Property_val1') - ..addCustomData( - 'Custom_Event_Property_Key2', 'Custom_Event_Property_val2'); + ..addCustomData('Custom_Event_Property_Key1', 'Custom_Event_Property_val1') + ..addCustomData('Custom_Event_Property_Key2', 'Custom_Event_Property_val2'); } - void showSnackBar({required String message, int duration = 2}) { + void showSnackBar({required String message, int duration = 2, bool error = false}) { scaffoldMessengerKey.currentState!.removeCurrentSnackBar(); scaffoldMessengerKey.currentState!.showSnackBar( SnackBar( content: Text(message), duration: Duration(seconds: duration), + backgroundColor: !error ? Colors.green : Colors.red, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), ), ); } void validSdkIntegration() { if (kIsWeb) { - showSnackBar( - message: 'validateSDKIntegration() not available in Flutter Web'); + showSnackBar(message: 'validateSDKIntegration() not available in Flutter Web'); return; } FlutterBranchSdk.validateSDKIntegration(); - if (Platform.isAndroid) { - showSnackBar(message: 'Check messages in run log or logcat'); - } } - void enableTracking() { - FlutterBranchSdk.disableTracking(false); - showSnackBar(message: 'Tracking enabled'); + void setConsumerProtectionFull() { + if (kIsWeb) { + showSnackBar(message: 'setConsumerProtectionFull() not available in Flutter Web'); + return; + } + FlutterBranchSdk.setConsumerProtectionAttributionLevel(BranchAttributionLevel.FULL); + showSnackBar(message: 'Consumer Preference Levels: Full Attribution'); } - void disableTracking() { - FlutterBranchSdk.disableTracking(true); - showSnackBar(message: 'Tracking disabled'); + void setConsumerProtectionNome() { + FlutterBranchSdk.setConsumerProtectionAttributionLevel(BranchAttributionLevel.NONE); + showSnackBar(message: 'Consumer Preference Levels: No Attribution - No Analytics (GDPR, CCPA)'); } void identifyUser() async { @@ -286,30 +280,25 @@ class _HomePageState extends State { } void getFirstParameters() async { - Map params = - await FlutterBranchSdk.getFirstReferringParams(); + Map params = await FlutterBranchSdk.getFirstReferringParams(); controllerData.sink.add(params.toString()); showSnackBar(message: 'First Parameters recovered'); } void getLastParameters() async { - Map params = - await FlutterBranchSdk.getLatestReferringParams(); + Map params = await FlutterBranchSdk.getLatestReferringParams(); controllerData.sink.add(params.toString()); showSnackBar(message: 'Last Parameters recovered'); } void getLastAttributed() async { - BranchResponse response = - await FlutterBranchSdk.getLastAttributedTouchData(); + BranchResponse response = await FlutterBranchSdk.getLastAttributedTouchData(); if (response.success) { controllerData.sink.add(response.result.toString()); showSnackBar(message: 'Last Attributed TouchData recovered'); } else { showSnackBar( - message: - 'getLastAttributed Error: ${response.errorCode} - ${response.errorMessage}', - duration: 5); + message: 'getLastAttributed Error: ${response.errorCode} - ${response.errorMessage}', duration: 5, error: true); } } @@ -335,31 +324,34 @@ class _HomePageState extends State { return; } bool success = await FlutterBranchSdk.removeFromSearch(buo: buo); - success = - await FlutterBranchSdk.removeFromSearch(buo: buo, linkProperties: lp); + success = await FlutterBranchSdk.removeFromSearch(buo: buo, linkProperties: lp); if (success) { showSnackBar(message: 'Removed from Search'); } } void generateLink(BuildContext context) async { - initDeepLinkData(); - BranchResponse response = - await FlutterBranchSdk.getShortUrl(buo: buo, linkProperties: lp); - if (response.success) { - if (context.mounted) { - showGeneratedLink(context, response.result); + try { + initDeepLinkData(); + BranchResponse response = await FlutterBranchSdk.getShortUrl(buo: buo, linkProperties: lp); + if (response.success) { + if (context.mounted) { + showGeneratedLink(context, response.result); + } + } else { + showSnackBar(message: 'Error : ${response.errorCode} - ${response.errorMessage}', error: true); } - } else { - showSnackBar( - message: 'Error : ${response.errorCode} - ${response.errorMessage}'); + } catch (error) { + showSnackBar(message: 'Error : ${error.toString()}', error: true); } } void generateQrCode( BuildContext context, ) async { - /* + try { + initDeepLinkData(); + /* BranchResponse responseQrCodeData = await FlutterBranchSdk.getQRCodeAsData( buo: buo!, linkProperties: lp, @@ -372,167 +364,172 @@ class _HomePageState extends State { if (responseQrCodeData.success) { print(responseQrCodeData.result); } else { - print( - 'Error : ${responseQrCodeData.errorCode} - ${responseQrCodeData.errorMessage}'); + showSnackBar(message: 'Error : ${responseQrCodeImage.errorCode} - ${responseQrCodeData.errorMessage}', error: true); } */ - initDeepLinkData(); - BranchResponse responseQrCodeImage = - await FlutterBranchSdk.getQRCodeAsImage( - buo: buo, - linkProperties: lp, - qrCode: BranchQrCode( - primaryColor: Colors.black, - //primaryColor: const Color(0xff443a49), //Hex colors - centerLogoUrl: imageURL, - backgroundColor: Colors.white, - imageFormat: BranchImageFormat.PNG)); - if (responseQrCodeImage.success) { - if (context.mounted) { - showQrCode(context, responseQrCodeImage.result); + BranchResponse responseQrCodeImage = await FlutterBranchSdk.getQRCodeAsImage( + buo: buo, + linkProperties: lp, + qrCode: BranchQrCode( + primaryColor: Colors.black, + //primaryColor: const Color(0xff443a80), //Hex colors + centerLogoUrl: imageURL, + backgroundColor: Colors.white54, + imageFormat: BranchImageFormat.PNG)); + if (responseQrCodeImage.success) { + if (context.mounted) { + showQrCode(context, responseQrCodeImage.result); + } + } else { + showSnackBar( + message: 'Error : ${responseQrCodeImage.errorCode} - ${responseQrCodeImage.errorMessage}', error: true); } - } else { - showSnackBar( - message: - 'Error : ${responseQrCodeImage.errorCode} - ${responseQrCodeImage.errorMessage}'); + } catch (error) { + showSnackBar(message: 'Error : ${error.toString()}', error: true); } } void showGeneratedLink(BuildContext context, String url) async { - initDeepLinkData(); - showModalBottomSheet( - isDismissible: true, - isScrollControlled: true, - context: context, - builder: (_) { - return Container( - padding: const EdgeInsets.all(12), - height: 200, - child: Column( - children: [ - const Center( - child: Text( - 'Link created', - style: TextStyle( - color: Colors.blue, fontWeight: FontWeight.bold), - )), - const SizedBox( - height: 10, - ), - Text(url, - maxLines: 1, - style: const TextStyle(overflow: TextOverflow.ellipsis)), - const SizedBox( - height: 10, - ), - IntrinsicWidth( - stepWidth: 300, - child: CustomButton( - onPressed: () async { - await Clipboard.setData(ClipboardData(text: url)); - if (context.mounted) { - Navigator.pop(context); - } - }, - child: const Center(child: Text('Copy link'))), - ), - const SizedBox( - height: 10, - ), - IntrinsicWidth( - stepWidth: 300, - child: CustomButton( - onPressed: () { - FlutterBranchSdk.handleDeepLink(url); - Navigator.pop(this.context); - }, - child: const Center(child: Text('Handle deep link'))), - ), - ], - ), - ); - }); + try { + initDeepLinkData(); + //FlutterBranchSdk.setRequestMetadata('key1_1', 'value1'); + //FlutterBranchSdk.setRequestMetadata('key2_1', 'value2'); + showModalBottomSheet( + isDismissible: true, + isScrollControlled: true, + context: context, + builder: (_) { + return Container( + padding: const EdgeInsets.all(12), + height: 200, + child: Column( + children: [ + const Center( + child: Text( + 'Link created', + style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold), + )), + const SizedBox( + height: 10, + ), + Text(url, maxLines: 1, style: const TextStyle(overflow: TextOverflow.ellipsis)), + const SizedBox( + height: 10, + ), + IntrinsicWidth( + stepWidth: 300, + child: CustomButton( + onPressed: () async { + await Clipboard.setData(ClipboardData(text: url)); + if (context.mounted) { + Navigator.pop(context); + } + }, + child: const Center(child: Text('Copy link'))), + ), + const SizedBox( + height: 10, + ), + IntrinsicWidth( + stepWidth: 300, + child: CustomButton( + onPressed: () { + FlutterBranchSdk.handleDeepLink(url); + Navigator.pop(this.context); + }, + child: const Center(child: Text('Handle deep link'))), + ), + ], + ), + ); + }); + } catch (error) { + showSnackBar(message: 'Error : ${error.toString()}', error: true); + } } void showQrCode(BuildContext context, 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: 250, - width: 250, - ), - IntrinsicWidth( - stepWidth: 300, - child: CustomButton( - onPressed: () => Navigator.pop(this.context), - child: const Center(child: Text('Close'))), - ), - ], - ), - ); - }); + try { + 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: 250, + width: 250, + ), + IntrinsicWidth( + stepWidth: 300, + child: CustomButton( + onPressed: () => Navigator.pop(this.context), child: const Center(child: Text('Close'))), + ), + ], + ), + ); + }); + } catch (error) { + showSnackBar(message: 'Error : ${error.toString()}', error: true); + } } void shareLink() async { - initDeepLinkData(); - 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(message: 'showShareSheet Success', duration: 5); - } else { - showSnackBar( - message: - 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', - duration: 5); + try { + initDeepLinkData(); + 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(message: 'showShareSheet Success', duration: 5); + } else { + showSnackBar( + message: 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', duration: 5, error: true); + } + } catch (error) { + showSnackBar(message: 'Error : ${error.toString()}', error: true); } } void shareWithLPLinkMetadata() async { - /// Create a BranchShareLink instance with a BranchUniversalObject and LinkProperties. - /// Set the BranchShareLink's LPLinkMetadata by using the addLPLinkMetadata() function. - ///Present the BranchShareLink's Share Sheet. + try { + /// Create a BranchShareLink instance with a BranchUniversalObject and LinkProperties. + /// Set the BranchShareLink's LPLinkMetadata by using the addLPLinkMetadata() function. + ///Present the BranchShareLink's Share Sheet. - ///Load icon from Assets - final iconData = (await rootBundle.load('assets/images/branch_logo.jpeg')) - .buffer - .asUint8List(); + ///Load icon from Assets + final iconData = (await rootBundle.load('assets/images/branch_logo.jpeg')).buffer.asUint8List(); - /* + /* ///Load icon from Web final iconData = (await NetworkAssetBundle(Uri.parse(imageURL)).load(imageURL)) .buffer .asUint8List(); */ - initDeepLinkData(); - FlutterBranchSdk.shareWithLPLinkMetadata( - buo: buo, - linkProperties: lp, - title: "Share With LPLinkMetadata", - icon: iconData); + initDeepLinkData(); + FlutterBranchSdk.shareWithLPLinkMetadata( + buo: buo, linkProperties: lp, title: 'Share With LPLinkMetadata', icon: iconData); + } catch (error) { + showSnackBar(message: 'Error : ${error.toString()}', error: true); + } } @override @@ -565,10 +562,7 @@ class _HomePageState extends State { child: Text( snapshot.data!, textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.red), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.red), )) ], ); @@ -586,14 +580,14 @@ class _HomePageState extends State { children: [ Expanded( child: CustomButton( - onPressed: enableTracking, - child: const Text('Enable tracking'), + onPressed: setConsumerProtectionFull, + child: const Text('Consumer Protection FULL', textAlign: TextAlign.center), ), ), Expanded( child: CustomButton( - onPressed: disableTracking, - child: const Text('Disable tracking'), + onPressed: setConsumerProtectionNome, + child: const Text('Consumer Protection NOME', textAlign: TextAlign.center), ), ), ], @@ -638,22 +632,19 @@ class _HomePageState extends State { Expanded( child: CustomButton( onPressed: getFirstParameters, - child: const Text('Get First Parameters', - textAlign: TextAlign.center), + child: const Text('Get First Parameters', textAlign: TextAlign.center), ), ), Expanded( child: CustomButton( onPressed: getLastParameters, - child: const Text('Get Last Parameters', - textAlign: TextAlign.center), + child: const Text('Get Last Parameters', textAlign: TextAlign.center), ), ), Expanded( child: CustomButton( onPressed: getLastAttributed, - child: const Text('Get Last Attributed', - textAlign: TextAlign.center), + child: const Text('Get Last Attributed', textAlign: TextAlign.center), ), ) ], @@ -664,15 +655,13 @@ class _HomePageState extends State { Expanded( child: CustomButton( onPressed: listOnSearch, - child: const Text('List on Search', - textAlign: TextAlign.center), + child: const Text('List on Search', textAlign: TextAlign.center), ), ), Expanded( child: CustomButton( onPressed: removeFromSearch, - child: const Text('Remove from Search', - textAlign: TextAlign.center), + child: const Text('Remove from Search', textAlign: TextAlign.center), ), ), ], @@ -683,15 +672,13 @@ class _HomePageState extends State { Expanded( child: CustomButton( onPressed: () => generateLink(context), - child: const Text('Generate Link', - textAlign: TextAlign.center), + child: const Text('Generate Link', textAlign: TextAlign.center), ), ), Expanded( child: CustomButton( onPressed: () => generateQrCode(context), - child: const Text('Generate QrCode', - textAlign: TextAlign.center), + child: const Text('Generate QrCode', textAlign: TextAlign.center), ), ), ], @@ -701,14 +688,12 @@ class _HomePageState extends State { Expanded( child: (CustomButton( onPressed: shareLink, - child: const Text('Share Link', - textAlign: TextAlign.center), + child: const Text('Share Link', textAlign: TextAlign.center), ))), Expanded( child: CustomButton( onPressed: shareWithLPLinkMetadata, - child: const Text('Share Link with LPLinkMetadata', - textAlign: TextAlign.center), + child: const Text('Share Link with LPLinkMetadata', textAlign: TextAlign.center), )) ], ), @@ -716,8 +701,7 @@ class _HomePageState extends State { const Center( child: Text( 'Data', - style: TextStyle( - color: Colors.blue, fontWeight: FontWeight.bold), + style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold), ), ), const Divider(), diff --git a/example/lib/main.dart b/example/lib/main.dart index 39e0d8b8..2d5905b7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart' show PlatformDispatcher; import 'package:flutter/material.dart'; import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; @@ -5,9 +6,17 @@ import 'app.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + FlutterError.onError = (details) { + FlutterError.presentError(details); + }; + + PlatformDispatcher.instance.onError = (error, stack) { + debugPrintStack(stackTrace: stack); + return true; + }; + //FlutterBranchSdk.setPreinstallCampaign('My Campaign Name'); //FlutterBranchSdk.setPreinstallPartner('Branch \$3p Parameter Value'); - //FlutterBranchSdk.clearPartnerParameters(); /* FlutterBranchSdk.addFacebookPartnerParameter( key: 'em', @@ -18,10 +27,21 @@ void main() async { value: '11234e56af071e9c79927651156bd7a10bca8ac34672aba121056e2698ee7088'); FlutterBranchSdk.setRequestMetadata('key1', 'value1'); - FlqutterBranchSdk.setRequestMetadata('key2', 'value2'); + FlutterBranchSdk.setRequestMetadata('key2', 'value2'); */ - //await FlutterBranchSdk.requestTrackingAuthorization(); - await FlutterBranchSdk.init(enableLogging: true, disableTracking: false); + FlutterBranchSdk.setAnonID('1234556'); + FlutterBranchSdk.setSDKWaitTimeForThirdPartyAPIs(2.5); + + await FlutterBranchSdk.init(enableLogging: true, branchAttributionLevel: BranchAttributionLevel.FULL); + FlutterBranchSdk.setConsumerProtectionAttributionLevel(BranchAttributionLevel.FULL); + + /* + AppTrackingStatus status = await FlutterBranchSdk.requestTrackingAuthorization(); + if (status == AppTrackingStatus.notSupported) { + debugPrint('not supported'); + } + FlutterBranchSdk.disableTracking(true); + */ runApp(const MyApp()); } diff --git a/example/pubspec.lock b/example/pubspec.lock index b37ba1d0..291f028d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,50 +5,50 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -61,18 +61,18 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -84,15 +84,15 @@ packages: path: ".." relative: true source: path - version: "8.0.4" + version: "8.10.0" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "5.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -107,82 +107,74 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" - js: - dependency: transitive - description: - name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf - url: "https://pub.dev" - source: hosted - version: "0.7.1" + version: "0.20.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "4a16b3f03741e1252fda5de3ce712666d010ba2122f8e912c94f9f7b90e1a4c3" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "5.1.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" plugin_platform_interface: dependency: transitive description: @@ -195,15 +187,15 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" sprintf: dependency: transitive description: @@ -216,74 +208,74 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.6" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" uuid: dependency: "direct main" description: name: uuid - sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.5.1" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "15.0.0" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.8.0-0 <4.0.0" + flutter: ">=3.27.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index cf66e382..daf38540 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,7 +6,8 @@ description: Demonstrates how to use the flutter_branch_sdk plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.18.0 <4.0.0" + sdk: ">=3.3.0 <4.0.0" + flutter: 3.27.0 # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -29,8 +30,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - uuid: ^4.4.0 - intl: ^0.19.0 + uuid: ^4.5.1 + intl: ^0.20.2 dev_dependencies: flutter_test: @@ -41,7 +42,7 @@ dev_dependencies: # 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: ^3.0.1 + flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -60,6 +61,7 @@ flutter: # - images/a_dot_ham.jpeg assets: - assets/images/branch_logo.jpeg + - assets/branch-config.json # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/example/web/index.html b/example/web/index.html index 8fcb70f9..f20b7e4b 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -31,35 +31,20 @@ Flutter Branch SDK Example - - - - - - - + + diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/ios/Classes/FlutterBranchSdkPlugin.h b/ios/Classes/FlutterBranchSdkPlugin.h deleted file mode 100644 index 4d7f89b8..00000000 --- a/ios/Classes/FlutterBranchSdkPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface FlutterBranchSdkPlugin : NSObject -@end diff --git a/ios/Classes/FlutterBranchSdkPlugin.m b/ios/Classes/FlutterBranchSdkPlugin.m deleted file mode 100644 index 3e76edf5..00000000 --- a/ios/Classes/FlutterBranchSdkPlugin.m +++ /dev/null @@ -1,15 +0,0 @@ -#import "FlutterBranchSdkPlugin.h" -#if __has_include() -#import -#else -// Support project import fallback if the generated compatibility header -// is not copied when this plugin is created as a library. -// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 -#import "flutter_branch_sdk-Swift.h" -#endif - -@implementation FlutterBranchSdkPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftFlutterBranchSdkPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift deleted file mode 100644 index 83953c1d..00000000 --- a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift +++ /dev/null @@ -1,709 +0,0 @@ -import Flutter -import UIKit -import BranchSDK -import AppTrackingTransparency -import AdSupport - -var methodChannel: FlutterMethodChannel? -var eventChannel: FlutterEventChannel? -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 COCOA_POD_NAME = "org.cocoapods.flutter-branch-sdk"; - -public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStreamHandler { - var eventSink: FlutterEventSink? - var initialParams : [String: Any]? = nil - var initialError : NSError? = nil - - var branch : Branch? - var isInitialized = false - - var requestMetadata : [String: String] = [:] - var facebookParameters : [String: String] = [:] - var snapParameters : [String: String] = [:] - - //--------------------------------------------------------------------------------------------- - // Plugin registry - // -------------------------------------------------------------------------------------------- - public static func register(with registrar: FlutterPluginRegistrar) { - let instance = SwiftFlutterBranchSdkPlugin() - - methodChannel = FlutterMethodChannel(name: MESSAGE_CHANNEL, binaryMessenger: registrar.messenger()) - eventChannel = FlutterEventChannel(name: EVENT_CHANNEL, binaryMessenger: registrar.messenger()) - eventChannel!.setStreamHandler(instance) - - registrar.addApplicationDelegate(instance) - registrar.addMethodCallDelegate(instance, channel: methodChannel!) - } - - func getPluginVersion() -> String { - var pluginVersion : String = "" - if let version = Bundle(identifier: COCOA_POD_NAME)?.infoDictionary?["CFBundleShortVersionString"] as? String { - pluginVersion = version; - } - return pluginVersion - } - - public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable : Any] = [:]) -> Bool { - Branch.getInstance().registerPluginName(PLUGIN_NAME, version: getPluginVersion()) - - if #available(iOS 15.0, *) { - Branch.getInstance().checkPasteboardOnInstall() - } - - Branch.getInstance().initSession(launchOptions: launchOptions) { (params, error) in - if error == nil { - print("Branch InitSession params: \(String(describing: params as? [String: Any]))") - guard let _ = self.eventSink else { - self.initialParams = params as? [String: Any] - return - } - self.eventSink!(params as? [String: Any]) - } else { - let err = (error! as NSError) - print("Branch InitSession error: \(err.localizedDescription)") - guard let _ = self.eventSink else { - self.initialError = err - return - } - self.eventSink!(FlutterError(code: String(err.code), - message: err.localizedDescription, - details: nil)) - } - } - return true - } - - public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { - let branchHandled = Branch.getInstance().application(app, open: url, options: options) - return branchHandled - } - - public func application(_ app: UIApplication, open url: URL, sourceApplication: String, annotation: Any) -> Bool { - let branchHandled = Branch.getInstance().application(app, open: url, sourceApplication: sourceApplication, annotation: annotation) - return branchHandled - } - - public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]) -> Void) -> Bool { - let handledByBranch = Branch.getInstance().continue(userActivity) - return handledByBranch - } - - public func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { - Branch.getInstance().handlePushNotification(userInfo) - } - - //--------------------------------------------------------------------------------------------- - // FlutterStreamHandler Interface Methods - // -------------------------------------------------------------------------------------------- - public func onListen(withArguments arguments: Any?, - eventSink: @escaping FlutterEventSink) -> FlutterError? { - self.eventSink = eventSink - if (initialParams != nil) { - self.eventSink!(self.initialParams) - initialParams = nil - initialError = nil - } else if (initialError != nil) { - self.eventSink!(FlutterError(code: String(self.initialError!.code), - message: self.initialError!.localizedDescription, - details: nil)) - initialParams = nil - initialError = nil - } - return nil - } - - public func onCancel(withArguments arguments: Any?) -> FlutterError? { - eventSink = nil - initialParams = nil - initialError = nil - return nil - } - - //--------------------------------------------------------------------------------------------- - // FlutterMethodChannel Interface Methods - // -------------------------------------------------------------------------------------------- - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch (call.method) { - case "init": - setupBranch(call: call, result: result) - break - case "getShortUrl": - getShortUrl(call: call, result: result) - break - case "showShareSheet": - showShareSheet(call: call, result: result) - break - case "registerView": - registerView(call: call) - break - case "listOnSearch": - listOnSearch(call: call, result: result) - break - case "removeFromSearch": - removeFromSearch(call: call, result: result) - break - case "trackContent": - trackContent(call: call) - break - case "trackContentWithoutBuo": - trackContentWithoutBuo(call: call) - break - case "setIdentity": - setIdentity(call: call) - break - case "setRequestMetadata": - setRequestMetadata(call: call); - break - case "logout": - logout() - break - case "getLatestReferringParams": - getLatestReferringParams(result: result) - break - case "getFirstReferringParams": - getFirstReferringParams(result: result) - break - case "setTrackingDisabled": - setTrackingDisabled(call: call) - break - case "validateSDKIntegration": - validateSDKIntegration() - break - case "isUserIdentified": - isUserIdentified(result: result) - break - case "requestTrackingAuthorization" : - requestTrackingAuthorization(result: result) - break - case "getTrackingAuthorizationStatus" : - getTrackingAuthorizationStatus(result: result) - break - case "getAdvertisingIdentifier" : - getAdvertisingIdentifier(result: result) - break - case "setConnectTimeout": - setConnectTimeout(call: call) - break - case "setRetryCount": - setRetryCount(call: call) - break - case "setRetryInterval": - setRetryInterval(call: call) - break - case "setTimeout": - setTimeout(call: call) - break - case "getLastAttributedTouchData": - getLastAttributedTouchData(call: call, result: result) - break - case "getQRCode": - getQRCode(call: call, result: result) - break - case "shareWithLPLinkMetadata": - shareWithLPLinkMetadata(call: call, result: result) - break - case "handleDeepLink": - handleDeepLink(call: call) - break - case "addFacebookPartnerParameter" : - addFacebookPartnerParameter(call: call) - break - case "clearPartnerParameters" : - Branch.getInstance().clearPartnerParameters() - break - case "setPreinstallCampaign" : - setPreinstallPartner(call: call) - break - case "setPreinstallPartner" : - setPreinstallPartner(call: call) - break - case "addSnapPartnerParameter" : - addSnapPartnerParameter(call: call) - break - case "setDMAParamsForEEA": - setDMAParamsForEEA(call: call) - break; - default: - result(FlutterMethodNotImplemented) - break - } - } - - //--------------------------------------------------------------------------------------------- - // Branch SDK Call Methods - // -------------------------------------------------------------------------------------------- - private func setupBranch(call: FlutterMethodCall, result: @escaping FlutterResult) { - - if (isInitialized) { - result(true) - } - let args = call.arguments as! [String: Any?] - -#if DEBUG - NSLog("setupBranch args: %@", args) -#endif - - if args["disableTracking"] as! Bool == true { - Branch.setTrackingDisabled(true) - } else { - Branch.setTrackingDisabled(false) - } - - - if args["enableLogging"] as! Bool == true { - Branch.enableLogging(at: BranchLogLevel.debug) - } - - if (!requestMetadata.isEmpty) { - for param in requestMetadata { - Branch.getInstance().setRequestMetadataKey(param.key, value: param.value) - } - } - if (!snapParameters.isEmpty) { - for param in snapParameters { - Branch.getInstance().addSnapPartnerParameter(withName: param.key, value: param.value) - } - } - if (!facebookParameters.isEmpty) { - for param in facebookParameters { - Branch.getInstance().addFacebookPartnerParameter(withName: param.key, value: param.value) - } - } - isInitialized = true - result(true) - } - - private func getShortUrl(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 buo: BranchUniversalObject? = convertToBUO(dict: buoDict) - let lp : BranchLinkProperties? = convertToLp(dict: lpDict ) - - let response : NSMutableDictionary! = [:] - buo?.getShortUrl(with: lp!) { (url, error) in - if ((error == nil && url != nil) || (error != nil && url != nil)) { - NSLog("getShortUrl: %@", url!) - response["success"] = NSNumber(value: true) - response["url"] = url! - } 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) - } - } - } - - private func showShareSheet(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 shareText = args["messageText"] as! String - let buo: BranchUniversalObject? = convertToBUO(dict: buoDict) - let lp : BranchLinkProperties? = convertToLp(dict: lpDict ) - let controller = UIApplication.shared.keyWindow!.rootViewController - - let response : NSMutableDictionary! = [:] - buo?.showShareSheet(with: lp, andShareText: shareText, from: controller) { (activityType, completed, error) in - if completed { - response["success"] = NSNumber(value: true) - } else { - response["success"] = NSNumber(value: false) - if let err = (error as NSError?) { - response["errorCode"] = String(err.code) - response["errorMessage"] = err.localizedDescription - } else { - response["errorCode"] = "-1" - response["errorMessage"] = "Canceled by user" - } - } - DispatchQueue.main.async { - result(response) - } - } - } - - private func validateSDKIntegration() { - DispatchQueue.main.async { - Branch.getInstance().validateSDKIntegration() - } - } - - private func trackContent(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let buoDict = args["buo"] as! [[String: Any?]] - let eventDict = args["event"] as! [String: Any?] - let buoList: [BranchUniversalObject] = buoDict.map { b in - convertToBUO(dict: b)! - } - let event: BranchEvent? = convertToEvent(dict : eventDict) - event!.contentItems = buoList - - DispatchQueue.main.async { - event!.logEvent() - } - } - - private func trackContentWithoutBuo(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let eventDict = args["event"] as! [String: Any?] - let event: BranchEvent? = convertToEvent(dict : eventDict) - - DispatchQueue.main.async { - event!.logEvent() - } - } - - private func registerView(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let buoDict = args["buo"] as! [String: Any?] - let buo: BranchUniversalObject? = convertToBUO(dict: buoDict) - - DispatchQueue.main.async { - buo!.registerView() - } - } - - private func listOnSearch(call: FlutterMethodCall, result: @escaping FlutterResult) { - let args = call.arguments as! [String: Any?] - let buoDict = args["buo"] as! [String: Any?] - let buo: BranchUniversalObject? = convertToBUO(dict: buoDict) - var response = NSNumber(value: true) - if let lpDict = args["lp"] as? [String: Any?] { - let lp : BranchLinkProperties! = convertToLp(dict: lpDict) - buo!.listOnSpotlight(with: lp) { (url, error) in - if (error == nil) { - print("Successfully indexed on spotlight") - response = NSNumber(value: true) - } else { - print("Failed indexed on spotlight") - response = NSNumber(value: false) - } - DispatchQueue.main.async { - result(response) - } - } - } else { - buo!.listOnSpotlight() { (url, error) in - if (error == nil) { - print("Successfully indexed on spotlight") - response = NSNumber(value: true) - } else { - print("Failed indexed on spotlight") - response = NSNumber(value: false) - } - DispatchQueue.main.async { - result(response) - } - } - } - } - - private func removeFromSearch(call: FlutterMethodCall, result: @escaping FlutterResult) { - let args = call.arguments as! [String: Any?] - let buoDict = args["buo"] as! [String: Any?] - let buo: BranchUniversalObject? = convertToBUO(dict: buoDict) - var response = NSNumber(value: true) - buo!.removeFromSpotlight { (error) in - if (error == nil) { - print("BUO successfully removed from spotlight") - response = NSNumber(value: true) - } else { - response = NSNumber(value: false) - } - DispatchQueue.main.async { - result(response) - } - } - } - - private func setIdentity(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let userId = args["userId"] as! String - - DispatchQueue.main.async { - Branch.getInstance().setIdentity(userId) - } - } - - private func setRequestMetadata(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let key = args["key"] as! String - let value = args["value"] as! String - - - if (requestMetadata.keys.contains(key) && value.isEmpty) { - requestMetadata.removeValue(forKey: key) - } else { - requestMetadata[key] = value; - } - - DispatchQueue.main.async { - Branch.getInstance().setRequestMetadataKey(key, value: value) - } - } - - private func logout() { - DispatchQueue.main.async { - Branch.getInstance().logout() - } - } - - private func getLatestReferringParams(result: @escaping FlutterResult) { - let latestParams = Branch.getInstance().getLatestReferringParams() - DispatchQueue.main.async { - result(latestParams) - } - } - - private func getFirstReferringParams(result: @escaping FlutterResult) { - let firstParams = Branch.getInstance().getFirstReferringParams() - DispatchQueue.main.async { - result(firstParams) - } - } - - private func setTrackingDisabled(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let value = args["disable"] as! Bool - - DispatchQueue.main.async { - Branch.setTrackingDisabled(value) - } - } - - private func getLastAttributedTouchData(call: FlutterMethodCall, result: @escaping FlutterResult) { - - let args = call.arguments as! [String: Any?] - let response : NSMutableDictionary! = [:] - let data : NSMutableDictionary! = [:] - let attributionWindow = args["attributionWindow"] as? Int ?? 0 - - Branch.getInstance().lastAttributedTouchData(withAttributionWindow: attributionWindow) { latd, error in - if error == nil { - if latd != nil { - data["latd"] = ["attibution_window": latd!.attributionWindow, - "last_atributed_touch_data" : latd!.lastAttributedTouchJSON] - } else { - data["latd"] = [:] - } - response["success"] = NSNumber(value: true) - response["data"] = data - } else { - print("Failed to lastAttributedTouchData: \(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 isUserIdentified(result: @escaping FlutterResult) { - DispatchQueue.main.async { - result(Branch.getInstance().isUserIdentified()) - } - } - - private func setTimeout(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let _ = args["timeout"] as? Int ?? 0 - } - - private func setConnectTimeout(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let connectTimeout = args["connectTimeout"] as? Int ?? 0 - DispatchQueue.main.async { - Branch.getInstance().setNetworkTimeout(TimeInterval(connectTimeout)) - } - } - - private func setRetryCount(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let _ = args["retryCount"] as? Int ?? 0 - } - - private func setRetryInterval(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let retryInterval = args["retryInterval"] as? Int ?? 0 - DispatchQueue.main.async { - Branch.getInstance().setRetryInterval(TimeInterval(retryInterval)) - } - } - - 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! = [:] - - 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) - } - - }) - } - - 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 messageText = args["messageText"] as! String - let buo: BranchUniversalObject? = convertToBUO(dict: buoDict) - let lp : BranchLinkProperties? = convertToLp(dict: lpDict ) - var iconImage : UIImage? - - if let iconData = args["iconData"] as? FlutterStandardTypedData { - iconImage = UIImage(data: iconData.data) - } else { - iconImage = Bundle.main.icon - } - - let bsl = BranchShareLink(universalObject: buo!, linkProperties: lp!) - if #available(iOS 13.0, *) { - bsl.addLPLinkMetadata(messageText, icon: iconImage) - let controller = UIApplication.shared.keyWindow!.rootViewController - bsl.presentActivityViewController(from: controller, anchor: nil) - } else { - showShareSheet(call: call, result: result) - } - } - - private func handleDeepLink(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let url = args["url"] as! String - Branch.getInstance().handleDeepLink(withNewSession: URL(string: url)) - } - - private func addFacebookPartnerParameter(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let key = args["key"] as! String - let value = args["value"] as! String - - if (facebookParameters.keys.contains(key) && value.isEmpty) { - facebookParameters.removeValue(forKey: key) - } else { - facebookParameters[key] = value; - } - - DispatchQueue.main.async { - Branch.getInstance().addFacebookPartnerParameter(withName: key, value:value) - } - } - - private func addSnapPartnerParameter(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let key = args["key"] as! String - let value = args["value"] as! String - - if (snapParameters.keys.contains(key) && value.isEmpty) { - snapParameters.removeValue(forKey: key) - } else { - snapParameters[key] = value; - } - - DispatchQueue.main.async { - Branch.getInstance().addSnapPartnerParameter(withName: key, value:value) - } - } - - private func setPreinstallCampaign(call: FlutterMethodCall) { - } - - private func setPreinstallPartner(call: FlutterMethodCall) { - } - - private func setDMAParamsForEEA(call: FlutterMethodCall) { - let args = call.arguments as! [String: Any?] - let eeaRegion = args["eeaRegion"] as! Bool - let adPersonalizationConsent = args["adPersonalizationConsent"] as! Bool - let adUserDataUsageConsent = args["adUserDataUsageConsent"] as! Bool - - DispatchQueue.main.async { - Branch.setDMAParamsForEEA(eeaRegion,adPersonalizationConsent: adPersonalizationConsent, adUserDataUsageConsent: adUserDataUsageConsent) - } - } - - - /* - 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/ios/flutter_branch_sdk.podspec b/ios/flutter_branch_sdk.podspec index 5c766e0c..49db8263 100644 --- a/ios/flutter_branch_sdk.podspec +++ b/ios/flutter_branch_sdk.podspec @@ -18,10 +18,10 @@ Flutter Plugin for create deep link using Brach SDK (https://branch.io). This pl s.license = { :file => '../LICENSE' } s.author = { 'Rodrigo S. Marques' => 'rodrigosmarques@gmail.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'flutter_branch_sdk/sources/flutter_branch_sdk/**/*.swift' s.dependency 'Flutter' - s.dependency 'BranchSDK', '~> 3.4.3' - s.platform = :ios, '12.0' + s.dependency 'BranchSDK', '~> 3.13.0' + s.platform = :ios, '13.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' diff --git a/ios/flutter_branch_sdk/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/ios/flutter_branch_sdk/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/ios/flutter_branch_sdk/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/flutter_branch_sdk/Package.resolved b/ios/flutter_branch_sdk/Package.resolved new file mode 100644 index 00000000..33740dae --- /dev/null +++ b/ios/flutter_branch_sdk/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "ios-branch-sdk-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/BranchMetrics/ios-branch-sdk-spm", + "state" : { + "revision" : "ce2309db5326aedfe4df8ffbf256a5c1cd3d993f", + "version" : "3.13.0" + } + } + ], + "version" : 2 +} diff --git a/ios/flutter_branch_sdk/Package.swift b/ios/flutter_branch_sdk/Package.swift new file mode 100644 index 00000000..0e3c0543 --- /dev/null +++ b/ios/flutter_branch_sdk/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "flutter_branch_sdk", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "flutter-branch-sdk", targets: ["flutter_branch_sdk"]) + ], + dependencies: [ + .package(url: "https://github.com/BranchMetrics/ios-branch-sdk-spm", "3.13.0"..."3.14.0") + ], + targets: [ + .target( + name: "flutter_branch_sdk", + dependencies: [ + .product(name: "BranchSDK", package: "ios-branch-sdk-spm"), + ], + linkerSettings: [ + .linkedFramework("CoreServices"), + .linkedFramework("SystemConfiguration"), + .linkedFramework("WebKit", .when(platforms: [.iOS])), + .linkedFramework("CoreSpotlight", .when(platforms: [.iOS])), + .linkedFramework("AdServices", .when(platforms: [.iOS])) + ] + ) + ] +) diff --git a/ios/flutter_branch_sdk/sources/flutter_branch_sdk/BranchJsonConfig.swift b/ios/flutter_branch_sdk/sources/flutter_branch_sdk/BranchJsonConfig.swift new file mode 100644 index 00000000..56456bdf --- /dev/null +++ b/ios/flutter_branch_sdk/sources/flutter_branch_sdk/BranchJsonConfig.swift @@ -0,0 +1,31 @@ +import Foundation +import Flutter + +struct BranchJsonConfig: Codable { + let apiUrl: String? + let apiUrlAndroid: String? + let apiUrlIOS: String? + let branchKey: String? + let liveKey: String? + let testKey: String? + let enableLogging: Bool? + let useTestInstance: Bool? + + static func loadFromFile(registrar: FlutterPluginRegistrar) -> BranchJsonConfig? { + let assetKey = registrar.lookupKey(forAsset: "assets/branch-config.json") + + guard let path = Bundle.main.path(forResource: assetKey, ofType: nil) else { + // File 'assets/branch-config.json' not exists + return nil + } + + do { + let data = try Data(contentsOf: URL(fileURLWithPath: path)) + return try JSONDecoder().decode(BranchJsonConfig.self, from: data) + } catch { + // Erro se o arquivo existir, mas for invรกlido. + LogUtils.debug(message: "Failed to decode 'assets/branch-config.json'. Check if the JSON is valid. Error:: \(error)") + return nil + } + } +} diff --git a/ios/Classes/FlutterBranchIoSdkHelper.swift b/ios/flutter_branch_sdk/sources/flutter_branch_sdk/FlutterBranchIoSdkHelper.swift similarity index 77% rename from ios/Classes/FlutterBranchIoSdkHelper.swift rename to ios/flutter_branch_sdk/sources/flutter_branch_sdk/FlutterBranchIoSdkHelper.swift index 7f97ae25..4c5523d5 100644 --- a/ios/Classes/FlutterBranchIoSdkHelper.swift +++ b/ios/flutter_branch_sdk/sources/flutter_branch_sdk/FlutterBranchIoSdkHelper.swift @@ -1,22 +1,15 @@ -// -// FlutterBranchIoSdkFunctions.swift -// -// -// Created by Rodrigo Marques on 04/11/19. -// - import Foundation import BranchSDK +import UIKit //--------------------------------------------------------------------------------------------- // Object Conversion Functions // -------------------------------------------------------------------------------------------- func convertToBUO(dict: [String: Any?]) -> BranchUniversalObject? { - guard let canonicalIdentifier = dict["canonicalIdentifier"] as? String? else { + guard let canonicalIdentifier = dict["canonicalIdentifier"] as? String else { return nil } - let buo = BranchUniversalObject.init() - buo.canonicalIdentifier = canonicalIdentifier + let buo = BranchUniversalObject(canonicalIdentifier: canonicalIdentifier) if let canonicalUrl = dict["canonicalUrl"] as? String { buo.canonicalUrl = canonicalUrl @@ -44,7 +37,7 @@ func convertToBUO(dict: [String: Any?]) -> BranchUniversalObject? { } if let contentMetadata = dict["contentMetadata"] as? [String: Any] { if let content_schema = contentMetadata["content_schema"] as? String { - buo.contentMetadata.contentSchema = BranchContentSchema.init(rawValue: content_schema) + buo.contentMetadata.contentSchema = BranchContentSchema(rawValue: content_schema) } if let quantity = contentMetadata["quantity"] as? Double { buo.contentMetadata.quantity = quantity @@ -53,7 +46,7 @@ func convertToBUO(dict: [String: Any?]) -> BranchUniversalObject? { buo.contentMetadata.price = NSDecimalNumber(floatLiteral: price) } if let currency = contentMetadata["currency"] as? String { - buo.contentMetadata.currency = BNCCurrency.init(rawValue: currency) + buo.contentMetadata.currency = BNCCurrency(rawValue: currency) } if let sku = contentMetadata["sku"] as? String { buo.contentMetadata.sku = sku @@ -65,13 +58,13 @@ func convertToBUO(dict: [String: Any?]) -> BranchUniversalObject? { buo.contentMetadata.productBrand = product_brand } if let product_category = contentMetadata["product_category"] as? String { - buo.contentMetadata.productCategory = BNCProductCategory.init(rawValue: product_category) + buo.contentMetadata.productCategory = BNCProductCategory(rawValue: product_category) } if let product_variant = contentMetadata["product_variant"] as? String { buo.contentMetadata.productVariant = product_variant } if let condition = contentMetadata["condition"] as? String { - buo.contentMetadata.condition = BranchCondition.init(rawValue: condition) + buo.contentMetadata.condition = BranchCondition(rawValue: condition) } if let rating_average = contentMetadata["rating_average"] as? Double { buo.contentMetadata.ratingAverage = rating_average @@ -106,12 +99,12 @@ func convertToBUO(dict: [String: Any?]) -> BranchUniversalObject? { if let longitude = contentMetadata["longitude"] as? Double { buo.contentMetadata.longitude = longitude } - if let image_captions = contentMetadata["image_captions"] as? NSMutableArray { - buo.contentMetadata.imageCaptions = image_captions + if let image_captions = contentMetadata["image_captions"] as? [String] { + buo.contentMetadata.imageCaptions.addObjects(from: image_captions) } if let customMetadata = contentMetadata["customMetadata"] as? [String: Any] { - for metaData in customMetadata { - buo.contentMetadata.customMetadata[metaData.key] = metaData.value + for (key, value) in customMetadata { + buo.contentMetadata.customMetadata[key] = value } } } @@ -119,7 +112,7 @@ func convertToBUO(dict: [String: Any?]) -> BranchUniversalObject? { } func convertToLp(dict: [String: Any?]) -> BranchLinkProperties? { - let lp: BranchLinkProperties = BranchLinkProperties() + let lp = BranchLinkProperties() if let lpChannel = dict["channel"] as? String { lp.channel = lpChannel } @@ -142,28 +135,28 @@ func convertToLp(dict: [String: Any?]) -> BranchLinkProperties? { lp.tags = lptags } if let lpControlParams = dict["controlParams"] as? [String: Any] { - for param in lpControlParams { - lp.addControlParam(param.key, withValue: param.value as? String) + for (key, value) in lpControlParams { + lp.addControlParam(key, withValue: value as? String) } } return lp } func convertToEvent(dict: [String: Any?]) -> BranchEvent? { - var event : BranchEvent - - let eventName = dict["eventName"] as! String - let isStandardEvent = dict["isStandardEvent"] as! Bool - if (isStandardEvent) { - event = BranchEvent.init(name: eventName) - } else { - event = BranchEvent.customEvent(withName: eventName) + guard let eventName = dict["eventName"] as? String, + let isStandardEvent = dict["isStandardEvent"] as? Bool else { + return nil } + + let event: BranchEvent = isStandardEvent ? + BranchEvent.standardEvent(BranchStandardEvent(rawValue: eventName)) : + BranchEvent.customEvent(withName: eventName) + if let transactionID = dict["transactionID"] as? String { event.transactionID = transactionID } if let currency = dict["currency"] as? String { - event.currency = BNCCurrency.init(rawValue: currency) + event.currency = BNCCurrency(rawValue: currency) } if let revenue = dict["revenue"] as? Double { event.revenue = NSDecimalNumber(floatLiteral: revenue) @@ -190,8 +183,10 @@ func convertToEvent(dict: [String: Any?]) -> BranchEvent? { event.adType = convertToAdType(adType: adType) } if let dictCustomData = dict["customData"] as? [String: Any] { - for customData in dictCustomData { - event.customData[customData.key] = (customData.value as! String) + for (key, value) in dictCustomData { + if let stringValue = value as? String { + event.customData[key] = stringValue + } } } if let alias = dict["alias"] as? String { @@ -202,40 +197,31 @@ func convertToEvent(dict: [String: Any?]) -> BranchEvent? { func convertToAdType(adType: String) -> BranchEventAdType { switch adType { - case "BANNER": - return BranchEventAdType.banner - case "INTERSTITIAL": - return BranchEventAdType.interstitial - case "REWARDED_VIDEO": - return BranchEventAdType.rewardedVideo - case "NATIVE": - return BranchEventAdType.native - default: - return BranchEventAdType.none + case "BANNER": return .banner + case "INTERSTITIAL": return .interstitial + case "REWARDED_VIDEO": return .rewardedVideo + case "NATIVE": return .native + default: return .none } } func convertToQRCode(dict: [String: Any?]) -> BranchQRCode { - let qrCode : BranchQRCode = BranchQRCode() + let qrCode = BranchQRCode() if let width = dict["width"] as? Int { - qrCode.width = NSNumber(value: width) + qrCode.width = width as NSNumber } if let margin = dict["margin"] as? Int { - qrCode.margin = NSNumber(value: margin) + qrCode.margin = margin as NSNumber } if let codeColor = dict["codeColor"] as? String { - qrCode.codeColor = UIColor.init(hexString: codeColor) + qrCode.codeColor = UIColor(hexString: codeColor) } if let backgroundColor = dict["backgroundColor"] as? String { - qrCode.backgroundColor = UIColor.init(hexString: backgroundColor) + qrCode.backgroundColor = UIColor(hexString: backgroundColor) } if let imageFormat = dict["imageFormat"] as? String { - if (imageFormat == "JPEG") { - qrCode.imageFormat = BranchQRCodeImageFormat.JPEG - } else { - qrCode.imageFormat = BranchQRCodeImageFormat.PNG - } + qrCode.imageFormat = (imageFormat == "JPEG") ? .JPEG : .PNG } if let centerLogoUrl = dict["centerLogoUrl"] as? String { qrCode.centerLogo = centerLogoUrl @@ -244,7 +230,7 @@ func convertToQRCode(dict: [String: Any?]) -> BranchQRCode { } //--------------------------------------------------------------------------------------------- -// Extension +// Extensions // -------------------------------------------------------------------------------------------- extension Date { @@ -264,6 +250,7 @@ extension Bundle { } return value } + public var icon: UIImage? { if let icons = infoDictionary?["CFBundleIcons"] as? [String: Any], let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], @@ -277,13 +264,13 @@ extension Bundle { extension UIColor { convenience init(hexString: String, alpha: CGFloat = 1.0) { - let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let hexString: String = hexString.trimmingCharacters(in: .whitespacesAndNewlines) let scanner = Scanner(string: hexString) - if (hexString.hasPrefix("#")) { + if hexString.hasPrefix("#") { scanner.scanLocation = 1 } - var color: UInt32 = 0 - scanner.scanHexInt32(&color) + var color: UInt64 = 0 + scanner.scanHexInt64(&color) let mask = 0x000000FF let r = Int(color >> 16) & mask let g = Int(color >> 8) & mask @@ -293,11 +280,12 @@ extension UIColor { 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 + 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) @@ -306,17 +294,15 @@ extension UIColor { 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) - } + let task = URLSession.shared.dataTask(with: url) { data, response, error in + guard let data = data, error == nil else { + DispatchQueue.main.async { completion(nil) } + return + } + DispatchQueue.main.async { + completion(UIImage(data: data)) } } + task.resume() } - } diff --git a/ios/flutter_branch_sdk/sources/flutter_branch_sdk/FlutterBranchSdkPlugin.swift b/ios/flutter_branch_sdk/sources/flutter_branch_sdk/FlutterBranchSdkPlugin.swift new file mode 100644 index 00000000..cefd6e46 --- /dev/null +++ b/ios/flutter_branch_sdk/sources/flutter_branch_sdk/FlutterBranchSdkPlugin.swift @@ -0,0 +1,928 @@ +import Flutter +import UIKit +import BranchSDK +import AppTrackingTransparency +import AdSupport + +// Plugin channel variables and constants +var methodChannel: FlutterMethodChannel? +var eventChannel: FlutterEventChannel? +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 = "8.10.0"; +let COCOA_POD_NAME = "org.cocoapods.flutter-branch-sdk"; + +public class FlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStreamHandler { + var eventSink: FlutterEventSink? + var initialParams : [String: Any]? = nil + var initialError : NSError? = nil + + var branch : Branch? + var isInitialized = false + + var requestMetadata : [String: String] = [:] + var facebookParameters : [String: String] = [:] + var snapParameters : [String: String] = [:] + static var branchJsonConfig: BranchJsonConfig? = nil + + //--------------------------------------------------------------------------------------------- + // Plugin registry + // -------------------------------------------------------------------------------------------- + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = FlutterBranchSdkPlugin() + + methodChannel = FlutterMethodChannel(name: MESSAGE_CHANNEL, binaryMessenger: registrar.messenger()) + eventChannel = FlutterEventChannel(name: EVENT_CHANNEL, binaryMessenger: registrar.messenger()) + eventChannel!.setStreamHandler(instance) + + registrar.addApplicationDelegate(instance) + registrar.addMethodCallDelegate(instance, channel: methodChannel!) + + self.branchJsonConfig = BranchJsonConfig.loadFromFile(registrar: registrar) + + } + + public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable : Any] = [:]) -> Bool { + + if let branchJsonConfig = FlutterBranchSdkPlugin.branchJsonConfig { + + if let apiUrl = branchJsonConfig.apiUrl as? String { + LogUtils.debug(message: "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + LogUtils.debug(message: "The apiUrl parameter has been deprecated. Please use apiUrlIOS instead. Check the documentation.") + LogUtils.debug(message: "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + return false + } + + if let apiUrlIOS = branchJsonConfig.apiUrlIOS as? String { + Branch.setAPIUrl(apiUrlIOS) + LogUtils.debug(message: "Set API URL from branch-config.json: \(apiUrlIOS)") + } + + if let enableLogging = branchJsonConfig.enableLogging as? Bool { + if (enableLogging) { + Branch.enableLogging(at: BranchLogLevel.debug) + LogUtils.debug(message: "Set enableLogging from branch-config.json") + } + } + + if let branchKey = branchJsonConfig.branchKey as? String { + Branch.setBranchKey(branchKey) + LogUtils.debug(message: "Set BranchKey from branch-config.json: \(branchKey)") + } else { + let testKey = branchJsonConfig.testKey ?? "" + let liveKey = branchJsonConfig.liveKey ?? "" + + let useTestInstance = branchJsonConfig.useTestInstance ?? false + + if (useTestInstance && !testKey.isEmpty) { + Branch.setBranchKey(testKey) + LogUtils.debug(message: "Set TestKey from branch-config.json: \(testKey)") + } else if (!liveKey.isEmpty) { + Branch.setBranchKey(liveKey) + LogUtils.debug(message: "Set LiveKey from branch-config.json: \(liveKey)") + } + } + } + + Branch.getInstance().registerPluginName(PLUGIN_NAME, version: PLUGIN_VERSION) + + let disable_nativelink: Bool = Bundle.main.object(forInfoDictionaryKey: "branch_disable_nativelink") as? Bool ?? false + LogUtils.debug(message: "Disable NativeLink: \(String(describing:disable_nativelink))") + + if !disable_nativelink { + if #available(iOS 15.0, *) { + Branch.getInstance().checkPasteboardOnInstall() + } + } + + Branch.getInstance().initSession(launchOptions: launchOptions) { (params, error) in + if error == nil { + LogUtils.debug(message: "InitSession params: \(String(describing: params as? [String: Any]))") + guard let _ = self.eventSink else { + self.initialParams = params as? [String: Any] + return + } + self.eventSink!(params as? [String: Any]) + } else { + let err = (error! as NSError) + LogUtils.debug(message: "Branch InitSession error: \(err.localizedDescription)") + guard let _ = self.eventSink else { + self.initialError = err + return + } + self.eventSink!(FlutterError(code: String(err.code), message: err.localizedDescription, details: nil)) + } + } + return true + } + + public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return Branch.getInstance().application(app, open: url, options: options) + } + + public func application(_ app: UIApplication, open url: URL, sourceApplication: String, annotation: Any) -> Bool { + return Branch.getInstance().application(app, open: url, sourceApplication: sourceApplication, annotation: annotation) + } + + public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]) -> Void) -> Bool { + return Branch.getInstance().continue(userActivity) + } + + public func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) { + Branch.getInstance().handlePushNotification(userInfo) + } + + //--------------------------------------------------------------------------------------------- + // FlutterStreamHandler Interface Methods + // -------------------------------------------------------------------------------------------- + public func onListen(withArguments arguments: Any?, eventSink: @escaping FlutterEventSink) -> FlutterError? { + self.eventSink = eventSink + if (initialParams != nil) { + self.eventSink!(self.initialParams) + } else if (initialError != nil) { + self.eventSink!(FlutterError(code: String(self.initialError!.code),message: self.initialError!.localizedDescription, details: nil)) + } + initialParams = nil + initialError = nil + return nil + } + + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + eventSink = nil + initialParams = nil + initialError = nil + return nil + } + + //--------------------------------------------------------------------------------------------- + // FlutterMethodChannel Interface Methods + // -------------------------------------------------------------------------------------------- + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch (call.method) { + case "init": + setupBranch(call: call, result: result) + break + case "getShortUrl": + getShortUrl(call: call, result: result) + break + case "showShareSheet": + showShareSheet(call: call, result: result) + break + case "registerView": + registerView(call: call) + break + case "listOnSearch": + listOnSearch(call: call, result: result) + break + case "removeFromSearch": + removeFromSearch(call: call, result: result) + break + case "trackContent": + trackContent(call: call) + break + case "trackContentWithoutBuo": + trackContentWithoutBuo(call: call) + break + case "setIdentity": + setIdentity(call: call) + break + case "setRequestMetadata": + setRequestMetadata(call: call); + break + case "logout": + logout() + break + case "getLatestReferringParams": + getLatestReferringParams(result: result) + break + case "getFirstReferringParams": + getFirstReferringParams(result: result) + break + case "setTrackingDisabled": + setTrackingDisabled(call: call) + break + case "validateSDKIntegration": + validateSDKIntegration() + break + case "isUserIdentified": + isUserIdentified(result: result) + break + case "requestTrackingAuthorization" : + requestTrackingAuthorization(result: result) + break + case "getTrackingAuthorizationStatus" : + getTrackingAuthorizationStatus(result: result) + break + case "getAdvertisingIdentifier" : + getAdvertisingIdentifier(result: result) + break + case "setConnectTimeout": + setConnectTimeout(call: call) + break + case "setRetryCount": + setRetryCount(call: call) + break + case "setRetryInterval": + setRetryInterval(call: call) + break + case "setTimeout": + setTimeout(call: call) + break + case "getLastAttributedTouchData": + getLastAttributedTouchData(call: call, result: result) + break + case "getQRCode": + getQRCode(call: call, result: result) + break + case "shareWithLPLinkMetadata": + shareWithLPLinkMetadata(call: call, result: result) + break + case "handleDeepLink": + handleDeepLink(call: call) + break + case "addFacebookPartnerParameter" : + addFacebookPartnerParameter(call: call) + break + case "clearPartnerParameters" : + Branch.getInstance().clearPartnerParameters() + break + case "setPreinstallCampaign" : + setPreinstallPartner(call: call) + break + case "setPreinstallPartner" : + setPreinstallPartner(call: call) + break + case "addSnapPartnerParameter" : + addSnapPartnerParameter(call: call) + break + case "setDMAParamsForEEA": + setDMAParamsForEEA(call: call) + break; + case "setConsumerProtectionAttributionLevel" : + setConsumerProtectionAttributionLevel(call: call) + break; + case "setAnonID": + setAnonID(call: call) + break; + case "setSDKWaitTimeForThirdPartyAPIs": + setSDKWaitTimeForThirdPartyAPIs(call: call) + break; + default: + result(FlutterMethodNotImplemented) + break + } + } + + //--------------------------------------------------------------------------------------------- + // Helper Functions + // -------------------------------------------------------------------------------------------- + + private func getRootViewController() -> UIViewController? { + if #available(iOS 13.0, *) { + let windowScene = UIApplication.shared.connectedScenes + .filter { $0.activationState == .foregroundActive } + .first as? UIWindowScene + return windowScene?.windows.first(where: { $0.isKeyWindow })?.rootViewController + } else { + return UIApplication.shared.keyWindow?.rootViewController + } + } + + private func flutterError(message: String, details: Any? = nil) -> FlutterError { + return FlutterError(code: ERROR_CODE, message: message, details: details) + } + + //--------------------------------------------------------------------------------------------- + // Branch SDK Call Methods + // -------------------------------------------------------------------------------------------- + private func setupBranch(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any], + let enableLogging = args["enableLogging"] as? Bool, + let branchAttributionLevel = args["branchAttributionLevel"] as? String + else { + result(flutterError(message: "Invalid arguments provided for setupBranch", details: call.arguments)) + return + } + + LogUtils.debug(message: "setupBranch args: \(args)") + + if isInitialized { + result(true) + return + } + + if !branchAttributionLevel.isEmpty { + Branch.getInstance().setConsumerProtectionAttributionLevel(BranchAttributionLevel(rawValue: branchAttributionLevel)) + } + + if enableLogging { + Branch.enableLogging(at: BranchLogLevel.debug) + } + + for (key, value) in requestMetadata { + Branch.getInstance().setRequestMetadataKey(key, value: value) + } + for (key, value) in snapParameters { + Branch.getInstance().addSnapPartnerParameter(withName: key, value: value) + } + for (key, value) in facebookParameters { + Branch.getInstance().addFacebookPartnerParameter(withName: key, value: value) + } + + isInitialized = true + result(true) + } + + private func getShortUrl(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any], + let buoDict = args["buo"] as? [String: Any?], + let lpDict = args["lp"] as? [String: Any?] + else { + result(flutterError(message: "Invalid arguments provided for getShortUrl", details: call.arguments)) + return + } + + guard let buo = convertToBUO(dict: buoDict), let lp = convertToLp(dict: lpDict) else { + result(flutterError(message: "Failed to create Branch Universal Object or Link Properties.", details: call.arguments)) + return + } + + var response: [String: Any] = [:] + buo.getShortUrl(with: lp) { (url, error) in + if let urlString = url, error == nil { + NSLog("getShortUrl: %@", urlString) + response["success"] = true + response["url"] = urlString + } else { + response["success"] = false + if let err = error as NSError? { + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } else { + response["errorCode"] = "" + response["errorMessage"] = "Error message not returned by Branch SDK. See log for details." + } + } + DispatchQueue.main.async { + result(response) + } + } + } + + private func showShareSheet(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any], + let buoDict = args["buo"] as? [String: Any?], + let lpDict = args["lp"] as? [String: Any?], + let shareText = args["messageText"] as? String + else { + result(flutterError(message: "Invalid arguments provided for showShareSheet", details: call.arguments)) + return + } + + guard let controller = getRootViewController() else { + result(flutterError(message: "Could not find root view controller to present share sheet")) + return + } + + guard let buo = convertToBUO(dict: buoDict), let lp = convertToLp(dict: lpDict) else { + result(flutterError(message: "Failed to create Branch Universal Object or Link Properties.", details: call.arguments)) + return + } + + var response: [String: Any] = [:] + buo.showShareSheet(with: lp, andShareText: shareText, from: controller) { (activityType, completed, error) in + if completed { + response["success"] = true + } else { + response["success"] = false + if let err = error as NSError? { + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } else { + response["errorCode"] = "-1" + response["errorMessage"] = "Share sheet cancelled by user or unknown error" + } + } + DispatchQueue.main.async { + result(response) + } + } + } + + private func validateSDKIntegration() { + DispatchQueue.main.async { + Branch.getInstance().validateSDKIntegration() + } + } + + private func trackContent(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let buoDictArray = args["buo"] as? [[String: Any?]], + let eventDict = args["event"] as? [String: Any?] + else { + LogUtils.debug(message: "Invalid arguments provided for trackContent") + return + } + + let buoList = buoDictArray.compactMap { convertToBUO(dict: $0) } + guard let event = convertToEvent(dict: eventDict) else { + LogUtils.debug(message: "Failed to create BranchEvent from event dictionary") + return + } + + event.contentItems = buoList + + DispatchQueue.main.async { + event.logEvent() + } + } + + private func trackContentWithoutBuo(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let eventDict = args["event"] as? [String: Any?] + else { + LogUtils.debug(message: "Invalid arguments provided for trackContentWithoutBuo") + return + } + + guard let event = convertToEvent(dict: eventDict) else { + LogUtils.debug(message: "Failed to create BranchEvent from event dictionary") + return + } + + DispatchQueue.main.async { + event.logEvent() + } + } + + private func registerView(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let buoDict = args["buo"] as? [String: Any?] + else { + LogUtils.debug(message: "Invalid arguments provided for registerView") + return + } + + guard let buo = convertToBUO(dict: buoDict) else { + LogUtils.debug(message: "ailed to create BranchUniversalObject from dictionary") + return + } + + DispatchQueue.main.async { + buo.registerView() + } + } + + private func listOnSearch(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any], + let buoDict = args["buo"] as? [String: Any?] + else { + result(flutterError(message: "Invalid arguments provided for listOnSearch", details: call.arguments)) + return + } + + guard let buo = convertToBUO(dict: buoDict) else { + result(flutterError(message: "Failed to create BranchUniversalObject from dictionary", details: buoDict)) + return + } + + if let lpDict = args["lp"] as? [String: Any?], let lp = convertToLp(dict: lpDict) { + buo.listOnSpotlight(with: lp) { (url, error) in + DispatchQueue.main.async { + if (error != nil) { + LogUtils.debug(message: "Failed indexed on spotlight \(error)") + } + result(error == nil) + } + } + } else { + buo.listOnSpotlight() { (url, error) in + DispatchQueue.main.async { + if (error != nil) { + LogUtils.debug(message: "Failed indexed on spotlight \(error)") + } + result(error == nil) + } + } + } + + } + + private func removeFromSearch(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any], + let buoDict = args["buo"] as? [String: Any?] + else { + result(flutterError(message: "Invalid arguments provided for removeFromSearch", details: call.arguments)) + return + } + + guard let buo = convertToBUO(dict: buoDict) else { + result(flutterError(message: "Failed to create BranchUniversalObject from dictionary", details: buoDict)) + return + } + + buo.removeFromSpotlight { (error) in + DispatchQueue.main.async { + if (error != nil) { + LogUtils.debug(message: "Failed remove on spotligh \(error)") + } + result(error == nil) + } + } + } + + private func setIdentity(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let userId = args["userId"] as? String else { + LogUtils.debug(message: "Invalid arguments provided for setIdentity") + return + } + + DispatchQueue.main.async { + Branch.getInstance().setIdentity(userId) + } + } + + private func setRequestMetadata(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let key = args["key"] as? String, + let value = args["value"] as? String else { + LogUtils.debug(message: "Invalid arguments provided for setRequestMetadata") + return + } + + if requestMetadata.keys.contains(key) && value.isEmpty { + requestMetadata.removeValue(forKey: key) + } else { + requestMetadata[key] = value + } + + DispatchQueue.main.async { + Branch.getInstance().setRequestMetadataKey(key, value: value) + } + } + + private func logout() { + DispatchQueue.main.async { + Branch.getInstance().logout() + } + } + + private func getLatestReferringParams(result: @escaping FlutterResult) { + let latestParams = Branch.getInstance().getLatestReferringParams() + DispatchQueue.main.async { + result(latestParams) + } + } + + private func getFirstReferringParams(result: @escaping FlutterResult) { + let firstParams = Branch.getInstance().getFirstReferringParams() + DispatchQueue.main.async { + result(firstParams) + } + } + + private func setTrackingDisabled(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let value = args["disable"] as? Bool else { + LogUtils.debug(message: "Invalid arguments provided for setTrackingDisabled") + return + } + + DispatchQueue.main.async { + Branch.setTrackingDisabled(value) + } + } + + private func getLastAttributedTouchData(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let args = call.arguments as? [String: Any] else { + result(flutterError(message: "Invalid arguments provided for getLastAttributedTouchData", details: call.arguments)) + return + } + + var response: [String: Any] = [:] + let attributionWindow = args["attributionWindow"] as? Int ?? 0 + + Branch.getInstance().lastAttributedTouchData(withAttributionWindow: attributionWindow) { latd, error in + if error == nil { + var data: [String: Any] = [:] + if let attributedData = latd { + data["latd"] = ["attibution_window": attributedData.attributionWindow, + "last_atributed_touch_data": attributedData.lastAttributedTouchJSON] + } else { + data["latd"] = [:] + } + response["success"] = true + response["data"] = data + } else { + LogUtils.debug(message: "Failed to get lastAttributedTouchData: \(String(describing: error))") + response["success"] = false + if let err = error as NSError? { + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } else { + response["errorCode"] = "" + response["errorMessage"] = "Error message not returned by Branch SDK. See log for details." + } + } + DispatchQueue.main.async { + result(response) + } + } + } + + private func isUserIdentified(result: @escaping FlutterResult) { + DispatchQueue.main.async { + result(Branch.getInstance().isUserIdentified()) + } + } + + private func setTimeout(call: FlutterMethodCall) { + // The Branch iOS SDK no longer directly exposes a method for `setTimeout`. + LogUtils.debug(message: "setTimeout called, but not applicable for iOS SDK version.") + } + + private func setConnectTimeout(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let connectTimeout = args["connectTimeout"] as? Int else { + LogUtils.debug(message: "Invalid arguments provided for setConnectTimeout") + return + } + DispatchQueue.main.async { + Branch.getInstance().setNetworkTimeout(TimeInterval(connectTimeout)) + } + } + + private func setRetryCount(call: FlutterMethodCall) { + // The Branch iOS SDK no longer directly exposes a method for `setRetryCount`. + LogUtils.debug(message: "setRetryCount called, but not applicable for iOS SDK version.") + } + + private func setRetryInterval(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let retryInterval = args["retryInterval"] as? Int else { + LogUtils.debug(message: "Invalid arguments provided for setRetryInterval") + return + } + DispatchQueue.main.async { + Branch.getInstance().setRetryInterval(TimeInterval(retryInterval)) + } + } + + private func getQRCode(call: FlutterMethodCall, result: @escaping FlutterResult) { + guard 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?] + else { + result(flutterError(message: "Invalid arguments provided for getQRCode", details: call.arguments)) + return + } + + // First, safely unwrap the optionals. + guard let buo = convertToBUO(dict: buoDict), + let lp = convertToLp(dict: lpDict) else { + result(flutterError(message: "Failed to create Branch Universal Object or Link Properties.", details: call.arguments)) + return + } + + + let qrCode = convertToQRCode(dict: qrCodeDict) + + var response: [String: Any] = [:] + + qrCode.getAsData(buo, linkProperties: lp, completion: { data, error in + if let imageData = data, error == nil { + response["success"] = true + response["result"] = FlutterStandardTypedData(bytes: imageData) + } else { + response["success"] = false + if let err = error as NSError? { + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } else { + response["errorCode"] = "" + response["errorMessage"] = "Error message not returned by Branch SDK. See log for details." + } + } + DispatchQueue.main.async { + result(response) + } + }) + } + + private func shareWithLPLinkMetadata(call: FlutterMethodCall, result: @escaping FlutterResult) { + + guard let args = call.arguments as? [String: Any], + let buoDict = args["buo"] as? [String: Any?], + let lpDict = args["lp"] as? [String: Any?], + let messageText = args["messageText"] as? String + else { + result(flutterError(message: "Invalid arguments provided for shareWithLPLinkMetadata", details: call.arguments)) + return + } + + guard let buo = convertToBUO(dict: buoDict), + let lp = convertToLp(dict: lpDict) else { + result(flutterError(message: "Failed to create BranchUniversalObject or BranchLinkProperties", details: call.arguments)) + return + } + + var iconImage: UIImage? + if let iconData = args["iconData"] as? FlutterStandardTypedData { + iconImage = UIImage(data: iconData.data) + } else { + iconImage = Bundle.main.icon + } + + let bsl = BranchShareLink(universalObject: buo, linkProperties: lp) + if #available(iOS 13.0, *) { + bsl.addLPLinkMetadata(messageText, icon: iconImage) + guard let controller = getRootViewController() else { + result(flutterError(message: "Could not find root view controller to present share sheet for LPLinkMetadata")) + return + } + bsl.presentActivityViewController(from: controller, anchor: nil) + result(true) + } else { + showShareSheet(call: call, result: result) + } + } + + private func handleDeepLink(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let urlString = args["url"] as? String, + let url = URL(string: urlString) else { + LogUtils.debug(message: "Invalid arguments provided for handleDeepLink (URL missing or invalid)") + return + } + Branch.getInstance().handleDeepLink(withNewSession: url) + } + + private func addFacebookPartnerParameter(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let key = args["key"] as? String, + let value = args["value"] as? String else { + LogUtils.debug(message: "Invalid arguments provided for addFacebookPartnerParameter") + return + } + + if facebookParameters.keys.contains(key) && value.isEmpty { + facebookParameters.removeValue(forKey: key) + } else { + facebookParameters[key] = value + } + + DispatchQueue.main.async { + Branch.getInstance().addFacebookPartnerParameter(withName: key, value: value) + } + } + + private func addSnapPartnerParameter(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let key = args["key"] as? String, + let value = args["value"] as? String else { + LogUtils.debug(message: "Invalid arguments provided for addSnapPartnerParameter") + return + } + + if snapParameters.keys.contains(key) && value.isEmpty { + snapParameters.removeValue(forKey: key) + } else { + snapParameters[key] = value + } + + DispatchQueue.main.async { + Branch.getInstance().addSnapPartnerParameter(withName: key, value: value) + } + } + + private func setPreinstallCampaign(call: FlutterMethodCall) { + // This function is primarily relevant for Android. + LogUtils.debug(message: "setPreinstallCampaign called, but not directly applicable for iOS SDK version.") + } + + private func setPreinstallPartner(call: FlutterMethodCall) { + // This function is primarily relevant for Android. + LogUtils.debug(message: "setPreinstallPartner called, but not directly applicable for iOS SDK version.") + } + + private func setDMAParamsForEEA(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let eeaRegion = args["eeaRegion"] as? Bool, + let adPersonalizationConsent = args["adPersonalizationConsent"] as? Bool, + let adUserDataUsageConsent = args["adUserDataUsageConsent"] as? Bool + else { + LogUtils.debug(message: "Invalid arguments provided for setDMAParamsForEEA") + return + } + + DispatchQueue.main.async { + Branch.setDMAParamsForEEA(eeaRegion, adPersonalizationConsent: adPersonalizationConsent, adUserDataUsageConsent: adUserDataUsageConsent) + } + } + + private func setConsumerProtectionAttributionLevel(call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let branchAttributionLevelString = args["branchAttributionLevel"] as? String + else { + LogUtils.debug(message: "Invalid arguments provided for setConsumerProtectionAttributionLevel") + return + } + + let branchAttributionLevel = BranchAttributionLevel(rawValue: branchAttributionLevelString) + + DispatchQueue.main.async { + Branch.getInstance().setConsumerProtectionAttributionLevel(branchAttributionLevel) + } + } + + + /* + 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 custom 'notSupported' code for iOS < 14 + } + } + } + + 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 custom 'notSupported' code for iOS < 14 + } + } + } + + 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 + } + } + } + + /* + Sets a custom Meta Anon ID for the current user. + @param anonID The custom Meta Anon ID to be used by Branch. + */ + private func setAnonID (call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let anonId = args["anonId"] as? String else { + LogUtils.debug(message: "Invalid arguments provided for setAnonID") + return + } + LogUtils.debug(message: "setAnonID: \(anonId)") + DispatchQueue.main.async { + Branch.setAnonID(anonId) + } + } + /* + Set the SDK wait time for third party APIs (for fetching ODM info and Apple Attribution Token) to finish + This timeout should be > 0 and <= 10 seconds. + @param waitTime Number of seconds before third party API calls are considered timed out. Default is 0.5 seconds (500ms). + */ + private func setSDKWaitTimeForThirdPartyAPIs (call: FlutterMethodCall) { + guard let args = call.arguments as? [String: Any], + let waitTime = args["waitTime"] as? Double else { + LogUtils.debug(message: "Invalid arguments provided for setSDKWaitTimeForThirdPartyAPIs") + return + } + LogUtils.debug(message: "setSDKWaitTimeForThirdPartyAPIs: \(String(describing: waitTime))") + DispatchQueue.main.async { + Branch.setSDKWaitTimeForThirdPartyAPIs(waitTime) + } + } + +} diff --git a/ios/flutter_branch_sdk/sources/flutter_branch_sdk/LogUtils.swift b/ios/flutter_branch_sdk/sources/flutter_branch_sdk/LogUtils.swift new file mode 100644 index 00000000..553394d3 --- /dev/null +++ b/ios/flutter_branch_sdk/sources/flutter_branch_sdk/LogUtils.swift @@ -0,0 +1,10 @@ +import Foundation + +public class LogUtils { + private static let DEBUG_NAME = "FlutterBranchSDK" + public static func debug(message: String) { + #if DEBUG + print(DEBUG_NAME, " - ", message) + #endif + } +} diff --git a/lib/flutter_branch_sdk.dart b/lib/flutter_branch_sdk.dart index cf430dcc..a2ba79cb 100644 --- a/lib/flutter_branch_sdk.dart +++ b/lib/flutter_branch_sdk.dart @@ -1,18 +1,13 @@ -/// This is a Flutter plugin that implemented Branch SDK - https://branch.io/ -/// Branch.io helps mobile apps grow with deep links that power referral systems, -/// sharing links and invites with full attribution and analytics. -/// Supports Android, iOS and Web. - -library flutter_branch_sdk; - import 'dart:typed_data'; import 'src/flutter_branch_sdk_platform_interface.dart'; import 'src/objects/app_tracking_transparency.dart'; +import 'src/objects/branch_attribution_level.dart'; import 'src/objects/branch_universal_object.dart'; export 'src/flutter_branch_sdk_platform_interface.dart'; export 'src/objects/app_tracking_transparency.dart'; +export 'src/objects/branch_attribution_level.dart'; export 'src/objects/branch_universal_object.dart'; part 'src/flutter_branch_sdk.dart'; diff --git a/lib/src/flutter_branch_sdk.dart b/lib/src/flutter_branch_sdk.dart index f1c36139..81b9d0e9 100644 --- a/lib/src/flutter_branch_sdk.dart +++ b/lib/src/flutter_branch_sdk.dart @@ -1,13 +1,23 @@ part of '../flutter_branch_sdk.dart'; class FlutterBranchSdk { - ///Initialize Branch SDK - /// [enableLogging] - Sets `true` turn on debug logging - /// [disableTracking] - Sets `true` to disable tracking in Branch SDK for GDPR compliant on start. After having consent, sets `false` - static Future init( - {bool enableLogging = false, bool disableTracking = false}) async { + /// Initializes the Branch SDK. + /// + /// This function initializes the Branch SDK with the specified configuration options. + /// + /// **Parameters:** + /// + /// - [enableLogging]: Whether to enable detailed logging. Defaults to `false`. + /// - [branchAttributionLevel]: The level of attribution data to collect. + /// - `BranchAttributionLevel.FULL`: Full Attribution (Default) + /// - `BranchAttributionLevel.REDUCE`: Reduced Attribution (Non-Ads + Privacy Frameworks) + /// - `BranchAttributionLevel.MINIMAL`: Minimal Attribution - Analytics Only + /// - `BranchAttributionLevel.NONE`: No Attribution - No Analytics (GDPR, CCPA) + /// + + static Future init({bool enableLogging = false, BranchAttributionLevel? branchAttributionLevel}) async { await FlutterBranchSdkPlatform.instance - .init(enableLogging: enableLogging, disableTracking: disableTracking); + .init(enableLogging: enableLogging, branchAttributionLevel: branchAttributionLevel); } ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value @@ -35,21 +45,9 @@ class FlutterBranchSdk { 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); - } - - ///Listen click em Branch Deeplinks - @Deprecated('Use `listSession') - static Stream> initSession() { - return FlutterBranchSdkPlatform.instance.initSession(); - } - ///Listen click em Branch Deeplinks static Stream> listSession() { - return FlutterBranchSdkPlatform.instance.initSession(); + return FlutterBranchSdkPlatform.instance.listSession(); } ///Use the SDK integration validator to check that you've added the Branch SDK and @@ -60,10 +58,8 @@ class FlutterBranchSdk { ///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); + {required BranchUniversalObject buo, required BranchLinkProperties linkProperties}) async { + return FlutterBranchSdkPlatform.instance.getShortUrl(buo: buo, linkProperties: linkProperties); } ///Showing a Share Sheet @@ -82,17 +78,13 @@ class FlutterBranchSdk { } ///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); + 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); + 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. @@ -102,21 +94,15 @@ class FlutterBranchSdk { ///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); + 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); + static Future removeFromSearch({required BranchUniversalObject buo, BranchLinkProperties? linkProperties}) async { + return FlutterBranchSdkPlatform.instance.removeFromSearch(buo: buo, linkProperties: linkProperties); } ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. @@ -174,10 +160,8 @@ class FlutterBranchSdk { } ///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); + static Future getLastAttributedTouchData({int? attributionWindow}) async { + return FlutterBranchSdkPlatform.instance.getLastAttributedTouchData(attributionWindow: attributionWindow); } ///Creates a Branch QR Code image. Returns the QR code as Uint8List. @@ -185,8 +169,8 @@ class FlutterBranchSdk { {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required BranchQrCode qrCode}) async { - return FlutterBranchSdkPlatform.instance.getQRCodeAsData( - buo: buo, linkProperties: linkProperties, qrCodeSettings: qrCode); + return FlutterBranchSdkPlatform.instance + .getQRCodeAsData(buo: buo, linkProperties: linkProperties, qrCodeSettings: qrCode); } ///Creates a Branch QR Code image. Returns the QR code as a Image. @@ -194,8 +178,8 @@ class FlutterBranchSdk { {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required BranchQrCode qrCode}) async { - return FlutterBranchSdkPlatform.instance.getQRCodeAsImage( - buo: buo, linkProperties: linkProperties, qrCodeSettings: qrCode); + return FlutterBranchSdkPlatform.instance + .getQRCodeAsImage(buo: buo, linkProperties: linkProperties, qrCodeSettings: qrCode); } ///Share with LPLinkMetadata on iOS @@ -204,13 +188,13 @@ class FlutterBranchSdk { required BranchLinkProperties linkProperties, required Uint8List icon, required String title}) { - Map params = {}; + final Map params = {}; params['buo'] = buo.toMap(); params['lp'] = linkProperties.toMap(); params['title'] = title; - FlutterBranchSdkPlatform.instance.shareWithLPLinkMetadata( - buo: buo, linkProperties: linkProperties, icon: icon, title: title); + FlutterBranchSdkPlatform.instance + .shareWithLPLinkMetadata(buo: buo, linkProperties: linkProperties, icon: icon, title: title); } ///Have Branch end the current deep link session and start a new session with the provided URL. @@ -221,10 +205,8 @@ class FlutterBranchSdk { /// Add a Partner Parameter for Facebook. /// Once set, this parameter is attached to installs, opens and events until cleared or the app restarts. /// See Facebook's documentation for details on valid parameters - static void addFacebookPartnerParameter( - {required String key, required String value}) { - FlutterBranchSdkPlatform.instance - .addFacebookPartnerParameter(key: key, value: value); + static void addFacebookPartnerParameter({required String key, required String value}) { + FlutterBranchSdkPlatform.instance.addFacebookPartnerParameter(key: key, value: value); } /// Clears all Partner Parameters @@ -244,10 +226,8 @@ class FlutterBranchSdk { ///Add a Partner Parameter for Snap. ///Once set, this parameter is attached to installs, opens and events until cleared or the app restarts. - static void addSnapPartnerParameter( - {required String key, required String value}) { - FlutterBranchSdkPlatform.instance - .addSnapPartnerParameter(key: key, value: value); + static void addSnapPartnerParameter({required String key, required String value}) { + FlutterBranchSdkPlatform.instance.addSnapPartnerParameter(key: key, value: value); } /// Sets the value of parameters required by Google Conversion APIs for DMA Compliance in EEA region. @@ -255,12 +235,28 @@ class FlutterBranchSdk { /// [adPersonalizationConsent] `true` If End user has granted/denied ads personalization consent. /// [adUserDataUsageConsent] `true If User has granted/denied consent for 3P transmission of user level data for ads. static void setDMAParamsForEEA( - {required bool eeaRegion, - required bool adPersonalizationConsent, - required bool adUserDataUsageConsent}) { + {required bool eeaRegion, required bool adPersonalizationConsent, required bool adUserDataUsageConsent}) { FlutterBranchSdkPlatform.instance.setDMAParamsForEEA( eeaRegion: eeaRegion, adPersonalizationConsent: adPersonalizationConsent, adUserDataUsageConsent: adUserDataUsageConsent); } + + /// Sets the consumer protection attribution level. + static void setConsumerProtectionAttributionLevel(BranchAttributionLevel branchAttributionLevel) { + FlutterBranchSdkPlatform.instance.setConsumerProtectionAttributionLevel(branchAttributionLevel); + } + + /// Sets a custom Meta Anon ID for the current user. + /// [anonID] The custom Meta Anon ID to be used by Branch. + static void setAnonID(String anonId) { + FlutterBranchSdkPlatform.instance.setAnonID(anonId); + } + + /// Set the SDK wait time for third party APIs (for fetching ODM info and Apple Attribution Token) to finish + /// This timeout should be > 0 and <= 10 seconds. + /// [waitTime] Number of seconds before third party API calls are considered timed out. Default is 0.5 seconds (500ms). + static void setSDKWaitTimeForThirdPartyAPIs(double waitTime) { + FlutterBranchSdkPlatform.instance.setSDKWaitTimeForThirdPartyAPIs(waitTime); + } } diff --git a/lib/src/flutter_branch_sdk_method_channel.dart b/lib/src/flutter_branch_sdk_method_channel.dart index 156aac69..1c5d1a00 100644 --- a/lib/src/flutter_branch_sdk_method_channel.dart +++ b/lib/src/flutter_branch_sdk_method_channel.dart @@ -6,6 +6,7 @@ import 'package:flutter_branch_sdk/src/constants.dart'; import 'flutter_branch_sdk_platform_interface.dart'; import 'objects/app_tracking_transparency.dart'; +import 'objects/branch_attribution_level.dart'; import 'objects/branch_universal_object.dart'; /// An implementation of [FlutterBranchSdkPlatform] that uses method channels. @@ -15,90 +16,77 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { final eventChannel = const EventChannel(AppConstants.EVENT_CHANNEL); static Stream>? _initSessionStream; - static bool isInitialized = false; - - ///Initialize Branch SDK - /// [enableLogging] - Sets `true` turn on debug logging - /// [disableTracking] - Sets `true` to disable tracking in Branch SDK for GDPR compliant on start. After having consent, sets `false` - @override - Future init( - {bool enableLogging = false, bool disableTracking = false}) async { + static var isInitialized = false; + + /// Initializes the Branch SDK. + /// + /// This function initializes the Branch SDK with the specified configuration options. + /// + /// **Parameters:** + /// + /// - [enableLogging]: Whether to enable detailed logging. Defaults to `false`. + /// - [branchAttributionLevel]: The level of attribution data to collect. + /// - `BranchAttributionLevel.FULL`: Full Attribution (Default) + /// - `BranchAttributionLevel.REDUCE`: Reduced Attribution (Non-Ads + Privacy Frameworks) + /// - `BranchAttributionLevel.MINIMAL`: Minimal Attribution - Analytics Only + /// - `BranchAttributionLevel.NONE`: No Attribution - No Analytics (GDPR, CCPA) + /// + @override + Future init({bool enableLogging = false, BranchAttributionLevel? branchAttributionLevel}) async { if (isInitialized) { return; } - await messageChannel.invokeMethod('init', - {'enableLogging': enableLogging, 'disableTracking': disableTracking}); + var branchAttributionLevelString = ''; + + if (branchAttributionLevel == null) { + branchAttributionLevelString = ''; + } else { + branchAttributionLevelString = getBranchAttributionLevelString(branchAttributionLevel); + } + await messageChannel + .invokeMethod('init', {'enableLogging': enableLogging, 'branchAttributionLevel': branchAttributionLevelString}); isInitialized = true; } ///Identifies the current user to the Branch API by supplying a unique identifier as a userId value @override void setIdentity(String userId) { - assert(isInitialized, - 'Call `setIdentity` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `setIdentity` after `FlutterBranchSdk.init()` method'); 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}); + 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() { - assert( - isInitialized, 'Call `logout` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `logout` after `FlutterBranchSdk.init()` method'); messageChannel.invokeMethod('logout'); } ///Returns the last parameters associated with the link that referred the user @override Future> getLatestReferringParams() async { - assert(isInitialized, - 'Call `getLatestReferringParams` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `getLatestReferringParams` after `FlutterBranchSdk.init()` method'); return await messageChannel.invokeMethod('getLatestReferringParams'); } ///Returns the first parameters associated with the link that referred the user @override Future> getFirstReferringParams() async { - assert(isInitialized, - 'Call `getFirstReferringParams` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `getFirstReferringParams` after `FlutterBranchSdk.init()` method'); 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 { - assert(isInitialized, - 'Call `disableTracking` after `FlutterBranchSdk.init()` method'); - messageChannel.invokeMethod('setTrackingDisabled', {'disable': value}); - } - - ///Initialises a session with the Branch API - ///Listen click em Branch DeepLinks - @Deprecated('Use `listSession') - @override - Stream> initSession() { - assert(isInitialized, - 'Call `initSession` after `FlutterBranchSdk.init()` method'); - _initSessionStream ??= - eventChannel.receiveBroadcastStream().cast>(); - - return _initSessionStream!; - } - ///Listen click em Branch DeepLinks @override Stream> listSession() { - assert(isInitialized, - 'Call `listSession` after `FlutterBranchSdk.init()` method'); - _initSessionStream ??= - eventChannel.receiveBroadcastStream().cast>(); + assert(isInitialized, 'Call `listSession` after `FlutterBranchSdk.init()` method'); + _initSessionStream ??= eventChannel.receiveBroadcastStream().cast>(); return _initSessionStream!; } @@ -107,27 +95,22 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { ///handle deep links correctly when you first integrate Branch into your app. @override void validateSDKIntegration() { - assert(isInitialized, - 'Call `validateSDKIntegration` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `validateSDKIntegration` after `FlutterBranchSdk.init()` method'); messageChannel.invokeMethod('validateSDKIntegration'); } ///Creates a short url for the BUO @override Future getShortUrl( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties}) async { - assert(isInitialized, - 'Call `getShortUrl` after `FlutterBranchSdk.init()` method'); - Map response = await messageChannel.invokeMethod( - 'getShortUrl', {'buo': buo.toMap(), 'lp': linkProperties.toMap()}); + {required BranchUniversalObject buo, required BranchLinkProperties linkProperties}) async { + assert(isInitialized, 'Call `getShortUrl` after `FlutterBranchSdk.init()` method'); + final 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']); + return BranchResponse.error(errorCode: response['errorCode'], errorMessage: response['errorMessage']); } } @@ -139,10 +122,8 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { required String messageText, String androidMessageTitle = '', String androidSharingTitle = ''}) async { - assert(isInitialized, - 'Call `showShareSheet` after `FlutterBranchSdk.init()` method'); - Map response = - await messageChannel.invokeMethod('showShareSheet', { + assert(isInitialized, 'Call `showShareSheet` after `FlutterBranchSdk.init()` method'); + final Map response = await messageChannel.invokeMethod('showShareSheet', { 'buo': buo.toMap(), 'lp': linkProperties.toMap(), 'messageText': messageText, @@ -153,20 +134,15 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { if (response['success']) { return BranchResponse.success(result: response['url']); } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); + 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}) { - assert(isInitialized, - 'Call `trackContent` after `FlutterBranchSdk.init()` method'); - Map params = {}; + void trackContent({required List buo, required BranchEvent branchEvent}) { + assert(isInitialized, 'Call `trackContent` after `FlutterBranchSdk.init()` method'); + final Map params = {}; params['buo'] = buo.map((b) => b.toMap()).toList(); if (branchEvent.toMap().isNotEmpty) { params['event'] = branchEvent.toMap(); @@ -177,32 +153,26 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { ///Logs this BranchEvent to Branch for tracking and analytics @override void trackContentWithoutBuo({required BranchEvent branchEvent}) { - assert(isInitialized, - 'Call `trackContentWithoutBuo` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `trackContentWithoutBuo` after `FlutterBranchSdk.init()` method'); if (branchEvent.toMap().isEmpty) { throw ArgumentError('branchEvent is required'); } - messageChannel - .invokeMethod('trackContentWithoutBuo', {'event': branchEvent.toMap()}); + 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}) { - assert(isInitialized, - 'Call `registerView` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `registerView` after `FlutterBranchSdk.init()` method'); 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 { - assert(isInitialized, - 'Call `listOnSearch` after `FlutterBranchSdk.init()` method'); - Map params = {}; + Future listOnSearch({required BranchUniversalObject buo, BranchLinkProperties? linkProperties}) async { + assert(isInitialized, 'Call `listOnSearch` after `FlutterBranchSdk.init()` method'); + final Map params = {}; params['buo'] = buo.toMap(); if (linkProperties != null && linkProperties.toMap().isNotEmpty) { params['lp'] = linkProperties.toMap(); @@ -214,12 +184,9 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { /// 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 { - assert(isInitialized, - 'Call `removeFromSearch` after `FlutterBranchSdk.init()` method'); - Map params = {}; + Future removeFromSearch({required BranchUniversalObject buo, BranchLinkProperties? linkProperties}) async { + assert(isInitialized, 'Call `removeFromSearch` after `FlutterBranchSdk.init()` method'); + final Map params = {}; params['buo'] = buo.toMap(); if (linkProperties != null && linkProperties.toMap().isNotEmpty) { params['lp'] = linkProperties.toMap(); @@ -232,8 +199,7 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { ///This includes persisting through uninstalls, as we track device id. @override Future isUserIdentified() async { - assert(isInitialized, - 'Call `isUserIdentified` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `isUserIdentified` after `FlutterBranchSdk.init()` method'); return await messageChannel.invokeMethod('isUserIdentified'); } @@ -241,13 +207,11 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { /// on Android returns notSupported @override Future requestTrackingAuthorization() async { - assert(isInitialized, - 'Call `requestTrackingAuthorization` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `requestTrackingAuthorization` after `FlutterBranchSdk.init()` method'); if (!Platform.isIOS) { return AppTrackingStatus.notSupported; } - final int status = (await messageChannel - .invokeMethod('requestTrackingAuthorization'))!; + final int status = (await messageChannel.invokeMethod('requestTrackingAuthorization'))!; return AppTrackingStatus.values[status]; } @@ -255,13 +219,11 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { /// on Android returns notSupported @override Future getTrackingAuthorizationStatus() async { - assert(isInitialized, - 'Call `getTrackingAuthorizationStatus` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `getTrackingAuthorizationStatus` after `FlutterBranchSdk.init()` method'); if (!Platform.isIOS) { return AppTrackingStatus.notSupported; } - final int status = (await messageChannel - .invokeMethod('getTrackingAuthorizationStatus'))!; + final int status = (await messageChannel.invokeMethod('getTrackingAuthorizationStatus'))!; return AppTrackingStatus.values[status]; } @@ -269,63 +231,60 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { /// on Android returns empty string @override Future getAdvertisingIdentifier() async { - assert(isInitialized, - 'Call `getAdvertisingIdentifier` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `getAdvertisingIdentifier` after `FlutterBranchSdk.init()` method'); if (!Platform.isIOS) { - return ""; + return ''; } - final String uuid = (await messageChannel - .invokeMethod('getAdvertisingIdentifier'))!; + final String uuid = (await messageChannel.invokeMethod('getAdvertisingIdentifier'))!; return uuid; } @override void setConnectTimeout(int connectTimeout) { - assert(isInitialized, - 'Call `setConnectTimeout` after `FlutterBranchSdk.init()` method'); - messageChannel - .invokeMethod('setConnectTimeout', {'connectTimeout': connectTimeout}); + assert(isInitialized, 'Call `setConnectTimeout` after `FlutterBranchSdk.init()` method'); + messageChannel.invokeMethod('setConnectTimeout', {'connectTimeout': connectTimeout}); } @override void setRetryCount(int retryCount) { - assert(isInitialized, - 'Call `setRetryCount` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `setRetryCount` after `FlutterBranchSdk.init()` method'); messageChannel.invokeMethod('setRetryCount', {'retryCount': retryCount}); } @override void setRetryInterval(int retryInterval) { - assert(isInitialized, - 'Call `setRetryInterval` after `FlutterBranchSdk.init()` method'); - messageChannel - .invokeMethod('setRetryInterval', {'retryInterval': retryInterval}); + assert(isInitialized, 'Call `setRetryInterval` after `FlutterBranchSdk.init()` method'); + messageChannel.invokeMethod('setRetryInterval', {'retryInterval': retryInterval}); } @override void setTimeout(int timeout) { - assert(isInitialized, - 'Call `setTimeout` after `FlutterBranchSdk.init()` method'); + assert(isInitialized, 'Call `setTimeout` after `FlutterBranchSdk.init()` method'); messageChannel.invokeMethod('setTimeout', {'timeout': timeout}); } @override - Future getLastAttributedTouchData( - {int? attributionWindow}) async { - assert(isInitialized, - 'Call `getLastAttributedTouchData` after `FlutterBranchSdk.init()` method'); - Map params = {}; + Future getLastAttributedTouchData({int? attributionWindow}) async { + assert(isInitialized, 'Call `getLastAttributedTouchData` after `FlutterBranchSdk.init()` method'); + final Map params = {}; if (attributionWindow != null) { params['attributionWindow'] = attributionWindow; } - Map response = - await messageChannel.invokeMethod('getLastAttributedTouchData', params); + final Map response = await messageChannel.invokeMethod('getLastAttributedTouchData', params); if (response['success']) { - return BranchResponse.success(result: response['data']['latd']); + final Map? data = response['data'] as Map?; + final Map? latd = data?['latd'] as Map?; + + if (latd != null) { + return BranchResponse.success(result: latd); + } else { + return BranchResponse.error( + errorCode: '-1', + errorMessage: 'Incomplete or null data', + ); + } } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); + return BranchResponse.error(errorCode: response['errorCode'], errorMessage: response['errorMessage']); } } @@ -335,21 +294,14 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required BranchQrCode qrCodeSettings}) async { - assert(isInitialized, - 'Call `getQRCodeAsData` after `FlutterBranchSdk.init()` method'); - Map response = - await messageChannel.invokeMethod('getQRCode', { - 'buo': buo.toMap(), - 'lp': linkProperties.toMap(), - 'qrCodeSettings': qrCodeSettings.toMap() - }); + assert(isInitialized, 'Call `getQRCodeAsData` after `FlutterBranchSdk.init()` method'); + final Map response = await messageChannel.invokeMethod( + 'getQRCode', {'buo': buo.toMap(), 'lp': linkProperties.toMap(), 'qrCodeSettings': qrCodeSettings.toMap()}); if (response['success']) { return BranchResponse.success(result: response['result']); } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); + return BranchResponse.error(errorCode: response['errorCode'], errorMessage: response['errorMessage']); } } @@ -359,34 +311,26 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required BranchQrCode qrCodeSettings}) async { - assert(isInitialized, - 'Call `getQRCodeAsImage` after `FlutterBranchSdk.init()` method'); - Map response = - await messageChannel.invokeMethod('getQRCode', { - 'buo': buo.toMap(), - 'lp': linkProperties.toMap(), - 'qrCodeSettings': qrCodeSettings.toMap() - }); + assert(isInitialized, 'Call `getQRCodeAsImage` after `FlutterBranchSdk.init()` method'); + final Map response = await messageChannel.invokeMethod( + 'getQRCode', {'buo': buo.toMap(), 'lp': linkProperties.toMap(), 'qrCodeSettings': qrCodeSettings.toMap()}); if (response['success']) { return BranchResponse.success(result: Image.memory(response['result'])); } else { - return BranchResponse.error( - errorCode: response['errorCode'], - errorMessage: response['errorMessage']); + return BranchResponse.error(errorCode: response['errorCode'], errorMessage: response['errorMessage']); } } ///Share with LPLinkMetadata on iOS @override - void shareWithLPLinkMetadata( + Future shareWithLPLinkMetadata( {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required Uint8List icon, required String title}) async { - assert(isInitialized, - 'Call `shareWithLPLinkMetadata` after `FlutterBranchSdk.init()` method'); - Map params = {}; + assert(isInitialized, 'Call `shareWithLPLinkMetadata` after `FlutterBranchSdk.init()` method'); + final Map params = {}; params['buo'] = buo.toMap(); params['lp'] = linkProperties.toMap(); params['messageText'] = title; @@ -397,9 +341,8 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { ///Have Branch end the current deep link session and start a new session with the provided URL. @override - void handleDeepLink(String url) { - assert(isInitialized, - 'Call `handleDeepLink` after `FlutterBranchSdk.init()` method'); + Future handleDeepLink(String url) async { + assert(isInitialized, 'Call `handleDeepLink` after `FlutterBranchSdk.init()` method'); if (url.isEmpty) { throw ArgumentError('url is required'); } @@ -410,10 +353,8 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { /// Once set, this parameter is attached to installs, opens and events until cleared or the app restarts. /// See Facebook's documentation for details on valid parameters @override - void addFacebookPartnerParameter( - {required String key, required String value}) { - messageChannel.invokeMethod( - 'addFacebookPartnerParameter', {'key': key, 'value': value}); + void addFacebookPartnerParameter({required String key, required String value}) { + messageChannel.invokeMethod('addFacebookPartnerParameter', {'key': key, 'value': value}); } /// Clears all Partner Parameters @@ -438,8 +379,7 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { ///Once set, this parameter is attached to installs, opens and events until cleared or the app restarts. @override void addSnapPartnerParameter({required String key, required String value}) { - messageChannel - .invokeMethod('addSnapPartnerParameter', {'key': key, 'value': value}); + messageChannel.invokeMethod('addSnapPartnerParameter', {'key': key, 'value': value}); } /// Sets the value of parameters required by Google Conversion APIs for DMA Compliance in EEA region. @@ -448,13 +388,41 @@ class FlutterBranchSdkMethodChannel implements FlutterBranchSdkPlatform { /// [adUserDataUsageConsent] `true If User has granted/denied consent for 3P transmission of user level data for ads. @override void setDMAParamsForEEA( - {required bool eeaRegion, - required bool adPersonalizationConsent, - required bool adUserDataUsageConsent}) { + {required bool eeaRegion, required bool adPersonalizationConsent, required bool adUserDataUsageConsent}) { messageChannel.invokeMethod('setDMAParamsForEEA', { 'eeaRegion': eeaRegion, 'adPersonalizationConsent': adPersonalizationConsent, 'adUserDataUsageConsent': adUserDataUsageConsent }); } + + /// Sets the consumer protection attribution level. + @override + void setConsumerProtectionAttributionLevel(BranchAttributionLevel branchAttributionLevel) { + messageChannel.invokeMethod('setConsumerProtectionAttributionLevel', + {'branchAttributionLevel': getBranchAttributionLevelString(branchAttributionLevel)}); + } + + /// Sets a custom Meta Anon ID for the current user. + /// [anonID] The custom Meta Anon ID to be used by Branch. + /// Only for iOS. + @override + void setAnonID(String anonId) { + if (!Platform.isIOS) { + return; + } + messageChannel.invokeMethod('setAnonID', {'anonId': anonId}); + } + + /// Set the SDK wait time for third party APIs (for fetching ODM info and Apple Attribution Token) to finish + /// This timeout should be > 0 and <= 10 seconds. + /// [waitTime] Number of seconds before third party API calls are considered timed out. Default is 0.5 seconds (500ms). + /// Only for iOS. + @override + void setSDKWaitTimeForThirdPartyAPIs(double waitTime) { + if (!Platform.isIOS) { + return; + } + messageChannel.invokeMethod('setSDKWaitTimeForThirdPartyAPIs', {'waitTime': waitTime}); + } } diff --git a/lib/src/flutter_branch_sdk_platform_interface.dart b/lib/src/flutter_branch_sdk_platform_interface.dart index b970a950..36319b36 100644 --- a/lib/src/flutter_branch_sdk_platform_interface.dart +++ b/lib/src/flutter_branch_sdk_platform_interface.dart @@ -4,6 +4,7 @@ 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_attribution_level.dart'; import 'objects/branch_universal_object.dart'; abstract class FlutterBranchSdkPlatform extends PlatformInterface { @@ -27,11 +28,21 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { _instance = instance; } - ///Initialize Branch SDK - /// [enableLogging] - Sets `true` turn on debug logging - /// [disableTracking] - Sets `true` to disable tracking in Branch SDK for GDPR compliant on start. After having consent, sets `false` - Future init( - {bool enableLogging = false, bool disableTracking = false}) async { + /// Initializes the Branch SDK. + /// + /// This function initializes the Branch SDK with the specified configuration options. + /// + /// **Parameters:** + /// + /// - [enableLogging]: Whether to enable detailed logging. Defaults to `false`. + /// - [branchAttributionLevel]: The level of attribution data to collect. + /// - `BranchAttributionLevel.FULL`: Full Attribution (Default) + /// - `BranchAttributionLevel.REDUCE`: Reduced Attribution (Non-Ads + Privacy Frameworks) + /// - `BranchAttributionLevel.MINIMAL`: Minimal Attribution - Analytics Only + /// - `BranchAttributionLevel.NONE`: No Attribution - No Analytics (GDPR, CCPA) + /// + + Future init({bool enableLogging = false, BranchAttributionLevel? branchAttributionLevel}) async { throw UnimplementedError('init has not been implemented'); } @@ -52,25 +63,12 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { ///Returns the last parameters associated with the link that referred the user Future> getLatestReferringParams() async { - throw UnimplementedError( - 'getLatestReferringParams has not been implemented'); + 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'); - } - - ///Listen click em Branch Deeplinks - Stream> initSession() { - throw UnimplementedError('initSession has not been implemented'); + throw UnimplementedError('getFirstReferringParams has not been implemented'); } ///Listen click em Branch Deeplinks @@ -86,8 +84,7 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { ///Creates a short url for the BUO Future getShortUrl( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties}) async { + {required BranchUniversalObject buo, required BranchLinkProperties linkProperties}) async { throw UnimplementedError('getShortUrl has not been implemented'); } @@ -102,9 +99,7 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { } ///Logs this BranchEvent to Branch for tracking and analytics - void trackContent( - {required List buo, - required BranchEvent branchEvent}) { + void trackContent({required List buo, required BranchEvent branchEvent}) { throw UnimplementedError('trackContent has not been implemented'); } @@ -120,18 +115,14 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { ///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 { + 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 { + Future removeFromSearch({required BranchUniversalObject buo, BranchLinkProperties? linkProperties}) async { throw UnimplementedError('removeFromSearch has not been implemented'); } @@ -145,22 +136,19 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { /// request AppTracking Autorization and return AppTrackingStatus /// on Android returns notSupported Future requestTrackingAuthorization() async { - throw UnimplementedError( - 'requestTrackingAuthorization has not been implemented'); + throw UnimplementedError('requestTrackingAuthorization has not been implemented'); } /// return AppTrackingStatus /// on Android returns notSupported Future getTrackingAuthorizationStatus() async { - throw UnimplementedError( - 'getTrackingAuthorizationStatus has not been implemented'); + 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'); + throw UnimplementedError('getAdvertisingIdentifier has not been implemented'); } ///Sets the duration in milliseconds that the system should wait for initializing @@ -193,10 +181,8 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { } ///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'); + Future getLastAttributedTouchData({int? attributionWindow}) async { + throw UnimplementedError('getLastAttributedTouchData has not been implemented'); } ///Creates a Branch QR Code image. Returns the QR code as Uint8List. @@ -216,17 +202,16 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { } ///Showing a Share Sheet with LPLinkMetadata in iOS - void shareWithLPLinkMetadata( + Future shareWithLPLinkMetadata( {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required Uint8List icon, - required String title}) { - throw UnimplementedError( - 'shareWithLPLinkMetadata has not been implemented'); + required String title}) async { + throw UnimplementedError('shareWithLPLinkMetadata has not been implemented'); } ///Have Branch end the current deep link session and start a new session with the provided URL. - void handleDeepLink(String url) async { + Future handleDeepLink(String url) async { throw UnimplementedError('handleDeepLink has not been implemented'); } @@ -243,10 +228,8 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { /// Add a Partner Parameter for Facebook. /// Once set, this parameter is attached to installs, opens and events until cleared or the app restarts. /// See Facebook's documentation for details on valid parameters - void addFacebookPartnerParameter( - {required String key, required String value}) { - throw UnimplementedError( - 'addFacebookPartnerParameter has not been implemented'); + void addFacebookPartnerParameter({required String key, required String value}) { + throw UnimplementedError('addFacebookPartnerParameter has not been implemented'); } ///Clears all Partner Parameters @@ -257,8 +240,7 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { ///Add a Partner Parameter for Snap. ///Once set, this parameter is attached to installs, opens and events until cleared or the app restarts. void addSnapPartnerParameter({required String key, required String value}) { - throw UnimplementedError( - 'addSnapPartnerParameter has not been implemented'); + throw UnimplementedError('addSnapPartnerParameter has not been implemented'); } /// Sets the value of parameters required by Google Conversion APIs for DMA Compliance in EEA region. @@ -266,9 +248,25 @@ abstract class FlutterBranchSdkPlatform extends PlatformInterface { /// [adPersonalizationConsent] `true` If End user has granted/denied ads personalization consent. /// [adUserDataUsageConsent] `true If User has granted/denied consent for 3P transmission of user level data for ads. void setDMAParamsForEEA( - {required bool eeaRegion, - required bool adPersonalizationConsent, - required bool adUserDataUsageConsent}) { + {required bool eeaRegion, required bool adPersonalizationConsent, required bool adUserDataUsageConsent}) { throw UnimplementedError('setDMAParamsForEEA has not been implemented'); } + + /// Sets the consumer protection attribution level. + void setConsumerProtectionAttributionLevel(BranchAttributionLevel branchAttributionLevel) { + throw UnimplementedError('setConsumerProtectionAttributionLevel has not been implemented'); + } + + /// Sets a custom Meta Anon ID for the current user. + /// [anonID] The custom Meta Anon ID to be used by Branch. + void setAnonID(String anonId) { + throw UnimplementedError('setAnonID has not been implemented'); + } + + /// Set the SDK wait time for third party APIs (for fetching ODM info and Apple Attribution Token) to finish + /// This timeout should be > 0 and <= 10 seconds. + /// [waitTime] Number of seconds before third party API calls are considered timed out. Default is 0.5 seconds (500ms). + void setSDKWaitTimeForThirdPartyAPIs(double waitTime) { + throw UnimplementedError('setSDKWaitTimeForThirdPartyAPIs has not been implemented'); + } } diff --git a/lib/src/flutter_branch_sdk_web.dart b/lib/src/flutter_branch_sdk_web.dart index 95abd6df..46516d68 100644 --- a/lib/src/flutter_branch_sdk_web.dart +++ b/lib/src/flutter_branch_sdk_web.dart @@ -1,11 +1,11 @@ -// In order to *not* need this ignore, consider extracting the "web" version +// 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' as js; -import 'dart:js_util'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe' as js; import 'dart:typed_data'; import 'package:flutter/widgets.dart'; @@ -13,13 +13,14 @@ 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_attribution_level.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 _jsObjectToDartObject(JSAny data) => json.decode(jsonStringify(data)); -dynamic _dartObjectToJsObject(data) => jsonParse(json.encode(data)); +JSAny _dartObjectToJsObject(Map data) => jsonParse(json.encode(data)); /// A web implementation of the FlutterBranchSdkPlatform of the FlutterBranchSdk plugin. class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { @@ -30,51 +31,39 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { FlutterBranchSdkPlatform.instance = FlutterBranchSdkWeb(); } - ///Initialize Branch SDK - /// [useTestKey] - Sets `true` to use the test `key_test_... - /// [enableLogging] - Sets `true` turn on debug logging - /// [disableTracking] - Sets `true` to disable tracking in Branch SDK for GDPR compliant on start. After having consent, sets `false` + /// Initializes the Branch SDK. + /// + /// This function initializes the Branch SDK with the specified configuration options. + /// + /// **Parameters:** + /// + /// - [enableLogging]: Whether to enable detailed logging. Defaults to `false`. + /// - [branchAttributionLevel]: The level of attribution data to collect. + /// - `BranchAttributionLevel.FULL`: Full Attribution (Default) + /// - `BranchAttributionLevel.REDUCE`: Reduced Attribution (Non-Ads + Privacy Frameworks) + /// - `BranchAttributionLevel.MINIMAL`: Minimal Attribution - Analytics Only + /// - `BranchAttributionLevel.NONE`: No Attribution - No Analytics (GDPR, CCPA) + /// + @override - Future init( - {bool useTestKey = false, - bool enableLogging = false, - bool disableTracking = false}) async { - debugPrint(''); + Future init({bool enableLogging = false, BranchAttributionLevel? branchAttributionLevel}) async { + debugPrint('For web, start the SDK in index.html'); } - static final StreamController> _initSessionStream = - StreamController>(); + static final StreamController> _initSessionStream = StreamController>(); static bool _userIdentified = false; static bool isInitialized = false; - ///Listen click em Branch DeepLinks - @Deprecated('Use `listSession') - @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; - } - ///Listen click em Branch Deeplinks @override Stream> listSession() { getLatestReferringParams().then((data) { if (data.isNotEmpty) { - _initSessionStream.sink - .add(data.map((key, value) => MapEntry('$key', value))); + _initSessionStream.sink.add(data.map((key, value) => MapEntry('$key', value))); } else { _initSessionStream.sink.add({}); } }); - return _initSessionStream.stream; } @@ -84,11 +73,10 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { final Completer> response = Completer(); try { - BranchJS.data(js.allowInterop((err, data) { + BranchJS.data((JSAny? err, JSAny? data) { if (err == null) { if (data != null) { - var responseData = - Map.from(_jsObjectToDartObject(data)); + final responseData = Map.from(_jsObjectToDartObject(data)); response.complete(responseData['data_parsed'] ?? {}); } else { response.complete({}); @@ -96,7 +84,7 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { } else { response.completeError(err); } - })); + }.toJS); } catch (e) { debugPrint('getLatestReferringParams() error: ${e.toString()}'); response.completeError(e); @@ -107,15 +95,13 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { ///Returns the first parameters associated with the link that referred the user @override Future> getFirstReferringParams() { - final Completer> response = - Completer>(); + final Completer> response = Completer>(); try { - BranchJS.first(js.allowInterop((err, data) { + BranchJS.first((JSAny? err, JSAny? data) { if (err == null) { if (data != null) { - var responseData = - Map.from(_jsObjectToDartObject(data)); + final responseData = Map.from(_jsObjectToDartObject(data)); response.complete(responseData['data_parsed'] ?? {}); } else { response.complete({}); @@ -123,7 +109,7 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { } else { response.completeError(err); } - })); + }.toJS); } catch (e) { debugPrint('getFirstReferringParams() error: ${e.toString()}'); response.completeError(e); @@ -135,11 +121,13 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { @override void setIdentity(String userId) { try { - BranchJS.setIdentity(userId, js.allowInterop((error, data) { - if (error == null) { - _userIdentified = true; - } - })); + BranchJS.setIdentity( + userId, + (JSAny? error, JSAny? data) { + if (error == null) { + _userIdentified = true; + } + }.toJS); } catch (e) { debugPrint('setIdentity() error: ${e.toString()}'); } @@ -149,55 +137,48 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { @override void logout() { try { - BranchJS.logout(js.allowInterop((error) { + BranchJS.logout((JSAny? error) { if (error == null) { _userIdentified = false; } - })); + }.toJS); } catch (e) { debugPrint('logout() error: ${e.toString()}'); } } - ///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.toString()}'); - } - } - ///Creates a short url for the BUO @override Future getShortUrl( - {required BranchUniversalObject buo, - required BranchLinkProperties linkProperties}) async { - Map data = buo.toMap(); + {required BranchUniversalObject buo, required BranchLinkProperties linkProperties}) async { + final Map data = buo.toMap(); linkProperties.getControlParams().forEach((key, value) { data[key] = value; }); - Map linkData = {...linkProperties.toMap(), 'data': data}; - - Completer responseCompleter = Completer(); + final Map linkData = {...linkProperties.toMap(), 'data': data}; + final Completer responseCompleter = Completer(); try { - BranchJS.link(_dartObjectToJsObject(linkData), - js.allowInterop((err, url) { - if (err == null) { - responseCompleter.complete(BranchResponse.success(result: url)); - } else { - responseCompleter.completeError( - BranchResponse.error(errorCode: '-1', errorMessage: err)); - } - })); + BranchJS.link( + _dartObjectToJsObject(linkData), + (JSAny? err, String url) { + if (err == null) { + responseCompleter.complete(BranchResponse.success(result: url)); + } else { + final dynamic jsError = err; + String errorMessage; + if (jsError is String) { + errorMessage = jsError; + } else { + errorMessage = jsError.toString(); // Como fallback, converte para string + } + responseCompleter.complete(BranchResponse.error(errorCode: '-1', errorMessage: errorMessage)); + } + }.toJS); } catch (e) { debugPrint('getShortUrl() error: ${e.toString()}'); - responseCompleter.completeError(BranchResponse.error( - errorCode: '-1', errorMessage: 'getShortUrl() error')); + responseCompleter.completeError(BranchResponse.error(errorCode: '-1', errorMessage: 'getShortUrl() error')); } return responseCompleter.future; } @@ -210,15 +191,11 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { required String messageText, String androidMessageTitle = '', String androidSharingTitle = ''}) async { - BranchResponse response = - await getShortUrl(buo: buo, linkProperties: linkProperties); + final BranchResponse response = await getShortUrl(buo: buo, linkProperties: linkProperties); if (response.success) { try { - await promiseToFuture(navigatorShare(_dartObjectToJsObject({ - "title": messageText, - "text": buo.title, - "url": response.result - }))); + await navigatorShare(_dartObjectToJsObject({'title': messageText, 'text': buo.title, 'url': response.result})) + .toDart; } catch (e) { browserPrompt(messageText, response.result); } @@ -228,24 +205,18 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { ///Logs this BranchEvent to Branch for tracking and analytics @override - void trackContent( - {required List buo, - required BranchEvent branchEvent}) { - List contentItems = []; - for (var element in buo) { + void trackContent({required List buo, required BranchEvent branchEvent}) { + final List contentItems = []; + for (final element in buo) { contentItems.add(_dartObjectToJsObject(element.toMap())); } try { if (branchEvent.alias.isNotEmpty) { BranchJS.logEvent( - branchEvent.eventName, - _dartObjectToJsObject(branchEvent.toMap()), - contentItems, - branchEvent.alias); + branchEvent.eventName, _dartObjectToJsObject(branchEvent.toMap()), contentItems.toJS, branchEvent.alias); } else { - BranchJS.logEvent(branchEvent.eventName, - _dartObjectToJsObject(branchEvent.toMap()), contentItems); + BranchJS.logEvent(branchEvent.eventName, _dartObjectToJsObject(branchEvent.toMap()), contentItems.toJS); } } catch (e) { debugPrint('trackContent() error: ${e.toString()}'); @@ -256,8 +227,7 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { @override void trackContentWithoutBuo({required BranchEvent branchEvent}) { try { - BranchJS.logEvent( - branchEvent.eventName, _dartObjectToJsObject(branchEvent.toMap())); + BranchJS.logEvent(branchEvent.eventName, _dartObjectToJsObject(branchEvent.toMap())); } catch (e) { debugPrint('trackContentWithoutBuo() error: ${e.toString()}'); } @@ -266,36 +236,40 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { ///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); + try { + final 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); + } catch (e) { + debugPrint('registerView() error: ${e.toString()}'); + } } ///Add key value pairs to all requests @override void setRequestMetadata(String key, String value) { - BranchJS.setRequestMetadata(key, value); + try { + BranchJS.setRequestMetadata(key, value); + } catch (e) { + debugPrint('setRequestMetadata() error: ${e.toString()}'); + } } ///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'); + Future listOnSearch({required BranchUniversalObject buo, BranchLinkProperties? linkProperties}) async { + debugPrint('listOnSearch() Not supported by Branch JS SDK'); + return true; } ///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'); + Future removeFromSearch({required BranchUniversalObject buo, BranchLinkProperties? linkProperties}) async { + debugPrint('removeFromSearch() Not supported by Branch JS SDK'); + return true; } ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. @@ -311,40 +285,38 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { /// on Android returns notSupported @override Future requestTrackingAuthorization() async { - throw UnsupportedError( - 'requestTrackingAuthorization() Not available in Branch JS SDK'); + debugPrint('requestTrackingAuthorization() Not supported by Branch JS SDK'); + return AppTrackingStatus.notSupported; } /// return AppTrackingStatus /// on Android returns notSupported @override Future getTrackingAuthorizationStatus() async { - throw UnsupportedError( - 'getTrackingAuthorizationStatus() Not available in Branch JS SDK'); + debugPrint('getTrackingAuthorizationStatus() Not supported by Branch JS SDK'); + return AppTrackingStatus.notSupported; } /// return advertising identifier (ie tracking data). /// on Android returns empty string @override Future getAdvertisingIdentifier() async { - throw UnsupportedError( - 'getAdvertisingIdentifier() Not available in Branch JS SDK'); + debugPrint('getAdvertisingIdentifier() Not supported by Branch JS SDK'); + return ''; } ///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'); + 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'); + debugPrint('setConnectTimeout() Not supported by Branch JS SDK'); } ///Sets the duration in milliseconds that the system should wait for a response @@ -353,7 +325,7 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { ///retries as set in setRetryCount(int). @override void setTimeout(int timeout) { - throw UnsupportedError('setTimeout() Not available in Branch JS SDK'); + debugPrint('setTimeout() Not supported by Branch JS SDK'); } ///Sets the max number of times to re-attempt a timed-out request to the Branch API, before @@ -363,41 +335,39 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { /// determine if the max retry count will be attempted. @override void setRetryCount(int retryCount) { - throw UnsupportedError('setRetryCount() Not available in Branch JS SDK'); + debugPrint('setRetryCount() Not supported by 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'); + debugPrint('setRetryInterval() Not supported by 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(); + Future getLastAttributedTouchData({int? attributionWindow}) async { + final Completer responseCompleter = Completer(); try { - BranchJS.lastAttributedTouchData(attributionWindow, - js.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())); - } - })); + BranchJS.lastAttributedTouchData( + attributionWindow?.toJS, + (JSAny? err, JSAny? 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())); + } + }.toJS); } catch (e) { debugPrint('getLastAttributedTouchData() error: ${e.toString()}'); - responseCompleter.complete(BranchResponse.error( - errorCode: '-1', errorMessage: 'getLastAttributedTouchData() error')); + responseCompleter + .complete(BranchResponse.error(errorCode: '-1', errorMessage: 'getLastAttributedTouchData() error')); } return responseCompleter.future; } @@ -408,35 +378,33 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required BranchQrCode qrCodeSettings}) async { - Completer responseCompleter = Completer(); + final Completer responseCompleter = Completer(); - Map data = buo.toMap(); + final Map data = buo.toMap(); linkProperties.getControlParams().forEach((key, value) { data[key] = value; }); - Map linkData = {...linkProperties.toMap(), 'data': data}; + final Map linkData = {...linkProperties.toMap(), 'data': data}; try { - BranchJS.qrCode(_dartObjectToJsObject(linkData), + BranchJS.qrCode( + _dartObjectToJsObject(linkData), _dartObjectToJsObject(qrCodeSettings.toMap()), - js.allowInterop((err, qrCode) { - if (err == null) { - if (qrCode != null) { - responseCompleter.complete( - BranchResponse.success(result: qrCode.rawBuffer.asUint8List())); - } else { - responseCompleter.complete(BranchResponse.error( - errorCode: '-1', errorMessage: 'Qrcode generate error')); - } - } else { - responseCompleter.complete(BranchResponse.error( - errorCode: '-1', errorMessage: err.toString())); - } - })); + (JSAny? err, QrCodeData? qrCode) { + if (err == null) { + if (qrCode != null) { + responseCompleter.complete(BranchResponse.success(result: qrCode.rawBuffer.toDart.asUint8List())); + } else { + responseCompleter.complete(BranchResponse.error(errorCode: '-1', errorMessage: 'Qrcode generate error')); + } + } else { + responseCompleter.complete(BranchResponse.error(errorCode: '-1', errorMessage: err.toString())); + } + }.toJS); } catch (e) { - responseCompleter.complete(BranchResponse.error( - errorCode: '-1', errorMessage: 'qrCode generate error')); + responseCompleter + .complete(BranchResponse.error(errorCode: '-1', errorMessage: 'qrCode generate error ${e.toString()}')); } return responseCompleter.future; } @@ -448,78 +416,75 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { required BranchLinkProperties linkProperties, required BranchQrCode qrCodeSettings}) async { try { - BranchResponse response = await getQRCodeAsData( - buo: buo, - linkProperties: linkProperties, - qrCodeSettings: qrCodeSettings); + final BranchResponse response = + await getQRCodeAsData(buo: buo, linkProperties: linkProperties, qrCodeSettings: qrCodeSettings); if (response.success) { return BranchResponse.success( result: Image.memory( response.result, )); } else { - return BranchResponse.error( - errorCode: response.errorCode, errorMessage: response.errorMessage); + return BranchResponse.error(errorCode: response.errorCode, errorMessage: response.errorMessage); } - } catch (error) { - return BranchResponse.error( - errorCode: "-1", errorMessage: error.toString()); + } catch (e) { + return BranchResponse.error(errorCode: '-1', errorMessage: 'qrCode generate error ${e.toString()}'); } } @override - void shareWithLPLinkMetadata( + Future shareWithLPLinkMetadata( {required BranchUniversalObject buo, required BranchLinkProperties linkProperties, required Uint8List icon, - required String title}) { - showShareSheet( - buo: buo, linkProperties: linkProperties, messageText: title); + required String title}) async { + try { + showShareSheet(buo: buo, linkProperties: linkProperties, messageText: title); + } catch (error) { + debugPrint('shareWithLPLinkMetadata() error: ${error.toString()}'); + } } ///Have Branch end the current deep link session and start a new session with the provided URL. @override - void handleDeepLink(String url) { - js.context.callMethod('open', [url, '_self']); + Future handleDeepLink(String url) async { + try { + globalContext.callMethodVarArgs('open'.toJS, [url.toJS, '_self'.toJS]); + } catch (e) { + debugPrint('handleDeepLink() error: ${e.toString()}'); + } } /// Add a Partner Parameter for Facebook. /// Once set, this parameter is attached to installs, opens and events until cleared or the app restarts. /// See Facebook's documentation for details on valid parameters @override - void addFacebookPartnerParameter( - {required String key, required String value}) { - throw UnsupportedError( - 'addFacebookPartnerParameter() Not available in Branch JS SDK'); + void addFacebookPartnerParameter({required String key, required String value}) { + debugPrint('addFacebookPartnerParameter() Not supported by Branch JS SDK'); } /// Clears all Partner Parameters @override void clearPartnerParameters() { - throw UnsupportedError( - 'clearPartnerParameters() Not available in Branch JS SDK'); + debugPrint('clearPartnerParameters() Not supported by Branch JS SDK'); } /// Add the pre-install campaign analytics @override void setPreinstallCampaign(String value) { - throw UnsupportedError( - 'setPreinstallCampaign() Not available in Branch JS SDK'); + debugPrint('setPreinstallCampaign() Not supported by Branch JS SDK'); } /// Add the pre-install campaign analytics @override void setPreinstallPartner(String value) { - throw UnsupportedError( - 'setPreinstallPartner() Not available in Branch JS SDK'); + debugPrint('setPreinstallPartner() Not supported by Branch JS SDK'); } ///Add a Partner Parameter for Snap. ///Once set, this parameter is attached to installs, opens and events until cleared or the app restarts. @override void addSnapPartnerParameter({required String key, required String value}) { - throw UnsupportedError( - 'addSnapPartnerParameter() Not available in Branch JS SDK'); + debugPrint('addSnapPartnerParameter() Not supported by Branch JS SDK'); } void close() { @@ -532,10 +497,32 @@ class FlutterBranchSdkWeb extends FlutterBranchSdkPlatform { /// [adUserDataUsageConsent] `true If User has granted/denied consent for 3P transmission of user level data for ads. @override void setDMAParamsForEEA( - {required bool eeaRegion, - required bool adPersonalizationConsent, - required bool adUserDataUsageConsent}) { - BranchJS.setDMAParamsForEEA( - eeaRegion, adPersonalizationConsent, adUserDataUsageConsent); + {required bool eeaRegion, required bool adPersonalizationConsent, required bool adUserDataUsageConsent}) { + try { + BranchJS.setDMAParamsForEEA(eeaRegion, adPersonalizationConsent, adUserDataUsageConsent); + } catch (e) { + debugPrint('handleDeepLink() error: ${e.toString()}'); + } + } + + /// Sets the consumer protection attribution level. + @override + void setConsumerProtectionAttributionLevel(BranchAttributionLevel branchAttributionLevel) { + debugPrint('setConsumerProtectionAttributionLevel() Not supported by Branch JS SDK'); + } + + /// Sets a custom Meta Anon ID for the current user. + /// [anonID] The custom Meta Anon ID to be used by Branch. + @override + void setAnonID(String anonId) { + debugPrint('setAnonID() Not supported by Branch JS SDK'); + } + + /// Set the SDK wait time for third party APIs (for fetching ODM info and Apple Attribution Token) to finish + /// This timeout should be > 0 and <= 10 seconds. + /// [waitTime] Number of seconds before third party API calls are considered timed out. Default is 0.5 seconds (500ms). + @override + void setSDKWaitTimeForThirdPartyAPIs(double waitTime) { + debugPrint('setSDKWaitTimeForThirdPartyAPIs() Not supported by Branch JS SDK'); } } diff --git a/lib/src/objects/branch_attribution_level.dart b/lib/src/objects/branch_attribution_level.dart new file mode 100644 index 00000000..2a3fa8fa --- /dev/null +++ b/lib/src/objects/branch_attribution_level.dart @@ -0,0 +1,38 @@ +enum BranchAttributionLevel { + /// Full Attribution (Default) + /// - Advertising Ids + /// - Device Ids + /// - Local IP + /// - Persisted Non-Aggregate Ids + /// - Persisted Aggregate Ids + /// - Ads Postbacks / Webhooks + /// - Data Integrations Webhooks + /// - SAN Callouts + /// - Privacy Frameworks + /// - Deep Linking + FULL, + + /// Reduced Attribution (Non-Ads + Privacy Frameworks) + /// - Device Ids + /// - Local IP + /// - Data Integrations Webhooks + /// - Privacy Frameworks + /// - Deep Linking + REDUCED, + + /// Minimal Attribution - Analytics Only + /// - Device Ids + /// - Local IP + /// - Data Integrations Webhooks + /// - Deep Linking + MINIMAL, + + /// No Attribution - No Analytics (GDPR, CCPA) + /// - Only Deterministic Deep Linking + /// - Disables all other Branch requests + NONE +} + +String getBranchAttributionLevelString(BranchAttributionLevel branchAttributionLevel) { + return branchAttributionLevel.toString().split('.').last; +} diff --git a/lib/src/objects/branch_event.dart b/lib/src/objects/branch_event.dart index 2a2c190e..b5be1c3a 100644 --- a/lib/src/objects/branch_event.dart +++ b/lib/src/objects/branch_event.dart @@ -83,54 +83,54 @@ class BranchEvent { } Map toMap() { - Map data = {}; + final Map data = {}; if (!kIsWeb) { - data["eventName"] = _eventName; - data["isStandardEvent"] = _isStandardEvent; + data['eventName'] = _eventName; + data['isStandardEvent'] = _isStandardEvent; if (transactionID.isNotEmpty) { - data["transactionID"] = transactionID; + data['transactionID'] = transactionID; } if (currency != null) { - data["currency"] = getCurrencyTypeString(currency!); + data['currency'] = getCurrencyTypeString(currency!); } - if (revenue != -1) data["revenue"] = revenue; - if (shipping != -1) data["shipping"] = shipping; - if (tax != -1) data["tax"] = tax; - if (coupon.isNotEmpty) data["coupon"] = coupon; - if (affiliation.isNotEmpty) data["affiliation"] = affiliation; + if (revenue != -1) data['revenue'] = revenue; + if (shipping != -1) data['shipping'] = shipping; + if (tax != -1) data['tax'] = tax; + if (coupon.isNotEmpty) data['coupon'] = coupon; + if (affiliation.isNotEmpty) data['affiliation'] = affiliation; if (eventDescription.isNotEmpty) { - data["eventDescription"] = eventDescription; + data['eventDescription'] = eventDescription; } if (searchQuery.isNotEmpty) { - data["searchQuery"] = searchQuery; + data['searchQuery'] = searchQuery; } if (adType != null) { - data["adType"] = getBranchEventAdTypeString(adType!); + data['adType'] = getBranchEventAdTypeString(adType!); } - if (_customData.isNotEmpty) data["customData"] = _customData; - if (alias.isNotEmpty) data["alias"] = alias; + if (_customData.isNotEmpty) data['customData'] = _customData; + if (alias.isNotEmpty) data['alias'] = alias; } else { if (_isStandardEvent) { if (transactionID.isNotEmpty) { - data["transactionID"] = transactionID; + data['transactionID'] = transactionID; } if (currency != null) { - data["currency"] = getCurrencyTypeString(currency!); + data['currency'] = getCurrencyTypeString(currency!); } - if (revenue != -1) data["revenue"] = revenue; - if (shipping != -1) data["shipping"] = shipping; - if (tax != -1) data["tax"] = tax; - if (coupon.isNotEmpty) data["coupon"] = coupon; - if (affiliation.isNotEmpty) data["affiliation"] = affiliation; + if (revenue != -1) data['revenue'] = revenue; + if (shipping != -1) data['shipping'] = shipping; + if (tax != -1) data['tax'] = tax; + if (coupon.isNotEmpty) data['coupon'] = coupon; + if (affiliation.isNotEmpty) data['affiliation'] = affiliation; if (eventDescription.isNotEmpty) { - data["eventDescription"] = eventDescription; + data['eventDescription'] = eventDescription; } if (searchQuery.isNotEmpty) { - data["searchQuery"] = searchQuery; + data['searchQuery'] = searchQuery; } if (adType != null) { - data["adType"] = getBranchEventAdTypeString(adType!); + data['adType'] = getBranchEventAdTypeString(adType!); } } _customData.forEach((key, value) { diff --git a/lib/src/objects/branch_qrcode.dart b/lib/src/objects/branch_qrcode.dart index 45a62e0e..372928e2 100644 --- a/lib/src/objects/branch_qrcode.dart +++ b/lib/src/objects/branch_qrcode.dart @@ -29,52 +29,60 @@ class BranchQrCode { this.imageFormat = BranchImageFormat.PNG, this.centerLogoUrl = ''}) { if (centerLogoUrl.isNotEmpty) { - assert(Uri.parse(centerLogoUrl).isAbsolute == true, 'Invalid URL'); + assert(Uri.parse(centerLogoUrl).isAbsolute, 'Invalid URL'); } } Map toMap() { - Map ret = {}; + final Map ret = {}; if (!kIsWeb) { if (primaryColor != null) { - ret["codeColor"] = _colorToHex(primaryColor!); + ret['codeColor'] = _colorToHex(primaryColor!); } if (backgroundColor != null) { - ret["backgroundColor"] = _colorToHex(backgroundColor!); + ret['backgroundColor'] = _colorToHex(backgroundColor!); } if (margin != null) { - ret["margin"] = margin; + ret['margin'] = margin; } if (width != null) { - ret["width"] = width; + ret['width'] = width; } - ret["imageFormat"] = imageFormat.name.toUpperCase(); + ret['imageFormat'] = imageFormat.name.toUpperCase(); if (centerLogoUrl.isNotEmpty) { - ret["centerLogoUrl"] = centerLogoUrl; + ret['centerLogoUrl'] = centerLogoUrl; } } else { if (primaryColor != null) { - ret["code_color"] = _colorToHex(primaryColor!); + ret['code_color'] = _colorToHex(primaryColor!); } if (backgroundColor != null) { - ret["background_color"] = _colorToHex(backgroundColor!); + ret['background_color'] = _colorToHex(backgroundColor!); } if (margin != null) { - ret["margin"] = margin; + ret['margin'] = margin; } if (width != null) { - ret["width"] = width; + ret['width'] = width; } - ret["image_format"] = imageFormat.name.toLowerCase(); + ret['image_format'] = imageFormat.name.toLowerCase(); if (centerLogoUrl.isNotEmpty) { - ret["center_logo_url"] = centerLogoUrl; + ret['center_logo_url'] = centerLogoUrl; } } return ret; } String _colorToHex(Color color) { - return '#${color.value.toRadixString(16).substring(2, 8)}'; + final rInt = (color.r * 255.0).round(); + final gInt = (color.g * 255.0).round(); + final bInt = (color.b * 255.0).round(); + + final r = rInt.toRadixString(16).padLeft(2, '0'); + final g = gInt.toRadixString(16).padLeft(2, '0'); + final b = bInt.toRadixString(16).padLeft(2, '0'); + + return '#$r$g$b'; } } diff --git a/lib/src/objects/branch_universal_object.dart b/lib/src/objects/branch_universal_object.dart index 7bf75421..77987793 100644 --- a/lib/src/objects/branch_universal_object.dart +++ b/lib/src/objects/branch_universal_object.dart @@ -1,5 +1,3 @@ -library flutter_branch_sdk_objects; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -81,68 +79,66 @@ class BranchUniversalObject { } Map toMap() { - Map ret = {}; + final Map ret = {}; if (!kIsWeb) { if (canonicalIdentifier.isNotEmpty) { - ret["canonicalIdentifier"] = canonicalIdentifier; + 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; + 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; + ret['expirationDate'] = expirationDateInMilliSec; } - ret["locallyIndex"] = locallyIndex; - ret["publiclyIndex"] = publiclyIndex; + ret['locallyIndex'] = locallyIndex; + ret['publiclyIndex'] = publiclyIndex; if (contentMetadata != null && contentMetadata!.toMap().isNotEmpty) { - ret["contentMetadata"] = contentMetadata!.toMap(); + ret['contentMetadata'] = contentMetadata!.toMap(); } } else { if (canonicalIdentifier.isNotEmpty) { - ret["\$canonical_identifier"] = canonicalIdentifier; + 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; + 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; + 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() - }; + final Map contentMetadata = {if (this.contentMetadata != null) ...this.contentMetadata!.toMapWeb()}; if (contentMetadata.containsKey('customMetadata')) { - var customMetadata = contentMetadata['customMetadata']; + final customMetadata = contentMetadata['customMetadata']; contentMetadata.remove('customMetadata'); contentMetadata.addAll(customMetadata); ret.addAll(contentMetadata); diff --git a/lib/src/objects/content_meta_data.dart b/lib/src/objects/content_meta_data.dart index e327b551..921bca2c 100644 --- a/lib/src/objects/content_meta_data.dart +++ b/lib/src/objects/content_meta_data.dart @@ -1,15 +1,6 @@ part of 'branch_universal_object.dart'; -enum BranchCondition { - OTHER, - NEW, - GOOD, - FAIR, - POOR, - USED, - REFURBISHED, - EXCELLENT -} +enum BranchCondition { OTHER, NEW, GOOD, FAIR, POOR, USED, REFURBISHED, EXCELLENT } enum BranchCurrencyType { AED, @@ -300,49 +291,47 @@ class BranchContentMetaData { if (productCategory == null) return null; switch (productCategory) { case BranchProductCategory.ANIMALS_AND_PET_SUPPLIES: - return "Animals & Pet Supplies"; + return 'Animals & Pet Supplies'; case BranchProductCategory.APPAREL_AND_ACCESSORIES: - return "Apparel & Accessories"; + return 'Apparel & Accessories'; case BranchProductCategory.ARTS_AND_ENTERTAINMENT: - return "Arts & Entertainment"; + return 'Arts & Entertainment'; case BranchProductCategory.BABY_AND_TODDLER: - return "Baby & Toddler"; + return 'Baby & Toddler'; case BranchProductCategory.BUSINESS_AND_INDUSTRIAL: - return "Business & Industrial"; + return 'Business & Industrial'; case BranchProductCategory.CAMERAS_AND_OPTICS: - return "Cameras & Optics"; + return 'Cameras & Optics'; case BranchProductCategory.ELECTRONICS: - return "Electronics"; + return 'Electronics'; case BranchProductCategory.FOOD_BEVERAGES_AND_TOBACCO: - return "Food, Beverages & Tobacco"; + return 'Food, Beverages & Tobacco'; case BranchProductCategory.FURNITURE: - return "Furniture"; + return 'Furniture'; case BranchProductCategory.HARDWARE: - return "Hardware"; + return 'Hardware'; case BranchProductCategory.HEALTH_AND_BEAUTY: - return "Health & Beauty"; + return 'Health & Beauty'; case BranchProductCategory.HOME_AND_GARDEN: - return "Home & Garden"; + return 'Home & Garden'; case BranchProductCategory.LUGGAGE_AND_BAGS: - return "Luggage & Bags"; + return 'Luggage & Bags'; case BranchProductCategory.MATURE: - return "Mature"; + return 'Mature'; case BranchProductCategory.MEDIA: - return "Media"; + return 'Media'; case BranchProductCategory.OFFICE_SUPPLIES: - return "Office Supplies"; + return 'Office Supplies'; case BranchProductCategory.RELIGIOUS_AND_CEREMONIAL: - return "Religious & Ceremonial"; + return 'Religious & Ceremonial'; case BranchProductCategory.SOFTWARE: - return "Software"; + return 'Software'; case BranchProductCategory.SPORTING_GOODS: - return "Sporting Goods"; + return 'Sporting Goods'; case BranchProductCategory.TOYS_AND_GAMES: - return "Toys & Games"; + return 'Toys & Games'; case BranchProductCategory.VEHICLES_AND_PARTS: - return "Vehicles & Parts"; - default: - return "Home & Garden"; + return 'Vehicles & Parts'; } } @@ -357,12 +346,7 @@ class BranchContentMetaData { return this; } - BranchContentMetaData setAddress( - {String? street, - String? city, - String? region, - String? country, - String? postalCode}) { + 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; @@ -378,80 +362,80 @@ class BranchContentMetaData { } Map toMap() { - Map ret = {}; + final Map ret = {}; if (contentSchema != null) { - ret["content_schema"] = getContentSchemaString(contentSchema); + ret['content_schema'] = getContentSchemaString(contentSchema); } - if (quantity > 0) ret["quantity"] = quantity; - if (price > 0) ret["price"] = price; + if (quantity > 0) ret['quantity'] = quantity; + if (price > 0) ret['price'] = price; if (currencyType != null) { - ret["currency"] = getCurrencyTypeString(currencyType!); + 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 (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); + ret['product_category'] = _getProductCategoryString(productCategory); } - if (productVariant.isNotEmpty) ret["product_variant"] = productVariant; + if (productVariant.isNotEmpty) ret['product_variant'] = productVariant; if (condition != null) { - ret["condition"] = _getProductConditionString(condition); + 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 (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; + 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 (_latitude != null) ret['latitude'] = _latitude; + if (_longitude != null) ret['longitude'] = _longitude; + if (_imageCaptions.isNotEmpty) ret['image_captions'] = _imageCaptions; if (_customMetadata.isNotEmpty) { - ret["customMetadata"] = _customMetadata; + ret['customMetadata'] = _customMetadata; } return ret; } Map toMapWeb() { - Map ret = {}; + final Map ret = {}; if (contentSchema != null) { - ret["\$content_schema"] = getContentSchemaString(contentSchema); + ret['\$content_schema'] = getContentSchemaString(contentSchema); } - if (quantity > 0) ret["\$quantity"] = quantity; - if (price > 0) ret["\$price"] = price; + if (quantity > 0) ret['\$quantity'] = quantity; + if (price > 0) ret['\$price'] = price; if (currencyType != null) { - ret["\$currency"] = getCurrencyTypeString(currencyType!); + 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 (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; + ret['\$product_category'] = productCategory.toString().split('.').last; } - if (productVariant.isNotEmpty) ret["\$product_variant"] = productVariant; + if (productVariant.isNotEmpty) ret['\$product_variant'] = productVariant; if (condition != null) { - ret["\$condition"] = _getProductConditionString(condition); + 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 (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; + ret['\$address_postal_code'] = _addressPostalCode; } - if (_latitude != null) ret["\$latitude"] = _latitude; - if (_longitude != null) ret["\$longitude"] = _longitude; + if (_latitude != null) ret['\$latitude'] = _latitude; + if (_longitude != null) ret['\$longitude'] = _longitude; if (_imageCaptions.isNotEmpty) { - ret["\$image_captions"] = _imageCaptions; + ret['\$image_captions'] = _imageCaptions; } _customMetadata.forEach((key, value) { ret[key] = value; diff --git a/lib/src/objects/content_schema.dart b/lib/src/objects/content_schema.dart index f281dfaa..e45875f5 100644 --- a/lib/src/objects/content_schema.dart +++ b/lib/src/objects/content_schema.dart @@ -29,7 +29,7 @@ enum BranchContentSchema { BranchContentSchema getValueContentSchema(String name) { BranchContentSchema? schema; - for (BranchContentSchema contentSchema in BranchContentSchema.values) { + for (final BranchContentSchema contentSchema in BranchContentSchema.values) { if (contentSchema.toString() == name) { schema = contentSchema; break; diff --git a/lib/src/objects/link_properties.dart b/lib/src/objects/link_properties.dart index 31520f60..00333617 100644 --- a/lib/src/objects/link_properties.dart +++ b/lib/src/objects/link_properties.dart @@ -46,7 +46,7 @@ class BranchLinkProperties { } Map toMap() { - Map ret = {}; + final Map ret = {}; if (tags.isNotEmpty) ret['tags'] = tags; if (feature.isNotEmpty) ret['feature'] = feature; diff --git a/lib/src/web/branch_js.dart b/lib/src/web/branch_js.dart index e9543dea..13dc4019 100644 --- a/lib/src/web/branch_js.dart +++ b/lib/src/web/branch_js.dart @@ -1,31 +1,27 @@ @JS() -library branchjs; - -import 'dart:typed_data'; - -import 'package:js/js.dart'; +import 'dart:js_interop'; @JS('JSON.stringify') -external String jsonStringify(Object obj); +external String jsonStringify(JSAny obj); @JS('JSON.parse') -external dynamic jsonParse(String str); +external JSAny jsonParse(String str); @JS('navigator.share') -external dynamic navigatorShare(Object data); +external JSPromise navigatorShare(JSAny data); @JS('prompt') -external dynamic browserPrompt(String message, [String data]); +external JSAny browserPrompt(String message, [String data]); @JS() @anonymous -class QrCodeData { - external ByteBuffer rawBuffer; - external Function base64(); +extension type QrCodeData._(JSObject _) implements JSObject { + external JSArrayBuffer rawBuffer; + external JSFunction base64(); } @JS('branch') -class BranchJS { +extension type BranchJS._(JSObject _) implements JSObject { /// addListener(event, listener) /// Parameters /// @@ -60,7 +56,7 @@ class BranchJS { /// 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]); + external static void addListener([String event, JSFunction listener]); // No documentation in full reference // @JS('banner') @@ -83,7 +79,7 @@ class BranchJS { /// /// branch.closeJourney(function(err) { console.log(err); }); @JS('closeJourney') - external static void closeJourney([Function callback]); + external static void closeJourney([JSFunction callback]); /// data(callback) /// Parameters @@ -98,7 +94,7 @@ class BranchJS { /// 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]); + external static void data([JSFunction callback]); /// deepview(data, options, callback) /// Parameters @@ -112,13 +108,13 @@ class BranchJS { /// /// 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 + /// 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. + /// 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. /// @@ -154,11 +150,10 @@ class BranchJS { /// Callback Format /// /// callback( - /// "Error message" + /// 'Error message' /// ); @JS('deepview') - external static void deepview(Object data, - [Object options, Function callback]); + external static void deepview(JSAny data, [JSAny options, JSFunction callback]); /// deepviewCta() /// Perform the branch deepview CTA (call to action) on mobile after branch.deepview() call is @@ -213,7 +208,7 @@ class BranchJS { /// 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]); + external static void deepviewCta([JSFunction errorCallback]); /// first(callback) /// Parameters @@ -228,7 +223,7 @@ class BranchJS { /// 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]); + external static void first([JSFunction callback]); // No documentation on reference // @JS('getCode') @@ -270,7 +265,7 @@ class BranchJS { /// 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. + /// 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 @@ -283,7 +278,7 @@ class BranchJS { /// Callback Format /// /// callback( - /// "Error message", + /// '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. @@ -294,8 +289,7 @@ class BranchJS { /// ); /// 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]); + external static void init(String branchKey, [JSAny? options, JSFunction? callback]); /// link(data, callback) /// Parameters @@ -320,28 +314,28 @@ class BranchJS { /// 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. + /// '$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 + /// '$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). + /// '$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( @@ -373,11 +367,11 @@ class BranchJS { /// Callback Format /// /// callback( - /// "Error message", + /// 'Error message', /// 'https://bnc.lt/l/3HZMytU-BW' // Branch deep linking URL /// ); @JS('link') - external static void link(Object data, Function callback); + external static void link(JSAny data, JSFunction callback); /// logout(callback) /// Parameters @@ -394,10 +388,10 @@ class BranchJS { /// Callback Format /// /// callback( - /// "Error message" + /// 'Error message' /// ); @JS('logout') - external static void logout([Function callback]); + external static void logout([JSFunction callback]); /// removeListener(listener) /// Parameters @@ -410,7 +404,7 @@ class BranchJS { /// 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); + external static void removeListener(JSFunction listener); /// sendSMS(phone, linkData, options, callback) /// Parameters @@ -484,10 +478,9 @@ class BranchJS { /// ); /// Callback Format /// - /// callback("Error message"); + /// callback('Error message'); @JS('sendSMS') - external static void sendSMS(String phone, Object linkData, - [Object options, Function callback]); + external static void sendSMS(String phone, JSAny linkData, [JSAny options, JSFunction callback]); /// setBranchViewData(data) /// Parameters @@ -515,7 +508,7 @@ class BranchJS { /// } /// }); @JS('setBranchViewData') - external static void setBranchViewData(Object data); + external static void setBranchViewData(JSAny data); /// setIdentity(identity, callback) /// Parameters @@ -541,7 +534,7 @@ class BranchJS { /// Callback Format /// /// callback( - /// "Error message", + /// '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`. @@ -550,7 +543,7 @@ class BranchJS { /// } /// ); @JS('setIdentity') - external static void setIdentity(String identity, [Function callback]); + external static void setIdentity(String identity, [JSFunction callback]); /// track(event, metadata, callback) /// Parameters @@ -574,10 +567,9 @@ class BranchJS { /// ); /// Callback Format /// - /// callback("Error message"); + /// callback('Error message'); @JS('track') - external static void track(String event, - [Object metadata, Function callback]); + external static void track(String event, [JSAny metadata, JSFunction callback]); /// trackCommerceEvent(event, commerce_data, metadata, callback) /// Parameters @@ -608,20 +600,20 @@ class BranchJS { /// 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} + /// '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" }; + /// var metadata = { 'foo': 'bar' }; /// /// branch.trackCommerceEvent('purchase', commerce_data, metadata, function(err) { /// if(err) { @@ -629,8 +621,7 @@ class BranchJS { /// } /// }); @JS('trackCommerceEvent') - external static void trackCommerceEvent(String name, Object commerceData, - [Object metadata, Function callback]); + external static void trackCommerceEvent(String name, JSAny commerceData, [JSAny metadata, JSFunction callback]); /// logEvent(event, event_data_and_custom_data, content_items, customer_event_alias, callback) /// Parameters @@ -655,7 +646,7 @@ class BranchJS { /// Logging Content Events /// Logging User Lifecycle /// Logging Custom Events - /// Usage for Commerce, Content & User Lifecycle "Standard Events" + /// Usage for Commerce, Content & User Lifecycle 'Standard Events' /// /// branch.logEvent( /// event, @@ -664,7 +655,7 @@ class BranchJS { /// customer_event_alias, /// callback (err) /// ); - /// Usage for "Custom Events" + /// Usage for 'Custom Events' /// /// JavaScript /// JavaScript @@ -681,71 +672,71 @@ class BranchJS { /// /// ### 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" + /// '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"} + /// '$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 + /// '$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"; + /// var customer_event_alias = 'event alias'; /// branch.logEvent( - /// "PURCHASE", + /// 'PURCHASE', /// event_and_custom_data, /// content_items, /// customer_event_alias, @@ -754,10 +745,7 @@ class BranchJS { @JS('logEvent') external static void logEvent(String event, - [Object eventDataAndCustomData, - Object contentItems, - String customerEventAlias, - Function callback]); + [JSAny eventDataAndCustomData, JSArray contentItems, String customerEventAlias, JSFunction callback]); /// disableTracking(disableTracking) /// Parameters @@ -779,7 +767,7 @@ class BranchJS { /// 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]); + external static void disableTracking(bool disableTracking); /// lastAttributedTouchData (number, callback) /// @@ -805,12 +793,11 @@ class BranchJS { /// Callback Format /// /// callback( - /// "Error message", + /// 'Error message', /// '{}' /// ); @JS('lastAttributedTouchData') - external static void lastAttributedTouchData(attributionWindow, - [Function callback]); + external static void lastAttributedTouchData(JSAny? attributionWindow, [JSFunction callback]); /// qrcode(data, callback) /// Parameters @@ -836,28 +823,28 @@ class BranchJS { /// 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. + /// '$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 + /// '$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). + /// '$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( @@ -889,21 +876,19 @@ class BranchJS { /// Callback Format /// /// callback( - /// "Error message", + /// 'Error message', /// 'https://bnc.lt/l/3HZMytU-BW' // Branch deep linking URL /// ); @JS('qrCode') - external static void qrCode(Object qrCodeLinkData, Object qrCodeSettings, - Function(String? err, QrCodeData? qrCode) callback); + external static void qrCode(JSAny qrCodeLinkData, JSAny qrCodeSettings, JSFunction callback); /// Sets the value of parameters required by Google Conversion APIs for DMA Compliance in EEA region. /// [eeaRegion] `true` If European regulations, including the DMA, apply to this user and conversion. /// [adPersonalizationConsent] `true` If End user has granted/denied ads personalization consent. /// [adUserDataUsageConsent] `true If User has granted/denied consent for 3P transmission of user level data for ads. @JS('setDMAParamsForEEA') - external static void setDMAParamsForEEA(bool eeaRegion, - bool adPersonalizationConsent, bool adUserDataUsageConsent); + external static void setDMAParamsForEEA(bool eeaRegion, bool adPersonalizationConsent, bool adUserDataUsageConsent); @JS('setRequestMetadata') external static void setRequestMetadata(String key, String value); diff --git a/pubspec.lock b/pubspec.lock index f3e8675f..ce77adbe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,50 +5,50 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -58,10 +58,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "6.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -72,78 +72,70 @@ packages: description: flutter source: sdk version: "0.0.0" - js: - dependency: "direct main" - description: - name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf - url: "https://pub.dev" - source: hosted - version: "0.7.1" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: name: lints - sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "6.0.0" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" plugin_platform_interface: dependency: "direct main" description: @@ -156,71 +148,71 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.6" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "15.0.0" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6a906155..cf63425d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,47 +1,53 @@ -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: 8.0.4 -homepage: https://github.com/RodrigoSMarques/flutter_branch_sdk - -environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" - -dependencies: - flutter: - sdk: flutter - flutter_web_plugins: - sdk: flutter - plugin_platform_interface: ^2.1.8 - js: ^0.7.1 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^4.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 +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: 8.10.0 +repository: https://github.com/RodrigoSMarques/flutter_branch_sdk + +environment: + sdk: ">=3.3.0 <4.0.0" + flutter: '>=3.19.0' + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + plugin_platform_interface: ^2.1.8 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.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 + +topics: + - deeplink + - app-links + - universal-links + - custom-url-schemes + - web-to-app \ No newline at end of file diff --git a/tool/build-web.sh b/tool/build-web.sh index bd706302..f0d7d87d 100644 --- a/tool/build-web.sh +++ b/tool/build-web.sh @@ -7,4 +7,5 @@ flutter config --no-analytics flutter pub get #flutter build web --source-maps #flutter build web --profile --source-maps --dart-define=Dart2jsOptimization=O0 -flutter build web \ No newline at end of file +#flutter build web +flutter build web --wasm --source-maps \ No newline at end of file