diff --git a/pushengage-flutter-sdk/.gitignore b/pushengage-flutter-sdk/.gitignore new file mode 100644 index 0000000..10cd633 --- /dev/null +++ b/pushengage-flutter-sdk/.gitignore @@ -0,0 +1,27 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# VS Code +.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/pushengage-flutter-sdk/.metadata b/pushengage-flutter-sdk/.metadata new file mode 100644 index 0000000..458e07f --- /dev/null +++ b/pushengage-flutter-sdk/.metadata @@ -0,0 +1,33 @@ +# 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: "761747bfc538b5af34aa0d3fac380f1bc331ec49" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: android + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: ios + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/pushengage-flutter-sdk/CHANGELOG.md b/pushengage-flutter-sdk/CHANGELOG.md new file mode 100644 index 0000000..26ca5d1 --- /dev/null +++ b/pushengage-flutter-sdk/CHANGELOG.md @@ -0,0 +1,2 @@ +For detailed version history and comprehensive release notes, visit our GitHub releases page: https://github.com/awesomemotive/pushengage-flutter-sdk/releases + diff --git a/pushengage-flutter-sdk/CONTRIBUTING.md b/pushengage-flutter-sdk/CONTRIBUTING.md new file mode 100644 index 0000000..c3794c9 --- /dev/null +++ b/pushengage-flutter-sdk/CONTRIBUTING.md @@ -0,0 +1,28 @@ + +# Contributing to PushEngage Flutter SDK + +We appreciate contributions from the community to improve and maintain the SDK. Please review the following guidelines before submitting issues or pull requests. + +## How to Contribute + +### Reporting Issues +If you encounter any issues or bugs: +1. Search for existing issues to avoid duplicates. +2. If a similar issue is not found, create a new issue with a detailed description. + +### Pull Requests +1. **Fork the Repository**: Create a fork of the repository on GitHub. +2. **Create a Branch**: Branch off from `main` for your contribution (e.g., `feature/add-deep-link-support`). +3. **Commit Changes**: Follow clear, concise commit messages. +4. **Create a Pull Request**: Open a pull request from your forked branch to the `main` branch of this repository. + +### Code Style +Please ensure your code adheres to Flutter’s style guidelines. This includes: +- Proper indentation and naming conventions. +- Comprehensive comments for complex logic. + +### Reviewing and Merging +Pull requests will be reviewed by our maintainers. If revisions are needed, the maintainer will communicate these in the PR comments. + +## Thank You! +We appreciate your effort to improve our SDK! diff --git a/pushengage-flutter-sdk/LICENSE b/pushengage-flutter-sdk/LICENSE new file mode 100644 index 0000000..36457bb --- /dev/null +++ b/pushengage-flutter-sdk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 PushEngage + +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/pushengage-flutter-sdk/README.md b/pushengage-flutter-sdk/README.md new file mode 100644 index 0000000..3ad42a1 --- /dev/null +++ b/pushengage-flutter-sdk/README.md @@ -0,0 +1,24 @@ +

+ +

