From 61312d2ce2c793e8ba73a52d14222a5b20daef00 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Mon, 23 Dec 2024 18:55:33 +0500 Subject: [PATCH 1/5] added moenage stuff --- .../ensemble/lib/action/moengage_actions.dart | 382 ++++++++++++++++ modules/ensemble/lib/framework/action.dart | 4 + .../lib/framework/stub/moengage_manager.dart | 336 ++++++++++++++ modules/ensemble/pubspec.yaml | 1 + modules/moengage/LICENSE | 28 ++ modules/moengage/README.md | 16 + modules/moengage/lib/moengage.dart | 432 ++++++++++++++++++ .../lib/moengage_notification_handler.dart | 94 ++++ .../melos_ensemble_firebase_analytics.iml | 29 ++ modules/moengage/melos_ensemble_moengage.iml | 29 ++ modules/moengage/pubspec.yaml | 103 +++++ starter/android/app/build.gradle | 8 + starter/ensemble/ensemble.properties | 3 +- starter/pubspec.yaml | 6 + 14 files changed, 1470 insertions(+), 1 deletion(-) create mode 100644 modules/ensemble/lib/action/moengage_actions.dart create mode 100644 modules/ensemble/lib/framework/stub/moengage_manager.dart create mode 100644 modules/moengage/LICENSE create mode 100644 modules/moengage/README.md create mode 100644 modules/moengage/lib/moengage.dart create mode 100644 modules/moengage/lib/moengage_notification_handler.dart create mode 100644 modules/moengage/melos_ensemble_firebase_analytics.iml create mode 100644 modules/moengage/melos_ensemble_moengage.iml create mode 100644 modules/moengage/pubspec.yaml diff --git a/modules/ensemble/lib/action/moengage_actions.dart b/modules/ensemble/lib/action/moengage_actions.dart new file mode 100644 index 000000000..7049e5683 --- /dev/null +++ b/modules/ensemble/lib/action/moengage_actions.dart @@ -0,0 +1,382 @@ +import 'package:ensemble/framework/action.dart'; +import 'package:ensemble/framework/error_handling.dart'; +import 'package:ensemble/framework/event.dart'; +import 'package:ensemble/framework/extensions.dart'; +import 'package:ensemble/framework/scope.dart'; +import 'package:ensemble/framework/stub/moengage_manager.dart'; +import 'package:ensemble/screen_controller.dart'; +import 'package:ensemble/util/utils.dart'; +import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:moengage_flutter/moengage_flutter.dart'; + +// Main action types available for MoEngage +enum MoEngageActionType { + // User attributes + setUser, // Generic action to set any user attribute + + // Events + trackEvent, + + // App State + appConfig, + + // Push/Notification + pushConfig, + + // InApp & Cards + displayConfig +} + +// Sub-types for setUser action +enum MoEngageUserAttributeType { + uniqueId, + userName, + firstName, + lastName, + email, + mobile, + gender, + birthday, + location, + alias, + custom, // For custom attribute with key-value + timestamp, + locationAttribute +} + +// Sub-types for app configuration +enum MoEngageAppConfigType { + enableSdk, + disableSdk, + enableDataTracking, + disableDataTracking, + enableDeviceIdTracking, + disableDeviceIdTracking, + enableAndroidIdTracking, + disableAndroidIdTracking, + enableAdIdTracking, + disableAdIdTracking, + setAppStatus, + logout, + deleteUser +} + +// Sub-types for push notification configuration +enum MoEngagePushConfigType { + registerForPush, + registerForProvisionalPush, + passFCMToken, + passPushKitToken, + passFCMPushPayload, + requestPushPermission, + updatePermissionCount, + pushPermissionResponse +} + +// Sub-types for display configuration +enum MoEngageDisplayConfigType { + showInApp, + showNudge, + setContext, + resetContext +} + +class MoEngageAction extends EnsembleAction { + final MoEngageActionType? type; + final dynamic actionType; // Sub type enum based on main type + final dynamic value; + final Map? properties; // For events and custom attributes + final String? attributeKey; // For custom user attributes + final EnsembleAction? onSuccess; + final EnsembleAction? onError; + + MoEngageAction( + {super.initiator, + required this.type, + this.actionType, + this.value, + this.properties, + this.attributeKey, + this.onSuccess, + this.onError}); + + factory MoEngageAction.fromYaml({Invokable? initiator, Map? payload}) { + if (payload == null || payload['type'] == null) { + throw ConfigError('MoEngage action requires type'); + } + + return MoEngageAction( + initiator: initiator, + type: MoEngageActionType.values.from(payload['type']) ?? null, + actionType: _getSubType(payload['type'], payload['actionType']), + value: payload['value'], + properties: Utils.getMap(payload['properties']), + attributeKey: Utils.optionalString(payload['attributeKey']), + onSuccess: EnsembleAction.from(payload['onSuccess']), + onError: EnsembleAction.from(payload['onError'])); + } + + static dynamic _getSubType(String mainType, String? subType) { + if (subType == null) return null; + + switch (MoEngageActionType.values.from(mainType)) { + case MoEngageActionType.setUser: + return MoEngageUserAttributeType.values.from(subType); + case MoEngageActionType.appConfig: + return MoEngageAppConfigType.values.from(subType); + case MoEngageActionType.pushConfig: + return MoEngagePushConfigType.values.from(subType); + case MoEngageActionType.displayConfig: + return MoEngageDisplayConfigType.values.from(subType); + default: + return null; + } + } + + @override + Future execute(BuildContext context, ScopeManager scopeManager) async { + try { + final moEngage = GetIt.instance(); + final evaluatedValue = scopeManager.dataContext.eval(value); + + if (type == null) { + throw ConfigError('MoEngage action requires a valid type'); + } + print(type); + + switch (type!) { + case MoEngageActionType.setUser: + await _handleUserAttribute(moEngage, evaluatedValue); + break; + + case MoEngageActionType.trackEvent: + await _handleTrackEvent(moEngage, evaluatedValue); + break; + + case MoEngageActionType.appConfig: + await _handleAppConfig(moEngage, evaluatedValue); + break; + + case MoEngageActionType.pushConfig: + await _handlePushConfig(moEngage, evaluatedValue); + break; + + case MoEngageActionType.displayConfig: + await _handleDisplayConfig(moEngage, evaluatedValue); + break; + } + + if (onSuccess != null) { + ScreenController().executeAction(context, onSuccess!); + } + } catch (error) { + if (onError != null) { + ScreenController().executeAction(context, onError!, + event: EnsembleEvent(null, error: error.toString())); + } else { + rethrow; + } + } + } + + Future _handleUserAttribute( + MoEngageModule moEngage, dynamic value) async { + + if (value == null) { + throw ConfigError('User attribute value cannot be null'); + } + + // Check attributeKey for types that require it + if ((actionType == MoEngageUserAttributeType.custom || + actionType == MoEngageUserAttributeType.timestamp || + actionType == MoEngageUserAttributeType.locationAttribute) && + (attributeKey == null || attributeKey!.isEmpty)) { + throw ConfigError('Attribute key is required for ${actionType.name}'); + } + + switch (actionType as MoEngageUserAttributeType) { + case MoEngageUserAttributeType.uniqueId: + await moEngage.setUniqueId(Utils.getString(value, fallback: '')); + break; + + case MoEngageUserAttributeType.userName: + await moEngage.setUserName(Utils.getString(value, fallback: '')); + break; + + case MoEngageUserAttributeType.firstName: + await moEngage.setFirstName(Utils.getString(value, fallback: '')); + break; + + case MoEngageUserAttributeType.lastName: + await moEngage.setLastName(Utils.getString(value, fallback: '')); + break; + + case MoEngageUserAttributeType.email: + await moEngage.setEmail(Utils.getString(value, fallback: '')); + break; + + case MoEngageUserAttributeType.mobile: + await moEngage.setPhoneNumber(Utils.getString(value, fallback: '')); + break; + + case MoEngageUserAttributeType.gender: + await moEngage + .setGender(MoEGender.values.from(value) ?? MoEGender.male); + break; + + case MoEngageUserAttributeType.birthday: + await moEngage.setBirthDate(Utils.getString(value, fallback: '')); + break; + + case MoEngageUserAttributeType.location: + final location = getLocation(value); + if (location != null) { + await moEngage.setLocation(location); + } + break; + + case MoEngageUserAttributeType.alias: + await moEngage.setAlias(Utils.getString(value, fallback: '')); + break; + + case MoEngageUserAttributeType.custom: + await moEngage.setUserAttribute(attributeKey!, value); + break; + + case MoEngageUserAttributeType.timestamp: + await moEngage.setUserAttributeIsoDate( + attributeKey!, + Utils.getString(value, fallback: '') + ); + break; + + case MoEngageUserAttributeType.locationAttribute: + final location = getLocation(value); + if (location != null) { + await moEngage.setUserAttributeLocation(attributeKey!, location); + } + break; + } + } + + Future _handleTrackEvent( + MoEngageModule moEngage, String eventName) async { + if (properties != null) { + final moEProperties = MoEProperties(); + properties!.forEach((key, value) { + moEProperties.addAttribute(key, value); + }); + await moEngage.trackEvent(eventName, moEProperties); + } else { + await moEngage.trackEvent(eventName); + } + } + + Future _handleAppConfig(MoEngageModule moEngage, dynamic value) async { + switch (actionType as MoEngageAppConfigType) { + case MoEngageAppConfigType.enableSdk: + await moEngage.enableSdk(); + break; + case MoEngageAppConfigType.disableSdk: + await moEngage.disableSdk(); + break; + case MoEngageAppConfigType.enableDataTracking: + await moEngage.enableDataTracking(); + break; + case MoEngageAppConfigType.disableDataTracking: + await moEngage.disableDataTracking(); + break; + case MoEngageAppConfigType.enableDeviceIdTracking: + await moEngage.enableDeviceIdTracking(); + break; + case MoEngageAppConfigType.disableDeviceIdTracking: + await moEngage.disableDeviceIdTracking(); + break; + case MoEngageAppConfigType.enableAndroidIdTracking: + await moEngage.enableAndroidIdTracking(); + break; + case MoEngageAppConfigType.disableAndroidIdTracking: + await moEngage.disableAndroidIdTracking(); + break; + case MoEngageAppConfigType.enableAdIdTracking: + await moEngage.enableAdIdTracking(); + break; + case MoEngageAppConfigType.disableAdIdTracking: + await moEngage.disableAdIdTracking(); + break; + case MoEngageAppConfigType.setAppStatus: + await moEngage.setAppStatus(MoEAppStatus.values.from(value)!); + break; + case MoEngageAppConfigType.logout: + await moEngage.logout(); + break; + case MoEngageAppConfigType.deleteUser: + await moEngage.deleteUser(); + break; + } + } + + Future _handlePushConfig(MoEngageModule moEngage, dynamic value) async { + switch (actionType as MoEngagePushConfigType) { + case MoEngagePushConfigType.registerForPush: + await moEngage.registerForPushNotification(); + break; + case MoEngagePushConfigType.registerForProvisionalPush: + await moEngage.registerForProvisionalPush(); + break; + case MoEngagePushConfigType.passFCMToken: + await moEngage.passFCMPushToken(value.toString()); + break; + case MoEngagePushConfigType.passPushKitToken: + await moEngage.passPushKitPushToken(value.toString()); + break; + case MoEngagePushConfigType.passFCMPushPayload: + await moEngage.passFCMPushPayload(Map.from(value)); + break; + case MoEngagePushConfigType.requestPushPermission: + await moEngage.requestPushPermissionAndroid(); + break; + case MoEngagePushConfigType.updatePermissionCount: + await moEngage.updatePushPermissionRequestCountAndroid(value); + break; + case MoEngagePushConfigType.pushPermissionResponse: + await moEngage.pushPermissionResponseAndroid(value); + break; + } + } + + Future _handleDisplayConfig( + MoEngageModule moEngage, dynamic value) async { + switch (actionType as MoEngageDisplayConfigType) { + case MoEngageDisplayConfigType.showInApp: + await moEngage.showInApp(); + break; + case MoEngageDisplayConfigType.showNudge: + await moEngage.showNudge( + position: MoEngageNudgePosition.values.from(value)!); + break; + case MoEngageDisplayConfigType.setContext: + await moEngage.setCurrentContext(List.from(value)); + break; + case MoEngageDisplayConfigType.resetContext: + await moEngage.resetCurrentContext(); + break; + } + } + + static MoEGeoLocation? getLocation(dynamic value) { + if (value is Map) { + final lat = Utils.getDouble(value['latitude'], fallback: 0); + final lng = Utils.getDouble(value['longitude'], fallback: 0); + return MoEGeoLocation(lat, lng); + } + + final locationData = Utils.getLatLng(value); + if (locationData != null) { + return MoEGeoLocation(locationData.latitude, locationData.longitude); + } + return null; + } +} diff --git a/modules/ensemble/lib/framework/action.dart b/modules/ensemble/lib/framework/action.dart index 5437e97b1..51c0c15b6 100644 --- a/modules/ensemble/lib/framework/action.dart +++ b/modules/ensemble/lib/framework/action.dart @@ -40,6 +40,7 @@ import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; import 'package:flutter/material.dart'; import 'package:source_span/source_span.dart'; import 'package:yaml/yaml.dart'; +import 'package:ensemble/action/moengage_actions.dart'; class ShowCameraAction extends EnsembleAction { ShowCameraAction({ @@ -1051,6 +1052,7 @@ enum ActionType { bluetoothDisconnect, bluetoothSubscribeCharacteristic, bluetoothUnsubscribeCharacteristic, + moEngageEvents, } /// payload representing an Action to do (navigateToScreen, InvokeAPI, ..) @@ -1267,6 +1269,8 @@ abstract class EnsembleAction { return SubscribeBluetoothCharacteristicsAction.from(payload: payload); } else if (actionType == ActionType.bluetoothUnsubscribeCharacteristic) { return UnSubscribeBluetoothCharacteristicsAction.from(payload: payload); + } else if (actionType == ActionType.moEngageEvents) { + return MoEngageAction.fromYaml(initiator: initiator, payload: payload); } else { throw LanguageError("Invalid action.", recovery: "Make sure to use one of Ensemble-provided actions."); diff --git a/modules/ensemble/lib/framework/stub/moengage_manager.dart b/modules/ensemble/lib/framework/stub/moengage_manager.dart new file mode 100644 index 000000000..a46c55a2a --- /dev/null +++ b/modules/ensemble/lib/framework/stub/moengage_manager.dart @@ -0,0 +1,336 @@ +import 'package:ensemble/framework/error_handling.dart'; +import 'package:moengage_flutter/moengage_flutter.dart'; + +typedef NotificationCallback = void Function(dynamic data); + +abstract class MoEngageModule { + // Initialization + Future initialize(String workspaceId, {bool enableLogs = false}); + + // Event Tracking + Future trackEvent(String eventName, [MoEProperties? properties]); + + // User Attributes - Basic + Future setUniqueId(String uniqueId); + Future setUserName(String userName); + Future setFirstName(String firstName); + Future setLastName(String lastName); + Future setEmail(String email); + Future setPhoneNumber(String phoneNumber); + Future setGender(MoEGender gender); + Future setBirthDate(String birthDate); + Future setLocation(MoEGeoLocation location); + Future setAlias(String alias); + + // User Attributes - Custom + Future setUserAttribute(String attributeName, dynamic value); + Future setUserAttributeIsoDate(String attributeName, String date); + Future setUserAttributeLocation(String attributeName, MoEGeoLocation location); + + // App Status & Tracking + Future setAppStatus(MoEAppStatus status); + Future enableDataTracking(); + Future disableDataTracking(); + + // Device ID Management + Future enableDeviceIdTracking(); + Future disableDeviceIdTracking(); + Future enableAndroidIdTracking(); + Future disableAndroidIdTracking(); + Future enableAdIdTracking(); + Future disableAdIdTracking(); + + // Push Notifications + Future registerForPushNotification(); + Future registerForProvisionalPush(); + Future passFCMPushToken(String token); + Future passPushKitPushToken(String token); + Future passFCMPushPayload(Map payload); + Future requestPushPermissionAndroid(); + Future updatePushPermissionRequestCountAndroid(int count); + Future pushPermissionResponseAndroid(bool granted); + + // InApp & Context + Future showInApp(); + Future showNudge({MoEngageNudgePosition position = MoEngageNudgePosition.bottom}); + Future setCurrentContext(List contexts); + Future resetCurrentContext(); + Future getSelfHandledInApp(); + Future selfHandledShown(dynamic data); + Future selfHandledClicked(dynamic data); + Future selfHandledDismissed(dynamic data); + + // Lifecycle + Future onOrientationChanged(); + + // Handlers + void setPushClickCallbackHandler(NotificationCallback handler); + void setPushTokenCallbackHandler(NotificationCallback handler); + void setInAppShownCallbackHandler(NotificationCallback handler); + void setInAppDismissedCallbackHandler(NotificationCallback handler); + void setSelfHandledInAppHandler(NotificationCallback handler); + + // SDK Control + Future enableSdk(); + Future disableSdk(); + Future logout(); + Future deleteUser(); +} + +class MoEngageModuleStub implements MoEngageModule { + final _errorMsg = "MoEngage module is not enabled. Please review the Ensemble documentation."; + + MoEngageModuleStub() { + throw RuntimeError(_errorMsg); + } + + @override + Future initialize(String workspaceId, {bool enableLogs = false}) { + throw ConfigError(_errorMsg); + } + + @override + Future trackEvent(String eventName, [MoEProperties? properties]) { + throw ConfigError(_errorMsg); + } + + @override + Future setUniqueId(String uniqueId) { + throw ConfigError(_errorMsg); + } + + @override + Future setUserName(String userName) { + throw ConfigError(_errorMsg); + } + + @override + Future setFirstName(String firstName) { + throw ConfigError(_errorMsg); + } + + @override + Future setLastName(String lastName) { + throw ConfigError(_errorMsg); + } + + @override + Future setEmail(String email) { + throw ConfigError(_errorMsg); + } + + @override + Future setPhoneNumber(String phoneNumber) { + throw ConfigError(_errorMsg); + } + + @override + Future setGender(MoEGender gender) { + throw ConfigError(_errorMsg); + } + + @override + Future setBirthDate(String birthDate) { + throw ConfigError(_errorMsg); + } + + @override + Future setLocation(MoEGeoLocation location) { + throw ConfigError(_errorMsg); + } + + @override + Future setAlias(String alias) { + throw ConfigError(_errorMsg); + } + + @override + Future setUserAttribute(String attributeName, dynamic value) { + throw ConfigError(_errorMsg); + } + + @override + Future setUserAttributeIsoDate(String attributeName, String date) { + throw ConfigError(_errorMsg); + } + + @override + Future setUserAttributeLocation(String attributeName, MoEGeoLocation location) { + throw ConfigError(_errorMsg); + } + + @override + Future setAppStatus(MoEAppStatus status) { + throw ConfigError(_errorMsg); + } + + @override + Future enableDataTracking() { + throw ConfigError(_errorMsg); + } + + @override + Future disableDataTracking() { + throw ConfigError(_errorMsg); + } + + @override + Future enableDeviceIdTracking() { + throw ConfigError(_errorMsg); + } + + @override + Future disableDeviceIdTracking() { + throw ConfigError(_errorMsg); + } + + @override + Future enableAndroidIdTracking() { + throw ConfigError(_errorMsg); + } + + @override + Future disableAndroidIdTracking() { + throw ConfigError(_errorMsg); + } + + @override + Future enableAdIdTracking() { + throw ConfigError(_errorMsg); + } + + @override + Future disableAdIdTracking() { + throw ConfigError(_errorMsg); + } + + @override + Future registerForPushNotification() { + throw ConfigError(_errorMsg); + } + + @override + Future registerForProvisionalPush() { + throw ConfigError(_errorMsg); + } + + @override + Future passFCMPushToken(String token) { + throw ConfigError(_errorMsg); + } + + @override + Future passPushKitPushToken(String token) { + throw ConfigError(_errorMsg); + } + + @override + Future passFCMPushPayload(Map payload) { + throw ConfigError(_errorMsg); + } + + @override + Future requestPushPermissionAndroid() { + throw ConfigError(_errorMsg); + } + + @override + Future updatePushPermissionRequestCountAndroid(int count) { + throw ConfigError(_errorMsg); + } + + @override + Future pushPermissionResponseAndroid(bool granted) { + throw ConfigError(_errorMsg); + } + + @override + Future showInApp() { + throw ConfigError(_errorMsg); + } + + @override + Future showNudge({MoEngageNudgePosition position = MoEngageNudgePosition.bottom}) { + throw ConfigError(_errorMsg); + } + + @override + Future setCurrentContext(List contexts) { + throw ConfigError(_errorMsg); + } + + @override + Future resetCurrentContext() { + throw ConfigError(_errorMsg); + } + + @override + Future getSelfHandledInApp() { + throw ConfigError(_errorMsg); + } + + @override + Future selfHandledShown(data) { + throw ConfigError(_errorMsg); + } + + @override + Future selfHandledClicked(data) { + throw ConfigError(_errorMsg); + } + + @override + Future selfHandledDismissed(data) { + throw ConfigError(_errorMsg); + } + + @override + Future onOrientationChanged() { + throw ConfigError(_errorMsg); + } + + @override + void setPushClickCallbackHandler(NotificationCallback handler) { + throw ConfigError(_errorMsg); + } + + @override + void setPushTokenCallbackHandler(NotificationCallback handler) { + throw ConfigError(_errorMsg); + } + + @override + void setInAppShownCallbackHandler(NotificationCallback handler) { + throw ConfigError(_errorMsg); + } + + @override + void setInAppDismissedCallbackHandler(NotificationCallback handler) { + throw ConfigError(_errorMsg); + } + + @override + void setSelfHandledInAppHandler(NotificationCallback handler) { + throw ConfigError(_errorMsg); + } + + @override + Future enableSdk() { + throw ConfigError(_errorMsg); + } + + @override + Future disableSdk() { + throw ConfigError(_errorMsg); + } + + @override + Future logout() { + throw ConfigError(_errorMsg); + } + + @override + Future deleteUser() { + throw ConfigError(_errorMsg); + } +} \ No newline at end of file diff --git a/modules/ensemble/pubspec.yaml b/modules/ensemble/pubspec.yaml index a0fe6a2d6..324d25391 100644 --- a/modules/ensemble/pubspec.yaml +++ b/modules/ensemble/pubspec.yaml @@ -141,6 +141,7 @@ dependencies: flutter_slidable: ^3.1.1 accordion: ^2.6.0 session_storage: ^0.0.1 + moengage_flutter: ^8.0.0 dependency_overrides: http: ^0.13.5 diff --git a/modules/moengage/LICENSE b/modules/moengage/LICENSE new file mode 100644 index 000000000..c43d53d2d --- /dev/null +++ b/modules/moengage/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2024, Ensemble + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/modules/moengage/README.md b/modules/moengage/README.md new file mode 100644 index 000000000..25e27bbdc --- /dev/null +++ b/modules/moengage/README.md @@ -0,0 +1,16 @@ +# firebase_analytics + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/modules/moengage/lib/moengage.dart b/modules/moengage/lib/moengage.dart new file mode 100644 index 000000000..22cb4f17c --- /dev/null +++ b/modules/moengage/lib/moengage.dart @@ -0,0 +1,432 @@ +import 'dart:io'; + +import 'package:ensemble/framework/stub/moengage_manager.dart'; +import 'package:ensemble_moengage/moengage_notification_handler.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:moengage_flutter/moengage_flutter.dart' hide LogLevel; +import 'package:moengage_flutter_platform_interface/src/log_level.dart'; +import 'package:moengage_cards/moengage_cards.dart'; +import 'package:moengage_inbox/moengage_inbox.dart'; +import 'package:moengage_geofence/moengage_geofence.dart'; + +class MoEngageImpl implements MoEngageModule { + static final MoEngageImpl _instance = MoEngageImpl._internal(); + MoEngageImpl._internal(); + + late MoEngageFlutter _moengagePlugin; + + bool _initialized = false; + String? _workspaceId; + + void _checkInitialization() { + if (!_initialized) { + throw StateError('MoEngage: Not initialized. Call initialize() first.'); + } + } + + factory MoEngageImpl({required String workspaceId, bool enableLogs = false}) { + if (!_instance._initialized) { + try { + // Initialize everything here + _instance._workspaceId = workspaceId; + + // 1. Create plugin instance + _instance._moengagePlugin = MoEngageFlutter( + workspaceId, + moEInitConfig: MoEInitConfig( + pushConfig: PushConfig(shouldDeliverCallbackOnForegroundClick: true), + analyticsConfig: AnalyticsConfig( + shouldTrackUserAttributeBooleanAsNumber: false + ) + ) + ); + + // 2. Configure logs if enabled + if (enableLogs) { + _instance._moengagePlugin.configureLogs(LogLevel.VERBOSE); + } + + // 3. Initialize notification handler + final notificationHandler = MoEngageNotificationHandler(); + notificationHandler.initialize(_instance._moengagePlugin); + + // 4. Initialize plugin + _instance._moengagePlugin.initialise(); + + // 5. Request push permissions + _instance._requestInitialPushPermissions(); + + _instance._initialized = true; + debugPrint('MoEngage: Initialization complete'); + } catch (e) { + debugPrint('MoEngage: Initialization failed: $e'); + rethrow; + } + } + return _instance; + } + + @override + Future initialize(String workspaceId, {bool enableLogs = false}) async { + if (_initialized) { + debugPrint('MoEngage: Already initialized'); + return; + } + + try { + _workspaceId = workspaceId; + + // 1. Create plugin instance + _moengagePlugin = MoEngageFlutter(workspaceId, + moEInitConfig: MoEInitConfig( + pushConfig: + PushConfig(shouldDeliverCallbackOnForegroundClick: true), + analyticsConfig: AnalyticsConfig( + shouldTrackUserAttributeBooleanAsNumber: false))); + + // 2. Configure logs BEFORE notification handler initialization + if (enableLogs) { + _moengagePlugin.configureLogs(LogLevel.VERBOSE); + } + + // 3. Initialize notification handler BEFORE plugin initialization + final notificationHandler = MoEngageNotificationHandler(); + await notificationHandler.initialize(_moengagePlugin); // Make this await + + // 4. Initialize plugin after callbacks are registered + _moengagePlugin.initialise(); + + // 5. Request push permissions if needed + await _requestInitialPushPermissions(); + + _initialized = true; + debugPrint('MoEngage: Initialization complete'); + } catch (e) { + debugPrint('MoEngage: Initialization failed: $e'); + _initialized = false; + rethrow; + } + } + + Future _requestInitialPushPermissions() async { + try { + if (Platform.isIOS) { + // First request Firebase permission + final settings = await FirebaseMessaging.instance.requestPermission( + alert: true, + badge: true, + sound: true, + ); + + // Only register with MoEngage if permission granted + if (settings.authorizationStatus == AuthorizationStatus.authorized) { + _moengagePlugin.registerForPushNotification(); + } + } else if (Platform.isAndroid) { + // Android 13+ needs runtime permission + _moengagePlugin.requestPushPermissionAndroid(); + } + } catch (e) { + debugPrint('MoEngage: Error requesting push permissions: $e'); + } +} + + @override + Future trackEvent(String eventName, [MoEProperties? properties]) async { + _checkInitialization(); + _moengagePlugin.trackEvent(eventName, properties); + } + + @override + Future setUniqueId(String uniqueId) async { + _checkInitialization(); + _moengagePlugin.setUniqueId(uniqueId); + } + + @override + Future setUserName(String userName) async { + print(userName); + _checkInitialization(); + _moengagePlugin.setUserName(userName); + } + + @override + Future setFirstName(String firstName) async { + _checkInitialization(); + _moengagePlugin.setFirstName(firstName); + } + + @override + Future setLastName(String lastName) async { + _checkInitialization(); + _moengagePlugin.setLastName(lastName); + } + + @override + Future setEmail(String email) async { + _checkInitialization(); + _moengagePlugin.setEmail(email); + } + + @override + Future setPhoneNumber(String phoneNumber) async { + _checkInitialization(); + _moengagePlugin.setPhoneNumber(phoneNumber); + } + + @override + Future setGender(MoEGender gender) async { + _checkInitialization(); + _moengagePlugin.setGender(gender); + } + + @override + Future setBirthDate(String birthDate) async { + _checkInitialization(); + _moengagePlugin.setBirthDate(birthDate); + } + + @override + Future setLocation(MoEGeoLocation location) async { + _checkInitialization(); + _moengagePlugin.setLocation(location); + } + + @override + Future setAlias(String alias) async { + _checkInitialization(); + _moengagePlugin.setAlias(alias); + } + + @override + Future setUserAttribute(String attributeName, dynamic value) async { + _checkInitialization(); + _moengagePlugin.setUserAttribute(attributeName, value); + } + + @override + Future setUserAttributeIsoDate( + String attributeName, String date) async { + _checkInitialization(); + _moengagePlugin.setUserAttributeIsoDate(attributeName, date); + } + + @override + Future setUserAttributeLocation( + String attributeName, MoEGeoLocation location) async { + _checkInitialization(); + _moengagePlugin.setUserAttributeLocation(attributeName, location); + } + + @override + Future setAppStatus(MoEAppStatus status) async { + _checkInitialization(); + _moengagePlugin.setAppStatus(status); + } + + @override + Future enableDataTracking() async { + _checkInitialization(); + _moengagePlugin.enableDataTracking(); + } + + @override + Future disableDataTracking() async { + _checkInitialization(); + _moengagePlugin.disableDataTracking(); + } + + @override + Future enableDeviceIdTracking() async { + _checkInitialization(); + _moengagePlugin.enableDeviceIdTracking(); + } + + @override + Future disableDeviceIdTracking() async { + _checkInitialization(); + _moengagePlugin.disableDeviceIdTracking(); + } + + @override + Future enableAndroidIdTracking() async { + _checkInitialization(); + _moengagePlugin.enableAndroidIdTracking(); + } + + @override + Future disableAndroidIdTracking() async { + _checkInitialization(); + _moengagePlugin.disableAndroidIdTracking(); + } + + @override + Future enableAdIdTracking() async { + _checkInitialization(); + _moengagePlugin.enableAdIdTracking(); + } + + @override + Future disableAdIdTracking() async { + _checkInitialization(); + _moengagePlugin.disableAdIdTracking(); + } + + @override + Future registerForPushNotification() async { + _checkInitialization(); + _moengagePlugin.registerForPushNotification(); + } + + @override + Future registerForProvisionalPush() async { + _checkInitialization(); + _moengagePlugin.registerForProvisionalPush(); + } + + @override + Future passFCMPushToken(String token) async { + _checkInitialization(); + _moengagePlugin.passFCMPushToken(token); + } + + @override + Future passPushKitPushToken(String token) async { + _checkInitialization(); + _moengagePlugin.passPushKitPushToken(token); + } + + @override + Future passFCMPushPayload(Map payload) async { + _checkInitialization(); + _moengagePlugin.passFCMPushPayload(payload); + } + + @override + Future requestPushPermissionAndroid() async { + _checkInitialization(); + _moengagePlugin.requestPushPermissionAndroid(); + } + + @override + Future updatePushPermissionRequestCountAndroid(int count) async { + _checkInitialization(); + _moengagePlugin.updatePushPermissionRequestCountAndroid(count); + } + + @override + Future pushPermissionResponseAndroid(bool granted) async { + _checkInitialization(); + _moengagePlugin.pushPermissionResponseAndroid(granted); + } + + @override + Future showInApp() async { + _checkInitialization(); + _moengagePlugin.showInApp(); + } + + @override + Future showNudge( + {MoEngageNudgePosition position = MoEngageNudgePosition.bottom}) async { + _checkInitialization(); + _moengagePlugin.showNudge(position: position); + } + + @override + Future setCurrentContext(List contexts) async { + _checkInitialization(); + _moengagePlugin.setCurrentContext(contexts); + } + + @override + Future resetCurrentContext() async { + _checkInitialization(); + _moengagePlugin.resetCurrentContext(); + } + + @override + Future getSelfHandledInApp() async { + _checkInitialization(); + _moengagePlugin.getSelfHandledInApp(); + } + + @override + Future selfHandledShown(data) async { + _checkInitialization(); + _moengagePlugin.selfHandledShown(data); + } + + @override + Future selfHandledClicked(data) async { + _checkInitialization(); + _moengagePlugin.selfHandledClicked(data); + } + + @override + Future selfHandledDismissed(data) async { + _checkInitialization(); + _moengagePlugin.selfHandledDismissed(data); + } + + @override + Future onOrientationChanged() async { + _checkInitialization(); + _moengagePlugin.onOrientationChanged(); + } + + @override + void setPushClickCallbackHandler(NotificationCallback handler) { + _checkInitialization(); + _moengagePlugin.setPushClickCallbackHandler(handler); + } + + @override + void setPushTokenCallbackHandler(NotificationCallback handler) { + _checkInitialization(); + _moengagePlugin.setPushTokenCallbackHandler(handler); + } + + @override + void setInAppShownCallbackHandler(NotificationCallback handler) { + _checkInitialization(); + _moengagePlugin.setInAppShownCallbackHandler(handler); + } + + @override + void setInAppDismissedCallbackHandler(NotificationCallback handler) { + _checkInitialization(); + _moengagePlugin.setInAppDismissedCallbackHandler(handler); + } + + @override + void setSelfHandledInAppHandler(NotificationCallback handler) { + _checkInitialization(); + _moengagePlugin.setSelfHandledInAppHandler(handler); + } + + @override + Future enableSdk() async { + _checkInitialization(); + _moengagePlugin.enableSdk(); + } + + @override + Future disableSdk() async { + _checkInitialization(); + _moengagePlugin.disableSdk(); + } + + @override + Future logout() async { + _checkInitialization(); + _moengagePlugin.logout(); + } + + @override + Future deleteUser() async { + _checkInitialization(); + return _moengagePlugin.deleteUser(); + } +} diff --git a/modules/moengage/lib/moengage_notification_handler.dart b/modules/moengage/lib/moengage_notification_handler.dart new file mode 100644 index 000000000..32656285e --- /dev/null +++ b/modules/moengage/lib/moengage_notification_handler.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:ensemble/framework/notification_manager.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:moengage_flutter/moengage_flutter.dart'; + +class MoEngageNotificationHandler { + static final MoEngageNotificationHandler _instance = + MoEngageNotificationHandler._internal(); + factory MoEngageNotificationHandler() => _instance; + MoEngageNotificationHandler._internal(); + + MoEngageFlutter? _moengagePlugin; + bool _initialized = false; + + Future initialize(MoEngageFlutter moengagePlugin) async { + if (_initialized) { + debugPrint('MoEngage notifications already initialized'); + return; + } + + try { + _moengagePlugin = moengagePlugin; + + await _registerHandlers(); + + _initialized = true; + } catch (e) { + debugPrint('Error initializing MoEngage notification handler: $e'); + } + } + + Future _registerHandlers() async { + _moengagePlugin?.setInAppClickHandler(_onInAppClick); + _moengagePlugin?.setPushClickCallbackHandler(_onPushClick); + } + + void _onInAppClick(ClickData message) { + + try { + // Convert MoEngage InApp click data to common notification format + Map inAppData = { + 'campaignId': message.campaignData.campaignId, + 'campaignName': message.campaignData.campaignName, + 'platform': message.platform.toString().split('.').last.toLowerCase(), + 'data': {}, // Base data object + }; + + // Add action data based on type + if (message.action is NavigationAction) { + final navAction = message.action as NavigationAction; + inAppData['data'].addAll({ + 'navigationType': navAction.navigationType.toString(), + 'navigationUrl': navAction.navigationUrl, + 'keyValuePairs': navAction.keyValuePairs, + }); + } else if (message.action is CustomAction) { + final customAction = message.action as CustomAction; + inAppData['data'].addAll({ + 'actionType': 'custom', + 'keyValuePairs': customAction.keyValuePairs, + }); + } + + NotificationManager().handleNotification(jsonEncode(inAppData)); + } catch (e) { + debugPrint('Error processing InApp click: $e'); + } + } + + void _onPushClick(PushCampaignData message) { + try { + final pushData = message.data; + + Map notificationData = { + ...pushData.payload, // Include all original payload + 'clickedAction': pushData.clickedAction, + 'platform': message.platform.toString().split('.').last.toLowerCase(), + }; + + if (Platform.isAndroid) { + notificationData['isDefaultAction'] = pushData.isDefaultAction; + } + + // Pass to NotificationManager + NotificationManager().handleNotification(jsonEncode(notificationData)); + } catch (e) { + debugPrint('Error processing MoEngage push click: $e'); + } + } + + bool get isInitialized => _initialized; +} diff --git a/modules/moengage/melos_ensemble_firebase_analytics.iml b/modules/moengage/melos_ensemble_firebase_analytics.iml new file mode 100644 index 000000000..9fc8ce79a --- /dev/null +++ b/modules/moengage/melos_ensemble_firebase_analytics.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/moengage/melos_ensemble_moengage.iml b/modules/moengage/melos_ensemble_moengage.iml new file mode 100644 index 000000000..9fc8ce79a --- /dev/null +++ b/modules/moengage/melos_ensemble_moengage.iml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/moengage/pubspec.yaml b/modules/moengage/pubspec.yaml new file mode 100644 index 000000000..71a8974ad --- /dev/null +++ b/modules/moengage/pubspec.yaml @@ -0,0 +1,103 @@ +name: ensemble_moengage +description: MoEngage integration for Ensemble Framework +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.5.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + ensemble: + git: + url: https://github.com/EnsembleUI/ensemble.git + ref: ensemble-v1.1.10 + path: modules/ensemble + + moengage_flutter: ^8.0.0 + get_it: ^8.0.0 + moengage_cards: + moengage_geofence: + moengage_inbox: + firebase_core: + firebase_messaging: + permission_handler: + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^3.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/starter/android/app/build.gradle b/starter/android/app/build.gradle index 36502d8da..1d47a4097 100644 --- a/starter/android/app/build.gradle +++ b/starter/android/app/build.gradle @@ -30,6 +30,7 @@ def auth0Domain = ensembleProperties.getProperty("auth0Domain") def auth0Scheme = ensembleProperties.getProperty("auth0Scheme") def branchTestKey = ensembleProperties.getProperty("branchTestKey") def branchLiveKey = ensembleProperties.getProperty("branchLiveKey") +def moengageAppId = ensembleProperties.getProperty("moengageAppId") def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { @@ -60,6 +61,10 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + buildFeatures { + buildConfig true + } + kotlinOptions { jvmTarget = '1.8' } @@ -77,6 +82,9 @@ android { targetSdkVersion androidTargetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName + + // pass properties to native kotlin files + buildConfigField("String", "MOENGAGE_WORKSPACE_ID", "\"${moengageAppId}\"") // pass properties to AndroidManifest manifestPlaceholders.googleMapsAPIKey = googleMapsAPIKey diff --git a/starter/ensemble/ensemble.properties b/starter/ensemble/ensemble.properties index b83347c14..94bcc4eb2 100644 --- a/starter/ensemble/ensemble.properties +++ b/starter/ensemble/ensemble.properties @@ -19,4 +19,5 @@ googleMapsAPIKey= auth0Domain=placeholder_domain auth0Scheme=placeholder_scheme branchTestKey= -branchLiveKey= \ No newline at end of file +branchLiveKey= +moengageAppId= \ No newline at end of file diff --git a/starter/pubspec.yaml b/starter/pubspec.yaml index 9a731dfee..51fb23466 100644 --- a/starter/pubspec.yaml +++ b/starter/pubspec.yaml @@ -40,6 +40,12 @@ dependencies: path: modules/ensemble + # ensemble_moengage: + # git: + # url: https://github.com/EnsembleUI/ensemble.git + # ref: ensemble-v1.1.12 + # path: modules/moengage + # ensemble_bluetooth: # git: # url: https://github.com/EnsembleUI/ensemble.git From 4c01687fbd9be59702deafe24b7534ba4615acda Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Tue, 24 Dec 2024 17:09:36 +0500 Subject: [PATCH 2/5] updated notification handler as it was creating 2 notifications --- .../lib/framework/notification_manager.dart | 57 ++++++++++++------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/modules/ensemble/lib/framework/notification_manager.dart b/modules/ensemble/lib/framework/notification_manager.dart index 476907879..47516e591 100644 --- a/modules/ensemble/lib/framework/notification_manager.dart +++ b/modules/ensemble/lib/framework/notification_manager.dart @@ -65,6 +65,10 @@ class NotificationManager { return null; } + bool isMoEngageNotification(RemoteMessage message) { + return message.data['push_from']?.toString().toLowerCase() == 'moengage'; + } + void _initListener( {Future Function(RemoteMessage)? backgroundNotificationHandler}) { /// listen for token changes and store a copy @@ -74,19 +78,28 @@ class NotificationManager { /// This is when the app is in the foreground FirebaseMessaging.onMessage.listen((RemoteMessage message) async { - Ensemble.externalDataContext.addAll({ - 'title': message.notification?.title, - 'body': message.notification?.body, - 'data': message.data - }); - // By default, notification won't show on foreground, so we leverage - // local notification to show it in the foreground - await notificationUtils.initNotifications(); - notificationUtils.showNotification( - message.notification?.title ?? '', - body: message.notification?.body, - payload: jsonEncode(message.toMap()), - ); + print("--------------"); + print(message.data); + print(message.notification); + + if (!isMoEngageNotification(message)) { + // Handle regular FCM notifications as before + Ensemble.externalDataContext.addAll({ + 'title': message.notification?.title, + 'body': message.notification?.body, + 'data': message.data + }); + + // By default, notification won't show on foreground, so we leverage + // local notification to show it in the foreground + + await notificationUtils.initNotifications(); + notificationUtils.showNotification( + message.notification?.title ?? '', + body: message.notification?.body, + payload: jsonEncode(message.toMap()), + ); + } }); /// when the app is in the background, we can't run UI logic. @@ -96,13 +109,16 @@ class NotificationManager { } /// This is when the app is in the background and the user taps on the notification + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { - Ensemble.externalDataContext.addAll({ - 'title': message.notification?.title, - 'body': message.notification?.body, - 'data': message.data - }); - handleNotification(jsonEncode(message.toMap())); + if (!isMoEngageNotification(message)) { + Ensemble.externalDataContext.addAll({ + 'title': message.notification?.title, + 'body': message.notification?.body, + 'data': message.data + }); + handleNotification(jsonEncode(message.toMap())); + } }); } @@ -138,12 +154,15 @@ class NotificationManager { } on Exception catch (e) { print("NotificationManager: Error receiving notification: $e"); } + print("----------"); + print(payload); if (payload is! Map) return; if (payload.containsKey('status') && (payload['status'] as String).toLowerCase() == 'error') { print('Error while running js function'); } final action = NavigateScreenAction.from(payload); + print(action.screenName); ScreenController().navigateToScreen(Utils.globalAppKey.currentContext!, screenName: action.screenName, From 9b7322e0ecfe7a15cbefd5479578f38fea2cd8aa Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Tue, 24 Dec 2024 17:29:18 +0500 Subject: [PATCH 3/5] added starter moduel file --- starter/lib/generated/ensemble_modules.dart | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/starter/lib/generated/ensemble_modules.dart b/starter/lib/generated/ensemble_modules.dart index 783c83d73..264850d4a 100644 --- a/starter/lib/generated/ensemble_modules.dart +++ b/starter/lib/generated/ensemble_modules.dart @@ -13,6 +13,7 @@ import 'package:ensemble/framework/stub/contacts_manager.dart'; import 'package:ensemble/framework/stub/plaid_link_manager.dart'; import 'package:ensemble/module/auth_module.dart'; import 'package:ensemble/module/location_module.dart'; +import 'package:ensemble/framework/stub/moengage_manager.dart'; //import 'package:ensemble_bluetooth/ensemble_bluetooth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; @@ -21,6 +22,9 @@ import 'package:firebase_messaging/firebase_messaging.dart'; // import 'package:ensemble_location/location_module.dart'; import 'package:get_it/get_it.dart'; +// Uncomment to enable moengage +// import 'package:ensemble_moengage/moengage.dart'; + // Uncomment to enable ensemble_chat widget // import 'package:ensemble_chat/ensemble_chat.dart'; @@ -66,6 +70,7 @@ class EnsembleModules { static const useDeeplink = false; static const useFirebaseAnalytics = false; static const useNotifications = false; + static const useMoEngage = false; static const useBracket = false; static const useNetworkInfo = false; @@ -80,7 +85,7 @@ class EnsembleModules { Future init() async { // Note that notifications is not a module - if (useNotifications || useFirebaseAnalytics) { + if (useMoEngage || useNotifications || useFirebaseAnalytics) { // if payload is not passed, Firebase configuration files // are required to be added manualy to iOS and Android try { @@ -100,6 +105,12 @@ class EnsembleModules { await NotificationManager().init(); } + if (useMoEngage) { + // GetIt.I.registerSingleton(MoEngageImpl()); + } else { + GetIt.I.registerSingleton(MoEngageModuleStub()); + } + if (useCamera) { // Uncomment to enable camera service // GetIt.I.registerSingleton(CameraManagerImpl()); From 88bed9ff3f039572214fb860c63124edbe89282c Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Fri, 27 Dec 2024 02:22:08 +0500 Subject: [PATCH 4/5] made requested changes --- .../ensemble/lib/action/Log_event_action.dart | 367 +++++++++++++++-- .../ensemble/lib/action/moengage_actions.dart | 382 ------------------ modules/ensemble/lib/framework/action.dart | 4 - .../lib/framework/notification_manager.dart | 6 - .../lib/framework/stub/moengage_manager.dart | 4 +- modules/moengage/lib/moengage.dart | 1 - .../lib/moengage_notification_handler.dart | 5 + 7 files changed, 341 insertions(+), 428 deletions(-) delete mode 100644 modules/ensemble/lib/action/moengage_actions.dart diff --git a/modules/ensemble/lib/action/Log_event_action.dart b/modules/ensemble/lib/action/Log_event_action.dart index cc0f09c32..da6f113b5 100644 --- a/modules/ensemble/lib/action/Log_event_action.dart +++ b/modules/ensemble/lib/action/Log_event_action.dart @@ -1,19 +1,29 @@ -import 'package:ensemble/framework/action.dart'; +import 'package:ensemble/framework/action.dart' as ensembleAction; import 'package:ensemble/framework/error_handling.dart'; +import 'package:ensemble/framework/event.dart'; +import 'package:ensemble/framework/extensions.dart'; import 'package:ensemble/framework/logging/log_manager.dart'; -import 'package:ensemble/framework/logging/log_provider.dart'; +import 'package:ensemble/framework/logging/log_provider.dart' as logging; import 'package:ensemble/framework/scope.dart'; +import 'package:ensemble/screen_controller.dart'; import 'package:ensemble/util/utils.dart'; import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:moengage_flutter/moengage_flutter.dart'; +import 'package:ensemble/framework/stub/moengage_manager.dart'; -class LogEvent extends EnsembleAction { +class LogEvent extends ensembleAction.EnsembleAction { final String? eventName; final Map? parameters; final String? provider; final String? operation; final String? userId; final String logLevel; + final dynamic value; + final String? attributeKey; + final ensembleAction.EnsembleAction? onSuccess; + final ensembleAction.EnsembleAction? onError; LogEvent({ required Invokable? initiator, @@ -23,57 +33,350 @@ class LogEvent extends EnsembleAction { this.operation, this.userId, this.parameters, + this.value, + this.attributeKey, + this.onSuccess, + this.onError, }) : super(initiator: initiator); factory LogEvent.from({Invokable? initiator, Map? payload}) { payload = Utils.convertYamlToDart(payload); String? eventName = payload?['name']; String? operation = payload?['operation']; + String? provider = payload?['provider'] ?? 'firebase'; + dynamic value = payload?['value']; + String? attributeKey = payload?['attributeKey']; - if (operation == 'logEvent' && eventName == null) { - throw LanguageError( - "${ActionType.logEvent.name} requires the event name"); - } else if (operation == 'setUserId' && payload?['userId'] == null) { - throw LanguageError("${ActionType.logEvent.name} requires the user id"); + // Firebase validation + if (provider == 'firebase') { + if (operation == 'logEvent' && eventName == null) { + throw LanguageError( + "${ensembleAction.ActionType.logEvent.name} requires the event name"); + } else if (operation == 'setUserId' && payload?['userId'] == null) { + throw LanguageError( + "${ensembleAction.ActionType.logEvent.name} requires the user id"); + } + } + // MoEngage validations + else if (provider == 'moengage') { + if (operation == null) { + throw LanguageError('MoEngage requires operation type'); + } + + // Value validation for operations requiring it + if (value == null && + !NoValueOperations.values.any((e) => e.name == operation)) { + throw LanguageError('Operation $operation requires a value'); + } + + // Operation-specific validations + switch (operation) { + case 'trackEvent': + if (eventName == null) { + throw LanguageError('trackEvent requires event name'); + } + break; + + case 'setLocation': + case 'setUserAttributeLocation': + final location = getLocation(value); + if (location == null) { + throw LanguageError('Invalid location format'); + } + break; + + case 'custom': + case 'timestamp': + case 'locationAttribute': + if (attributeKey == null) { + throw LanguageError('Operation $operation requires attributeKey'); + } + break; + + case 'setContext': + if (value != null && value is! List) { + throw LanguageError('setContext requires List value'); + } + break; + } } return LogEvent( initiator: initiator, - eventName: eventName ?? '', + eventName: eventName, parameters: payload?['parameters'] is Map ? payload!['parameters'] : null, - logLevel: payload?['logLevel'] ?? LogLevel.info.name, - provider: payload?['provider'] ?? 'firebase', - operation: payload?['operation'], + logLevel: payload?['logLevel'] ?? logging.LogLevel.info.name, + provider: provider, + operation: operation, userId: payload?['userId'], + value: value, + attributeKey: attributeKey, + onSuccess: ensembleAction.EnsembleAction.from(payload?['onSuccess']), + onError: ensembleAction.EnsembleAction.from(payload?['onError']), ); } - static LogLevel stringToLogLevel(String? levelStr) { - // If the level string is null, default to LogLevel.info - if (levelStr == null) return LogLevel.info; - // Attempt to match the string with an enum value - for (LogLevel level in LogLevel.values) { + static logging.LogLevel stringToLogLevel(String? levelStr) { + if (levelStr == null) return logging.LogLevel.info; + for (logging.LogLevel level in logging.LogLevel.values) { if (level.name.toLowerCase() == levelStr.toLowerCase()) { return level; } } - // Default to LogLevel.info if no match is found - return LogLevel.info; + return logging.LogLevel.info; + } + + static MoEGeoLocation? getLocation(dynamic value) { + if (value is Map) { + final lat = Utils.getDouble(value['latitude'], fallback: 0); + final lng = Utils.getDouble(value['longitude'], fallback: 0); + return MoEGeoLocation(lat, lng); + } + + final locationData = Utils.getLatLng(value); + if (locationData != null) { + return MoEGeoLocation(locationData.latitude, locationData.longitude); + } + return null; } @override Future execute(BuildContext context, ScopeManager scopeManager) async { - LogManager().log( - LogType.appAnalytics, - { - 'name': scopeManager.dataContext.eval(eventName), - 'parameters': scopeManager.dataContext.eval(parameters) ?? {}, - 'logLevel': stringToLogLevel(scopeManager.dataContext.eval(logLevel)), - 'provider': scopeManager.dataContext.eval(provider), - 'operation': scopeManager.dataContext.eval(operation), - 'userId': scopeManager.dataContext.eval(userId), - }, - ); - // Instead of awaiting, we'll let LogManager figure it out as we don't want to block the UI + try { + final evaluatedProvider = scopeManager.dataContext.eval(provider); + + if (evaluatedProvider == 'moengage') { + await _handleMoEngageOperations( + context, + scopeManager, + operation: scopeManager.dataContext.eval(operation), + value: scopeManager.dataContext.eval(value), + eventName: scopeManager.dataContext.eval(eventName), + parameters: scopeManager.dataContext.eval(parameters), + attributeKey: scopeManager.dataContext.eval(attributeKey), + ); + + if (onSuccess != null) { + await ScreenController().executeAction(context, onSuccess!); + } + } else { + LogManager().log( + logging.LogType.appAnalytics, + { + 'name': scopeManager.dataContext.eval(eventName), + 'parameters': scopeManager.dataContext.eval(parameters) ?? {}, + 'logLevel': + stringToLogLevel(scopeManager.dataContext.eval(logLevel)), + 'provider': evaluatedProvider, + 'operation': scopeManager.dataContext.eval(operation), + 'userId': scopeManager.dataContext.eval(userId), + }, + ); + } + } catch (error) { + if (onError != null) { + await ScreenController().executeAction( + context, + onError!, + event: EnsembleEvent(null, error: error.toString()), + ); + } else { + rethrow; + } + } } + + Future _handleMoEngageOperations( + BuildContext context, + ScopeManager scopeManager, { + String? operation, + dynamic value, + String? eventName, + Map? parameters, + String? attributeKey, + }) async { + final moEngage = GetIt.instance(); + + // Handle standard user operations + switch (operation) { + // User Attributes + case 'setUniqueId': + await moEngage.setUniqueId(Utils.getString(value, fallback: '')); + break; + case 'setUserName': + await moEngage.setUserName(Utils.getString(value, fallback: '')); + break; + case 'setFirstName': + await moEngage.setFirstName(Utils.getString(value, fallback: '')); + break; + case 'setLastName': + await moEngage.setLastName(Utils.getString(value, fallback: '')); + break; + case 'setEmail': + await moEngage.setEmail(Utils.getString(value, fallback: '')); + break; + case 'setPhoneNumber': + await moEngage.setPhoneNumber(Utils.getString(value, fallback: '')); + break; + case 'setBirthDate': + await moEngage.setBirthDate(Utils.getString(value, fallback: '')); + break; + case 'setGender': + await moEngage + .setGender(MoEGender.values.from(value) ?? MoEGender.male); + break; + case 'setAlias': + await moEngage.setAlias(Utils.getString(value, fallback: '')); + break; + case 'setLocation': + final location = getLocation(value); + if (location != null) { + await moEngage.setLocation(location); + } + break; + + // Custom Attributes + case 'custom': + await moEngage.setUserAttribute(attributeKey!, value); + break; + case 'timestamp': + await moEngage.setUserAttributeIsoDate( + attributeKey!, Utils.getString(value, fallback: '')); + break; + case 'locationAttribute': + final location = getLocation(value); + if (location != null) { + await moEngage.setUserAttributeLocation(attributeKey!, location); + } + break; + + // Tracking Events + case 'trackEvent': + if (parameters != null) { + final moEProperties = MoEProperties(); + parameters.forEach((key, value) { + moEProperties.addAttribute(key, value); + }); + await moEngage.trackEvent(eventName!, moEProperties); + } else { + await moEngage.trackEvent(eventName!); + } + break; + + // App Configuration + case 'enableSdk': + await moEngage.enableSdk(); + break; + case 'disableSdk': + await moEngage.disableSdk(); + break; + case 'enableDataTracking': + await moEngage.enableDataTracking(); + break; + case 'disableDataTracking': + await moEngage.disableDataTracking(); + break; + case 'enableDeviceIdTracking': + await moEngage.enableDeviceIdTracking(); + break; + case 'disableDeviceIdTracking': + await moEngage.disableDeviceIdTracking(); + break; + case 'enableAndroidIdTracking': + await moEngage.enableAndroidIdTracking(); + break; + case 'disableAndroidIdTracking': + await moEngage.disableAndroidIdTracking(); + break; + case 'enableAdIdTracking': + await moEngage.enableAdIdTracking(); + break; + case 'disableAdIdTracking': + await moEngage.disableAdIdTracking(); + break; + case 'setAppStatus': + await moEngage.setAppStatus(MoEAppStatus.values.from(value)!); + break; + case 'logout': + await moEngage.logout(); + break; + case 'deleteUser': + await moEngage.deleteUser(); + break; + + // Push Configuration + case 'registerForPush': + await moEngage.registerForPushNotification(); + break; + case 'registerForProvisionalPush': + await moEngage.registerForProvisionalPush(); + break; + case 'passFCMToken': + await moEngage.passFCMPushToken(value.toString()); + break; + case 'passPushKitToken': + await moEngage.passPushKitPushToken(value.toString()); + break; + case 'passFCMPushPayload': + await moEngage.passFCMPushPayload(Map.from(value)); + break; + case 'requestPushPermission': + await moEngage.requestPushPermissionAndroid(); + break; + case 'updatePermissionCount': + await moEngage.updatePushPermissionRequestCountAndroid(value); + break; + case 'pushPermissionResponse': + await moEngage.pushPermissionResponseAndroid(value); + break; + + // Display Configuration + case 'showInApp': + await moEngage.showInApp(); + break; + case 'showNudge': + await moEngage.showNudge( + position: MoEngageNudgePosition.values.from(value) ?? + MoEngageNudgePosition.bottom); + break; + case 'setContext': + if (value is List) { + final contextList = value.map((e) => e.toString()).toList(); + await moEngage.setCurrentContext(contextList); + } + break; + case 'resetContext': + await moEngage.resetCurrentContext(); + break; + } + } +} + +// Operations that don't require value parameter +enum NoValueOperations { + showInApp, + showNudge, + resetContext, + setContext, + logout, + deleteUser, + enableSdk, + disableSdk, + enableDataTracking, + disableDataTracking, + enableDeviceIdTracking, + disableDeviceIdTracking, + enableAndroidIdTracking, + disableAndroidIdTracking, + enableAdIdTracking, + disableAdIdTracking, + requestPushPermission, + trackEvent, + registerForPush, + registerForProvisionalPush, + passFCMToken, + passPushKitToken, + passFCMPushPayload, + updatePermissionCount, + pushPermissionResponse, } diff --git a/modules/ensemble/lib/action/moengage_actions.dart b/modules/ensemble/lib/action/moengage_actions.dart deleted file mode 100644 index 7049e5683..000000000 --- a/modules/ensemble/lib/action/moengage_actions.dart +++ /dev/null @@ -1,382 +0,0 @@ -import 'package:ensemble/framework/action.dart'; -import 'package:ensemble/framework/error_handling.dart'; -import 'package:ensemble/framework/event.dart'; -import 'package:ensemble/framework/extensions.dart'; -import 'package:ensemble/framework/scope.dart'; -import 'package:ensemble/framework/stub/moengage_manager.dart'; -import 'package:ensemble/screen_controller.dart'; -import 'package:ensemble/util/utils.dart'; -import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; -import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; -import 'package:moengage_flutter/moengage_flutter.dart'; - -// Main action types available for MoEngage -enum MoEngageActionType { - // User attributes - setUser, // Generic action to set any user attribute - - // Events - trackEvent, - - // App State - appConfig, - - // Push/Notification - pushConfig, - - // InApp & Cards - displayConfig -} - -// Sub-types for setUser action -enum MoEngageUserAttributeType { - uniqueId, - userName, - firstName, - lastName, - email, - mobile, - gender, - birthday, - location, - alias, - custom, // For custom attribute with key-value - timestamp, - locationAttribute -} - -// Sub-types for app configuration -enum MoEngageAppConfigType { - enableSdk, - disableSdk, - enableDataTracking, - disableDataTracking, - enableDeviceIdTracking, - disableDeviceIdTracking, - enableAndroidIdTracking, - disableAndroidIdTracking, - enableAdIdTracking, - disableAdIdTracking, - setAppStatus, - logout, - deleteUser -} - -// Sub-types for push notification configuration -enum MoEngagePushConfigType { - registerForPush, - registerForProvisionalPush, - passFCMToken, - passPushKitToken, - passFCMPushPayload, - requestPushPermission, - updatePermissionCount, - pushPermissionResponse -} - -// Sub-types for display configuration -enum MoEngageDisplayConfigType { - showInApp, - showNudge, - setContext, - resetContext -} - -class MoEngageAction extends EnsembleAction { - final MoEngageActionType? type; - final dynamic actionType; // Sub type enum based on main type - final dynamic value; - final Map? properties; // For events and custom attributes - final String? attributeKey; // For custom user attributes - final EnsembleAction? onSuccess; - final EnsembleAction? onError; - - MoEngageAction( - {super.initiator, - required this.type, - this.actionType, - this.value, - this.properties, - this.attributeKey, - this.onSuccess, - this.onError}); - - factory MoEngageAction.fromYaml({Invokable? initiator, Map? payload}) { - if (payload == null || payload['type'] == null) { - throw ConfigError('MoEngage action requires type'); - } - - return MoEngageAction( - initiator: initiator, - type: MoEngageActionType.values.from(payload['type']) ?? null, - actionType: _getSubType(payload['type'], payload['actionType']), - value: payload['value'], - properties: Utils.getMap(payload['properties']), - attributeKey: Utils.optionalString(payload['attributeKey']), - onSuccess: EnsembleAction.from(payload['onSuccess']), - onError: EnsembleAction.from(payload['onError'])); - } - - static dynamic _getSubType(String mainType, String? subType) { - if (subType == null) return null; - - switch (MoEngageActionType.values.from(mainType)) { - case MoEngageActionType.setUser: - return MoEngageUserAttributeType.values.from(subType); - case MoEngageActionType.appConfig: - return MoEngageAppConfigType.values.from(subType); - case MoEngageActionType.pushConfig: - return MoEngagePushConfigType.values.from(subType); - case MoEngageActionType.displayConfig: - return MoEngageDisplayConfigType.values.from(subType); - default: - return null; - } - } - - @override - Future execute(BuildContext context, ScopeManager scopeManager) async { - try { - final moEngage = GetIt.instance(); - final evaluatedValue = scopeManager.dataContext.eval(value); - - if (type == null) { - throw ConfigError('MoEngage action requires a valid type'); - } - print(type); - - switch (type!) { - case MoEngageActionType.setUser: - await _handleUserAttribute(moEngage, evaluatedValue); - break; - - case MoEngageActionType.trackEvent: - await _handleTrackEvent(moEngage, evaluatedValue); - break; - - case MoEngageActionType.appConfig: - await _handleAppConfig(moEngage, evaluatedValue); - break; - - case MoEngageActionType.pushConfig: - await _handlePushConfig(moEngage, evaluatedValue); - break; - - case MoEngageActionType.displayConfig: - await _handleDisplayConfig(moEngage, evaluatedValue); - break; - } - - if (onSuccess != null) { - ScreenController().executeAction(context, onSuccess!); - } - } catch (error) { - if (onError != null) { - ScreenController().executeAction(context, onError!, - event: EnsembleEvent(null, error: error.toString())); - } else { - rethrow; - } - } - } - - Future _handleUserAttribute( - MoEngageModule moEngage, dynamic value) async { - - if (value == null) { - throw ConfigError('User attribute value cannot be null'); - } - - // Check attributeKey for types that require it - if ((actionType == MoEngageUserAttributeType.custom || - actionType == MoEngageUserAttributeType.timestamp || - actionType == MoEngageUserAttributeType.locationAttribute) && - (attributeKey == null || attributeKey!.isEmpty)) { - throw ConfigError('Attribute key is required for ${actionType.name}'); - } - - switch (actionType as MoEngageUserAttributeType) { - case MoEngageUserAttributeType.uniqueId: - await moEngage.setUniqueId(Utils.getString(value, fallback: '')); - break; - - case MoEngageUserAttributeType.userName: - await moEngage.setUserName(Utils.getString(value, fallback: '')); - break; - - case MoEngageUserAttributeType.firstName: - await moEngage.setFirstName(Utils.getString(value, fallback: '')); - break; - - case MoEngageUserAttributeType.lastName: - await moEngage.setLastName(Utils.getString(value, fallback: '')); - break; - - case MoEngageUserAttributeType.email: - await moEngage.setEmail(Utils.getString(value, fallback: '')); - break; - - case MoEngageUserAttributeType.mobile: - await moEngage.setPhoneNumber(Utils.getString(value, fallback: '')); - break; - - case MoEngageUserAttributeType.gender: - await moEngage - .setGender(MoEGender.values.from(value) ?? MoEGender.male); - break; - - case MoEngageUserAttributeType.birthday: - await moEngage.setBirthDate(Utils.getString(value, fallback: '')); - break; - - case MoEngageUserAttributeType.location: - final location = getLocation(value); - if (location != null) { - await moEngage.setLocation(location); - } - break; - - case MoEngageUserAttributeType.alias: - await moEngage.setAlias(Utils.getString(value, fallback: '')); - break; - - case MoEngageUserAttributeType.custom: - await moEngage.setUserAttribute(attributeKey!, value); - break; - - case MoEngageUserAttributeType.timestamp: - await moEngage.setUserAttributeIsoDate( - attributeKey!, - Utils.getString(value, fallback: '') - ); - break; - - case MoEngageUserAttributeType.locationAttribute: - final location = getLocation(value); - if (location != null) { - await moEngage.setUserAttributeLocation(attributeKey!, location); - } - break; - } - } - - Future _handleTrackEvent( - MoEngageModule moEngage, String eventName) async { - if (properties != null) { - final moEProperties = MoEProperties(); - properties!.forEach((key, value) { - moEProperties.addAttribute(key, value); - }); - await moEngage.trackEvent(eventName, moEProperties); - } else { - await moEngage.trackEvent(eventName); - } - } - - Future _handleAppConfig(MoEngageModule moEngage, dynamic value) async { - switch (actionType as MoEngageAppConfigType) { - case MoEngageAppConfigType.enableSdk: - await moEngage.enableSdk(); - break; - case MoEngageAppConfigType.disableSdk: - await moEngage.disableSdk(); - break; - case MoEngageAppConfigType.enableDataTracking: - await moEngage.enableDataTracking(); - break; - case MoEngageAppConfigType.disableDataTracking: - await moEngage.disableDataTracking(); - break; - case MoEngageAppConfigType.enableDeviceIdTracking: - await moEngage.enableDeviceIdTracking(); - break; - case MoEngageAppConfigType.disableDeviceIdTracking: - await moEngage.disableDeviceIdTracking(); - break; - case MoEngageAppConfigType.enableAndroidIdTracking: - await moEngage.enableAndroidIdTracking(); - break; - case MoEngageAppConfigType.disableAndroidIdTracking: - await moEngage.disableAndroidIdTracking(); - break; - case MoEngageAppConfigType.enableAdIdTracking: - await moEngage.enableAdIdTracking(); - break; - case MoEngageAppConfigType.disableAdIdTracking: - await moEngage.disableAdIdTracking(); - break; - case MoEngageAppConfigType.setAppStatus: - await moEngage.setAppStatus(MoEAppStatus.values.from(value)!); - break; - case MoEngageAppConfigType.logout: - await moEngage.logout(); - break; - case MoEngageAppConfigType.deleteUser: - await moEngage.deleteUser(); - break; - } - } - - Future _handlePushConfig(MoEngageModule moEngage, dynamic value) async { - switch (actionType as MoEngagePushConfigType) { - case MoEngagePushConfigType.registerForPush: - await moEngage.registerForPushNotification(); - break; - case MoEngagePushConfigType.registerForProvisionalPush: - await moEngage.registerForProvisionalPush(); - break; - case MoEngagePushConfigType.passFCMToken: - await moEngage.passFCMPushToken(value.toString()); - break; - case MoEngagePushConfigType.passPushKitToken: - await moEngage.passPushKitPushToken(value.toString()); - break; - case MoEngagePushConfigType.passFCMPushPayload: - await moEngage.passFCMPushPayload(Map.from(value)); - break; - case MoEngagePushConfigType.requestPushPermission: - await moEngage.requestPushPermissionAndroid(); - break; - case MoEngagePushConfigType.updatePermissionCount: - await moEngage.updatePushPermissionRequestCountAndroid(value); - break; - case MoEngagePushConfigType.pushPermissionResponse: - await moEngage.pushPermissionResponseAndroid(value); - break; - } - } - - Future _handleDisplayConfig( - MoEngageModule moEngage, dynamic value) async { - switch (actionType as MoEngageDisplayConfigType) { - case MoEngageDisplayConfigType.showInApp: - await moEngage.showInApp(); - break; - case MoEngageDisplayConfigType.showNudge: - await moEngage.showNudge( - position: MoEngageNudgePosition.values.from(value)!); - break; - case MoEngageDisplayConfigType.setContext: - await moEngage.setCurrentContext(List.from(value)); - break; - case MoEngageDisplayConfigType.resetContext: - await moEngage.resetCurrentContext(); - break; - } - } - - static MoEGeoLocation? getLocation(dynamic value) { - if (value is Map) { - final lat = Utils.getDouble(value['latitude'], fallback: 0); - final lng = Utils.getDouble(value['longitude'], fallback: 0); - return MoEGeoLocation(lat, lng); - } - - final locationData = Utils.getLatLng(value); - if (locationData != null) { - return MoEGeoLocation(locationData.latitude, locationData.longitude); - } - return null; - } -} diff --git a/modules/ensemble/lib/framework/action.dart b/modules/ensemble/lib/framework/action.dart index 51c0c15b6..5437e97b1 100644 --- a/modules/ensemble/lib/framework/action.dart +++ b/modules/ensemble/lib/framework/action.dart @@ -40,7 +40,6 @@ import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; import 'package:flutter/material.dart'; import 'package:source_span/source_span.dart'; import 'package:yaml/yaml.dart'; -import 'package:ensemble/action/moengage_actions.dart'; class ShowCameraAction extends EnsembleAction { ShowCameraAction({ @@ -1052,7 +1051,6 @@ enum ActionType { bluetoothDisconnect, bluetoothSubscribeCharacteristic, bluetoothUnsubscribeCharacteristic, - moEngageEvents, } /// payload representing an Action to do (navigateToScreen, InvokeAPI, ..) @@ -1269,8 +1267,6 @@ abstract class EnsembleAction { return SubscribeBluetoothCharacteristicsAction.from(payload: payload); } else if (actionType == ActionType.bluetoothUnsubscribeCharacteristic) { return UnSubscribeBluetoothCharacteristicsAction.from(payload: payload); - } else if (actionType == ActionType.moEngageEvents) { - return MoEngageAction.fromYaml(initiator: initiator, payload: payload); } else { throw LanguageError("Invalid action.", recovery: "Make sure to use one of Ensemble-provided actions."); diff --git a/modules/ensemble/lib/framework/notification_manager.dart b/modules/ensemble/lib/framework/notification_manager.dart index 47516e591..c353246a8 100644 --- a/modules/ensemble/lib/framework/notification_manager.dart +++ b/modules/ensemble/lib/framework/notification_manager.dart @@ -78,9 +78,6 @@ class NotificationManager { /// This is when the app is in the foreground FirebaseMessaging.onMessage.listen((RemoteMessage message) async { - print("--------------"); - print(message.data); - print(message.notification); if (!isMoEngageNotification(message)) { // Handle regular FCM notifications as before @@ -154,15 +151,12 @@ class NotificationManager { } on Exception catch (e) { print("NotificationManager: Error receiving notification: $e"); } - print("----------"); - print(payload); if (payload is! Map) return; if (payload.containsKey('status') && (payload['status'] as String).toLowerCase() == 'error') { print('Error while running js function'); } final action = NavigateScreenAction.from(payload); - print(action.screenName); ScreenController().navigateToScreen(Utils.globalAppKey.currentContext!, screenName: action.screenName, diff --git a/modules/ensemble/lib/framework/stub/moengage_manager.dart b/modules/ensemble/lib/framework/stub/moengage_manager.dart index a46c55a2a..1bf01db29 100644 --- a/modules/ensemble/lib/framework/stub/moengage_manager.dart +++ b/modules/ensemble/lib/framework/stub/moengage_manager.dart @@ -80,9 +80,7 @@ abstract class MoEngageModule { class MoEngageModuleStub implements MoEngageModule { final _errorMsg = "MoEngage module is not enabled. Please review the Ensemble documentation."; - MoEngageModuleStub() { - throw RuntimeError(_errorMsg); - } + MoEngageModuleStub() {} @override Future initialize(String workspaceId, {bool enableLogs = false}) { diff --git a/modules/moengage/lib/moengage.dart b/modules/moengage/lib/moengage.dart index 22cb4f17c..cc16f6fee 100644 --- a/modules/moengage/lib/moengage.dart +++ b/modules/moengage/lib/moengage.dart @@ -146,7 +146,6 @@ class MoEngageImpl implements MoEngageModule { @override Future setUserName(String userName) async { - print(userName); _checkInitialization(); _moengagePlugin.setUserName(userName); } diff --git a/modules/moengage/lib/moengage_notification_handler.dart b/modules/moengage/lib/moengage_notification_handler.dart index 32656285e..ff2a2184c 100644 --- a/modules/moengage/lib/moengage_notification_handler.dart +++ b/modules/moengage/lib/moengage_notification_handler.dart @@ -1,9 +1,12 @@ import 'dart:convert'; import 'dart:io'; import 'package:ensemble/framework/notification_manager.dart'; +import 'package:ensemble/screen_controller.dart'; +import 'package:ensemble/util/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:moengage_flutter/moengage_flutter.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; class MoEngageNotificationHandler { static final MoEngageNotificationHandler _instance = @@ -24,6 +27,7 @@ class MoEngageNotificationHandler { _moengagePlugin = moengagePlugin; await _registerHandlers(); + // await _setupPushToken(); _initialized = true; } catch (e) { @@ -51,6 +55,7 @@ class MoEngageNotificationHandler { if (message.action is NavigationAction) { final navAction = message.action as NavigationAction; inAppData['data'].addAll({ + 'actionType': 'navigation', 'navigationType': navAction.navigationType.toString(), 'navigationUrl': navAction.navigationUrl, 'keyValuePairs': navAction.keyValuePairs, From d04cebb9d27bd824444ac99847f9a7e6f664c07d Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Mon, 30 Dec 2024 21:07:23 +0500 Subject: [PATCH 5/5] updated notification payload --- modules/moengage/lib/moengage_notification_handler.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/moengage/lib/moengage_notification_handler.dart b/modules/moengage/lib/moengage_notification_handler.dart index ff2a2184c..86e5dfd0d 100644 --- a/modules/moengage/lib/moengage_notification_handler.dart +++ b/modules/moengage/lib/moengage_notification_handler.dart @@ -48,6 +48,7 @@ class MoEngageNotificationHandler { 'campaignId': message.campaignData.campaignId, 'campaignName': message.campaignData.campaignName, 'platform': message.platform.toString().split('.').last.toLowerCase(), + 'notificationType': 'inApp', 'data': {}, // Base data object }; @@ -82,6 +83,7 @@ class MoEngageNotificationHandler { ...pushData.payload, // Include all original payload 'clickedAction': pushData.clickedAction, 'platform': message.platform.toString().split('.').last.toLowerCase(), + 'notificationType': 'push', }; if (Platform.isAndroid) {