diff --git a/CHANGELOG.md b/CHANGELOG.md index 251b9d7..3021b8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ ## CHANGE LOG +### Version 4.2.0 *(11 August 2026)* +------------------------------------------- +**What's new** + +* **[Android Platform]** + * Supports [CleverTap Android SDK v8.4.1](https://github.com/CleverTap/clevertap-android-sdk/blob/master/docs/CTCORECHANGELOG.md#version-841-august-7-2026). + * Custom-HTML header and footer in-apps now render when the host Activity is not a `FragmentActivity`. Opt in by adding the `CLEVERTAP_INAPP_FRAGMENTLESS_BANNERS` flag to your app's `AndroidManifest.xml`; default off, so existing integrations are unaffected. + * Improves accessibility across in-app notifications and App Inbox with dynamic text scaling, screen-reader announcements and content descriptions, a larger 48dp dismiss-button tap area, and corrected carousel TalkBack navigation. + +* **[iOS Platform]** + * Supports [CleverTap iOS SDK v7.8.1](https://github.com/CleverTap/clevertap-ios-sdk/blob/master/CHANGELOG.md#version-781-august-7-2026). + * Improves accessibility across in-app notifications and App Inbox with VoiceOver labels, a larger dismiss-button tap area, and better handling of image content descriptions. + * Adds Swift Package Manager (SPM) support alongside the existing CocoaPods integration - consumers can pick either package manager based on their project setup. Raises the minimum iOS deployment target to 13.0; projects below this are auto-promoted by Flutter's SPM tooling with a build log notice. See [Integrate-iOS-SPM.md](https://github.com/CleverTap/clevertap-flutter/blob/master/doc/Integrate-iOS-SPM.md) for setup and migration guidance. + * Adds `UIScene` lifecycle support via a new `sceneWillConnectWithOptions:` method on `CleverTapPlugin`. Apps that use a `SceneDelegate` should call it from `scene(_:willConnectTo:options:)` so `getInitialUrl()` returns the deep link on a cold launch; apps that do not use the `UIScene` lifecycle need no changes. + +* **[Android and iOS Platform]** + * Adds Spin-the-Wheel and Scratch Card gamified templates to Advanced InApp Builder in-app notifications — campaign-driven, no app-side wiring needed. + * Adds per-element click attribution to the in-app `Notification Clicked` event across all in-app templates, plus configurable dismiss gestures (swipe-to-dismiss, and tap-outside on iOS) that are also tracked as `Notification Clicked` events. + +**API changes** +* **[Android and iOS Platform]** + * New API: `dismissPipInApp()` - Dismisses the currently visible Picture-in-Picture (PIP) in-app notification. A no-op when no PIP in-app is visible, and other in-app types are never affected. Dismissing frees the in-app display slot, so pair it with `suspendInAppNotifications()` and `resumeInAppNotifications()` to keep a screen free of all in-apps. + +**Bug Fixes** +* **[Android Platform]** + * Fixes a critical issue where App Inbox messages were never saved on the device on Android 6.0 through Android 10 (API 23 – 29), leaving the inbox permanently empty. Affects every app using App Inbox on `v4.0.0` and `v4.1.0`; messages reappear automatically on the next inbox fetch after updating, with no code change required. + * Fixes an `AbstractMethodError` crash when a Picture-in-Picture in-app notification played a video on Android 6.0 (API 23). Only affects apps whose `minSdkVersion` is below 24; the fix also covers in-app and App Inbox video playback. + +* **[iOS Platform]** + * Fixes `getAppLaunchNotification()` not being implemented on iOS, which caused a `PlatformException` (`MissingPluginException`) when called. + * Fixes an issue where in-apps that were missed while the app was in the background were not shown when the app returned to the foreground. + * Fixes an issue where the `Notification Clicked` event did not include deeplink properties for custom-HTML in-apps. + * Fixes an issue where Advanced InApp Builder full-screen cover in-apps did not respect the device safe area. + * Fixes an issue where client-side in-apps were not persisted when an empty campaign list was received. + * Fixes a potential crash while saving variable diffs, and a potential crash when `syncVariables()` and concurrent variable updates ran at the same time. + +* **[Android and iOS Platform]** + * Fixes an issue where the built-in App Inbox did not repaint the message list after a pull-to-refresh — newly fetched messages only appeared after closing and reopening the inbox. + ### Version 4.1.0 *(9 June 2026)* ------------------------------------------- **What's new** diff --git a/README.md b/README.md index c89d05a..95ea071 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ To get started, sign up [here](https://clevertap.com/live-product-demo/). ```yaml dependencies: - clevertap_plugin: 4.1.0 + clevertap_plugin: 4.2.0 ``` - Run `flutter packages get` to install the SDK diff --git a/android/build.gradle b/android/build.gradle index 569fc8a..427977d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ group = 'com.clevertap.clevertap_plugin' -version = '4.1.0' +version = '4.2.0' buildscript { ext.kotlin_version = "1.8.22" @@ -62,7 +62,7 @@ android { dependencies { implementation 'androidx.core:core-ktx:1.9.0' testImplementation 'junit:junit:4.13.2' - api 'com.clevertap.android:clevertap-android-sdk:8.3.0' + api 'com.clevertap.android:clevertap-android-sdk:8.4.1' compileOnly 'androidx.fragment:fragment:1.3.6' compileOnly 'androidx.core:core:1.9.0' } diff --git a/android/src/main/java/com/clevertap/clevertap_plugin/DartToNativePlatformCommunicator.kt b/android/src/main/java/com/clevertap/clevertap_plugin/DartToNativePlatformCommunicator.kt index 136890b..dc8ea19 100644 --- a/android/src/main/java/com/clevertap/clevertap_plugin/DartToNativePlatformCommunicator.kt +++ b/android/src/main/java/com/clevertap/clevertap_plugin/DartToNativePlatformCommunicator.kt @@ -373,6 +373,10 @@ class DartToNativePlatformCommunicator( resumeInAppNotifications(result) } + "dismissPipInApp" -> { + dismissPipInApp(result) + } + "initializeInbox" -> { initializeInbox(result) } @@ -1536,6 +1540,15 @@ class DartToNativePlatformCommunicator( } } + private fun dismissPipInApp(result: MethodChannel.Result) { + if (cleverTapAPI != null) { + cleverTapAPI.dismissPipInApp() + result.success(null) + } else { + result.error(TAG, ERROR_MSG, null) + } + } + private fun initializeInbox(result: MethodChannel.Result) { if (cleverTapAPI != null) { cleverTapAPI.initializeInbox() diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 7ad3451..7ef6184 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -3,4 +3,8 @@ android.useAndroidX=true #android.enableDexingArtifactTransform.desugaring=false -> this was used before -> check below line android.useFullClasspathForDexingTransform=true android.nonTransitiveRClass=false -android.nonFinalResIds=false \ No newline at end of file +android.nonFinalResIds=false +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/example/ios/.gitignore b/example/ios/.gitignore index e96ef60..61fa3c4 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -22,6 +22,7 @@ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ Flutter/flutter_export_environment.sh +Flutter/ephemeral/ ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index d57061d..ab8e063 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/example/ios/Flutter/ephemeral/flutter_lldbinit b/example/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/example/ios/Flutter/ephemeral/flutter_native_integration.env b/example/ios/Flutter/ephemeral/flutter_native_integration.env new file mode 100644 index 0000000..0828b25 --- /dev/null +++ b/example/ios/Flutter/ephemeral/flutter_native_integration.env @@ -0,0 +1,13 @@ +FLUTTER_ROOT=/Users/runner/hostedtoolcache/flutter/stable-3.44.9-arm64/flutter +FLUTTER_APPLICATION_PATH=/Users/runner/work/clevertap-flutter/clevertap-flutter/wrapper/example +FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/Users/runner/work/clevertap-flutter/clevertap-flutter/wrapper/example/ios/Flutter/ephemeral/Packages/.packages/FlutterFramework +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuNDQuOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NmIxODJkMmM3NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NWEyYTZhNDJjYw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMi4y +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=false +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=/Users/runner/work/clevertap-flutter/clevertap-flutter/wrapper/example/.dart_tool/package_config.json diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index cd9901d..0be0735 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,8 +1,8 @@ PODS: - - CleverTap-iOS-SDK (7.7.1): + - CleverTap-iOS-SDK (7.8.1): - SDWebImage (~> 5.11) - - clevertap_plugin (4.1.0): - - CleverTap-iOS-SDK (= 7.7.1) + - clevertap_plugin (4.2.0): + - CleverTap-iOS-SDK (= 7.8.1) - Flutter - Firebase/CoreOnly (10.25.0): - FirebaseCore (= 10.25.0) @@ -71,7 +71,7 @@ PODS: - nanopb/encode (= 2.30910.0) - nanopb/decode (2.30910.0) - nanopb/encode (2.30910.0) - - PromisesObjC (2.4.0) + - PromisesObjC (2.4.1) - SDWebImage (5.21.7): - SDWebImage/Core (= 5.21.7) - SDWebImage/Core (5.21.7) @@ -107,8 +107,8 @@ EXTERNAL SOURCES: :path: Flutter SPEC CHECKSUMS: - CleverTap-iOS-SDK: c80354ca6bca086bd9ea242c0ad15e859b867ea5 - clevertap_plugin: 967312b3841bf78738a922ebe1becbe6085e8029 + CleverTap-iOS-SDK: 5cdc6452d832182516340f76444f757962b75db8 + clevertap_plugin: 4b1cd9391438bcb537ba4020c3cc7c16271f694b Firebase: 0312a2352584f782ea56f66d91606891d4607f06 firebase_core: a626d00494efa398e7c54f25f1454a64c8abf197 firebase_messaging: 06391e8f35dc65a00c56580266285263d2861f10 @@ -120,9 +120,9 @@ SPEC CHECKSUMS: GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 nanopb: 438bc412db1928dac798aa6fd75726007be04262 - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273 SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf PODFILE CHECKSUM: 291c6e37be125d1f160f3b46b95592d2c695ff17 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/lib/main.dart b/example/lib/main.dart index e49fb2e..714ab33 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -766,6 +766,10 @@ class _MyAppState extends State { "Resume InApp notifications", resumeInAppNotifications, "Resumes display of InApp Notifications."), + _buildListTile( + "Dismiss PIP InApp", + dismissPipInApp, + "Dismisses the currently visible Picture-in-Picture InApp notification, if any."), ]), if (!kIsWeb) _buildExpansionTile("Event History", [ @@ -1793,6 +1797,12 @@ class _MyAppState extends State { showToast("InApp notification is resumed"); } + void dismissPipInApp() { + CleverTapPlugin.dismissPipInApp(); + showToast("PIP InApp notification dismissed (if visible)"); + print("InApp -> dismissPipInApp called"); + } + void enablePersonalization() { CleverTapPlugin.enablePersonalization(); showToast("Personalization enabled"); diff --git a/ios/Classes/CleverTapPlugin.m b/ios/Classes/CleverTapPlugin.m index 5daa851..9d095f5 100644 --- a/ios/Classes/CleverTapPlugin.m +++ b/ios/Classes/CleverTapPlugin.m @@ -167,6 +167,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { [self discardInAppNotifications:call withResult:result]; else if ([@"resumeInAppNotifications" isEqualToString:call.method]) [self resumeInAppNotifications]; + else if ([@"dismissPipInApp" isEqualToString:call.method]) + [self dismissPipInApp:call withResult:result]; else if ([@"fetchInbox" isEqualToString:call.method]) [self fetchInbox:call withResult:result]; else if ([@"fetchInboxWithCallback" isEqualToString:call.method]) @@ -671,10 +673,15 @@ - (void)discardInAppNotifications:(FlutterMethodCall *)call withResult:(FlutterR } - (void)resumeInAppNotifications { - + [[CleverTap sharedInstance] resumeInAppNotifications]; } +- (void)dismissPipInApp:(FlutterMethodCall *)call withResult:(FlutterResult)result { + [[CleverTap sharedInstance] dismissPipInApp]; + result(nil); +} + #pragma mark - InApp Controls - (void)fetchInApps:(FlutterMethodCall *)call withResult:(FlutterResult)result { diff --git a/ios/clevertap_plugin.podspec b/ios/clevertap_plugin.podspec index 7777a58..e3babd4 100644 --- a/ios/clevertap_plugin.podspec +++ b/ios/clevertap_plugin.podspec @@ -3,7 +3,7 @@ # Pod::Spec.new do |s| s.name = 'clevertap_plugin' - s.version = '4.1.0' + s.version = '4.2.0' s.summary = 'CleverTap Flutter plugin.' s.description = 'The CleverTap iOS SDK for App Analytics and Engagement.' s.homepage = 'https://github.com/CleverTap/clevertap-ios-sdk' @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.dependency 'CleverTap-iOS-SDK', '7.7.1' + s.dependency 'CleverTap-iOS-SDK', '7.8.1' s.ios.deployment_target = '9.0' end diff --git a/lib/clevertap_plugin.dart b/lib/clevertap_plugin.dart index 575b008..fb7d345 100644 --- a/lib/clevertap_plugin.dart +++ b/lib/clevertap_plugin.dart @@ -80,7 +80,7 @@ class CleverTapPlugin { static const libName = 'Flutter'; static const libVersion = - 40100; // If the current version is X.X.X then pass as X0X0X + 40200; // If the current version is X.X.X then pass as X0X0X CleverTapPlugin._internal() { /// Set the CleverTap Flutter library name and the current version for version tracking @@ -1043,6 +1043,18 @@ class CleverTapPlugin { .invokeMethod('resumeInAppNotifications', {}); } + /// Dismisses the currently visible Picture-in-Picture (PIP) InApp Notification, if any. + /// + /// This is a no-op when no PIP InApp is visible. Other InApp Notification types are + /// never affected. Safe to call from any thread. + /// + /// Note: dismissing releases the InApp display slot, so the next queued InApp (if any) + /// may show immediately. To keep a screen free of all InApps, pair this with + /// suspendInAppNotifications() on screen entry and resumeInAppNotifications() on exit. + static Future dismissPipInApp() async { + return await _dartToNativeMethodChannel.invokeMethod('dismissPipInApp', {}); + } + /// Resumes event tracking, overriding any active mute period. static Future unmute() async { return await _dartToNativeMethodChannel.invokeMethod('unmute', {}); diff --git a/pubspec.yaml b/pubspec.yaml index 448f14b..4779bb5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: clevertap_plugin description: The CleverTap Flutter SDK for Mobile Customer Engagement,Analytics and Retention solutions. -version: 4.1.0 +version: 4.2.0 homepage: https://github.com/CleverTap/clevertap-flutter environment: