From 839b68ba237d9ea573fdd7698fb18728114a54cb Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Wed, 7 Aug 2024 19:32:24 -0400 Subject: [PATCH] wip --- packages/superdeck/example/lib/main.dart | 2 + .../flutter/generated_plugin_registrant.cc | 4 + .../linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + packages/superdeck/example/macos/Podfile.lock | 6 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + .../components/atoms/cache_image_widget.dart | 8 +- .../lib/components/atoms/slide_thumbnail.dart | 4 +- .../lib/components/atoms/slide_view.dart | 8 +- .../components/molecules/slide_content.dart | 15 +- .../molecules/slide_thumbnail_list.dart | 14 +- .../lib/components/organisms/app_shell.dart | 2 +- .../lib/components/superdeck_app.dart | 23 +- .../lib/providers/snapshot_provider.dart | 39 ++-- .../superdeck/lib/screens/export_screen.dart | 52 +++-- .../lib/services/snapshot_service.dart | 206 ++++++++++-------- .../lib/templates/image_template.dart | 4 +- packages/superdeck/pubspec.yaml | 2 + packages/superdeck/test/test_helpers.dart | 1 - 20 files changed, 211 insertions(+), 186 deletions(-) diff --git a/packages/superdeck/example/lib/main.dart b/packages/superdeck/example/lib/main.dart index d1c5004c..eb5a4faf 100644 --- a/packages/superdeck/example/lib/main.dart +++ b/packages/superdeck/example/lib/main.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:superdeck/superdeck.dart'; import 'src/style.dart'; import 'src/widget/mix_demo.dart'; void main() async { + debugRepaintRainbowEnabled = true; await SuperDeckApp.initialize(); runApp( Builder(builder: (context) { diff --git a/packages/superdeck/example/linux/flutter/generated_plugin_registrant.cc b/packages/superdeck/example/linux/flutter/generated_plugin_registrant.cc index 0cacc750..a5ffb432 100644 --- a/packages/superdeck/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/superdeck/example/linux/flutter/generated_plugin_registrant.cc @@ -6,11 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_saver_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); + file_saver_plugin_register_with_registrar(file_saver_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); diff --git a/packages/superdeck/example/linux/flutter/generated_plugins.cmake b/packages/superdeck/example/linux/flutter/generated_plugins.cmake index 62f151fd..44e8a351 100644 --- a/packages/superdeck/example/linux/flutter/generated_plugins.cmake +++ b/packages/superdeck/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_saver screen_retriever url_launcher_linux window_manager diff --git a/packages/superdeck/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/superdeck/example/macos/Flutter/GeneratedPluginRegistrant.swift index 0370eb95..85004bb7 100644 --- a/packages/superdeck/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/superdeck/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import file_saver import path_provider_foundation import screen_retriever import sqflite @@ -12,6 +13,7 @@ import url_launcher_macos import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) diff --git a/packages/superdeck/example/macos/Podfile.lock b/packages/superdeck/example/macos/Podfile.lock index 25acaf76..ee2c4e79 100644 --- a/packages/superdeck/example/macos/Podfile.lock +++ b/packages/superdeck/example/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - file_saver (0.0.1): + - FlutterMacOS - FlutterMacOS (1.0.0) - path_provider_foundation (0.0.1): - Flutter @@ -14,6 +16,7 @@ PODS: - FlutterMacOS DEPENDENCIES: + - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) @@ -22,6 +25,8 @@ DEPENDENCIES: - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) EXTERNAL SOURCES: + file_saver: + :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos FlutterMacOS: :path: Flutter/ephemeral path_provider_foundation: @@ -36,6 +41,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: + file_saver: 44e6fbf666677faf097302460e214e977fdd977b FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 diff --git a/packages/superdeck/example/windows/flutter/generated_plugin_registrant.cc b/packages/superdeck/example/windows/flutter/generated_plugin_registrant.cc index fe7ec1cb..60dffbe8 100644 --- a/packages/superdeck/example/windows/flutter/generated_plugin_registrant.cc +++ b/packages/superdeck/example/windows/flutter/generated_plugin_registrant.cc @@ -6,11 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSaverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSaverPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/packages/superdeck/example/windows/flutter/generated_plugins.cmake b/packages/superdeck/example/windows/flutter/generated_plugins.cmake index fb2dea6b..db2d0cbc 100644 --- a/packages/superdeck/example/windows/flutter/generated_plugins.cmake +++ b/packages/superdeck/example/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_saver screen_retriever url_launcher_windows window_manager diff --git a/packages/superdeck/lib/components/atoms/cache_image_widget.dart b/packages/superdeck/lib/components/atoms/cache_image_widget.dart index b1f18f8f..1d7ba1ae 100644 --- a/packages/superdeck/lib/components/atoms/cache_image_widget.dart +++ b/packages/superdeck/lib/components/atoms/cache_image_widget.dart @@ -25,9 +25,7 @@ class CacheImage extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedImageSpecWidget( - image: getImageProvider( - url: url, - ), + image: getImageProvider(url), spec: spec.copyWith( fit: fit, alignment: alignment, @@ -63,9 +61,7 @@ class CacheImage extends StatelessWidget { return (width: cacheWidth, height: cacheHeight); } -ImageProvider getImageProvider({ - required String url, -}) { +ImageProvider getImageProvider(String url) { ImageProvider provider; final assets = $superdeck.assets; diff --git a/packages/superdeck/lib/components/atoms/slide_thumbnail.dart b/packages/superdeck/lib/components/atoms/slide_thumbnail.dart index c8294189..258939e9 100644 --- a/packages/superdeck/lib/components/atoms/slide_thumbnail.dart +++ b/packages/superdeck/lib/components/atoms/slide_thumbnail.dart @@ -43,9 +43,7 @@ class SlideThumbnail extends HookWidget { data: (file) { return Image( gaplessPlayback: true, - image: getImageProvider( - url: file.path, - ), + image: getImageProvider(file.path), ); }, loading: () { diff --git a/packages/superdeck/lib/components/atoms/slide_view.dart b/packages/superdeck/lib/components/atoms/slide_view.dart index a0557eb8..ba28123d 100644 --- a/packages/superdeck/lib/components/atoms/slide_view.dart +++ b/packages/superdeck/lib/components/atoms/slide_view.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import '../../providers/slide_provider.dart'; -import '../../providers/snapshot_provider.dart'; import '../../providers/style_provider.dart'; import '../../superdeck.dart'; import 'cache_image_widget.dart'; @@ -20,7 +19,6 @@ class SlideView extends StatelessWidget { final slide = this.slide; final variantStyle = StyleProvider.of(context, slide.style); - final isSnapshot = SnapshotProvider.of(context); final backgroundWidget = slide.background != null ? CacheImage( @@ -30,10 +28,8 @@ class SlideView extends StatelessWidget { ) : const SizedBox(); - final duration = isSnapshot ? Duration.zero : null; - return TransitionWidget( - key: ValueKey(slide.transition?.copyWith(duration: duration)), + key: ValueKey(slide.transition), transition: slide.transition, child: SpecBuilder( style: variantStyle, @@ -42,7 +38,7 @@ class SlideView extends StatelessWidget { return Builder(builder: (context) { return AnimatedBoxSpecWidget( spec: spec.outerContainer, - duration: duration ?? const Duration(milliseconds: 300), + duration: const Duration(milliseconds: 300), child: Stack( children: [ Positioned.fill(child: backgroundWidget), diff --git a/packages/superdeck/lib/components/molecules/slide_content.dart b/packages/superdeck/lib/components/molecules/slide_content.dart index aaf07d53..7de51d4e 100644 --- a/packages/superdeck/lib/components/molecules/slide_content.dart +++ b/packages/superdeck/lib/components/molecules/slide_content.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:mix/mix.dart'; import '../../models/options_model.dart'; -import '../../providers/snapshot_provider.dart'; import '../../styles/style_spec.dart'; import '../atoms/markdown_viewer.dart'; @@ -21,7 +20,6 @@ class SlideContent extends StatelessWidget { Widget build(context) { final alignment = options?.alignment ?? ContentAlignment.center; final spec = SlideSpec.of(context); - final isSnapshot = SnapshotProvider.of(context); Widget child = AnimatedMarkdownViewer( content: content, @@ -29,23 +27,12 @@ class SlideContent extends StatelessWidget { duration: Durations.medium1, ); - if (!isSnapshot) { - child = SingleChildScrollView( - child: child, - ); - } else { - child = Container( - clipBehavior: Clip.hardEdge, - child: child, - ); - } - return AnimatedBoxSpecWidget( duration: const Duration(milliseconds: 300), spec: spec.contentContainer.copyWith( alignment: alignment.toAlignment(), ), - child: child, + child: SingleChildScrollView(child: child), ); } } diff --git a/packages/superdeck/lib/components/molecules/slide_thumbnail_list.dart b/packages/superdeck/lib/components/molecules/slide_thumbnail_list.dart index 835750a9..13f9a3e3 100644 --- a/packages/superdeck/lib/components/molecules/slide_thumbnail_list.dart +++ b/packages/superdeck/lib/components/molecules/slide_thumbnail_list.dart @@ -19,7 +19,7 @@ class SlideThumbnailList extends HookWidget { @override Widget build(BuildContext context) { final navigation = useNavigation(); - final currentSlide = navigation.page; + final currentPage = navigation.page; final slides = useSlides(); final controller = useScrollVisibleController(); final visibleItems = controller.visibleItems; @@ -28,12 +28,12 @@ class SlideThumbnailList extends HookWidget { if (visibleItems.isEmpty) return; final visibleItem = - visibleItems.firstWhereOrNull((e) => e.index == currentSlide); + visibleItems.firstWhereOrNull((e) => e.index == currentPage); double alignment; if (visibleItem == null) { - final isBeginning = visibleItems.first.index > currentSlide; + final isBeginning = visibleItems.first.index > currentPage; alignment = isBeginning ? 0 : 0.7; } else { @@ -48,14 +48,14 @@ class SlideThumbnailList extends HookWidget { } } controller.itemScrollController.scrollTo( - index: currentSlide, + index: currentPage, alignment: alignment, duration: _duration, curve: _curve, ); return; - }, [currentSlide, slides]); + }, [currentPage, slides]); return Scaffold( appBar: PreferredSize( @@ -78,9 +78,9 @@ class SlideThumbnailList extends HookWidget { ], onTap: (index) { if (index == 0) { - navigation.goToPage(currentSlide - 1); + navigation.goToPage(currentPage - 1); } else { - navigation.goToPage(currentSlide + 1); + navigation.goToPage(currentPage + 1); } }, ), diff --git a/packages/superdeck/lib/components/organisms/app_shell.dart b/packages/superdeck/lib/components/organisms/app_shell.dart index a7bc4d47..95315fc6 100644 --- a/packages/superdeck/lib/components/organisms/app_shell.dart +++ b/packages/superdeck/lib/components/organisms/app_shell.dart @@ -106,7 +106,7 @@ class ScaffoldWithNavBar extends HookWidget { toolbarOpacity: animation, actions: [ IconButton( - onPressed: () {}, + onPressed: () => context.go('/export'), icon: const Icon( Icons.picture_as_pdf, ), diff --git a/packages/superdeck/lib/components/superdeck_app.dart b/packages/superdeck/lib/components/superdeck_app.dart index f814911b..6170fe43 100644 --- a/packages/superdeck/lib/components/superdeck_app.dart +++ b/packages/superdeck/lib/components/superdeck_app.dart @@ -12,12 +12,12 @@ import '../../superdeck.dart'; import '../helpers/constants.dart'; import '../helpers/theme.dart'; import '../providers/examples_provider.dart'; +import '../providers/snapshot_provider.dart'; import '../providers/style_provider.dart'; import '../screens/export_screen.dart'; import '../screens/home_screen.dart'; import 'atoms/loading_indicator.dart'; -final kAppKey = GlobalKey(); final _uniqueKey = UniqueKey(); var _initialized = false; @@ -70,17 +70,18 @@ class SuperDeckApp extends HookWidget { title: 'Superdeck', routerConfig: _router, theme: Theme.of(context), - key: kAppKey, builder: (context, child) { - return LoadingOverlay( - isLoading: $superdeck.loading, - key: _uniqueKey, - child: Builder( - builder: (context) { - return $superdeck.completed - ? child! - : const SizedBox(); - }, + return SnapshotProvider( + child: LoadingOverlay( + isLoading: $superdeck.loading, + key: _uniqueKey, + child: Builder( + builder: (context) { + return $superdeck.completed + ? child! + : const SizedBox(); + }, + ), ), ); }, diff --git a/packages/superdeck/lib/providers/snapshot_provider.dart b/packages/superdeck/lib/providers/snapshot_provider.dart index 60410163..86abbe00 100644 --- a/packages/superdeck/lib/providers/snapshot_provider.dart +++ b/packages/superdeck/lib/providers/snapshot_provider.dart @@ -1,23 +1,32 @@ import 'package:flutter/widgets.dart'; -class SnapshotProvider extends InheritedWidget { - const SnapshotProvider({ - super.key, - required this.isSnapshot, - required super.child, - }); +class SnapshotRef { + BuildContext? _context; + + SnapshotRef._(); - final bool isSnapshot; + static final SnapshotRef instance = SnapshotRef._(); + + void setContext(BuildContext context) { + Scrollable.ensureVisible(context); + _context = context; + } - static bool of(BuildContext context) { - return context - .dependOnInheritedWidgetOfExactType() - ?.isSnapshot ?? - false; + BuildContext get context { + assert(_context != null, '$runtimeType has not updated its context'); + return _context!; } +} + +class SnapshotProvider extends StatelessWidget { + final Widget child; + + const SnapshotProvider({ + required this.child, + }); - @override - bool updateShouldNotify(covariant SnapshotProvider oldWidget) { - return isSnapshot != oldWidget.isSnapshot; + Widget build(BuildContext context) { + SnapshotRef.instance.setContext(context); + return child; } } diff --git a/packages/superdeck/lib/screens/export_screen.dart b/packages/superdeck/lib/screens/export_screen.dart index 5949eadd..a87c286c 100644 --- a/packages/superdeck/lib/screens/export_screen.dart +++ b/packages/superdeck/lib/screens/export_screen.dart @@ -1,19 +1,18 @@ import 'dart:developer'; import 'dart:io'; -import 'dart:js_interop'; -import 'package:file_picker/file_picker.dart'; +import 'package:file_saver/file_saver.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; -import 'package:web/web.dart' as web; import '../../helpers/constants.dart'; import '../components/atoms/linear_progresss_indicator_widget.dart'; import '../components/atoms/slide_view.dart'; import '../components/molecules/scaled_app.dart'; +import '../helpers/hooks.dart'; import '../services/snapshot_service.dart'; import '../superdeck.dart'; @@ -157,13 +156,12 @@ class ExportingProcessScreen extends HookWidget { final startConversion = useCallback(() async { try { - final generator = SnapshotService.instance; status.value = ExportProcessStatus.converting; List> futures = []; Future convertSlide(Slide slide) async { - final convertedImage = await generator.generate( + final convertedImage = await SnapshotService.instance.generate( quality: quality, slide: slide, ); @@ -187,27 +185,29 @@ class ExportingProcessScreen extends HookWidget { status.value = ExportProcessStatus.complete; await Future.delayed(Durations.short1); - if (kIsWeb) { - // Create a Blob from the PDF bytes - final blob = web.Blob( - [pdf.toJS].toJS, web.BlobPropertyBag(type: 'application/pdf')); + final pdfFileName = 'superdeck'; - // Create a URL for the Blob - final url = web.URL.createObjectURL(blob); + // if (kIsWeb) { + // // Create a Blob from the PDF bytes + // final blob = web.Blob([pdf.toJS].toJS, + // web.BlobPropertyBag(type: 'application/pdf')); - web.HTMLAnchorElement() - ..href = url - ..setAttribute('download', 'superdeck.pdf') - ..click(); + // // Create a URL for the Blob + // final url = web.URL.createObjectURL(blob); - return; - } + // web.HTMLAnchorElement() + // ..href = url + // ..setAttribute('download', pdfFileName) + // ..click(); + + // return; + // } - final outputFile = await FilePicker.platform.saveFile( - dialogTitle: 'Save PDF', - fileName: 'superdeck.pdf', - type: FileType.custom, - allowedExtensions: ['pdf'], + final outputFile = await FileSaver.instance.saveAs( + name: pdfFileName, + bytes: pdf, + ext: 'pdf', + mimeType: MimeType.pdf, ); if (outputFile != null) { @@ -222,12 +222,12 @@ class ExportingProcessScreen extends HookWidget { } }, []); - useEffect(() { + usePostFrameEffect(() { startConversion(); return null; }, []); - useEffect(() { + useUpdateEffect(() { if (images.value.length == slides.length) { pageController.jumpToPage(0); } else { @@ -272,9 +272,7 @@ class ExportingProcessScreen extends HookWidget { return [ Container( - decoration: const BoxDecoration( - color: Colors.black, - ), + color: Colors.black, height: 225, width: 400, child: PageView.builder( diff --git a/packages/superdeck/lib/services/snapshot_service.dart b/packages/superdeck/lib/services/snapshot_service.dart index 804ad600..1aa94bb0 100644 --- a/packages/superdeck/lib/services/snapshot_service.dart +++ b/packages/superdeck/lib/services/snapshot_service.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:developer'; +import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/material.dart'; @@ -41,24 +42,25 @@ class SnapshotService { }) async { final queueKey = '${slide.key}_${quality.name.toLowerCase()}'; try { - while (_generationQueue.length > _maxConcurrentGenerations) { - await Future.delayed(const Duration(milliseconds: 100)); + while (_generationQueue.length >= _maxConcurrentGenerations) { + await Future.delayed(Duration( + milliseconds: math.Random().nextInt(100), + )); } _generationQueue.add(queueKey); + print('Generating image for slide: ${slide.key}'); - final image = await _fromWidgetToImage( + return await _fromWidgetToImage( SlideView(slide), - context: kAppKey.currentContext!, pixelRatio: quality.pixelRatio, targetSize: kResolution, ); - - return _imageToUint8List(image); } catch (e, stackTrace) { log('Error generating image: $e', stackTrace: stackTrace); rethrow; } finally { + print('Finished generating image for slide: ${slide.key}'); _generationQueue.remove(queueKey); } } @@ -79,117 +81,137 @@ class SnapshotService { return byteData!.buffer.asUint8List(); } - Future _fromWidgetToImage( + Future _fromWidgetToImage( Widget widget, { required double pixelRatio, - required BuildContext context, Size? targetSize, }) async { - try { - Widget child = widget; + return offStage( + widget, + context: SnapshotRef.instance.context, + pixelRatio: pixelRatio, + ); + } +} - child = StyleProvider.inherit( +Future offStage( + Widget widget, { + Duration? wait, + double? pixelRatio, + required BuildContext context, +}) async { + /// finding the widget in the current context by the key. + final repaintBoundary = RenderRepaintBoundary(); + + bool _needsUpdate = false; + + /// create a new pipeline owner + final pipelineOwner = PipelineOwner( + onNeedVisualUpdate: () => _needsUpdate = true, + ); + + /// create a new build owner + final buildOwner = BuildOwner(focusManager: FocusManager()); + + final logicalSize = + View.of(context).physicalSize / View.of(context).devicePixelRatio; + pixelRatio ??= View.of(context).devicePixelRatio; + + final renderView = RenderView( + view: View.of(context), + child: RenderPositionedBox( + alignment: Alignment.center, child: repaintBoundary), + configuration: ViewConfiguration( + logicalConstraints: BoxConstraints( + maxWidth: logicalSize.width, + maxHeight: logicalSize.height, + ), + physicalConstraints: BoxConstraints( + maxWidth: logicalSize.width, + maxHeight: logicalSize.height, + ), + devicePixelRatio: 1.0, + ), + ); + + /// setting the rootNode to the renderview of the widget + pipelineOwner.rootNode = renderView; + + /// setting the renderView to prepareInitialFrame + renderView.prepareInitialFrame(); + + /// setting the rootElement with the widget that has to be captured + final rootElement = RenderObjectToWidgetAdapter( + container: repaintBoundary, + child: Directionality( + textDirection: TextDirection.ltr, + child: StyleProvider.inherit( context: context, child: AssetsProvider.inherit( context: context, child: ExamplesProvider.inherit( context: context, - child: SnapshotProvider( - isSnapshot: true, - child: InheritedTheme.captureAll( - context, - MediaQuery( - data: MediaQuery.of(context), - child: MaterialApp( - debugShowCheckedModeBanner: false, - theme: Theme.of(context), - color: Colors.transparent, - home: Scaffold(body: child), - ), + child: InheritedTheme.captureAll( + context, + MediaQuery( + data: MediaQuery.of(context), + child: MaterialApp( + debugShowCheckedModeBanner: false, + theme: Theme.of(context), + color: Colors.transparent, + home: Scaffold(body: widget), ), ), ), ), ), - ); - - final repaintBoundary = RenderRepaintBoundary(); - final platformDispatcher = WidgetsBinding.instance.platformDispatcher; - - final view = View.maybeOf(context) ?? platformDispatcher.views.first; - final logicalSize = - targetSize ?? view.physicalSize / view.devicePixelRatio; - - int retryCount = 10; - bool isDirty = false; - - final renderView = RenderView( - view: view, - child: RenderPositionedBox( - alignment: Alignment.center, - child: repaintBoundary, - ), - configuration: ViewConfiguration( - logicalConstraints: BoxConstraints( - maxWidth: logicalSize.width, - maxHeight: logicalSize.height, - ), - devicePixelRatio: pixelRatio, - ), - ); + ), + ), + ).attachToRenderTree(buildOwner); - final pipelineOwner = PipelineOwner( - onNeedVisualUpdate: () { - isDirty = true; - }, - ); + ///adding the rootElement to the buildScope + buildOwner.buildScope(rootElement); - final buildOwner = BuildOwner( - focusManager: FocusManager(), - onBuildScheduled: () { - isDirty = true; - }, - ); + /// finialize the buildOwner + buildOwner.finalizeTree(); - pipelineOwner.rootNode = renderView; - renderView.prepareInitialFrame(); + ///Flush Layout + pipelineOwner.flushLayout(); - final rootElement = RenderObjectToWidgetAdapter( - container: repaintBoundary, - child: Directionality( - textDirection: TextDirection.ltr, - child: child, - ), - ).attachToRenderTree(buildOwner); + /// Flush Compositing Bits + pipelineOwner.flushCompositingBits(); - while (retryCount > 0) { - isDirty = false; - buildOwner.buildScope(rootElement); - buildOwner.finalizeTree(); - pipelineOwner.flushLayout(); - pipelineOwner.flushCompositingBits(); - pipelineOwner.flushPaint(); + pipelineOwner.flushSemantics(); - await Future.delayed(const Duration(milliseconds: 250)); + /// Flush paint + pipelineOwner.flushPaint(); - if (!isDirty) { - log('Image generation completed.'); - break; - } + await Future.delayed(const Duration(milliseconds: 100)); + while (_needsUpdate) { + _needsUpdate = false; + await Future.delayed(const Duration(milliseconds: 250)); + } - log('Image generation.. waiting...'); + /// we start the createImageProcess once we have the repaintBoundry of + /// the widget we attached to the widget tree. + return await _createImageProcess( + repaintBoundary: repaintBoundary, + pixelRatio: pixelRatio, + ); +} - retryCount--; - } +/// create image process +Future _createImageProcess({ + required RenderRepaintBoundary repaintBoundary, + double? pixelRatio, +}) async { + // the boundary is converted to Image. - final image = await repaintBoundary.toImage(pixelRatio: pixelRatio); + final image = await repaintBoundary.toImage(pixelRatio: pixelRatio!); - buildOwner.finalizeTree(); + /// The raw image is converted to byte data. + final byteData = await image.toByteData(format: ui.ImageByteFormat.png); - return image; - } catch (e) { - log('Error finalizing tree: $e'); - rethrow; - } - } + /// The byteData is converted to uInt8List image aka memory Image. + return byteData!.buffer.asUint8List(); } diff --git a/packages/superdeck/lib/templates/image_template.dart b/packages/superdeck/lib/templates/image_template.dart index cc2bc156..76a12b5f 100644 --- a/packages/superdeck/lib/templates/image_template.dart +++ b/packages/superdeck/lib/templates/image_template.dart @@ -38,9 +38,7 @@ class ImageTemplate extends SplitTemplateBuilder { alignment: spec.image.alignment, decoration: BoxDecoration( image: DecorationImage( - image: getImageProvider( - url: src, - ), + image: getImageProvider(src), centerSlice: spec.image.centerSlice, repeat: spec.image.repeat ?? ImageRepeat.noRepeat, filterQuality: spec.image.filterQuality ?? FilterQuality.low, diff --git a/packages/superdeck/pubspec.yaml b/packages/superdeck/pubspec.yaml index f442095c..817ba091 100644 --- a/packages/superdeck/pubspec.yaml +++ b/packages/superdeck/pubspec.yaml @@ -32,6 +32,8 @@ dependencies: markdown: ^7.2.2 flutter_hooks: ^0.20.5 web: ^0.5.1 + file_saver: ^0.2.13 + render: ^0.0.1 dev_dependencies: flutter_test: diff --git a/packages/superdeck/test/test_helpers.dart b/packages/superdeck/test/test_helpers.dart index 71c742d2..76b6ce5c 100644 --- a/packages/superdeck/test/test_helpers.dart +++ b/packages/superdeck/test/test_helpers.dart @@ -21,7 +21,6 @@ extension WidgetTesterX on WidgetTester { }) async { return pumpWithScaffold( SnapshotProvider( - isSnapshot: isSnapshot, child: StyleProvider( baseStyle: style, child: AssetsProvider(