diff --git a/.gitignore b/.gitignore index 86f284979..e765a1228 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,6 @@ lib/basic_api/generated/*.json *.iws .idea/ -.fvm - # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. @@ -32,7 +30,6 @@ coverage/ /packages/**/pubspec_overrides.yaml /packages/**/pubspec.lock - gradlew.bat local.properties gradlew @@ -73,3 +70,6 @@ gradle/ !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages /.idea/ + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/packages/intercom_flutter/.gitignore b/packages/intercom_flutter/.gitignore new file mode 100644 index 000000000..94ca74321 --- /dev/null +++ b/packages/intercom_flutter/.gitignore @@ -0,0 +1 @@ +/intercom_flutter/example/lib/generated_plugin_registrant.dart diff --git a/packages/intercom_flutter/README.md b/packages/intercom_flutter/README.md new file mode 100755 index 000000000..7b48a8619 --- /dev/null +++ b/packages/intercom_flutter/README.md @@ -0,0 +1,212 @@ +# intercom_flutter + +[![Pub](https://img.shields.io/pub/v/intercom_flutter.svg)](https://pub.dev/packages/intercom_flutter) +![CI](https://github.com/v3rm0n/intercom_flutter/workflows/CI/badge.svg) + +Flutter wrapper for Intercom [Android](https://github.com/intercom/intercom-android), [iOS](https://github.com/intercom/intercom-ios), and [Web](https://developers.intercom.com/installing-intercom/docs/basic-javascript) projects. + +- Uses Intercom Android SDK Version `15.11.2`. +- The minimum Android SDK `minSdk` required is 21. +- The compile Android SDK `compileSdk` required is 34. +- Uses Intercom iOS SDK Version `18.2.2`. +- The minimum iOS target version required is 15. +- The Xcode version required is 15. + +## Usage + +Import `package:intercom_flutter/intercom_flutter.dart` and use the methods in `Intercom` class. + +Example: +```dart +import 'package:flutter/material.dart'; +import 'package:intercom_flutter/intercom_flutter.dart'; + +void main() async { + // initialize the flutter binding. + WidgetsFlutterBinding.ensureInitialized(); + // initialize the Intercom. + // make sure to add keys from your Intercom workspace. + // don't forget to set up the custom application class on Android side. + await Intercom.instance.initialize('appIdHere', iosApiKey: 'iosKeyHere', androidApiKey: 'androidKeyHere'); + runApp(App()); +} + +class App extends StatelessWidget { + + @override + Widget build(BuildContext context) { + return FlatButton( + child: Text('Open Intercom'), + onPressed: () async { + // messenger will load the messages only if the user is registered in Intercom. + // either identified or unidentified. + await Intercom.instance.displayMessenger(); + }, + ); + } +} + +``` + +See Intercom [Android](https://developers.intercom.com/installing-intercom/docs/intercom-for-android) and [iOS](https://developers.intercom.com/installing-intercom/docs/intercom-for-ios) package documentation for more information. + +### Android + +Make sure that your app's `MainActivity` extends `FlutterFragmentActivity` (you can check the example). + +Permissions: +```xml + +``` + +Optional permissions: + +```xml + + + +``` + +Enable AndroidX + Jetifier support in your android/gradle.properties file (see example app): + +``` +android.useAndroidX=true +android.enableJetifier=true +``` + +According to the documentation, Intercom must be initialized in the Application onCreate. So follow the below steps to achieve the same: +- Setup custom application class if you don't have any. + - Create a custom `android.app.Application` class named `MyApp`. + - Add an `onCreate()` override. The class should look like this: + ```kotlin + import android.app.Application + + class MyApp: Application() { + + override fun onCreate() { + super.onCreate() + } + } + ``` + - Open your `AndroidManifest.xml` and find the `application` tag. In it, add an `android:name` attribute, and set the value to your class' name, prefixed by a dot (.). + ```xml + + ``` +- Now initialize the Intercom SDK inside the `onCreate()` of custom application class according to the following: +```kotlin +import android.app.Application +import io.maido.intercom.IntercomFlutterPlugin + +class MyApp : Application() { + override fun onCreate() { + super.onCreate() + + // Add this line with your keys + IntercomFlutterPlugin.initSdk(this, appId = "appId", androidApiKey = "androidApiKey") + } +} +``` + +### iOS +Make sure that you have a `NSPhotoLibraryUsageDescription` entry in your `Info.plist`. + +### Push notifications setup +This plugin works in combination with the [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) plugin to receive Push Notifications. To set this up: + +* First, implement [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) +* Then, add the Firebase server key to Intercom, as described [here](https://developers.intercom.com/installing-intercom/docs/android-fcm-push-notifications#section-step-3-add-your-server-key-to-intercom-for-android-settings) (you can skip 1 and 2 as you have probably done them while configuring `firebase_messaging`) +* Follow the steps as described [here](https://developers.intercom.com/installing-intercom/docs/ios-push-notifications) to enable push notification in iOS. +* Starting from Android 13 you may need to ask for notification permissions (as of version 13 `firebase_messaging` should support that) +* Ask FirebaseMessaging for the token that we need to send to Intercom, and give it to Intercom (so Intercom can send push messages to the correct device), please note that in order to receive push notifications in your iOS app, you have to send the APNS token to Intercom. The example below uses [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) to get either the FCM or APNS token based on the platform: + +```dart +final firebaseMessaging = FirebaseMessaging.instance; +final intercomToken = Platform.isIOS ? await firebaseMessaging.getAPNSToken() : await firebaseMessaging.getToken(); + +Intercom.instance.sendTokenToIntercom(intercomToken); +``` + +Now, if either Firebase direct (e.g. by your own backend server) or Intercom sends you a message, it will be delivered to your app. + +### Web +You don't need to do any extra steps for the web. Intercom script will be automatically injected. +But you can pre-define some Intercom settings, if you want (optional). +```html + +``` +#### Following functions are not yet supported on Web: + +- [ ] unreadConversationCount +- [ ] setInAppMessagesVisibility +- [ ] sendTokenToIntercom +- [ ] handlePushMessage +- [ ] isIntercomPush +- [ ] handlePush +- [ ] displayCarousel +- [ ] displayHelpCenterCollections + +## Using Intercom keys with `--dart-define` + +Use `--dart-define` variables to avoid hardcoding Intercom keys. + +### Pass the Intercom keys with `flutter run` or `flutter build` command using `--dart-define`. +```dart +flutter run --dart-define="INTERCOM_APP_ID=appID" --dart-define="INTERCOM_ANDROID_KEY=androidKey" --dart-define="INTERCOM_IOS_KEY=iosKey" +``` +Note: You can also use `--dart-define-from-file` which is introduced in Flutter 3.7. + +### Reading keys in Dart side and initialize the SDK. +```dart +String appId = String.fromEnvironment("INTERCOM_APP_ID", ""); +String androidKey = String.fromEnvironment("INTERCOM_ANDROID_KEY", ""); +String iOSKey = String.fromEnvironment("INTERCOM_IOS_KEY", ""); + +Intercom.instance.initialize(appId, iosApiKey: iOSKey, androidApiKey: androidKey); +``` + +### Reading keys in Android native side and initialize the SDK. + +* Add the following code to `build.gradle`. +``` +def dartEnvironmentVariables = [] +if (project.hasProperty('dart-defines')) { + dartEnvironmentVariables = project.property('dart-defines') + .split(',') + .collectEntries { entry -> + def pair = new String(entry.decodeBase64(), 'UTF-8').split('=') + [(pair.first()): pair.last()] + } +} +``` + +* Place `dartEnvironmentVariables` inside the build config +``` +defaultConfig { + ... + buildConfigField 'String', 'INTERCOM_APP_ID', "\"${dartEnvironmentVariables.INTERCOM_APP_ID}\"" + buildConfigField 'String', 'INTERCOM_ANDROID_KEY', "\"${dartEnvironmentVariables.INTERCOM_ANDROID_KEY}\"" +} +``` + +* Read the build config fields +```kotlin +import android.app.Application +import android.os.Build +import io.maido.intercom.IntercomFlutterPlugin + +class MyApp : Application() { + override fun onCreate() { + super.onCreate() + + // Add this line with your keys + IntercomFlutterPlugin.initSdk(this, + appId = BuildConfig.INTERCOM_APP_ID, + androidApiKey = BuildConfig.INTERCOM_ANDROID_KEY) + } +} +``` diff --git a/packages/intercom_flutter/intercom_flutter/.gitignore b/packages/intercom_flutter/intercom_flutter/.gitignore new file mode 100644 index 000000000..0af474fe6 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ +pubspec.lock + +build/ +.idea/* +!.idea/runConfigurations +android/bin/ +example/.flutter-plugins-dependencies +example/ios/Flutter/flutter_export_environment.sh diff --git a/packages/intercom_flutter/intercom_flutter/CHANGELOG.md b/packages/intercom_flutter/intercom_flutter/CHANGELOG.md new file mode 100755 index 000000000..ab846def0 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/CHANGELOG.md @@ -0,0 +1,549 @@ +# Changelog + +## 9.2.2 + +* Bump Intercom iOS SDK version to 18.2.2 + +## 9.2.1 + +* Bump Intercom Android SDK version to 15.11.2 + +## 9.2.0 + +* Bump Intercom Android SDK version to 15.11.1 +* Bump Intercom iOS SDK version to 18.2.0 +* Added API `isUserLoggedIn`. +* Added API `fetchLoggedInUserAttributes`. +* Fixed [#479](https://github.com/v3rm0n/intercom_flutter/issues/479). +* Fixed [#481](https://github.com/v3rm0n/intercom_flutter/issues/481). + +## 9.1.1 + +* Bump Intercom iOS SDK version to 18.1.0 + +## 9.1.0 + +* Bump Intercom iOS SDK version to 18.0.0 + +## 9.0.11 + +* Removed references to v1 embedding. +* Implemented `displayHome` for all platforms. +* Bump Intercom Android SDK version to 15.10.3 +* Bump Intercom iOS SDK version to 17.4.0 + +## 9.0.10 + +* Bump `intercom_flutter_web` to `1.1.2` supporting `web` `^1.0.0`. + +## 9.0.9 + +* Bump Intercom Android SDK version to 15.10.2 +* Bump Intercom iOS SDK version to 17.3.0 + +## 9.0.8 + +* Bump Intercom Android SDK version to 15.10.1 +* Bump Intercom iOS SDK version to 17.2.1 + +## 9.0.7 + +* Bump Intercom Android SDK version to 15.9.1 +* Bump Intercom iOS SDK version to 17.1.2 + +## 9.0.6 + +* Bump Intercom iOS SDK version to 17.1.1 + +## 9.0.5 + +* Bump Intercom Android SDK version to 15.9.0 +* Bump Intercom iOS SDK version to 17.1.0 + +## 9.0.4 + +* Bump Intercom iOS SDK version to 17.0.4 + +## 9.0.3 + +* Bump Intercom Android SDK version to 15.8.3 +* Bump Intercom iOS SDK version to 17.0.3 + +## 9.0.2 + +* Bump Intercom Android SDK version to 15.8.2 +* Bump Intercom iOS SDK version to 17.0.2 + +## 9.0.1 + +* Bump Intercom Android SDK version to 15.8.1 +* Bump Intercom iOS SDK version to 17.0.1 + +## 9.0.0 + +* Bump Intercom Android SDK version to 15.8.0 +* Bump Intercom iOS SDK version to 17.0.0 (requires minimum iOS 15) + +## 8.1.4 + +* Bump Intercom iOS SDK version to 16.6.2 +> Note: This version is not published on pub.dev. So to use this version: +``` +dependencies: + intercom_flutter: + git: + url: https://github.com/v3rm0n/intercom_flutter + ref: v8.1.4 + path: intercom_flutter +``` + +## 8.1.3 + +* Bump Intercom Android SDK version to 15.7.1 +* Bump Intercom iOS SDK version to 16.6.1 + +## 8.1.2 + +* Bump Intercom Android SDK version to 15.7.0 +* Bump Intercom iOS SDK version to 16.6.0 + +## 8.1.1 + +* Bump Intercom Android SDK version to 15.6.3 +* Bump Intercom iOS SDK version to 16.5.9 + +## 8.1.0 + +* Bump Intercom iOS SDK version to 16.5.6 +* Migrated web to js_interop to be compatible with WASM +* Updated minimum Dart version to 3.2. + +## 8.0.12 + +* Fix never completing future when calling displayMessageComposer on iOS + +## 8.0.11 + +* Automatically Injected Intercom Script, if it is not added. + +## 8.0.10 + +* Bump Intercom iOS SDK version to 16.5.5 +* Bump Intercom Android SDK version to 15.6.2 + +## 8.0.9 + +* Bump Intercom iOS SDK version to 16.5.1 +* Bump Intercom Android SDK version to 15.6.0 + +## 8.0.8 + +* Bump Intercom iOS SDK version to 16.3.2 +* Bump Intercom Android SDK version to 15.5.1 + +## 8.0.7 + +* Added support of AGP 8. + +## 8.0.6 + +* Bump `intercom_flutter_web` to `1.0.1` to support `uuid: ^4.2.1`. +* Bump Intercom iOS SDK version to 16.3.1 + +## 8.0.5 + +* Bump Intercom iOS SDK version to 16.3.0 +* Bump Intercom Android SDK version to 15.5.0 + +## 8.0.4 + +* Bump Intercom iOS SDK version to 16.2.3 +* Bump Intercom Android SDK compile version to 34 + +## 8.0.3 + +* Bump Intercom iOS SDK version to 16.2.1 +* Bump Intercom Android SDK version to 15.4.0 + +## 8.0.2 + +* Bump Intercom iOS SDK version to 16.1.1 +* Bump Intercom Android SDK version to 15.3.0 + +## 8.0.1 + +* Bump Intercom iOS SDK version to 16.0.1 +* Bump Intercom Android SDK version to 15.2.3 + +## 8.0.0 + +* Bump Intercom iOS SDK version to 16.0.0. This supports Xcode 15 and iOS 17. (Note: Xcode 15 is required when using Intercom iOS SDK 16.0.0) +* Removed deprecated methods `registerIdentifiedUser` and `registerUnidentifiedUser`. + +## 7.9.0 + +* Bump Intercom iOS SDK version to [15.2.3](https://github.com/intercom/intercom-ios/releases/tag/15.2.3) +* Bump Intercom Android SDK version to [15.2.2](https://github.com/intercom/intercom-android/releases/tag/15.2.2), requires **Android SDK 34** +* Added documentation about using `FlutterFragmentActivity` instead of `FlutterActivity` in Android. + +## 7.8.5 + +* Bump Intercom iOS SDK version to 15.2.1 +* Bump Intercom Android SDK version to 15.2.0 +* Implemented `displayTickets` for all platforms. + +## 7.8.4 + +* Bump Intercom iOS SDK version to 15.1.4 +* Bump Intercom Android SDK version to 15.1.6 +* Implemented `displayConversation` for all platforms. + +## 7.8.3 + +* Bump Intercom iOS SDK version to 15.1.3 +* Bump Intercom Android SDK version to 15.1.4 + +## 7.8.2 + +* Implemented `isIntercomPush` and `handlePush` in iOS. +* Bump Intercom iOS SDK version to 15.0.3 +* Bump Intercom Android SDK version to 15.1.3 +* Removed **Optimistic operator (~>)** from iOS podspec to use the exact version instead of getting the next *major | minor | patch* version. + +## 7.8.1 + +* Implemented method `displayHelpCenter` for web. +* Bump Intercom iOS SDK version to 15.0.1 +* Bump Intercom Android SDK version to 15.1.1 +* Added readMe section `Using Intercom keys with --dart-define`. (Thanks [@sirTomasson](https://github.com/sirTomasson)) + +## 7.8.0 + +* Bump Intercom iOS SDK version to 15.0.0 +* Bump Intercom Android SDK version to 15.0.0 + +## 7.7.0 + +* Update minimum Dart version to Dart 3. + +## 7.6.9 + +* Bump Intercom iOS SDK version to 14.1.0 +* Bump Intercom Android SDK version to 14.2.0 + +## 7.6.8 + +* Added way to initialize the Intercom SDK in Android application class. + +## 7.6.7 + +* Bump Intercom Android SDK version to 14.1.0 + +## 7.6.6 + +* Bump Intercom Android SDK version to 14.0.6 +* Removed documentation of using `FlutterFragmentActivity` which was added in version 7.6.1 as it is not required now. + +## 7.6.5 + +* Bump Intercom iOS SDK version to 14.0.7 ([#289](https://github.com/v3rm0n/intercom_flutter/pull/289)) +* Bump Intercom Android SDK version to 14.0.5 ([#289](https://github.com/v3rm0n/intercom_flutter/pull/289)) + +## 7.6.4 + +* Added method `displayHelpCenterCollections`. + +## 7.6.3 + +* Added method `displayMessages`. + +## 7.6.2 + +* Bump Intercom iOS SDK version to 14.0.6 ([#280](https://github.com/v3rm0n/intercom_flutter/pull/280)) +* Bump Intercom Android SDK version to 14.0.4 ([#280](https://github.com/v3rm0n/intercom_flutter/pull/280)) + +## 7.6.1 + +* Bump Intercom iOS SDK version to 14.0.2 +* Bump Intercom Android SDK version to 14.0.3 +* Added extra documentation to fix Android exception after background push notification is received ([#270](https://github.com/v3rm0n/intercom_flutter/issues/270#issuecomment-1330510979)) + +## 7.6.0 + +* Bump Intercom iOS SDK version to 14.0.0 ([#269](https://github.com/v3rm0n/intercom_flutter/pull/269)) +* Bump Intercom Android SDK version to 14.0.0 ([#269](https://github.com/v3rm0n/intercom_flutter/pull/269)) + +## 7.5.0 + +* Bump Intercom iOS SDK version to 13.0.0 +* Bump Android `compileSdkVersion` to 33 + +## 7.4.1 + +* Bump Intercom Android SDK version to 12.5.1 [(#261)](https://github.com/v3rm0n/intercom_flutter/pull/261) +* Android 13 support + +## 7.4.0 + +* Bump Intercom Android SDK version to 12.4.3 ([#259](https://github.com/v3rm0n/intercom_flutter/pull/259)) +* Bump Intercom iOS SDK version to 12.4.3 ([#259](https://github.com/v3rm0n/intercom_flutter/pull/259)) + +## 7.3.0 + +* Bump Intercom Android SDK version to 12.4.2 ([#248](https://github.com/v3rm0n/intercom_flutter/pull/248)) +* Bump Intercom iOS SDK version to 12.4.2 ([#248](https://github.com/v3rm0n/intercom_flutter/pull/248)) + +## 7.2.0 + +* Updated dependency `intercom_flutter_platform_interface: ^1.2.0` +* Updated dependency `intercom_flutter_web: ^0.2.0` +* Bump Intercom Android SDK version to 12.2.2 +* Bump Intercom iOS SDK version to 12.2.1 +* Implemented `displaySurvey`. + +## 7.1.0 + +* Implemented `displayArticle` for web ([#231](https://github.com/v3rm0n/intercom_flutter/pull/231)). +* Bump Intercom Android SDK version to 12.1.1 +* Bump Intercom iOS SDK version to 12.1.1 +* Updated dependency `intercom_flutter_platform_interface: ^1.1.0` +* Updated dependency `intercom_flutter_web: ^0.1.0` +* Added method `loginIdentifiedUser` with `IntercomStatusCallback` support. +* Deprecated `registerIdentifiedUser` in favor of `loginIdentifiedUser`. +* Added method `loginUnidentifiedUser` with `IntercomStatusCallback` support. +* Deprecated `registerUnidentifiedUser` in favor of `loginUnidentifiedUser`. +* Added parameter `statusCallback` in `updateUser` to support `IntercomStatusCallback`. +* Renamed the following methods in the MethodChannel: + - `registerIdentifiedUserWithUserId` to `loginIdentifiedUserWithUserId`. + - `regsiterIdentifiedUserWithEmail` to `loginIdentifiedUserWithEmail`. + - `registerUnidentifiedUser` to `loginUnidentifiedUser`. + +## 7.0.0 +> Note: This release has breaking changes. + +* Updated `displayArticle` method documentation. ([#224](https://github.com/v3rm0n/intercom_flutter/pull/224)) +* API methods are now available at instance level instead of static. ([#226](https://github.com/v3rm0n/intercom_flutter/pull/226)) + - Now use `Intercom.instance` instead of just `Intercom`, for e.g: `Intercom.instance.displayMessenger()`. + +## 6.2.0 + +* Bump Intercom Android SDK version to 12.0.0 ([#220](https://github.com/v3rm0n/intercom_flutter/pull/220)) +* Bump Intercom iOS SDK version to 12.0.0 ([#220](https://github.com/v3rm0n/intercom_flutter/pull/220)) + +## 6.1.0 + +* Bump Intercom Android SDK version to 10.7.0 ([#217](https://github.com/v3rm0n/intercom_flutter/pull/217)) +* Bump Intercom iOS SDK version to 11.2.0 ([#217](https://github.com/v3rm0n/intercom_flutter/pull/217)) + +## 6.0.0 +> Note: This release has breaking changes. + +* Bump Intercom Android SDK version to 10.6.1 ([#204](https://github.com/v3rm0n/intercom_flutter/pull/204)) +* Bump Intercom iOS SDK version to 11.0.1 ([#204](https://github.com/v3rm0n/intercom_flutter/pull/204)) +* Resolved issue [#151](https://github.com/v3rm0n/intercom_flutter/issues/151) +* Changed Android push intercepting technique ([#192](https://github.com/v3rm0n/intercom_flutter/pull/192)) +* Updated README ([#205](https://github.com/v3rm0n/intercom_flutter/pull/205)) +* **BREAKING** + - Intercom iOS SDK v11 requires minimum deployment target version 13. So iOS minimum version is updated from 10 to 13. See https://github.com/intercom/intercom-ios/blob/master/CHANGELOG.md#1100 + - The service `io.maido.intercom.PushInterceptService` is deleted. Now plugin itself will handle the push messages using the new added receiver `io.maido.intercom.PushInterceptReceiver`. + - remove the service `io.maido.intercom.PushInterceptService`, if you have, from your `AndroidManifest.xml`. + - remove the code to handle the background Intercom push from your `firebase_messaging` background handler. Now it is not required to handle manually. + +## 5.3.0 +* Added API documentation ([#194](https://github.com/v3rm0n/intercom_flutter/pull/194)) +* Bump Intercom Android SDK version to 10.6.0 ([#195](https://github.com/v3rm0n/intercom_flutter/pull/195)) +* Bump Intercom iOS SDK version to 10.4.0 ([#195](https://github.com/v3rm0n/intercom_flutter/pull/195)) +* Bump android kotlin version to 1.5.30 ([#196](https://github.com/v3rm0n/intercom_flutter/pull/196)) +* Bump android `com.android.tools.build:gradle` to 7.0.4 ([#196](https://github.com/v3rm0n/intercom_flutter/pull/196)) +* Bump android `compileSdkVersion` to 31 ([#196](https://github.com/v3rm0n/intercom_flutter/pull/196)) +* Bump android `gradle` plugin version to 7.3.3 ([#196](https://github.com/v3rm0n/intercom_flutter/pull/196)) +* Updated README ([#197](https://github.com/v3rm0n/intercom_flutter/pull/197)) +* Updated dependency `intercom_flutter_platform_interface: ^1.0.1` +* Updated dependency `intercom_flutter_web: ^0.0.4` + +## 5.2.0 +* Bump Intercom Android SDK version to 10.4.2 ([#187](https://github.com/v3rm0n/intercom_flutter/pull/187)) +* Bump Intercom iOS SDK version to 10.3.4 ([#187](https://github.com/v3rm0n/intercom_flutter/pull/187)) + +## 5.1.0+1 +* Resolved issue [#181](https://github.com/v3rm0n/intercom_flutter/issues/181) + +## 5.1.0 +* Bump Intercom Android SDK version to 10.4.0 ([#178](https://github.com/v3rm0n/intercom_flutter/pull/178)) +* Bump Intercom iOS SDK version to 10.3.0 ([#178](https://github.com/v3rm0n/intercom_flutter/pull/178)) + +## 5.0.3 +* Resolved issue [#173](https://github.com/v3rm0n/intercom_flutter/issues/173) + +## 5.0.2 +* Updated README: Removed the `
` tag that was being shown on the pub.dev. +* Updated intercom_flutter pod version to `5.0.0`. + +## 5.0.1 +* Clear warning `PushInterceptService.java uses unchecked or unsafe operations.` + +## 5.0.0 +* Added web support +* Bump Intercom iOS SDK version to 10.0.2 + - this will solve the displayArticle issue. See https://github.com/intercom/intercom-ios/blob/master/CHANGELOG.md#1002 + +## 4.0.0 +* Bump Intercom Android SDK version to 10.0.0 +* Bump Intercom iOS SDK version to 10.0.0 +* Adjustment to encode the iOS device token with HexString. +* Added support for displayCarousel +* Added support for displayArticle + - Note: Intercom iOS SDK has an issue with displayArticle if your Intercom account does have that feature enabled. It crashes the app. The bug is already reported at https://forum.intercom.com/s/question/0D52G000050ZFNoSAO/intercom-display-article-crash-on-ios. As per the conversation with Intercom support, they are working on the issue. The fix may take some time. +* Internal Changes: + - used `hideIntercom()` as `hideMessenger()` is deprecated and removed in Intercom SDK 10.0.0 + - Android - updated gradle version and dependencies. + +## 3.2.1 +* Fix `application has not been initialized` crash on Android when calling from background isolate. + +## 3.2.0 +* Migrate to use intercom_flutter_platform_interface + +## 3.1.0 +* Added support for language_override + +## 3.0.0 +* Migrate to null-safety + +## 2.3.4 +* Added support for setting bottom padding + +## 2.3.3 +* Added signedUpAt user attribute + +## 2.3.2 +* Fix crash if app is closed before fully initialised + +## 2.3.1 +* Fix Android build issue +* Updated Android dependencies + +## 2.3.0 +* Migrate Android side to Flutter's v2 Android Plugin APIs + +## 2.2.1 +* Implement sendTokenToIntercom method on iOS side to support push notifications + +## 2.2.0+1 +* Fix project dependencies + +## 2.2.0 +* Added unread messages count listener + +## 2.1.1 +* Fix incremental installation error + +## 2.1.0 +* Bump Intercom SDK version to 6.0.0 (thanks @marbarroso) +* Bump minimum Android supported version to Lollipop (API 21) +* Bump minimum iOS supported version to iOS 10.0 + +## 2.0.7 +* Fixed background notifications being swallowed by intercom_flutter in Android (thanks @LinusU) + +## 2.0.6 +* Added hideMessenger (thanks @Spikes042) + +## 2.0.5+2 +* Fix iOS build error + +## 2.0.5+1 +* Fix example project dependencies + +## 2.0.5 +* Add displayMessageComposer (thanks @knaeckeKami) +* Add support for Android 10 +* Add support for iOS 13 + +## 2.0.4 +* Support for push notifications + +## 2.0.3 +* Upgraded Intercom SDK to 5.3 +* Upgraded Kotlin, Android Studio, Gradle and CocoaPods to latest version +* Upgraded minimum Flutter version to `1.0.0` +* Upgraded minimum Dart version to `2.0.0` +* Fixed iOS warning + +## 2.0.2 +* Added logEvent method (thanks @MrAlek) +* Fixed registerIdentifiedUser (thanks @Spikes042) + +## 2.0.1 +* Added argument validation to registerIdentifiedUser (thanks @Zazo032) + +## 2.0.0 +* Changed message channel name +* Added email to user registration + +## 1.0.12 +* Added setUserHash (thanks @Spikes042) + +## 1.0.11 +* Added unreadConversationCount and setInAppMessagesVisible +* Migrated to AndroidX (thanks @LeonidVeremchuk and @Zazo032) + +## 1.0.10 +* Updated author + +## 1.0.9 +* Added support for companies +* Added support for custom attributes + +## 1.0.8 +* Fixed issues with nullability in Intercom Android SDK + +## 1.0.7 +* Added Help Center support + +## 1.0.6 + +* Fixed null check in ObjectiveC + +## 1.0.5 + +* Fixed ObjectiveC warnings + +## 1.0.4 + +* Converter Swift code to ObjectiveC + +## 1.0.3 + +* Updated iOS project to Swift 4.2 + +## 1.0.2 + +* Fixed plugin name in all places + +## 1.0.1 + +* Fixed ios headers + +## 1.0.0 + +* Added user attributes (name, email, phone, userId and company) +* Renamed package to `intercom_flutter` because of the name clash with Intercom pod + +## 0.0.4 + +* Fixed pod name in podspec + +## 0.0.3 + +* Added example project +* Formatted code +* Added test + +## 0.0.2 + +* Changed minimum SDK version to `2.0.0-dev.28.0` + +## 0.0.1 + +* Implemented `initialize`, `registerIdentifiedUser`, `registerUnidentifiedUser`, `logout`, `setLauncherVisibility`, `displayMessenger` on both Android and iOS diff --git a/packages/intercom_flutter/intercom_flutter/LICENSE b/packages/intercom_flutter/intercom_flutter/LICENSE new file mode 100644 index 000000000..fface4a6c --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 xChange OÜ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/intercom_flutter/intercom_flutter/analysis_options.yml b/packages/intercom_flutter/intercom_flutter/analysis_options.yml new file mode 100644 index 000000000..4135297cc --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/analysis_options.yml @@ -0,0 +1,4 @@ +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false diff --git a/packages/intercom_flutter/intercom_flutter/android/.gitignore b/packages/intercom_flutter/intercom_flutter/android/.gitignore new file mode 100644 index 000000000..b339598d0 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/android/.gitignore @@ -0,0 +1,7 @@ +*.iml +.gradle +/local.properties +/.idea/* +.DS_Store +/build +/captures diff --git a/packages/intercom_flutter/intercom_flutter/android/build.gradle b/packages/intercom_flutter/intercom_flutter/android/build.gradle new file mode 100644 index 000000000..d4cde47b8 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/android/build.gradle @@ -0,0 +1,55 @@ +group 'io.maido.intercom' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.9.21' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.1.4' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdk 34 + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + minSdk 21 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + if (project.android.hasProperty('namespace')) { + namespace 'io.maido.intercom' + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'io.intercom.android:intercom-sdk:15.11.2' + implementation 'com.google.firebase:firebase-messaging:23.3.1' +} diff --git a/packages/intercom_flutter/intercom_flutter/android/gradle.properties b/packages/intercom_flutter/intercom_flutter/android/gradle.properties new file mode 100644 index 000000000..678cd6244 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/packages/intercom_flutter/intercom_flutter/android/settings.gradle b/packages/intercom_flutter/intercom_flutter/android/settings.gradle new file mode 100644 index 000000000..f21b7e7f7 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'intercom_flutter' diff --git a/packages/intercom_flutter/intercom_flutter/android/src/main/AndroidManifest.xml b/packages/intercom_flutter/intercom_flutter/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..1d0ecaf55 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/android/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/android/src/main/java/io/maido/intercom/PushInterceptReceiver.kt b/packages/intercom_flutter/intercom_flutter/android/src/main/java/io/maido/intercom/PushInterceptReceiver.kt new file mode 100644 index 000000000..75ff55e98 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/android/src/main/java/io/maido/intercom/PushInterceptReceiver.kt @@ -0,0 +1,36 @@ +package io.maido.intercom + +import android.app.Application +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import com.google.firebase.messaging.RemoteMessage +import io.intercom.android.sdk.push.IntercomPushClient + +class PushInterceptReceiver : BroadcastReceiver() { + + private val intercomPushClient = IntercomPushClient() + + override fun onReceive(context: Context?, intent: Intent?) { + if (context == null) return + + val application = context.applicationContext as Application + + val dataBundle = intent?.extras ?: return + + val remoteMessage = RemoteMessage(dataBundle) + val message = remoteMessage.data + + if (intercomPushClient.isIntercomPush(message)) { + Log.d(TAG, "Intercom message received") + intercomPushClient.handlePush(application, message) + } else { + Log.d(TAG, "Push message received, not for Intercom") + } + } + + companion object { + private const val TAG = "PushInterceptReceiver" + } +} diff --git a/packages/intercom_flutter/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt b/packages/intercom_flutter/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt new file mode 100644 index 000000000..187b7afe5 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/android/src/main/kotlin/io/maido/intercom/IntercomFlutterPlugin.kt @@ -0,0 +1,366 @@ +package io.maido.intercom + +import android.app.Application +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.intercom.android.sdk.* +import io.intercom.android.sdk.identity.Registration +import io.intercom.android.sdk.push.IntercomPushClient + +class IntercomFlutterPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ActivityAware { + companion object { + @JvmStatic + lateinit var application: Application + + @JvmStatic + fun initSdk(application: Application, appId: String, androidApiKey: String) { + Intercom.initialize(application, apiKey = androidApiKey, appId = appId) + } + } + + private val intercomPushClient = IntercomPushClient() + private var unreadConversationCountListener: UnreadConversationCountListener? = null + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "maido.io/intercom") + channel.setMethodCallHandler(IntercomFlutterPlugin()) + val unreadEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "maido.io/intercom/unread") + unreadEventChannel.setStreamHandler(IntercomFlutterPlugin()) + application = flutterPluginBinding.applicationContext as Application + } + + // https://stackoverflow.com/a/62206235 + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + application = binding.activity.application + } + + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "initialize" -> { + val apiKey = call.argument("androidApiKey") + val appId = call.argument("appId") + Intercom.initialize(application, apiKey, appId) + result.success("Intercom initialized") + } + "setBottomPadding" -> { + val padding = call.argument("bottomPadding") + if (padding != null) { + Intercom.client().setBottomPadding(padding) + result.success("Bottom padding set") + } + } + "setUserHash" -> { + val userHash = call.argument("userHash") + if (userHash != null) { + Intercom.client().setUserHash(userHash) + result.success("User hash added") + } + } + "loginIdentifiedUserWithUserId" -> { + val userId = call.argument("userId") + if (userId != null) { + var registration = Registration.create() + registration = registration.withUserId(userId) + Intercom.client().loginIdentifiedUser(registration, intercomStatusCallback = object : IntercomStatusCallback { + override fun onFailure(intercomError: IntercomError) { + // Handle failure + result.error(intercomError.errorCode.toString(), intercomError.errorMessage, getIntercomError( + errorCode = intercomError.errorCode, + errorMessage = intercomError.errorMessage, + )) + } + + override fun onSuccess() { + // Handle success + result.success("User created") + } + }) + } + } + "loginIdentifiedUserWithEmail" -> { + val email = call.argument("email") + if (email != null) { + var registration = Registration.create() + registration = registration.withEmail(email) + Intercom.client().loginIdentifiedUser(registration, intercomStatusCallback = object : IntercomStatusCallback { + override fun onFailure(intercomError: IntercomError) { + // Handle failure + result.error(intercomError.errorCode.toString(), intercomError.errorMessage, getIntercomError( + errorCode = intercomError.errorCode, + errorMessage = intercomError.errorMessage, + )) + } + + override fun onSuccess() { + // Handle success + result.success("User created") + } + }) + } + } + "loginUnidentifiedUser" -> { + Intercom.client().loginUnidentifiedUser(intercomStatusCallback = object : IntercomStatusCallback { + override fun onFailure(intercomError: IntercomError) { + // Handle failure + result.error(intercomError.errorCode.toString(), intercomError.errorMessage, getIntercomError( + errorCode = intercomError.errorCode, + errorMessage = intercomError.errorMessage, + )) + } + + override fun onSuccess() { + // Handle success + result.success("User created") + } + }) + } + "logout" -> { + Intercom.client().logout() + result.success("logout") + } + "setLauncherVisibility" -> { + val visibility = call.argument("visibility") + if (visibility != null) { + Intercom.client().setLauncherVisibility(Intercom.Visibility.valueOf(visibility)) + result.success("Showing launcher: $visibility") + } + } + "displayMessenger" -> { + Intercom.client().present() + result.success("Launched") + } + "hideMessenger" -> { + Intercom.client().hideIntercom() + result.success("Hidden") + } + "displayHelpCenter" -> { + Intercom.client().present(IntercomSpace.HelpCenter) + result.success("Launched") + } + "displayHelpCenterCollections" -> { + val collectionIds = call.argument>("collectionIds") + Intercom.client().presentContent( + content = IntercomContent.HelpCenterCollections( + ids = collectionIds ?: emptyList() + ) + ) + result.success("Launched") + } + "displayMessages" -> { + Intercom.client().present(IntercomSpace.Messages) + result.success("Launched") + } + "setInAppMessagesVisibility" -> { + val visibility = call.argument("visibility") + if (visibility != null) { + Intercom.client().setInAppMessageVisibility(Intercom.Visibility.valueOf(visibility)) + result.success("Showing in app messages: $visibility") + } else { + result.success("Launched") + } + } + "unreadConversationCount" -> { + val count = Intercom.client().unreadConversationCount + result.success(count) + } + "updateUser" -> { + Intercom.client().updateUser(getUserAttributes(call), intercomStatusCallback = object : IntercomStatusCallback { + override fun onFailure(intercomError: IntercomError) { + // Handle failure + result.error(intercomError.errorCode.toString(), intercomError.errorMessage, getIntercomError( + errorCode = intercomError.errorCode, + errorMessage = intercomError.errorMessage, + )) + } + + override fun onSuccess() { + // Handle success + result.success("User updated") + } + }) + } + "logEvent" -> { + val name = call.argument("name") + val metaData = call.argument>("metaData") + if (name != null) { + Intercom.client().logEvent(name, metaData) + result.success("Logged event") + } + } + "sendTokenToIntercom" -> { + val token = call.argument("token") + if (token != null) { + intercomPushClient.sendTokenToIntercom(application, token) + + result.success("Token sent to Intercom") + } + } + "handlePushMessage" -> { + Intercom.client().handlePushMessage() + result.success("Push message handled") + } + "displayMessageComposer" -> { + if (call.hasArgument("message")) { + Intercom.client().displayMessageComposer(call.argument("message")) + } else { + Intercom.client().displayMessageComposer() + } + result.success("Message composer displayed") + } + "isIntercomPush" -> { + result.success(intercomPushClient.isIntercomPush(call.argument>("message")!!)) + } + "handlePush" -> { + intercomPushClient.handlePush(application, call.argument>("message")!!) + result.success(null) + } + "displayArticle" -> { + val articleId = call.argument("articleId") + if (articleId != null) { + Intercom.client().presentContent(IntercomContent.Article(articleId)) + result.success("displaying article $articleId") + } + } + "displayCarousel" -> { + val carouselId = call.argument("carouselId") + if (carouselId != null) { + Intercom.client().presentContent(IntercomContent.Carousel(carouselId)) + result.success("displaying carousel $carouselId") + } + } + "displaySurvey" -> { + val surveyId = call.argument("surveyId") + if (surveyId != null) { + Intercom.client().presentContent(IntercomContent.Survey(surveyId)) + result.success("displaying survey $surveyId") + } + } + "displayConversation" -> { + val conversationId = call.argument("conversationId") + if (conversationId != null) { + Intercom.client().presentContent(IntercomContent.Conversation(conversationId)) + result.success("displaying conversation $conversationId") + } + } + "displayTickets" -> { + Intercom.client().present(IntercomSpace.Tickets) + result.success("Launched Tickets space") + } + "displayHome" -> { + Intercom.client().present(IntercomSpace.Home) + result.success("Launched Home space") + } + "isUserLoggedIn" -> { + result.success(Intercom.client().isUserLoggedIn()) + } + "fetchLoggedInUserAttributes" -> { + val reg = Intercom.client().fetchLoggedInUserAttributes() + val map = reg?.attributes?.toMap() ?: mutableMapOf() + if(reg != null){ + // put the user_id and email from registration + map["user_id"] = reg.userId + map["email"] = reg.email + } + result.success(map) + } + else -> result.notImplemented() + } + } + + // generate a errorDetails object to pass + private fun getIntercomError(errorCode: Int = -1, errorMessage: String = ""): Map { + return mapOf( + "errorCode" to errorCode, + "errorMessage" to errorMessage, + ) + } + + // generate the user attributes + private fun getUserAttributes(call: MethodCall): UserAttributes { + // user attributes + val name = call.argument("name") + val email = call.argument("email") + val phone = call.argument("phone") + val userId = call.argument("userId") + val company = call.argument("company") + val companyId = call.argument("companyId") + val customAttributes = call.argument>("customAttributes") + val signedUpAt = call.argument("signedUpAt") + val language = call.argument("language") + + val userAttributes = UserAttributes.Builder() + + if (name != null) { + userAttributes.withName(name) + } + + if (email != null) { + userAttributes.withEmail(email) + } + + if (phone != null) { + userAttributes.withPhone(phone) + } + + if (userId != null) { + userAttributes.withUserId(userId) + } + + if (company != null && companyId != null) { + val icmCompany = Company.Builder() + icmCompany.withName(company) + icmCompany.withCompanyId(companyId) + userAttributes.withCompany(icmCompany.build()) + } + + if (customAttributes != null) { + for ((key, value) in customAttributes) { + userAttributes.withCustomAttribute(key, value) + } + } + + val seconds: Long? = signedUpAt?.toString()?.toLongOrNull() + if (seconds != null) + userAttributes.withSignedUpAt(seconds) + + if (language != null) { + userAttributes.withLanguageOverride(language) + } + + return userAttributes.build() + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + unreadConversationCountListener = UnreadConversationCountListener { count -> events?.success(count) } + .also { + Intercom.client().addUnreadConversationCountListener(it) + } + } + + override fun onCancel(arguments: Any?) { + if (unreadConversationCountListener != null) { + Intercom.client().removeUnreadConversationCountListener(unreadConversationCountListener) + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + if (unreadConversationCountListener != null) { + Intercom.client().removeUnreadConversationCountListener(unreadConversationCountListener) + } + } + + override fun onDetachedFromActivity() { + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + } + + override fun onDetachedFromActivityForConfigChanges() { + } +} diff --git a/packages/intercom_flutter/intercom_flutter/example/.gitignore b/packages/intercom_flutter/intercom_flutter/example/.gitignore new file mode 100644 index 000000000..2f844581c --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +.dart_tool/ +.idea/* +!.idea/runConfigurations + +.packages +.pub/ + +build/ + +.flutter-plugins diff --git a/packages/intercom_flutter/intercom_flutter/example/.metadata b/packages/intercom_flutter/intercom_flutter/example/.metadata new file mode 100644 index 000000000..c4204e56b --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/.metadata @@ -0,0 +1,8 @@ +# 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: 66091f969653fd3535b265ddcd87436901858a1d + channel: dev diff --git a/packages/intercom_flutter/intercom_flutter/example/README.md b/packages/intercom_flutter/intercom_flutter/example/README.md new file mode 100644 index 000000000..1ff822647 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/README.md @@ -0,0 +1,8 @@ +# intercom_example + +Demonstrates how to use the intercom plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](https://flutter.io/). diff --git a/packages/intercom_flutter/intercom_flutter/example/android/.gitignore b/packages/intercom_flutter/intercom_flutter/example/android/.gitignore new file mode 100644 index 000000000..ed63e3499 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +*.class +.gradle +/local.properties +/.idea/* +.DS_Store +/build +/captures +GeneratedPluginRegistrant.java diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/build.gradle b/packages/intercom_flutter/intercom_flutter/example/android/app/build.gradle new file mode 100644 index 000000000..8f5551dfa --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/app/build.gradle @@ -0,0 +1,66 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + compileSdk 34 + + namespace 'io.maido.intercomexample' + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + + defaultConfig { + applicationId "io.maido.intercomexample" + minSdk 21 + targetSdk 34 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + signingConfig signingConfigs.debug + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + lint { + disable 'InvalidPackage' + } +} + +flutter { + source '../..' +} diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..42f4138b9 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/kotlin/io/maido/intercomexample/MainActivity.kt b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/kotlin/io/maido/intercomexample/MainActivity.kt new file mode 100644 index 000000000..ff8c4a9dd --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/kotlin/io/maido/intercomexample/MainActivity.kt @@ -0,0 +1,6 @@ +package io.maido.intercomexample + +import io.flutter.embedding.android.FlutterFragmentActivity + +class MainActivity() : FlutterFragmentActivity() { +} diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/kotlin/io/maido/intercomexample/MyApp.kt b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/kotlin/io/maido/intercomexample/MyApp.kt new file mode 100644 index 000000000..251dc6c29 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/kotlin/io/maido/intercomexample/MyApp.kt @@ -0,0 +1,14 @@ +package io.maido.intercomexample + +import android.app.Application +import io.maido.intercom.IntercomFlutterPlugin + +class MyApp : Application() { + override fun onCreate() { + super.onCreate() + + // Initialize the Intercom SDK here also as Android requires to initialize it in the onCreate of + // the application. + IntercomFlutterPlugin.initSdk(this, appId = "appId", androidApiKey = "androidApiKey") + } +} diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/drawable/launch_background.xml b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/values/styles.xml b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..ec443780e --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/android/build.gradle b/packages/intercom_flutter/intercom_flutter/example/android/build.gradle new file mode 100644 index 000000000..3b4a3384e --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/build.gradle @@ -0,0 +1,18 @@ +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/intercom_flutter/intercom_flutter/example/android/gradle.properties b/packages/intercom_flutter/intercom_flutter/example/android/gradle.properties new file mode 100644 index 000000000..4d3226abc --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/packages/intercom_flutter/intercom_flutter/example/android/settings.gradle b/packages/intercom_flutter/intercom_flutter/example/android/settings.gradle new file mode 100644 index 000000000..71227cc82 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/android/settings.gradle @@ -0,0 +1,27 @@ +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 + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version '8.7.2' apply false + id "org.jetbrains.kotlin.android" version "1.9.21" apply false + id "com.google.gms.google-services" version "4.4.2" apply false +} + +include ":app" diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/.gitignore b/packages/intercom_flutter/intercom_flutter/example/ios/.gitignore new file mode 100644 index 000000000..79cc4da80 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/.gitignore @@ -0,0 +1,45 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/app.flx +/Flutter/app.zip +/Flutter/flutter_assets/ +/Flutter/App.framework +/Flutter/Flutter.framework +/Flutter/Generated.xcconfig +/ServiceDefinitions.json + +Pods/ +.symlinks/ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/AppFrameworkInfo.plist b/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..ea52ba26f --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 10.0 + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/Debug.xcconfig b/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..e8efba114 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/Release.xcconfig b/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..399e9340e --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Podfile b/packages/intercom_flutter/intercom_flutter/example/ios/Podfile new file mode 100644 index 000000000..f17bddc91 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '15.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/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..940003158 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,496 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B06D52763675792FF5758304 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 048B61CED45EBBF51CE94732 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 048B61CED45EBBF51CE94732 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 6E6FC6613C7F6AD808A93CA8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; 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 = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 91F1A3F12BB17B10A8DDD5A8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.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; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B06D52763675792FF5758304 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2A6C7810F03691DC014053EA /* Pods */ = { + isa = PBXGroup; + children = ( + 6E6FC6613C7F6AD808A93CA8 /* Pods-Runner.debug.xcconfig */, + 91F1A3F12BB17B10A8DDD5A8 /* Pods-Runner.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 2A6C7810F03691DC014053EA /* Pods */, + C9471D72A17F1097BE4222C5 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C9471D72A17F1097BE4222C5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 048B61CED45EBBF51CE94732 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + FFF3B901266682DBA3941B63 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 5737A5BAF870233794884398 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1020; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 11.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 5737A5BAF870233794884398 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + 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", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + FFF3B901266682DBA3941B63 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + 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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.maido.intercomFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.maido.intercomFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..50a8cfc99 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..530b83358 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + BuildSystemType + Latest + PreviewsEnabled + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/AppDelegate.swift b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/AppDelegate.swift @@ -0,0 +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) + } +} diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +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" + } +} diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..3d43d11e6 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +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" + } +} diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# 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/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Base.lproj/Main.storyboard b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Info.plist b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Info.plist new file mode 100644 index 000000000..abc92b33f --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + intercom_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + + diff --git a/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Runner-Bridging-Header.h b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..7335fdf90 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/packages/intercom_flutter/intercom_flutter/example/lib/main.dart b/packages/intercom_flutter/intercom_flutter/example/lib/main.dart new file mode 100644 index 000000000..69ac660fa --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/lib/main.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:intercom_flutter/intercom_flutter.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + // TODO: make sure to add keys from your Intercom workspace. + await Intercom.instance.initialize( + 'appId', + androidApiKey: 'androidApiKey', + iosApiKey: 'iosApiKey', + ); + // TODO: don't forget to set up the custom application class on Android side. + runApp(SampleApp()); +} + +class SampleApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Intercom example app'), + ), + body: Center( + child: TextButton( + onPressed: () { + // NOTE: + // Messenger will load the messages only if the user is registered + // in Intercom. + // Either identified or unidentified. + // So make sure to login the user in Intercom first before opening + // the intercom messenger. + // Otherwise messenger will not load. + Intercom.instance.displayMessenger(); + }, + child: Text('Show messenger'), + ), + ), + ), + ); + } +} diff --git a/packages/intercom_flutter/intercom_flutter/example/pubspec.yaml b/packages/intercom_flutter/intercom_flutter/example/pubspec.yaml new file mode 100644 index 000000000..5c77c6647 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/pubspec.yaml @@ -0,0 +1,26 @@ +name: intercom_example +description: Demonstrates how to use the intercom_flutter plugin. +publish_to: none + +version: 1.0.0+1 + +dependencies: + flutter: + sdk: flutter + + intercom_flutter: + path: ../ + +dependency_overrides: + intercom_flutter: + path: ../ + intercom_flutter_platform_interface: + path: ../../intercom_flutter_platform_interface + intercom_flutter_web: + path: ../../intercom_flutter_web + +flutter: + uses-material-design: true + +environment: + sdk: '>=3.0.0 <4.0.0' diff --git a/packages/intercom_flutter/intercom_flutter/example/web/favicon.png b/packages/intercom_flutter/intercom_flutter/example/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/web/favicon.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-192.png b/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-192.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-512.png b/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-512.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-maskable-192.png b/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-maskable-512.png b/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/intercom_flutter/intercom_flutter/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/intercom_flutter/intercom_flutter/example/web/index.html b/packages/intercom_flutter/intercom_flutter/example/web/index.html new file mode 100644 index 000000000..39739d35e --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/web/index.html @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + \ No newline at end of file diff --git a/packages/intercom_flutter/intercom_flutter/example/web/manifest.json b/packages/intercom_flutter/intercom_flutter/example/web/manifest.json new file mode 100644 index 000000000..096edf8fe --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/intercom_flutter/intercom_flutter/ios/.gitignore b/packages/intercom_flutter/intercom_flutter/ios/.gitignore new file mode 100644 index 000000000..710ec6cf1 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/ios/.gitignore @@ -0,0 +1,36 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig diff --git a/packages/intercom_flutter/intercom_flutter/ios/Assets/.gitkeep b/packages/intercom_flutter/intercom_flutter/ios/Assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/intercom_flutter/intercom_flutter/ios/Classes/IntercomFlutterPlugin.h b/packages/intercom_flutter/intercom_flutter/ios/Classes/IntercomFlutterPlugin.h new file mode 100644 index 000000000..676773163 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/ios/Classes/IntercomFlutterPlugin.h @@ -0,0 +1,7 @@ +#import + +@interface IntercomFlutterPlugin : NSObject +@end + +@interface UnreadStreamHandler : NSObject +@end diff --git a/packages/intercom_flutter/intercom_flutter/ios/Classes/IntercomFlutterPlugin.m b/packages/intercom_flutter/intercom_flutter/ios/Classes/IntercomFlutterPlugin.m new file mode 100644 index 000000000..d40e284c4 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/ios/Classes/IntercomFlutterPlugin.m @@ -0,0 +1,354 @@ +#import "IntercomFlutterPlugin.h" +#import + +id unread; + +@implementation UnreadStreamHandler +- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { + unread = [[NSNotificationCenter defaultCenter] addObserverForName:IntercomUnreadConversationCountDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { + NSNumber *myNum = @([Intercom unreadConversationCount]); + eventSink(myNum); + }]; + return nil; +} + +- (FlutterError*)onCancelWithArguments:(id)arguments { + [[NSNotificationCenter defaultCenter] removeObserver:unread]; + return nil; +} +@end + +@implementation IntercomFlutterPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + IntercomFlutterPlugin* instance = [[IntercomFlutterPlugin alloc] init]; + FlutterMethodChannel* channel = + [FlutterMethodChannel methodChannelWithName:@"maido.io/intercom" + binaryMessenger:[registrar messenger]]; + [registrar addMethodCallDelegate:instance channel:channel]; + FlutterEventChannel* unreadChannel = [FlutterEventChannel eventChannelWithName:@"maido.io/intercom/unread" + binaryMessenger:[registrar messenger]]; + UnreadStreamHandler* unreadStreamHandler = + [[UnreadStreamHandler alloc] init]; + [unreadChannel setStreamHandler:unreadStreamHandler]; + +} + +- (void) handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result{ + if([@"initialize" isEqualToString:call.method]) { + NSString *iosApiKey = call.arguments[@"iosApiKey"]; + NSString *appId = call.arguments[@"appId"]; + [Intercom setApiKey:iosApiKey forAppId:appId]; + result(@"Initialized Intercom"); + } + else if([@"loginUnidentifiedUser" isEqualToString:call.method]) { + [Intercom loginUnidentifiedUserWithSuccess:^{ + // Handle success + result(@"Registered unidentified user"); + } failure:^(NSError * _Nonnull error) { + // Handle error + NSInteger errorCode = error.code; + NSString *errorMsg = error.localizedDescription; + + result([FlutterError errorWithCode:[@(errorCode) stringValue] + message:errorMsg + details: [self getIntercomError:errorCode:errorMsg]]); + }]; + } + else if([@"setBottomPadding" isEqualToString:call.method]) { + NSNumber *value = call.arguments[@"bottomPadding"]; + if(value != (id)[NSNull null] && value != nil) { + CGFloat padding = [value doubleValue]; + [Intercom setBottomPadding:padding]; + result(@"Set bottom padding"); + } + } + else if([@"setUserHash" isEqualToString:call.method]) { + NSString *userHash = call.arguments[@"userHash"]; + [Intercom setUserHash:userHash]; + result(@"User hash added"); + } + else if([@"loginIdentifiedUserWithUserId" isEqualToString:call.method]) { + NSString *userId = call.arguments[@"userId"]; + ICMUserAttributes *attributes = [ICMUserAttributes new]; + attributes.userId = userId; + [Intercom loginUserWithUserAttributes:attributes success:^{ + // Handle success + result(@"Registered user"); + } failure:^(NSError * _Nonnull error) { + // Handle failure + NSInteger errorCode = error.code; + NSString *errorMsg = error.localizedDescription; + + result([FlutterError errorWithCode:[@(errorCode) stringValue] + message:errorMsg + details: [self getIntercomError:errorCode:errorMsg]]); + }]; + } + else if([@"loginIdentifiedUserWithEmail" isEqualToString:call.method]) { + NSString *email = call.arguments[@"email"]; + ICMUserAttributes *attributes = [ICMUserAttributes new]; + attributes.email = email; + [Intercom loginUserWithUserAttributes:attributes success:^{ + // Handle success + result(@"Registered user"); + } failure:^(NSError * _Nonnull error) { + // Handle failure + NSInteger errorCode = error.code; + NSString *errorMsg = error.localizedDescription; + + result([FlutterError errorWithCode:[@(errorCode) stringValue] + message:errorMsg + details: [self getIntercomError:errorCode:errorMsg]]); + }]; + } + else if([@"setLauncherVisibility" isEqualToString:call.method]) { + NSString *visibility = call.arguments[@"visibility"]; + [Intercom setLauncherVisible:[@"VISIBLE" isEqualToString:visibility]]; + result(@"Setting launcher visibility"); + } + else if([@"setInAppMessagesVisibility" isEqualToString:call.method]) { + NSString *visibility = call.arguments[@"visibility"]; + [Intercom setInAppMessagesVisible:[@"VISIBLE" isEqualToString:visibility]]; + result(@"Setting in app messages visibility"); + } + else if([@"unreadConversationCount" isEqualToString:call.method]) { + NSUInteger count = [Intercom unreadConversationCount]; + result(@(count)); + } + else if([@"displayMessenger" isEqualToString:call.method]) { + [Intercom presentIntercom]; + result(@"Presented messenger"); + } + else if([@"hideMessenger" isEqualToString:call.method]) { + [Intercom hideIntercom]; + result(@"Messenger hidden"); + } + else if([@"displayHelpCenter" isEqualToString:call.method]) { + [Intercom presentIntercom:helpCenter]; + result(@"Presented help center"); + } + else if([@"displayHelpCenterCollections" isEqualToString:call.method]) { + NSArray *collectionIds = call.arguments[@"collectionIds"]; + if(collectionIds != (id)[NSNull null] && collectionIds != nil) { + [Intercom presentContent:[IntercomContent helpCenterCollectionsWithIds:collectionIds]]; + } else { + [Intercom presentContent:[IntercomContent helpCenterCollectionsWithIds:@[]]]; + } + result(@"Presented help center collections"); + } + else if([@"displayMessages" isEqualToString:call.method]) { + [Intercom presentIntercom:messages]; + result(@"Presented messages space"); + } + else if([@"updateUser" isEqualToString:call.method]) { + [Intercom updateUser:[self getAttributes:call] success:^{ + // Handle success + result(@"Updated user"); + } failure:^(NSError * _Nonnull error) { + // Handle failure + NSInteger errorCode = error.code; + NSString *errorMsg = error.localizedDescription; + + result([FlutterError errorWithCode:[@(errorCode) stringValue] + message:errorMsg + details: [self getIntercomError:errorCode:errorMsg]]); + }]; + } + else if([@"logout" isEqualToString:call.method]) { + [Intercom logout]; + result(@"Logged out"); + } + else if ([@"logEvent" isEqualToString:call.method]) { + NSString *name = call.arguments[@"name"]; + NSDictionary *metaData = call.arguments[@"metaData"]; + if(name != (id)[NSNull null] && name != nil) { + if(metaData != (id)[NSNull null] && metaData != nil) { + [Intercom logEventWithName:name metaData:metaData]; + } else { + [Intercom logEventWithName:name]; + } + result(@"Logged event"); + } + } + else if([@"handlePushMessage" isEqualToString:call.method]) { + result(@"No op"); + } + else if([@"displayMessageComposer" isEqualToString:call.method]) { + NSString *message = call.arguments[@"message"]; + [Intercom presentMessageComposer:message]; + result(@"Presented message composer"); + } else if([@"sendTokenToIntercom" isEqualToString:call.method]){ + NSString *token = call.arguments[@"token"]; + if(token != (id)[NSNull null] && token != nil) { + NSData *encodedToken=[self createDataWithHexString:token]; + // NSData* encodedToken=[token dataUsingEncoding:NSUTF8StringEncoding]; + NSLog(@"%@", encodedToken); + [Intercom setDeviceToken:encodedToken failure:^(NSError * _Nonnull error) { + // Handle failure + NSLog(@"Error setting device token: %@", error.localizedDescription); + }]; + result(@"Token set"); + } + } else if([@"displayArticle" isEqualToString:call.method]) { + NSString *articleId = call.arguments[@"articleId"]; + NSLog(@"%@", articleId); + if(articleId != (id)[NSNull null] && articleId != nil) { + [Intercom presentContent:[IntercomContent articleWithId:articleId]]; + result(@"displaying article"); + } + } else if([@"displayCarousel" isEqualToString:call.method]) { + NSString *carouselId = call.arguments[@"carouselId"]; + if(carouselId != (id)[NSNull null] && carouselId != nil) { + [Intercom presentContent:[IntercomContent carouselWithId:carouselId]]; + result(@"displaying carousel"); + } + } else if([@"displaySurvey" isEqualToString:call.method]) { + NSString *surveyId = call.arguments[@"surveyId"]; + if(surveyId != (id)[NSNull null] && surveyId != nil) { + [Intercom presentContent:[IntercomContent surveyWithId:surveyId]]; + result(@"displaying survey"); + } + } else if([@"isIntercomPush" isEqualToString:call.method]) { + NSDictionary *message = call.arguments[@"message"]; + if([Intercom isIntercomPushNotification:message]) { + result(@(YES)); + }else{ + result(@(NO)); + } + } else if([@"handlePush" isEqualToString:call.method]) { + NSDictionary *message = call.arguments[@"message"]; + [Intercom handleIntercomPushNotification:message]; + result(@"handle push"); + } else if([@"displayConversation" isEqualToString:call.method]) { + NSString *conversationId = call.arguments[@"conversationId"]; + if(conversationId != (id)[NSNull null] && conversationId != nil) { + [Intercom presentContent:[IntercomContent conversationWithId:conversationId]]; + result(@"displaying conversation"); + } + } else if([@"displayTickets" isEqualToString:call.method]) { + [Intercom presentIntercom:tickets]; + result(@"Presented tickets space"); + } else if([@"displayHome" isEqualToString:call.method]) { + [Intercom presentIntercom:home]; + result(@"Presented home space"); + } else if([@"isUserLoggedIn" isEqualToString:call.method]) { + if([Intercom isUserLoggedIn]) { + result(@(YES)); + }else{ + result(@(NO)); + } + } else if([@"fetchLoggedInUserAttributes" isEqualToString:call.method]) { + ICMUserAttributes *data = [Intercom fetchLoggedInUserAttributes]; + if(data != (id)[NSNull null]){ + NSDictionary *attributes = data.attributes; + NSMutableDictionary *map = [attributes mutableCopy]; + + // Add custom attributes + map[@"custom_attributes"] = data.customAttributes; + + // Add companies + if (data.companies) { + NSMutableArray *companiesArray = [NSMutableArray array]; + for (ICMCompany *company in data.companies) { + [companiesArray addObject:[company attributes]]; + } + map[@"companies"] = companiesArray; + } + + result(map); + } + result([NSMutableDictionary dictionary]); + } + else { + result(FlutterMethodNotImplemented); + } +} + +- (NSMutableDictionary *) getIntercomError:(NSInteger)errorCode :(NSString *)errorMessage { + NSMutableDictionary *details = [NSMutableDictionary dictionary]; + [details setObject:[NSNumber numberWithInteger:errorCode] forKey: @"errorCode"]; + [details setObject: errorMessage forKey: @"errorMessage"]; + + return details; +} + +- (ICMUserAttributes *) getAttributes:(FlutterMethodCall *)call { + ICMUserAttributes *attributes = [ICMUserAttributes new]; + NSString *email = call.arguments[@"email"]; + if(email != (id)[NSNull null]) { + attributes.email = email; + } + NSString *name = call.arguments[@"name"]; + if(name != (id)[NSNull null]) { + attributes.name = name; + } + NSString *phone = call.arguments[@"phone"]; + if(phone != (id)[NSNull null]) { + attributes.phone = phone; + } + NSString *userId = call.arguments[@"userId"]; + if(userId != (id)[NSNull null]) { + attributes.userId = userId; + } + NSString *companyName = call.arguments[@"company"]; + NSString *companyId = call.arguments[@"companyId"]; + if(companyName != (id)[NSNull null] && companyId != (id)[NSNull null]) { + ICMCompany *company = [ICMCompany new]; + company.name = companyName; + company.companyId = companyId; + attributes.companies = @[company]; + } + NSDictionary *customAttributes = call.arguments[@"customAttributes"]; + if(customAttributes != (id)[NSNull null]) { + attributes.customAttributes = customAttributes; + } + + NSNumber *signedUpAt = call.arguments[@"signedUpAt"]; + if(signedUpAt != (id)[NSNull null]) { + attributes.signedUpAt = [NSDate dateWithTimeIntervalSince1970: signedUpAt.doubleValue]; + } + + NSString *language = call.arguments[@"language"]; + if(language != (id)[NSNull null]) { + attributes.languageOverride = language; + } + + return attributes; +} + +- (NSData *) createDataWithHexString:(NSString*)inputString { + NSUInteger inLength = [inputString length]; + + unichar *inCharacters = alloca(sizeof(unichar) * inLength); + [inputString getCharacters:inCharacters range:NSMakeRange(0, inLength)]; + + UInt8 *outBytes = malloc(sizeof(UInt8) * ((inLength / 2) + 1)); + + NSInteger i, o = 0; + UInt8 outByte = 0; + + for (i = 0; i < inLength; i++) { + UInt8 c = inCharacters[i]; + SInt8 value = -1; + + if (c >= '0' && c <= '9') value = (c - '0'); + else if (c >= 'A' && c <= 'F') value = 10 + (c - 'A'); + else if (c >= 'a' && c <= 'f') value = 10 + (c - 'a'); + + if (value >= 0) { + if (i % 2 == 1) { + outBytes[o++] = (outByte << 4) | value; + outByte = 0; + } else { + outByte = value; + } + + } else { + if (o != 0) break; + } + } + + NSData *a = [[NSData alloc] initWithBytesNoCopy:outBytes length:o freeWhenDone:YES]; + return a; +} +@end diff --git a/packages/intercom_flutter/intercom_flutter/ios/intercom_flutter.podspec b/packages/intercom_flutter/intercom_flutter/ios/intercom_flutter.podspec new file mode 100644 index 000000000..ab58116ce --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/ios/intercom_flutter.podspec @@ -0,0 +1,22 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'intercom_flutter' + s.version = '9.0.0' + s.summary = 'Intercom integration for Flutter' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'https://github.com/ChangeFinance/intercom_flutter' + s.license = { :file => '../LICENSE' } + s.author = { 'xChange OÜ' => 'maido@getchange.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.dependency 'Intercom' + s.static_framework = true + s.dependency 'Intercom', '18.2.2' + s.ios.deployment_target = '15.0' +end diff --git a/packages/intercom_flutter/intercom_flutter/lib/intercom_flutter.dart b/packages/intercom_flutter/intercom_flutter/lib/intercom_flutter.dart new file mode 100755 index 000000000..23b5cae0d --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/lib/intercom_flutter.dart @@ -0,0 +1,290 @@ +library intercom_flutter; + +import 'dart:async'; + +import 'package:intercom_flutter_platform_interface/intercom_flutter_platform_interface.dart'; +import 'package:intercom_flutter_platform_interface/intercom_status_callback.dart'; + +/// export the [IntercomVisibility] enum +export 'package:intercom_flutter_platform_interface/intercom_flutter_platform_interface.dart' + show IntercomVisibility; +export 'package:intercom_flutter_platform_interface/intercom_status_callback.dart' + show IntercomStatusCallback, IntercomError; + +class Intercom { + /// private constructor to not allow the object creation from outside. + Intercom._(); + + static final Intercom _instance = Intercom._(); + + /// get the instance of the [Intercom]. + static Intercom get instance => _instance; + + /// Function to initialize the Intercom SDK. + /// + /// First, you'll need to get your Intercom [appId]. + /// [androidApiKey] is required if you want to use Intercom in Android. + /// [iosApiKey] is required if you want to use Intercom in iOS. + /// + /// You can get these from Intercom settings: + /// * [Android](https://app.intercom.com/a/apps/_/settings/android) + /// * [iOS](https://app.intercom.com/a/apps/_/settings/ios) + /// + /// Then, initialize Intercom in main method. + Future initialize( + String appId, { + String? androidApiKey, + String? iosApiKey, + }) { + return IntercomFlutterPlatform.instance + .initialize(appId, androidApiKey: androidApiKey, iosApiKey: iosApiKey); + } + + /// You can check how many unread conversations a user has + /// even if a user dismisses a notification. + /// + /// You can listen for unread conversation count with this method. + Stream getUnreadStream() { + return IntercomFlutterPlatform.instance.getUnreadStream(); + } + + /// This method allows you to set a fixed bottom padding for in app messages and the launcher. + /// + /// It is useful if your app has a tab bar or similar UI at the bottom of your window. + /// [padding] is the size of the bottom padding in points. + Future setBottomPadding(int padding) { + return IntercomFlutterPlatform.instance.setBottomPadding(padding); + } + + /// To make sure that conversations between you and your users are kept private + /// and that one user can't impersonate another then you need you need to setup + /// the identity verification. + /// + /// This function helps to set up the identity verification. + /// Here you need to pass hash (HMAC) of the user. + /// + /// This must be called before registering the user in Intercom. + /// + /// To generate the user hash (HMAC) see + /// + /// + /// Note: identity verification does not apply to unidentified users. + Future setUserHash(String userHash) { + return IntercomFlutterPlatform.instance.setUserHash(userHash); + } + + /// Function to create a identified user in Intercom. + /// You need to register your users before you can talk to them and + /// track their activity in your app. + /// + /// You can register a identified user either with [userId] or with [email], + /// but not with both. + Future loginIdentifiedUser( + {String? userId, String? email, IntercomStatusCallback? statusCallback}) { + return IntercomFlutterPlatform.instance.loginIdentifiedUser( + userId: userId, email: email, statusCallback: statusCallback); + } + + /// Function to create a unidentified user in Intercom. + /// You need to register your users before you can talk to them and + /// track their activity in your app. + Future loginUnidentifiedUser({IntercomStatusCallback? statusCallback}) { + return IntercomFlutterPlatform.instance + .loginUnidentifiedUser(statusCallback: statusCallback); + } + + /// Updates the attributes of the current Intercom user. + /// + /// The [signedUpAt] param should be seconds since epoch. + /// + /// The [language] param should be an an ISO 639-1 two-letter code such as `en` for English or `fr` for French. + /// You’ll need to use a four-letter code for Chinese like `zh-CN`. + /// check this link https://www.intercom.com/help/en/articles/180-localize-intercom-to-work-with-multiple-languages. + /// + /// See also: + /// * [Localize Intercom to work with multiple languages](https://www.intercom.com/help/en/articles/180-localize-intercom-to-work-with-multiple-languages) + Future updateUser({ + String? email, + String? name, + String? phone, + String? company, + String? companyId, + String? userId, + int? signedUpAt, + String? language, + Map? customAttributes, + IntercomStatusCallback? statusCallback, + }) { + return IntercomFlutterPlatform.instance.updateUser( + email: email, + name: name, + phone: phone, + company: company, + companyId: companyId, + userId: userId, + signedUpAt: signedUpAt, + language: language, + customAttributes: customAttributes, + statusCallback: statusCallback, + ); + } + + /// To logout a user from Intercom. + /// This clears the Intercom SDK's cache of your user's identity. + Future logout() { + return IntercomFlutterPlatform.instance.logout(); + } + + /// To hide or show the standard launcher on the bottom right-hand side of the screen. + Future setLauncherVisibility(IntercomVisibility visibility) { + return IntercomFlutterPlatform.instance.setLauncherVisibility(visibility); + } + + /// You can check how many unread conversations a user has + /// even if a user dismisses a notification. + /// + /// You can get the current unread conversation count with this method. + Future unreadConversationCount() { + return IntercomFlutterPlatform.instance.unreadConversationCount(); + } + + /// To allow or prevent in app messages from popping up in certain parts of your app. + Future setInAppMessagesVisibility(IntercomVisibility visibility) { + return IntercomFlutterPlatform.instance + .setInAppMessagesVisibility(visibility); + } + + /// To open the Intercom messenger. + /// + /// This is used when you manually want to launch Intercom messenger. + /// for e.g: from your custom launcher (Help & Support) or (Talk to us). + Future displayMessenger() { + return IntercomFlutterPlatform.instance.displayMessenger(); + } + + /// To close the Intercom messenger. + /// + /// This is used when you manually want to close Intercom messenger. + Future hideMessenger() { + return IntercomFlutterPlatform.instance.hideMessenger(); + } + + /// To display an Activity with your Help Center content. + /// + /// Make sure Help Center is turned on. + /// If you don't have Help Center enabled in your Intercom settings the method + /// displayHelpCenter will fail to load. + Future displayHelpCenter() { + return IntercomFlutterPlatform.instance.displayHelpCenter(); + } + + /// To display an Activity with your Help Center content for specific collections. + /// + /// Make sure Help Center is turned on. + /// If you don't have Help Center enabled in your Intercom settings the method + /// displayHelpCenterCollections will fail to load. + /// The [collectionIds] you want to display. + Future displayHelpCenterCollections(List collectionIds) { + return IntercomFlutterPlatform.instance + .displayHelpCenterCollections(collectionIds); + } + + /// To display an Activity with your Messages content. + Future displayMessages() { + return IntercomFlutterPlatform.instance.displayMessages(); + } + + /// To log events in Intercom that record what users do in your app and when they do it. + /// For example, you can record when user opened a specific screen in your app. + /// You can also pass [metaData] about the event. + Future logEvent(String name, [Map? metaData]) { + return IntercomFlutterPlatform.instance.logEvent(name, metaData); + } + + /// The [token] to send to the Intercom to receive the notifications. + /// + /// For the Android, this [token] must be a FCM (Firebase cloud messaging) token. + /// For the iOS, this [token] must be a APNS token. + Future sendTokenToIntercom(String token) { + return IntercomFlutterPlatform.instance.sendTokenToIntercom(token); + } + + /// When a user taps on a push notification Intercom hold onto data + /// such as the URI in your message or the conversation to open. + /// + /// When you want Intercom to act on that data, use this method. + Future handlePushMessage() { + return IntercomFlutterPlatform.instance.handlePushMessage(); + } + + /// To open the Intercom messenger to the composer screen with [message] + /// field pre-populated. + Future displayMessageComposer(String message) { + return IntercomFlutterPlatform.instance.displayMessageComposer(message); + } + + /// To check if the push [message] is for Intercom or not. + /// This is useful when your app is also configured to receive push messages + /// from third parties. + Future isIntercomPush(Map message) async { + return IntercomFlutterPlatform.instance.isIntercomPush(message); + } + + /// If the push [message] is for Intercom then use this method to let + /// Intercom handle that push. + Future handlePush(Map message) async { + return IntercomFlutterPlatform.instance.handlePush(message); + } + + /// To display an Article, pass in an [articleId] from your Intercom workspace. + /// + /// An article must be ‘live’ to be used in this feature. + /// If it is in a draft or paused state, + /// end-users will see an error if the app tries to open the content. + Future displayArticle(String articleId) async { + return IntercomFlutterPlatform.instance.displayArticle(articleId); + } + + /// To display a Carousel, pass in a [carouselId] from your Intercom workspace. + /// + /// A carousel must be ‘live’ to be used in this feature. + /// If it is in a draft or paused state, + /// end-users will see an error if the app tries to open the content. + Future displayCarousel(String carouselId) async { + return IntercomFlutterPlatform.instance.displayCarousel(carouselId); + } + + /// To display a Survey, pass in a [surveyId] from your Intercom workspace. + /// + /// A survey must be ‘live’ to be used in this feature. + /// If it is in a draft or paused state, + /// end-users will see an error if the app tries to open the content. + Future displaySurvey(String surveyId) { + return IntercomFlutterPlatform.instance.displaySurvey(surveyId); + } + + /// To display a Conversation, pass in a [conversationId] from your Intercom workspace. + Future displayConversation(String conversationId) { + return IntercomFlutterPlatform.instance.displayConversation(conversationId); + } + + /// To display an activity with all your tickets. + Future displayTickets() { + return IntercomFlutterPlatform.instance.displayTickets(); + } + + /// To display an Intercom Home space. + Future displayHome() { + return IntercomFlutterPlatform.instance.displayHome(); + } + + /// Determine if a user is currently logged in to Intercom. + Future isUserLoggedIn() { + return IntercomFlutterPlatform.instance.isUserLoggedIn(); + } + + /// Retrieve the details of the currently logged in user. + Future> fetchLoggedInUserAttributes() { + return IntercomFlutterPlatform.instance.fetchLoggedInUserAttributes(); + } +} diff --git a/packages/intercom_flutter/intercom_flutter/pubspec.yaml b/packages/intercom_flutter/intercom_flutter/pubspec.yaml new file mode 100644 index 000000000..ac299e466 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/pubspec.yaml @@ -0,0 +1,33 @@ +name: intercom_flutter +description: Flutter plugin for Intercom integration. Provides in-app messaging + and help-center Intercom services +version: 9.2.2 +homepage: https://github.com/v3rm0n/intercom_flutter + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + intercom_flutter_platform_interface: ^2.0.2 + intercom_flutter_web: ^1.1.5 + +dev_dependencies: + flutter_test: + sdk: flutter + collection: ^1.17.2 + +flutter: + plugin: + platforms: + android: + package: io.maido.intercom + pluginClass: IntercomFlutterPlugin + ios: + pluginClass: IntercomFlutterPlugin + web: + default_package: intercom_flutter_web + +environment: + sdk: ">=3.2.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/packages/intercom_flutter/intercom_flutter/test/intercom_flutter_test.dart b/packages/intercom_flutter/intercom_flutter/test/intercom_flutter_test.dart new file mode 100755 index 000000000..62dfa6a04 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/test/intercom_flutter_test.dart @@ -0,0 +1,286 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intercom_flutter/intercom_flutter.dart'; + +import 'test_method_channel.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('Intercom', () { + setUp(() { + setUpTestMethodChannel('maido.io/intercom'); + }); + + test('initialize', () { + final appId = 'mock'; + final androidApiKey = 'android-key'; + final iosApiKey = 'ios-key'; + + Intercom.instance.initialize( + appId, + androidApiKey: androidApiKey, + iosApiKey: iosApiKey, + ); + + expectMethodCall('initialize', arguments: { + 'appId': appId, + 'androidApiKey': androidApiKey, + 'iosApiKey': iosApiKey, + }); + }); + + test('testSendingAPNTokenToIntercom', () { + Intercom.instance.sendTokenToIntercom('mock_apn_token'); + expectMethodCall('sendTokenToIntercom', arguments: { + 'token': 'mock_apn_token', + }); + }); + + group('loginIdentifiedUser', () { + test('with userId', () { + Intercom.instance.loginIdentifiedUser(userId: 'test'); + expectMethodCall('loginIdentifiedUserWithUserId', arguments: { + 'userId': 'test', + }); + }); + + test('with email', () { + Intercom.instance.loginIdentifiedUser(email: 'test'); + expectMethodCall('loginIdentifiedUserWithEmail', arguments: { + 'email': 'test', + }); + }); + + test('with userId and email should fail', () { + expect( + () => Intercom.instance.loginIdentifiedUser( + userId: 'testId', + email: 'testEmail', + ), + throwsArgumentError, + ); + }); + + test('without parameters', () { + expect( + () => Intercom.instance.loginIdentifiedUser(), throwsArgumentError); + }); + }); + + test('loginUnidentifiedUser', () { + Intercom.instance.loginUnidentifiedUser(); + expectMethodCall('loginUnidentifiedUser'); + }); + + test('setBottomPadding', () { + final padding = 64; + Intercom.instance.setBottomPadding(padding); + expectMethodCall('setBottomPadding', arguments: { + 'bottomPadding': padding, + }); + }); + + test('setUserHash', () { + Intercom.instance.setUserHash('test'); + expectMethodCall('setUserHash', arguments: { + 'userHash': 'test', + }); + }); + + test('logout', () { + Intercom.instance.logout(); + expectMethodCall('logout'); + }); + + group('toggleMessengerVisibility', () { + test('displayMessenger', () { + Intercom.instance.displayMessenger(); + expectMethodCall('displayMessenger'); + }); + + test('hideMessenger', () { + Intercom.instance.hideMessenger(); + expectMethodCall('hideMessenger'); + }); + }); + + test('displayHelpCenter', () { + Intercom.instance.displayHelpCenter(); + expectMethodCall('displayHelpCenter'); + }); + + test('displayHelpCenterCollections', () { + final collectionIds = ['collection1', 'collection2']; + Intercom.instance.displayHelpCenterCollections(collectionIds); + expectMethodCall('displayHelpCenterCollections', + arguments: {'collectionIds': collectionIds}); + }); + + test('displayMessages', () { + Intercom.instance.displayMessages(); + expectMethodCall('displayMessages'); + }); + + test('unreadConversationCount', () { + Intercom.instance.unreadConversationCount(); + expectMethodCall('unreadConversationCount'); + }); + + test('displayMessageComposer', () { + Intercom.instance.displayMessageComposer("message"); + expectMethodCall( + 'displayMessageComposer', + arguments: {"message": "message"}, + ); + }); + + group('setInAppMessagesVisibility', () { + test('visible', () { + Intercom.instance + .setInAppMessagesVisibility(IntercomVisibility.visible); + expectMethodCall('setInAppMessagesVisibility', arguments: { + 'visibility': 'VISIBLE', + }); + }); + + test('gone', () { + Intercom.instance.setInAppMessagesVisibility(IntercomVisibility.gone); + expectMethodCall('setInAppMessagesVisibility', arguments: { + 'visibility': 'GONE', + }); + }); + }); + + group('setLauncherVisibility', () { + test('visible', () { + Intercom.instance.setLauncherVisibility(IntercomVisibility.visible); + expectMethodCall('setLauncherVisibility', arguments: { + 'visibility': 'VISIBLE', + }); + }); + + test('gone', () { + Intercom.instance.setLauncherVisibility(IntercomVisibility.gone); + expectMethodCall('setLauncherVisibility', arguments: { + 'visibility': 'GONE', + }); + }); + }); + + test('updateUser', () { + Intercom.instance.updateUser( + email: 'test@example.com', + name: 'John Doe', + userId: '1', + phone: '+37256123456', + company: 'Some Company LLC', + companyId: '2', + signedUpAt: 1590949800, + language: 'en', + ); + expectMethodCall('updateUser', arguments: { + 'email': 'test@example.com', + 'name': 'John Doe', + 'userId': '1', + 'phone': '+37256123456', + 'company': 'Some Company LLC', + 'companyId': '2', + 'signedUpAt': 1590949800, + 'language': 'en', + 'customAttributes': null, + }); + }); + }); + + group('logEvent', () { + test('withoutMetaData', () { + Intercom.instance.logEvent("TEST"); + expectMethodCall('logEvent', arguments: { + 'name': 'TEST', + 'metaData': null, + }); + }); + + test('withMetaData', () { + Intercom.instance.logEvent( + "TEST", + {'string': 'A string', 'number': 10, 'bool': true}, + ); + expectMethodCall('logEvent', arguments: { + 'name': 'TEST', + 'metaData': {'string': 'A string', 'number': 10, 'bool': true}, + }); + }); + }); + + group('UnreadMessageCount', () { + const String channelName = 'maido.io/intercom/unread'; + const MethodChannel channel = MethodChannel(channelName); + final int value = 9; + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + channelName, + const StandardMethodCodec().encodeSuccessEnvelope(value), + (ByteData? data) {}, + ); + return; + }); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + test('testStream', () async { + expect(await Intercom.instance.getUnreadStream().first, value); + }); + }); + + test('displayArticle', () async { + final String testArticleId = "123456"; + await Intercom.instance.displayArticle(testArticleId); + expectMethodCall('displayArticle', arguments: { + 'articleId': testArticleId, + }); + }); + + test('displayCarousel', () async { + final String testCarouselId = "123456"; + await Intercom.instance.displayCarousel(testCarouselId); + expectMethodCall('displayCarousel', arguments: { + 'carouselId': testCarouselId, + }); + }); + + test('displaySurvey', () async { + final String testSurveyId = "123456"; + await Intercom.instance.displaySurvey(testSurveyId); + expectMethodCall('displaySurvey', arguments: { + 'surveyId': testSurveyId, + }); + }); + + test('displayConversation', () async { + final String testConversationId = "123456"; + await Intercom.instance.displayConversation(testConversationId); + expectMethodCall('displayConversation', arguments: { + 'conversationId': testConversationId, + }); + }); + + test('displayTickets', () async { + await Intercom.instance.displayTickets(); + expectMethodCall('displayTickets'); + }); + + test('displayHome', () async { + await Intercom.instance.displayHome(); + expectMethodCall('displayHome'); + }); +} diff --git a/packages/intercom_flutter/intercom_flutter/test/test_method_channel.dart b/packages/intercom_flutter/intercom_flutter/test/test_method_channel.dart new file mode 100644 index 000000000..fca7c5cf3 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter/test/test_method_channel.dart @@ -0,0 +1,54 @@ +import 'package:collection/collection.dart' show IterableExtension; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final _log = []; +final _responses = {}; + +var _expectCounter = 0; + +void setUpTestMethodChannel(String methodChannel) { + final channel = MethodChannel(methodChannel); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (methodCall) async { + _log.add(methodCall); + final matchingStubbing = + _responses.keys.firstWhereOrNull((s) => s.matches(methodCall)); + if (matchingStubbing != null) { + return _responses[matchingStubbing]; + } + return; + }); + addTearDown(() { + _log.clear(); + _responses.clear(); + _expectCounter = 0; + }); +} + +class MethodCallStubbing { + final Matcher nameMatcher; + final Matcher argMatcher; + + const MethodCallStubbing(this.nameMatcher, this.argMatcher); + + void thenReturn(dynamic answer) { + _responses[this] = answer; + } + + bool matches(MethodCall methodCall) { + return nameMatcher.matches(methodCall.method, {}) && + argMatcher.matches(methodCall.arguments, {}); + } +} + +MethodCallStubbing whenMethodCall(Matcher nameMatcher, Matcher argMatcher) { + return MethodCallStubbing(nameMatcher, argMatcher); +} + +void expectMethodCall(String name, {Map? arguments}) { + expect( + _log[_expectCounter++], + isMethodCall(name, arguments: arguments), + ); +} diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/.gitignore b/packages/intercom_flutter/intercom_flutter_platform_interface/.gitignore new file mode 100644 index 000000000..f396a1134 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ +pubspec.lock + +build/ +.idea/ +android/bin/ +example/.flutter-plugins-dependencies +example/ios/Flutter/flutter_export_environment.sh diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/CHANGELOG.md b/packages/intercom_flutter/intercom_flutter_platform_interface/CHANGELOG.md new file mode 100755 index 000000000..fa4e7b51b --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/CHANGELOG.md @@ -0,0 +1,67 @@ +# Changelog + +## 2.0.2 + +* Added method `isUserLoggedIn`. +* Added method `fetchLoggedInUserAttributes`. + +## 2.0.1 + +* Added method `displayHome`. + +## 2.0.0 + +* Removed deprecated methods `registerIdentifiedUser` and `registerUnidentifiedUser`. + +## 1.3.2 + +* Added method `displayTickets` + +## 1.3.1 + +* Added method `displayConversation` + +## 1.3.0 + +* Update minimum Dart version to Dart 3. + +## 1.2.3 + +* Added method `displayHelpCenterCollections`. + +## 1.2.2 + +* Added method `displayMessages`. + +## 1.2.1 + +* Updated dependencies + +## 1.2.0 + +* Added method `displaySurvey`. + +## 1.1.0 + +* Added method `loginIdentifiedUser` with `IntercomStatusCallback` support. +* Deprecated `registerIdentifiedUser` in favor of `loginIdentifiedUser`. +* Added method `loginUnidentifiedUser` with `IntercomStatusCallback` support. +* Deprecated `registerUnidentifiedUser` in favor of `loginUnidentifiedUser`. +* Added parameter `statusCallback` in `updateUser` to support `IntercomStatusCallback`. +* Renamed the following methods in the MethodChannel: + * `registerIdentifiedUserWithUserId` to `loginIdentifiedUserWithUserId`. + * `regsiterIdentifiedUserWithEmail` to `loginIdentifiedUserWithEmail`. + * `registerUnidentifiedUser` to `loginUnidentifiedUser`. + +## 1.0.1 + +* Added API documentation. + +## 1.0.0 + +* displayCarousel +* displayArticle + +## 0.0.1 + +* Initial open source release diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/LICENSE b/packages/intercom_flutter/intercom_flutter_platform_interface/LICENSE new file mode 100644 index 000000000..fface4a6c --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 xChange OÜ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/README.md b/packages/intercom_flutter/intercom_flutter_platform_interface/README.md new file mode 100755 index 000000000..f8e373c4a --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/README.md @@ -0,0 +1,26 @@ +# intercom_flutter_platform_interface + +A common platform interface for the [`intercom_flutter`][1] plugin. + +This interface allows platform-specific implementations of the `intercom_flutter` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `intercom_flutter`, extend +[`IntercomFlutterPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`IntercomFlutterPlatform` by calling +`IntercomFlutterPlatform.instance = MyPlatformIntercomFlutter()`. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../intercom_flutter +[2]: lib/intercom_flutter_platform_interface.dart diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/analysis_options.yml b/packages/intercom_flutter/intercom_flutter_platform_interface/analysis_options.yml new file mode 100644 index 000000000..4135297cc --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/analysis_options.yml @@ -0,0 +1,4 @@ +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/lib/intercom_flutter_platform_interface.dart b/packages/intercom_flutter/intercom_flutter_platform_interface/lib/intercom_flutter_platform_interface.dart new file mode 100644 index 000000000..9065fd327 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/lib/intercom_flutter_platform_interface.dart @@ -0,0 +1,281 @@ +import 'package:intercom_flutter_platform_interface/intercom_status_callback.dart'; +import 'package:intercom_flutter_platform_interface/method_channel_intercom_flutter.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +enum IntercomVisibility { gone, visible } + +abstract class IntercomFlutterPlatform extends PlatformInterface { + IntercomFlutterPlatform() : super(token: _token); + + static final Object _token = Object(); + + static IntercomFlutterPlatform _instance = MethodChannelIntercomFlutter(); + + /// The default instance of to use. + static IntercomFlutterPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + static set instance(IntercomFlutterPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Function to initialize the Intercom SDK. + /// + /// First, you'll need to get your Intercom [appId]. + /// [androidApiKey] is required if you want to use Intercom in Android. + /// [iosApiKey] is required if you want to use Intercom in iOS. + /// + /// You can get these from Intercom settings: + /// * [Android](https://app.intercom.com/a/apps/_/settings/android) + /// * [iOS](https://app.intercom.com/a/apps/_/settings/ios) + /// + /// Then, initialize Intercom in main method. + Future initialize( + String appId, { + String? androidApiKey, + String? iosApiKey, + }) { + throw UnimplementedError('initialize() has not been implemented.'); + } + + /// You can check how many unread conversations a user has + /// even if a user dismisses a notification. + /// + /// You can listen for unread conversation count with this method. + Stream getUnreadStream() { + throw UnimplementedError('getUnreadStream() has not been implemented.'); + } + + /// To make sure that conversations between you and your users are kept private + /// and that one user can't impersonate another then you need you need to setup + /// the identity verification. + /// + /// This function helps to set up the identity verification. + /// Here you need to pass hash (HMAC) of the user. + /// + /// This must be called before registering the user in Intercom. + /// + /// To generate the user hash (HMAC) see + /// + /// + /// Note: identity verification does not apply to unidentified users. + Future setUserHash(String userHash) { + throw UnimplementedError('setUserHash() has not been implemented.'); + } + + /// Function to create a identified user in Intercom. + /// You need to register your users before you can talk to them and + /// track their activity in your app. + /// + /// You can register a identified user either with [userId] or with [email], + /// but not with both. + Future loginIdentifiedUser( + {String? userId, String? email, IntercomStatusCallback? statusCallback}) { + throw UnimplementedError('loginIdentifiedUser() has not been implemented.'); + } + + /// Function to create a unidentified user in Intercom. + /// You need to register your users before you can talk to them and + /// track their activity in your app. + Future loginUnidentifiedUser({IntercomStatusCallback? statusCallback}) { + throw UnimplementedError( + 'loginUnidentifiedUser() has not been implemented.'); + } + + /// Updates the attributes of the current Intercom user. + /// + /// The [signedUpAt] param should be seconds since epoch. + /// + /// The [language] param should be an an ISO 639-1 two-letter code such as `en` for English or `fr` for French. + /// You’ll need to use a four-letter code for Chinese like `zh-CN`. + /// check this link https://www.intercom.com/help/en/articles/180-localize-intercom-to-work-with-multiple-languages. + /// + /// See also: + /// * [Localize Intercom to work with multiple languages](https://www.intercom.com/help/en/articles/180-localize-intercom-to-work-with-multiple-languages) + Future updateUser({ + String? email, + String? name, + String? phone, + String? company, + String? companyId, + String? userId, + int? signedUpAt, + String? language, + Map? customAttributes, + IntercomStatusCallback? statusCallback, + }) { + throw UnimplementedError('updateUser() has not been implemented.'); + } + + /// To logout a user from Intercom. + /// This clears the Intercom SDK's cache of your user's identity. + Future logout() { + throw UnimplementedError('logout() has not been implemented.'); + } + + /// To hide or show the standard launcher on the bottom right-hand side of the screen. + Future setLauncherVisibility(IntercomVisibility visibility) { + throw UnimplementedError( + 'setLauncherVisibility() has not been implemented.'); + } + + /// You can check how many unread conversations a user has + /// even if a user dismisses a notification. + /// + /// You can get the current unread conversation count with this method. + Future unreadConversationCount() { + throw UnimplementedError( + 'unreadConversationCount() has not been implemented.'); + } + + /// To allow or prevent in app messages from popping up in certain parts of your app. + Future setInAppMessagesVisibility(IntercomVisibility visibility) { + throw UnimplementedError( + 'setInAppMessagesVisibility() has not been implemented.'); + } + + /// To open the Intercom messenger. + /// + /// This is used when you manually want to launch Intercom messenger. + /// for e.g: from your custom launcher (Help & Support) or (Talk to us). + Future displayMessenger() { + throw UnimplementedError('displayMessenger() has not been implemented.'); + } + + /// To close the Intercom messenger. + /// + /// This is used when you manually want to close Intercom messenger. + Future hideMessenger() { + throw UnimplementedError('hideMessenger() has not been implemented.'); + } + + /// To display an Activity with your Help Center content. + /// + /// Make sure Help Center is turned on. + /// If you don't have Help Center enabled in your Intercom settings the method + /// displayHelpCenter will fail to load. + Future displayHelpCenter() { + throw UnimplementedError('displayHelpCenter() has not been implemented.'); + } + + /// To display an Activity with your Help Center content for specific collections. + /// + /// Make sure Help Center is turned on. + /// If you don't have Help Center enabled in your Intercom settings the method + /// displayHelpCenterCollections will fail to load. + /// The [collectionIds] you want to display. + Future displayHelpCenterCollections(List collectionIds) { + throw UnimplementedError( + 'displayHelpCenterCollections() has not been implemented.'); + } + + /// To display an Activity with your Messages content. + Future displayMessages() { + throw UnimplementedError('displayMessages() has not been implemented.'); + } + + /// To log events in Intercom that record what users do in your app and when they do it. + /// For example, you can record when user opened a specific screen in your app. + /// You can also pass [metaData] about the event. + Future logEvent(String name, [Map? metaData]) { + throw UnimplementedError('logEvent() has not been implemented.'); + } + + /// The [token] to send to the Intercom to receive the notifications. + /// + /// For the Android, this [token] must be a FCM (Firebase cloud messaging) token. + /// For the iOS, this [token] must be a APNS token. + Future sendTokenToIntercom(String token) { + throw UnimplementedError('sendTokenToIntercom() has not been implemented.'); + } + + /// When a user taps on a push notification Intercom hold onto data + /// such as the URI in your message or the conversation to open. + /// + /// When you want Intercom to act on that data, use this method. + Future handlePushMessage() { + throw UnimplementedError('handlePushMessage() has not been implemented.'); + } + + /// To open the Intercom messenger to the composer screen with [message] + /// field pre-populated. + Future displayMessageComposer(String message) { + throw UnimplementedError( + 'displayMessageComposer() has not been implemented.'); + } + + /// To check if the push [message] is for Intercom or not. + /// This is useful when your app is also configured to receive push messages + /// from third parties. + Future isIntercomPush(Map message) async { + throw UnimplementedError('isIntercomPush() has not been implemented.'); + } + + /// If the push [message] is for Intercom then use this method to let + /// Intercom handle that push. + Future handlePush(Map message) async { + throw UnimplementedError('handlePush() has not been implemented.'); + } + + /// This method allows you to set a fixed bottom padding for in app messages and the launcher. + /// + /// It is useful if your app has a tab bar or similar UI at the bottom of your window. + /// [padding] is the size of the bottom padding in points. + Future setBottomPadding(int padding) { + throw UnimplementedError('setBottomPadding() has not been implemented.'); + } + + /// To display an Article, pass in an [articleId] from your Intercom workspace. + /// + /// An article must be ‘live’ to be used in this feature. + /// If it is in a draft or paused state, + /// end-users will see an error if the app tries to open the content. + Future displayArticle(String articleId) { + throw UnimplementedError('displayArticle() has not been implemented.'); + } + + /// To display a Carousel, pass in a [carouselId] from your Intercom workspace. + /// + /// A carousel must be ‘live’ to be used in this feature. + /// If it is in a draft or paused state, + /// end-users will see an error if the app tries to open the content. + Future displayCarousel(String carouselId) { + throw UnimplementedError('displayCarousel() has not been implemented.'); + } + + /// To display a Survey, pass in a [surveyId] from your Intercom workspace. + /// + /// A survey must be ‘live’ to be used in this feature. + /// If it is in a draft or paused state, + /// end-users will see an error if the app tries to open the content. + Future displaySurvey(String surveyId) { + throw UnimplementedError('displaySurvey() has not been implemented.'); + } + + /// To display a Conversation, pass in a [conversationId] from your Intercom workspace. + Future displayConversation(String conversationId) { + throw UnimplementedError('displayConversation() has not been implemented.'); + } + + /// To display an activity with all your tickets. + Future displayTickets() { + throw UnimplementedError('displayTickets() has not been implemented.'); + } + + /// To display an Intercom Home space. + Future displayHome() { + throw UnimplementedError('displayHome() has not been implemented.'); + } + + /// Determine if a user is currently logged in to Intercom. + Future isUserLoggedIn() { + throw UnimplementedError('isUserLoggedIn() has not been implemented.'); + } + + /// Retrieve the details of the currently logged in user. + Future> fetchLoggedInUserAttributes() { + throw UnimplementedError( + 'fetchLoggedInUserAttributes() has not been implemented.'); + } +} diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/lib/intercom_status_callback.dart b/packages/intercom_flutter/intercom_flutter_platform_interface/lib/intercom_status_callback.dart new file mode 100644 index 000000000..255e529f9 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/lib/intercom_status_callback.dart @@ -0,0 +1,32 @@ +class IntercomStatusCallback { + /// Callback when intercom operation is failed. + /// It will contain the error information. + final Function(IntercomError error)? onFailure; + + /// Callback when intercom operation is success. + final Function()? onSuccess; + + /// Class for intercom status to check if the operation is success or failure. + /// If the operation failed then [onFailure] callback will be executed with + /// [IntercomError] details. + IntercomStatusCallback({ + this.onSuccess, + this.onFailure, + }); +} + +class IntercomError { + /// error code + final int errorCode; + + /// error message + final String errorMessage; + + /// Class for the Intercom error data. + IntercomError(this.errorCode, this.errorMessage); + + @override + String toString() { + return ("errorCode: $errorCode, errorMessage: $errorMessage"); + } +} diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/lib/method_channel_intercom_flutter.dart b/packages/intercom_flutter/intercom_flutter_platform_interface/lib/method_channel_intercom_flutter.dart new file mode 100644 index 000000000..43ab113b7 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/lib/method_channel_intercom_flutter.dart @@ -0,0 +1,265 @@ +import 'package:flutter/services.dart'; +import 'package:intercom_flutter_platform_interface/intercom_flutter_platform_interface.dart'; +import 'package:intercom_flutter_platform_interface/intercom_status_callback.dart'; + +const MethodChannel _channel = MethodChannel('maido.io/intercom'); +const EventChannel _unreadChannel = EventChannel('maido.io/intercom/unread'); + +/// An implementation of [IntercomFlutterPlatform] that uses method channels. +class MethodChannelIntercomFlutter extends IntercomFlutterPlatform { + @override + Future initialize( + String appId, { + String? androidApiKey, + String? iosApiKey, + }) async { + await _channel.invokeMethod('initialize', { + 'appId': appId, + 'androidApiKey': androidApiKey, + 'iosApiKey': iosApiKey, + }); + } + + @override + Stream getUnreadStream() { + return _unreadChannel.receiveBroadcastStream(); + } + + @override + Future setUserHash(String userHash) async { + await _channel.invokeMethod('setUserHash', {'userHash': userHash}); + } + + @override + Future loginIdentifiedUser({ + String? userId, + String? email, + IntercomStatusCallback? statusCallback, + }) async { + if (userId?.isNotEmpty ?? false) { + if (email?.isNotEmpty ?? false) { + throw ArgumentError( + 'The parameter `email` must be null if `userId` is provided.'); + } + try { + await _channel.invokeMethod('loginIdentifiedUserWithUserId', { + 'userId': userId, + }); + statusCallback?.onSuccess?.call(); + } on PlatformException catch (e) { + statusCallback?.onFailure?.call(_convertExceptionToIntercomError(e)); + } + } else if (email?.isNotEmpty ?? false) { + try { + await _channel.invokeMethod('loginIdentifiedUserWithEmail', { + 'email': email, + }); + statusCallback?.onSuccess?.call(); + } on PlatformException catch (e) { + statusCallback?.onFailure?.call(_convertExceptionToIntercomError(e)); + } + } else { + throw ArgumentError( + 'An identification method must be provided as a parameter, either `userId` or `email`.'); + } + } + + @override + Future loginUnidentifiedUser( + {IntercomStatusCallback? statusCallback}) async { + try { + await _channel.invokeMethod('loginUnidentifiedUser'); + statusCallback?.onSuccess?.call(); + } on PlatformException catch (e) { + statusCallback?.onFailure?.call(_convertExceptionToIntercomError(e)); + } + } + + @override + Future updateUser({ + String? email, + String? name, + String? phone, + String? company, + String? companyId, + String? userId, + int? signedUpAt, + String? language, + Map? customAttributes, + IntercomStatusCallback? statusCallback, + }) async { + try { + await _channel.invokeMethod('updateUser', { + 'email': email, + 'name': name, + 'phone': phone, + 'company': company, + 'companyId': companyId, + 'userId': userId, + 'signedUpAt': signedUpAt, + 'language': language, + 'customAttributes': customAttributes, + }); + statusCallback?.onSuccess?.call(); + } on PlatformException catch (e) { + statusCallback?.onFailure?.call(_convertExceptionToIntercomError(e)); + } + } + + @override + Future logout() async { + await _channel.invokeMethod('logout'); + } + + @override + Future setLauncherVisibility(IntercomVisibility visibility) async { + String visibilityString = + visibility == IntercomVisibility.visible ? 'VISIBLE' : 'GONE'; + await _channel.invokeMethod('setLauncherVisibility', { + 'visibility': visibilityString, + }); + } + + @override + Future unreadConversationCount() async { + final result = await _channel.invokeMethod('unreadConversationCount'); + return result ?? 0; + } + + @override + Future setInAppMessagesVisibility(IntercomVisibility visibility) async { + String visibilityString = + visibility == IntercomVisibility.visible ? 'VISIBLE' : 'GONE'; + await _channel.invokeMethod('setInAppMessagesVisibility', { + 'visibility': visibilityString, + }); + } + + @override + Future displayMessenger() async { + await _channel.invokeMethod('displayMessenger'); + } + + @override + Future hideMessenger() async { + await _channel.invokeMethod('hideMessenger'); + } + + @override + Future displayHelpCenter() async { + await _channel.invokeMethod('displayHelpCenter'); + } + + @override + Future displayHelpCenterCollections(List collectionIds) { + return _channel.invokeMethod( + 'displayHelpCenterCollections', {'collectionIds': collectionIds}); + } + + @override + Future displayMessages() async { + await _channel.invokeMethod('displayMessages'); + } + + @override + Future logEvent(String name, [Map? metaData]) async { + await _channel + .invokeMethod('logEvent', {'name': name, 'metaData': metaData}); + } + + @override + Future sendTokenToIntercom(String token) async { + assert(token.isNotEmpty); + print("Start sending token to Intercom"); + await _channel.invokeMethod('sendTokenToIntercom', {'token': token}); + } + + @override + Future handlePushMessage() async { + await _channel.invokeMethod('handlePushMessage'); + } + + @override + Future displayMessageComposer(String message) async { + await _channel.invokeMethod('displayMessageComposer', {'message': message}); + } + + @override + Future isIntercomPush(Map message) async { + if (!message.values.every((item) => item is String)) { + return false; + } + final result = await _channel + .invokeMethod('isIntercomPush', {'message': message}); + return result ?? false; + } + + @override + Future handlePush(Map message) async { + if (!message.values.every((item) => item is String)) { + throw new ArgumentError( + 'Intercom push messages can only have string values'); + } + + return await _channel + .invokeMethod('handlePush', {'message': message}); + } + + @override + Future setBottomPadding(int padding) async { + await _channel.invokeMethod('setBottomPadding', {'bottomPadding': padding}); + } + + @override + Future displayArticle(String articleId) async { + await _channel.invokeMethod('displayArticle', {'articleId': articleId}); + } + + @override + Future displayCarousel(String carouselId) async { + await _channel.invokeMethod('displayCarousel', {'carouselId': carouselId}); + } + + @override + Future displaySurvey(String surveyId) async { + await _channel.invokeMethod('displaySurvey', {'surveyId': surveyId}); + } + + @override + Future displayConversation(String conversationId) async { + await _channel.invokeMethod( + 'displayConversation', {'conversationId': conversationId}); + } + + @override + Future displayTickets() async { + await _channel.invokeMethod('displayTickets'); + } + + @override + Future displayHome() async { + await _channel.invokeMethod('displayHome'); + } + + @override + Future isUserLoggedIn() async { + return await _channel.invokeMethod('isUserLoggedIn') ?? false; + } + + @override + Future> fetchLoggedInUserAttributes() async { + var attributes = Map.from( + await _channel.invokeMethod('fetchLoggedInUserAttributes') ?? {}); + return attributes; + } + + /// Convert the [PlatformException] details to [IntercomError]. + /// From the Platform side if the intercom operation failed then error details + /// will be sent as details in [PlatformException]. + IntercomError _convertExceptionToIntercomError(PlatformException e) { + var details = e.details ?? {}; + + return IntercomError( + details['errorCode'] ?? -1, details['errorMessage'] ?? e.message ?? ""); + } +} diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/pubspec.yaml b/packages/intercom_flutter/intercom_flutter_platform_interface/pubspec.yaml new file mode 100644 index 000000000..0e086936f --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/pubspec.yaml @@ -0,0 +1,17 @@ +name: intercom_flutter_platform_interface +description: A common platform interface for the intercom_flutter plugin. +version: 2.0.2 +homepage: https://github.com/v3rm0n/intercom_flutter + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.1.4 + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: ^5.4.0 + +environment: + sdk: '>=3.0.0 <4.0.0' diff --git a/packages/intercom_flutter/intercom_flutter_platform_interface/test/method_channel_intercom_flutter_test.dart b/packages/intercom_flutter/intercom_flutter_platform_interface/test/method_channel_intercom_flutter_test.dart new file mode 100644 index 000000000..f5dae9cf3 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_platform_interface/test/method_channel_intercom_flutter_test.dart @@ -0,0 +1,468 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intercom_flutter_platform_interface/intercom_flutter_platform_interface.dart'; +import 'package:intercom_flutter_platform_interface/method_channel_intercom_flutter.dart'; +import 'package:mockito/mockito.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$IntercomFlutterPlatform', () { + test('$MethodChannelIntercomFlutter() is the default instance', () { + expect(IntercomFlutterPlatform.instance, + isInstanceOf()); + }); + + test('Cannot be implemented with `implements`', () { + expect(() { + IntercomFlutterPlatform.instance = ImplementsIntercomFlutterPlatform(); + }, throwsA(isInstanceOf())); + }); + + test('Can be mocked with `implements`', () { + final IntercomFlutterPlatformMock mock = IntercomFlutterPlatformMock(); + IntercomFlutterPlatform.instance = mock; + }); + + test('Can be extended', () { + IntercomFlutterPlatform.instance = ExtendsIntercomFlutterPlatform(); + }); + }); + + group('$MethodChannelIntercomFlutter', () { + const MethodChannel channel = MethodChannel('maido.io/intercom'); + final List log = []; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + log.add(methodCall); + + // Return null explicitly instead of relying on the implicit null + // returned by the method channel if no return statement is specified. + return null; + }); + + final MethodChannelIntercomFlutter intercom = + MethodChannelIntercomFlutter(); + + tearDown(() { + log.clear(); + }); + + test('initialize', () async { + final appId = 'mock'; + final androidApiKey = 'android-key'; + final iosApiKey = 'ios-key'; + + await intercom.initialize( + appId, + androidApiKey: androidApiKey, + iosApiKey: iosApiKey, + ); + + expect( + log, + [ + isMethodCall('initialize', arguments: { + 'appId': appId, + 'androidApiKey': androidApiKey, + 'iosApiKey': iosApiKey, + }) + ], + ); + }); + + test('testSendingAPNTokenToIntercom', () async { + await intercom.sendTokenToIntercom('mock_apn_token'); + expect( + log, + [ + isMethodCall('sendTokenToIntercom', arguments: { + 'token': 'mock_apn_token', + }) + ], + ); + }); + + group('loginIdentifiedUser', () { + test('with userId', () async { + await intercom.loginIdentifiedUser(userId: 'test'); + expect( + log, + [ + isMethodCall('loginIdentifiedUserWithUserId', arguments: { + 'userId': 'test', + }) + ], + ); + }); + + test('with email', () async { + await intercom.loginIdentifiedUser(email: 'test'); + expect( + log, + [ + isMethodCall('loginIdentifiedUserWithEmail', arguments: { + 'email': 'test', + }) + ], + ); + }); + + test('with userId and email should fail', () { + expect( + () => intercom.loginIdentifiedUser( + userId: 'testId', + email: 'testEmail', + ), + throwsArgumentError, + ); + }); + + test('without parameters', () { + expect(() => intercom.loginIdentifiedUser(), throwsArgumentError); + }); + }); + + test('loginUnidentifiedUser', () async { + await intercom.loginUnidentifiedUser(); + expect( + log, + [isMethodCall('loginUnidentifiedUser', arguments: null)], + ); + }); + + test('setBottomPadding', () async { + final padding = 64; + await intercom.setBottomPadding(padding); + expect( + log, + [ + isMethodCall('setBottomPadding', arguments: { + 'bottomPadding': padding, + }) + ], + ); + }); + + test('setUserHash', () async { + await intercom.setUserHash('test'); + expect( + log, + [ + isMethodCall('setUserHash', arguments: { + 'userHash': 'test', + }) + ], + ); + }); + + test('logout', () async { + await intercom.logout(); + expect( + log, + [isMethodCall('logout', arguments: null)], + ); + }); + + group('toggleMessengerVisibility', () { + test('displayMessenger', () async { + await intercom.displayMessenger(); + expect( + log, + [isMethodCall('displayMessenger', arguments: null)], + ); + }); + + test('hideMessenger', () async { + await intercom.hideMessenger(); + expect( + log, + [isMethodCall('hideMessenger', arguments: null)], + ); + }); + }); + + test('displayHelpCenter', () async { + await intercom.displayHelpCenter(); + expect( + log, + [isMethodCall('displayHelpCenter', arguments: null)], + ); + }); + + test('displayHelpCenter', () async { + final collectionIds = ['collectionId1', 'collectionId2']; + await intercom.displayHelpCenterCollections(collectionIds); + expect( + log, + [ + isMethodCall('displayHelpCenterCollections', + arguments: {'collectionIds': collectionIds}) + ], + ); + }); + + test('displayMessages', () async { + await intercom.displayMessages(); + expect( + log, + [isMethodCall('displayMessages', arguments: null)], + ); + }); + + test('unreadConversationCount', () async { + await intercom.unreadConversationCount(); + expect( + log, + [isMethodCall('unreadConversationCount', arguments: null)], + ); + }); + + test('displayMessageComposer', () async { + await intercom.displayMessageComposer("message"); + expect( + log, + [ + isMethodCall( + 'displayMessageComposer', + arguments: {"message": "message"}, + ) + ], + ); + }); + + group('setInAppMessagesVisibility', () { + test('visible', () async { + await intercom.setInAppMessagesVisibility(IntercomVisibility.visible); + expect( + log, + [ + isMethodCall('setInAppMessagesVisibility', arguments: { + 'visibility': 'VISIBLE', + }) + ], + ); + }); + + test('gone', () async { + await intercom.setInAppMessagesVisibility(IntercomVisibility.gone); + expect( + log, + [ + isMethodCall('setInAppMessagesVisibility', arguments: { + 'visibility': 'GONE', + }) + ], + ); + }); + }); + + group('setLauncherVisibility', () { + test('visible', () async { + await intercom.setLauncherVisibility(IntercomVisibility.visible); + expect( + log, + [ + isMethodCall('setLauncherVisibility', arguments: { + 'visibility': 'VISIBLE', + }) + ], + ); + }); + + test('gone', () async { + await intercom.setLauncherVisibility(IntercomVisibility.gone); + expect( + log, + [ + isMethodCall('setLauncherVisibility', arguments: { + 'visibility': 'GONE', + }) + ], + ); + }); + }); + + test('updateUser', () async { + await intercom.updateUser( + email: 'test@example.com', + name: 'John Doe', + userId: '1', + phone: '+37256123456', + company: 'Some Company LLC', + companyId: '2', + signedUpAt: 1590949800, + language: 'en', + ); + expect( + log, + [ + isMethodCall('updateUser', arguments: { + 'email': 'test@example.com', + 'name': 'John Doe', + 'userId': '1', + 'phone': '+37256123456', + 'company': 'Some Company LLC', + 'companyId': '2', + 'signedUpAt': 1590949800, + 'language': 'en', + 'customAttributes': null, + }) + ], + ); + }); + + group('logEvent', () { + test('withoutMetaData', () async { + await intercom.logEvent("TEST"); + expect( + log, + [ + isMethodCall('logEvent', arguments: { + 'name': 'TEST', + 'metaData': null, + }) + ], + ); + }); + + test('withMetaData', () async { + await intercom.logEvent( + "TEST", + {'string': 'A string', 'number': 10, 'bool': true}, + ); + expect( + log, + [ + isMethodCall('logEvent', arguments: { + 'name': 'TEST', + 'metaData': {'string': 'A string', 'number': 10, 'bool': true}, + }) + ], + ); + }); + }); + + group('UnreadMessageCount', () { + const String channelName = 'maido.io/intercom/unread'; + const MethodChannel channel = MethodChannel(channelName); + final int value = 9; + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + channelName, + const StandardMethodCodec().encodeSuccessEnvelope(value), + (ByteData? data) {}, + ); + return; + }); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + test('testStream', () async { + expect(await intercom.getUnreadStream().first, value); + }); + }); + + String testArticleId = "123456"; + test('displayArticle', () async { + await intercom.displayArticle(testArticleId); + expect( + log, + [ + isMethodCall('displayArticle', arguments: { + 'articleId': testArticleId, + }) + ], + ); + }); + + String testCarouselId = "123456"; + test('displayCarousel', () async { + await intercom.displayCarousel(testCarouselId); + expect( + log, + [ + isMethodCall('displayCarousel', arguments: { + 'carouselId': testCarouselId, + }) + ], + ); + }); + + String testSurveyId = "123456"; + test('displaySurvey', () async { + await intercom.displaySurvey(testSurveyId); + expect( + log, + [ + isMethodCall('displaySurvey', arguments: { + 'surveyId': testSurveyId, + }) + ], + ); + }); + + String testConversationId = "123456"; + test('displayConversation', () async { + await intercom.displayConversation(testConversationId); + expect( + log, + [ + isMethodCall('displayConversation', arguments: { + 'conversationId': testConversationId, + }) + ], + ); + }); + + test('displayTickets', () async { + await intercom.displayTickets(); + expect( + log, + [isMethodCall('displayTickets', arguments: null)], + ); + }); + + test('displayHome', () async { + await intercom.displayHome(); + expect( + log, + [isMethodCall('displayHome', arguments: null)], + ); + }); + + test('isUserLoggedIn', () async { + await intercom.isUserLoggedIn(); + expect( + log, + [isMethodCall('isUserLoggedIn', arguments: null)], + ); + }); + + test('fetchLoggedInUserAttributes', () async { + await intercom.fetchLoggedInUserAttributes(); + expect( + log, + [isMethodCall('fetchLoggedInUserAttributes', arguments: null)], + ); + }); + }); +} + +class IntercomFlutterPlatformMock extends Mock + with MockPlatformInterfaceMixin + implements IntercomFlutterPlatform {} + +class ImplementsIntercomFlutterPlatform extends Mock + implements IntercomFlutterPlatform {} + +class ExtendsIntercomFlutterPlatform extends IntercomFlutterPlatform {} diff --git a/packages/intercom_flutter/intercom_flutter_web/.gitignore b/packages/intercom_flutter/intercom_flutter_web/.gitignore new file mode 100644 index 000000000..5e0743224 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/.gitignore @@ -0,0 +1,14 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ +pubspec.lock + +build/ +.idea/ +android/bin/ +example/.flutter-plugins-dependencies +example/.flutter-plugins +example/lib/generated_plugin_registrant.dart +example/ios/Flutter/flutter_export_environment.sh diff --git a/packages/intercom_flutter/intercom_flutter_web/CHANGELOG.md b/packages/intercom_flutter/intercom_flutter_web/CHANGELOG.md new file mode 100755 index 000000000..39290fcec --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/CHANGELOG.md @@ -0,0 +1,100 @@ +# Changelog + +## 1.1.5 + +* Set user_hash, user_id, email in intercomSettings. +* Implemented methods `isUserLoggedIn` and `fetchLoggedInUserAttributes`. + +## 1.1.4 + +* Updated dependency `intercom_flutter_platform_interface: ^2.0.2`. + +## 1.1.3 + +* Implemented method `displayHome`. + +## 1.1.2 + +* Bump `web` to `^1.0.0` + +## 1.1.1 + +* Updated window.intercomSettings as Map instead of JSObject. + +## 1.1.0 + +* Migrated to js_interop to be compatible with WASM. + +## 1.0.2 + +* Automatically Injected Intercom script, if it is not added. + +## 1.0.1 + +* Updated dependency `uuid: ^4.2.1`. + +## 1.0.0 + +* Removed deprecated methods `registerIdentifiedUser` and `registerUnidentifiedUser`. + +## 0.3.3 + +* Implemented method `displayTickets`. + +## 0.3.2 + +* Implemented method `displayConversation`. + +## 0.3.1 + +* Implemented method `displayHelpCenter`. + +## 0.3.0 + +* Update minimum Dart version to Dart 3. + +## 0.2.3 + +* Updated dependency `intercom_flutter_platform_interface: ^1.2.3`. + +## 0.2.2 + +* Added method `displayMessages`. + +## 0.2.1 + +* Updated dependencies + +## 0.2.0 + +* Updated dependency `intercom_flutter_platform_interface: ^1.2.0`. +* Implemented `displaySurvey` [(startSurvey)](https://developers.intercom.com/installing-intercom/docs/intercom-javascript#intercomstartsurvey-surveyid) + +## 0.1.0 + +* Added method `loginIdentifiedUser` with `IntercomStatusCallback` support. +* Deprecated `registerIdentifiedUser` in favor of `loginIdentifiedUser`. +* Added method `loginUnidentifiedUser` with `IntercomStatusCallback` support. +* Deprecated `registerUnidentifiedUser` in favor of `loginUnidentifiedUser`. +* Added parameter `statusCallback` in updateUser to support `IntercomStatusCallback`. +* Updated `intercom_flutter_platform_interface` version to `1.1.0`. + +## 0.0.5 + +* Implemented `displayArticle` [(showArticle)](https://developers.intercom.com/installing-intercom/docs/intercom-javascript#intercomshowarticle-articleid) + +## 0.0.4 + +* Updated dependency `intercom_flutter_platform_interface: ^1.0.1` + +## 0.0.3 + +* Resolved issue [#173](https://github.com/v3rm0n/intercom_flutter/issues/173) + +## 0.0.2 + +* Updated dependency intercom_flutter_platform_interface: ^1.0.0 + +## 0.0.1 + +* Initial open source release diff --git a/packages/intercom_flutter/intercom_flutter_web/LICENSE b/packages/intercom_flutter/intercom_flutter_web/LICENSE new file mode 100644 index 000000000..fface4a6c --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 xChange OÜ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/intercom_flutter/intercom_flutter_web/README.md b/packages/intercom_flutter/intercom_flutter_web/README.md new file mode 100755 index 000000000..c8e14acc2 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/README.md @@ -0,0 +1,31 @@ +# intercom_flutter_web + +The web implementation of [`intercom_flutter`][1]. + +## Usage + +This package is already included as part of the `intercom_flutter` package dependency, and will be included when using `intercom_flutter` as normal. + +But if you want to use this package as alone, add the dependency `intercom_flutter_web`. +You don't need to add Intercom script in the index.html file, it will be automatically injected. +But you can pre-define some Intercom settings, if you want (optional). +```html + +``` +#### Following functions are not yet supported on Web: + +- unreadConversationCount +- setInAppMessagesVisibility +- sendTokenToIntercom +- handlePushMessage +- isIntercomPush +- handlePush +- displayCarousel +- displayHelpCenterCollections + +[1]: ../intercom_flutter + diff --git a/packages/intercom_flutter/intercom_flutter_web/analysis_options.yml b/packages/intercom_flutter/intercom_flutter_web/analysis_options.yml new file mode 100644 index 000000000..4135297cc --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/analysis_options.yml @@ -0,0 +1,4 @@ +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false diff --git a/packages/intercom_flutter/intercom_flutter_web/example/.gitignore b/packages/intercom_flutter/intercom_flutter_web/example/.gitignore new file mode 100644 index 000000000..8470f198e --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ +pubspec.lock + +build/ +.idea/ diff --git a/packages/intercom_flutter/intercom_flutter_web/example/README.md b/packages/intercom_flutter/intercom_flutter_web/example/README.md new file mode 100755 index 000000000..00ce76537 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/README.md @@ -0,0 +1,21 @@ +# Testing + +This package utilizes the `integration_test` package to run its tests in a web browser. + +See [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. + +## Running the tests + +Make sure you have updated to the latest Flutter master. + +1. Check what version of Chrome is running on the machine you're running tests on. + +2. Download and install driver for that version from here: + * + +3. Start the driver using `chromedriver --port=4444` + +4. Run tests: `flutter drive -d web-server --browser-name=chrome --driver=test_driver/integration_test.dart --target=integration_test/intercom_flutter_web_test.dart`, or (in Linux): + + * Single: `./run_test.sh integration_test/TEST_NAME.dart` + * All: `./run_test.sh` diff --git a/packages/intercom_flutter/intercom_flutter_web/example/build.yaml b/packages/intercom_flutter/intercom_flutter_web/example/build.yaml new file mode 100755 index 000000000..db3104bb0 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + sources: + - integration_test/*.dart + - lib/$lib$ + - $package$ diff --git a/packages/intercom_flutter/intercom_flutter_web/example/integration_test/intercom_flutter_web_test.dart b/packages/intercom_flutter/intercom_flutter_web/example/integration_test/intercom_flutter_web_test.dart new file mode 100755 index 000000000..9a7939826 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/integration_test/intercom_flutter_web_test.dart @@ -0,0 +1,150 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:intercom_flutter_web/intercom_flutter_web.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('IntercomFlutter', () { + late IntercomFlutterWeb plugin; + + setUp(() { + plugin = IntercomFlutterWeb(); + }); + + testWidgets("initialize", (WidgetTester _) async { + expect(plugin.initialize("mock"), completes); + }); + + group('loginIdentifiedUser', () { + testWidgets('with userId', (WidgetTester _) async { + expect(plugin.loginIdentifiedUser(userId: 'test'), completes); + }); + + testWidgets('with email', (WidgetTester _) async { + expect(plugin.loginIdentifiedUser(email: 'test'), completes); + }); + + testWidgets('with userId and email should fail', (WidgetTester _) async { + expect(plugin.loginIdentifiedUser(userId: 'testId', email: 'testEmail'), + throwsArgumentError); + }); + + testWidgets('without parameters', (WidgetTester _) async { + expect(plugin.loginIdentifiedUser(), throwsArgumentError); + }); + }); + + testWidgets('loginUnidentifiedUser', (WidgetTester _) async { + expect(plugin.loginUnidentifiedUser(), completes); + }); + + testWidgets('setBottomPadding', (WidgetTester _) async { + expect(plugin.setBottomPadding(64), completes); + }); + + testWidgets('setUserHash', (WidgetTester _) async { + expect(plugin.setUserHash('test'), completes); + }); + + testWidgets('logout', (WidgetTester _) async { + expect(plugin.logout(), completes); + }); + + group('toggleMessengerVisibility', () { + testWidgets('displayMessenger', (WidgetTester _) async { + expect(plugin.displayMessenger(), completes); + }); + + testWidgets('hideMessenger', (WidgetTester _) async { + expect(plugin.hideMessenger(), completes); + }); + }); + + testWidgets('displayMessages', (WidgetTester _) async { + expect(plugin.displayMessages(), completes); + }); + + testWidgets('displayHelpCenter', (WidgetTester _) async { + expect(plugin.displayHelpCenter(), completes); + }); + + testWidgets('displayMessageComposer', (WidgetTester _) async { + expect(plugin.displayMessageComposer("message"), completes); + }); + + group('setLauncherVisibility', () { + testWidgets('visible', (WidgetTester _) async { + expect(plugin.setLauncherVisibility(IntercomVisibility.visible), + completes); + }); + + testWidgets('gone', (WidgetTester _) async { + expect( + plugin.setLauncherVisibility(IntercomVisibility.gone), completes); + }); + }); + + testWidgets('updateUser', (WidgetTester _) async { + expect( + plugin.updateUser( + email: 'test@example.com', + name: 'John Doe', + userId: '1', + phone: '+37256123456', + company: 'Some Company LLC', + companyId: '2', + signedUpAt: 1590949800, + language: 'en', + ), + completes); + }); + + group('logEvent', () { + testWidgets('withoutMetaData', (WidgetTester _) async { + expect(plugin.logEvent("TEST"), completes); + }); + + testWidgets('withMetaData', (WidgetTester _) async { + expect( + plugin.logEvent( + "TEST", + {'string': 'A string', 'number': 10, 'bool': true}, + ), + completes); + }); + }); + + testWidgets('testStream', (WidgetTester _) async { + expect(plugin.getUnreadStream().first, completes); + }); + + testWidgets('displayArticle', (WidgetTester _) async { + expect(plugin.displayArticle("123456"), completes); + }); + + testWidgets('displaySurvey', (WidgetTester _) async { + expect(plugin.displaySurvey("123456"), completes); + }); + + testWidgets('displayConversation', (WidgetTester _) async { + expect(plugin.displayConversation("123456"), completes); + }); + + testWidgets('displayTickets', (WidgetTester _) async { + expect(plugin.displayTickets(), completes); + }); + + testWidgets('displayHome', (WidgetTester _) async { + expect(plugin.displayHome(), completes); + }); + + testWidgets('isUserLoggedIn', (WidgetTester _) async { + expect(plugin.isUserLoggedIn(), completes); + }); + + testWidgets('fetchLoggedInUserAttributes', (WidgetTester _) async { + expect(plugin.fetchLoggedInUserAttributes(), completes); + }); + }); +} diff --git a/packages/intercom_flutter/intercom_flutter_web/example/lib/main.dart b/packages/intercom_flutter/intercom_flutter_web/example/lib/main.dart new file mode 100755 index 000000000..bf0030fb2 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/lib/main.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(MyApp()); +} + +/// App for testing +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: Text('Testing... Look at the console output for results!'), + ); + } +} diff --git a/packages/intercom_flutter/intercom_flutter_web/example/pubspec.yaml b/packages/intercom_flutter/intercom_flutter_web/example/pubspec.yaml new file mode 100755 index 000000000..782789ab3 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/pubspec.yaml @@ -0,0 +1,19 @@ +name: regular_integration_tests +publish_to: none + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + intercom_flutter_web: + path: ../ + flutter_driver: + sdk: flutter + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +environment: + sdk: ">=3.0.0 <4.0.0" diff --git a/packages/intercom_flutter/intercom_flutter_web/example/run_test.sh b/packages/intercom_flutter/intercom_flutter_web/example/run_test.sh new file mode 100755 index 000000000..dabf9a863 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/run_test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/bash +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +if pgrep -lf chromedriver > /dev/null; then + echo "chromedriver is running." + + flutter pub get + + echo "(Re)generating mocks." + flutter pub run build_runner build --delete-conflicting-outputs + + if [ $# -eq 0 ]; then + echo "No target specified, running all tests..." + find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' + else + echo "Running test target: $1..." + set -x + flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 + fi + + else + echo "chromedriver is not running." + echo "Please, check the README.md for instructions on how to use run_test.sh" +fi + diff --git a/packages/intercom_flutter/intercom_flutter_web/example/test_driver/integration_test.dart b/packages/intercom_flutter/intercom_flutter_web/example/test_driver/integration_test.dart new file mode 100755 index 000000000..4045054df --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/test_driver/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() async => integrationDriver(); diff --git a/packages/intercom_flutter/intercom_flutter_web/example/web/index.html b/packages/intercom_flutter/intercom_flutter_web/example/web/index.html new file mode 100755 index 000000000..3eb011545 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/example/web/index.html @@ -0,0 +1,15 @@ + + + + Browser Tests + + + + + + diff --git a/packages/intercom_flutter/intercom_flutter_web/lib/intercom_flutter_web.dart b/packages/intercom_flutter/intercom_flutter_web/lib/intercom_flutter_web.dart new file mode 100644 index 000000000..790033011 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/lib/intercom_flutter_web.dart @@ -0,0 +1,369 @@ +import 'dart:async'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:intercom_flutter_platform_interface/intercom_flutter_platform_interface.dart'; +import 'package:intercom_flutter_platform_interface/intercom_status_callback.dart'; +import 'package:uuid/uuid.dart'; +import 'package:web/web.dart' as web; + +/// export the enum [IntercomVisibility] +export 'package:intercom_flutter_platform_interface/intercom_flutter_platform_interface.dart' + show IntercomVisibility; +export 'package:intercom_flutter_platform_interface/intercom_status_callback.dart' + show IntercomStatusCallback, IntercomError; + +/// A web implementation of the IntercomFlutter plugin. +class IntercomFlutterWeb extends IntercomFlutterPlatform { + static void registerWith(Registrar registrar) { + IntercomFlutterPlatform.instance = IntercomFlutterWeb(); + } + + @override + Stream getUnreadStream() { + // It's fine to let the StreamController be garbage collected once all the + // subscribers have cancelled; this analyzer warning is safe to ignore. + // ignore: close_sinks + StreamController _unreadController = + StreamController.broadcast(); + _unreadController.onListen = () { + globalContext.callMethod( + 'Intercom'.toJS, + 'onUnreadCountChange'.toJS, + ((JSAny unreadCount) { + _unreadController.add(unreadCount); + }).toJS, + ); + }; + return _unreadController.stream; + } + + @override + Future initialize( + String appId, { + String? androidApiKey, + String? iosApiKey, + }) async { + if (globalContext.getProperty('Intercom'.toJS) == null) { + // Intercom script is not added yet + // Inject it from here in the body + web.HTMLScriptElement script = + web.document.createElement("script") as web.HTMLScriptElement; + script.text = """ + window.intercomSettings = ${updateIntercomSettings('app_id', "'$appId'")}; + (function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',w.intercomSettings);}else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};w.Intercom=i;var l=function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/' + '$appId';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s, x);};if(document.readyState==='complete'){l();}else if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})(); + """; + if (web.document.body != null) { + web.document.body!.appendChild(script); + } + } else { + // boot the Intercom + globalContext.callMethod( + 'Intercom'.toJS, + 'boot'.toJS, + updateIntercomSettings('app_id', appId).jsify(), + ); + } + print("initialized"); + } + + @override + Future setUserHash(String userHash) async { + globalContext.callMethod( + 'Intercom'.toJS, + 'update'.toJS, + updateIntercomSettings('user_hash', userHash).jsify(), + ); + print("user hash added"); + } + + @override + Future loginIdentifiedUser({ + String? userId, + String? email, + IntercomStatusCallback? statusCallback, + }) async { + if (userId?.isNotEmpty ?? false) { + if (email?.isNotEmpty ?? false) { + throw ArgumentError( + 'The parameter `email` must be null if `userId` is provided.'); + } + // register the user with userId + globalContext.callMethod( + 'Intercom'.toJS, + 'update'.toJS, + updateIntercomSettings('user_id', userId).jsify(), + ); + // send the success callback only as web does not support the statusCallback. + statusCallback?.onSuccess?.call(); + } else if (email?.isNotEmpty ?? false) { + // register the user with email + globalContext.callMethod( + 'Intercom'.toJS, + 'update'.toJS, + updateIntercomSettings('email', email).jsify(), + ); + // send the success callback only as web does not support the statusCallback. + statusCallback?.onSuccess?.call(); + } else { + throw ArgumentError( + 'An identification method must be provided as a parameter, either `userId` or `email`.'); + } + } + + @override + Future loginUnidentifiedUser( + {IntercomStatusCallback? statusCallback}) async { + // to register an unidentified user, a unique id will be created using the package uuid + String userId = Uuid().v1(); + globalContext.callMethod( + 'Intercom'.toJS, + 'update'.toJS, + updateIntercomSettings('user_id', userId).jsify(), + ); + // send the success callback only as web does not support the statusCallback. + statusCallback?.onSuccess?.call(); + } + + @override + Future updateUser({ + String? email, + String? name, + String? phone, + String? company, + String? companyId, + String? userId, + int? signedUpAt, + String? language, + Map? customAttributes, + IntercomStatusCallback? statusCallback, + }) async { + Map userAttributes = {}; + + if (name != null) { + userAttributes['name'] = name; + } + + if (email != null) { + userAttributes['email'] = email; + } + + if (phone != null) { + userAttributes['phone'] = phone; + } + + if (userId != null) { + userAttributes['user_id'] = userId; + } + + if (company != null && companyId != null) { + Map companyObj = {}; + companyObj['company_id'] = companyId; + companyObj['name'] = company; + + userAttributes['company'] = companyObj; + } + + if (customAttributes != null) { + customAttributes.forEach((key, value) { + userAttributes[key] = value; + }); + } + + if (signedUpAt != null) { + userAttributes['created_at'] = signedUpAt; + } + + if (language != null) { + userAttributes['language_override'] = language; + } + + globalContext.callMethod( + 'Intercom'.toJS, 'update'.toJS, userAttributes.jsify()); + // send the success callback only as web does not support the statusCallback. + statusCallback?.onSuccess?.call(); + } + + @override + Future logEvent(String name, [Map? metaData]) async { + globalContext.callMethod( + 'Intercom'.toJS, + 'trackEvent'.toJS, + name.toJS, + metaData != null ? metaData.jsify() : null, + ); + + print("Logged event"); + } + + @override + Future setLauncherVisibility(IntercomVisibility visibility) async { + globalContext.callMethod( + 'Intercom'.toJS, + 'update'.toJS, + updateIntercomSettings( + 'hide_default_launcher', + visibility == IntercomVisibility.visible ? false : true, + ).jsify(), + ); + + print("Showing launcher: $visibility"); + } + + @override + Future logout() async { + // shutdown will effectively clear out any user data that you have been passing through the JS API. + // but not from intercomSettings + // so manually clear some intercom settings + removeIntercomSettings(['user_hash', 'user_id', 'email']); + // shutdown + globalContext.callMethod('Intercom'.toJS, 'shutdown'.toJS); + print("logout"); + } + + @override + Future displayMessenger() async { + globalContext.callMethod('Intercom'.toJS, 'show'.toJS); + print("Launched"); + } + + @override + Future hideMessenger() async { + globalContext.callMethod('Intercom'.toJS, 'hide'.toJS); + print("Hidden"); + } + + @override + Future displayHelpCenter() async { + globalContext.callMethod('Intercom'.toJS, 'showSpace'.toJS, 'help'.toJS); + print("Launched the Help Center"); + } + + @override + Future displayMessageComposer(String message) async { + globalContext.callMethod( + 'Intercom'.toJS, 'showNewMessage'.toJS, message.toJS); + print("Message composer displayed"); + } + + @override + Future displayMessages() async { + globalContext.callMethod('Intercom'.toJS, 'showMessages'.toJS); + print("Launched the Messenger with the message list"); + } + + @override + Future setBottomPadding(int padding) async { + globalContext.callMethod( + 'Intercom'.toJS, + 'update'.toJS, + updateIntercomSettings( + 'vertical_padding', + padding, + ).jsify(), + ); + + print("Bottom padding set"); + } + + @override + Future displayArticle(String articleId) async { + globalContext.callMethod( + 'Intercom'.toJS, 'showArticle'.toJS, articleId.toJS); + } + + @override + Future displaySurvey(String surveyId) async { + globalContext.callMethod( + 'Intercom'.toJS, 'startSurvey'.toJS, surveyId.toJS); + } + + @override + Future displayConversation(String conversationId) async { + globalContext.callMethod( + 'Intercom'.toJS, 'showConversation'.toJS, conversationId.toJS); + } + + @override + Future displayTickets() async { + globalContext.callMethod('Intercom'.toJS, 'showSpace'.toJS, 'tickets'.toJS); + print("Launched Tickets space"); + } + + @override + Future displayHome() async { + globalContext.callMethod('Intercom'.toJS, 'showSpace'.toJS, 'home'.toJS); + print("Launched Home space"); + } + + @override + Future isUserLoggedIn() async { + // There is no direct JS API available + // Here we check if intercomSettings has user_id or email then user is + // logged in + var settings = getIntercomSettings(); + var user_id = settings['user_id'] as String? ?? ""; + var email = settings['email'] as String? ?? ""; + + return user_id.isNotEmpty || email.isNotEmpty; + } + + @override + Future> fetchLoggedInUserAttributes() async { + // There is no direct JS API available + // Just return the user_id or email from intercomSettings + var settings = getIntercomSettings(); + var user_id = settings['user_id'] as String? ?? ""; + var email = settings['email'] as String? ?? ""; + + if (user_id.isNotEmpty) { + return {'user_id': user_id}; + } else if (email.isNotEmpty) { + return {'email': email}; + } + + return {}; + } + + /// get the [window.intercomSettings] + Map getIntercomSettings() { + if (globalContext.hasProperty('intercomSettings'.toJS).toDart) { + var settings = + globalContext.getProperty('intercomSettings'.toJS).dartify(); + // settings are of type LinkedMap + return settings as Map; + } + + return {}; + } + + /// add/update property to [window.intercomSettings] + /// and returns the updated object + Map updateIntercomSettings(String key, dynamic value) { + var intercomSettings = getIntercomSettings(); + intercomSettings[key] = value; + + // Update the [window.intercomSettings] + globalContext.setProperty( + "intercomSettings".toJS, intercomSettings.jsify()); + + return intercomSettings; + } + + /// Remove properties from [window.intercomSettings] + Map removeIntercomSettings(List keys) { + var intercomSettings = getIntercomSettings(); + + // remove the keys + for (var key in keys) { + intercomSettings.remove(key); + } + + // Update the [window.intercomSettings] + globalContext.setProperty( + "intercomSettings".toJS, intercomSettings.jsify()); + + return intercomSettings; + } +} diff --git a/packages/intercom_flutter/intercom_flutter_web/pubspec.yaml b/packages/intercom_flutter/intercom_flutter_web/pubspec.yaml new file mode 100644 index 000000000..45e0bf4bc --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/pubspec.yaml @@ -0,0 +1,29 @@ +name: intercom_flutter_web +description: Web platform implementation of intercom_flutter +version: 1.1.5 +homepage: https://github.com/v3rm0n/intercom_flutter + +flutter: + plugin: + platforms: + web: + pluginClass: IntercomFlutterWeb + fileName: intercom_flutter_web.dart + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + intercom_flutter_platform_interface: ^2.0.2 + uuid: ^4.2.1 # to get the random uuid for loginUnidentifiedUser in web + web: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: ^5.4.0 + +environment: + sdk: ">=3.2.0 <4.0.0" + flutter: ">=3.10.0" \ No newline at end of file diff --git a/packages/intercom_flutter/intercom_flutter_web/test/README.md b/packages/intercom_flutter/intercom_flutter_web/test/README.md new file mode 100755 index 000000000..7c5b4ad68 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/test/README.md @@ -0,0 +1,5 @@ +## test + +This package uses integration tests for testing. + +See `example/README.md` for more info. diff --git a/packages/intercom_flutter/intercom_flutter_web/test/tests_exist_elsewhere_test.dart b/packages/intercom_flutter/intercom_flutter_web/test/tests_exist_elsewhere_test.dart new file mode 100755 index 000000000..442c50144 --- /dev/null +++ b/packages/intercom_flutter/intercom_flutter_web/test/tests_exist_elsewhere_test.dart @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('Tell the user where to find the real tests', () { + print('---'); + print('This package uses integration_test for its tests.'); + print('See `example/README.md` for more info.'); + print('---'); + }); +}