From 53cfdd41e16adf698bfd8c64325fb22045e096c7 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Dec 2024 21:29:05 +0500 Subject: [PATCH 1/8] Added ScreenshotContainer --- .../lib/widget/screenshot_container.dart | 194 ++++++++++++++++++ .../ensemble/lib/widget/widget_registry.dart | 2 + 2 files changed, 196 insertions(+) create mode 100644 modules/ensemble/lib/widget/screenshot_container.dart diff --git a/modules/ensemble/lib/widget/screenshot_container.dart b/modules/ensemble/lib/widget/screenshot_container.dart new file mode 100644 index 000000000..a8e9d4d0d --- /dev/null +++ b/modules/ensemble/lib/widget/screenshot_container.dart @@ -0,0 +1,194 @@ +import 'dart:typed_data'; +import 'dart:io'; +import 'package:ensemble/framework/action.dart'; +import 'package:ensemble/framework/event.dart'; +import 'package:ensemble/framework/widget/widget.dart'; +import 'package:ensemble/screen_controller.dart'; +import 'package:ensemble/util/utils.dart'; +import 'package:ensemble/widget/helpers/box_wrapper.dart'; +import 'package:ensemble/widget/helpers/controllers.dart'; +import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; +import 'package:flutter/material.dart'; +import 'package:screenshot/screenshot.dart'; + +class ScreenshotContainer extends StatefulWidget + with + Invokable, + HasController { + static const type = 'ScreenshotContainer'; + + ScreenshotContainer({Key? key}) : super(key: key); + + final ScreenshotContainerController _controller = + ScreenshotContainerController(); + + @override + ScreenshotContainerController get controller => _controller; + + ScreenshotContainerState? _state; + + @override + ScreenshotContainerState? get state => _state; + + set state(covariant ScreenshotContainerState? state) { + _state = state; + } + + @override + State createState() => ScreenshotContainerState(); + + @override + Map getters() { + return { + 'id': () => _controller.id, + 'widget': () => _controller.widget, + }; + } + + @override + Map methods() { + return { + 'capture': () async { + try { + // Ensure state is not null + final Uint8List? image = await state?.captureScreenshot(); + // state?.saveImageToDisk(image, 'screenshot.png'); + print(image); + if (image != null) { + // Success payload + Map successPayload = { + 'image': image, + 'success': true, + }; + + // Execute onCapture action + if (_controller.onCapture != null) { + await ScreenController().executeAction( + state!.context, + _controller.onCapture!, + event: EnsembleEvent(this, data: successPayload), + ); + } + } + return image; + } catch (e) { + print('Error capturing screenshot: $e'); + + // Error payload + Map errorPayload = { + 'success': false, + 'error': e.toString(), + }; + + if (_controller.onCapture != null) { + await ScreenController().executeAction( + state!.context, + _controller.onCapture!, + event: EnsembleEvent(this, data: errorPayload), + ); + } + } + return null; + }, + }; + } + + @override + Map setters() { + return { + 'id': (value) => _controller.id = Utils.optionalString(value), + 'widget': (widget) => _controller.widget = widget, + 'onCapture': (funcDefinition) => _controller.onCapture = + EnsembleAction.from(funcDefinition, initiator: this), + }; + } +} + +class ScreenshotContainerController extends BoxController { + String? _id; + dynamic _widget; + EnsembleAction? onCapture; + + String? get id => _id; + set id(String? value) { + _id = value; + notifyListeners(); + } + + dynamic get widget => _widget; + set widget(dynamic value) { + _widget = value; + notifyListeners(); + } +} + +class ScreenshotContainerState extends EWidgetState { + final ScreenshotController _screenshotController = ScreenshotController(); + + @override + void initState() { + super.initState(); + widget.state = this; // Properly link state to the widget + } + + @override + Widget buildWidget(BuildContext context) { + // If no widget is provided, return an empty container + if (widget._controller.widget == null) { + return Container(); + } + + // Build the child widget + Widget childWidget = scopeManager != null + ? scopeManager!.buildWidgetFromDefinition(widget._controller.widget) + : Container(); + + // Wrap the child widget with Screenshot widget + Widget screenshotWidget = Screenshot( + controller: _screenshotController, + child: childWidget, + ); + + // Wrap with BoxWrapper for standard box properties + return BoxWrapper( + widget: screenshotWidget, + boxController: widget._controller, + ); + } + + /// Save Uint8List to a file + // Future saveImageToDisk(Uint8List? imageBytes, String fileName) async { + // try { + // // Get the current working directory + // final String directoryPath = Directory.current.path; + + // // Define the full path where the image will be saved + // final String filePath = 'D:/Ensemble/ABHI/fileName.png'; + + // // Write the image bytes to the file + // final File file = File(filePath); + // await file.writeAsBytes(imageBytes as List); + + // print('Image saved at: $filePath'); + // return filePath; // Return the saved file path + // } catch (e) { + // print('Error saving image: $e'); + // rethrow; + // } + // } + + Future captureScreenshot() async { + try { + await Future.delayed(Duration(milliseconds: 100)); // Optional delay + final Uint8List? image = await _screenshotController.capture(); + // return Image.memory( + // image?.buffer.asUint8List() ?? Uint8List(0), + // fit: BoxFit.contain, + // ); + return image; + } catch (e) { + print('Error capturing screenshot: $e'); + return null; + } + } +} diff --git a/modules/ensemble/lib/widget/widget_registry.dart b/modules/ensemble/lib/widget/widget_registry.dart index bb364cf2a..fdfbc8874 100644 --- a/modules/ensemble/lib/widget/widget_registry.dart +++ b/modules/ensemble/lib/widget/widget_registry.dart @@ -70,6 +70,7 @@ import 'package:ensemble/widget/visualization/topology_chart.dart'; import 'package:ensemble/widget/webview/webview.dart'; import 'package:ensemble/widget/youtube/youtube.dart'; import 'package:ensemble/widget/weeklyscheduler.dart'; +import 'package:ensemble/widget/screenshot_container.dart'; import 'package:get_it/get_it.dart'; import 'fintech/tabapayconnect.dart'; @@ -183,6 +184,7 @@ class WidgetRegistry { AppScroller.type: () => AppScroller(), LoadingContainer.type: () => LoadingContainer(), EnsembleSlidable.type: () => EnsembleSlidable(), + ScreenshotContainer.type: () => ScreenshotContainer(), // charts Highcharts.type: () => Highcharts(), From 9b2e680af7163dd57d483efbbed0dfce8feda16f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 3 Jan 2025 02:39:27 +0500 Subject: [PATCH 2/8] added takeScreenshot action --- .../ensemble/lib/action/action_invokable.dart | 1 + .../ensemble/lib/action/take_screenshot.dart | 116 ++++++++++++++++++ modules/ensemble/lib/framework/action.dart | 4 + 3 files changed, 121 insertions(+) create mode 100644 modules/ensemble/lib/action/take_screenshot.dart diff --git a/modules/ensemble/lib/action/action_invokable.dart b/modules/ensemble/lib/action/action_invokable.dart index dea90b0de..c030e5bf4 100644 --- a/modules/ensemble/lib/action/action_invokable.dart +++ b/modules/ensemble/lib/action/action_invokable.dart @@ -39,6 +39,7 @@ abstract class ActionInvokable with Invokable { ActionType.dismissDialog, ActionType.closeAllDialogs, ActionType.executeActionGroup, + ActionType.takeScreenshot ]); } diff --git a/modules/ensemble/lib/action/take_screenshot.dart b/modules/ensemble/lib/action/take_screenshot.dart new file mode 100644 index 000000000..950466347 --- /dev/null +++ b/modules/ensemble/lib/action/take_screenshot.dart @@ -0,0 +1,116 @@ +import 'dart:typed_data'; +import 'dart:io'; +import 'package:ensemble/framework/action.dart'; +import 'package:ensemble/framework/error_handling.dart'; +import 'package:ensemble/framework/extensions.dart'; +import 'package:ensemble/framework/event.dart'; +import 'package:ensemble/screen_controller.dart'; +import 'package:ensemble/framework/scope.dart'; +import 'package:ensemble/util/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:screenshot/screenshot.dart'; + +class TakeScreenshotAction extends EnsembleAction { + TakeScreenshotAction({ + super.initiator, + required this.widgetId, + this.pixelRatio, + this.onSuccess, + this.onFailure, + }); + + final dynamic widgetId; + final double? pixelRatio; + final EnsembleAction? onSuccess; + final EnsembleAction? onFailure; + + factory TakeScreenshotAction.fromYaml({Map? payload}) { + if (payload == null || payload['widgetId'] == null) { + throw LanguageError( + "${ActionType.takeScreenshot.name} requires 'widgetId'"); + } + return TakeScreenshotAction( + widgetId: payload['widgetId'], + pixelRatio: Utils.optionalDouble(payload['options']?['pixelRatio']), + onSuccess: payload['onSuccess'] != null + ? EnsembleAction.from(payload['onSuccess']) + : null, + onFailure: payload['onFailure'] != null + ? EnsembleAction.from(payload['onFailure']) + : null, + ); + } + + factory TakeScreenshotAction.fromMap(dynamic inputs) => + TakeScreenshotAction.fromYaml(payload: Utils.getYamlMap(inputs)); + + @override + Future execute(BuildContext context, ScopeManager scopeManager) async { + final screenshotController = ScreenshotController(); + + try { + final resolvedWidget = scopeManager.dataContext.eval(widgetId); + if (resolvedWidget == null) { + throw LanguageError("Widget not found: '$widgetId'"); + } + + final widget = Screenshot( + controller: screenshotController, + child: resolvedWidget, + ); + + final Uint8List? capturedImage = + await screenshotController.captureFromWidget( + MediaQuery( + data: MediaQuery.of(context), + child: Theme( + data: Theme.of(context), + child: Material( + child: widget, + )), + ), + delay: const Duration(milliseconds: 100), + pixelRatio: pixelRatio ?? MediaQuery.of(context).devicePixelRatio, + context: context, + ); + + if (capturedImage == null) { + throw LanguageError("Failed to capture screenshot"); + } + + final filePath = await _saveImageToFile(capturedImage); + + if (onSuccess != null) { + await ScreenController().executeAction( + context, + onSuccess!, + event: EnsembleEvent(initiator, data: {'filePath': filePath}), + ); + } + } catch (e) { + if (onFailure != null) { + await ScreenController().executeAction( + context, + onFailure!, + event: EnsembleEvent(initiator, data: {'error': e.toString()}), + ); + } + rethrow; + } + } + + Future _saveImageToFile(Uint8List imageBytes) async { + try { + final directory = Directory('/storage/emulated/0/DCIM/Pictures'); + await directory.create(recursive: true); + + final filePath = + '${directory.path}/screenshot_${DateTime.now().millisecondsSinceEpoch}.png'; + await File(filePath).writeAsBytes(imageBytes); + + return filePath; + } catch (e) { + throw LanguageError("Failed to save screenshot: $e"); + } + } +} diff --git a/modules/ensemble/lib/framework/action.dart b/modules/ensemble/lib/framework/action.dart index 0a049f45d..4fe3bf652 100644 --- a/modules/ensemble/lib/framework/action.dart +++ b/modules/ensemble/lib/framework/action.dart @@ -21,6 +21,7 @@ import 'package:ensemble/action/notification_actions.dart'; import 'package:ensemble/action/phone_contact_action.dart'; import 'package:ensemble/action/sign_in_out_action.dart'; import 'package:ensemble/action/toast_actions.dart'; +import 'package:ensemble/action/take_screenshot.dart'; import 'package:ensemble/ensemble.dart'; import 'package:ensemble/framework/data_context.dart'; import 'package:ensemble/framework/error_handling.dart'; @@ -1033,6 +1034,7 @@ enum ActionType { dispatchEvent, executeConditionalAction, executeActionGroup, + takeScreenshot, playAudio, stopAudio, pauseAudio, @@ -1153,6 +1155,8 @@ abstract class EnsembleAction { return FilePickerAction.fromYaml(payload: payload); } else if (actionType == ActionType.openUrl) { return OpenUrlAction.fromYaml(payload: payload); + } else if (actionType == ActionType.takeScreenshot) { + return TakeScreenshotAction.fromYaml(payload: payload); } else if (actionType == ActionType.connectWallet) { return WalletConnectAction.fromYaml(payload: payload); } else if (actionType == ActionType.authorizeOAuthService) { From 8f7ba294f34298aac3d63ffb1f1a73839593eccd Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 6 Jan 2025 15:53:52 +0500 Subject: [PATCH 3/8] Added screenshot package --- modules/ensemble/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ensemble/pubspec.yaml b/modules/ensemble/pubspec.yaml index a0fe6a2d6..fb9082898 100644 --- a/modules/ensemble/pubspec.yaml +++ b/modules/ensemble/pubspec.yaml @@ -109,6 +109,7 @@ dependencies: web_socket_client: ^0.1.0 app_links: ^6.3.2 share_plus: ^10.0.3 + screenshot: ^3.0.0 rate_my_app: ^2.0.0 table_calendar: git: From 32e53850c041d454b1191e7c13578e3132cf3bd7 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Mon, 6 Jan 2025 21:35:00 +0500 Subject: [PATCH 4/8] updated screenshot implementation --- .../ensemble/lib/action/take_screenshot.dart | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/modules/ensemble/lib/action/take_screenshot.dart b/modules/ensemble/lib/action/take_screenshot.dart index 950466347..b645a6a16 100644 --- a/modules/ensemble/lib/action/take_screenshot.dart +++ b/modules/ensemble/lib/action/take_screenshot.dart @@ -1,9 +1,8 @@ import 'dart:typed_data'; -import 'dart:io'; import 'package:ensemble/framework/action.dart'; import 'package:ensemble/framework/error_handling.dart'; -import 'package:ensemble/framework/extensions.dart'; import 'package:ensemble/framework/event.dart'; +import 'package:ensemble/framework/view/data_scope_widget.dart'; import 'package:ensemble/screen_controller.dart'; import 'package:ensemble/framework/scope.dart'; import 'package:ensemble/util/utils.dart'; @@ -31,7 +30,7 @@ class TakeScreenshotAction extends EnsembleAction { } return TakeScreenshotAction( widgetId: payload['widgetId'], - pixelRatio: Utils.optionalDouble(payload['options']?['pixelRatio']), + pixelRatio: Utils.optionalDouble(payload['pixelRatio']), onSuccess: payload['onSuccess'] != null ? EnsembleAction.from(payload['onSuccess']) : null, @@ -41,9 +40,6 @@ class TakeScreenshotAction extends EnsembleAction { ); } - factory TakeScreenshotAction.fromMap(dynamic inputs) => - TakeScreenshotAction.fromYaml(payload: Utils.getYamlMap(inputs)); - @override Future execute(BuildContext context, ScopeManager scopeManager) async { final screenshotController = ScreenshotController(); @@ -56,20 +52,22 @@ class TakeScreenshotAction extends EnsembleAction { final widget = Screenshot( controller: screenshotController, - child: resolvedWidget, + child: DataScopeWidget( + scopeManager: scopeManager, + child: resolvedWidget, + ), ); final Uint8List? capturedImage = - await screenshotController.captureFromWidget( - MediaQuery( - data: MediaQuery.of(context), - child: Theme( - data: Theme.of(context), - child: Material( - child: widget, - )), + await screenshotController.captureFromLongWidget( + InheritedTheme.captureAll( + context, + Material( + type: MaterialType.transparency, + child: widget, + ), ), - delay: const Duration(milliseconds: 100), + delay: const Duration(milliseconds: 1000), pixelRatio: pixelRatio ?? MediaQuery.of(context).devicePixelRatio, context: context, ); @@ -78,13 +76,17 @@ class TakeScreenshotAction extends EnsembleAction { throw LanguageError("Failed to capture screenshot"); } - final filePath = await _saveImageToFile(capturedImage); + final dimensions = await _getImageDimensions(capturedImage); if (onSuccess != null) { await ScreenController().executeAction( context, onSuccess!, - event: EnsembleEvent(initiator, data: {'filePath': filePath}), + event: EnsembleEvent(initiator, data: { + 'imageData': capturedImage, + 'size': capturedImage.length, + 'dimensions': dimensions, + }), ); } } catch (e) { @@ -99,18 +101,11 @@ class TakeScreenshotAction extends EnsembleAction { } } - Future _saveImageToFile(Uint8List imageBytes) async { - try { - final directory = Directory('/storage/emulated/0/DCIM/Pictures'); - await directory.create(recursive: true); - - final filePath = - '${directory.path}/screenshot_${DateTime.now().millisecondsSinceEpoch}.png'; - await File(filePath).writeAsBytes(imageBytes); - - return filePath; - } catch (e) { - throw LanguageError("Failed to save screenshot: $e"); - } + Future> _getImageDimensions(Uint8List imageData) async { + final image = await decodeImageFromList(imageData); + return { + 'width': image.width, + 'height': image.height, + }; } } From bc99b17398a05cee61bb3c7258fff17c1e88461f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 7 Jan 2025 14:26:42 +0500 Subject: [PATCH 5/8] removed screenshot container widget --- .../lib/widget/screenshot_container.dart | 194 ------------------ .../ensemble/lib/widget/widget_registry.dart | 2 - 2 files changed, 196 deletions(-) delete mode 100644 modules/ensemble/lib/widget/screenshot_container.dart diff --git a/modules/ensemble/lib/widget/screenshot_container.dart b/modules/ensemble/lib/widget/screenshot_container.dart deleted file mode 100644 index a8e9d4d0d..000000000 --- a/modules/ensemble/lib/widget/screenshot_container.dart +++ /dev/null @@ -1,194 +0,0 @@ -import 'dart:typed_data'; -import 'dart:io'; -import 'package:ensemble/framework/action.dart'; -import 'package:ensemble/framework/event.dart'; -import 'package:ensemble/framework/widget/widget.dart'; -import 'package:ensemble/screen_controller.dart'; -import 'package:ensemble/util/utils.dart'; -import 'package:ensemble/widget/helpers/box_wrapper.dart'; -import 'package:ensemble/widget/helpers/controllers.dart'; -import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; -import 'package:flutter/material.dart'; -import 'package:screenshot/screenshot.dart'; - -class ScreenshotContainer extends StatefulWidget - with - Invokable, - HasController { - static const type = 'ScreenshotContainer'; - - ScreenshotContainer({Key? key}) : super(key: key); - - final ScreenshotContainerController _controller = - ScreenshotContainerController(); - - @override - ScreenshotContainerController get controller => _controller; - - ScreenshotContainerState? _state; - - @override - ScreenshotContainerState? get state => _state; - - set state(covariant ScreenshotContainerState? state) { - _state = state; - } - - @override - State createState() => ScreenshotContainerState(); - - @override - Map getters() { - return { - 'id': () => _controller.id, - 'widget': () => _controller.widget, - }; - } - - @override - Map methods() { - return { - 'capture': () async { - try { - // Ensure state is not null - final Uint8List? image = await state?.captureScreenshot(); - // state?.saveImageToDisk(image, 'screenshot.png'); - print(image); - if (image != null) { - // Success payload - Map successPayload = { - 'image': image, - 'success': true, - }; - - // Execute onCapture action - if (_controller.onCapture != null) { - await ScreenController().executeAction( - state!.context, - _controller.onCapture!, - event: EnsembleEvent(this, data: successPayload), - ); - } - } - return image; - } catch (e) { - print('Error capturing screenshot: $e'); - - // Error payload - Map errorPayload = { - 'success': false, - 'error': e.toString(), - }; - - if (_controller.onCapture != null) { - await ScreenController().executeAction( - state!.context, - _controller.onCapture!, - event: EnsembleEvent(this, data: errorPayload), - ); - } - } - return null; - }, - }; - } - - @override - Map setters() { - return { - 'id': (value) => _controller.id = Utils.optionalString(value), - 'widget': (widget) => _controller.widget = widget, - 'onCapture': (funcDefinition) => _controller.onCapture = - EnsembleAction.from(funcDefinition, initiator: this), - }; - } -} - -class ScreenshotContainerController extends BoxController { - String? _id; - dynamic _widget; - EnsembleAction? onCapture; - - String? get id => _id; - set id(String? value) { - _id = value; - notifyListeners(); - } - - dynamic get widget => _widget; - set widget(dynamic value) { - _widget = value; - notifyListeners(); - } -} - -class ScreenshotContainerState extends EWidgetState { - final ScreenshotController _screenshotController = ScreenshotController(); - - @override - void initState() { - super.initState(); - widget.state = this; // Properly link state to the widget - } - - @override - Widget buildWidget(BuildContext context) { - // If no widget is provided, return an empty container - if (widget._controller.widget == null) { - return Container(); - } - - // Build the child widget - Widget childWidget = scopeManager != null - ? scopeManager!.buildWidgetFromDefinition(widget._controller.widget) - : Container(); - - // Wrap the child widget with Screenshot widget - Widget screenshotWidget = Screenshot( - controller: _screenshotController, - child: childWidget, - ); - - // Wrap with BoxWrapper for standard box properties - return BoxWrapper( - widget: screenshotWidget, - boxController: widget._controller, - ); - } - - /// Save Uint8List to a file - // Future saveImageToDisk(Uint8List? imageBytes, String fileName) async { - // try { - // // Get the current working directory - // final String directoryPath = Directory.current.path; - - // // Define the full path where the image will be saved - // final String filePath = 'D:/Ensemble/ABHI/fileName.png'; - - // // Write the image bytes to the file - // final File file = File(filePath); - // await file.writeAsBytes(imageBytes as List); - - // print('Image saved at: $filePath'); - // return filePath; // Return the saved file path - // } catch (e) { - // print('Error saving image: $e'); - // rethrow; - // } - // } - - Future captureScreenshot() async { - try { - await Future.delayed(Duration(milliseconds: 100)); // Optional delay - final Uint8List? image = await _screenshotController.capture(); - // return Image.memory( - // image?.buffer.asUint8List() ?? Uint8List(0), - // fit: BoxFit.contain, - // ); - return image; - } catch (e) { - print('Error capturing screenshot: $e'); - return null; - } - } -} diff --git a/modules/ensemble/lib/widget/widget_registry.dart b/modules/ensemble/lib/widget/widget_registry.dart index fdfbc8874..bb364cf2a 100644 --- a/modules/ensemble/lib/widget/widget_registry.dart +++ b/modules/ensemble/lib/widget/widget_registry.dart @@ -70,7 +70,6 @@ import 'package:ensemble/widget/visualization/topology_chart.dart'; import 'package:ensemble/widget/webview/webview.dart'; import 'package:ensemble/widget/youtube/youtube.dart'; import 'package:ensemble/widget/weeklyscheduler.dart'; -import 'package:ensemble/widget/screenshot_container.dart'; import 'package:get_it/get_it.dart'; import 'fintech/tabapayconnect.dart'; @@ -184,7 +183,6 @@ class WidgetRegistry { AppScroller.type: () => AppScroller(), LoadingContainer.type: () => LoadingContainer(), EnsembleSlidable.type: () => EnsembleSlidable(), - ScreenshotContainer.type: () => ScreenshotContainer(), // charts Highcharts.type: () => Highcharts(), From 9912d03dc4a0d45dae6574231fee642d7a412d51 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 13 Jan 2025 21:23:53 +0500 Subject: [PATCH 6/8] save screenshot & added comments --- .../lib/action/saveFile/save_file.dart | 81 +++---------------- .../lib/action/saveFile/save_mobile.dart | 64 +++++++++++++++ .../ensemble/lib/action/take_screenshot.dart | 23 +++++- 3 files changed, 96 insertions(+), 72 deletions(-) create mode 100644 modules/ensemble/lib/action/saveFile/save_mobile.dart diff --git a/modules/ensemble/lib/action/saveFile/save_file.dart b/modules/ensemble/lib/action/saveFile/save_file.dart index 47fc5a1f6..34c322d2c 100644 --- a/modules/ensemble/lib/action/saveFile/save_file.dart +++ b/modules/ensemble/lib/action/saveFile/save_file.dart @@ -1,17 +1,13 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'dart:io'; import 'package:ensemble/framework/action.dart'; import 'package:ensemble/framework/scope.dart'; import 'package:flutter/material.dart'; -import 'package:image_gallery_saver/image_gallery_saver.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:ensemble/framework/error_handling.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; -// Conditionally import the file that has `dart:html` vs. the stub: -import 'download_stub.dart' if (dart.library.html) 'download_web.dart'; +import 'save_mobile.dart'; /// Custom action to save files (images and documents) in platform-specific accessible directories class SaveToFileSystemAction extends EnsembleAction { @@ -73,79 +69,24 @@ class SaveToFileSystemAction extends EnsembleAction { throw Exception('Missing blobData and source.'); } - if (type == 'image') { - // Save images to Default Image Path - await _saveImageToDCIM(fileName!, fileBytes); - } else if (type == 'document') { - // Save documents to Documents folder - await _saveDocumentToDocumentsFolder(fileName!, fileBytes); - } + // Save the file to the storage system + await _saveFile(type!, fileName!, fileBytes); } catch (e) { throw Exception('Failed to save file: $e'); } } - Future _saveImageToDCIM(String fileName, Uint8List fileBytes) async { - try { - if (kIsWeb) { - _downloadFileOnWeb(fileName, fileBytes); - } else { - final result = await ImageGallerySaver.saveImage( - fileBytes, - name: fileName, - ); - if (result['isSuccess']) { - debugPrint('Image saved to gallery: $result'); - } else { - throw Exception('Failed to save image to gallery.'); - } - } - } catch (e) { - throw Exception('Failed to save image: $e'); + Future _saveFile( + String type, String fileName, Uint8List fileBytes) async { + if (type == 'image') { + // Save images to Default Image Path + await saveImageToDCIM(fileName!, fileBytes); + } else if (type == 'document') { + // Save documents to Documents folder + await saveDocumentToDocumentsFolder(fileName!, fileBytes); } } - /// Save documents to the default "Documents" directory - Future _saveDocumentToDocumentsFolder( - String fileName, Uint8List fileBytes) async { - try { - String filePath; - - if (Platform.isAndroid) { - // Get the default "Documents" directory on Android - Directory? directory = Directory('/storage/emulated/0/Documents'); - if (!directory.existsSync()) { - directory.createSync( - recursive: true); // Create the directory if it doesn't exist - } - filePath = '${directory.path}/$fileName'; - } else if (Platform.isIOS) { - // On iOS, use the app-specific Documents directory - final directory = await getApplicationDocumentsDirectory(); - filePath = '${directory.path}/$fileName'; - - // Optionally, use a share intent to let users save the file to their desired location - } else if (kIsWeb) { - _downloadFileOnWeb(fileName, fileBytes); - return; - } else { - throw UnsupportedError('Platform not supported'); - } - - // Write the file to the determined path - final file = File(filePath); - await file.writeAsBytes(fileBytes); - - debugPrint('Document saved to: $filePath'); - } catch (e) { - throw Exception('Failed to save document: $e'); - } - } - - Future _downloadFileOnWeb(String fileName, Uint8List fileBytes) async { - downloadFileOnWeb(fileName, fileBytes); - } - /// Factory method to construct the action from JSON static SaveToFileSystemAction fromJson(Map json) { return SaveToFileSystemAction( diff --git a/modules/ensemble/lib/action/saveFile/save_mobile.dart b/modules/ensemble/lib/action/saveFile/save_mobile.dart new file mode 100644 index 000000000..f8bcad0b1 --- /dev/null +++ b/modules/ensemble/lib/action/saveFile/save_mobile.dart @@ -0,0 +1,64 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:path_provider/path_provider.dart'; +// Conditionally import the file that has `dart:html` vs. the stub: +import 'download_stub.dart' if (dart.library.html) 'download_web.dart'; + +Future saveImageToDCIM(String fileName, Uint8List fileBytes) async { + try { + if (kIsWeb) { + downloadFileOnWeb(fileName, fileBytes); + } else { + final result = await ImageGallerySaver.saveImage( + fileBytes, + name: fileName, + ); + if (result['isSuccess']) { + debugPrint('Image saved to gallery: $result'); + } else { + throw Exception('Failed to save image to gallery.'); + } + } + } catch (e) { + throw Exception('Failed to save image: $e'); + } +} + +/// Save documents to the default "Documents" directory +Future saveDocumentToDocumentsFolder( + String fileName, Uint8List fileBytes) async { + try { + String filePath; + + if (Platform.isAndroid) { + // Get the default "Documents" directory on Android + Directory? directory = Directory('/storage/emulated/0/Documents'); + if (!directory.existsSync()) { + directory.createSync( + recursive: true); // Create the directory if it doesn't exist + } + filePath = '${directory.path}/$fileName'; + } else if (Platform.isIOS) { + // On iOS, use the app-specific Documents directory + final directory = await getApplicationDocumentsDirectory(); + filePath = '${directory.path}/$fileName'; + + // Optionally, use a share intent to let users save the file to their desired location + } else if (kIsWeb) { + downloadFileOnWeb(fileName, fileBytes); + return; + } else { + throw UnsupportedError('Platform not supported'); + } + + // Write the file to the determined path + final file = File(filePath); + await file.writeAsBytes(fileBytes); + + debugPrint('Document saved to: $filePath'); + } catch (e) { + throw Exception('Failed to save document: $e'); + } +} diff --git a/modules/ensemble/lib/action/take_screenshot.dart b/modules/ensemble/lib/action/take_screenshot.dart index b645a6a16..fee13b267 100644 --- a/modules/ensemble/lib/action/take_screenshot.dart +++ b/modules/ensemble/lib/action/take_screenshot.dart @@ -6,8 +6,11 @@ import 'package:ensemble/framework/view/data_scope_widget.dart'; import 'package:ensemble/screen_controller.dart'; import 'package:ensemble/framework/scope.dart'; import 'package:ensemble/util/utils.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:screenshot/screenshot.dart'; +import 'saveFile/save_mobile.dart'; class TakeScreenshotAction extends EnsembleAction { TakeScreenshotAction({ @@ -59,6 +62,7 @@ class TakeScreenshotAction extends EnsembleAction { ); final Uint8List? capturedImage = + // It will only capture readonly widgets await screenshotController.captureFromLongWidget( InheritedTheme.captureAll( context, @@ -67,7 +71,6 @@ class TakeScreenshotAction extends EnsembleAction { child: widget, ), ), - delay: const Duration(milliseconds: 1000), pixelRatio: pixelRatio ?? MediaQuery.of(context).devicePixelRatio, context: context, ); @@ -77,13 +80,15 @@ class TakeScreenshotAction extends EnsembleAction { } final dimensions = await _getImageDimensions(capturedImage); + // Save screenshot to gallery and download on web + await _saveScreenshot(capturedImage); if (onSuccess != null) { await ScreenController().executeAction( context, onSuccess!, event: EnsembleEvent(initiator, data: { - 'imageData': capturedImage, + 'imageBytes': capturedImage, // capturedImage contains Image Bytes 'size': capturedImage.length, 'dimensions': dimensions, }), @@ -101,6 +106,20 @@ class TakeScreenshotAction extends EnsembleAction { } } + Future _saveScreenshot(Uint8List fileBytes) async { + // Screenshot name of current date + // Get the current date and time + DateTime now = DateTime.now(); + + // Format the date and time + String formattedDateTime = DateFormat('yyyyMMdd_HHmmss').format(now); + + // Combine the prefix with the formatted date and time + String screenshotName = 'screenshot_$formattedDateTime'; + + await saveImageToDCIM(screenshotName, fileBytes); + } + Future> _getImageDimensions(Uint8List imageData) async { final image = await decodeImageFromList(imageData); return { From a6f3577a51b3d5d06b142fb569d25f6e3c40f9f3 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 13 Jan 2025 22:19:26 +0500 Subject: [PATCH 7/8] removed unwanted error rethrow --- modules/ensemble/lib/action/take_screenshot.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ensemble/lib/action/take_screenshot.dart b/modules/ensemble/lib/action/take_screenshot.dart index fee13b267..da736fc3f 100644 --- a/modules/ensemble/lib/action/take_screenshot.dart +++ b/modules/ensemble/lib/action/take_screenshot.dart @@ -102,7 +102,6 @@ class TakeScreenshotAction extends EnsembleAction { event: EnsembleEvent(initiator, data: {'error': e.toString()}), ); } - rethrow; } } From 65846559da9f1ea0be78ed3811f03c1147c1cf5f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Jan 2025 17:54:11 +0500 Subject: [PATCH 8/8] updated onFailure to onError --- modules/ensemble/lib/action/take_screenshot.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ensemble/lib/action/take_screenshot.dart b/modules/ensemble/lib/action/take_screenshot.dart index da736fc3f..8d7be85da 100644 --- a/modules/ensemble/lib/action/take_screenshot.dart +++ b/modules/ensemble/lib/action/take_screenshot.dart @@ -18,13 +18,13 @@ class TakeScreenshotAction extends EnsembleAction { required this.widgetId, this.pixelRatio, this.onSuccess, - this.onFailure, + this.onError, }); final dynamic widgetId; final double? pixelRatio; final EnsembleAction? onSuccess; - final EnsembleAction? onFailure; + final EnsembleAction? onError; factory TakeScreenshotAction.fromYaml({Map? payload}) { if (payload == null || payload['widgetId'] == null) { @@ -37,8 +37,8 @@ class TakeScreenshotAction extends EnsembleAction { onSuccess: payload['onSuccess'] != null ? EnsembleAction.from(payload['onSuccess']) : null, - onFailure: payload['onFailure'] != null - ? EnsembleAction.from(payload['onFailure']) + onError: payload['onError'] != null + ? EnsembleAction.from(payload['onError']) : null, ); } @@ -95,10 +95,10 @@ class TakeScreenshotAction extends EnsembleAction { ); } } catch (e) { - if (onFailure != null) { + if (onError != null) { await ScreenController().executeAction( context, - onFailure!, + onError!, event: EnsembleEvent(initiator, data: {'error': e.toString()}), ); }