From 3c506ed3c5e4aabd9ea78ec1d5140205487b14c9 Mon Sep 17 00:00:00 2001 From: thisames Date: Tue, 14 Jan 2025 11:46:41 -0300 Subject: [PATCH 1/7] feat: add wrapper support PostHogNoMaskWidget for Flutter widgets --- example/lib/main.dart | 41 +++++++++------ lib/posthog_flutter.dart | 1 + .../replay/element_parsers/element_data.dart | 16 ++++++ .../element_object_parser.dart | 39 ++++++++++++++ lib/src/replay/mask/image_mask_painter.dart | 51 +++++++++++++++---- .../replay/mask/posthog_mask_controller.dart | 25 +++++++++ .../replay/mask/posthog_nomask_widget.dart | 35 +++++++++++++ .../screenshot/screenshot_capturer.dart | 8 +++ 8 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 lib/src/replay/mask/posthog_nomask_widget.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index c96f7c0..974ca44 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -82,7 +82,11 @@ class _InitialScreenState extends State<_InitialScreen> { builder: (context) => const _FirstRoute()), ); }, - child: const Text('Go to Second Route'), + child: const PostHogNoMaskWidget( + child: Text( + 'Go to Second Route', + ), + ), ), const Padding( padding: EdgeInsets.all(8.0), @@ -204,14 +208,16 @@ class _InitialScreenState extends State<_InitialScreen> { child: const Text("Flush"), ), ElevatedButton( - onPressed: () async { - final result = await _posthogFlutterPlugin.getDistinctId(); - setState(() { - _result = result; - }); - }, - child: const Text("distinctId"), - ), + onPressed: () async { + final result = + await _posthogFlutterPlugin.getDistinctId(); + setState(() { + _result = result; + }); + }, + child: const PostHogNoMaskWidget( + child: Text("distinctId"), + )), const Divider(), const Padding( padding: EdgeInsets.all(8.0), @@ -254,7 +260,8 @@ class _InitialScreenState extends State<_InitialScreen> { onPressed: () async { await _posthogFlutterPlugin.reloadFeatureFlags(); }, - child: const Text("reloadFeatureFlags"), + child: const PostHogNoMaskWidget( + child: Text("reloadFeatureFlags")), ), const Divider(), const Padding( @@ -291,7 +298,7 @@ class _FirstRouteState extends State<_FirstRoute> with WidgetsBindingObserver { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('First Route'), + title: const PostHogNoMaskWidget(child: Text('First Route')), ), body: Center( child: RepaintBoundary( @@ -299,7 +306,7 @@ class _FirstRouteState extends State<_FirstRoute> with WidgetsBindingObserver { mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( - child: const Text('Open route'), + child: const PostHogNoMaskWidget(child: Text('Open route')), onPressed: () { Navigator.push( context, @@ -310,18 +317,20 @@ class _FirstRouteState extends State<_FirstRoute> with WidgetsBindingObserver { }, ), const SizedBox(height: 20), - const TextField( + const PostHogNoMaskWidget( + child: TextField( decoration: InputDecoration( labelText: 'Sensitive Text Input', hintText: 'Enter sensitive data', border: OutlineInputBorder(), ), - ), + )), const SizedBox(height: 20), - Image.asset( + PostHogNoMaskWidget( + child: Image.asset( 'assets/training_posthog.png', height: 200, - ), + )), const SizedBox(height: 20), ], ), diff --git a/lib/posthog_flutter.dart b/lib/posthog_flutter.dart index 8a6da67..ebb7d90 100644 --- a/lib/posthog_flutter.dart +++ b/lib/posthog_flutter.dart @@ -4,3 +4,4 @@ export 'src/posthog.dart'; export 'src/posthog_config.dart'; export 'src/posthog_observer.dart'; export 'src/posthog_widget_widget.dart'; +export 'src/replay/mask/posthog_nomask_widget.dart'; diff --git a/lib/src/replay/element_parsers/element_data.dart b/lib/src/replay/element_parsers/element_data.dart index c65f5dc..d9283a0 100644 --- a/lib/src/replay/element_parsers/element_data.dart +++ b/lib/src/replay/element_parsers/element_data.dart @@ -16,6 +16,12 @@ class ElementData { children?.add(elementData); } + List extractNoMaskWidgetRects() { + final rects = []; + _collectNoMaskWidgetRects(rects); + return rects; + } + List extractRects({bool isRoot = true}) { List rects = []; @@ -35,4 +41,14 @@ class ElementData { } return rects; } + + void _collectNoMaskWidgetRects(List rects) { + if (type == 'PostHogNoMaskWidget' && !rects.contains(rect)) { + rects.add(rect); + } + + for (final child in children!) { + child._collectNoMaskWidgetRects(rects); + } + } } diff --git a/lib/src/replay/element_parsers/element_object_parser.dart b/lib/src/replay/element_parsers/element_object_parser.dart index 45a8d48..7b94682 100644 --- a/lib/src/replay/element_parsers/element_object_parser.dart +++ b/lib/src/replay/element_parsers/element_object_parser.dart @@ -1,12 +1,49 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter/rendering.dart'; import 'package:posthog_flutter/src/replay/element_parsers/element_data.dart'; +import 'package:posthog_flutter/src/replay/element_parsers/element_parser.dart'; import 'package:posthog_flutter/src/replay/mask/posthog_mask_controller.dart'; +import 'package:posthog_flutter/src/replay/mask/posthog_nomask_widget.dart'; class ElementObjectParser { ElementData? relateRenderObject( ElementData activeElementData, Element element, ) { + if (element.widget is PostHogNoMaskWidget) { + final elementData = ElementParser().relate(element, activeElementData); + + if (elementData != null) { + activeElementData.addChildren(elementData); + return elementData; + } + } + + if (element.widget is Text) { + final elementData = ElementParser().relate(element, activeElementData); + + if (elementData != null) { + activeElementData.addChildren(elementData); + return elementData; + } + } + + if (element.renderObject is RenderImage) { + final String dataType = element.renderObject.runtimeType.toString(); + + final parser = PostHogMaskController.instance.parsers[dataType]; + if (parser != null) { + final elementData = parser.relate(element, activeElementData); + + if (elementData != null) { + activeElementData.addChildren(elementData); + return elementData; + } + } + } + + // THIS WAY IN THE FUTURE WE CAN MOUNTED FULL WIREFRAME MORE EASILY + /* if (element.renderObject is RenderBox) { final String dataType = element.renderObject.runtimeType.toString(); @@ -20,6 +57,8 @@ class ElementObjectParser { } } } + */ + return null; } } diff --git a/lib/src/replay/mask/image_mask_painter.dart b/lib/src/replay/mask/image_mask_painter.dart index 506fe7b..13c851e 100644 --- a/lib/src/replay/mask/image_mask_painter.dart +++ b/lib/src/replay/mask/image_mask_painter.dart @@ -3,16 +3,49 @@ import 'package:posthog_flutter/src/replay/element_parsers/element_data.dart'; class ImageMaskPainter { void drawMaskedImage( - Canvas canvas, List items, double pixelRatio) { + Canvas canvas, + List items, + double pixelRatio, + ) { final paint = Paint()..style = PaintingStyle.fill; - for (var elementData in items) { - paint.color = Colors.black; - final scaled = Rect.fromLTRB( - elementData.rect.left * pixelRatio, - elementData.rect.top * pixelRatio, - elementData.rect.right * pixelRatio, - elementData.rect.bottom * pixelRatio); - canvas.drawRect(scaled, paint); + + for (final element in items) { + paint.color = _getColorForElement(element); + + final scaledRect = _scaleRect(element.rect, pixelRatio); + + canvas.drawRect(scaledRect, paint); + } + } + + void drawMaskedImageWrapper( + Canvas canvas, + List items, + double pixelRatio, + ) { + final paint = Paint() + ..style = PaintingStyle.fill + ..color = Colors.pinkAccent; + + for (final rect in items) { + final scaledRect = _scaleRect(rect, pixelRatio); + canvas.drawRect(scaledRect, paint); + } + } + + Color _getColorForElement(ElementData element) { + if (element.type == 'PostHogNoMaskWidget') { + return Colors.pinkAccent; } + return Colors.black; + } + + Rect _scaleRect(Rect rect, double pixelRatio) { + return Rect.fromLTRB( + rect.left * pixelRatio, + rect.top * pixelRatio, + rect.right * pixelRatio, + rect.bottom * pixelRatio, + ); } } diff --git a/lib/src/replay/mask/posthog_mask_controller.dart b/lib/src/replay/mask/posthog_mask_controller.dart index 93dd037..6530f39 100644 --- a/lib/src/replay/mask/posthog_mask_controller.dart +++ b/lib/src/replay/mask/posthog_mask_controller.dart @@ -67,4 +67,29 @@ class PostHogMaskController { return null; } } + + List? getPostHogWidgetWrapperElements() { + final BuildContext? context = containerKey.currentContext; + + if (context == null) { + printIfDebug('Error: containerKey.currentContext is null.'); + return null; + } + + try { + final ElementData? widgetElementsTree = + _widgetScraper.parseRenderTree(context); + + if (widgetElementsTree == null) { + printIfDebug('Error: widgetElementsTree is null after parsing.'); + return null; + } + + return widgetElementsTree.extractNoMaskWidgetRects(); + } catch (e) { + printIfDebug( + 'Error during render tree parsing or rectangle extraction: $e'); + return null; + } + } } diff --git a/lib/src/replay/mask/posthog_nomask_widget.dart b/lib/src/replay/mask/posthog_nomask_widget.dart new file mode 100644 index 0000000..c503d15 --- /dev/null +++ b/lib/src/replay/mask/posthog_nomask_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +class PostHogNoMaskWidget extends StatefulWidget { + final Widget child; + + const PostHogNoMaskWidget({ + Key? key, + required this.child, + }) : super(key: key); + + @override + _PostHogNoMaskWidgetState createState() => _PostHogNoMaskWidgetState(); +} + +class _PostHogNoMaskWidgetState extends State { + final GlobalKey _widgetKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + key: _widgetKey, + child: widget.child, + ); + } +} diff --git a/lib/src/replay/screenshot/screenshot_capturer.dart b/lib/src/replay/screenshot/screenshot_capturer.dart index 3be4086..16c7bc5 100644 --- a/lib/src/replay/screenshot/screenshot_capturer.dart +++ b/lib/src/replay/screenshot/screenshot_capturer.dart @@ -98,6 +98,9 @@ class ScreenshotCapturer { final replayConfig = _config.sessionReplayConfig; + final postHogWidgetWrapperElements = + PostHogMaskController.instance.getPostHogWidgetWrapperElements(); + // call getCurrentScreenRects if really necessary List? elementsDataWidgets; if (replayConfig.maskAllTexts || replayConfig.maskAllImages) { @@ -179,6 +182,11 @@ class ScreenshotCapturer { picture.dispose(); } } else { + if (postHogWidgetWrapperElements!.isNotEmpty) { + _imageMaskPainter.drawMaskedImageWrapper( + canvas, postHogWidgetWrapperElements, pixelRatio); + } + final picture = recorder.endRecording(); final finalImage = From 25a3fd93724759545df816fef48d5b136b2a9338 Mon Sep 17 00:00:00 2001 From: thisames Date: Tue, 14 Jan 2025 12:17:10 -0300 Subject: [PATCH 2/7] fix _collectNoMaskWidgetRects to works with null operators --- lib/src/replay/element_parsers/element_data.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/src/replay/element_parsers/element_data.dart b/lib/src/replay/element_parsers/element_data.dart index d9283a0..0e7d9b4 100644 --- a/lib/src/replay/element_parsers/element_data.dart +++ b/lib/src/replay/element_parsers/element_data.dart @@ -18,7 +18,7 @@ class ElementData { List extractNoMaskWidgetRects() { final rects = []; - _collectNoMaskWidgetRects(rects); + _collectNoMaskWidgetRects(this, rects); return rects; } @@ -42,13 +42,17 @@ class ElementData { return rects; } - void _collectNoMaskWidgetRects(List rects) { - if (type == 'PostHogNoMaskWidget' && !rects.contains(rect)) { - rects.add(rect); + void _collectNoMaskWidgetRects(ElementData element, List rectList) { + if (!rectList.contains(element.rect)) { + if (element.type == "PostHogNoMaskWidget") { + rectList.add(element.rect); + } } - for (final child in children!) { - child._collectNoMaskWidgetRects(rects); + if (element.children != null && element.children!.isNotEmpty) { + for (var child in element.children!) { + _collectNoMaskWidgetRects(child, rectList); + } } } } From 56e491c0889e6428e86b251c8d9d52b928868b8e Mon Sep 17 00:00:00 2001 From: thisames Date: Wed, 15 Jan 2025 11:41:36 -0300 Subject: [PATCH 3/7] fix code review --- example/lib/main.dart | 14 ++--- .../replay/element_parsers/element_data.dart | 10 ++- .../element_object_parser.dart | 27 ++------ .../element_parsers/element_parser.dart | 6 +- lib/src/replay/mask/image_mask_painter.dart | 61 +++++++------------ .../replay/mask/posthog_mask_controller.dart | 4 +- .../replay/mask/posthog_nomask_widget.dart | 8 +-- 7 files changed, 52 insertions(+), 78 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 974ca44..9ad46bb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -82,7 +82,7 @@ class _InitialScreenState extends State<_InitialScreen> { builder: (context) => const _FirstRoute()), ); }, - child: const PostHogNoMaskWidget( + child: const PostHogMaskWidget( child: Text( 'Go to Second Route', ), @@ -215,7 +215,7 @@ class _InitialScreenState extends State<_InitialScreen> { _result = result; }); }, - child: const PostHogNoMaskWidget( + child: const PostHogMaskWidget( child: Text("distinctId"), )), const Divider(), @@ -260,7 +260,7 @@ class _InitialScreenState extends State<_InitialScreen> { onPressed: () async { await _posthogFlutterPlugin.reloadFeatureFlags(); }, - child: const PostHogNoMaskWidget( + child: const PostHogMaskWidget( child: Text("reloadFeatureFlags")), ), const Divider(), @@ -298,7 +298,7 @@ class _FirstRouteState extends State<_FirstRoute> with WidgetsBindingObserver { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const PostHogNoMaskWidget(child: Text('First Route')), + title: const PostHogMaskWidget(child: Text('First Route')), ), body: Center( child: RepaintBoundary( @@ -306,7 +306,7 @@ class _FirstRouteState extends State<_FirstRoute> with WidgetsBindingObserver { mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( - child: const PostHogNoMaskWidget(child: Text('Open route')), + child: const PostHogMaskWidget(child: Text('Open route')), onPressed: () { Navigator.push( context, @@ -317,7 +317,7 @@ class _FirstRouteState extends State<_FirstRoute> with WidgetsBindingObserver { }, ), const SizedBox(height: 20), - const PostHogNoMaskWidget( + const PostHogMaskWidget( child: TextField( decoration: InputDecoration( labelText: 'Sensitive Text Input', @@ -326,7 +326,7 @@ class _FirstRouteState extends State<_FirstRoute> with WidgetsBindingObserver { ), )), const SizedBox(height: 20), - PostHogNoMaskWidget( + PostHogMaskWidget( child: Image.asset( 'assets/training_posthog.png', height: 200, diff --git a/lib/src/replay/element_parsers/element_data.dart b/lib/src/replay/element_parsers/element_data.dart index 0e7d9b4..c377671 100644 --- a/lib/src/replay/element_parsers/element_data.dart +++ b/lib/src/replay/element_parsers/element_data.dart @@ -1,14 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:posthog_flutter/posthog_flutter.dart'; class ElementData { List? children; Rect rect; String type; + Widget? widget; ElementData({ this.children, required this.rect, required this.type, + this.widget, }); void addChildren(ElementData elementData) { @@ -44,13 +47,14 @@ class ElementData { void _collectNoMaskWidgetRects(ElementData element, List rectList) { if (!rectList.contains(element.rect)) { - if (element.type == "PostHogNoMaskWidget") { + if (element.widget is PostHogMaskWidget) { rectList.add(element.rect); } } - if (element.children != null && element.children!.isNotEmpty) { - for (var child in element.children!) { + final children = element.children; + if (children != null && children.isNotEmpty) { + for (var child in children) { _collectNoMaskWidgetRects(child, rectList); } } diff --git a/lib/src/replay/element_parsers/element_object_parser.dart b/lib/src/replay/element_parsers/element_object_parser.dart index 7b94682..de9856e 100644 --- a/lib/src/replay/element_parsers/element_object_parser.dart +++ b/lib/src/replay/element_parsers/element_object_parser.dart @@ -6,12 +6,14 @@ import 'package:posthog_flutter/src/replay/mask/posthog_mask_controller.dart'; import 'package:posthog_flutter/src/replay/mask/posthog_nomask_widget.dart'; class ElementObjectParser { + final ElementParser _elementParser = ElementParser(); + ElementData? relateRenderObject( ElementData activeElementData, Element element, ) { - if (element.widget is PostHogNoMaskWidget) { - final elementData = ElementParser().relate(element, activeElementData); + if (element.widget is PostHogMaskWidget) { + final elementData = _elementParser.relate(element, activeElementData); if (elementData != null) { activeElementData.addChildren(elementData); @@ -20,7 +22,7 @@ class ElementObjectParser { } if (element.widget is Text) { - final elementData = ElementParser().relate(element, activeElementData); + final elementData = _elementParser.relate(element, activeElementData); if (elementData != null) { activeElementData.addChildren(elementData); @@ -29,23 +31,7 @@ class ElementObjectParser { } if (element.renderObject is RenderImage) { - final String dataType = element.renderObject.runtimeType.toString(); - - final parser = PostHogMaskController.instance.parsers[dataType]; - if (parser != null) { - final elementData = parser.relate(element, activeElementData); - - if (elementData != null) { - activeElementData.addChildren(elementData); - return elementData; - } - } - } - - // THIS WAY IN THE FUTURE WE CAN MOUNTED FULL WIREFRAME MORE EASILY - /* - if (element.renderObject is RenderBox) { - final String dataType = element.renderObject.runtimeType.toString(); + final dataType = element.renderObject.runtimeType.toString(); final parser = PostHogMaskController.instance.parsers[dataType]; if (parser != null) { @@ -57,7 +43,6 @@ class ElementObjectParser { } } } - */ return null; } diff --git a/lib/src/replay/element_parsers/element_parser.dart b/lib/src/replay/element_parsers/element_parser.dart index 92fa44b..f09ed63 100644 --- a/lib/src/replay/element_parsers/element_parser.dart +++ b/lib/src/replay/element_parsers/element_parser.dart @@ -15,9 +15,9 @@ class ElementParser { } final thisElementData = ElementData( - type: element.widget.runtimeType.toString(), - rect: elementRect, - ); + type: element.widget.runtimeType.toString(), + rect: elementRect, + widget: element.widget); return thisElementData; } diff --git a/lib/src/replay/mask/image_mask_painter.dart b/lib/src/replay/mask/image_mask_painter.dart index 13c851e..e3838b8 100644 --- a/lib/src/replay/mask/image_mask_painter.dart +++ b/lib/src/replay/mask/image_mask_painter.dart @@ -1,51 +1,36 @@ import 'package:flutter/material.dart'; +import 'package:posthog_flutter/posthog_flutter.dart'; import 'package:posthog_flutter/src/replay/element_parsers/element_data.dart'; class ImageMaskPainter { void drawMaskedImage( - Canvas canvas, - List items, - double pixelRatio, - ) { + Canvas canvas, List items, double pixelRatio) { final paint = Paint()..style = PaintingStyle.fill; - - for (final element in items) { - paint.color = _getColorForElement(element); - - final scaledRect = _scaleRect(element.rect, pixelRatio); - - canvas.drawRect(scaledRect, paint); + for (var elementData in items) { + paint.color = Colors.black; + if (elementData.widget is PostHogMaskWidget) { + paint.color = Colors.pinkAccent; + } + final scaled = Rect.fromLTRB( + elementData.rect.left * pixelRatio, + elementData.rect.top * pixelRatio, + elementData.rect.right * pixelRatio, + elementData.rect.bottom * pixelRatio); + canvas.drawRect(scaled, paint); } } void drawMaskedImageWrapper( - Canvas canvas, - List items, - double pixelRatio, - ) { - final paint = Paint() - ..style = PaintingStyle.fill - ..color = Colors.pinkAccent; - - for (final rect in items) { - final scaledRect = _scaleRect(rect, pixelRatio); - canvas.drawRect(scaledRect, paint); - } - } - - Color _getColorForElement(ElementData element) { - if (element.type == 'PostHogNoMaskWidget') { - return Colors.pinkAccent; + Canvas canvas, List items, double pixelRatio) { + final paint = Paint()..style = PaintingStyle.fill; + for (var rect in items) { + paint.color = Colors.pinkAccent; + final scaled = Rect.fromLTRB( + rect.left * pixelRatio, + rect.top * pixelRatio, + rect.right * pixelRatio, + rect.bottom * pixelRatio); + canvas.drawRect(scaled, paint); } - return Colors.black; - } - - Rect _scaleRect(Rect rect, double pixelRatio) { - return Rect.fromLTRB( - rect.left * pixelRatio, - rect.top * pixelRatio, - rect.right * pixelRatio, - rect.bottom * pixelRatio, - ); } } diff --git a/lib/src/replay/mask/posthog_mask_controller.dart b/lib/src/replay/mask/posthog_mask_controller.dart index 6530f39..5e49656 100644 --- a/lib/src/replay/mask/posthog_mask_controller.dart +++ b/lib/src/replay/mask/posthog_mask_controller.dart @@ -45,7 +45,7 @@ class PostHogMaskController { /// renderable elements. /// List? getCurrentWidgetsElements() { - final BuildContext? context = containerKey.currentContext; + final context = containerKey.currentContext; if (context == null) { printIfDebug('Error: containerKey.currentContext is null.'); @@ -69,7 +69,7 @@ class PostHogMaskController { } List? getPostHogWidgetWrapperElements() { - final BuildContext? context = containerKey.currentContext; + final context = containerKey.currentContext; if (context == null) { printIfDebug('Error: containerKey.currentContext is null.'); diff --git a/lib/src/replay/mask/posthog_nomask_widget.dart b/lib/src/replay/mask/posthog_nomask_widget.dart index c503d15..0b59986 100644 --- a/lib/src/replay/mask/posthog_nomask_widget.dart +++ b/lib/src/replay/mask/posthog_nomask_widget.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; -class PostHogNoMaskWidget extends StatefulWidget { +class PostHogMaskWidget extends StatefulWidget { final Widget child; - const PostHogNoMaskWidget({ + const PostHogMaskWidget({ Key? key, required this.child, }) : super(key: key); @override - _PostHogNoMaskWidgetState createState() => _PostHogNoMaskWidgetState(); + _PostHogMaskWidgetState createState() => _PostHogMaskWidgetState(); } -class _PostHogNoMaskWidgetState extends State { +class _PostHogMaskWidgetState extends State { final GlobalKey _widgetKey = GlobalKey(); @override From 1fef06879a8ea5beba0d165df198cd7684b75ba7 Mon Sep 17 00:00:00 2001 From: thisames Date: Wed, 15 Jan 2025 12:01:50 -0300 Subject: [PATCH 4/7] fix code review --- lib/src/replay/screenshot/screenshot_capturer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/replay/screenshot/screenshot_capturer.dart b/lib/src/replay/screenshot/screenshot_capturer.dart index 16c7bc5..495a93f 100644 --- a/lib/src/replay/screenshot/screenshot_capturer.dart +++ b/lib/src/replay/screenshot/screenshot_capturer.dart @@ -182,7 +182,7 @@ class ScreenshotCapturer { picture.dispose(); } } else { - if (postHogWidgetWrapperElements!.isNotEmpty) { + if (postHogWidgetWrapperElements != null && postHogWidgetWrapperElements.isNotEmpty) { _imageMaskPainter.drawMaskedImageWrapper( canvas, postHogWidgetWrapperElements, pixelRatio); } From 5a08417b83893c6562523c08055f9e3ebd209f7f Mon Sep 17 00:00:00 2001 From: thisames Date: Wed, 15 Jan 2025 13:49:18 -0300 Subject: [PATCH 5/7] fix code review --- lib/posthog_flutter.dart | 2 +- lib/src/replay/element_parsers/element_data.dart | 6 +++--- lib/src/replay/element_parsers/element_object_parser.dart | 2 +- lib/src/replay/mask/image_mask_painter.dart | 2 +- lib/src/replay/mask/posthog_mask_controller.dart | 3 +-- ...{posthog_nomask_widget.dart => posthog_mask_widget.dart} | 0 lib/src/replay/screenshot/screenshot_capturer.dart | 3 ++- 7 files changed, 9 insertions(+), 9 deletions(-) rename lib/src/replay/mask/{posthog_nomask_widget.dart => posthog_mask_widget.dart} (100%) diff --git a/lib/posthog_flutter.dart b/lib/posthog_flutter.dart index ebb7d90..7b7d9f7 100644 --- a/lib/posthog_flutter.dart +++ b/lib/posthog_flutter.dart @@ -4,4 +4,4 @@ export 'src/posthog.dart'; export 'src/posthog_config.dart'; export 'src/posthog_observer.dart'; export 'src/posthog_widget_widget.dart'; -export 'src/replay/mask/posthog_nomask_widget.dart'; +export 'src/replay/mask/posthog_mask_widget.dart'; diff --git a/lib/src/replay/element_parsers/element_data.dart b/lib/src/replay/element_parsers/element_data.dart index c377671..0a977c6 100644 --- a/lib/src/replay/element_parsers/element_data.dart +++ b/lib/src/replay/element_parsers/element_data.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:posthog_flutter/posthog_flutter.dart'; +import 'package:posthog_flutter/src/replay/mask/posthog_mask_widget.dart'; class ElementData { - List? children; Rect rect; String type; + List? children; Widget? widget; ElementData({ - this.children, required this.rect, required this.type, + this.children, this.widget, }); diff --git a/lib/src/replay/element_parsers/element_object_parser.dart b/lib/src/replay/element_parsers/element_object_parser.dart index de9856e..3110387 100644 --- a/lib/src/replay/element_parsers/element_object_parser.dart +++ b/lib/src/replay/element_parsers/element_object_parser.dart @@ -3,7 +3,7 @@ import 'package:flutter/rendering.dart'; import 'package:posthog_flutter/src/replay/element_parsers/element_data.dart'; import 'package:posthog_flutter/src/replay/element_parsers/element_parser.dart'; import 'package:posthog_flutter/src/replay/mask/posthog_mask_controller.dart'; -import 'package:posthog_flutter/src/replay/mask/posthog_nomask_widget.dart'; +import 'package:posthog_flutter/src/replay/mask/posthog_mask_widget.dart'; class ElementObjectParser { final ElementParser _elementParser = ElementParser(); diff --git a/lib/src/replay/mask/image_mask_painter.dart b/lib/src/replay/mask/image_mask_painter.dart index e3838b8..137f254 100644 --- a/lib/src/replay/mask/image_mask_painter.dart +++ b/lib/src/replay/mask/image_mask_painter.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:posthog_flutter/posthog_flutter.dart'; import 'package:posthog_flutter/src/replay/element_parsers/element_data.dart'; +import 'package:posthog_flutter/src/replay/mask/posthog_mask_widget.dart'; class ImageMaskPainter { void drawMaskedImage( diff --git a/lib/src/replay/mask/posthog_mask_controller.dart b/lib/src/replay/mask/posthog_mask_controller.dart index 5e49656..03846bc 100644 --- a/lib/src/replay/mask/posthog_mask_controller.dart +++ b/lib/src/replay/mask/posthog_mask_controller.dart @@ -77,8 +77,7 @@ class PostHogMaskController { } try { - final ElementData? widgetElementsTree = - _widgetScraper.parseRenderTree(context); + final widgetElementsTree = _widgetScraper.parseRenderTree(context); if (widgetElementsTree == null) { printIfDebug('Error: widgetElementsTree is null after parsing.'); diff --git a/lib/src/replay/mask/posthog_nomask_widget.dart b/lib/src/replay/mask/posthog_mask_widget.dart similarity index 100% rename from lib/src/replay/mask/posthog_nomask_widget.dart rename to lib/src/replay/mask/posthog_mask_widget.dart diff --git a/lib/src/replay/screenshot/screenshot_capturer.dart b/lib/src/replay/screenshot/screenshot_capturer.dart index 495a93f..ec6bdd2 100644 --- a/lib/src/replay/screenshot/screenshot_capturer.dart +++ b/lib/src/replay/screenshot/screenshot_capturer.dart @@ -182,7 +182,8 @@ class ScreenshotCapturer { picture.dispose(); } } else { - if (postHogWidgetWrapperElements != null && postHogWidgetWrapperElements.isNotEmpty) { + if (postHogWidgetWrapperElements != null && + postHogWidgetWrapperElements.isNotEmpty) { _imageMaskPainter.drawMaskedImageWrapper( canvas, postHogWidgetWrapperElements, pixelRatio); } From 9db9d984de03fabba48c582381c5cc2cac60496d Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 16 Jan 2025 11:27:57 +0100 Subject: [PATCH 6/7] add fixes --- CHANGELOG.md | 2 ++ lib/src/replay/element_parsers/element_data.dart | 8 ++++---- lib/src/replay/mask/posthog_mask_controller.dart | 2 +- lib/src/replay/mask/posthog_mask_widget.dart | 8 ++++---- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4608ca1..d5b53eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- chore: add support for session replay manual masking with the PostHogMaskWidget widget ([#153](https://github.com/PostHog/posthog-flutter/pull/153)) + ## 4.9.4 - fix: solve masks out of sync when moving too fast ([#147](https://github.com/PostHog/posthog-flutter/pull/147)) diff --git a/lib/src/replay/element_parsers/element_data.dart b/lib/src/replay/element_parsers/element_data.dart index 0a977c6..5485234 100644 --- a/lib/src/replay/element_parsers/element_data.dart +++ b/lib/src/replay/element_parsers/element_data.dart @@ -19,9 +19,9 @@ class ElementData { children?.add(elementData); } - List extractNoMaskWidgetRects() { + List extractMaskWidgetRects() { final rects = []; - _collectNoMaskWidgetRects(this, rects); + _collectMaskWidgetRects(this, rects); return rects; } @@ -45,7 +45,7 @@ class ElementData { return rects; } - void _collectNoMaskWidgetRects(ElementData element, List rectList) { + void _collectMaskWidgetRects(ElementData element, List rectList) { if (!rectList.contains(element.rect)) { if (element.widget is PostHogMaskWidget) { rectList.add(element.rect); @@ -55,7 +55,7 @@ class ElementData { final children = element.children; if (children != null && children.isNotEmpty) { for (var child in children) { - _collectNoMaskWidgetRects(child, rectList); + _collectMaskWidgetRects(child, rectList); } } } diff --git a/lib/src/replay/mask/posthog_mask_controller.dart b/lib/src/replay/mask/posthog_mask_controller.dart index 03846bc..7fd8e37 100644 --- a/lib/src/replay/mask/posthog_mask_controller.dart +++ b/lib/src/replay/mask/posthog_mask_controller.dart @@ -84,7 +84,7 @@ class PostHogMaskController { return null; } - return widgetElementsTree.extractNoMaskWidgetRects(); + return widgetElementsTree.extractMaskWidgetRects(); } catch (e) { printIfDebug( 'Error during render tree parsing or rectangle extraction: $e'); diff --git a/lib/src/replay/mask/posthog_mask_widget.dart b/lib/src/replay/mask/posthog_mask_widget.dart index 0b59986..25ec21c 100644 --- a/lib/src/replay/mask/posthog_mask_widget.dart +++ b/lib/src/replay/mask/posthog_mask_widget.dart @@ -4,15 +4,15 @@ class PostHogMaskWidget extends StatefulWidget { final Widget child; const PostHogMaskWidget({ - Key? key, + super.key, required this.child, - }) : super(key: key); + }); @override - _PostHogMaskWidgetState createState() => _PostHogMaskWidgetState(); + PostHogMaskWidgetState createState() => PostHogMaskWidgetState(); } -class _PostHogMaskWidgetState extends State { +class PostHogMaskWidgetState extends State { final GlobalKey _widgetKey = GlobalKey(); @override From a8ac8ba7b38d8d635835ba0dfbbd089b39728cd0 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Thu, 16 Jan 2025 11:31:45 +0100 Subject: [PATCH 7/7] fix --- lib/src/replay/mask/image_mask_painter.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/replay/mask/image_mask_painter.dart b/lib/src/replay/mask/image_mask_painter.dart index 137f254..fd55373 100644 --- a/lib/src/replay/mask/image_mask_painter.dart +++ b/lib/src/replay/mask/image_mask_painter.dart @@ -9,7 +9,7 @@ class ImageMaskPainter { for (var elementData in items) { paint.color = Colors.black; if (elementData.widget is PostHogMaskWidget) { - paint.color = Colors.pinkAccent; + paint.color = Colors.black; } final scaled = Rect.fromLTRB( elementData.rect.left * pixelRatio, @@ -24,7 +24,7 @@ class ImageMaskPainter { Canvas canvas, List items, double pixelRatio) { final paint = Paint()..style = PaintingStyle.fill; for (var rect in items) { - paint.color = Colors.pinkAccent; + paint.color = Colors.black; final scaled = Rect.fromLTRB( rect.left * pixelRatio, rect.top * pixelRatio,