diff --git a/packages/share_plus/share_plus/example/assets/flutter_logo.png b/packages/share_plus/share_plus/example/assets/flutter_logo.png new file mode 100644 index 0000000000..cfc0ee42f3 Binary files /dev/null and b/packages/share_plus/share_plus/example/assets/flutter_logo.png differ diff --git a/packages/share_plus/share_plus/example/integration_test/share_plus_test.dart b/packages/share_plus/share_plus/example/integration_test/share_plus_test.dart index 0da8c7f5d1..f50553ef13 100644 --- a/packages/share_plus/share_plus/example/integration_test/share_plus_test.dart +++ b/packages/share_plus/share_plus/example/integration_test/share_plus_test.dart @@ -6,6 +6,8 @@ import 'dart:io'; +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:share_plus/share_plus.dart'; import 'package:integration_test/integration_test.dart'; @@ -24,4 +26,13 @@ void main() { testWidgets('Can launch shareWithResult', (WidgetTester tester) async { expect(Share.shareWithResult('message', subject: 'title'), isNotNull); }); + + testWidgets('Can shareXFile created using File.fromData()', + (WidgetTester tester) async { + final bytes = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]); + final XFile file = + XFile.fromData(bytes, name: 'image.jpg', mimeType: 'image/jpeg'); + + expect(Share.shareXFiles([file], text: "example"), isNotNull); + }); } diff --git a/packages/share_plus/share_plus/example/lib/main.dart b/packages/share_plus/share_plus/example/lib/main.dart index c46eba0e3e..024c956e90 100644 --- a/packages/share_plus/share_plus/example/lib/main.dart +++ b/packages/share_plus/share_plus/example/lib/main.dart @@ -8,6 +8,7 @@ import 'dart:io'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:share_plus/share_plus.dart'; @@ -123,6 +124,17 @@ class DemoAppState extends State { ); }, ), + const Padding(padding: EdgeInsets.only(top: 12.0)), + Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + _onShareXFileFromAssets(context); + }, + child: const Text('Share XFile from Assets'), + ); + }, + ), ], ), ), @@ -185,4 +197,24 @@ class DemoAppState extends State { content: Text("Share result: ${result.status}"), )); } + + void _onShareXFileFromAssets(BuildContext context) async { + final box = context.findRenderObject() as RenderBox?; + final scaffoldMessenger = ScaffoldMessenger.of(context); + final data = await rootBundle.load('assets/flutter_logo.png'); + final buffer = data.buffer; + final result = await Share.shareXFiles( + [ + XFile.fromData( + buffer.asUint8List(data.offsetInBytes, data.lengthInBytes), + name: 'flutter_logo.png', + mimeType: 'image/png'), + ], + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); + + scaffoldMessenger.showSnackBar(SnackBar( + content: Text("Share result: ${result.status}"), + )); + } } diff --git a/packages/share_plus/share_plus/example/pubspec.yaml b/packages/share_plus/share_plus/example/pubspec.yaml index 38a5462f3b..badaf5dd61 100644 --- a/packages/share_plus/share_plus/example/pubspec.yaml +++ b/packages/share_plus/share_plus/example/pubspec.yaml @@ -25,5 +25,8 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/flutter_logo.png + environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/share_plus/share_plus_platform_interface/lib/method_channel/method_channel_share.dart b/packages/share_plus/share_plus_platform_interface/lib/method_channel/method_channel_share.dart index a32614f560..8bbed87007 100644 --- a/packages/share_plus/share_plus_platform_interface/lib/method_channel/method_channel_share.dart +++ b/packages/share_plus/share_plus_platform_interface/lib/method_channel/method_channel_share.dart @@ -3,6 +3,8 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; + // Keep dart:ui for retrocompatiblity with Flutter <3.3.0 // ignore: unnecessary_import import 'dart:ui'; @@ -10,8 +12,10 @@ import 'dart:ui'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart' show visibleForTesting; -import 'package:mime/mime.dart' show lookupMimeType; +import 'package:mime/mime.dart' show extensionFromMime, lookupMimeType; import 'package:share_plus_platform_interface/share_plus_platform_interface.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:uuid/uuid.dart'; /// Plugin for summoning a platform share sheet. class MethodChannelShare extends SharePlatform { @@ -141,12 +145,15 @@ class MethodChannelShare extends SharePlatform { String? subject, String? text, Rect? sharePositionOrigin, - }) { - final mimeTypes = - files.map((e) => e.mimeType ?? _mimeTypeForPath(e.path)).toList(); + }) async { + final filesWithPath = await _getFiles(files); + + final mimeTypes = filesWithPath + .map((e) => e.mimeType ?? _mimeTypeForPath(e.path)) + .toList(); return shareFilesWithResult( - files.map((e) => e.path).toList(), + filesWithPath.map((e) => e.path).toList(), mimeTypes: mimeTypes, subject: subject, text: text, @@ -154,6 +161,42 @@ class MethodChannelShare extends SharePlatform { ); } + /// if file doesn't contain path + /// then make new file in TemporaryDirectory and return with path + /// + /// the system will automatically delete files in this + /// TemporaryDirectory as disk space is needed elsewhere on the device + Future> _getFiles(List files) async { + if (files.any((element) => element.path.isEmpty)) { + final newFiles = []; + + final String tempPath = (await getTemporaryDirectory()).path; + + const uuid = Uuid(); + for (final XFile element in files) { + if (element.path.isEmpty) { + final name = uuid.v4(); + + final extension = + extensionFromMime(element.mimeType ?? 'octet-stream'); + + final path = '$tempPath/$name.$extension'; + final file = File(path); + + await file.writeAsBytes(await element.readAsBytes()); + + newFiles.add(XFile(path)); + } else { + newFiles.add(element); + } + } + + return newFiles; + } else { + return files; + } + } + static String _mimeTypeForPath(String path) { return lookupMimeType(path) ?? 'application/octet-stream'; } diff --git a/packages/share_plus/share_plus_platform_interface/pubspec.yaml b/packages/share_plus/share_plus_platform_interface/pubspec.yaml index 6edf55167f..90c09d66ea 100644 --- a/packages/share_plus/share_plus_platform_interface/pubspec.yaml +++ b/packages/share_plus/share_plus_platform_interface/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: meta: ^1.7.0 mime: ^1.0.2 plugin_platform_interface: ^2.0.0 + path_provider: ^2.0.11 + uuid: ^3.0.6 dev_dependencies: flutter_test: diff --git a/packages/share_plus/share_plus_platform_interface/test/share_plus_platform_interface_test.dart b/packages/share_plus/share_plus_platform_interface/test/share_plus_platform_interface_test.dart index 1d18348b50..3a0bfa2b33 100644 --- a/packages/share_plus/share_plus_platform_interface/test/share_plus_platform_interface_test.dart +++ b/packages/share_plus/share_plus_platform_interface/test/share_plus_platform_interface_test.dart @@ -163,10 +163,7 @@ void main() { () => SharePlatform.instance.shareFilesWithResult(['']), throwsA(const TypeMatcher()), ); - expect( - () => sharePlatform.shareXFiles([XFile('')]), - throwsA(const TypeMatcher()), - ); + verifyZeroInteractions(mockChannel); });