Skip to content

Commit

Permalink
Merge branch 'main' into Media-Support-for-shareAction
Browse files Browse the repository at this point in the history
  • Loading branch information
mehsaandev authored Jan 14, 2025
2 parents ddbf47e + b15a5ba commit ca29973
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 70 deletions.
1 change: 1 addition & 0 deletions modules/ensemble/lib/action/action_invokable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ abstract class ActionInvokable with Invokable {
ActionType.dismissDialog,
ActionType.closeAllDialogs,
ActionType.executeActionGroup,
ActionType.takeScreenshot
ActionType.saveFile,
ActionType.controlDeviceBackNavigation,
ActionType.closeApp,
Expand Down
81 changes: 11 additions & 70 deletions modules/ensemble/lib/action/saveFile/save_file.dart
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<void> _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<void> _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<void> _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<void> _downloadFileOnWeb(String fileName, Uint8List fileBytes) async {
downloadFileOnWeb(fileName, fileBytes);
}

/// Factory method to construct the action from JSON
static SaveToFileSystemAction fromJson(Map<String, dynamic> json) {
return SaveToFileSystemAction(
Expand Down
64 changes: 64 additions & 0 deletions modules/ensemble/lib/action/saveFile/save_mobile.dart
Original file line number Diff line number Diff line change
@@ -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<void> 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<void> 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');
}
}
129 changes: 129 additions & 0 deletions modules/ensemble/lib/action/take_screenshot.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import 'dart:typed_data';
import 'package:ensemble/framework/action.dart';
import 'package:ensemble/framework/error_handling.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';
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({
super.initiator,
required this.widgetId,
this.pixelRatio,
this.onSuccess,
this.onError,
});

final dynamic widgetId;
final double? pixelRatio;
final EnsembleAction? onSuccess;
final EnsembleAction? onError;

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['pixelRatio']),
onSuccess: payload['onSuccess'] != null
? EnsembleAction.from(payload['onSuccess'])
: null,
onError: payload['onError'] != null
? EnsembleAction.from(payload['onError'])
: null,
);
}

@override
Future<void> 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: DataScopeWidget(
scopeManager: scopeManager,
child: resolvedWidget,
),
);

final Uint8List? capturedImage =
// It will only capture readonly widgets
await screenshotController.captureFromLongWidget(
InheritedTheme.captureAll(
context,
Material(
type: MaterialType.transparency,
child: widget,
),
),
pixelRatio: pixelRatio ?? MediaQuery.of(context).devicePixelRatio,
context: context,
);

if (capturedImage == null) {
throw LanguageError("Failed to capture screenshot");
}

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: {
'imageBytes': capturedImage, // capturedImage contains Image Bytes
'size': capturedImage.length,
'dimensions': dimensions,
}),
);
}
} catch (e) {
if (onError != null) {
await ScreenController().executeAction(
context,
onError!,
event: EnsembleEvent(initiator, data: {'error': e.toString()}),
);
}
}
}

Future<void> _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<Map<String, int>> _getImageDimensions(Uint8List imageData) async {
final image = await decodeImageFromList(imageData);
return {
'width': image.width,
'height': image.height,
};
}
}
4 changes: 4 additions & 0 deletions modules/ensemble/lib/framework/action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'package:ensemble/action/saveFile/save_file.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/action/disable_hardware_navigation.dart';
import 'package:ensemble/action/close_app.dart';
import 'package:ensemble/ensemble.dart';
Expand Down Expand Up @@ -1042,6 +1043,7 @@ enum ActionType {
dispatchEvent,
executeConditionalAction,
executeActionGroup,
takeScreenshot,
playAudio,
stopAudio,
pauseAudio,
Expand Down Expand Up @@ -1165,6 +1167,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) {
Expand Down
1 change: 1 addition & 0 deletions modules/ensemble/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,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:
Expand Down

0 comments on commit ca29973

Please sign in to comment.