+ +# PushEngage Flutter SDK + +The PushEngage Flutter SDK simplifies the integration of push notifications into your Flutter apps, supporting both Android and iOS platforms. + +## Introduction +PushEngage SDK enables seamless push notifications for your Flutter applications, enhancing user engagement on Android and iOS. + +## Prerequisites +Before setup, ensure you have: +- A Flutter project. +- [PushEngage account](https://www.pushengage.com) +- Firebase project for Android. +- Apple Developer account for APN services. + +## Getting started guide +Please follow the instructions as mentioned in the [Getting Started Guide](https://www.pushengage.com/documentation/setting-up-app-push-notification-in-flutter-using-pushengage/) + +## Flutter Plugin Public APIs +Please follow the instructions as mentioned in the [PushEngage Flutter Public APIs](https://pushengage.com/api/mobile-sdk/flutter-sdk) + diff --git a/pushengage-flutter-sdk/analysis_options.yaml b/pushengage-flutter-sdk/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/pushengage-flutter-sdk/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/pushengage-flutter-sdk/android/.gitignore b/pushengage-flutter-sdk/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/pushengage-flutter-sdk/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/pushengage-flutter-sdk/android/build.gradle b/pushengage-flutter-sdk/android/build.gradle new file mode 100644 index 0000000..b3fc9a6 --- /dev/null +++ b/pushengage-flutter-sdk/android/build.gradle @@ -0,0 +1,68 @@ +group = "com.pushengage.pushengage_flutter_sdk" +version = "1.0-SNAPSHOT" + +buildscript { + ext.kotlin_version = "1.7.10" + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:7.3.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: "com.android.library" +apply plugin: "kotlin-android" + +android { + if (project.android.hasProperty("namespace")) { + namespace = "com.pushengage.pushengage_flutter_sdk" + } + + compileSdk = 34 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" + } + + sourceSets { + main.java.srcDirs += "src/main/kotlin" + test.java.srcDirs += "src/test/kotlin" + } + + defaultConfig { + minSdk = 21 + } + + dependencies { + implementation 'com.github.awesomemotive:pushengage-android-sdk:0.0.5' + testImplementation("org.jetbrains.kotlin:kotlin-test") + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/pushengage-flutter-sdk/android/settings.gradle b/pushengage-flutter-sdk/android/settings.gradle new file mode 100644 index 0000000..df5ffce --- /dev/null +++ b/pushengage-flutter-sdk/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'pushengage_flutter_sdk' diff --git a/pushengage-flutter-sdk/android/src/main/AndroidManifest.xml b/pushengage-flutter-sdk/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..216f9a5 --- /dev/null +++ b/pushengage-flutter-sdk/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/pushengage-flutter-sdk/android/src/main/kotlin/com/pushengage/pushengage_flutter_sdk/PushengageFlutterSdkPlugin.kt b/pushengage-flutter-sdk/android/src/main/kotlin/com/pushengage/pushengage_flutter_sdk/PushengageFlutterSdkPlugin.kt new file mode 100644 index 0000000..262c0b6 --- /dev/null +++ b/pushengage-flutter-sdk/android/src/main/kotlin/com/pushengage/pushengage_flutter_sdk/PushengageFlutterSdkPlugin.kt @@ -0,0 +1,445 @@ +package com.pushengage.pushengage_flutter_sdk + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.pushengage.pushengage.Callbacks.PushEngageResponseCallback +import com.pushengage.pushengage.PushEngage +import com.pushengage.pushengage.model.request.AddDynamicSegmentRequest +import com.pushengage.pushengage.model.request.Goal +import com.pushengage.pushengage.model.request.TriggerAlert +import com.pushengage.pushengage.model.request.TriggerCampaign +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.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry +import java.text.SimpleDateFormat +import java.util.Locale +import org.json.JSONObject + +/** PushEngageFlutterSdkPlugin */ +class PushEngageFlutterSdkPlugin : + FlutterPlugin, + MethodCallHandler, + ActivityAware, + PluginRegistry.RequestPermissionsResultListener, + PluginRegistry.NewIntentListener { + + private lateinit var channel: MethodChannel + private lateinit var context: Context + private lateinit var activity: Activity + private var permissionResult: Result? = null + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "PushEngage") + channel.setMethodCallHandler(this) + context = flutterPluginBinding.applicationContext + } + + private fun handleIntent(intent: Intent) { + val action = intent.action + val data = intent.data + if (Intent.ACTION_VIEW == action && data != null) { + val deepLink = data.toString() + val additionalData = intent.extras?.get("data") + channel.invokeMethod( + "onDeepLink", + mapOf("deepLink" to deepLink, "data" to additionalData) + ) + } + } + + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "PushEngage#setAppId" -> { + PushEngage.Builder() + .addContext(context) + .setAppId(call.argument("appId").toString()) + .build() + + result.success(null) + } + "PushEngage#getSdkVersion" -> { + result.success(PushEngage.getSdkVersion()) + } + "PushEngage#getDeviceTokenHash" -> { + result.success(PushEngage.getDeviceTokenHash()) + } + "PushEngage#enableLogging" -> { + val status = call.argument("status") ?: false + PushEngage.enableLogging(status) + result.success(null) + } + "PushEngage#automatedNotification" -> { + val status = call.argument("status") ?: true + PushEngage.automatedNotification( + if (status) PushEngage.TriggerStatusType.enabled + else PushEngage.TriggerStatusType.disabled, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any) { + result.success( + "Automated notification " + + (if (status) "enabled" else "disabled") + + " successfully" + ) + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + "PushEngage#sendTriggerEvent" -> { + val triggerMap = call.arguments>() + sendTriggerEvent(triggerMap, result) + } + "PushEngage#addAlert" -> { + val map = call.arguments>() + if (map == null) { + result.error("INVALID_ARGUMENT", "Missing required arguments", null) + return + } + addAlert(map, result) + } + "PushEngage#sendGoal" -> { + call.arguments>()?.let { goalMap -> + val goalName = goalMap["name"] as? String + val goalCount = goalMap["count"] as? Int + val value = goalMap["value"] as? Double + val goal = Goal(goalName ?: "", goalCount, value) + + PushEngage.sendGoal( + goal, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any) { + result.success("Goal sent successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + } + "PushEngage#getSubscriberDetails" -> { + val subscriberAttributes = call.argument>("values") + PushEngage.getSubscriberDetails( + subscriberAttributes, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + val jsonResponse = + JSONObject(responseObject as Map<*, *>).toString() + result.success(jsonResponse) + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + "PushEngage#requestNotificationPermission" -> { + requestNotificationPermission(result) + } + "PushEngage#getSubscriberAttributes" -> { + PushEngage.getSubscriberAttributes( + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + if (responseObject != null) { + val jsonResponse = responseObject as Map<*, *> + result.success(jsonResponse) + } else { + result.success(mapOf()) + } + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + "PushEngage#addSegment" -> { + val segments = call.argument>("segments") + PushEngage.addSegment( + segments, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + result.success("Subscriber added to segment(s) successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + "PushEngage#removeSegment" -> { + val segments = call.argument>("segments") + PushEngage.removeSegment( + segments, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + result.success("Subscriber removed from segment(s) successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + "PushEngage#addDynamicSegment" -> { + val segmentsList = call.argument>>("segments") ?: emptyList() + val segments: MutableList = ArrayList() + segmentsList.forEach { map -> + val segment = AddDynamicSegmentRequest().Segment() + segment.name = map["name"] as String + segment.duration = (map["duration"] as Number).toLong() + segments.add(segment) + } + PushEngage.addDynamicSegment( + segments, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + result.success("Subscriber added to dynamic segment successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + "PushEngage#addSubscriberAttributes" -> { + val jsonString = call.argument("attributes") + try { + val jsonObject = JSONObject(jsonString ?: "{}") + PushEngage.addSubscriberAttributes( + jsonObject, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + result.success("Subscriber attribute(s) added successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } catch (e: Exception) { + result.error("INVALID_ARGUMENT", "Missing required arguments", null) + } + } + "PushEngage#deleteSubscriberAttributes" -> { + val attributes = call.argument>("attributes") + PushEngage.deleteSubscriberAttributes( + attributes, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + result.success("Subscriber attribute(s) deleted successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + "PushEngage#addProfileId" -> { + val profileId = call.argument("profileId") + PushEngage.addProfileId( + profileId, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + result.success("Profile Id added successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + "PushEngage#setSubscriberAttributes" -> { + val jsonString = call.argument("attributes") + try { + val jsonObject = JSONObject(jsonString ?: "{}") + PushEngage.setSubscriberAttributes( + jsonObject, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any?) { + result.success("Subscriber attribute(s) set successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } catch (e: Exception) { + result.error(400.toString(), "Invalid input", null) + } + } + "PushEngage#setSmallIconResource" -> { + val resourceName = call.argument("resourceName").toString() + PushEngage.setSmallIconResource(resourceName) + } + else -> { + result.notImplemented() + } + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + binding.addRequestPermissionsResultListener(this) + binding.addOnNewIntentListener(this) + handleIntent(activity.intent) + } + + override fun onDetachedFromActivityForConfigChanges() {} + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activity = binding.activity + binding.addOnNewIntentListener(this) + } + + override fun onDetachedFromActivity() {} + + private fun requestNotificationPermission(result: Result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission( + activity, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + permissionResult = result + ActivityCompat.requestPermissions( + activity, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + 100 + ) + } else { + // Permission already granted + result.success(true) + } + } else { + // For versions below Android 13, notification permission is granted at install time. + result.success(true) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ): Boolean { + when (requestCode) { + 100 -> { // The request code used in requestPermissions + val isGranted = + grantResults.isNotEmpty() && + grantResults[0] == PackageManager.PERMISSION_GRANTED + permissionResult?.success(isGranted) + PushEngage.subscribe() + return true + } + } + return false + } + + private fun addAlert(map: Map, result: Result) { + val alert = + TriggerAlert( + type = getAlertType(map["type"] as String), + productId = map["productId"] as String, + link = map["link"] as String, + price = map["price"] as Double, + variantId = map["variantId"] as String?, + expiryTimestamp = + map["expiryTimestamp"]?.let { + SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS", + Locale.getDefault() + ) + .parse(it as String) + }, + alertPrice = map["alertPrice"] as? Double, + availability = + (map["availability"] as? String)?.let { + PushEngage.TriggerAlertAvailabilityType.valueOf(it) + }, + profileId = map["profileId"] as? String, + mrp = map["mrp"] as? Double, + data = map["data"] as? Map + ) + + PushEngage.addAlert( + alert, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any) { + result.success("Alert added successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + + private fun getAlertType(type: String): PushEngage.TriggerAlertType { + if (type == "priceDrop") { + return PushEngage.TriggerAlertType.priceDrop + } else { + return PushEngage.TriggerAlertType.inventory + } + } + + private fun sendTriggerEvent(triggerMap: Map?, result: Result) { + val triggerCampaign = + (triggerMap?.get("campaignName") as? String)?.let { campaignName -> + (triggerMap["eventName"] as? String)?.let { eventName -> + TriggerCampaign( + campaignName = campaignName, + eventName = eventName, + referenceId = triggerMap["referenceId"] as? String, + profileId = triggerMap["profileId"] as? String, + data = triggerMap["data"] as? Map + ) + } + } + PushEngage.sendTriggerEvent( + triggerCampaign, + object : PushEngageResponseCallback { + override fun onSuccess(responseObject: Any) { + result.success("Trigger sent successfully") + } + + override fun onFailure(errorCode: Int, errorMessage: String) { + result.error(errorCode.toString(), errorMessage, null) + } + } + ) + } + + override fun onNewIntent(intent: Intent): Boolean { + handleIntent(intent) + return false + } +} diff --git a/pushengage-flutter-sdk/example/.gitignore b/pushengage-flutter-sdk/example/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/pushengage-flutter-sdk/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/pushengage-flutter-sdk/example/README.md b/pushengage-flutter-sdk/example/README.md new file mode 100644 index 0000000..7d88654 --- /dev/null +++ b/pushengage-flutter-sdk/example/README.md @@ -0,0 +1,5 @@ +# pushengage_flutter_sdk_example + +Sample app for pushengage_flutter_sdk plugin. + + diff --git a/pushengage-flutter-sdk/example/analysis_options.yaml b/pushengage-flutter-sdk/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/pushengage-flutter-sdk/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/pushengage-flutter-sdk/example/android/.gitignore b/pushengage-flutter-sdk/example/android/.gitignore new file mode 100644 index 0000000..a4b4ee1 --- /dev/null +++ b/pushengage-flutter-sdk/example/android/.gitignore @@ -0,0 +1,15 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks + +**/google-services.json diff --git a/pushengage-flutter-sdk/example/android/app/build.gradle b/pushengage-flutter-sdk/example/android/app/build.gradle new file mode 100644 index 0000000..d24505a --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/build.gradle @@ -0,0 +1,60 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +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 { + namespace = "com.example.pushengage_flutter_sdk_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.pushengage_flutter_sdk_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} + +apply plugin: 'com.google.gms.google-services' diff --git a/pushengage-flutter-sdk/example/android/app/src/debug/AndroidManifest.xml b/pushengage-flutter-sdk/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/pushengage-flutter-sdk/example/android/app/src/main/AndroidManifest.xml b/pushengage-flutter-sdk/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..69f96fd --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pushengage-flutter-sdk/example/android/app/src/main/kotlin/com/example/pushengage_flutter_sdk_example/MainActivity.kt b/pushengage-flutter-sdk/example/android/app/src/main/kotlin/com/example/pushengage_flutter_sdk_example/MainActivity.kt new file mode 100644 index 0000000..1442941 --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/src/main/kotlin/com/example/pushengage_flutter_sdk_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.pushengage_flutter_sdk_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/drawable-v21/launch_background.xml b/pushengage-flutter-sdk/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/drawable/launch_background.xml b/pushengage-flutter-sdk/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/pushengage-flutter-sdk/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/values-night/styles.xml b/pushengage-flutter-sdk/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/pushengage-flutter-sdk/example/android/app/src/main/res/values/styles.xml b/pushengage-flutter-sdk/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/pushengage-flutter-sdk/example/android/app/src/profile/AndroidManifest.xml b/pushengage-flutter-sdk/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/pushengage-flutter-sdk/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/pushengage-flutter-sdk/example/android/build.gradle b/pushengage-flutter-sdk/example/android/build.gradle new file mode 100644 index 0000000..5bc2fbc --- /dev/null +++ b/pushengage-flutter-sdk/example/android/build.gradle @@ -0,0 +1,31 @@ +allprojects { + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} + +buildscript { + repositories { + google() + jcenter() + mavenCentral() + } + dependencies { + // Add this line + classpath 'com.google.gms:google-services:4.3.8' + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/pushengage-flutter-sdk/example/android/gradle.properties b/pushengage-flutter-sdk/example/android/gradle.properties new file mode 100644 index 0000000..3b5b324 --- /dev/null +++ b/pushengage-flutter-sdk/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/pushengage-flutter-sdk/example/android/settings.gradle b/pushengage-flutter-sdk/example/android/settings.gradle new file mode 100644 index 0000000..536165d --- /dev/null +++ b/pushengage-flutter-sdk/example/android/settings.gradle @@ -0,0 +1,25 @@ +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 + }() + + includeBuild("$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 "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/pushengage-flutter-sdk/example/ios/.gitignore b/pushengage-flutter-sdk/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/pushengage-flutter-sdk/example/ios/Flutter/AppFrameworkInfo.plist b/pushengage-flutter-sdk/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/pushengage-flutter-sdk/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 + 12.0 + + diff --git a/pushengage-flutter-sdk/example/ios/Flutter/Debug.xcconfig b/pushengage-flutter-sdk/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/pushengage-flutter-sdk/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/pushengage-flutter-sdk/example/ios/Flutter/Release.xcconfig b/pushengage-flutter-sdk/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/pushengage-flutter-sdk/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/pushengage-flutter-sdk/example/ios/NotificationContentExtension/Base.lproj/MainInterface.storyboard b/pushengage-flutter-sdk/example/ios/NotificationContentExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000..aaf7b7f --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/NotificationContentExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pushengage-flutter-sdk/example/ios/NotificationContentExtension/ContentView.swift b/pushengage-flutter-sdk/example/ios/NotificationContentExtension/ContentView.swift new file mode 100644 index 0000000..6cb678d --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/NotificationContentExtension/ContentView.swift @@ -0,0 +1,43 @@ +// +// ContentView.swift +// NotificationContentExtension +// +// Created by Himshikhar Gayan on 13/08/24. +// + +import SwiftUI +import PushEngage + +@available(iOSApplicationExtension 13.0, *) +struct ContentView: View { + + var payLoadInfo: CustomUIModel + + var body: some View { + VStack(alignment: .center,spacing: 10) { + HStack(alignment: .center) { + Text("PushEngage Notification") + .foregroundColor(.black) + .bold() + Image("image") + .resizable() + .padding() + .frame(width: 20, height: 20, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) + } + Image(uiImage: payLoadInfo.image ?? UIImage()) + .resizable() + .frame(width: 300, height: 300, + alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) + Text(payLoadInfo.title) + .foregroundColor(.black) + .fontWeight(.medium) + .padding(.all, 10) + Text(payLoadInfo.body) + .foregroundColor(.black) + .fontWeight(.regular) + .padding(.all, 10) + }.background(Color.white) + } + +} + diff --git a/pushengage-flutter-sdk/example/ios/NotificationContentExtension/Info.plist b/pushengage-flutter-sdk/example/ios/NotificationContentExtension/Info.plist new file mode 100644 index 0000000..823d23b --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/NotificationContentExtension/Info.plist @@ -0,0 +1,24 @@ + + + + + NSExtension + + NSExtensionAttributes + + UNNotificationExtensionCategory + + customNotification + + UNNotificationExtensionDefaultContentHidden + YES + UNNotificationExtensionInitialContentSizeRatio + 0.5 + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.usernotifications.content-extension + + + diff --git a/pushengage-flutter-sdk/example/ios/NotificationContentExtension/NotificationViewController.swift b/pushengage-flutter-sdk/example/ios/NotificationContentExtension/NotificationViewController.swift new file mode 100644 index 0000000..43d6479 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/NotificationContentExtension/NotificationViewController.swift @@ -0,0 +1,41 @@ +// +// NotificationViewController.swift +// NotificationContentExtension +// +// Created by Himshikhar Gayan on 13/08/24. +// + +import UIKit +import UserNotifications +import UserNotificationsUI +import PushEngage +import SwiftUI + +class NotificationViewController: UIViewController, UNNotificationContentExtension { + + fileprivate var hostingView: UIHostingController? + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .white + } + + func didReceive(_ notification: UNNotification) { + print(notification.request.content.categoryIdentifier) + if(notification.request.content.categoryIdentifier == "customNotification"){ + let payLoad = PushEngage.getCustomUIPayLoad(for: notification.request) + let view = ContentView(payLoadInfo: payLoad) + hostingView = UIHostingController(rootView: view) + addChild(hostingView!) + hostingView!.view.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(hostingView!.view) + NSLayoutConstraint.activate([hostingView!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), + hostingView!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), + hostingView!.view.topAnchor.constraint(equalTo: self.view.topAnchor), + hostingView!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), + hostingView!.view.centerYAnchor.constraint(equalTo: self.view.centerYAnchor), + hostingView!.view.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)]) + } + } + +} diff --git a/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/Info.plist b/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/Info.plist new file mode 100644 index 0000000..5cf6c23 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/Info.plist @@ -0,0 +1,15 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + PushEngage_App_Group_Key + group.appGroupFlutter.pushengage.com + + diff --git a/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/NotificationService.swift b/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/NotificationService.swift new file mode 100644 index 0000000..f650d58 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/NotificationService.swift @@ -0,0 +1,40 @@ +// +// NotificationService.swift +// NotificationServiceExtension +// +// Created by Himshikhar Gayan on 08/08/24. +// + +import UserNotifications +import PushEngage + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + var request : UNNotificationRequest? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.request = request + self.contentHandler = contentHandler + self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + if let bestContent = bestAttemptContent { + PushEngage.didReceiveNotificationExtensionRequest(request, bestContentHandler: bestContent) + contentHandler(bestContent) + } + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let request = request ,let bestAttemptContent = bestAttemptContent { + guard let content = PushEngage.serviceExtensionTimeWillExpire(request, content: bestAttemptContent) else { + contentHandler(bestAttemptContent) + return + } + contentHandler(content) + } + } + +} diff --git a/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/NotificationServiceExtension.entitlements b/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/NotificationServiceExtension.entitlements new file mode 100644 index 0000000..54f5b73 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/NotificationServiceExtension/NotificationServiceExtension.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + com.apple.security.application-groups + + group.appGroupFlutter.pushengage.com + + + diff --git a/pushengage-flutter-sdk/example/ios/Podfile b/pushengage-flutter-sdk/example/ios/Podfile new file mode 100644 index 0000000..302b8a9 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Podfile @@ -0,0 +1,57 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +# target 'RunnerTests' do +# inherit! :search_paths +# end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'No' + end + end +end + +target 'NotificationServiceExtension' do + use_frameworks! + pod 'PushEngage', '0.0.5' +end + +target 'NotificationContentExtension' do + use_frameworks! + pod 'PushEngage', '0.0.5' +end diff --git a/pushengage-flutter-sdk/example/ios/Podfile.lock b/pushengage-flutter-sdk/example/ios/Podfile.lock new file mode 100644 index 0000000..49cd0aa --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Podfile.lock @@ -0,0 +1,30 @@ +PODS: + - Flutter (1.0.0) + - PushEngage (0.0.5) + - pushengage_flutter_sdk (0.0.1): + - Flutter + - PushEngage (= 0.0.5) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - PushEngage (= 0.0.5) + - pushengage_flutter_sdk (from `.symlinks/plugins/pushengage_flutter_sdk/ios`) + +SPEC REPOS: + trunk: + - PushEngage + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + pushengage_flutter_sdk: + :path: ".symlinks/plugins/pushengage_flutter_sdk/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + PushEngage: b9deba0da0a859f539c9d22299baf38227f31cf1 + pushengage_flutter_sdk: 6f4c8cfee9858407f2d296b45331103e5dd0dfee + +PODFILE CHECKSUM: 7341bb74505d77a4878b94b648a3923b820fe89e + +COCOAPODS: 1.16.2 diff --git a/pushengage-flutter-sdk/example/ios/PushEngageNotificationServiceExtension/Info.plist b/pushengage-flutter-sdk/example/ios/PushEngageNotificationServiceExtension/Info.plist new file mode 100644 index 0000000..57421eb --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/PushEngageNotificationServiceExtension/Info.plist @@ -0,0 +1,13 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.usernotifications.service + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).NotificationService + + + diff --git a/pushengage-flutter-sdk/example/ios/PushEngageNotificationServiceExtension/NotificationService.swift b/pushengage-flutter-sdk/example/ios/PushEngageNotificationServiceExtension/NotificationService.swift new file mode 100644 index 0000000..b0c7871 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/PushEngageNotificationServiceExtension/NotificationService.swift @@ -0,0 +1,35 @@ +// +// NotificationService.swift +// PushEngageNotificationServiceExtension +// +// Created by Himshikhar Gayan on 08/08/24. +// + +import UserNotifications + +class NotificationService: UNNotificationServiceExtension { + + var contentHandler: ((UNNotificationContent) -> Void)? + var bestAttemptContent: UNMutableNotificationContent? + + override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { + self.contentHandler = contentHandler + bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + if let bestAttemptContent = bestAttemptContent { + // Modify the notification content here... + bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" + + contentHandler(bestAttemptContent) + } + } + + override func serviceExtensionTimeWillExpire() { + // Called just before the extension will be terminated by the system. + // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. + if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { + contentHandler(bestAttemptContent) + } + } + +} diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.pbxproj b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..9a4ac56 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,1215 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0BB9543E005AA514F390661A /* Pods_NotificationContentExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB60080E46AF6CCDAF3D9E6D /* Pods_NotificationContentExtension.framework */; }; + 0C3B0D4B89B83AC1B0722DCB /* Pods_NotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE10952DA9475848D799F776 /* Pods_NotificationServiceExtension.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 1C69905DF5F9814C9DC01270 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69A2B6BC2E34924E291ACDFC /* Pods_Runner.framework */; }; + 1D379C072C6B37B8002C6561 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D379C062C6B37B8002C6561 /* UserNotifications.framework */; }; + 1D379C092C6B37B8002C6561 /* UserNotificationsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D379C082C6B37B8002C6561 /* UserNotificationsUI.framework */; }; + 1D379C0C2C6B37B8002C6561 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D379C0B2C6B37B8002C6561 /* NotificationViewController.swift */; }; + 1D379C0F2C6B37B8002C6561 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1D379C0D2C6B37B8002C6561 /* MainInterface.storyboard */; }; + 1D379C132C6B37B8002C6561 /* NotificationContentExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1D379C052C6B37B8002C6561 /* NotificationContentExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 1D379C192C6B3834002C6561 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D379C182C6B3834002C6561 /* ContentView.swift */; }; + 1DBBA8DB2C64A14400CA8234 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBBA8DA2C64A14400CA8234 /* NotificationService.swift */; }; + 1DBBA8DF2C64A14400CA8234 /* NotificationServiceExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1DBBA8D82C64A14400CA8234 /* NotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 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 */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 1D379C112C6B37B8002C6561 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1D379C042C6B37B8002C6561; + remoteInfo = NotificationContentExtension; + }; + 1DBBA8DD2C64A14400CA8234 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1DBBA8D72C64A14400CA8234; + remoteInfo = NotificationServiceExtension; + }; + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 1DBBA8E42C64A14400CA8234 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 1DBBA8DF2C64A14400CA8234 /* NotificationServiceExtension.appex in Embed Foundation Extensions */, + 1D379C132C6B37B8002C6561 /* NotificationContentExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 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 = ""; }; + 1C59E638C6338D0447AA805A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1D379C052C6B37B8002C6561 /* NotificationContentExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationContentExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 1D379C062C6B37B8002C6561 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; + 1D379C082C6B37B8002C6561 /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; }; + 1D379C0B2C6B37B8002C6561 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = ""; }; + 1D379C0E2C6B37B8002C6561 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 1D379C102C6B37B8002C6561 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1D379C182C6B3834002C6561 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 1DBBA8D82C64A14400CA8234 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DBBA8DA2C64A14400CA8234 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 1DBBA8DC2C64A14400CA8234 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1DBBA8E52C64A57200CA8234 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 1DBBA8E62C64E74200CA8234 /* NotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationServiceExtension.entitlements; sourceTree = ""; }; + 2161E9A657690EF8A98BF26F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 44CA8A9BF3602FBDA76234A5 /* Pods-NotificationServiceExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.profile.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.profile.xcconfig"; sourceTree = ""; }; + 46E63AC7911C32971849E344 /* Pods-NotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; + 4BF4280EB399BD9970B7E53E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 69A2B6BC2E34924E291ACDFC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; + 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 /* PushEngageFlutter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PushEngageFlutter.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 = ""; }; + 9D96775613F24392975989D7 /* Pods-NotificationServiceExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.release.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.release.xcconfig"; sourceTree = ""; }; + AB60080E46AF6CCDAF3D9E6D /* Pods_NotificationContentExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationContentExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AC1277D2A740A25BFB105881 /* Pods-NotificationContentExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationContentExtension.debug.xcconfig"; path = "Target Support Files/Pods-NotificationContentExtension/Pods-NotificationContentExtension.debug.xcconfig"; sourceTree = ""; }; + B49C7DE45473B4170A4B0742 /* Pods-NotificationContentExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationContentExtension.release.xcconfig"; path = "Target Support Files/Pods-NotificationContentExtension/Pods-NotificationContentExtension.release.xcconfig"; sourceTree = ""; }; + EE10952DA9475848D799F776 /* Pods_NotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F7736C82E37D04FE7EEB3903 /* Pods-NotificationContentExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationContentExtension.profile.xcconfig"; path = "Target Support Files/Pods-NotificationContentExtension/Pods-NotificationContentExtension.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1D379C022C6B37B8002C6561 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D379C092C6B37B8002C6561 /* UserNotificationsUI.framework in Frameworks */, + 1D379C072C6B37B8002C6561 /* UserNotifications.framework in Frameworks */, + 0BB9543E005AA514F390661A /* Pods_NotificationContentExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DBBA8D52C64A14400CA8234 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0C3B0D4B89B83AC1B0722DCB /* Pods_NotificationServiceExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66D68C305FE7B603C05FD1CF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1C69905DF5F9814C9DC01270 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1D379C0A2C6B37B8002C6561 /* NotificationContentExtension */ = { + isa = PBXGroup; + children = ( + 1D379C0B2C6B37B8002C6561 /* NotificationViewController.swift */, + 1D379C0D2C6B37B8002C6561 /* MainInterface.storyboard */, + 1D379C102C6B37B8002C6561 /* Info.plist */, + 1D379C182C6B3834002C6561 /* ContentView.swift */, + ); + path = NotificationContentExtension; + sourceTree = ""; + }; + 1DBBA8D92C64A14400CA8234 /* NotificationServiceExtension */ = { + isa = PBXGroup; + children = ( + 1DBBA8E62C64E74200CA8234 /* NotificationServiceExtension.entitlements */, + 1DBBA8DA2C64A14400CA8234 /* NotificationService.swift */, + 1DBBA8DC2C64A14400CA8234 /* Info.plist */, + ); + path = NotificationServiceExtension; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + 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 */, + 1DBBA8D92C64A14400CA8234 /* NotificationServiceExtension */, + 1D379C0A2C6B37B8002C6561 /* NotificationContentExtension */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + B65C65B35C2D342D36E2A68B /* Pods */, + C1618696E73441E9E8ECDDBA /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* PushEngageFlutter.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + 1DBBA8D82C64A14400CA8234 /* NotificationServiceExtension.appex */, + 1D379C052C6B37B8002C6561 /* NotificationContentExtension.appex */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 1DBBA8E52C64A57200CA8234 /* Runner.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + B65C65B35C2D342D36E2A68B /* Pods */ = { + isa = PBXGroup; + children = ( + 4BF4280EB399BD9970B7E53E /* Pods-Runner.debug.xcconfig */, + 1C59E638C6338D0447AA805A /* Pods-Runner.release.xcconfig */, + 2161E9A657690EF8A98BF26F /* Pods-Runner.profile.xcconfig */, + 46E63AC7911C32971849E344 /* Pods-NotificationServiceExtension.debug.xcconfig */, + 9D96775613F24392975989D7 /* Pods-NotificationServiceExtension.release.xcconfig */, + 44CA8A9BF3602FBDA76234A5 /* Pods-NotificationServiceExtension.profile.xcconfig */, + AC1277D2A740A25BFB105881 /* Pods-NotificationContentExtension.debug.xcconfig */, + B49C7DE45473B4170A4B0742 /* Pods-NotificationContentExtension.release.xcconfig */, + F7736C82E37D04FE7EEB3903 /* Pods-NotificationContentExtension.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + C1618696E73441E9E8ECDDBA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 69A2B6BC2E34924E291ACDFC /* Pods_Runner.framework */, + EE10952DA9475848D799F776 /* Pods_NotificationServiceExtension.framework */, + 1D379C062C6B37B8002C6561 /* UserNotifications.framework */, + 1D379C082C6B37B8002C6561 /* UserNotificationsUI.framework */, + AB60080E46AF6CCDAF3D9E6D /* Pods_NotificationContentExtension.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D379C042C6B37B8002C6561 /* NotificationContentExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D379C172C6B37B8002C6561 /* Build configuration list for PBXNativeTarget "NotificationContentExtension" */; + buildPhases = ( + B570635B7F4DFA339AF05C49 /* [CP] Check Pods Manifest.lock */, + 1D379C012C6B37B8002C6561 /* Sources */, + 1D379C022C6B37B8002C6561 /* Frameworks */, + 1D379C032C6B37B8002C6561 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NotificationContentExtension; + productName = NotificationContentExtension; + productReference = 1D379C052C6B37B8002C6561 /* NotificationContentExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 1DBBA8D72C64A14400CA8234 /* NotificationServiceExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DBBA8E02C64A14400CA8234 /* Build configuration list for PBXNativeTarget "NotificationServiceExtension" */; + buildPhases = ( + 97789A5B1E0C9F01356077F1 /* [CP] Check Pods Manifest.lock */, + 1DBBA8D42C64A14400CA8234 /* Sources */, + 1DBBA8D52C64A14400CA8234 /* Frameworks */, + 1DBBA8D62C64A14400CA8234 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NotificationServiceExtension; + productName = NotificationServiceExtension; + productReference = 1DBBA8D82C64A14400CA8234 /* NotificationServiceExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 66D68C305FE7B603C05FD1CF /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + B6B172F01BAAC0953EFC50BF /* [CP] Check Pods Manifest.lock */, + 2B2522547327ED14269FA464 /* [CP] Embed Pods Frameworks */, + 1DBBA8E42C64A14400CA8234 /* Embed Foundation Extensions */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + 1DBBA8DE2C64A14400CA8234 /* PBXTargetDependency */, + 1D379C122C6B37B8002C6561 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* PushEngageFlutter.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 1D379C042C6B37B8002C6561 = { + CreatedOnToolsVersion = 15.0; + }; + 1DBBA8D72C64A14400CA8234 = { + CreatedOnToolsVersion = 15.0; + }; + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + 1DBBA8D72C64A14400CA8234 /* NotificationServiceExtension */, + 1D379C042C6B37B8002C6561 /* NotificationContentExtension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1D379C032C6B37B8002C6561 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D379C0F2C6B37B8002C6561 /* MainInterface.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DBBA8D62C64A14400CA8234 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 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 */ + 2B2522547327ED14269FA464 /* [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; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + 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\n"; + }; + 97789A5B1E0C9F01356077F1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NotificationServiceExtension-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; + }; + B570635B7F4DFA339AF05C49 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NotificationContentExtension-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; + }; + B6B172F01BAAC0953EFC50BF /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1D379C012C6B37B8002C6561 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D379C192C6B3834002C6561 /* ContentView.swift in Sources */, + 1D379C0C2C6B37B8002C6561 /* NotificationViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DBBA8D42C64A14400CA8234 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DBBA8DB2C64A14400CA8234 /* NotificationService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 1D379C122C6B37B8002C6561 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1D379C042C6B37B8002C6561 /* NotificationContentExtension */; + targetProxy = 1D379C112C6B37B8002C6561 /* PBXContainerItemProxy */; + }; + 1DBBA8DE2C64A14400CA8234 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1DBBA8D72C64A14400CA8234 /* NotificationServiceExtension */; + targetProxy = 1DBBA8DD2C64A14400CA8234 /* PBXContainerItemProxy */; + }; + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 1D379C0D2C6B37B8002C6561 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 1D379C0E2C6B37B8002C6561 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; + 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 */ + 1D379C142C6B37B8002C6561 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AC1277D2A740A25BFB105881 /* Pods-NotificationContentExtension.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationContentExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationContentExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample.NotificationContentExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1D379C152C6B37B8002C6561 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B49C7DE45473B4170A4B0742 /* Pods-NotificationContentExtension.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationContentExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationContentExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample.NotificationContentExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1D379C162C6B37B8002C6561 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F7736C82E37D04FE7EEB3903 /* Pods-NotificationContentExtension.profile.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationContentExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationContentExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample.NotificationContentExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; + 1DBBA8E12C64A14400CA8234 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 46E63AC7911C32971849E344 /* Pods-NotificationServiceExtension.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationServiceExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample.NotificationServiceExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1DBBA8E22C64A14400CA8234 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9D96775613F24392975989D7 /* Pods-NotificationServiceExtension.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationServiceExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample.NotificationServiceExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 1DBBA8E32C64A14400CA8234 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 44CA8A9BF3602FBDA76234A5 /* Pods-NotificationServiceExtension.profile.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = NotificationServiceExtension/NotificationServiceExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NotificationServiceExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = NotificationServiceExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample.NotificationServiceExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PushEngage; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample; + PRODUCT_NAME = PushEngageFlutter; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pushengageFlutterSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pushengageFlutterSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pushengageFlutterSdkExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = 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 = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PushEngage; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample; + PRODUCT_NAME = PushEngageFlutter; + 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 = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 38PU74KZ48; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = PushEngage; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.0.1; + PRODUCT_BUNDLE_IDENTIFIER = com.pushengage.pushengageFlutterSdkExample; + PRODUCT_NAME = PushEngageFlutter; + 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 */ + 1D379C172C6B37B8002C6561 /* Build configuration list for PBXNativeTarget "NotificationContentExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D379C142C6B37B8002C6561 /* Debug */, + 1D379C152C6B37B8002C6561 /* Release */, + 1D379C162C6B37B8002C6561 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DBBA8E02C64A14400CA8234 /* Build configuration list for PBXNativeTarget "NotificationServiceExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DBBA8E12C64A14400CA8234 /* Debug */, + 1DBBA8E22C64A14400CA8234 /* Release */, + 1DBBA8E32C64A14400CA8234 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationServiceExtension.xcscheme b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationServiceExtension.xcscheme new file mode 100644 index 0000000..ea82b85 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationServiceExtension.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e49b8e4 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner/AppDelegate.swift b/pushengage-flutter-sdk/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..963833d --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,56 @@ +import Flutter +import UIKit +import PushEngage + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + + override init() { + super.init() + PushEngage.swizzleInjection(isEnabled: true) + } + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + if #available(iOSApplicationExtension 10.0, *) { + UNUserNotificationCenter.current().delegate = self + } + GeneratedPluginRegistrant.register(with: self) + PushEngage.setBadgeCount(count: 0) + PushEngage.setNotificationWillShowInForegroundHandler { notification, completion in + if notification.contentAvailable == 1 { + // in case developer failed to set completion handler. After 25 sec handler will call. + completion(nil) + } else { + completion(notification) + } + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + print("HOST didRegisterForRemoteNotificationsWithDeviceToken is implemented device Token: -, \(deviceToken.description)") + let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() + print("Token: \(token)") + super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) +// Uncomment below line if swizzling is not used +// PushEngage.registerDeviceToServer(with: deviceToken) + } + + override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + print("didReceiveRemoteNotification callllllled") +// Uncomment below line if swizzling is not used +// PushEngage.receivedRemoteNotification(application: application, userInfo: userInfo, completionHandler: completionHandler) + } + +// @available(iOSApplicationExtension 10.0, *) + override func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + print("HOST implemented the notification didRecive notification.") +// Uncomment below line if swizzling is not used +// PushEngage.didReceiveRemoteNotification(with: response) + completionHandler() + } + +} diff --git a/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..6dd9ad0 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "pe_icon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/pe_icon.png b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/pe_icon.png new file mode 100644 index 0000000..d01a82b Binary files /dev/null and b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/pe_icon.png differ diff --git a/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/pushengage-flutter-sdk/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/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/pushengage-flutter-sdk/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/pushengage-flutter-sdk/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/pushengage-flutter-sdk/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/pushengage-flutter-sdk/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner/Base.lproj/Main.storyboard b/pushengage-flutter-sdk/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner/Info.plist b/pushengage-flutter-sdk/example/ios/Runner/Info.plist new file mode 100644 index 0000000..f364572 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner/Info.plist @@ -0,0 +1,58 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + PushEngage + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + pushengage_flutter_sdk_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + PushEngageAutoHandleDeeplinkURL + NO + PushEngageInAppEnabled + NO + PushEngage_App_Group_Key + group.appGroupFlutter.pushengage.com + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + fetch + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/pushengage-flutter-sdk/example/ios/Runner/Runner-Bridging-Header.h b/pushengage-flutter-sdk/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/pushengage-flutter-sdk/example/ios/Runner/Runner.entitlements b/pushengage-flutter-sdk/example/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..54f5b73 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/Runner/Runner.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + com.apple.security.application-groups + + group.appGroupFlutter.pushengage.com + + + diff --git a/pushengage-flutter-sdk/example/ios/RunnerTests/RunnerTests.swift b/pushengage-flutter-sdk/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..1779ce1 --- /dev/null +++ b/pushengage-flutter-sdk/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,26 @@ +import Flutter +import UIKit +import XCTest + +@testable import pushengage_flutter_sdk + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + +class RunnerTests: XCTestCase { + + func testGetPlatformVersion() { + let plugin = PushengageFlutterSdkPlugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + +} diff --git a/pushengage-flutter-sdk/example/lib/alert_entry.dart b/pushengage-flutter-sdk/example/lib/alert_entry.dart new file mode 100644 index 0000000..aeec132 --- /dev/null +++ b/pushengage-flutter-sdk/example/lib/alert_entry.dart @@ -0,0 +1,371 @@ +import 'package:flutter/material.dart'; +import 'package:pushengage_flutter_sdk/helper/pushengage_result.dart'; +import 'package:pushengage_flutter_sdk/model/trigger_alert.dart'; +import 'package:pushengage_flutter_sdk/pushengage_flutter_sdk.dart'; + +enum SelectedType { priceDrop, inventory } + +extension SelectedTypeString on SelectedType { + String get value => + this == SelectedType.priceDrop ? 'Price Drop' : 'Inventory'; +} + +class AlertEntryScreen extends StatefulWidget { + const AlertEntryScreen({Key? key}) : super(key: key); + + @override + _AlertEntryScreenState createState() => _AlertEntryScreenState(); +} + +class _AlertEntryScreenState extends State { + SelectedType _selectedType = SelectedType.priceDrop; + DateTime? _selectedDate; + TimeOfDay? _selectedTime; + String _selectedAvailability = 'Nil'; + final TextEditingController _profileIdController = TextEditingController(); + final TextEditingController _mrpController = TextEditingController(); + final TextEditingController _productIdController = TextEditingController(); + final TextEditingController _linkController = TextEditingController(); + final TextEditingController _priceController = TextEditingController(); + final TextEditingController _variantIdController = TextEditingController(); + final TextEditingController _alertPriceController = TextEditingController(); + final TextEditingController _keyController = TextEditingController(); + final TextEditingController _valueController = TextEditingController(); + + final List> _dataList = []; + + String _formatDate(DateTime date) { + const List monthNames = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' + ]; + return '${date.day} ${monthNames[date.month - 1]} ${date.year}'; + } + + DateTime? _getCombinedDateTime() { + if (_selectedDate != null && _selectedTime != null) { + return DateTime( + _selectedDate!.year, + _selectedDate!.month, + _selectedDate!.day, + _selectedTime!.hour, + _selectedTime!.minute, + ); + } + return null; + } + + Future _addAlert() async { + final alert = TriggerAlert( + type: _selectedType == SelectedType.priceDrop + ? TriggerAlertType.priceDrop + : TriggerAlertType.inventory, + productId: _productIdController.text, + link: _linkController.text, + price: double.tryParse(_priceController.text) ?? 0, + variantId: _variantIdController.text.isNotEmpty + ? _variantIdController.text + : null, + expiryTimestamp: _getCombinedDateTime(), + alertPrice: double.tryParse(_alertPriceController.text), + availability: _getAvailability(_selectedAvailability), + profileId: _profileIdController.text.isNotEmpty + ? _profileIdController.text + : null, + mrp: double.tryParse(_mrpController.text), + data: _combineMaps(), + ); + + PushEngageResult result = await PushEngage.addAlert(alert); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(result.isSuccess ? result.data : result.error)), + ); + } + + TriggerAlertAvailabilityType? _getAvailability(String availability) { + return availability == 'Out of Stock' + ? TriggerAlertAvailabilityType.outOfStock + : null; + } + + Map _combineMaps() { + return Map.fromEntries( + _dataList.map((map) => MapEntry(map['key']!, map['value']!))); + } + + void _addData() { + if (_keyController.text.isNotEmpty && _valueController.text.isNotEmpty) { + setState(() { + _dataList.add({ + 'key': _keyController.text, + 'value': _valueController.text, + }); + _keyController.clear(); + _valueController.clear(); + }); + } + } + + void _removeData(int index) { + setState(() { + _dataList.removeAt(index); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Trigger Campaign'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTextField(_profileIdController, 'Enter profile id'), + const SizedBox(height: 10), + _buildTextField(_mrpController, 'MRP', + TextInputType.numberWithOptions(decimal: true)), + const SizedBox(height: 10), + _buildTypeDropdown(), + const SizedBox(height: 10), + _buildTextField(_productIdController, 'Enter product id'), + const SizedBox(height: 10), + _buildTextField(_linkController, 'Enter link'), + const SizedBox(height: 10), + _buildTextField(_priceController, 'Enter price', + TextInputType.numberWithOptions(decimal: true)), + const SizedBox(height: 10), + _buildTextField(_variantIdController, 'Enter variant id'), + const SizedBox(height: 10), + _buildTextField(_alertPriceController, 'Enter alert price', + TextInputType.numberWithOptions(decimal: true)), + const SizedBox(height: 20), + _buildDateTimePickers(), + const SizedBox(height: 20), + _buildAvailabilityDropdown(), + const SizedBox(height: 20), + _buildDataEntry(), + const SizedBox(height: 20), + _buildDataList(), + Center( + child: ElevatedButton( + onPressed: _addAlert, + style: ElevatedButton.styleFrom( + minimumSize: const Size(200, 50), + backgroundColor: const Color.fromARGB(255, 34, 74, 219), + ), + child: const Text('Done', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500)), + ), + ), + ], + ), + ), + ); + } + + Widget _buildTextField(TextEditingController controller, String hint, + [TextInputType? keyboardType]) { + return TextField( + controller: controller, + keyboardType: keyboardType, + decoration: InputDecoration( + hintText: hint, + border: const OutlineInputBorder(), + ), + ); + } + + Widget _buildTypeDropdown() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Select type', style: TextStyle(color: Colors.blue)), + DropdownButton( + isExpanded: true, + value: _selectedType, + onChanged: (SelectedType? newValue) { + if (newValue != null) { + setState(() { + _selectedType = newValue; + }); + } + }, + items: SelectedType.values.map((SelectedType value) { + return DropdownMenuItem( + value: value, + child: Text(value.value), + ); + }).toList(), + ), + ], + ); + } + + Widget _buildDateTimePickers() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Select expiry time'), + Row( + children: [ + Expanded( + child: ElevatedButton( + child: Text(_selectedDate == null + ? 'Select Date' + : _formatDate(_selectedDate!)), + onPressed: () async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _selectedDate ?? DateTime.now(), + firstDate: DateTime(2024), + lastDate: DateTime.now().add(const Duration(days: 365)), + ); + if (picked != null && picked != _selectedDate) { + setState(() { + _selectedDate = picked; + }); + } + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: ElevatedButton( + child: Text(_selectedTime == null + ? 'Select Time' + : _selectedTime!.format(context)), + onPressed: () async { + final TimeOfDay? picked = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (picked != null && picked != _selectedTime) { + setState(() { + _selectedTime = picked; + }); + } + }, + ), + ), + ], + ), + ], + ); + } + + Widget _buildAvailabilityDropdown() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Select Availability'), + DropdownButton( + isExpanded: true, + value: _selectedAvailability, + onChanged: (String? newValue) { + if (newValue != null) { + setState(() { + _selectedAvailability = newValue; + }); + } + }, + items: ['Nil', 'Out of Stock'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + ], + ); + } + + Widget _buildDataEntry() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Enter Data', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + Row( + children: [ + Expanded(child: _buildTextField(_keyController, 'Key')), + const SizedBox(width: 10), + Expanded(child: _buildTextField(_valueController, 'Value')), + const SizedBox(width: 10), + ElevatedButton( + onPressed: _addData, + style: ElevatedButton.styleFrom( + minimumSize: const Size(100, 40), + backgroundColor: const Color.fromARGB(255, 34, 74, 219), + ), + child: const Text('Add', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500)), + ), + ], + ), + ], + ); + } + + Widget _buildDataList() { + return SizedBox( + height: 200, + child: ListView.builder( + itemCount: _dataList.length, + itemBuilder: (context, index) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${_dataList[index]['key']} : ${_dataList[index]['value']}', + style: const TextStyle( + color: Colors.black, + fontSize: 20, + fontWeight: FontWeight.w800), + ), + ), + ), + ElevatedButton( + onPressed: () => _removeData(index), + style: ElevatedButton.styleFrom( + minimumSize: const Size(60, 40), + backgroundColor: const Color.fromARGB(255, 219, 34, 34), + ), + child: const Text('Cancel', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500)), + ), + ], + ); + }, + ), + ); + } +} diff --git a/pushengage-flutter-sdk/example/lib/goal.dart b/pushengage-flutter-sdk/example/lib/goal.dart new file mode 100644 index 0000000..2937be3 --- /dev/null +++ b/pushengage-flutter-sdk/example/lib/goal.dart @@ -0,0 +1,110 @@ +import 'dart:ffi'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:pushengage_flutter_sdk/helper/pushengage_result.dart'; +import 'package:pushengage_flutter_sdk/model/goal.dart'; +import 'package:pushengage_flutter_sdk/pushengage_flutter_sdk.dart'; + +class SendGoalPage extends StatefulWidget { + const SendGoalPage({super.key}); + + @override + _SendGoalPageState createState() => _SendGoalPageState(); +} + +class _SendGoalPageState extends State { + final TextEditingController _goalNameController = TextEditingController(); + final TextEditingController _countController = TextEditingController(); + final TextEditingController _valueController = TextEditingController(); + + Future _sendGoal() async { + final goal = Goal( + name: _goalNameController.text, + count: int.tryParse(_countController.text), + value: double.tryParse(_valueController.text), + ); + PushEngageResult result = await PushEngage.sendGoal(goal); + if (result == null) { + return; + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(result.isSuccess ? result.data : result.error)), + ); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + }, + child: Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBar( + title: Text('Send Goal'), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Enter Goal Name'), + TextField( + controller: _goalNameController, + decoration: const InputDecoration( + hintText: 'enter name', + ), + ), + const SizedBox(height: 16), + const Text('Enter Count'), + TextField( + controller: _countController, + decoration: const InputDecoration( + hintText: 'enter count', + ), + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + ), + const SizedBox(height: 16), + const Text('Enter Value'), + TextField( + controller: _valueController, + decoration: const InputDecoration( + hintText: 'enter value', + ), + keyboardType: TextInputType.number, + ), + const SizedBox(height: 32), + Center( + child: ElevatedButton( + onPressed: () { + _sendGoal(); + FocusScope.of(context).unfocus(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color.fromARGB(255, 34, 74, 219), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 8), // Set the corner radius to 20 + ), + ), + child: const Text( + 'Send Goal', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/pushengage-flutter-sdk/example/lib/home.dart b/pushengage-flutter-sdk/example/lib/home.dart new file mode 100644 index 0000000..50a03cf --- /dev/null +++ b/pushengage-flutter-sdk/example/lib/home.dart @@ -0,0 +1,442 @@ +import 'dart:async'; +import 'dart:ffi'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:pushengage_flutter_sdk/helper/pushengage_result.dart'; +import 'package:pushengage_flutter_sdk/model/DynamicSegment.dart'; +import 'package:pushengage_flutter_sdk/model/trigger_campaign.dart'; +import 'package:pushengage_flutter_sdk/pushengage_flutter_sdk.dart'; +import 'package:pushengage_flutter_sdk_example/goal.dart'; +import 'package:pushengage_flutter_sdk_example/trigger_entry.dart'; +import 'package:pushengage_flutter_sdk_example/trigger_listing.dart'; + +enum PushEngageAction { + addSegment, + removeSegments, + addDynamicSegments, + addSubscriberAttributes, + deleteAttributes, + addProfileId, + getSubscriberDetails, + getSubscriberAttributes, + setSubscriberAttributes, + sendGoal, + triggerCampaigns +} + +extension PushEngageActionString on PushEngageAction { + String get value { + switch (this) { + case PushEngageAction.addSegment: + return "Add Segment"; + case PushEngageAction.removeSegments: + return "Remove Segments"; + case PushEngageAction.addDynamicSegments: + return "Add Dynamic Segments"; + case PushEngageAction.addSubscriberAttributes: + return "Add Subscriber Attributes"; + case PushEngageAction.deleteAttributes: + return "Delete Attributes"; + case PushEngageAction.addProfileId: + return "Add Profile Id"; + case PushEngageAction.getSubscriberDetails: + return "Get Subscriber Details"; + case PushEngageAction.getSubscriberAttributes: + return "Get Subscriber Attributes"; + case PushEngageAction.setSubscriberAttributes: + return "Set Subscriber Attributes"; + case PushEngageAction.sendGoal: + return "Send Goal"; + case PushEngageAction.triggerCampaigns: + return "Trigger Campaigns"; + default: + return ""; + } + } +} + +class Home extends StatefulWidget { + const Home({super.key}); + + @override + _HomeState createState() => _HomeState(); +} + +class _HomeState extends State { + String responseText = ""; // Initial text + final TextEditingController _controller = TextEditingController(); + late StreamSubscription _deepLinkSubscription; + + @override + void initState() { + super.initState(); + PushEngage.enableLogging(true); + PushEngage.deepLinkStream.listen((data) { + _handleDeepLink(data); + }); + } + + void _handleDeepLink(Map? data) { + // Parse the deep link and navigate accordingly + Uri uri = Uri.parse(data?['deepLink']); + print('Path: ${uri.path}'); + updateResponseText(data.toString()); + switch (uri.path) { + case 'trigger': + case '/trigger': + Navigator.push(context, + MaterialPageRoute(builder: (context) => TriggerCampaignEntry())); + break; + } + } + + void updateResponseText(String newText) { + setState(() { + responseText = newText; // Update the text on response + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: const Text( + 'PushEngage', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + backgroundColor: Color.fromARGB(255, 34, 74, 219), + centerTitle: true, + elevation: 2, + ), + body: Column( + mainAxisAlignment: MainAxisAlignment + .spaceBetween, // Aligns the children with space between them + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(20, 10, 20, 0), + child: Container( + alignment: Alignment.centerLeft, + child: const Text( + 'Result', + style: TextStyle( + color: Colors.black, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + )), + Padding( + padding: const EdgeInsets.fromLTRB(20, 10, 20, 10), + child: DecoratedBox( + decoration: const BoxDecoration( + color: Color.fromARGB(255, 243, 239, + 239), // Background color for the text display area + ), + child: Container( + height: 150, // Fixed height for the text display area + padding: const EdgeInsets.all(8.0), + alignment: Alignment + .topLeft, // Aligns the text inside to the top left + child: SingleChildScrollView( + // Makes the text inside scrollable + child: Text( + responseText, + style: const TextStyle( + color: Colors.black, + fontSize: 16), // Adjust text style as needed + ), + ), + )), + ), + Expanded( + child: ListView.builder( + itemCount: PushEngageAction.values.length, // Number of buttons + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 4.0), // Adjust horizontal padding here + child: ElevatedButton( + onPressed: () { + handleButtonClick(PushEngageAction.values[ + index]); // Call the handleButtonClick function with the selected action + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 8), // Set the corner radius to 20 + ), + ), + child: Text( + PushEngageAction.values[index].value, + style: const TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.w500), + )), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(20, 10, 20, 20), + child: ElevatedButton( + onPressed: () { + handleNotificationPermissionRequest(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Color.fromARGB(255, 34, 74, 219), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(8), // Set the corner radius to 20 + ), + ), + child: const Text( + "Request Notification Permission", + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + )), + ) + ], + ), + ); + } + + void handleNotificationPermissionRequest() { + PushEngage.requestNotificationPermission(); + } + + Future showInputDialog() async { + String? inputText = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Enter Values'), + content: TextField( + controller: _controller, + decoration: InputDecoration(hintText: "Comma separated values"), + ), + actions: [ + TextButton( + child: Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(null); + }, + ), + TextButton( + child: Text('Submit'), + onPressed: () { + Navigator.of(context).pop(_controller.text); + }, + ), + ], + ); + }, + ); + return inputText; + } + + Future handleButtonClick(PushEngageAction action) async { + switch (action) { + case PushEngageAction.addSegment: + String? input = await showInputDialog(); + List? segments = + input?.split(',').map((e) => e.trim()).toList(); + if (segments == null) { + return; + } + + final result = await PushEngage.addSegment(segments); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText(result.data?.toString() ?? "Segment added"); + break; + case PushEngageResultStatus.failure: + updateResponseText("Failed to add segment"); + break; + } + break; + case PushEngageAction.removeSegments: + String? input = await showInputDialog(); + List? segments = + input?.split(',').map((e) => e.trim()).toList(); + if (segments == null) { + return; + } + + final result = await PushEngage.removeSegment(segments); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText(result.data?.toString() ?? "Segment removed"); + break; + case PushEngageResultStatus.failure: + updateResponseText("Failed to remove segment"); + break; + } + break; + case PushEngageAction.addDynamicSegments: + String? input = await showInputDialog(); + List segments = []; + List inputList = input?.split(',') ?? []; + for (String item in inputList) { + List keyValue = item.split(':'); + if (keyValue.length == 2) { + String name = keyValue[0].trim(); + int days = int.parse(keyValue[1].trim()); + var segment = DynamicSegment(name: name, duration: days); + segments.add(segment); + } + } + if (segments.isEmpty) { + return; + } + + final result = await PushEngage.addDynamicSegment(segments); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText( + result.data?.toString() ?? "Dynamic Segment added"); + break; + case PushEngageResultStatus.failure: + updateResponseText("Failed to add Dynamic Segment"); + break; + } + break; + case PushEngageAction.addSubscriberAttributes: + String? input = await showInputDialog(); + List inputList = input?.split(',') ?? []; + Map inputMap = {}; + for (String item in inputList) { + List keyValue = item.split(':'); + if (keyValue.length == 2) { + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + inputMap[key] = value; + } + } + + final result = await PushEngage.addSubscriberAttributes(inputMap); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText(result.data?.toString() ?? "Attributes added"); + break; + case PushEngageResultStatus.failure: + updateResponseText("Failed to add attributes"); + break; + } + break; + case PushEngageAction.deleteAttributes: + String? input = await showInputDialog(); + List? attributes = + input?.split(',').map((e) => e.trim()).toList(); + if (attributes == null) { + return; + } + + final result = await PushEngage.deleteSubscriberAttributes(attributes); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText(result.data?.toString() ?? "Attributes deleted"); + break; + case PushEngageResultStatus.failure: + updateResponseText("Failed to delete attributes"); + break; + } + break; + case PushEngageAction.addProfileId: + String? input = await showInputDialog(); + if (input == null) { + return; + } + + final result = await PushEngage.addProfileId(input); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText(result.data?.toString() ?? "Profile ID added"); + break; + case PushEngageResultStatus.failure: + updateResponseText("Failed to add Profile ID"); + break; + } + break; + case PushEngageAction.getSubscriberDetails: + List subscriberAttributes = [ + "city", + "device", + "host", + "user_agent", + "has_unsubscribed", + "device_type", + "timezone", + "country", + "ts_created", + "state", + "profile_id" + ]; + + final result = + await PushEngage.getSubscriberDetails(subscriberAttributes); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText(result.data?.toString() ?? "No details found"); + break; + case PushEngageResultStatus.failure: + updateResponseText("Failed to fetch details"); + break; + } + break; + case PushEngageAction.getSubscriberAttributes: + final result = await PushEngage.getSubscriberAttributes(); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText( + result.data?.toString() ?? "No attributes found"); + break; + case PushEngageResultStatus.failure: + updateResponseText("Failed to fetch attributes"); + break; + } + case PushEngageAction.setSubscriberAttributes: + String? input = await showInputDialog(); + List inputList = input?.split(',') ?? []; + Map inputMap = {}; + for (String item in inputList) { + List keyValue = item.split(':'); + if (keyValue.length == 2) { + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + inputMap[key] = value; + } + } + final result = await PushEngage.setSubscriberAttributes(inputMap); + switch (result.status) { + case PushEngageResultStatus.success: + updateResponseText(result.data?.toString() ?? "Attributes set"); + break; + case PushEngageResultStatus.failure: + updateResponseText(result.error ?? "Failed to set attributes"); + break; + } + break; + case PushEngageAction.sendGoal: + Navigator.push( + context, MaterialPageRoute(builder: (context) => SendGoalPage())); + break; + case PushEngageAction.triggerCampaigns: + Navigator.push(context, + MaterialPageRoute(builder: (context) => TriggerCampaignsPage())); + break; + default: + break; + } + } +} diff --git a/pushengage-flutter-sdk/example/lib/main.dart b/pushengage-flutter-sdk/example/lib/main.dart new file mode 100644 index 0000000..ddc89ec --- /dev/null +++ b/pushengage-flutter-sdk/example/lib/main.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:pushengage_flutter_sdk/model/trigger_campaign.dart'; +import 'package:pushengage_flutter_sdk/pushengage_flutter_sdk.dart'; +import 'package:pushengage_flutter_sdk_example/home.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + super.initState(); + initPlatformState(); + } + + Future initPlatformState() async { + PushEngage.setAppId("42ff42bc-32e5-4188-b65f-d3e5412c5ba9"); + } + + @override + Widget build(BuildContext context) { + return const MaterialApp( + title: 'PushEngage', + debugShowCheckedModeBanner: false, + home: Home(), + ); + } +} diff --git a/pushengage-flutter-sdk/example/lib/trigger_entry.dart b/pushengage-flutter-sdk/example/lib/trigger_entry.dart new file mode 100644 index 0000000..76b238a --- /dev/null +++ b/pushengage-flutter-sdk/example/lib/trigger_entry.dart @@ -0,0 +1,201 @@ +import 'package:flutter/material.dart'; +import 'package:pushengage_flutter_sdk/helper/pushengage_result.dart'; +import 'package:pushengage_flutter_sdk/model/trigger_campaign.dart'; +import 'package:pushengage_flutter_sdk/pushengage_flutter_sdk.dart'; + +class TriggerCampaignEntry extends StatefulWidget { + const TriggerCampaignEntry({Key? key}) : super(key: key); + + @override + _TriggerCampaignEntryState createState() => _TriggerCampaignEntryState(); +} + +class _TriggerCampaignEntryState extends State { + final TextEditingController campaignController = TextEditingController(); + final TextEditingController eventController = TextEditingController(); + final TextEditingController profileController = TextEditingController(); + final TextEditingController referenceController = TextEditingController(); + final TextEditingController keyController = TextEditingController(); + final TextEditingController valueController = TextEditingController(); + + final List> dataList = []; + + void _addData() { + if (keyController.text.isNotEmpty && valueController.text.isNotEmpty) { + setState(() { + dataList.add({ + 'key': keyController.text, + 'value': valueController.text, + }); + keyController.clear(); + valueController.clear(); + }); + } + } + + Map _combineMaps() { + return Map.fromEntries( + dataList.map((map) => MapEntry(map['key']!, map['value']!))); + } + + Future _addTriggerCampaign() async { + final triggerCampaign = TriggerCampaign( + campaignName: campaignController.text, + eventName: eventController.text, + referenceId: + referenceController.text.isEmpty ? null : referenceController.text, + profileId: profileController.text.isEmpty ? null : profileController.text, + data: _combineMaps(), + ); + PushEngageResult result = + await PushEngage.sendTriggerEvent(triggerCampaign); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(result.isSuccess ? result.data : result.error)), + ); + } + + void _removeData(int index) { + setState(() { + dataList.removeAt(index); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Trigger Campaign'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + ), + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTextField(campaignController, 'Enter campaign name'), + const SizedBox(height: 10), + _buildTextField(eventController, 'Enter event name'), + const SizedBox(height: 10), + _buildTextField(profileController, 'Enter profile id'), + const SizedBox(height: 10), + _buildTextField(referenceController, 'Enter reference id'), + const SizedBox(height: 20), + const Text( + 'Enter Data', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + _buildDataEntryRow(), + const SizedBox(height: 20), + _buildDataList(), + const SizedBox(height: 20), + Center( + child: ElevatedButton( + onPressed: _addTriggerCampaign, + style: ElevatedButton.styleFrom( + minimumSize: const Size(200, 50), + backgroundColor: const Color.fromARGB(255, 34, 74, 219), + ), + child: const Text( + 'Done', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildTextField(TextEditingController controller, String label) { + return TextField( + controller: controller, + decoration: InputDecoration( + labelText: label, + border: const OutlineInputBorder(), + ), + ); + } + + Widget _buildDataEntryRow() { + return Row( + children: [ + Expanded( + child: _buildTextField(keyController, 'Key'), + ), + const SizedBox(width: 10), + Expanded( + child: _buildTextField(valueController, 'Value'), + ), + const SizedBox(width: 10), + ElevatedButton( + onPressed: _addData, + style: ElevatedButton.styleFrom( + minimumSize: const Size(100, 40), + backgroundColor: const Color.fromARGB(255, 34, 74, 219), + ), + child: const Text( + 'Add', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ); + } + + Widget _buildDataList() { + return SizedBox( + height: 100, + child: ListView.builder( + itemCount: dataList.length, + itemBuilder: (context, index) { + return Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${dataList[index]['key']} : ${dataList[index]['value']}', + style: const TextStyle( + color: Colors.black, + fontSize: 20, + fontWeight: FontWeight.w800, + ), + ), + ), + ), + ElevatedButton( + onPressed: () => _removeData(index), + style: ElevatedButton.styleFrom( + minimumSize: const Size(60, 40), + backgroundColor: const Color.fromARGB(255, 219, 34, 34), + ), + child: const Text( + 'Cancel', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/pushengage-flutter-sdk/example/lib/trigger_listing.dart b/pushengage-flutter-sdk/example/lib/trigger_listing.dart new file mode 100644 index 0000000..ef92033 --- /dev/null +++ b/pushengage-flutter-sdk/example/lib/trigger_listing.dart @@ -0,0 +1,152 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:pushengage_flutter_sdk/helper/pushengage_result.dart'; +import 'package:pushengage_flutter_sdk/model/goal.dart'; +import 'package:pushengage_flutter_sdk/model/trigger_campaign.dart'; +import 'package:pushengage_flutter_sdk/pushengage_flutter_sdk.dart'; +import 'package:pushengage_flutter_sdk_example/alert_entry.dart'; +import 'package:pushengage_flutter_sdk_example/trigger_entry.dart'; + +class TriggerCampaignsPage extends StatefulWidget { + const TriggerCampaignsPage({super.key}); + + @override + // ignore: library_private_types_in_public_api + _TriggerCampaignsPageState createState() => _TriggerCampaignsPageState(); +} + +class _TriggerCampaignsPageState extends State { + bool _isEnableLoading = false; + bool _isDisableLoading = false; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.of(context).pop(), + ), + title: const Text('Trigger Campaigns'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + ElevatedButton( + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => TriggerCampaignEntry())); + }, + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 50), + backgroundColor: const Color.fromARGB(255, 34, 74, 219), + ), + child: const Text( + 'Send Trigger Event', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => AlertEntryScreen())); + }, + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 50), + backgroundColor: const Color.fromARGB(255, 34, 74, 219)), + child: const Text('Add Alert', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500)), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _isEnableLoading + ? null + : () async { + setState(() => _isEnableLoading = true); + final res = await PushEngage.automatedNotification( + TriggerStatusType.enabled); + switch (res.status) { + case PushEngageResultStatus.success: + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(res.data.toString()))); + break; + case PushEngageResultStatus.failure: + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(res.error.toString()))); + break; + } + + setState(() => _isEnableLoading = false); + }, + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 50), + backgroundColor: const Color.fromARGB(255, 34, 74, 219)), + child: _isEnableLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(color: Colors.white), + ) + : const Text('Enable Automated Notification', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500)), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _isDisableLoading + ? null + : () async { + setState(() => _isDisableLoading = true); + final res = await PushEngage.automatedNotification( + TriggerStatusType.disabled); + switch (res.status) { + case PushEngageResultStatus.success: + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(res.data.toString()))); + break; + case PushEngageResultStatus.failure: + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(res.error.toString()))); + break; + } + + setState(() => _isDisableLoading = false); + }, + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 50), + backgroundColor: const Color.fromARGB(255, 34, 74, 219)), + child: _isDisableLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(color: Colors.white), + ) + : const Text('Disable Automated Notification', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500)), + ), + ], + ), + ), + ); + } +} diff --git a/pushengage-flutter-sdk/example/pubspec.lock b/pushengage-flutter-sdk/example/pubspec.lock new file mode 100644 index 0000000..3fb072c --- /dev/null +++ b/pushengage-flutter-sdk/example/pubspec.lock @@ -0,0 +1,228 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pushengage_flutter_sdk: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" +sdks: + dart: ">=3.4.3 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pushengage-flutter-sdk/example/pubspec.yaml b/pushengage-flutter-sdk/example/pubspec.yaml new file mode 100644 index 0000000..b15ccf4 --- /dev/null +++ b/pushengage-flutter-sdk/example/pubspec.yaml @@ -0,0 +1,84 @@ +name: pushengage_flutter_sdk_example +description: "Demonstrates how to use the pushengage_flutter_sdk plugin." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev +environment: + sdk: ">=3.4.3 <4.0.0" +version: 0.0.1+2 + +# 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 + + pushengage_flutter_sdk: + # When depending on this package from a real application you should use: + # pushengage_flutter_sdk: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.6 + +dev_dependencies: + # integration_test: + # sdk: flutter + 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/pushengage-flutter-sdk/example/test/widget_test.dart b/pushengage-flutter-sdk/example/test/widget_test.dart new file mode 100644 index 0000000..9ba95e4 --- /dev/null +++ b/pushengage-flutter-sdk/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:pushengage_flutter_sdk_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/pushengage-flutter-sdk/ios/.gitignore b/pushengage-flutter-sdk/ios/.gitignore new file mode 100644 index 0000000..034771f --- /dev/null +++ b/pushengage-flutter-sdk/ios/.gitignore @@ -0,0 +1,38 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh diff --git a/pushengage-flutter-sdk/ios/Assets/.gitkeep b/pushengage-flutter-sdk/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pushengage-flutter-sdk/ios/Classes/PushEngageFlutterSdkPlugin.swift b/pushengage-flutter-sdk/ios/Classes/PushEngageFlutterSdkPlugin.swift new file mode 100644 index 0000000..f58a6e6 --- /dev/null +++ b/pushengage-flutter-sdk/ios/Classes/PushEngageFlutterSdkPlugin.swift @@ -0,0 +1,331 @@ +import Flutter +import UIKit +import PushEngage + +public class PushEngageFlutterSdkPlugin: NSObject, + FlutterPlugin, + FlutterApplicationLifeCycleDelegate, UNUserNotificationCenterDelegate { + static var channel: FlutterMethodChannel? + public static func register(with registrar: FlutterPluginRegistrar) { + self.channel = FlutterMethodChannel(name: "PushEngage", binaryMessenger: registrar.messenger()) + let instance = PushEngageFlutterSdkPlugin() + guard let channel = PushEngageFlutterSdkPlugin.channel else { return } + registrar.addMethodCallDelegate(instance, channel: channel) + registrar.addApplicationDelegate(instance) + + } + + public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable : Any] = [:]) -> Bool { + + PushEngage.setInitialInfo(for: application, with: [:]) + + PushEngage.setNotificationOpenHandler { (result) in + let additionalData: [String: String]? = result.notification.additionalData + //Deeplink - trigger + let deeplink = result.notificationAction.actionID + let arguments: [String: Any] = ["deepLink": deeplink as Any, "data": additionalData as Any] + PushEngageFlutterSdkPlugin.channel?.invokeMethod("onDeepLink", arguments: arguments) + } + + return true + } + + //this is very important for background notifications - otherwise subscription happens everytime + public func application( _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) -> Bool + { + completionHandler(.newData) + return true + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "PushEngage#setAppId": + if let args = call.arguments as? [String: Any], + let appId = args["appId"] as? String { + PushEngage.setAppID(id: appId) + result(nil) + } else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + } + + case "PushEngage#getDeviceTokenHash": + result(nil) + + case "PushEngage#subscribe": + result(nil) + + case "PushEngage#enableLogging": + if let status = (call.arguments as? [String: Any])?["status"] as? Bool { + PushEngage.enableLogging = status + result(nil) + } else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + } + case "PushEngage#automatedNotification": + if let status = (call.arguments as? [String: Any])?["status"] as? Bool { + PushEngage.automatedNotification(status: status ? .enabled : .disabled) { response, error in + if response { + result("Automated notification " + (status ? "enabled" : "disabled") + " successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Trigger enabled failed", details: nil)) + } + } + } else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + } + case "PushEngage#sendTriggerEvent": + if let triggerMap = call.arguments as? [String: Any] { + handleSendTriggerEvent(args: triggerMap, result: result) + } else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + } + case "PushEngage#sendGoal": + if let args = call.arguments as? [String: Any] { + self.handleSendGoal(args: args, result: result) + } else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + } + case "PushEngage#addAlert": + if let alertMap = call.arguments as? [String: Any] { + handleAddAlert(args: alertMap, result: result) + } else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + } + case "PushEngage#getSubscriberDetails": + guard let args = call.arguments as? [String: Any], + let values = args["values"] as? [String] else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + return + } + self.getSubscriberDetails(values: values, result: result) + case "PushEngage#requestNotificationPermission": + PushEngage.requestNotificationPermission() + result(nil) + case "PushEngage#getSubscriberAttributes": + PushEngage.getSubscriberAttributes { info, error in + if let info { + result(info) + } else { + result(FlutterError(code: "FAILURE", message: "Failed to retrieve subscriber attributes", details: error?.localizedDescription)) + } + } + case "PushEngage#addSegment": + guard let args = call.arguments as? [String: Any], + let segments = args["segments"] as? [String] else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + return + } + PushEngage.addSegments(segments) { response, error in + if response { + result("Subscriber added to segment(s) successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Failed to add subscriber to segment", details: error?.localizedDescription)) + } + } + case "PushEngage#removeSegment": + guard let args = call.arguments as? [String: Any], + let segments = args["segments"] as? [String] else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + return + } + PushEngage.removeSegments(segments) { response, error in + if response { + result("Subscriber removed from segment(s) successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Failed to remove subscriber from segment(s)", details: error?.localizedDescription)) + } + } + case "PushEngage#addDynamicSegment": + guard let args = call.arguments as? [String: Any], + let segments = args["segments"] as? [[String: Any]] else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + return + } + PushEngage.addDynamicSegments(segments) { response, error in + if response { + result("Subscriber added to dynamic segment successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Failed to add subscriber to dynamic segment(s)", details: error?.localizedDescription)) + } + } + case "PushEngage#addSubscriberAttributes": + guard let args = call.arguments as? [String: Any], + let attributesJsonString = args["attributes"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + return + } + PushEngage.add(attributes: jsonStringToDictionary(attributesJsonString) ?? [:]) { response, error in + if response { + result("Subscriber attribute(s) added successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Failed to add subscriber attribute(s)", details: error?.localizedDescription)) + } + } + case "PushEngage#deleteSubscriberAttributes": + guard let args = call.arguments as? [String: Any], + let attributes = args["attributes"] as? [String] else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + return + } + PushEngage.deleteSubscriberAttributes(for: attributes) { response, error in + if response { + result("Subscriber attribute(s) deleted successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Failed to delete subscriber attribute(s)", details: error?.localizedDescription)) + } + } + case "PushEngage#addProfileId": + guard let args = call.arguments as? [String: Any], + let profileId = args["profileId"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + return + } + + PushEngage.addProfile(for: profileId) { response, error in + if response { + result("Profile Id added successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Failed to add profile Id", details: error?.localizedDescription)) + } + } + case "PushEngage#setSubscriberAttributes": + guard let args = call.arguments as? [String: Any], + let attributesJsonString = args["attributes"] as? String, + let attributesDictionary = self.jsonStringToDictionary(attributesJsonString) else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required arguments", details: nil)) + return + } + + PushEngage.set(attributes: attributesDictionary) { response, error in + if response { + result("Subscriber attribute(s) set successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Failed to set subscriber attribute(s)", details: error?.localizedDescription)) + } + } + default: + result(FlutterMethodNotImplemented) + } + } + + private func jsonStringToDictionary(_ jsonString: String) -> [String: Any]? { + if let data = jsonString.data(using: .utf8) { + do { + if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { + return json + } + } catch { + print("Error converting JSON string to dictionary: \(error.localizedDescription)") + } + } + return nil + } + + private func getSubscriberDetails(values: [String], result: @escaping FlutterResult) { + PushEngage.getSubscriberDetails(for: values) { response, error in + if let value = response { + + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + do { + let jsonData = try encoder.encode(value) + result(String(data: jsonData, encoding: .utf8)) + } catch { + result(FlutterError(code: "FAILURE", message: "Failed decoding subscriber details", details: nil)) + } + } else { + result(FlutterError(code: "FAILURE", message: "Failed retrieving subscriber details", details: error?.localizedDescription)) + } + } + } + + private func handleSendTriggerEvent(args: [String: Any], result: @escaping FlutterResult) { + guard let campaignName = args["campaignName"] as? String, + let eventName = args["eventName"] as? String else { + result(FlutterError(code: "MISSING_ARGUMENTS", message: "Missing required arguments", details: nil)) + return + } + + let referenceId = args["referenceId"] as? String + let profileId = args["profileId"] as? String + let data = args["data"] as? [String: String] + + let trigger = TriggerCampaign(campaignName: campaignName, + eventName: eventName, + referenceId: referenceId, + profileId: profileId, + data: data) + + PushEngage.sendTriggerEvent(triggerCampaign: trigger) { response, error in + if response { + result("Trigger sent successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Trigger sending failed", details: nil)) + } + } + } + + private func handleSendGoal(args: [String: Any], result: @escaping FlutterResult) { + guard let name = args["name"] as? String else { + result(FlutterError(code: "MISSING_ARGUMENTS", message: "Missing required arguments", details: nil)) + return + } + + let count = args["count"] as? Int + let value = args["value"] as? Double + + let goal = Goal(name: name, count: count, value: value) + + PushEngage.sendGoal(goal: goal) { response, error in + if response { + result("Goal sent successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Goal sending failed", details: nil)) + } + } + } + + private func handleAddAlert(args: [String: Any], result: @escaping FlutterResult) { + + guard let typeString = args["type"] as? String, + let productId = args["productId"] as? String, + let link = args["link"] as? String, + let price = args["price"] as? Double else { + return + } + var expiryTimestampDate: Date? + if let expiryTimestamp = args["expiryTimestamp"] as? String { + expiryTimestampDate = ISO8601DateFormatter().date(from: expiryTimestamp) + } + var availability: TriggerAlertAvailabilityType? + if let availabilityString = args["availability"] as? String { + if availabilityString == "inStock" { + availability = .inStock + } else if availabilityString == "outOfStock" { + availability = .outOfStock + } + } + + let triggerAlert = TriggerAlert(type: (typeString == "priceDrop") ? TriggerAlertType.priceDrop : TriggerAlertType.inventory, + productId: productId, + link: link, + price: price, + variantId: args["variantId"] as? String, + expiryTimestamp: expiryTimestampDate, + alertPrice: args["alertPrice"] as? Double, + availability: availability, + profileId: args["profileId"] as? String, + mrp: args["mrp"] as? Double, + data: args["data"] as? [String: String]) + + PushEngage.addAlert(triggerAlert: triggerAlert) { response, error in + if response { + result("Alert added successfully") + } else { + result(FlutterError(code: "FAILURE", message: "Alert sending failed", details: error?.localizedDescription)) + } + } + } +} + diff --git a/pushengage-flutter-sdk/ios/pushengage_flutter_sdk.podspec b/pushengage-flutter-sdk/ios/pushengage_flutter_sdk.podspec new file mode 100644 index 0000000..dde5bcb --- /dev/null +++ b/pushengage-flutter-sdk/ios/pushengage_flutter_sdk.podspec @@ -0,0 +1,22 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint pushengage_flutter_sdk.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'pushengage_flutter_sdk' + s.version = '0.0.1' + s.summary = 'PushEngage Flutter SDK' + s.description = 'Provide the feature for Apple push notification.' + s.homepage = 'http://www.pushengage.com' + s.license = { :file => '../LICENSE' } + s.author = { "PushEngage" => "care@pushengage.com" } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.dependency 'PushEngage', '0.0.5' + s.platform = :ios, '9.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/pushengage-flutter-sdk/lib/helper/logger.dart b/pushengage-flutter-sdk/lib/helper/logger.dart new file mode 100644 index 0000000..aa1fa2c --- /dev/null +++ b/pushengage-flutter-sdk/lib/helper/logger.dart @@ -0,0 +1,20 @@ +class DebugLogger { + static bool _isLoggingEnabled = false; + + // Enable logging + static void enableLogging() { + _isLoggingEnabled = true; + } + + // Disable logging + static void disableLogging() { + _isLoggingEnabled = false; + } + + // Log message if logging is enabled + static void log(String message) { + if (_isLoggingEnabled) { + print(message); + } + } +} diff --git a/pushengage-flutter-sdk/lib/helper/pushengage_result.dart b/pushengage-flutter-sdk/lib/helper/pushengage_result.dart new file mode 100644 index 0000000..0bf8b12 --- /dev/null +++ b/pushengage-flutter-sdk/lib/helper/pushengage_result.dart @@ -0,0 +1,21 @@ +enum PushEngageResultStatus { success, failure } + +class PushEngageResult { + final T? data; + final String? error; + final PushEngageResultStatus status; + + PushEngageResult._({this.data, this.error, required this.status}); + + factory PushEngageResult.success(T data) { + return PushEngageResult._( + data: data, error: null, status: PushEngageResultStatus.success); + } + + factory PushEngageResult.failure(String error) { + return PushEngageResult._( + data: null, error: error, status: PushEngageResultStatus.failure); + } + + bool get isSuccess => status == PushEngageResultStatus.success; +} diff --git a/pushengage-flutter-sdk/lib/interface/mappable.dart b/pushengage-flutter-sdk/lib/interface/mappable.dart new file mode 100644 index 0000000..67a193f --- /dev/null +++ b/pushengage-flutter-sdk/lib/interface/mappable.dart @@ -0,0 +1,3 @@ +abstract class Mappable { + Map toMap(); +} diff --git a/pushengage-flutter-sdk/lib/model/DynamicSegment.dart b/pushengage-flutter-sdk/lib/model/DynamicSegment.dart new file mode 100644 index 0000000..be5ef1e --- /dev/null +++ b/pushengage-flutter-sdk/lib/model/DynamicSegment.dart @@ -0,0 +1,13 @@ +import 'package:pushengage_flutter_sdk/interface/mappable.dart'; + +class DynamicSegment implements Mappable { + final String name; + final int duration; + + DynamicSegment({required this.name, required this.duration}); + + Map toMap() => { + 'name': name, + 'duration': duration, + }; +} diff --git a/pushengage-flutter-sdk/lib/model/goal.dart b/pushengage-flutter-sdk/lib/model/goal.dart new file mode 100644 index 0000000..0ab1142 --- /dev/null +++ b/pushengage-flutter-sdk/lib/model/goal.dart @@ -0,0 +1,15 @@ +class Goal { + String name; + int? count; + double? value; + + Goal({required this.name, this.count, this.value}); + + Map toMap() { + return { + 'name': name, + 'count': count, + 'value': value, + }; + } +} diff --git a/pushengage-flutter-sdk/lib/model/trigger_alert.dart b/pushengage-flutter-sdk/lib/model/trigger_alert.dart new file mode 100644 index 0000000..009e71d --- /dev/null +++ b/pushengage-flutter-sdk/lib/model/trigger_alert.dart @@ -0,0 +1,80 @@ +class TriggerAlert { + final TriggerAlertType type; + final String productId; + final String link; + final double price; + final String? variantId; + final DateTime? expiryTimestamp; + final double? alertPrice; + final TriggerAlertAvailabilityType? availability; + final String? profileId; + final double? mrp; + final Map? data; + + TriggerAlert({ + required this.type, + required this.productId, + required this.link, + required this.price, + this.variantId, + this.expiryTimestamp, + this.alertPrice, + this.availability, + this.profileId, + this.mrp, + this.data, + }); + + Map toMap() { + return { + 'type': type.name, + 'productId': productId, + 'link': link, + 'price': price, + 'variantId': variantId, + 'expiryTimestamp': expiryTimestamp?.toIso8601String(), + 'alertPrice': alertPrice, + 'availability': availability?.name, + 'profileId': profileId, + 'mrp': mrp, + 'data': data, + }; + } +} + +enum TriggerAlertType { + priceDrop, + inventory, +} + +extension TriggerAlertTypeExtension on TriggerAlertType { + String get name { + switch (this) { + case TriggerAlertType.priceDrop: + return 'priceDrop'; + case TriggerAlertType.inventory: + return 'inventory'; + default: + return ''; + } + } +} + +enum TriggerAlertAvailabilityType { + inStock, + outOfStock, +} + +extension TriggerAlertAvailabilityTypeExtension + on TriggerAlertAvailabilityType { + String get name { + switch (this) { + case TriggerAlertAvailabilityType.inStock: + return 'inStock'; + case TriggerAlertAvailabilityType.outOfStock: + return 'outOfStock'; + default: + return ''; + } + } +} diff --git a/pushengage-flutter-sdk/lib/model/trigger_campaign.dart b/pushengage-flutter-sdk/lib/model/trigger_campaign.dart new file mode 100644 index 0000000..0f7ece9 --- /dev/null +++ b/pushengage-flutter-sdk/lib/model/trigger_campaign.dart @@ -0,0 +1,30 @@ +import 'package:pushengage_flutter_sdk/interface/mappable.dart'; + +class TriggerCampaign implements Mappable { + final String campaignName; + final String eventName; + String? referenceId; + String? profileId; + Map? data; + + TriggerCampaign({ + required this.campaignName, + required this.eventName, + this.referenceId, + this.profileId, + this.data, + }); + + @override + Map toMap() { + return { + 'campaignName': campaignName, + 'eventName': eventName, + 'referenceId': referenceId, + 'profileId': profileId, + 'data': data, + }; + } +} + +enum TriggerStatusType { enabled, disabled } diff --git a/pushengage-flutter-sdk/lib/pushengage_flutter_sdk.dart b/pushengage-flutter-sdk/lib/pushengage_flutter_sdk.dart new file mode 100644 index 0000000..6c4d620 --- /dev/null +++ b/pushengage-flutter-sdk/lib/pushengage_flutter_sdk.dart @@ -0,0 +1,442 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:pushengage_flutter_sdk/helper/logger.dart'; +import 'package:pushengage_flutter_sdk/model/DynamicSegment.dart'; +import 'package:pushengage_flutter_sdk/model/goal.dart'; +import 'package:pushengage_flutter_sdk/model/trigger_alert.dart'; +import 'package:pushengage_flutter_sdk/model/trigger_campaign.dart'; +import 'package:pushengage_flutter_sdk/helper/pushengage_result.dart'; + +class PushEngage { + static const MethodChannel _channel = MethodChannel('PushEngage'); + static Stream>? _deepLinkStream; + static const _sdkVersion = "0.0.1"; + + /// A static getter that returns a stream of deep link data. + /// + /// This stream emits a map containing deep link data whenever a deep link is + /// received. The stream is lazily initialized and uses a broadcast stream + /// controller to allow multiple listeners. + /// + /// The stream listens for method calls from the platform channel and emits + /// the deep link data when the 'onDeepLink' method is called along with additional data. + /// + /// Returns: + /// A [Stream] of [Map] containing deep link data. + static Stream?> get deepLinkStream { + if (_deepLinkStream == null) { + final StreamController> controller = + StreamController>.broadcast(); + + _channel.setMethodCallHandler((call) async { + if (call.method == 'onDeepLink') { + final Map arguments = + Map.from(call.arguments); + DebugLogger.log("Data from deeplink: $arguments"); + controller.add(arguments); + } + }); + + _deepLinkStream = controller.stream; + } + return _deepLinkStream!; + } + + /// Sets the application ID for PushEngage. + /// + /// This method sets the application ID. + /// + /// [appId] The application ID to be set. + static Future setAppId(String appId) async { + try { + await _channel.invokeMethod('PushEngage#setAppId', {'appId': appId}); + DebugLogger.log('App Id set successfully'); + } on PlatformException catch (e) { + DebugLogger.log('Failed to set AppId: ${e.message}'); + } + } + + /// Returns the current version of the SDK. + /// + /// This method retrieves the version of the SDK as a string. + /// + /// Returns: + /// A [String] representing the SDK version. + static String getSdkVersion() { + return _sdkVersion; + } + + /// Android only + /// Sets the small icon resource for notifications on Android. + /// + /// This method is only applicable for Android platforms. + /// + /// The [resourceName] parameter should be the name of the drawable resource + /// to be used as the small icon in notifications. + /// + static Future setSmallIconResource(String resourceName) async { + if (Platform.isAndroid) { + try { + await _channel.invokeMethod( + 'PushEngage#setSmallIconResource', {'resourceName': resourceName}); + } on PlatformException catch (e) { + DebugLogger.log('Failed to set small icon resource: ${e.message}'); + } + } + } + + /// Retrieves the device token hash for Android devices. + /// + /// This method invokes a platform-specific method to get the device token hash. + /// + /// Returns a [Future] that completes with a [PushEngageResult] containing the + /// device token hash or an error message. + /// + /// Returns: + /// - A [Future] that completes with the device token hash as a [String], or `null` if + /// an error occurs or the platform is not Android. + static Future> getDeviceTokenHash() async { + if (Platform.isAndroid) { + try { + final deviceTokenHash = + await _channel.invokeMethod('PushEngage#getDeviceTokenHash'); + return PushEngageResult.success(deviceTokenHash); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + return PushEngageResult.failure('Platform is not Android'); + } + + /// Enables or disables logging for the PushEngage SDK. + /// + /// This method allows you to control whether logging is enabled or disabled + /// for debugging purposes. When logging is enabled, debug information will + /// be printed to the console. + /// + /// The method also communicates the logging preference to the native side + /// via a method channel. + /// + /// - Parameter shouldEnable: A boolean value indicating whether logging + /// should be enabled (`true`) or disabled (`false`). + /// + static void enableLogging(bool shouldEnable) { + if (shouldEnable) { + DebugLogger.enableLogging(); + } else { + DebugLogger.disableLogging(); + } + try { + _channel + .invokeMethod('PushEngage#enableLogging', {'status': shouldEnable}); + } catch (e) { + DebugLogger.log('Failed to enable logging: $e'); + } + } + + /// Update trigger campaign status + /// + /// - [status]: The trigger status of type [TriggerStatusType]. If the status + /// is [TriggerStatusType.enabled], the notification will be sent. + /// + /// Returns a [Future] that completes with a [PushEngageResult] containing + /// the result of the notification operation. If the operation is successful, + /// the result will contain a [String] message. If there is an error, the + /// result will contain the error message. + static Future> automatedNotification( + TriggerStatusType status) async { + try { + final String result = await _channel.invokeMethod( + 'PushEngage#automatedNotification', + {'status': status == TriggerStatusType.enabled}); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Sends a trigger event for a specific campaign. + /// + /// Returns a [PushEngageResult] containing a [String] if the operation + /// is successful, or an error message if it fails. + /// + /// - Parameter [trigger]: The [TriggerCampaign] object containing the + /// data for the trigger event. + /// + /// - Returns: A [Future] that completes with a [PushEngageResult] containing + /// either the result of the trigger event or an error message. + static Future> sendTriggerEvent( + TriggerCampaign trigger) async { + try { + final String result = await _channel.invokeMethod( + 'PushEngage#sendTriggerEvent', trigger.toMap()); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Sends a goal event. + /// + /// It returns a [PushEngageResult] containing the result + /// as a string. If an error occurs, it logs the error and returns a + /// [PushEngageResult] containing the error message. + /// + /// - Parameter goal: The [Goal] object to be sent. + /// - Returns: A [Future] that completes with a [PushEngageResult] containing + /// either the result string or an error message. + static Future> sendGoal(Goal goal) async { + try { + final String result = + await _channel.invokeMethod('PushEngage#sendGoal', goal.toMap()); + return PushEngageResult.success(result); + } catch (e) { + DebugLogger.log('Unexpected error: $e'); + return PushEngageResult.failure(e.toString()); + } + } + + /// Adds an alert to be triggered. + /// + /// This method sends a request to add a new alert using the provided + /// [TriggerAlert] object. + /// + /// [alert] - The [TriggerAlert] object representing the alert to be added. + /// + /// Returns a [Future] that completes with a [PushEngageResult] containing + /// either a success or an error message. + static Future> addAlert(TriggerAlert alert) async { + try { + final String result = + await _channel.invokeMethod('PushEngage#addAlert', alert.toMap()); + return PushEngageResult.success(result); + } catch (e) { + DebugLogger.log('Unexpected error: $e'); + return PushEngageResult.failure(e.toString()); + } + } + + /// Retrieves the details of a subscriber based on the provided values. + /// + /// The [values] parameter is a list of strings that specify the details to + /// be retrieved. + /// + /// Returns a [Future] that completes with a [PushEngageResult] containing + /// a map of the subscriber details or an error message. + /// + static Future?>> getSubscriberDetails( + List? values) async { + try { + final String response = await _channel + .invokeMethod('PushEngage#getSubscriberDetails', {'values': values}); + final Map decodedData = + jsonDecode(response) as Map; + return PushEngageResult.success(decodedData); + } catch (e) { + DebugLogger.log('Unexpected error: $e'); + return PushEngageResult.failure(e.toString()); + } + } + + /// Requests notification permission from the user. + /// + /// Returns a [PushEngageResult] containing a boolean value indicating the success + /// of the permission request. + /// + /// If an error occurs during the permission request, it catches the exception + /// and returns a [PushEngageResult] with a value of `false`. + static Future> requestNotificationPermission() async { + try { + if (Platform.isAndroid) { + final bool? isGranted = await _channel + .invokeMethod('PushEngage#requestNotificationPermission'); + return isGranted == true + ? PushEngageResult.success(true) + : PushEngageResult.success(false); + } else { + final _ = await _channel + .invokeMethod('PushEngage#requestNotificationPermission'); + return PushEngageResult.success(true); + } + } catch (e) { + return PushEngageResult.success(false); + } + } + + /// Retrieves the attributes of a subscriber. + /// + /// This method invokes the 'PushEngage#getSubscriberAttributes' method + /// on the platform channel and returns the result. + /// + /// Returns a [PushEngageResult] containing a map of subscriber attributes + /// if the operation is successful, or an error message if it fails. + /// + /// Throws an exception if there is an error during the method invocation. + static Future>> + getSubscriberAttributes() async { + try { + final res = + await _channel.invokeMethod('PushEngage#getSubscriberAttributes'); + if (res is Map) { + return PushEngageResult.success(Map.from(res)); + } else { + return PushEngageResult.failure('Unexpected response'); + } + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Adds subscriber to segments. + /// + /// [segments] A list of segment names to be added. + /// + /// Returns a [PushEngageResult] containing the result of the operation. + /// If successful, the result will contain a [String?] value. If an error + /// occurs, the result will contain the error message. + /// + static Future> addSegment( + List segments) async { + try { + final String? result = await _channel + .invokeMethod('PushEngage#addSegment', {'segments': segments}); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Remove Segments for Subscriber. + /// + /// [segments] A list of segment names to be removed. + /// + /// Returns a [PushEngageResult] containing a [String] which is the result of the operation. + /// If the operation is successful, the result will be a success with the corresponding message. + /// If the operation fails, the result will be a failure with the error message. + static Future> removeSegment( + List segments) async { + try { + final String? result = await _channel + .invokeMethod('PushEngage#removeSegment', {'segments': segments}); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Add subscriber to dynamic segments. + /// + /// Returns a [PushEngageResult] containing a [String] if the operation + /// is successful, or an error message if it fails. + /// + /// - Parameters: + /// - segments: A list of [DynamicSegment] objects to be added. + /// + /// - Returns: A [Future] that completes with a [PushEngageResult] containing + /// a [String] if successful, or an error message if it fails. + static Future> addDynamicSegment( + List segments) async { + try { + List> serializedSegments = + segments.map((segment) => segment.toMap()).toList(); + + final String result = await _channel.invokeMethod( + 'PushEngage#addDynamicSegment', + {'segments': serializedSegments}, + ); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Updates attributes of a subscriber. If an attribute with the specified key already exists, the existing value + /// will be replaced. + /// + /// If the operation is successful, a [PushEngageResult] containing the result + /// string is returned. If an error occurs, a [PushEngageResult] containing + /// the error message is returned. + /// + /// - Parameters: + /// - attributes: A map of attributes to be added for the subscriber. + /// + /// - Returns: A [Future] that resolves to a [PushEngageResult] containing + /// either the result string or an error message. + static Future> addSubscriberAttributes( + Map attributes) async { + try { + String attributesJsonString = jsonEncode(attributes); + + final String result = await _channel.invokeMethod( + 'PushEngage#addSubscriberAttributes', + {'attributes': attributesJsonString}); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Deletes Subscriber Attributes. + /// + /// [attributes] A list of attribute names to be deleted. + /// + /// Returns a [PushEngageResult] containing a [String] message indicating + /// the result of the operation. If the operation is successful, the message + /// will contain the result string. If the operation fails, the message will + /// contain the error string. + /// + static Future> deleteSubscriberAttributes( + List attributes) async { + try { + final String result = await _channel.invokeMethod( + 'PushEngage#deleteSubscriberAttributes', {'attributes': attributes}); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Add a subscriber profile ID. + /// Use this method to associate a subscriber ID (e.g., the username of the subscriber in the host application) with the SDK. + /// [profileId]: The profile ID to be added. + /// + /// Returns a [Future] that completes with a [PushEngageResult] containing + /// either the result or the error message. + static Future> addProfileId( + String profileId) async { + try { + final String result = await _channel + .invokeMethod('PushEngage#addProfileId', {'profileId': profileId}); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } + + /// Sets attributes of a subscriber replacing any previously associated attributes. + /// + /// The [attributes] parameter is a map where the keys are attribute names + /// and the values are the corresponding attribute values. + /// + /// Returns a [PushEngageResult] containing a [String] which indicates + /// the result of the operation. If the operation is successful, the result + /// will be a success message. If there is an error, the result will be a + /// failure message containing the error description. + /// + static Future> setSubscriberAttributes( + Map attributes) async { + try { + String attributesJsonString = jsonEncode(attributes); + + final String result = await _channel.invokeMethod( + 'PushEngage#setSubscriberAttributes', + {'attributes': attributesJsonString}); + return PushEngageResult.success(result); + } catch (e) { + return PushEngageResult.failure(e.toString()); + } + } +} diff --git a/pushengage-flutter-sdk/lib/pushengage_flutter_sdk_method_channel.dart b/pushengage-flutter-sdk/lib/pushengage_flutter_sdk_method_channel.dart new file mode 100644 index 0000000..f551225 --- /dev/null +++ b/pushengage-flutter-sdk/lib/pushengage_flutter_sdk_method_channel.dart @@ -0,0 +1,18 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'pushengage_flutter_sdk_platform_interface.dart'; + +/// An implementation of [PushengageFlutterSdkPlatform] that uses method channels. +class MethodChannelPushengageFlutterSdk extends PushengageFlutterSdkPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('pushengage_flutter_sdk'); + + @override + Future getPlatformVersion() async { + final version = + await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/pushengage-flutter-sdk/lib/pushengage_flutter_sdk_platform_interface.dart b/pushengage-flutter-sdk/lib/pushengage_flutter_sdk_platform_interface.dart new file mode 100644 index 0000000..6dc4c4a --- /dev/null +++ b/pushengage-flutter-sdk/lib/pushengage_flutter_sdk_platform_interface.dart @@ -0,0 +1,30 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'pushengage_flutter_sdk_method_channel.dart'; + +abstract class PushengageFlutterSdkPlatform extends PlatformInterface { + /// Constructs a PushengageFlutterSdkPlatform. + PushengageFlutterSdkPlatform() : super(token: _token); + + static final Object _token = Object(); + + static PushengageFlutterSdkPlatform _instance = + MethodChannelPushengageFlutterSdk(); + + /// The default instance of [PushengageFlutterSdkPlatform] to use. + /// + /// Defaults to [MethodChannelPushengageFlutterSdk]. + static PushengageFlutterSdkPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [PushengageFlutterSdkPlatform] when + /// they register themselves. + static set instance(PushengageFlutterSdkPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/pushengage-flutter-sdk/pubspec.yaml b/pushengage-flutter-sdk/pubspec.yaml new file mode 100644 index 0000000..b44e347 --- /dev/null +++ b/pushengage-flutter-sdk/pubspec.yaml @@ -0,0 +1,29 @@ +name: pushengage_flutter_sdk +description: PushEngage Flutter Plugin allows you to integrate Push Notification service into your Flutter application. + +version: 0.0.1 +homepage: https://github.com/awesomemotive/pushengage-flutter-sdk +repository: https://github.com/awesomemotive/pushengage-flutter-sdk + +environment: + sdk: ">=3.4.3 <4.0.0" + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + plugin: + platforms: + android: + package: com.pushengage.pushengage_flutter_sdk + pluginClass: PushEngageFlutterSdkPlugin + ios: + pluginClass: PushEngageFlutterSdkPlugin diff --git a/pushengage-flutter-sdk/setup.md b/pushengage-flutter-sdk/setup.md new file mode 100644 index 0000000..37ff0a5 --- /dev/null +++ b/pushengage-flutter-sdk/setup.md @@ -0,0 +1,25 @@ +## Setup + +1. Open your Flutter project in a code editor. +2. Add the SDK to `pubspec.yaml`: + ```yaml + dependencies: + pushengage_flutter_sdk: ^0.0.1 +3. Run: + ```bash + flutter pub get + +## Demo project + +We have added a demo project for showcasing the various features available and ways to interact with the SDK. The project can be found inside the ```\example``` folder. + +Steps to run the sample project: +1. Clone this repo. +2. Open it in Visual Studio Code. +3. ```cd``` to ```/example``` -> ```ios``` and do a ```pod install``` (Only required if you want to run it on iOS) +4. ```cd``` to the root of the project +5. Run ```flutter pub get``` +6. Select a simulator for iOS or emulator for Android: ```Command + Shift + P``` -> ```Flutter: Select Device``` +7. Open the Run and Debug Panel from the left and click on Run and Debug. Based on the device selected, it will run the sample app on that device. + +