Skip to content

feat(share_plus)!: Introduce optional parameter nameOverride to shareXFiles. #3077

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

Merged
merged 16 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/share_plus/share_plus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ if (result.status == ShareResultStatus.dismissed) {
}
```

On web, you can use `SharePlus.shareXFiles()`. This uses the [Web Share API](https://web.dev/web-share/)
On web, this uses the [Web Share API](https://web.dev/web-share/)
if it's available. Otherwise it falls back to downloading the shared files.
See [Can I Use - Web Share API](https://caniuse.com/web-share) to understand
which browsers are supported. This builds on the [`cross_file`](https://pub.dev/packages/cross_file)
Expand All @@ -101,6 +101,19 @@ package.
Share.shareXFiles([XFile('assets/hello.txt')], text: 'Great picture');
```

#### Share Data

You can also share files that you dynamically generate from its data using [`XFile.fromData`](https://pub.dev/documentation/share_plus/latest/share_plus/XFile/XFile.fromData.html).

To set the name of such files, use the `fileNameOverrides` parameter, otherwise the file name will be a random UUID string.

```dart
Share.shareXFiles([XFile.fromData(utf8.encode(text), mimeType: 'text/plain')], fileNameOverrides: ['myfile.txt']);
```

> [!CAUTION]
> The `name` parameter in the `XFile.fromData` method is ignored in most platforms. Use `fileNameOverrides` instead.

### Share URI

iOS supports fetching metadata from a URI when shared using `UIActivityViewController`.
Expand Down
48 changes: 48 additions & 0 deletions packages/share_plus/share_plus/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// ignore_for_file: public_member_api_docs

import 'dart:convert';
import 'dart:io';

import 'package:file_selector/file_selector.dart'
Expand Down Expand Up @@ -32,6 +33,7 @@ class DemoAppState extends State<DemoApp> {
String text = '';
String subject = '';
String uri = '';
String fileName = '';
List<String> imageNames = [];
List<String> imagePaths = [];

Expand Down Expand Up @@ -90,6 +92,18 @@ class DemoAppState extends State<DemoApp> {
},
),
const SizedBox(height: 16),
TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Share Text as File',
hintText: 'Enter the filename you want to share your text as',
),
maxLines: null,
onChanged: (String value) {
setState(() => fileName = value);
},
),
const SizedBox(height: 16),
ImagePreviews(imagePaths, onDelete: _onDeleteImage),
ElevatedButton.icon(
label: const Text('Add image'),
Expand Down Expand Up @@ -157,6 +171,21 @@ class DemoAppState extends State<DemoApp> {
);
},
),
const SizedBox(height: 16),
Builder(
builder: (BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
onPressed: fileName.isEmpty || text.isEmpty
? null
: () => _onShareTextAsXFile(context),
child: const Text('Share text as XFile'),
);
},
),
],
),
),
Expand Down Expand Up @@ -228,6 +257,25 @@ class DemoAppState extends State<DemoApp> {
scaffoldMessenger.showSnackBar(getResultSnackBar(shareResult));
}

void _onShareTextAsXFile(BuildContext context) async {
final box = context.findRenderObject() as RenderBox?;
final scaffoldMessenger = ScaffoldMessenger.of(context);
final data = utf8.encode(text);
final shareResult = await Share.shareXFiles(
[
XFile.fromData(
data,
// name: fileName, // Notice, how setting the name here does not work.
mimeType: 'text/plain',
),
],
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size,
fileNameOverrides: [fileName],
);

scaffoldMessenger.showSnackBar(getResultSnackBar(shareResult));
}

