From 019f4fd1472c46d0c4ebf030d6e4fc984882c0fc Mon Sep 17 00:00:00 2001 From: "bruno.silva" Date: Tue, 2 Jun 2026 09:47:25 +0100 Subject: [PATCH 1/2] Release: 2.27.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ android/build.gradle | 2 +- example/ios/Podfile.lock | 16 ++++++++-------- example/pubspec.lock | 10 +++++----- ios/usercentrics_sdk.podspec | 2 +- pubspec.lock | 16 ++++++++-------- pubspec.yaml | 2 +- 7 files changed, 44 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f6e939..d83abfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,24 @@ [Release Notes](https://docs.usercentrics.com/cmp_in_app_sdk/latest/about/history/) +### 2.27.0 – Jun 1, 2026 +## Features +* **[PUR Compliance]** Implemented Mandatory Label and Hide Vendor Toggles on the second layer — purposes and special features absent from `publisherRestrictions`/`specialFeatures` maps are rendered as mandatory (pre-consented, non-interactive with a "Mandatory" label), and `showTogglesForVendors: false` hides toggles on the Vendors/DPS tab while keeping them on the Purposes tab. All logic is gated behind `enableConsentOrPay`; existing configurations are fully unaffected +* **[PUR Compliance]** Extended mandatory label and hide vendor toggles behaviour to Flutter, React Native, and Unity bridges +* **[Unity]** Exposed GDPR legal texts in `getCmpData`, enabling fully custom TCF UI builds + +## Fixes +* **[Android — Accessibility]** Fixed TalkBack reading consent layer content with wrong language pronunciation (WCAG 3.1.2 Language of Parts) +* **[Android — Accessibility]** Added visible focus indicators to interactive elements in the consent layer (WCAG 2.4.7 Focus Visible) +* **[Android — Accessibility]** Action buttons now correctly announced as buttons by TalkBack (WCAG 4.1.2 Name, Role, Value) +* **[Android — Accessibility]** Accordion controls now announced as interactive buttons by TalkBack (WCAG 4.1.2 Name, Role, Value) +* **[Android — Accessibility]** External links in the consent layer now announced as links by TalkBack (WCAG 4.1.2 Name, Role, Value) +* **[Android — Accessibility]** Fixed TalkBack focus jumping unexpectedly after accordion expand/collapse (WCAG 3.2.1 On Focus) +* **[Android — Accessibility]** Consent history table now semantically exposed to screen readers (WCAG 1.3.1 Info and Relationships) +* **[Android — Accessibility]** History section heading now programmatically marked as a heading for TalkBack navigation (WCAG 1.3.1 Info and Relationships) +* **[Android]** Fixed button text losing center alignment when label wraps to multiple lines +* **[Android]** Fixed first layer being blocked on the preview page when consent was already stored +* **[iOS]** Fixed SPM UI package incompatibility with Xcode 26 +* **[TV]** Fixed CMP initialization failing on Samsung TV 2018 with `AbortController is not defined` +* **[Unity]** Added `changeLanguage()` API to the Unity bridge ### 2.26.3 – May 14, 2026 ## Fixes diff --git a/android/build.gradle b/android/build.gradle index f0d9140..a0d5886 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,4 +1,4 @@ -def usercentrics_version = "2.26.3" +def usercentrics_version = "2.27.0" group 'com.usercentrics.sdk.flutter' version usercentrics_version diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 05d66b8..a5a056b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -88,12 +88,12 @@ PODS: - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) - PromisesObjC (2.4.0) - - Usercentrics (2.26.3) - - usercentrics_sdk (2.26.3): + - Usercentrics (2.27.0) + - usercentrics_sdk (2.27.0): - Flutter - - UsercentricsUI (= 2.26.3) - - UsercentricsUI (2.26.3): - - Usercentrics (= 2.26.3) + - UsercentricsUI (= 2.27.0) + - UsercentricsUI (2.27.0): + - Usercentrics (= 2.27.0) - webview_flutter_wkwebview (0.0.1): - Flutter - FlutterMacOS @@ -137,9 +137,9 @@ SPEC CHECKSUMS: GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - Usercentrics: 82516ec0af3d552dced169285731571b71d74fc2 - usercentrics_sdk: c4758f2cc6eca0f410990a92e3b846adb9e5da48 - UsercentricsUI: 085bb51480cdd35d1b8c0f4661c72fa728a163ba + Usercentrics: e980f9ab4cf1d1c6de5825d7c23ca8ada2d6f0a5 + usercentrics_sdk: 8c1ae61a079ede9cbe1849cf575ac97500aef364 + UsercentricsUI: 3297ec6fb0e2cbb781abf4e61377979099007982 webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 PODFILE CHECKSUM: e97bfdbafeca8809c8aa9477153c465633bd1c02 diff --git a/example/pubspec.lock b/example/pubspec.lock index 25c147b..1dac9e0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -111,10 +111,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.18" material_color_utilities: dependency: transitive description: @@ -196,17 +196,17 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.9" usercentrics_sdk: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.26.2" + version: "2.27.0" vector_math: dependency: transitive description: diff --git a/ios/usercentrics_sdk.podspec b/ios/usercentrics_sdk.podspec index eff5b9e..4ee151f 100644 --- a/ios/usercentrics_sdk.podspec +++ b/ios/usercentrics_sdk.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'usercentrics_sdk' - s.version = '2.26.3' + s.version = '2.27.0' s.summary = 'Usercentrics Flutter SDK.' s.description = <<-DESC Usercentrics Flutter SDK. diff --git a/pubspec.lock b/pubspec.lock index cee705f..fa4433a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -215,10 +215,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.18" material_color_utilities: dependency: transitive description: @@ -380,26 +380,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7" + sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a" url: "https://pub.dev" source: hosted - version: "1.30.0" + version: "1.29.0" test_api: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.9" test_core: dependency: transitive description: name: test_core - sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51" + sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943" url: "https://pub.dev" source: hosted - version: "0.6.16" + version: "0.6.15" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ea7c2f1..dd21ad6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,7 @@ repository: https://github.com/Usercentrics/flutter-sdk/ # [X] android/build.gradle # [X] ios/usercentrics_sdk.podspec + pod install/update # [X] CHANGELOG.md -version: 2.26.3 +version: 2.27.0 environment: sdk: ">=2.17.1 <4.0.0" From 895efc58ab10a8fb68d874949127d5c868041d17 Mon Sep 17 00:00:00 2001 From: uc-brunosilva Date: Tue, 2 Jun 2026 10:17:58 +0100 Subject: [PATCH 2/2] [MSDK-3955] Expose mandatoryLabel in TCF2Settings Flutter bridge for PUR compliance (#174) Co-authored-by: Claude Sonnet 4.6 --- .../sdk/flutter/serializer/CMPDataSerializer.kt | 3 ++- ios/Classes/Serializer/CMPDataSerializer.swift | 3 ++- .../internal/serializer/tcf2_settings_serializer.dart | 3 ++- lib/src/model/tcf2_settings.dart | 10 +++++++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/android/src/main/kotlin/com/usercentrics/sdk/flutter/serializer/CMPDataSerializer.kt b/android/src/main/kotlin/com/usercentrics/sdk/flutter/serializer/CMPDataSerializer.kt index 4266e25..75ccb32 100644 --- a/android/src/main/kotlin/com/usercentrics/sdk/flutter/serializer/CMPDataSerializer.kt +++ b/android/src/main/kotlin/com/usercentrics/sdk/flutter/serializer/CMPDataSerializer.kt @@ -227,7 +227,8 @@ private fun TCF2Settings.serialize(): Any { "changedPurposes" to changedPurposes?.serialize(), "acmV2Enabled" to acmV2Enabled, "selectedATPIds" to selectedATPIds, - "consentOrPay" to consentOrPay?.serialize() + "consentOrPay" to consentOrPay?.serialize(), + "mandatoryLabel" to mandatoryLabel ) } diff --git a/ios/Classes/Serializer/CMPDataSerializer.swift b/ios/Classes/Serializer/CMPDataSerializer.swift index 7219691..e8d8f48 100644 --- a/ios/Classes/Serializer/CMPDataSerializer.swift +++ b/ios/Classes/Serializer/CMPDataSerializer.swift @@ -217,7 +217,8 @@ extension TCF2Settings { "changedPurposes": self.changedPurposes?.serialize() as Any, "acmV2Enabled" : self.acmV2Enabled, "selectedATPIds" : self.selectedATPIds, - "consentOrPay": self.consentOrPay?.serialize() as Any + "consentOrPay": self.consentOrPay?.serialize() as Any, + "mandatoryLabel": self.mandatoryLabel ] } } diff --git a/lib/src/internal/serializer/tcf2_settings_serializer.dart b/lib/src/internal/serializer/tcf2_settings_serializer.dart index f43b048..59a2c76 100644 --- a/lib/src/internal/serializer/tcf2_settings_serializer.dart +++ b/lib/src/internal/serializer/tcf2_settings_serializer.dart @@ -70,7 +70,8 @@ class TCF2SettingsSerializer { acmV2Enabled: value['acmV2Enabled'] ?? false, selectedATPIds: value['selectedATPIds']?.cast() ?? [], consentOrPay: TCF2ConsentOrPaySettingsSerializer.deserialize( - value['consentOrPay'])); + value['consentOrPay']), + mandatoryLabel: value['mandatoryLabel'] ?? "Mandatory"); } } diff --git a/lib/src/model/tcf2_settings.dart b/lib/src/model/tcf2_settings.dart index 0c84616..9346e63 100644 --- a/lib/src/model/tcf2_settings.dart +++ b/lib/src/model/tcf2_settings.dart @@ -61,7 +61,8 @@ class TCF2Settings { required this.changedPurposes, required this.acmV2Enabled, required this.selectedATPIds, - this.consentOrPay}); + this.consentOrPay, + this.mandatoryLabel = "Mandatory"}); final String firstLayerTitle; final String secondLayerTitle; @@ -123,6 +124,7 @@ class TCF2Settings { final bool acmV2Enabled; final List selectedATPIds; final TCF2ConsentOrPaySettings? consentOrPay; + final String mandatoryLabel; @override bool operator ==(Object other) => @@ -192,7 +194,8 @@ class TCF2Settings { changedPurposes == other.changedPurposes && acmV2Enabled == other.acmV2Enabled && listEquals(selectedATPIds, other.selectedATPIds) && - consentOrPay == other.consentOrPay; + consentOrPay == other.consentOrPay && + mandatoryLabel == other.mandatoryLabel; @override int get hashCode => @@ -255,7 +258,8 @@ class TCF2Settings { changedPurposes.hashCode + acmV2Enabled.hashCode + selectedATPIds.hashCode + - consentOrPay.hashCode; + consentOrPay.hashCode + + mandatoryLabel.hashCode; @override String toString() => "$TCF2Settings($hashCode)";