captureBoardPng function

Future<Uint8List?> captureBoardPng(
  1. GlobalKey<State<StatefulWidget>> boundaryKey, {
  2. double pixelRatio = 2.0,
  3. String? overlayText,
})

Captures boundaryKey (must be a RepaintBoundary) as a PNG, overlaying overlayText (if any) on a translucent bar at the bottom of the image — so the image carries its own level/score/date info even if the recipient only views/saves the picture, independent of the share caption. Returns null if the widget hasn't built yet (context is null). Kept separate from shareBoardImage so it's testable without touching share_plus's platform channel.

Implementation

Future<Uint8List?> captureBoardPng(
  GlobalKey boundaryKey, {
  double pixelRatio = 2.0,
  String? overlayText,
}) async {
  final ctx = boundaryKey.currentContext;
  if (ctx == null) return null;
  // BUG-29: type-check instead of force-casting — a key mismatched to a
  // non-RepaintBoundary widget (caller integration bug) now returns the
  // already-documented "no capture" result instead of an unhandled crash.
  final renderObject = ctx.findRenderObject();
  if (renderObject is! RenderRepaintBoundary) return null;

  final board = await renderObject.toImage(pixelRatio: pixelRatio);
  // BUG-29: both `board` and (when an overlay is drawn) the new composited
  // image hold native pixel buffers that leak unless disposed — every
  // share (score card, journey card) previously leaked at least one.
  ui.Image? overlayImage;
  try {
    final image = (overlayText == null || overlayText.isEmpty)
        ? board
        : (overlayImage = await _withTextOverlay(
            board,
            overlayText,
            pixelRatio,
          ));
    final bytes = await image.toByteData(format: ui.ImageByteFormat.png);
    return bytes?.buffer.asUint8List();
  } finally {
    board.dispose();
    overlayImage?.dispose();
  }
}