-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added saveFile
action
#1799
added saveFile
action
#1799
Changes from 3 commits
7d457fa
0535da8
b4df9b3
9d63558
9f2eafc
5eaff75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
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:dio/dio.dart'; | ||
import 'dart:html' as html; | ||
|
||
/// Custom action to save files (images and documents) in platform-specific accessible directories | ||
class SaveToFileSystemAction extends EnsembleAction { | ||
final String? fileName; | ||
final dynamic blobData; | ||
final String? source; // Optional source for URL if blobData is not available | ||
final String? type; // file type | ||
|
||
SaveToFileSystemAction({ | ||
required this.fileName, | ||
this.blobData, | ||
this.source, | ||
this.type, | ||
}); | ||
|
||
factory SaveToFileSystemAction.from({Map? payload}) { | ||
if (payload == null || payload['fileName'] == null) { | ||
throw LanguageError('${ActionType.saveFile.name} requires fileName.'); | ||
} | ||
|
||
return SaveToFileSystemAction( | ||
fileName: payload['fileName'], | ||
blobData: payload['blobData'], | ||
source: payload['source'], | ||
type: payload['type'], | ||
); | ||
} | ||
|
||
@override | ||
Future<void> execute(BuildContext context, ScopeManager scopeManager) async { | ||
try { | ||
if (fileName == null) { | ||
throw Exception('Missing required parameter: fileName.'); | ||
} | ||
|
||
Uint8List? fileBytes; | ||
|
||
// If blobData is provided, process it | ||
if (blobData != null) { | ||
// Handle base64 blob or binary data | ||
if (blobData is String) { | ||
fileBytes = base64Decode(blobData); // Decode base64 | ||
} else if (blobData is List<int>) { | ||
fileBytes = Uint8List.fromList(blobData); | ||
} else { | ||
throw Exception( | ||
'Invalid blob data format. Must be base64 or List<int>.'); | ||
} | ||
} else if (source != null) { | ||
// If blobData is not available, check for source (network URL) | ||
Dio dio = Dio(); | ||
var response = await dio.get(source!, | ||
options: Options(responseType: ResponseType.bytes)); | ||
fileBytes = Uint8List.fromList(response.data); | ||
} else { | ||
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); | ||
} | ||
} catch (e) { | ||
throw Exception('Failed to save file: $e'); | ||
} | ||
} | ||
|
||
Future<void> _saveImageToDCIM(String fileName, Uint8List fileBytes) async { | ||
try { | ||
final result = await ImageGallerySaver.saveImage( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is reason for using ImageGallerySaver, cause adding in android would be placing file on that location. is it ios? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I just place the file in that location it doesn't register the image in the gallery, using ImageGallerySaver it automatically registers the image to the gallery in the respective platforms I.e. Android and IOS |
||
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'); | ||
|
||
// Notify Android's media store to make it visible | ||
if (Platform.isAndroid) { | ||
await _notifyFileSystem(filePath); | ||
} | ||
} catch (e) { | ||
throw Exception('Failed to save document: $e'); | ||
} | ||
} | ||
|
||
/// Notify the Android media store about the new file | ||
Future<void> _notifyFileSystem(String filePath) async { | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Running a process is not recommended if possible please find another way |
||
await Process.run('am', [ | ||
'broadcast', | ||
'-a', | ||
'android.intent.action.MEDIA_SCANNER_SCAN_FILE', | ||
'-d', | ||
'file://$filePath', | ||
]); | ||
} catch (e) { | ||
debugPrint('Failed to notify media store: $e'); | ||
} | ||
} | ||
|
||
Future<void> _downloadFileOnWeb(String fileName, Uint8List fileBytes) async { | ||
try { | ||
// Convert Uint8List to a Blob | ||
final blob = html.Blob([fileBytes]); | ||
|
||
// Create an object URL for the Blob | ||
final url = html.Url.createObjectUrlFromBlob(blob); | ||
|
||
// Create a download anchor element | ||
final anchor = html.AnchorElement(href: url) | ||
..target = 'blank' // Open in a new tab if needed | ||
..download = fileName; // Set the download file name | ||
|
||
// Trigger the download | ||
anchor.click(); | ||
|
||
// Revoke the object URL to free resources | ||
html.Url.revokeObjectUrl(url); | ||
|
||
debugPrint('File downloaded: $fileName'); | ||
} catch (e) { | ||
throw Exception('Failed to download file: $e'); | ||
} | ||
} | ||
|
||
/// Factory method to construct the action from JSON | ||
static SaveToFileSystemAction fromJson(Map<String, dynamic> json) { | ||
return SaveToFileSystemAction( | ||
fileName: json['fileName'], | ||
blobData: json['blobData'], | ||
source: json['source'], | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why use dio when we have http package for network handling?