feedback icon indicating copy to clipboard operation
feedback copied to clipboard

Hide sensitive content in screenshot

Open JakesMD opened this issue 3 months ago • 5 comments

To comply with GDPR it would be helpful to blot out sensitive data.

Proposal

  • A SensitiveFeedback widget. Wrapping a widget with it will mean that it will be a back box in the screenshot.
  • Some sort of SensitiveFeedbackProvider containing a isTakingScreenshot variable that the SensitiveFeedback widget can observe.
  • Toggle the isTakingScreenshot variable in ScreenshotController.capture.

I would be happy to implement this myself. But maybe someone has a better proposal before I open a PR.

JakesMD avatar Sep 30 '25 18:09 JakesMD

You could also blot out the content in draw mode, so that the user knows what the screenshot will actually look like.

JakesMD avatar Sep 30 '25 18:09 JakesMD

It would be awesome to make it happen with the SensitiveContent widget from the Flutter framework, instead of building a new widget.

After taking a screenshot, traverse the widget tree, get the positions of it, and do some image manipulation on the screenshot. I guess that would result in a more user friendly API.

ueman avatar Oct 01 '25 05:10 ueman

I've successfully got all SensitiveContent widgets to be blurred in draw mode and during a screenshot.

The only issue is that we don't know whether a SensitiveContent widget is actually on the page below and therefore not visible. So currently a blurred box is created over widgets that are not even visible.

import 'dart:ui';

import 'package:feedback/src/painter.dart';
import 'package:flutter/material.dart';

class PaintOnChild extends StatelessWidget {
  const PaintOnChild({
    super.key,
    required this.child,
    required this.isPaintingActive,
    required this.controller,
  });

  final Widget child;
  final bool isPaintingActive;
  final PainterController controller;

  @override
  Widget build(BuildContext context) {
    final sensitiveContent = _findSensitiveContent(context);

    return Stack(
      children: <Widget>[
        child,
        if (isPaintingActive)
          for (final content in sensitiveContent)
            Positioned.fromRect(
              rect: content,
              child: ClipRect(
                clipBehavior: Clip.hardEdge,
                child: BackdropFilter(
                  filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
                  child: SizedBox.expand(),
                ),
              ),
            ),
        if (isPaintingActive) Painter(controller),
      ],
    );
  }

  List<Rect> _findSensitiveContent(BuildContext context) {
    final List<Rect> results = [];

    // Get the RenderBox of the ancestor to use as the reference frame
    final ancestorRenderBox = context.findRenderObject() as RenderBox?;
    if (ancestorRenderBox == null || !ancestorRenderBox.hasSize) return results;

    void searchElement(Element element) {
      if (element.widget is SensitiveContent) {
        final targetRenderBox = element.renderObject;

        if (targetRenderBox is RenderBox &&
            targetRenderBox.hasSize &&
            targetRenderBox.attached) {
          final size = targetRenderBox.size;

          final position = targetRenderBox.localToGlobal(Offset.zero,
              ancestor: ancestorRenderBox);

          results.add(
            Rect.fromLTWH(position.dx, position.dy, size.width, size.height),
          );
        }
      }

      element.visitChildren(searchElement);
    }

    searchElement(context as Element);

    return results;
  }
}

JakesMD avatar Oct 03 '25 12:10 JakesMD

The other issue of course is that if a SensitiveContent is partially obscured, the widget obscuring it will also be blurred.

JakesMD avatar Oct 03 '25 12:10 JakesMD

I have a successful implementation of the custom widget approach here: https://github.com/JakesMD/feedback/tree/hide-sensitive-content

It's less elegant than directly adding the blurring to PaintOnChild - but it works.

JakesMD avatar Oct 03 '25 12:10 JakesMD