refactor(share): share the widget capture helper

The lyrics card and the rewind slide both rendered a RepaintBoundary to a
temp PNG. captureBoundaryToPngFile does that; pixel ratio, file name, frame
waiting and share parameters stay with each caller.
This commit is contained in:
zarzet committed 2026-10-19 00:48:31 +07:00
1 parent da2fc2c9c9
commit b10370e4fc
4 files changed
+45 -29

No files matched your search

+2 -4
View File
@@ -1,17 +1,14 @@
import 'dart:async';
import 'dart:io';
import 'dart:math' as math;
import 'dart:ui' show ImageByteFormat, ImageFilter;
import 'dart:ui' show ImageFilter;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show RenderRepaintBoundary;
import 'package:flutter/semantics.dart' show CustomSemanticsAction;
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:material_3_expressive/material_3_expressive.dart'
show M3EShapeKind;
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart' show ShareParams, SharePlus, XFile;
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/providers/listening_rewind_provider.dart';
@@ -19,6 +16,7 @@ import 'package:spotiflac_android/services/listening_rewind.dart';
import 'package:spotiflac_android/theme/app_motion.dart';
import 'package:spotiflac_android/theme/app_tokens.dart';
import 'package:spotiflac_android/theme/mornye_theme.dart';
import 'package:spotiflac_android/utils/widget_capture.dart';
import 'package:spotiflac_android/widgets/app_action_button.dart';
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
import 'package:spotiflac_android/widgets/app_content_card.dart';
+6 -11
View File
@@ -137,17 +137,12 @@ class _RewindStoryState extends State<_RewindStory>
final pixelRatio = MediaQuery.devicePixelRatioOf(context);
setState(() => _sharing = true);
try {
final boundary =
_captureKey.currentContext?.findRenderObject()
as RenderRepaintBoundary?;
if (boundary == null) return;
final image = await boundary.toImage(pixelRatio: pixelRatio);
final bytes = await image.toByteData(format: ImageByteFormat.png);
image.dispose();
if (bytes == null) return;
final directory = await getTemporaryDirectory();
final file = File('${directory.path}/spotiflac_rewind.png');
await file.writeAsBytes(bytes.buffer.asUint8List(), flush: true);
final file = await captureBoundaryToPngFile(
_captureKey,
pixelRatio: pixelRatio,
fileName: 'spotiflac_rewind.png',
);
if (file == null) return;
await SharePlus.instance.share(
ShareParams(
files: [XFile(file.path, mimeType: 'image/png')],
+8 -14
View File
@@ -1,17 +1,15 @@
import 'dart:io';
import 'dart:ui' show ImageByteFormat, ImageFilter;
import 'dart:ui' show ImageFilter;
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show RenderRepaintBoundary;
import 'package:material_3_expressive/material_3_expressive.dart'
show M3EShapeKind;
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart' show ShareParams, SharePlus, XFile;
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/theme/app_tokens.dart';
import 'package:spotiflac_android/theme/cover_palette.dart';
import 'package:spotiflac_android/utils/lyrics_parser.dart';
import 'package:spotiflac_android/utils/widget_capture.dart';
import 'package:spotiflac_android/widgets/app_icon.dart';
import 'package:spotiflac_android/widgets/app_icon_button.dart';
import 'package:spotiflac_android/widgets/app_shape.dart';
@@ -151,16 +149,12 @@ class _LyricsShareScreenState extends State<LyricsShareScreen> {
// Share is tapped. Capture the current card after that frame finishes.
await WidgetsBinding.instance.endOfFrame;
if (!mounted) return;
final boundary =
_cardKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
if (boundary == null) throw StateError('Card is not laid out');
final image = await boundary.toImage(pixelRatio: 3);
final bytes = await image.toByteData(format: ImageByteFormat.png);
image.dispose();
if (bytes == null) throw StateError('Card could not be encoded');
final directory = await getTemporaryDirectory();
final file = File('${directory.path}/spotiflac_lyrics.png');
await file.writeAsBytes(bytes.buffer.asUint8List(), flush: true);
final file = await captureBoundaryToPngFile(
_cardKey,
pixelRatio: 3,
fileName: 'spotiflac_lyrics.png',
);
if (file == null) throw StateError('Card could not be captured');
await SharePlus.instance.share(
ShareParams(
files: [XFile(file.path, mimeType: 'image/png')],
+29
View File
@@ -0,0 +1,29 @@
import 'dart:io';
import 'dart:ui' show ImageByteFormat;
import 'package:flutter/rendering.dart' show RenderRepaintBoundary;
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart';
/// Renders the [RepaintBoundary] behind [boundaryKey] to a PNG named
/// [fileName] in the temporary directory.
///
/// Returns null when the boundary is not laid out or the image cannot be
/// encoded, so each caller decides how to report that.
Future<File?> captureBoundaryToPngFile(
GlobalKey boundaryKey, {
required double pixelRatio,
required String fileName,
}) async {
final boundary =
boundaryKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
if (boundary == null) return null;
final image = await boundary.toImage(pixelRatio: pixelRatio);
final bytes = await image.toByteData(format: ImageByteFormat.png);
image.dispose();
if (bytes == null) return null;
final directory = await getTemporaryDirectory();
final file = File('${directory.path}/$fileName');
await file.writeAsBytes(bytes.buffer.asUint8List(), flush: true);
return file;
}