diff --git a/packages/package_info_plus/package_info_plus/README.md b/packages/package_info_plus/package_info_plus/README.md index 513950023c..1be9253799 100644 --- a/packages/package_info_plus/package_info_plus/README.md +++ b/packages/package_info_plus/package_info_plus/README.md @@ -71,6 +71,26 @@ See https://github.com/fluttercommunity/plus_plugins/issues/309 There was an [issue](https://github.com/flutter/flutter/issues/73652) in Flutter, which is already resolved since Flutter 3.3. If your project was created before Flutter 3.3 you need to migrate the project according to [this guide] (https://docs.flutter.dev/release/breaking-changes/windows-version-information) first to get correct version with `package_info_plus` +### Web + +In a web environment, the package uses the `version.json` file that it is generated in the build process. + +#### Accessing the `version.json` + +The package tries to locate the `version.json` using three methods: + +1. Using the provided `baseUrl` in the `fromPlatform()` method. +2. Checking the configured `assets` folder in the Flutter web configuration. +3. Checking the path where the application is installed. + +See the documentation at the method `fromPlatform()` to learn more. + +#### CORS `version.json` access + +It could be possible that the plugin cannot access the `version.json` file because the server is preventing it. +This can be due a CORS issue, and it is known to happen when hosting the Flutter code on Firebase Hosting. +Ensure that your CORS Firebase configuration allows it. + ## Learn more - [API Documentation](https://pub.dev/documentation/package_info_plus/latest/package_info_plus/package_info_plus-library.html) diff --git a/packages/package_info_plus/package_info_plus/android/src/main/kotlin/dev/fluttercommunity/plus/packageinfo/PackageInfoPlugin.kt b/packages/package_info_plus/package_info_plus/android/src/main/kotlin/dev/fluttercommunity/plus/packageinfo/PackageInfoPlugin.kt index e546e38aa1..4cbc084fbf 100644 --- a/packages/package_info_plus/package_info_plus/android/src/main/kotlin/dev/fluttercommunity/plus/packageinfo/PackageInfoPlugin.kt +++ b/packages/package_info_plus/package_info_plus/android/src/main/kotlin/dev/fluttercommunity/plus/packageinfo/PackageInfoPlugin.kt @@ -94,9 +94,9 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin { val signingInfo = packageInfo.signingInfo ?: return null if (signingInfo.hasMultipleSigners()) { - signatureToSha1(signingInfo.apkContentsSigners.first().toByteArray()) + signatureToSha256(signingInfo.apkContentsSigners.first().toByteArray()) } else { - signatureToSha1(signingInfo.signingCertificateHistory.first().toByteArray()) + signatureToSha256(signingInfo.signingCertificateHistory.first().toByteArray()) } } else { val packageInfo = pm.getPackageInfo( @@ -108,7 +108,7 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin { if (signatures.isNullOrEmpty() || packageInfo.signatures.first() == null) { null } else { - signatureToSha1(signatures.first().toByteArray()) + signatureToSha256(signatures.first().toByteArray()) } } } catch (e: PackageManager.NameNotFoundException) { @@ -120,8 +120,8 @@ class PackageInfoPlugin : MethodCallHandler, FlutterPlugin { // Credits https://gist.github.com/scottyab/b849701972d57cf9562e @Throws(NoSuchAlgorithmException::class) - private fun signatureToSha1(sig: ByteArray): String { - val digest = MessageDigest.getInstance("SHA1") + private fun signatureToSha256(sig: ByteArray): String { + val digest = MessageDigest.getInstance("SHA-256") digest.update(sig) val hashText = digest.digest() return bytesToHex(hashText) diff --git a/packages/package_info_plus/package_info_plus/lib/package_info_plus.dart b/packages/package_info_plus/package_info_plus/lib/package_info_plus.dart index df0e9e1b40..1c19e28c9a 100644 --- a/packages/package_info_plus/package_info_plus/lib/package_info_plus.dart +++ b/packages/package_info_plus/package_info_plus/lib/package_info_plus.dart @@ -106,7 +106,9 @@ class PackageInfo { /// Docs about CFBundleVersion: https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion final String buildNumber; - /// The build signature. Empty string on iOS, signing key signature (hex) on Android. + /// The build signature. + /// Empty string on iOS. + /// SHA-256 signing key signature (hex) on Android. final String buildSignature; /// The installer store. Indicates through which store this application was installed. diff --git a/packages/share_plus/share_plus/README.md b/packages/share_plus/share_plus/README.md index d9f480fb12..79b2412f16 100644 --- a/packages/share_plus/share_plus/README.md +++ b/packages/share_plus/share_plus/README.md @@ -11,14 +11,16 @@ A Flutter plugin to share content from your Flutter app via the platform's share dialog. -Wraps the `ACTION_SEND` Intent on Android and `UIActivityViewController` -on iOS. +Wraps the `ACTION_SEND` Intent on Android, `UIActivityViewController` +on iOS, or equivalent platform content sharing methods. ## Platform Support -| Android | iOS | MacOS | Web | Linux | Windows | -| :-----: | :-: | :---: | :-: | :---: | :----: | -| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Method | Android | iOS | MacOS | Web | Linux | Windows | +| :-----------: | :-----: | :-: | :---: | :-: | :---: | :----: | +| `share` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| `shareUri` | ✅ | ✅ | | | | | +| `shareXFiles` | ✅ | ✅ | ✅ | ✅ | | ✅ | Also compatible with Windows and Linux by using "mailto" to share text via Email. @@ -28,15 +30,15 @@ Sharing files is not supported on Linux. To use this plugin, add `share_plus` as a [dependency in your pubspec.yaml file](https://plus.fluttercommunity.dev/docs/overview). -## Example - Import the library. ```dart import 'package:share_plus/share_plus.dart'; ``` -Then invoke the static `share` method anywhere in your Dart code. +### Share Text + +Invoke the static `share()` method anywhere in your Dart code. ```dart Share.share('check out my website https://example.com'); @@ -49,16 +51,18 @@ sharing to email. Share.share('check out my website https://example.com', subject: 'Look what I made!'); ``` -If you are interested in the action your user performed with the share sheet, you can instead use the `shareWithResult` method. +`share()` returns `status` object that allows to check the result of user action in the share sheet. ```dart -final result = await Share.shareWithResult('check out my website https://example.com'); +final result = await Share.share('check out my website https://example.com'); if (result.status == ShareResultStatus.success) { print('Thank you for sharing my website!'); } ``` +### Share Files + To share one or multiple files, invoke the static `shareXFiles` method anywhere in your Dart code. The method returns a `ShareResult`. Optionally, you can pass `subject`, `text` and `sharePositionOrigin`. ```dart @@ -77,7 +81,6 @@ if (result.status == ShareResultStatus.dismissed) { } ``` - On web, you can use `SharePlus.shareXFiles()`. 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 @@ -89,6 +92,24 @@ package. Share.shareXFiles([XFile('assets/hello.txt')], text: 'Great picture'); ``` +### Share URI + +iOS supports fetching metadata from a URI when shared using `UIActivityViewController`. +This special method is only properly supported on iOS. + +```dart +Share.shareUri(uri: uri); +``` + +### Share Results + +All three methods return a `ShareResult` object which contains the following information: + +- `status`: a `ShareResultStatus` +- `raw`: a `String` describing the share result, e.g. the opening app ID. + +Note: `status` will be `ShareResultStatus.unavailable` if the platform does not support identifying the user action. + ## Known Issues ### Sharing data created with XFile.fromData @@ -103,9 +124,13 @@ Alternatively, don't use `XFile.fromData` and instead write the data down to a ` ### Mobile platforms (Android and iOS) -#### Facebook limitations (WhatsApp, Instagram, Facebook Messenger) +#### Meta (WhatsApp, Instagram, Facebook Messenger) and similar apps + +Due to restrictions set up by Meta/Facebook this plugin isn't capable of sharing data reliably to Facebook related apps on Android and iOS. This includes eg. sharing text to the Facebook Messenger. If you require this functionality please check the native Facebook Sharing SDK ([https://developers.facebook.com/docs/sharing](https://developers.facebook.com/docs/sharing)) or search for other Flutter plugins implementing this SDK. More information can be found in [this issue](https://github.com/fluttercommunity/plus_plugins/issues/413). -Due to restrictions set up by Facebook this plugin isn't capable of sharing data reliably to Facebook related apps on Android and iOS. This includes eg. sharing text to the Facebook Messenger. If you require this functionality please check the native Facebook Sharing SDK ([https://developers.facebook.com/docs/sharing](https://developers.facebook.com/docs/sharing)) or search for other Flutter plugins implementing this SDK. More information can be found in [this issue](https://github.com/fluttercommunity/plus_plugins/issues/413). +Other apps may also give problems when attempting to share content to them. +We cannot warranty that a 3rd party app will properly implement the share functionality. +Therefore, all bugs reported regarding compatibility with a specific app will be closed. #### Localization in Apple platforms diff --git a/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/MethodCallHandler.kt b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/MethodCallHandler.kt index a4bff43e0d..d4488922e5 100644 --- a/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/MethodCallHandler.kt +++ b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/MethodCallHandler.kt @@ -1,6 +1,7 @@ package dev.fluttercommunity.plus.share import android.os.Build +import io.flutter.BuildConfig import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import java.io.IOException @@ -14,33 +15,34 @@ internal class MethodCallHandler( override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { expectMapArguments(call) - // The user used a *WithResult method - val isResultRequested = call.method.endsWith("WithResult") // We don't attempt to return a result if the current API version doesn't support it val isWithResult = - isResultRequested && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 - if (isWithResult && !manager.setCallback(result)) return + if (isWithResult) + manager.setCallback(result) try { when (call.method) { "shareUri" -> { share.share( - call.argument("uri") as String, subject = null, withResult = false + call.argument("uri") as String, + subject = null, + withResult = isWithResult, ) - success(isWithResult, isResultRequested, result) + success(isWithResult, result) } - "share", "shareWithResult" -> { + "share" -> { share.share( call.argument("text") as String, call.argument("subject") as String?, isWithResult, ) - success(isWithResult, isResultRequested, result) + success(isWithResult, result) } - "shareFiles", "shareFilesWithResult" -> { + "shareFiles" -> { share.shareFiles( call.argument>("paths")!!, call.argument?>("mimeTypes"), @@ -48,7 +50,7 @@ internal class MethodCallHandler( call.argument("subject"), isWithResult, ) - success(isWithResult, isResultRequested, result) + success(isWithResult, result) } else -> result.notImplemented() @@ -61,15 +63,10 @@ internal class MethodCallHandler( private fun success( isWithResult: Boolean, - isResultRequested: Boolean, result: MethodChannel.Result ) { if (!isWithResult) { - if (isResultRequested) { - result.success("dev.fluttercommunity.plus/share/unavailable") - } else { - result.success(null) - } + result.success("dev.fluttercommunity.plus/share/unavailable") } } diff --git a/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/ShareSuccessManager.kt b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/ShareSuccessManager.kt index 3fdb20dea1..1e80935119 100644 --- a/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/ShareSuccessManager.kt +++ b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/ShareSuccessManager.kt @@ -17,20 +17,22 @@ internal class ShareSuccessManager(private val context: Context) : ActivityResul * Set result callback that will wait for the share-sheet to close and get either * the componentname of the chosen option or an empty string on dismissal. */ - fun setCallback(callback: MethodChannel.Result): Boolean { + fun setCallback(callback: MethodChannel.Result) { return if (isCalledBack.compareAndSet(true, false)) { // Prepare all state for new share SharePlusPendingIntent.result = "" isCalledBack.set(false) this.callback = callback - true } else { - callback.error( - "Share callback error", - "prior share-sheet did not call back, did you await it? Maybe use non-result variant", - null, - ) - false + // Ensure no deadlocks. + // Return result of any waiting call. + // e.g. user called to `share` but did not await for result. + this.callback?.success(RESULT_UNAVAILABLE) + + // Prepare all state for new share + SharePlusPendingIntent.result = "" + isCalledBack.set(false) + this.callback = callback } } 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 067518a710..18caa757f1 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 @@ -12,17 +12,15 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can launch share', (WidgetTester tester) async { - expect(Share.share('message', subject: 'title'), completes); - }, skip: Platform.isMacOS); - - testWidgets('Can launch share in MacOS', (WidgetTester tester) async { + // Check isNotNull because we cannot wait for ShareResult expect(Share.share('message', subject: 'title'), isNotNull); - }, skip: !Platform.isMacOS); - - testWidgets('Can launch shareWithResult', (WidgetTester tester) async { - expect(Share.shareWithResult('message', subject: 'title'), isNotNull); }); + testWidgets('Can launch shareUri', (WidgetTester tester) async { + // Check isNotNull because we cannot wait for ShareResult + expect(Share.shareUri(Uri.parse('https://example.com')), isNotNull); + }, skip: !Platform.isAndroid && !Platform.isIOS); + testWidgets('Can shareXFile created using File.fromData()', (WidgetTester tester) async { final bytes = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]); diff --git a/packages/share_plus/share_plus/example/ios/Flutter/AppFrameworkInfo.plist b/packages/share_plus/share_plus/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105df..7c56964006 100644 --- a/packages/share_plus/share_plus/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/share_plus/share_plus/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/packages/share_plus/share_plus/example/ios/Runner.xcodeproj/project.pbxproj b/packages/share_plus/share_plus/example/ios/Runner.xcodeproj/project.pbxproj index f8a2a332c6..2a3b6e0036 100644 --- a/packages/share_plus/share_plus/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/share_plus/share_plus/example/ios/Runner.xcodeproj/project.pbxproj @@ -97,7 +97,6 @@ 535124FC9BB266447EC60189 /* Pods-RunnerTests.release.xcconfig */, A8F8F8E1F770CE9853568BC6 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -215,7 +214,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/packages/share_plus/share_plus/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/share_plus/share_plus/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e42adcb34c..8e3ca5dfe1 100644 --- a/packages/share_plus/share_plus/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/share_plus/share_plus/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,8 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,11 +45,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSPhotoLibraryUsageDescription + Load photos for sharing functionality UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/packages/share_plus/share_plus/example/lib/main.dart b/packages/share_plus/share_plus/example/lib/main.dart index f89e98ae39..220d626c59 100644 --- a/packages/share_plus/share_plus/example/lib/main.dart +++ b/packages/share_plus/share_plus/example/lib/main.dart @@ -128,21 +128,6 @@ class DemoAppState extends State { icon: const Icon(Icons.add), ), const SizedBox(height: 32), - Builder( - builder: (BuildContext context) { - return ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context).colorScheme.primary, - ), - onPressed: text.isEmpty && imagePaths.isEmpty && uri.isEmpty - ? null - : () => _onShare(context), - child: const Text('Share'), - ); - }, - ), - const SizedBox(height: 16), Builder( builder: (BuildContext context) { return ElevatedButton( @@ -153,7 +138,7 @@ class DemoAppState extends State { onPressed: text.isEmpty && imagePaths.isEmpty ? null : () => _onShareWithResult(context), - child: const Text('Share With Result'), + child: const Text('Share'), ); }, ), @@ -186,7 +171,7 @@ class DemoAppState extends State { }); } - void _onShare(BuildContext context) async { + void _onShareWithResult(BuildContext context) async { // A builder is used to retrieve the context immediately // surrounding the ElevatedButton. // @@ -196,29 +181,6 @@ class DemoAppState extends State { // has its position and size after it's built. final box = context.findRenderObject() as RenderBox?; - if (uri.isNotEmpty) { - await Share.shareUri( - Uri.parse(uri), - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, - ); - } else if (imagePaths.isNotEmpty) { - final files = []; - for (var i = 0; i < imagePaths.length; i++) { - files.add(XFile(imagePaths[i], name: imageNames[i])); - } - await Share.shareXFiles(files, - text: text, - subject: subject, - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); - } else { - await Share.share(text, - subject: subject, - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); - } - } - - void _onShareWithResult(BuildContext context) async { - final box = context.findRenderObject() as RenderBox?; final scaffoldMessenger = ScaffoldMessenger.of(context); ShareResult shareResult; if (imagePaths.isNotEmpty) { @@ -226,14 +188,23 @@ class DemoAppState extends State { for (var i = 0; i < imagePaths.length; i++) { files.add(XFile(imagePaths[i], name: imageNames[i])); } - shareResult = await Share.shareXFiles(files, - text: text, - subject: subject, - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); + shareResult = await Share.shareXFiles( + files, + text: text, + subject: subject, + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); + } else if (uri.isNotEmpty) { + shareResult = await Share.shareUri( + Uri.parse(uri), + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); } else { - shareResult = await Share.shareWithResult(text, - subject: subject, - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); + shareResult = await Share.share( + text, + subject: subject, + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); } scaffoldMessenger.showSnackBar(getResultSnackBar(shareResult)); } diff --git a/packages/share_plus/share_plus/example/macos/Runner.xcodeproj/project.pbxproj b/packages/share_plus/share_plus/example/macos/Runner.xcodeproj/project.pbxproj index 9965b4443c..da39a4ccaf 100644 --- a/packages/share_plus/share_plus/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/share_plus/share_plus/example/macos/Runner.xcodeproj/project.pbxproj @@ -259,7 +259,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/packages/share_plus/share_plus/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/share_plus/share_plus/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index bf62751fe9..54258c2c04 100644 --- a/packages/share_plus/share_plus/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/share_plus/share_plus/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ *)registrar { [shareChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { - BOOL withResult = [call.method hasSuffix:@"WithResult"]; NSDictionary *arguments = [call arguments]; NSNumber *originX = arguments[@"originX"]; NSNumber *originY = arguments[@"originY"]; @@ -261,8 +260,7 @@ + (void)registerWithRegistrar:(NSObject *)registrar { [originWidth doubleValue], [originHeight doubleValue]); } - if ([@"share" isEqualToString:call.method] || - [@"shareWithResult" isEqualToString:call.method]) { + if ([@"share" isEqualToString:call.method]) { NSString *shareText = arguments[@"text"]; NSString *shareSubject = arguments[@"subject"]; @@ -287,12 +285,8 @@ + (void)registerWithRegistrar:(NSObject *)registrar { subject:shareSubject withController:topViewController atSource:originRect - toResult:result - withResult:withResult]; - if (!withResult) - result(nil); - } else if ([@"shareFiles" isEqualToString:call.method] || - [@"shareFilesWithResult" isEqualToString:call.method]) { + toResult:result]; + } else if ([@"shareFiles" isEqualToString:call.method]) { NSArray *paths = arguments[@"paths"]; NSArray *mimeTypes = arguments[@"mimeTypes"]; NSString *subject = arguments[@"subject"]; @@ -329,10 +323,7 @@ + (void)registerWithRegistrar:(NSObject *)registrar { withText:text withController:topViewController atSource:originRect - toResult:result - withResult:withResult]; - if (!withResult) - result(nil); + toResult:result]; } else if ([@"shareUri" isEqualToString:call.method]) { NSString *uri = arguments[@"uri"]; @@ -356,10 +347,7 @@ + (void)registerWithRegistrar:(NSObject *)registrar { [self shareUri:uri withController:topViewController atSource:originRect - toResult:result - withResult:withResult]; - if (!withResult) - result(nil); + toResult:result]; } else { result(FlutterMethodNotImplemented); } @@ -370,8 +358,7 @@ + (void)share:(NSArray *)shareItems withSubject:(NSString *)subject withController:(UIViewController *)controller atSource:(CGRect)origin - toResult:(FlutterResult)result - withResult:(BOOL)withResult { + toResult:(FlutterResult)result { UIActivityViewSuccessController *activityViewController = [[UIActivityViewSuccessController alloc] initWithActivityItems:shareItems applicationActivities:nil]; @@ -408,17 +395,16 @@ + (void)share:(NSArray *)shareItems activityViewController.popoverPresentationController.sourceRect = origin; } - if (withResult) { - UIActivityViewSuccessCompanion *companion = - [[UIActivityViewSuccessCompanion alloc] initWithResult:result]; - activityViewController.companion = companion; - activityViewController.completionWithItemsHandler = - ^(UIActivityType activityType, BOOL completed, NSArray *returnedItems, - NSError *activityError) { - companion.activityType = activityType; - companion.completed = completed; - }; - } + UIActivityViewSuccessCompanion *companion = + [[UIActivityViewSuccessCompanion alloc] initWithResult:result]; + activityViewController.companion = companion; + activityViewController.completionWithItemsHandler = + ^(UIActivityType activityType, BOOL completed, NSArray *returnedItems, + NSError *activityError) { + companion.activityType = activityType; + companion.completed = completed; + }; + [controller presentViewController:activityViewController animated:YES completion:nil]; @@ -427,31 +413,27 @@ + (void)share:(NSArray *)shareItems + (void)shareUri:(NSString *)uri withController:(UIViewController *)controller atSource:(CGRect)origin - toResult:(FlutterResult)result - withResult:(BOOL)withResult { + toResult:(FlutterResult)result { NSURL *data = [NSURL URLWithString:uri]; [self share:@[ data ] withSubject:nil withController:controller atSource:origin - toResult:result - withResult:withResult]; + toResult:result]; } + (void)shareText:(NSString *)shareText subject:(NSString *)subject withController:(UIViewController *)controller atSource:(CGRect)origin - toResult:(FlutterResult)result - withResult:(BOOL)withResult { + toResult:(FlutterResult)result { NSObject *data = [[SharePlusData alloc] initWithSubject:subject text:shareText]; [self share:@[ data ] withSubject:subject withController:controller atSource:origin - toResult:result - withResult:withResult]; + toResult:result]; } + (void)shareFiles:(NSArray *)paths @@ -460,8 +442,7 @@ + (void)shareFiles:(NSArray *)paths withText:(NSString *)text withController:(UIViewController *)controller atSource:(CGRect)origin - toResult:(FlutterResult)result - withResult:(BOOL)withResult { + toResult:(FlutterResult)result { NSMutableArray *items = [[NSMutableArray alloc] init]; for (int i = 0; i < [paths count]; i++) { @@ -480,8 +461,7 @@ + (void)shareFiles:(NSArray *)paths withSubject:subject withController:controller atSource:origin - toResult:result - withResult:withResult]; + toResult:result]; } @end diff --git a/packages/share_plus/share_plus/lib/share_plus.dart b/packages/share_plus/share_plus/lib/share_plus.dart index d36871b794..4430151a78 100644 --- a/packages/share_plus/share_plus/lib/share_plus.dart +++ b/packages/share_plus/share_plus/lib/share_plus.dart @@ -26,13 +26,15 @@ class Share { /// (if available), and the website icon will be extracted and displayed on /// the iOS share sheet. /// - /// The optional `sharePositionOrigin` parameter can be used to specify a global + /// The optional [sharePositionOrigin] parameter can be used to specify a global /// origin rect for the share sheet to popover from on iPads and Macs. It has no effect /// on other devices. /// /// May throw [PlatformException] /// from [MethodChannel]. - static Future shareUri( + /// + /// See documentation about [ShareResult] on [share] method. + static Future shareUri( Uri uri, { Rect? sharePositionOrigin, }) async { @@ -57,65 +59,13 @@ class Share { /// /// May throw [PlatformException] or [FormatException] /// from [MethodChannel]. - static Future share( - String text, { - String? subject, - Rect? sharePositionOrigin, - }) { - assert(text.isNotEmpty); - return _platform.share( - text, - subject: subject, - sharePositionOrigin: sharePositionOrigin, - ); - } - - /// Summons the platform's share sheet to share multiple files. /// - /// Wraps the platform's native share dialog. Can share a file. - /// It uses the `ACTION_SEND` Intent on Android and `UIActivityViewController` - /// on iOS. + /// [ShareResult] provides feedback on how the user + /// interacted with the share-sheet. /// - /// The optional `mimeTypes` parameter can be used to specify MIME types for - /// the provided files. - /// Android supports all natively available MIME types (wildcards like image/* - /// are also supported) and it's considered best practice to avoid mixing - /// unrelated file types (eg. image/jpg & application/pdf). If MIME types are - /// mixed the plugin attempts to find the lowest common denominator. Even - /// if MIME types are supplied the receiving app decides if those are used - /// or handled. - /// On iOS image/jpg, image/jpeg and image/png are handled as images, while - /// every other MIME type is considered a normal file. - /// - /// The optional `sharePositionOrigin` parameter can be used to specify a global - /// origin rect for the share sheet to popover from on iPads and Macs. It has no effect - /// on other devices. - /// - /// May throw [PlatformException] or [FormatException] - /// from [MethodChannel]. - @Deprecated("Use shareXFiles instead.") - static Future shareFiles( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) { - assert(paths.isNotEmpty); - assert(paths.every((element) => element.isNotEmpty)); - return _platform.shareFiles( - paths, - mimeTypes: mimeTypes, - subject: subject, - text: text, - sharePositionOrigin: sharePositionOrigin, - ); - } - - /// Behaves exactly like [share] while providing feedback on how the user - /// interacted with the share-sheet. Until the returned future is completed, - /// any other call to any share method that returns a result _might_ result in - /// a [PlatformException] (on Android). + /// To avoid deadlocks on Android, + /// any new call to [share] when there is a call pending, + /// will cause the previous call to return a [ShareResult.unavailable]. /// /// Because IOS, Android and macOS provide different feedback on share-sheet /// interaction, a result on IOS will be more specific than on Android or macOS. @@ -125,59 +75,23 @@ class Share { /// dismissed the share-sheet. It is not guaranteed that the user actually shared /// something. /// - /// **Currently only implemented on IOS, Android and macOS.** + /// Providing result is only supported on Android, iOS and macOS. /// /// Will gracefully fall back to the non result variant if not implemented /// for the current environment and return [ShareResult.unavailable]. - static Future shareWithResult( + static Future share( String text, { String? subject, Rect? sharePositionOrigin, }) async { assert(text.isNotEmpty); - return _platform.shareWithResult( + return _platform.share( text, subject: subject, sharePositionOrigin: sharePositionOrigin, ); } - /// Behaves exactly like [shareFiles] while providing feedback on how the user - /// interacted with the share-sheet. Until the returned future is completed, - /// any other call to any share method that returns a result _might_ result in - /// a [PlatformException] (on Android). - /// - /// Because IOS, Android and macOS provide different feedback on share-sheet - /// interaction, a result on IOS will be more specific than on Android or macOS. - /// While on IOS the selected action can inform its caller that it was completed - /// or dismissed midway (_actions are free to return whatever they want_), - /// Android and macOS only record if the user selected an action or outright - /// dismissed the share-sheet. It is not guaranteed that the user actually shared - /// something. - /// - /// **Currently only implemented on IOS, Android and macOS.** - /// - /// Will gracefully fall back to the non result variant if not implemented - /// for the current environment and return [ShareResult.unavailable]. - @Deprecated("Use shareXFiles instead.") - static Future shareFilesWithResult( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) async { - assert(paths.isNotEmpty); - assert(paths.every((element) => element.isNotEmpty)); - return _platform.shareFilesWithResult( - paths, - mimeTypes: mimeTypes, - subject: subject, - text: text, - sharePositionOrigin: sharePositionOrigin, - ); - } - /// Summons the platform's share sheet to share multiple files. /// /// Wraps the platform's native share dialog. Can share a file. @@ -193,12 +107,14 @@ class Share { /// On iOS image/jpg, image/jpeg and image/png are handled as images, while /// every other MIME type is considered a normal file. /// - /// The optional `sharePositionOrigin` parameter can be used to specify a global + /// The optional [sharePositionOrigin] parameter can be used to specify a global /// origin rect for the share sheet to popover from on iPads and Macs. It has no effect /// on other devices. /// /// May throw [PlatformException] or [FormatException] /// from [MethodChannel]. + /// + /// See documentation about [ShareResult] on [share] method. static Future shareXFiles( List files, { String? subject, diff --git a/packages/share_plus/share_plus/lib/src/share_plus_linux.dart b/packages/share_plus/share_plus/lib/src/share_plus_linux.dart index f1cd13b7a7..27f74d3f1d 100644 --- a/packages/share_plus/share_plus/lib/src/share_plus_linux.dart +++ b/packages/share_plus/share_plus/lib/src/share_plus_linux.dart @@ -18,9 +18,20 @@ class SharePlusLinuxPlugin extends SharePlatform { SharePlatform.instance = SharePlusLinuxPlugin(UrlLauncherLinux()); } + @override + Future shareUri( + Uri uri, { + String? subject, + String? text, + Rect? sharePositionOrigin, + }) async { + throw UnimplementedError( + 'shareUri() has not been implemented on Linux. Use share().'); + } + /// Share text. @override - Future share( + Future share( String text, { String? subject, Rect? sharePositionOrigin, @@ -46,18 +57,8 @@ class SharePlusLinuxPlugin extends SharePlatform { if (!launchResult) { throw Exception('Failed to launch $uri'); } - } - /// Share files. - @override - Future shareFiles( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) { - throw UnimplementedError('shareFiles() has not been implemented on Linux.'); + return ShareResult.unavailable; } /// Share [XFile] objects with Result. 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 a24ed4e3da..567e7c802a 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 @@ -30,7 +30,7 @@ class SharePlusWebPlugin extends SharePlatform { }) : _navigator = debugNavigator ?? web.window.navigator; @override - Future shareUri( + Future shareUri( Uri uri, { Rect? sharePositionOrigin, }) async { @@ -47,39 +47,29 @@ class SharePlusWebPlugin extends SharePlatform { error: e, ); - return; + throw Exception('Navigator.canShare() is unavailable'); } if (!canShare) { - return; + throw Exception('Navigator.canShare() is false'); } try { await _navigator.share(data).toDart; } on web.DOMException catch (e) { - // Ignore DOMException developer.log( 'Failed to share uri', error: '${e.name}: ${e.message}', ); + + throw Exception('Navigator.share() failed: ${e.message}'); } - } - @override - Future share( - String text, { - String? subject, - Rect? sharePositionOrigin, - }) async { - await shareWithResult( - text, - subject: subject, - sharePositionOrigin: sharePositionOrigin, - ); + return ShareResult.unavailable; } @override - Future shareWithResult( + Future share( String text, { String? subject, Rect? sharePositionOrigin, @@ -128,18 +118,18 @@ class SharePlusWebPlugin extends SharePlatform { throw Exception('Failed to launch $uri'); } - return _resultUnavailable; + return ShareResult.unavailable; } if (!canShare) { - return _resultUnavailable; + throw Exception('Navigator.canShare() is false'); } try { await _navigator.share(data).toDart; // actions is success, but can't get the action name - return _resultUnavailable; + return ShareResult.unavailable; } on web.DOMException catch (e) { if (e.name case 'AbortError') { return _resultDismissed; @@ -149,46 +139,9 @@ class SharePlusWebPlugin extends SharePlatform { 'Failed to share text', error: '${e.name}: ${e.message}', ); - } - - return _resultUnavailable; - } - @override - Future shareFiles( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) async { - await shareFilesWithResult( - paths, - mimeTypes: mimeTypes, - subject: subject, - text: text, - sharePositionOrigin: sharePositionOrigin, - ); - } - - @override - Future shareFilesWithResult( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) async { - final files = []; - for (var i = 0; i < paths.length; i++) { - files.add(XFile(paths[i], mimeType: mimeTypes?[i])); + throw Exception('Navigator.share() failed: ${e.message}'); } - return shareXFiles( - files, - subject: subject, - text: text, - sharePositionOrigin: sharePositionOrigin, - ); } /// Share [XFile] objects. @@ -243,18 +196,18 @@ class SharePlusWebPlugin extends SharePlatform { error: e, ); - return _resultUnavailable; + throw Exception('Navigator.canShare() is unavailable'); } if (!canShare) { - return _resultUnavailable; + throw Exception('Navigator.canShare() is false'); } try { await _navigator.share(data).toDart; // actions is success, but can't get the action name - return _resultUnavailable; + return ShareResult.unavailable; } on web.DOMException catch (e) { if (e.name case 'AbortError') { return _resultDismissed; @@ -264,9 +217,9 @@ class SharePlusWebPlugin extends SharePlatform { 'Failed to share files', error: '${e.name}: ${e.message}', ); - } - return _resultUnavailable; + throw Exception('Navigator.share() failed: ${e.message}'); + } } static Future _fromXFile(XFile file) async { @@ -290,11 +243,6 @@ const _resultDismissed = ShareResult( ShareResultStatus.dismissed, ); -const _resultUnavailable = ShareResult( - 'dev.fluttercommunity.plus/share/unavailable', - ShareResultStatus.unavailable, -); - extension on web.Navigator { /// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/canShare external bool canShare(ShareData data); diff --git a/packages/share_plus/share_plus/lib/src/share_plus_windows.dart b/packages/share_plus/share_plus/lib/src/share_plus_windows.dart index 05f49880cb..cb2cba9ad6 100644 --- a/packages/share_plus/share_plus/lib/src/share_plus_windows.dart +++ b/packages/share_plus/share_plus/lib/src/share_plus_windows.dart @@ -23,9 +23,20 @@ class SharePlusWindowsPlugin extends SharePlatform { } } + @override + Future shareUri( + Uri uri, { + String? subject, + String? text, + Rect? sharePositionOrigin, + }) async { + throw UnimplementedError( + 'shareUri() has not been implemented on Windows. Use share().'); + } + /// Share text. @override - Future share( + Future share( String text, { String? subject, Rect? sharePositionOrigin, @@ -51,20 +62,8 @@ class SharePlusWindowsPlugin extends SharePlatform { if (!launchResult) { throw Exception('Failed to launch $uri'); } - } - /// Share files. - @override - Future shareFiles( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) { - throw UnimplementedError( - 'shareFiles() is only available for Windows versions higher than 10.0.${VersionHelper.kWindows10RS5BuildNumber}.', - ); + return ShareResult.unavailable; } /// Share [XFile] objects with Result. diff --git a/packages/share_plus/share_plus/macos/Classes/SharePlusMacosPlugin.swift b/packages/share_plus/share_plus/macos/Classes/SharePlusMacosPlugin.swift index f50a219997..414467e1f3 100644 --- a/packages/share_plus/share_plus/macos/Classes/SharePlusMacosPlugin.swift +++ b/packages/share_plus/share_plus/macos/Classes/SharePlusMacosPlugin.swift @@ -21,20 +21,10 @@ public class SharePlusMacosPlugin: NSObject, FlutterPlugin, NSSharingServicePick switch call.method { case "share": - let text = args["text"] as! String - let subject = args["subject"] as? String - shareItems([text], subject: subject, origin: origin, view: registrar.view!) - result(true) - case "shareFiles": - let paths = args["paths"] as! [String] - let urls = paths.map { NSURL.fileURL(withPath: $0) } - shareItems(urls, origin: origin, view: registrar.view!) - result(true) - case "shareWithResult": let text = args["text"] as! String let subject = args["subject"] as? String shareItems([text], subject: subject, origin: origin, view: registrar.view!, callback: result) - case "shareFilesWithResult": + case "shareFiles": let paths = args["paths"] as! [String] let urls = paths.map { NSURL.fileURL(withPath: $0) } shareItems(urls, origin: origin, view: registrar.view!, callback: result) @@ -48,15 +38,10 @@ public class SharePlusMacosPlugin: NSObject, FlutterPlugin, NSSharingServicePick return sharingService.delegate } - private func shareItems(_ items: [Any], subject: String? = nil, origin: NSRect, view: NSView, callback: FlutterResult? = nil) { + private func shareItems(_ items: [Any], subject: String? = nil, origin: NSRect, view: NSView, callback: @escaping FlutterResult) { DispatchQueue.main.async { let picker = NSSharingServicePicker(items: items) - if callback != nil { - picker.delegate = SharePlusMacosSuccessDelegate(subject: subject, callback: callback!).keep() - } else { - picker.delegate = self - self.subject = subject - } + picker.delegate = SharePlusMacosSuccessDelegate(subject: subject, callback: callback).keep() picker.show(relativeTo: origin, of: view, preferredEdge: NSRectEdge.maxY) } } diff --git a/packages/share_plus/share_plus/windows/share_plus_plugin.cpp b/packages/share_plus/share_plus/windows/share_plus_plugin.cpp index 686cde4985..4fff212571 100644 --- a/packages/share_plus/share_plus/windows/share_plus_plugin.cpp +++ b/packages/share_plus/share_plus/windows/share_plus_plugin.cpp @@ -93,10 +93,7 @@ HRESULT SharePlusWindowsPlugin::GetStorageFileFromPath( void SharePlusWindowsPlugin::HandleMethodCall( const flutter::MethodCall &method_call, std::unique_ptr> result) { - if (method_call.method_name().compare(kShare) == 0 || - method_call.method_name().compare(kShareWithResult) == 0) { - auto is_result_requested = - method_call.method_name().compare(kShareWithResult) == 0; + if (method_call.method_name().compare(kShare) == 0) { auto data_transfer_manager = GetDataTransferManager(); auto args = std::get(*method_call.arguments()); if (auto text_value = @@ -142,15 +139,8 @@ void SharePlusWindowsPlugin::HandleMethodCall( if (data_transfer_manager_interop_ != nullptr) { data_transfer_manager_interop_->ShowShareUIForWindow(GetWindow()); } - if (is_result_requested) { - result->Success(flutter::EncodableValue(kShareResultUnavailable)); - } else { - result->Success(flutter::EncodableValue()); - } - } else if (method_call.method_name().compare(kShareFiles) == 0 || - method_call.method_name().compare(kShareFilesWithResult) == 0) { - auto is_result_requested = - method_call.method_name().compare(kShareFilesWithResult) == 0; + result->Success(flutter::EncodableValue(kShareResultUnavailable)); + } else if (method_call.method_name().compare(kShareFiles) == 0) { auto data_transfer_manager = GetDataTransferManager(); auto args = std::get(*method_call.arguments()); if (auto text_value = @@ -238,11 +228,7 @@ void SharePlusWindowsPlugin::HandleMethodCall( if (data_transfer_manager_interop_ != nullptr) { data_transfer_manager_interop_->ShowShareUIForWindow(GetWindow()); } - if (is_result_requested) { - result->Success(flutter::EncodableValue(kShareResultUnavailable)); - } else { - result->Success(flutter::EncodableValue()); - } + result->Success(flutter::EncodableValue(kShareResultUnavailable)); } else { result->NotImplemented(); } diff --git a/packages/share_plus/share_plus/windows/share_plus_windows_plugin.h b/packages/share_plus/share_plus/windows/share_plus_windows_plugin.h index 83d201c829..848be0091f 100644 --- a/packages/share_plus/share_plus/windows/share_plus_windows_plugin.h +++ b/packages/share_plus/share_plus/windows/share_plus_windows_plugin.h @@ -46,8 +46,6 @@ class SharePlusWindowsPlugin : public flutter::Plugin { static constexpr auto kShare = "share"; static constexpr auto kShareFiles = "shareFiles"; - static constexpr auto kShareWithResult = "shareWithResult"; - static constexpr auto kShareFilesWithResult = "shareFilesWithResult"; HWND GetWindow(); 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 ccbffe858e..b512f4b2a9 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 @@ -24,10 +24,10 @@ class MethodChannelShare extends SharePlatform { MethodChannel('dev.fluttercommunity.plus/share'); @override - Future shareUri( + Future shareUri( Uri uri, { Rect? sharePositionOrigin, - }) { + }) async { final params = {'uri': uri.toString()}; if (sharePositionOrigin != null) { @@ -37,16 +37,19 @@ class MethodChannelShare extends SharePlatform { params['originHeight'] = sharePositionOrigin.height; } - return channel.invokeMethod('shareUri', params); + final result = await channel.invokeMethod('shareUri', params) ?? + 'dev.fluttercommunity.plus/share/unavailable'; + + return ShareResult(result, _statusFromResult(result)); } /// Summons the platform's share sheet to share text. @override - Future share( + Future share( String text, { String? subject, Rect? sharePositionOrigin, - }) { + }) async { assert(text.isNotEmpty); final params = { 'text': text, @@ -60,81 +63,36 @@ class MethodChannelShare extends SharePlatform { params['originHeight'] = sharePositionOrigin.height; } - return channel.invokeMethod('share', params); + final result = await channel.invokeMethod('share', params) ?? + 'dev.fluttercommunity.plus/share/unavailable'; + + return ShareResult(result, _statusFromResult(result)); } /// Summons the platform's share sheet to share multiple files. @override - Future shareFiles( - List paths, { - List? mimeTypes, + Future shareXFiles( + List files, { String? subject, String? text, Rect? sharePositionOrigin, - }) { - assert(paths.isNotEmpty); - assert(paths.every((element) => element.isNotEmpty)); - final params = { - 'paths': paths, - 'mimeTypes': mimeTypes ?? - paths.map((String path) => _mimeTypeForPath(path)).toList(), - }; - - if (subject != null) params['subject'] = subject; - if (text != null) params['text'] = text; - - if (sharePositionOrigin != null) { - params['originX'] = sharePositionOrigin.left; - params['originY'] = sharePositionOrigin.top; - params['originWidth'] = sharePositionOrigin.width; - params['originHeight'] = sharePositionOrigin.height; - } - - return channel.invokeMethod('shareFiles', params); - } - - /// Summons the platform's share sheet to share text and returns the result. - @override - Future shareWithResult( - String text, { - String? subject, - Rect? sharePositionOrigin, }) async { - assert(text.isNotEmpty); - final params = { - 'text': text, - 'subject': subject, - }; + assert(files.isNotEmpty); - if (sharePositionOrigin != null) { - params['originX'] = sharePositionOrigin.left; - params['originY'] = sharePositionOrigin.top; - params['originWidth'] = sharePositionOrigin.width; - params['originHeight'] = sharePositionOrigin.height; - } + final filesWithPath = await _getFiles(files); + assert(filesWithPath.every((element) => element.path.isNotEmpty)); - final result = - await channel.invokeMethod('shareWithResult', params) ?? - 'dev.fluttercommunity.plus/share/unavailable'; + final mimeTypes = filesWithPath + .map((e) => e.mimeType ?? _mimeTypeForPath(e.path)) + .toList(); - return ShareResult(result, _statusFromResult(result)); - } + final paths = filesWithPath.map((e) => e.path).toList(); + assert(paths.length == mimeTypes.length); + assert(mimeTypes.every((element) => element.isNotEmpty)); - /// Summons the platform's share sheet to share multiple files and returns the result. - @override - Future shareFilesWithResult( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) async { - assert(paths.isNotEmpty); - assert(paths.every((element) => element.isNotEmpty)); final params = { 'paths': paths, - 'mimeTypes': mimeTypes ?? - paths.map((String path) => _mimeTypeForPath(path)).toList(), + 'mimeTypes': mimeTypes, }; if (subject != null) params['subject'] = subject; @@ -147,36 +105,12 @@ class MethodChannelShare extends SharePlatform { params['originHeight'] = sharePositionOrigin.height; } - final result = - await channel.invokeMethod('shareFilesWithResult', params) ?? - 'dev.fluttercommunity.plus/share/unavailable'; + final result = await channel.invokeMethod('shareFiles', params) ?? + 'dev.fluttercommunity.plus/share/unavailable'; return ShareResult(result, _statusFromResult(result)); } - /// Summons the platform's share sheet to share multiple files. - @override - Future shareXFiles( - List files, { - String? subject, - String? text, - Rect? sharePositionOrigin, - }) async { - final filesWithPath = await _getFiles(files); - - final mimeTypes = filesWithPath - .map((e) => e.mimeType ?? _mimeTypeForPath(e.path)) - .toList(); - - return shareFilesWithResult( - filesWithPath.map((e) => e.path).toList(), - mimeTypes: mimeTypes, - subject: subject, - text: text, - sharePositionOrigin: sharePositionOrigin, - ); - } - /// Ensure that a file is readable from the file system. Will create file on-demand under TemporaryDiectory and return the temporary file otherwise. /// /// if file doesn't contain path, 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 7bc3bb7963..08df3cc8fe 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 @@ -32,7 +32,7 @@ class SharePlatform extends PlatformInterface { } /// Share uri. - Future shareUri( + Future shareUri( Uri uri, { Rect? sharePositionOrigin, }) { @@ -42,70 +42,17 @@ class SharePlatform extends PlatformInterface { ); } - /// Share text. - Future share( - String text, { - String? subject, - Rect? sharePositionOrigin, - }) { - return _instance.share( - text, - subject: subject, - sharePositionOrigin: sharePositionOrigin, - ); - } - - /// Share files. - @Deprecated("Use shareXFiles instead.") - Future shareFiles( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) { - return _instance.shareFiles( - paths, - mimeTypes: mimeTypes, - subject: subject, - text: text, - sharePositionOrigin: sharePositionOrigin, - ); - } - /// Share text with Result. - Future shareWithResult( + Future share( String text, { String? subject, Rect? sharePositionOrigin, }) async { - await _instance.share( + return await _instance.share( text, subject: subject, sharePositionOrigin: sharePositionOrigin, ); - - return _resultUnavailable; - } - - /// Share files with Result. - @Deprecated("Use shareXFiles instead.") - Future shareFilesWithResult( - List paths, { - List? mimeTypes, - String? subject, - String? text, - Rect? sharePositionOrigin, - }) async { - await _instance.shareFiles( - paths, - mimeTypes: mimeTypes, - subject: subject, - text: text, - sharePositionOrigin: sharePositionOrigin, - ); - - return _resultUnavailable; } /// Share [XFile] objects with Result. @@ -143,6 +90,26 @@ class ShareResult { final ShareResultStatus status; const ShareResult(this.raw, this.status); + + static const unavailable = ShareResult( + 'dev.fluttercommunity.plus/share/unavailable', + ShareResultStatus.unavailable, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ShareResult && other.raw == raw && other.status == status; + } + + @override + int get hashCode => raw.hashCode ^ status.hashCode; + + @override + String toString() { + return 'ShareResult(raw: $raw, status: $status)'; + } } /// How the user handled the share-sheet @@ -153,12 +120,7 @@ enum ShareResultStatus { /// The user dismissed the share-sheet dismissed, - /// The status can not be determined + /// The platform succeed to share content to user + /// but the user action can not be determined unavailable, } - -/// Returned if the platform is not supported -const _resultUnavailable = ShareResult( - 'dev.fluttercommunity.plus/share/unavailable', - ShareResultStatus.unavailable, -); 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 cc05d3c52a..03c796bddc 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 @@ -55,7 +55,7 @@ void main() { throwsA(const TypeMatcher()), ); expect( - () => SharePlatform.instance.shareWithResult(''), + () => SharePlatform.instance.share(''), throwsA(const TypeMatcher()), ); verifyZeroInteractions(mockChannel); @@ -66,7 +66,7 @@ void main() { Uri.parse('https://pub.dev/packages/share_plus'), sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), ); - verify(mockChannel.invokeMethod('shareUri', { + verify(mockChannel.invokeMethod('shareUri', { 'uri': 'https://pub.dev/packages/share_plus', 'originX': 1.0, 'originY': 2.0, @@ -79,21 +79,7 @@ void main() { subject: 'some subject to share', sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), ); - verify(mockChannel.invokeMethod('share', { - 'text': 'some text to share', - 'subject': 'some subject to share', - 'originX': 1.0, - 'originY': 2.0, - 'originWidth': 3.0, - 'originHeight': 4.0, - })); - - await SharePlatform.instance.shareWithResult( - 'some text to share', - subject: 'some subject to share', - sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), - ); - verify(mockChannel.invokeMethod('shareWithResult', { + verify(mockChannel.invokeMethod('share', { 'text': 'some text to share', 'subject': 'some subject to share', 'originX': 1.0, @@ -103,56 +89,14 @@ void main() { })); await withFile('tempfile-83649a.png', (File fd) async { - // ignore: deprecated_member_use_from_same_package - await sharePlatform.shareFiles( - [fd.path], - subject: 'some subject to share', - text: 'some text to share', - sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), - ); - verify(mockChannel.invokeMethod( - 'shareFiles', - { - 'paths': [fd.path], - 'mimeTypes': ['image/png'], - 'subject': 'some subject to share', - 'text': 'some text to share', - 'originX': 1.0, - 'originY': 2.0, - 'originWidth': 3.0, - 'originHeight': 4.0, - }, - )); - - // ignore: deprecated_member_use_from_same_package - await SharePlatform.instance.shareFilesWithResult( - [fd.path], - subject: 'some subject to share', - text: 'some text to share', - sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), - ); - verify(mockChannel.invokeMethod( - 'shareFilesWithResult', - { - 'paths': [fd.path], - 'mimeTypes': ['image/png'], - 'subject': 'some subject to share', - 'text': 'some text to share', - 'originX': 1.0, - 'originY': 2.0, - 'originWidth': 3.0, - 'originHeight': 4.0, - }, - )); - await sharePlatform.shareXFiles( [XFile(fd.path)], subject: 'some subject to share', text: 'some text to share', sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), ); - verify(mockChannel.invokeMethod( - 'shareFilesWithResult', + verify(mockChannel.invokeMethod( + 'shareFiles', { 'paths': [fd.path], 'mimeTypes': ['image/png'], @@ -167,39 +111,10 @@ void main() { }); }); - test('sharing empty file fails', () { - expect( - // ignore: deprecated_member_use_from_same_package - () => sharePlatform.shareFiles(['']), - throwsA(const TypeMatcher()), - ); - expect( - // ignore: deprecated_member_use_from_same_package - () => SharePlatform.instance.shareFilesWithResult(['']), - throwsA(const TypeMatcher()), - ); - - verifyZeroInteractions(mockChannel); - }); - test('sharing file sets correct mimeType', () async { await withFile('tempfile-83649b.png', (File fd) async { - // ignore: deprecated_member_use_from_same_package - await sharePlatform.shareFiles([fd.path]); - verify(mockChannel.invokeMethod('shareFiles', { - 'paths': [fd.path], - 'mimeTypes': ['image/png'], - })); - - // ignore: deprecated_member_use_from_same_package - await SharePlatform.instance.shareFilesWithResult([fd.path]); - verify(mockChannel.invokeMethod('shareFilesWithResult', { - 'paths': [fd.path], - 'mimeTypes': ['image/png'], - })); - await sharePlatform.shareXFiles([XFile(fd.path)]); - verify(mockChannel.invokeMethod('shareFilesWithResult', { + verify(mockChannel.invokeMethod('shareFiles', { 'paths': [fd.path], 'mimeTypes': ['image/png'], })); @@ -208,23 +123,8 @@ void main() { test('sharing file sets passed mimeType', () async { await withFile('tempfile-83649c.png', (File fd) async { - // ignore: deprecated_member_use_from_same_package - await sharePlatform.shareFiles([fd.path], mimeTypes: ['*/*']); - verify(mockChannel.invokeMethod('shareFiles', { - 'paths': [fd.path], - 'mimeTypes': ['*/*'], - })); - - await SharePlatform.instance - // ignore: deprecated_member_use_from_same_package - .shareFilesWithResult([fd.path], mimeTypes: ['*/*']); - verify(mockChannel.invokeMethod('shareFilesWithResult', { - 'paths': [fd.path], - 'mimeTypes': ['*/*'], - })); - await sharePlatform.shareXFiles([XFile(fd.path, mimeType: '*/*')]); - verify(mockChannel.invokeMethod('shareFilesWithResult', { + verify(mockChannel.invokeMethod('shareFiles', { 'paths': [fd.path], 'mimeTypes': ['*/*'], })); @@ -238,26 +138,25 @@ void main() { ); expect( - sharePlatform.shareWithResult('some text to share'), + sharePlatform.share('some text to share'), completion(equals(resultUnavailable)), ); await withFile('tempfile-83649d.png', (File fd) async { expect( - // ignore: deprecated_member_use_from_same_package - sharePlatform.shareFilesWithResult([fd.path]), + sharePlatform.shareXFiles([XFile(fd.path)]), completion(equals(resultUnavailable)), ); }); }); test('withResult methods invoke normal share on non IOS & Android', () async { - await sharePlatform.shareWithResult( + await sharePlatform.share( 'some text to share', subject: 'some subject to share', sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), ); - verify(mockChannel.invokeMethod('share', { + verify(mockChannel.invokeMethod('share', { 'text': 'some text to share', 'subject': 'some subject to share', 'originX': 1.0, @@ -266,18 +165,9 @@ void main() { 'originHeight': 4.0, })); - await withFile('tempfile-83649e.png', (File fd) async { - // ignore: deprecated_member_use_from_same_package - await sharePlatform.shareFilesWithResult([fd.path]); - verify(mockChannel.invokeMethod('shareFiles', { - 'paths': [fd.path], - 'mimeTypes': ['image/png'], - })); - }); - await withFile('tempfile-83649e.png', (File fd) async { await sharePlatform.shareXFiles([XFile(fd.path)]); - verify(mockChannel.invokeMethod('shareFilesWithResult', { + verify(mockChannel.invokeMethod('shareFiles', { 'paths': [fd.path], 'mimeTypes': ['image/png'], }));