163 lines
5.4 KiB
Dart
163 lines
5.4 KiB
Dart
import 'dart:ui' as ui;
|
|
import 'dart:typed_data';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
|
import 'package:lamiter/Class/Screenshot/screenshot_widget.dart';
|
|
import 'package:lamiter/Provider/Diagnosis/Diagnosis_Item/constitution_provider.dart';
|
|
import 'package:lamiter/Provider/Diagnosis/Diagnosis_Item/urban_disease_provider.dart';
|
|
import 'package:lamiter/Provider/Diagnosis/Diagnosis_Item/posture_issue_provider.dart';
|
|
import 'package:lamiter/Provider/Diagnosis/Diagnosis_Item/symptom_provider.dart';
|
|
import 'package:lamiter/Provider/Language/language_provider.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
|
|
class Screenshot {
|
|
final List<ScreenshotWidget> children;
|
|
final double width = 2024;
|
|
|
|
const Screenshot({
|
|
required this.children,
|
|
});
|
|
|
|
Future<void> screenshot(Locale locale) async {
|
|
final pngBytes = await _mergeWidgetsToPng(
|
|
children,
|
|
locale,
|
|
width: width,
|
|
pixelRatio: 1,
|
|
);
|
|
if (pngBytes == null) return;
|
|
await ImageGallerySaver.saveImage(
|
|
pngBytes,
|
|
quality: 60,
|
|
);
|
|
}
|
|
|
|
// 合併多個 widget 的 PNG bytes
|
|
Future<Uint8List?> _mergeWidgetsToPng(
|
|
List<Widget> widgets,
|
|
Locale locale, {
|
|
required double width,
|
|
double pixelRatio = 1.0,
|
|
}) async {
|
|
final images = <ui.Image>[];
|
|
|
|
// 渲染每個 widget 成為圖片
|
|
for (var widget in widgets) {
|
|
final image = await _widgetToImage(widget, width, locale);
|
|
if (image == null) return null;
|
|
images.add(image);
|
|
}
|
|
|
|
// 計算合併後圖像的總高度
|
|
final totalHeight =
|
|
images.fold(0.0, (sum, img) => sum + img.height.toDouble());
|
|
|
|
// 創建畫布
|
|
final recorder = ui.PictureRecorder();
|
|
final canvas = Canvas(
|
|
recorder,
|
|
Rect.fromLTWH(0, 0, width * pixelRatio, totalHeight * pixelRatio),
|
|
);
|
|
|
|
// 將圖像逐一繪製到畫布
|
|
double currentOffset = 0.0;
|
|
for (var image in images) {
|
|
final src =
|
|
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
|
|
final dst = Rect.fromLTWH(0, currentOffset * pixelRatio,
|
|
width * pixelRatio, image.height.toDouble() * pixelRatio);
|
|
canvas.drawImageRect(image, src, dst, Paint());
|
|
currentOffset += image.height.toDouble();
|
|
}
|
|
|
|
// 將畫布轉為圖像
|
|
final mergedImage = await recorder.endRecording().toImage(
|
|
(width * pixelRatio).toInt(), (totalHeight * pixelRatio).toInt());
|
|
|
|
// 將圖像轉為 PNG bytes
|
|
final byteData =
|
|
await mergedImage.toByteData(format: ui.ImageByteFormat.png);
|
|
return byteData?.buffer.asUint8List();
|
|
}
|
|
|
|
// 將 widget 轉換為 PNG
|
|
Future<ui.Image?> _widgetToImage(
|
|
Widget widget, double width, Locale locale) async {
|
|
// 創建一個離屏的 RenderRepaintBoundary
|
|
final repaintBoundary = RenderRepaintBoundary();
|
|
|
|
// 創建一個 RenderView 並將 RepaintBoundary 作為子節點
|
|
final renderView = RenderView(
|
|
child: RenderPositionedBox(
|
|
alignment: Alignment.center,
|
|
child: repaintBoundary,
|
|
),
|
|
configuration: ViewConfiguration(
|
|
physicalConstraints: BoxConstraints(maxWidth: width),
|
|
logicalConstraints: BoxConstraints(maxWidth: width),
|
|
devicePixelRatio: 1.0,
|
|
),
|
|
view: WidgetsBinding.instance.window,
|
|
);
|
|
|
|
// 構建 PipelineOwner
|
|
final pipelineOwner = PipelineOwner();
|
|
renderView.attach(pipelineOwner);
|
|
renderView.prepareInitialFrame();
|
|
// pipelineOwner.rootNode = renderView;
|
|
|
|
// 構建 BuildOwner
|
|
final buildOwner = BuildOwner(focusManager: FocusManager());
|
|
final constitutionProvider = ConstitutionProvider();
|
|
await constitutionProvider.init();
|
|
final postureIssueProvider = PostureIssueProvider();
|
|
await postureIssueProvider.init();
|
|
final symptomProvider = SymptomProvider();
|
|
await symptomProvider.init();
|
|
final urbanDiseaseProvider = UrbanDiseaseProvider();
|
|
await urbanDiseaseProvider.init();
|
|
|
|
final providerWidgetTree = MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider(create: (context) => constitutionProvider),
|
|
ChangeNotifierProvider(create: (context) => postureIssueProvider),
|
|
ChangeNotifierProvider(create: (context) => symptomProvider),
|
|
ChangeNotifierProvider(create: (context) => urbanDiseaseProvider),
|
|
ChangeNotifierProvider(create: (context) => LanguageProvider()),
|
|
],
|
|
child: Localizations(
|
|
delegates: const [
|
|
AppLocalizations.delegate,
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
GlobalCupertinoLocalizations.delegate,
|
|
],
|
|
locale: locale,
|
|
child: widget,
|
|
),
|
|
);
|
|
|
|
final renderElement = RenderObjectToWidgetAdapter<RenderBox>(
|
|
container: repaintBoundary,
|
|
child: providerWidgetTree,
|
|
).attachToRenderTree(buildOwner);
|
|
|
|
// 執行布局和繪製
|
|
buildOwner.buildScope(renderElement);
|
|
pipelineOwner.flushLayout();
|
|
pipelineOwner.flushCompositingBits();
|
|
pipelineOwner.flushPaint();
|
|
|
|
Size size = renderElement.size ?? Size(1, 1);
|
|
final pixelRatio = width / size.width;
|
|
|
|
// 提取圖像
|
|
final image = await repaintBoundary.toImage(pixelRatio: pixelRatio);
|
|
return image;
|
|
}
|
|
}
|