diff --git a/packages/share_plus/share_plus/lib/src/share_plus_web.dart b/packages/share_plus/share_plus/lib/src/share_plus_web.dart index 69c13dbadd..09a34360d2 100644 --- a/packages/share_plus/share_plus/lib/src/share_plus_web.dart +++ b/packages/share_plus/share_plus/lib/src/share_plus_web.dart @@ -1,12 +1,10 @@ import 'dart:developer' as developer; import 'dart:js_interop'; import 'dart:typed_data'; -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'; @@ -29,229 +27,164 @@ class SharePlusWebPlugin extends SharePlatform { @visibleForTesting Navigator? debugNavigator, }) : _navigator = debugNavigator ?? window.navigator; - // @override - // Future shareUri( - // Uri uri, { - // Rect? sharePositionOrigin, - // }) async { - // final data = ShareData( - // url: uri.toString(), - // ); - - // final bool canShare; - // try { - // canShare = _navigator.canShare(data); - // } on NoSuchMethodError catch (e) { - // developer.log( - // 'Share API is not supported in this User Agent.', - // error: e, - // ); - - // throw Exception('Navigator.canShare() is unavailable'); - // } - - // if (!canShare) { - // throw Exception('Navigator.canShare() is false'); - // } - - // try { - // await _navigator.share(data).toDart; - // } on DOMException catch (e) { - // if (e.name case 'AbortError') { - // return _resultDismissed; - // } - - // developer.log( - // 'Failed to share uri', - // error: '${e.name}: ${e.message}', - // ); - - // throw Exception('Navigator.share() failed: ${e.message}'); - // } - - // return ShareResult.unavailable; - // } - - // @override - // Future share( - // String text, { - // String? subject, - // Rect? sharePositionOrigin, - // }) async { - // final ShareData data; - // if (subject != null && subject.isNotEmpty) { - // data = ShareData( - // title: subject, - // text: text, - // ); - // } else { - // data = ShareData( - // text: text, - // ); - // } - - // final bool canShare; - // try { - // canShare = _navigator.canShare(data); - // } on NoSuchMethodError catch (e) { - // developer.log( - // 'Share API is not supported in this User Agent.', - // error: e, - // ); - - // // Navigator is not available or the webPage is not served on https - // final queryParameters = { - // if (subject != null) 'subject': subject, - // 'body': text, - // }; - - // // see https://github.com/dart-lang/sdk/issues/43838#issuecomment-823551891 - // final uri = Uri( - // scheme: 'mailto', - // query: queryParameters.entries - // .map((e) => - // '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}') - // .join('&'), - // ); - - // final launchResult = await urlLauncher.launchUrl( - // uri.toString(), - // const LaunchOptions(), - // ); - // if (!launchResult) { - // throw Exception('Failed to launch $uri'); - // } - - // return ShareResult.unavailable; - // } - - // if (!canShare) { - // throw Exception('Navigator.canShare() is false'); - // } - - // try { - // await _navigator.share(data).toDart; - - // // actions is success, but can't get the action name - // return ShareResult.unavailable; - // } on DOMException catch (e) { - // if (e.name case 'AbortError') { - // return _resultDismissed; - // } - - // developer.log( - // 'Failed to share text', - // error: '${e.name}: ${e.message}', - // ); - - // throw Exception('Navigator.share() failed: ${e.message}'); - // } - // } - - // /// Share [XFile] objects. - // /// - // /// Remarks for the web implementation: - // /// This uses the [Web Share API](https://web.dev/web-share/) if it's - // /// available. This builds on the - // /// [`cross_file`](https://pub.dev/packages/cross_file) package. - // @override - // Future shareXFiles( - // List files, { - // String? subject, - // String? text, - // Rect? sharePositionOrigin, - // List? fileNameOverrides, - // }) async { - // assert( - // fileNameOverrides == null || files.length == fileNameOverrides.length); - // final webFiles = []; - // 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; - // if (text != null && text.isNotEmpty) { - // if (subject != null && subject.isNotEmpty) { - // data = ShareData( - // files: webFiles.toJS, - // text: text, - // title: subject, - // ); - // } else { - // data = ShareData( - // files: webFiles.toJS, - // text: text, - // ); - // } - // } else if (subject != null && subject.isNotEmpty) { - // data = ShareData( - // files: webFiles.toJS, - // title: subject, - // ); - // } else { - // data = ShareData( - // files: webFiles.toJS, - // ); - // } - - // final bool canShare; - // try { - // canShare = _navigator.canShare(data); - // } on NoSuchMethodError catch (e) { - // developer.log( - // 'Share API is not supported in this User Agent.', - // error: e, - // ); - - // return _downloadIfFallbackEnabled( - // files, - // fileNameOverrides, - // 'Navigator.canShare() is unavailable', - // ); - // } - - // if (!canShare) { - // return _downloadIfFallbackEnabled( - // files, - // fileNameOverrides, - // 'Navigator.canShare() is false', - // ); - // } - - // try { - // await _navigator.share(data).toDart; - - // // actions is success, but can't get the action name - // return ShareResult.unavailable; - // } on DOMException catch (e) { - // final name = e.name; - // final message = e.message; - - // if (name case 'AbortError') { - // return _resultDismissed; - // } - - // return _downloadIfFallbackEnabled( - // files, - // fileNameOverrides, - // 'Navigator.share() failed: $message', - // ); - // } - // } - - Future _downloadIfFallbackEnabled( - List files, - List? fileNameOverrides, - String message, - ) { - developer.log(message); - if (Share.downloadFallbackEnabled) { - return _download(files, fileNameOverrides); + @override + Future share(ShareParams params) async { + // Prepare share data params + final ShareData data = await prepareData(params); + + // Check if can share + final bool canShare; + try { + canShare = _navigator.canShare(data); + } on NoSuchMethodError catch (e) { + developer.log( + 'Share API is not supported in this User Agent.', + error: e, + ); + + return _fallback(params, 'Navigator.canShare() is unavailable'); + } + + if (!canShare) { + return _fallback(params, 'Navigator.canShare() is false'); + } + + try { + await _navigator.share(data).toDart; + } on DOMException catch (e) { + if (e.name case 'AbortError') { + return _resultDismissed; + } + + developer.log( + 'Failed to share uri', + error: '${e.name}: ${e.message}', + ); + + return _fallback(params, 'Navigator.share() failed: ${e.message}'); + } + + return ShareResult.unavailable; + } + + Future prepareData(ShareParams params) async { + // Prepare share data params + final uri = params.uri?.toString(); + final text = params.text; + final title = params.subject ?? params.title; + ShareData data; + + // Prepare files + final webFiles = []; + if (params.files != null) { + final files = params.files; + if (files != null && files.isNotEmpty == true) { + for (var index = 0; index < files.length; index++) { + final xFile = files[index]; + final filename = params.fileNameOverrides?.elementAt(index); + webFiles.add(await _fromXFile(xFile, nameOverride: filename)); + } + } + } + + if (uri == null && text == null && webFiles.isEmpty) { + throw ArgumentError( + 'At least one of uri, text, or files must be provided', + ); + } + + if (uri != null && text != null) { + throw ArgumentError('Only one of uri or text can be provided'); + } + + if (uri != null) { + data = ShareData( + url: uri, + ); + } else if (webFiles.isNotEmpty && text != null && title != null) { + data = ShareData( + text: text, + title: title, + files: webFiles.toJS, + ); + } else if (webFiles.isNotEmpty && text != null) { + data = ShareData( + text: text, + files: webFiles.toJS, + ); + } else if (webFiles.isNotEmpty && title != null) { + data = ShareData( + title: title, + files: webFiles.toJS, + ); + } else if (webFiles.isNotEmpty) { + data = ShareData( + files: webFiles.toJS, + ); + } else if (text != null && title != null) { + data = ShareData( + text: text, + title: title, + ); } else { - throw Exception(message); + data = ShareData( + text: text!, + ); + } + + return data; + } + + /// Fallback method to when sharing on web fails. + /// If [ShareParams.downloadFallbackEnabled] is true, it will attempt to download the files. + /// If [ShareParams.mailToFallbackEnabled] is true, it will attempt to share text as email. + /// Otherwise, it will throw an exception. + Future _fallback(ShareParams params, String error) async { + developer.log(error); + + final title = params.title ?? params.subject; + final text = params.text ?? params.uri?.toString() ?? ''; + final files = params.files; + final fileNameOverrides = params.fileNameOverrides; + final downloadFallbackEnabled = params.downloadFallbackEnabled; + final mailToFallbackEnabled = params.mailToFallbackEnabled; + + if (files != null && files.isNotEmpty) { + if (downloadFallbackEnabled) { + return _download(files, fileNameOverrides); + } else { + throw Exception(error); + } } + + if (!mailToFallbackEnabled) { + throw Exception(error); + } + + final queryParameters = { + if (title != null) 'subject': title, + 'body': text, + }; + + // see https://github.com/dart-lang/sdk/issues/43838#issuecomment-823551891 + final uri = Uri( + scheme: 'mailto', + query: queryParameters.entries + .map((e) => + '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}') + .join('&'), + ); + + final launchResult = await urlLauncher.launchUrl( + uri.toString(), + const LaunchOptions(), + ); + + if (!launchResult) { + throw Exception(error); + } + + return ShareResult.unavailable; } Future _download( diff --git a/packages/share_plus/share_plus_platform_interface/lib/platform_interface/share_plus_platform.dart b/packages/share_plus/share_plus_platform_interface/lib/platform_interface/share_plus_platform.dart index f951611348..a3b0872afd 100644 --- a/packages/share_plus/share_plus_platform_interface/lib/platform_interface/share_plus_platform.dart +++ b/packages/share_plus/share_plus_platform_interface/lib/platform_interface/share_plus_platform.dart @@ -44,7 +44,9 @@ class ShareParams { /// Used as share sheet title where supported (e.g. EXTRA_TITLE on Android) /// - /// * Supported platforms: ??? + /// Provided to web Navigator Share API as title. + /// + /// * Supported platforms: Android, Web final String? title; /// Only used as email subject where supported (e.g. EXTRA_SUBJECT on Android) @@ -112,6 +114,12 @@ class ShareParams { /// Parameter ignored on other platforms. final bool downloadFallbackEnabled; + /// Whether to fall back to sending an email if [share] fails on web. + /// + /// * Supported platforms: Web + /// Parameter ignored on other platforms. + final bool mailToFallbackEnabled; + ShareParams({ this.text, this.subject, @@ -122,6 +130,7 @@ class ShareParams { this.files, this.fileNameOverrides, this.downloadFallbackEnabled = true, + this.mailToFallbackEnabled = true, }); }