Skip to content

Commit

Permalink
fix(share_plus): fallback for shareXFiles() to use download on web (#…
Browse files Browse the repository at this point in the history
…3388)

Co-authored-by: Miguel Beltran <[email protected]>
  • Loading branch information
ekuleshov and miquelbeltran authored Dec 12, 2024
1 parent b5d24a0 commit 95a12ee
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 34 deletions.
6 changes: 6 additions & 0 deletions packages/share_plus/share_plus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ package.
Share.shareXFiles([XFile('assets/hello.txt')], text: 'Great picture');
```

File downloading fallback mechanism for web can be disabled by setting:

```dart
Share.downloadFallbackEnabled = false;
```

#### 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).
Expand Down
69 changes: 42 additions & 27 deletions packages/share_plus/share_plus/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import 'package:share_plus/share_plus.dart';
import 'image_previews.dart';

void main() {
// Set `downloadFallbackEnabled` to `false`
// to disable downloading files if `shareXFiles` fails on web.
Share.downloadFallbackEnabled = true;

runApp(const DemoApp());
}

Expand Down Expand Up @@ -241,39 +245,50 @@ class DemoAppState extends State<DemoApp> {
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 shareResult = 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(getResultSnackBar(shareResult));
try {
final data = await rootBundle.load('assets/flutter_logo.png');
final buffer = data.buffer;
final shareResult = 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(getResultSnackBar(shareResult));
} catch (e) {
scaffoldMessenger.showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}

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));
try {
final shareResult = await Share.shareXFiles(
[
XFile.fromData(
utf8.encode(text),
// 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));
} catch (e) {
scaffoldMessenger.showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}

SnackBar getResultSnackBar(ShareResult result) {
Expand Down
3 changes: 3 additions & 0 deletions packages/share_plus/share_plus/lib/share_plus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export 'src/share_plus_windows.dart'
class Share {
static SharePlatform get _platform => SharePlatform.instance;

/// Whether to fall back to downloading files if [shareXFiles] fails on web.
static bool downloadFallbackEnabled = true;

/// Summons the platform's share sheet to share uri.
///
/// Wraps the platform's native share dialog. Can share a URL.
Expand Down
63 changes: 56 additions & 7 deletions packages/share_plus/share_plus/lib/src/share_plus_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:ui';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:meta/meta.dart';
import 'package:mime/mime.dart' show lookupMimeType;
import 'package:share_plus/share_plus.dart';
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
import 'package:url_launcher_web/url_launcher_web.dart';
Expand Down Expand Up @@ -204,11 +205,19 @@ class SharePlusWebPlugin extends SharePlatform {
error: e,
);

throw Exception('Navigator.canShare() is unavailable');
return _downloadIfFallbackEnabled(
files,
fileNameOverrides,
'Navigator.canShare() is unavailable',
);
}

if (!canShare) {
throw Exception('Navigator.canShare() is false');
return _downloadIfFallbackEnabled(
files,
fileNameOverrides,
'Navigator.canShare() is false',
);
}

try {
Expand All @@ -217,16 +226,56 @@ class SharePlusWebPlugin extends SharePlatform {
// actions is success, but can't get the action name
return ShareResult.unavailable;
} on DOMException catch (e) {
if (e.name case 'AbortError') {
final name = e.name;
final message = e.message;

if (name case 'AbortError') {
return _resultDismissed;
}

developer.log(
'Failed to share files',
error: '${e.name}: ${e.message}',
return _downloadIfFallbackEnabled(
files,
fileNameOverrides,
'Navigator.share() failed: $message',
);
}
}

throw Exception('Navigator.share() failed: ${e.message}');
Future<ShareResult> _downloadIfFallbackEnabled(
List<XFile> files,
List<String>? fileNameOverrides,
String message,
) {
developer.log(message);
if (Share.downloadFallbackEnabled) {
return _download(files, fileNameOverrides);
} else {
throw Exception(message);
}
}

Future<ShareResult> _download(
List<XFile> files,
List<String>? fileNameOverrides,
) async {
developer.log('Download files as fallback');
try {
for (final (index, file) in files.indexed) {
final bytes = await file.readAsBytes();

final anchor = document.createElement('a') as HTMLAnchorElement
..href = Uri.dataFromBytes(bytes).toString()
..style.display = 'none'
..download = fileNameOverrides?.elementAt(index) ?? file.name;
document.body!.children.add(anchor);
anchor.click();
anchor.remove();
}

return ShareResult.unavailable;
} catch (error) {
developer.log('Failed to download files', error: error);
throw Exception('Failed to to download files: $error');
}
}

Expand Down

0 comments on commit 95a12ee

Please sign in to comment.