Skip to content

Commit

Permalink
feat: Refactor screenshot capture for improved performance and reliab…
Browse files Browse the repository at this point in the history
…ility (#147)
  • Loading branch information
thisames authored Jan 13, 2025
1 parent 0771e5f commit 31e9390
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 120 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Next

## 4.9.4

- fix: solve masks out of sync when moving too fast ([#147](https://github.com/PostHog/posthog-flutter/pull/147))

## 4.9.3

- chore: pin the iOS SDK to 3.18.0 ([#149](https://github.com/PostHog/posthog-flutter/pull/149))
Expand Down
10 changes: 4 additions & 6 deletions lib/src/posthog_widget_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:posthog_flutter/posthog_flutter.dart';
import 'package:posthog_flutter/src/replay/mask/posthog_mask_controller.dart';
import 'package:posthog_flutter/src/util/logging.dart';

import 'replay/change_detector.dart';
import 'replay/native_communicator.dart';
Expand Down Expand Up @@ -65,14 +66,11 @@ class PostHogWidgetState extends State<PostHogWidget> {
}

Future<void> _generateSnapshot() async {
final isSessionReplayActive =
await _nativeCommunicator?.isSessionReplayActive() ?? false;
if (!isSessionReplayActive) {
return;
}

// Ensure no asynchronous calls occur before this function,
// as it relies on a consistent state.
final imageInfo = await _screenshotCapturer?.captureScreenshot();
if (imageInfo == null) {
printIfDebug('Error: Failed to capture screenshot.');
return;
}

Expand Down
14 changes: 5 additions & 9 deletions lib/src/replay/element_parsers/element_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,20 @@ class ElementData {
children?.add(elementData);
}

List<Rect> extractRects([bool isRoot = true]) {
List<Rect> rects = [];

if (!isRoot) {
rects.add(rect);
}
List<ElementData> extractRects({bool isRoot = true}) {
List<ElementData> rects = [];

if (children != null) {
for (var child in children ?? []) {
if (child.children == null) {
rects.add(child.rect);
rects.add(child);
continue;
} else if ((child.children?.length ?? 0) > 1) {
for (var grandChild in child.children ?? []) {
rects.add(grandChild.rect);
rects.add(grandChild);
}
} else {
rects.add(child.rect);
rects.add(child);
}
}
}
Expand Down
41 changes: 12 additions & 29 deletions lib/src/replay/mask/image_mask_painter.dart
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:posthog_flutter/src/replay/element_parsers/element_data.dart';

class ImageMaskPainter {
Future<ui.Image> drawMaskedImage(
ui.Image image, List<Rect> rects, double pixelRatio) async {
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
final paint = Paint();

canvas.drawImage(image, Offset.zero, paint);

final rectPaint = Paint()
..color = Colors.black
..style = PaintingStyle.fill;

for (Rect rect in rects) {
Rect scaledRect = Rect.fromLTRB(
rect.left * pixelRatio,
rect.top * pixelRatio,
rect.right * pixelRatio,
rect.bottom * pixelRatio,
);
canvas.drawRect(scaledRect, rectPaint);
void drawMaskedImage(
Canvas canvas, List<ElementData> 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);
}

final picture = recorder.endRecording();

final maskedImage = await picture.toImage(
(image.width * pixelRatio).round(),
(image.height * pixelRatio).round(),
);
return maskedImage;
}
}
36 changes: 30 additions & 6 deletions lib/src/replay/mask/posthog_mask_controller.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:posthog_flutter/posthog_flutter.dart';
import 'package:posthog_flutter/src/replay/element_parsers/element_data.dart';
Expand All @@ -10,6 +8,7 @@ import 'package:posthog_flutter/src/replay/element_parsers/element_data_factory.
import 'package:posthog_flutter/src/replay/element_parsers/element_object_parser.dart';
import 'package:posthog_flutter/src/replay/element_parsers/root_element_provider.dart';
import 'package:posthog_flutter/src/replay/mask/widget_elements_decipher.dart';
import 'package:posthog_flutter/src/util/logging.dart';

class PostHogMaskController {
late final Map<String, ElementParser> parsers;
Expand All @@ -32,15 +31,40 @@ class PostHogMaskController {
PostHogMaskController._privateConstructor(
Posthog().config?.sessionReplayConfig);

Future<List<Rect>?> getCurrentScreenRects() async {
/// Extracts a flattened list of [ElementData] objects representing the
/// renderable elements in the widget tree.
///
/// This method traverses the tree of [ElementData] objects and returns a
/// list of elements that have no children or only one child.
///
/// The method is designed to extract the elements that are directly
/// renderable on the screen.
///
/// **Returns:**
/// - `List<ElementData>`: A list of [ElementData] objects representing the
/// renderable elements.
///
List<ElementData>? getCurrentWidgetsElements() {
final BuildContext? context = containerKey.currentContext;

if (context == null) {
printIfDebug('Error: containerKey.currentContext is null.');
return null;
}
final ElementData? widgetElementsTree =
_widgetScraper.parseRenderTree(context);

return widgetElementsTree?.extractRects();
try {
final widgetElementsTree = _widgetScraper.parseRenderTree(context);

if (widgetElementsTree == null) {
printIfDebug('Error: widgetElementsTree is null after parsing.');
return null;
}

return widgetElementsTree.extractRects();
} catch (e) {
printIfDebug(
'Error during render tree parsing or rectangle extraction: $e');
return null;
}
}
}
Loading

0 comments on commit 31e9390

Please sign in to comment.