import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:data_transfer_flutter/src/json_type_group.dart'; import 'package:data_transfer_flutter/src/platform.dart'; import 'package:file_picker/file_picker.dart'; import 'package:file_selector/file_selector.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; /// Writes [bytes] through the system "Save as" picker; false on cancel. typedef DocumentSaver = Future Function({ required String fileName, required Uint8List bytes, required String mimeType, }); /// Offers [text] as a file named [fileName] to the share sheet. typedef SheetSharer = Future Function({ required String fileName, required String text, required String mimeType, }); /// The platform for hosts that have `dart:io`. DataTransferPlatform createDefaultPlatform() => IoDataTransferPlatform(); /// Android, iOS and desktop. /// /// Mobile saves through the Storage Access Framework / document picker /// (`file_picker`'s `saveFile`), because the share sheet on a stock phone has /// no local save target and an export that can only be shared is an upload. /// Desktop uses `file_selector`'s save dialog. Opening is `file_selector`'s /// `openFile` everywhere: ACTION_OPEN_DOCUMENT on Android. class IoDataTransferPlatform implements DataTransferPlatform { /// Creates the platform; every argument is a test seam. IoDataTransferPlatform({ bool? isMobile, DocumentSaver? documentSaver, SheetSharer? sheetSharer, }) : _isMobile = isMobile ?? (Platform.isAndroid || Platform.isIOS), _documentSaver = documentSaver ?? _saveViaDocumentPicker, _sheetSharer = sheetSharer ?? _shareViaSheet; final bool _isMobile; final DocumentSaver _documentSaver; final SheetSharer _sheetSharer; @override Future saveText({ required String fileName, required String text, String mimeType = 'application/json', }) async { if (_isMobile) { return await _documentSaver( fileName: fileName, bytes: Uint8List.fromList(utf8.encode(text)), mimeType: mimeType, ); } final location = await getSaveLocation( suggestedName: fileName, acceptedTypeGroups: [saveTypeGroup(fileName, mimeType)], ); if (location == null) return false; await File(location.path).writeAsString(text); return true; } @override Future shareText({ required String fileName, required String text, String mimeType = 'application/json', }) => _isMobile ? _sheetSharer(fileName: fileName, text: text, mimeType: mimeType) : saveText(fileName: fileName, text: text, mimeType: mimeType); @override Future pickText() async { final file = await openFile(acceptedTypeGroups: const [jsonTypeGroup]); return await file?.readAsString(); } } // coverage:ignore-start // Real plugin calls: unreachable on the Linux test host (a platform channel // answers nothing there) and faked at the typedef seams above instead. Future _saveViaDocumentPicker({ required String fileName, required Uint8List bytes, required String mimeType, }) async { final saved = await FilePicker.saveFile( fileName: fileName, bytes: bytes, mimeType: mimeType, ); return saved != null; } Future _shareViaSheet({ required String fileName, required String text, required String mimeType, }) async { final dir = await getTemporaryDirectory(); final file = File(p.join(dir.path, fileName)); await file.writeAsString(text); await SharePlus.instance.share( ShareParams(files: [XFile(file.path, mimeType: mimeType)]), ); return true; } // coverage:ignore-end