diff --git a/.changeset/fresh-apples-knock.md b/.changeset/fresh-apples-knock.md new file mode 100644 index 00000000..77f658b4 --- /dev/null +++ b/.changeset/fresh-apples-knock.md @@ -0,0 +1,5 @@ +--- +"posthog_flutter": minor +--- + +**Breaking:** Change `PostHogSessionReplayConfig.screenshotScale` from `double` to `double?` with a `null` default, so native screens recorded with `captureNativeScreens` on Android pick up posthog-android's one-pixel-per-dp default instead of physical resolution. Flutter screens still capture at one pixel per logical pixel. Set `screenshotScale: 1.0` to keep native screens at physical resolution. Code that reads the value into a `double` needs a null check. diff --git a/api/posthog_flutter.api.json b/api/posthog_flutter.api.json index 5c5fd73a..5ed23fdc 100644 --- a/api/posthog_flutter.api.json +++ b/api/posthog_flutter.api.json @@ -3048,7 +3048,7 @@ "isWriteable": true, "name": "screenshotScale", "relativePath": "lib/src/posthog_config.dart", - "typeName": "double" + "typeName": "double?" }, { "entryPoints": [], diff --git a/posthog_flutter/android/build.gradle b/posthog_flutter/android/build.gradle index fbff207c..29e462b5 100644 --- a/posthog_flutter/android/build.gradle +++ b/posthog_flutter/android/build.gradle @@ -67,7 +67,7 @@ android { testImplementation 'org.robolectric:robolectric:4.14.1' testImplementation 'junit:junit:4.13.2' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.10.1' - implementation 'com.posthog:posthog-android:[3.72.9,4.0.0)' + implementation 'com.posthog:posthog-android:[3.74.0,4.0.0)' } testOptions { diff --git a/posthog_flutter/android/src/test/kotlin/com/posthog/flutter/PosthogFlutterPluginTest.kt b/posthog_flutter/android/src/test/kotlin/com/posthog/flutter/PosthogFlutterPluginTest.kt index fc526752..7a56bac9 100644 --- a/posthog_flutter/android/src/test/kotlin/com/posthog/flutter/PosthogFlutterPluginTest.kt +++ b/posthog_flutter/android/src/test/kotlin/com/posthog/flutter/PosthogFlutterPluginTest.kt @@ -267,7 +267,7 @@ internal class PosthogFlutterPluginTest { ) val replay = assertNotNull(plugin.lastBuiltConfig).sessionReplayConfig - assertEquals(1f, replay.screenshotScale) + assertNull(replay.screenshotScale) assertEquals(30, replay.screenshotCompressionQuality) assertEquals(PostHogScreenshotColorMode.ARGB_8888, replay.screenshotColorMode) } finally { diff --git a/posthog_flutter/lib/src/posthog_config.dart b/posthog_flutter/lib/src/posthog_config.dart index a62f0afa..10e9a90e 100644 --- a/posthog_flutter/lib/src/posthog_config.dart +++ b/posthog_flutter/lib/src/posthog_config.dart @@ -925,20 +925,24 @@ class PostHogSessionReplayConfig { /// Multiplier for the width and height of Android replay screenshots. /// - /// Flutter screenshots scale from their logical-resolution baseline; native - /// screens recorded with [captureNativeScreens] scale from physical resolution. - /// Defaults to `1.0`. Values are clamped to `0.1`–`1.0`; NaN and infinity use - /// `1.0`. Scaled dimensions round up to at least one pixel without changing the - /// logical replay viewport. Masks round outward to cover output pixels; frames - /// with mask bounds that cannot be mapped safely are skipped. + /// Defaults to `null`: Flutter screenshots capture at one pixel per logical + /// pixel and native screens recorded with [captureNativeScreens] use the + /// Android SDK default of one pixel per dp. An explicit value multiplies the + /// logical size of Flutter screenshots and the physical size of native + /// screens, so `1.0` captures native screens at physical resolution. Values + /// are clamped to `0.1`–`1.0`; NaN and infinity reset to `null`. Scaled + /// dimensions round up to at least one pixel without changing the logical + /// replay viewport. Masks round outward to cover output pixels; frames with + /// mask bounds that cannot be mapped safely are skipped. /// Set before `Posthog().setup(config)`. @experimental - double get screenshotScale => _screenshotScale; - double _screenshotScale = 1.0; + double? get screenshotScale => _screenshotScale; + double? _screenshotScale; @experimental - set screenshotScale(double value) { - _screenshotScale = value.isFinite ? value.clamp(0.1, 1.0) : 1.0; + set screenshotScale(double? value) { + _screenshotScale = + value != null && value.isFinite ? value.clamp(0.1, 1.0) : null; } /// Compression quality for Android replay screenshots. @@ -1056,7 +1060,7 @@ class PostHogSessionReplayConfig { 'maskAllPlatformViews': maskAllPlatformViews, 'captureNativeScreens': captureNativeScreens, 'verifyScreenshotMaskAlignment': verifyScreenshotMaskAlignment, - 'screenshotScale': screenshotScale, + if (screenshotScale != null) 'screenshotScale': screenshotScale, 'screenshotCompressionQuality': screenshotCompressionQuality, 'screenshotColorMode': screenshotColorMode.name, if (sampleRate != null) 'sampleRate': sampleRate, diff --git a/posthog_flutter/lib/src/replay/screenshot/screenshot_capturer.dart b/posthog_flutter/lib/src/replay/screenshot/screenshot_capturer.dart index 17a2b1c3..b74769a7 100644 --- a/posthog_flutter/lib/src/replay/screenshot/screenshot_capturer.dart +++ b/posthog_flutter/lib/src/replay/screenshot/screenshot_capturer.dart @@ -246,7 +246,7 @@ class ScreenshotCapturer { } double get _screenshotScale => defaultTargetPlatform == TargetPlatform.android - ? effectiveConfig.sessionReplayConfig.screenshotScale + ? effectiveConfig.sessionReplayConfig.screenshotScale ?? 1.0 : 1.0; int _imageDimension(double dimension, double scale) => scale == 1.0 diff --git a/posthog_flutter/test/replay_screenshot_capture_test.dart b/posthog_flutter/test/replay_screenshot_capture_test.dart index e8a3dabb..3ed0e2bf 100644 --- a/posthog_flutter/test/replay_screenshot_capture_test.dart +++ b/posthog_flutter/test/replay_screenshot_capture_test.dart @@ -68,7 +68,7 @@ void main() { ), ); - Future mount(WidgetTester tester, double scale, + Future mount(WidgetTester tester, double? scale, {Size size = const Size(101, 99), bool waitForSnapshot = true}) async { final config = PostHogConfig('test_project_token') ..sessionReplay = true @@ -103,10 +103,11 @@ void main() { await tester.pump(const Duration(milliseconds: 1)); } - for (final scale in [1.0, 0.5, 0.333, 0.1]) { - testWidgets('Android scale $scale preserves geometry and masks', + for (final configured in [null, 1.0, 0.5, 0.333, 0.1]) { + final scale = configured ?? 1.0; + testWidgets('Android scale $configured preserves geometry and masks', (tester) async { - final config = await mount(tester, scale); + final config = await mount(tester, configured); final full = calls.firstWhere((call) => call.method == 'sendFullSnapshot'); final args = full.arguments as Map; diff --git a/posthog_flutter/test/replay_screenshot_config_test.dart b/posthog_flutter/test/replay_screenshot_config_test.dart index edc745a2..84354b4a 100644 --- a/posthog_flutter/test/replay_screenshot_config_test.dart +++ b/posthog_flutter/test/replay_screenshot_config_test.dart @@ -5,10 +5,10 @@ void main() { test('screenshot controls preserve native defaults', () { final config = PostHogSessionReplayConfig(); - expect(config.screenshotScale, 1.0); + expect(config.screenshotScale, isNull); expect(config.screenshotCompressionQuality, 30); expect(config.screenshotColorMode, PostHogScreenshotColorMode.argb8888); - expect(config.toMap(), containsPair('screenshotScale', 1.0)); + expect(config.toMap(), isNot(contains('screenshotScale'))); expect(config.toMap(), containsPair('screenshotCompressionQuality', 30)); expect(config.toMap(), containsPair('screenshotColorMode', 'argb8888')); }); @@ -26,10 +26,18 @@ void main() { expect(replay['screenshotColorMode'], 'rgb565'); }); + test('an explicit screenshot scale of 1.0 is sent to the native SDK', () { + final config = PostHogConfig('test_project_token') + ..sessionReplayConfig.screenshotScale = 1.0; + + final replay = config.toMap()['sessionReplayConfig'] as Map; + expect(replay['screenshotScale'], 1.0); + }); + test('screenshot scale clamps finite values and normalizes non-finite values', () { final config = PostHogSessionReplayConfig(); - for (final entry in <(double, double)>[ + for (final entry in <(double?, double?)>[ (-1, 0.1), (0, 0.1), (0.1, 0.1), @@ -37,10 +45,12 @@ void main() { (0.5, 0.5), (1, 1), (2, 1), - (double.nan, 1), - (double.infinity, 1), - (double.negativeInfinity, 1), + (double.nan, null), + (double.infinity, null), + (double.negativeInfinity, null), + (null, null), ]) { + config.screenshotScale = 0.5; config.screenshotScale = entry.$1; expect(config.screenshotScale, entry.$2); expect(config.toMap()['screenshotScale'], entry.$2); diff --git a/posthog_flutter/test/snapshots/setup_request.json b/posthog_flutter/test/snapshots/setup_request.json index 58543998..65d79fe5 100644 --- a/posthog_flutter/test/snapshots/setup_request.json +++ b/posthog_flutter/test/snapshots/setup_request.json @@ -33,7 +33,6 @@ "maskAllPlatformViews": true, "captureNativeScreens": false, "verifyScreenshotMaskAlignment": false, - "screenshotScale": 1.0, "screenshotCompressionQuality": 30, "screenshotColorMode": "argb8888", "sampleRate": 0.25