SnackBar getResultSnackBar(ShareResult result) {
return SnackBar(
content: Column(
Expand Down
7 changes: 7 additions & 0 deletions packages/share_plus/share_plus/lib/share_plus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ class Share {
/// origin rect for the share sheet to popover from on iPads and Macs. It has no effect
/// on other devices.
///
/// The optional parameter [fileNameOverrides] can be used to override the names of shared files
/// When set, the list length must match the number of [files] to share.
/// This is useful when sharing files that were created by [`XFile.fromData`](https://github.com/flutter/packages/blob/754de1918a339270b70971b6841cf1e04dd71050/packages/cross_file/lib/src/types/io.dart#L43),
/// because name property will be ignored by [`cross_file`](https://pub.dev/packages/cross_file) on all platforms except on web.
///
/// May throw [PlatformException] or [FormatException]
/// from [MethodChannel].
///
Expand All @@ -120,13 +125,15 @@ class Share {
String? subject,
String? text,
Rect? sharePositionOrigin,
List<String>? fileNameOverrides,
}) async {
assert(files.isNotEmpty);
return _platform.shareXFiles(
files,
subject: subject,
text: text,
sharePositionOrigin: sharePositionOrigin,
fileNameOverrides: fileNameOverrides,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class SharePlusLinuxPlugin extends SharePlatform {
String? subject,
String? text,
Rect? sharePositionOrigin,
List<String>? fileNameOverrides,
}) {
throw UnimplementedError(
'shareXFiles() has not been implemented on Linux.',
Expand Down
13 changes: 9 additions & 4 deletions packages/share_plus/share_plus/lib/src/share_plus_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,15 @@ class SharePlusWebPlugin extends SharePlatform {
String? subject,
String? text,
Rect? sharePositionOrigin,
List<String>? fileNameOverrides,
}) async {
assert(
fileNameOverrides == null || files.length == fileNameOverrides.length);
final webFiles = <web.File>[];
for (final xFile in files) {
webFiles.add(await _fromXFile(xFile));
for (var index = 0; index < files.length; index++) {
final xFile = files[index];
final filename = fileNameOverrides?.elementAt(index);
webFiles.add(await _fromXFile(xFile, nameOverride: filename));
}

final ShareData data;
Expand Down Expand Up @@ -222,11 +227,11 @@ class SharePlusWebPlugin extends SharePlatform {
}
}

static Future<web.File> _fromXFile(XFile file) async {
static Future<web.File> _fromXFile(XFile file, {String? nameOverride}) async {
final bytes = await file.readAsBytes();
return web.File(
[bytes.buffer.toJS].toJS,
file.name,
nameOverride ?? file.name,
web.FilePropertyBag()
..type = file.mimeType ?? _mimeTypeForPath(file, bytes),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SharePlusWindowsPlugin extends SharePlatform {
String? subject,
String? text,
Rect? sharePositionOrigin,
List<String>? fileNameOverrides,
}) {
throw UnimplementedError(
'shareXFiles() is only available for Windows versions higher than 10.0.${VersionHelper.kWindows10RS5BuildNumber}.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,14 @@ class MethodChannelShare extends SharePlatform {
String? subject,
String? text,
Rect? sharePositionOrigin,
List<String>? fileNameOverrides,
}) async {
assert(files.isNotEmpty);

final filesWithPath = await _getFiles(files);
assert(
fileNameOverrides == null || files.length == fileNameOverrides.length,
"fileNameOverrides list must have the same length as files list.",
);
final filesWithPath = await _getFiles(files, fileNameOverrides);
assert(filesWithPath.every((element) => element.path.isNotEmpty));

final mimeTypes = filesWithPath
Expand Down Expand Up @@ -117,7 +121,11 @@ class MethodChannelShare extends SharePlatform {
/// 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<XFile> _getFile(XFile file, {String? tempRoot}) async {
Future<XFile> _getFile(
XFile file, {
String? tempRoot,
String? nameOverride,
}) async {
if (file.path.isNotEmpty) {
return file;
} else {
Expand All @@ -137,13 +145,16 @@ class MethodChannelShare extends SharePlatform {
final tempSubfolderPath = "$tempRoot/${const Uuid().v4()}";
await Directory(tempSubfolderPath).create(recursive: true);

// True if filename exists or the filename has a valid extension
final filenameNotEmptyOrHasValidExt =
file.name.isNotEmpty || lookupMimeType(file.name) != null;

//Per Issue [#3032](https://github.com/fluttercommunity/plus_plugins/issues/3032): use overridden name when available.
//Per Issue [#1548](https://github.com/fluttercommunity/plus_plugins/issues/1548): attempt to use XFile.name when available
final filename = file.name.isNotEmpty // If filename exists
||
lookupMimeType(file.name) !=
null //If the filename has a valid extension
? file.name
: "${const Uuid().v1().substring(10)}.$extension";
final filename = nameOverride ??
(filenameNotEmptyOrHasValidExt
? file.name
: "${const Uuid().v1().substring(10)}.$extension");

final path = "$tempSubfolderPath/$filename";

Expand All @@ -155,8 +166,18 @@ class MethodChannelShare extends SharePlatform {
}

/// A wrapper of [MethodChannelShare._getFile] for multiple files.
Future<List<XFile>> _getFiles(List<XFile> files) async =>
await Future.wait(files.map((entry) => _getFile(entry)));
Future<List<XFile>> _getFiles(
List<XFile> files,
List<String>? fileNameOverrides,
) async {
return Future.wait([
for (var index = 0; index < files.length; index++)
_getFile(
files[index],
nameOverride: fileNameOverrides?.elementAt(index),
)
]);
}

static String _mimeTypeForPath(String path) {
return lookupMimeType(path) ?? 'application/octet-stream';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ class SharePlatform extends PlatformInterface {
String? subject,
String? text,
Rect? sharePositionOrigin,
List<String>? fileNameOverrides,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we are changing the platform interface, this requires a major version, otherwise this will cause issues with clients where the platform interface and the main package are not in sync.

}) async {
return _instance.shareXFiles(
files,
subject: subject,
text: text,
sharePositionOrigin: sharePositionOrigin,
fileNameOverrides: fileNameOverrides,
);
}
}
Expand Down
Loading