diff --git a/packages/share_plus/share_plus/README.md b/packages/share_plus/share_plus/README.md index 17211bd542..ae29590b32 100644 --- a/packages/share_plus/share_plus/README.md +++ b/packages/share_plus/share_plus/README.md @@ -110,6 +110,17 @@ if (result.status == ShareResultStatus.dismissed) { } ``` +On iOS or macOS, if you want to exclude certain options from appearing in your share sheet, +you can set the excludedActivityTypes array. +For the list of supported excludedActivityTypes, you can refer to [CupertinoActivityType](https://pub.dev/documentation/share_plus/latest/share_plus/ShareParams-class.html). + +```dart +ShareParams( + // rest of params + excludedActivityTypes: [CupertinoActivityType.postToFacebook], +) +``` + 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 diff --git a/packages/share_plus/share_plus/example/lib/excluded_activity_type.dart b/packages/share_plus/share_plus/example/lib/excluded_activity_type.dart new file mode 100644 index 0000000000..f1bb63b7e1 --- /dev/null +++ b/packages/share_plus/share_plus/example/lib/excluded_activity_type.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:share_plus/share_plus.dart'; + +class ExcludedCupertinoActivityTypePage extends StatefulWidget { + final List? excludedActivityType; + + const ExcludedCupertinoActivityTypePage({ + super.key, + this.excludedActivityType, + }); + + @override + State createState() => _ExcludedActivityTypePageState(); +} + +class _ExcludedActivityTypePageState + extends State { + final List options = []; + final List selected = []; + + @override + void initState() { + for (final type in CupertinoActivityType.values) { + options.add(type.value); + } + if (widget.excludedActivityType != null && + widget.excludedActivityType!.isNotEmpty) { + for (final type in widget.excludedActivityType!) { + selected.add(type.value); + } + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Excluded Activity Type'), + actions: [ + IconButton( + icon: const Icon(Icons.check), + onPressed: () { + final List tempSelected = []; + for (final String type in selected) { + tempSelected.add( + CupertinoActivityType.values + .firstWhere((e) => e.value == type), + ); + } + Navigator.pop(context, tempSelected); + }, + ), + ], + ), + body: ListView( + children: options.map((option) { + return CheckboxListTile( + value: selected.contains(option), + title: Text(option), + controlAffinity: ListTileControlAffinity.leading, + onChanged: (bool? checked) { + setState(() { + if (checked == true) { + selected.add(option); + } else { + selected.remove(option); + } + }); + }, + ); + }).toList(), + ), + ); + } +} diff --git a/packages/share_plus/share_plus/example/lib/main.dart b/packages/share_plus/share_plus/example/lib/main.dart index e698caa98c..3e4a137ba9 100644 --- a/packages/share_plus/share_plus/example/lib/main.dart +++ b/packages/share_plus/share_plus/example/lib/main.dart @@ -6,7 +6,6 @@ import 'dart:convert'; import 'dart:io'; - import 'package:file_selector/file_selector.dart' hide XFile; // hides to test if share_plus exports XFile import 'package:flutter/foundation.dart'; @@ -16,20 +15,37 @@ import 'package:image_picker/image_picker.dart' hide XFile; // hides to test if share_plus exports XFile import 'package:share_plus/share_plus.dart'; +import 'excluded_activity_type.dart'; import 'image_previews.dart'; void main() { - runApp(const DemoApp()); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Share Plus Plugin Demo', + theme: ThemeData( + useMaterial3: true, + colorSchemeSeed: const Color(0x9f4376f8), + ), + home: const MyHomePage(), + ); + } } -class DemoApp extends StatefulWidget { - const DemoApp({super.key}); +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); @override - DemoAppState createState() => DemoAppState(); + State createState() => MyHomePageState(); } -class DemoAppState extends State { +class MyHomePageState extends State { String text = ''; String subject = ''; String title = ''; @@ -37,170 +53,170 @@ class DemoAppState extends State { String fileName = ''; List imageNames = []; List imagePaths = []; + List excludedCupertinoActivityType = []; @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Share Plus Plugin Demo', - theme: ThemeData( - useMaterial3: true, - colorSchemeSeed: const Color(0x9f4376f8), + return Scaffold( + appBar: AppBar( + title: const Text('Share Plus Plugin Demo'), + elevation: 4, ), - home: Scaffold( - appBar: AppBar( - title: const Text('Share Plus Plugin Demo'), - elevation: 4, - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Share text', - hintText: 'Enter some text and/or link to share', - ), - maxLines: null, - onChanged: (String value) => setState(() { - text = value; - }), - ), - const SizedBox(height: 16), - TextField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Share subject', - hintText: 'Enter subject to share (optional)', - ), - maxLines: null, - onChanged: (String value) => setState(() { - subject = value; - }), - ), - const SizedBox(height: 16), - TextField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Share title', - hintText: 'Enter title to share (optional)', - ), - maxLines: null, - onChanged: (String value) => setState(() { - title = value; - }), + body: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Share text', + hintText: 'Enter some text and/or link to share', ), - const SizedBox(height: 16), - TextField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Share uri', - hintText: 'Enter the uri you want to share', - ), - maxLines: null, - onChanged: (String value) { - setState(() => uri = value); - }, + maxLines: null, + onChanged: (String value) => setState(() { + text = value; + }), + ), + const SizedBox(height: 16), + TextField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Share subject', + hintText: 'Enter subject to share (optional)', ), - 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); - }, + maxLines: null, + onChanged: (String value) => setState(() { + subject = value; + }), + ), + const SizedBox(height: 16), + TextField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Share title', + hintText: 'Enter title to share (optional)', ), - const SizedBox(height: 16), - ImagePreviews(imagePaths, onDelete: _onDeleteImage), - ElevatedButton.icon( - label: const Text('Add image'), - onPressed: () async { - // Using `package:image_picker` to get image from gallery. - if (!kIsWeb && - (Platform.isMacOS || - Platform.isLinux || - Platform.isWindows)) { - // Using `package:file_selector` on windows, macos & Linux, since `package:image_picker` is not supported. - const XTypeGroup typeGroup = XTypeGroup( - label: 'images', - extensions: ['jpg', 'jpeg', 'png', 'gif'], - ); - final file = await openFile( - acceptedTypeGroups: [typeGroup]); - if (file != null) { - setState(() { - imagePaths.add(file.path); - imageNames.add(file.name); - }); - } - } else { - final imagePicker = ImagePicker(); - final pickedFile = await imagePicker.pickImage( - source: ImageSource.gallery, - ); - if (pickedFile != null) { - setState(() { - imagePaths.add(pickedFile.path); - imageNames.add(pickedFile.name); - }); - } - } - }, - icon: const Icon(Icons.add), + maxLines: null, + onChanged: (String value) => setState(() { + title = value; + }), + ), + const SizedBox(height: 16), + TextField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Share uri', + hintText: 'Enter the uri you want to share', ), - 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 - ? null - : () => _onShareWithResult(context), - child: const Text('Share'), - ); - }, + maxLines: null, + onChanged: (String value) { + setState(() => uri = value); + }, + ), + 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', ), - 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: () { - _onShareXFileFromAssets(context); - }, - child: const Text('Share XFile from Assets'), + maxLines: null, + onChanged: (String value) { + setState(() => fileName = value); + }, + ), + const SizedBox(height: 16), + ImagePreviews(imagePaths, onDelete: _onDeleteImage), + ElevatedButton.icon( + label: const Text('Add image'), + onPressed: () async { + // Using `package:image_picker` to get image from gallery. + if (!kIsWeb && + (Platform.isMacOS || + Platform.isLinux || + Platform.isWindows)) { + // Using `package:file_selector` on windows, macos & Linux, since `package:image_picker` is not supported. + const XTypeGroup typeGroup = XTypeGroup( + label: 'images', + extensions: ['jpg', 'jpeg', 'png', 'gif'], ); - }, - ), - 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'), + final file = await openFile( + acceptedTypeGroups: [typeGroup]); + if (file != null) { + setState(() { + imagePaths.add(file.path); + imageNames.add(file.name); + }); + } + } else { + final imagePicker = ImagePicker(); + final pickedFile = await imagePicker.pickImage( + source: ImageSource.gallery, ); - }, + if (pickedFile != null) { + setState(() { + imagePaths.add(pickedFile.path); + imageNames.add(pickedFile.name); + }); + } + } + }, + icon: const Icon(Icons.add), + ), + if (Platform.isIOS || Platform.isMacOS) const SizedBox(height: 16), + if (Platform.isIOS || Platform.isMacOS) + ElevatedButton( + onPressed: _onSelectExcludedActivityType, + child: const Text('Add Excluded Activity Type'), ), - ], - ), + 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 + ? null + : () => _onShareWithResult(context), + child: const Text('Share'), + ); + }, + ), + 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: () { + _onShareXFileFromAssets(context); + }, + child: const Text('Share XFile from Assets'), + ); + }, + ), + 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'), + ); + }, + ), + ], ), ), ); @@ -213,6 +229,19 @@ class DemoAppState extends State { }); } + void _onSelectExcludedActivityType() async { + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ExcludedCupertinoActivityTypePage( + excludedActivityType: excludedCupertinoActivityType, + ), + ), + ); + if (result != null) { + excludedCupertinoActivityType = result; + } + } + void _onShareWithResult(BuildContext context) async { // A builder is used to retrieve the context immediately // surrounding the ElevatedButton. @@ -237,6 +266,7 @@ class DemoAppState extends State { title: title.isEmpty ? null : title, files: files, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + excludedCupertinoActivities: [CupertinoActivityType.airDrop], ), ); } else if (uri.isNotEmpty) { @@ -246,6 +276,7 @@ class DemoAppState extends State { subject: subject.isEmpty ? null : subject, title: title.isEmpty ? null : title, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + excludedCupertinoActivities: excludedCupertinoActivityType, ), ); } else { @@ -255,6 +286,7 @@ class DemoAppState extends State { subject: subject.isEmpty ? null : subject, title: title.isEmpty ? null : title, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + excludedCupertinoActivities: excludedCupertinoActivityType, ), ); } @@ -278,6 +310,7 @@ class DemoAppState extends State { ], sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, downloadFallbackEnabled: true, + excludedCupertinoActivities: excludedCupertinoActivityType, ), ); scaffoldMessenger.showSnackBar(getResultSnackBar(shareResult)); @@ -305,6 +338,7 @@ class DemoAppState extends State { sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, fileNameOverrides: [fileName], downloadFallbackEnabled: true, + excludedCupertinoActivities: excludedCupertinoActivityType, ), ); diff --git a/packages/share_plus/share_plus/ios/share_plus/Sources/share_plus/FPPSharePlusPlugin.m b/packages/share_plus/share_plus/ios/share_plus/Sources/share_plus/FPPSharePlusPlugin.m index 035abe65a8..144b35dbb9 100644 --- a/packages/share_plus/share_plus/ios/share_plus/Sources/share_plus/FPPSharePlusPlugin.m +++ b/packages/share_plus/share_plus/ios/share_plus/Sources/share_plus/FPPSharePlusPlugin.m @@ -39,6 +39,69 @@ return viewController; } +static NSDictionary *activityTypes; + +static void initializeActivityTypeMapping(void) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableDictionary *originalTypes = + [[NSMutableDictionary alloc] initWithDictionary:@{ + @"postToFacebook" : UIActivityTypePostToFacebook, + @"postToTwitter" : UIActivityTypePostToTwitter, + @"postToWeibo" : UIActivityTypePostToWeibo, + @"message" : UIActivityTypeMessage, + @"mail" : UIActivityTypeMail, + @"print" : UIActivityTypePrint, + @"copyToPasteboard" : UIActivityTypeCopyToPasteboard, + @"assignToContact" : UIActivityTypeAssignToContact, + @"saveToCameraRoll" : UIActivityTypeSaveToCameraRoll, + @"addToReadingList" : UIActivityTypeAddToReadingList, + @"postToFlickr" : UIActivityTypePostToFlickr, + @"postToVimeo" : UIActivityTypePostToVimeo, + @"postToTencentWeibo" : UIActivityTypePostToTencentWeibo, + @"airDrop" : UIActivityTypeAirDrop, + @"openInIBooks" : UIActivityTypeOpenInIBooks, + @"markupAsPDF" : UIActivityTypeMarkupAsPDF, + }]; + + if (@available(iOS 15.4, *)) { + originalTypes[@"sharePlay"] = UIActivityTypeSharePlay; + } + + if (@available(iOS 16.0, *)) { + originalTypes[@"collaborationInviteWithLink"] = + UIActivityTypeCollaborationInviteWithLink; + } + + if (@available(iOS 16.0, *)) { + originalTypes[@"collaborationCopyLink"] = + UIActivityTypeCollaborationCopyLink; + } + if (@available(iOS 16.4, *)) { + originalTypes[@"addToHomeScreen"] = UIActivityTypeAddToHomeScreen; + } + activityTypes = originalTypes; + }); +} + +static NSArray * +activityTypesForStrings(NSArray *activityTypeStrings) { + if (activityTypeStrings == nil || activityTypeStrings.count == 0) { + return nil; + } + initializeActivityTypeMapping(); + NSMutableArray *result = [NSMutableArray array]; + + for (NSString *key in activityTypeStrings) { + UIActivityType mapped = activityTypes[key]; + if (mapped) { + [result addObject:mapped]; + } + } + + return [result copy]; +} + // We need the companion to avoid ARC deadlock @interface UIActivityViewSuccessCompanion : NSObject @@ -254,6 +317,9 @@ + (void)registerWithRegistrar:(NSObject *)registrar { NSNumber *originY = arguments[@"originY"]; NSNumber *originWidth = arguments[@"originWidth"]; NSNumber *originHeight = arguments[@"originHeight"]; + NSArray *excludedActivityTypeStrings = arguments[@"excludedCupertinoActivities"]; + NSArray *excludedActivityTypes = + activityTypesForStrings(excludedActivityTypeStrings); CGRect originRect = CGRectZero; if (originX && originY && originWidth && originHeight) { @@ -342,23 +408,26 @@ + (void)registerWithRegistrar:(NSObject *)registrar { if (uri) { [self shareUri:uri - withController:topViewController - atSource:originRect - toResult:result]; + excludedActivityTypes:excludedActivityTypes + withController:topViewController + atSource:originRect + toResult:result]; } else if (paths) { [self shareFiles:paths - withMimeType:mimeTypes - withSubject:shareTitle - withText:shareText - withController:rootViewController - atSource:originRect - toResult:result]; + withMimeType:mimeTypes + withSubject:shareTitle + withText:shareText + excludedActivityTypes:excludedActivityTypes + withController:rootViewController + atSource:originRect + toResult:result]; } else if (shareText) { [self shareText:shareText - subject:shareTitle - withController:rootViewController - atSource:originRect - toResult:result]; + subject:shareTitle + excludedActivityTypes:excludedActivityTypes + withController:rootViewController + atSource:originRect + toResult:result]; } else { result([FlutterError errorWithCode:@"error" message:@"No share content provided" @@ -371,14 +440,17 @@ + (void)registerWithRegistrar:(NSObject *)registrar { } + (void)share:(NSArray *)shareItems - withSubject:(NSString *)subject - withController:(UIViewController *)controller - atSource:(CGRect)origin - toResult:(FlutterResult)result { + withSubject:(NSString *)subject + excludedActivityTypes:(NSArray *)excludedActivityTypes + withController:(UIViewController *)controller + atSource:(CGRect)origin + toResult:(FlutterResult)result { UIActivityViewSuccessController *activityViewController = [[UIActivityViewSuccessController alloc] initWithActivityItems:shareItems applicationActivities:nil]; + activityViewController.excludedActivityTypes = excludedActivityTypes; + // Force subject when sharing a raw url or files if (![subject isKindOfClass:[NSNull class]]) { [activityViewController setValue:subject forKey:@"subject"]; @@ -427,38 +499,43 @@ + (void)share:(NSArray *)shareItems } + (void)shareUri:(NSString *)uri - withController:(UIViewController *)controller - atSource:(CGRect)origin - toResult:(FlutterResult)result { + excludedActivityTypes:(NSArray *)excludedActivityTypes + withController:(UIViewController *)controller + atSource:(CGRect)origin + toResult:(FlutterResult)result { NSURL *data = [NSURL URLWithString:uri]; [self share:@[ data ] - withSubject:nil - withController:controller - atSource:origin - toResult:result]; + withSubject:nil + excludedActivityTypes:excludedActivityTypes + withController:controller + atSource:origin + toResult:result]; } + (void)shareText:(NSString *)shareText - subject:(NSString *)subject - withController:(UIViewController *)controller - atSource:(CGRect)origin - toResult:(FlutterResult)result { + subject:(NSString *)subject + excludedActivityTypes:(NSArray *)excludedActivityTypes + withController:(UIViewController *)controller + atSource:(CGRect)origin + toResult:(FlutterResult)result { NSObject *data = [[SharePlusData alloc] initWithSubject:subject text:shareText]; [self share:@[ data ] - withSubject:subject - withController:controller - atSource:origin - toResult:result]; + withSubject:subject + excludedActivityTypes:excludedActivityTypes + withController:controller + atSource:origin + toResult:result]; } + (void)shareFiles:(NSArray *)paths - withMimeType:(NSArray *)mimeTypes - withSubject:(NSString *)subject - withText:(NSString *)text - withController:(UIViewController *)controller - atSource:(CGRect)origin - toResult:(FlutterResult)result { + withMimeType:(NSArray *)mimeTypes + withSubject:(NSString *)subject + withText:(NSString *)text + excludedActivityTypes:(NSArray *)excludedActivityTypes + withController:(UIViewController *)controller + atSource:(CGRect)origin + toResult:(FlutterResult)result { NSMutableArray *items = [[NSMutableArray alloc] init]; for (int i = 0; i < [paths count]; i++) { @@ -474,10 +551,11 @@ + (void)shareFiles:(NSArray *)paths } [self share:items - withSubject:subject - withController:controller - atSource:origin - toResult:result]; + withSubject:subject + excludedActivityTypes:excludedActivityTypes + withController:controller + atSource:origin + 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 bfae848b73..b6e055e3de 100644 --- a/packages/share_plus/share_plus/lib/share_plus.dart +++ b/packages/share_plus/share_plus/lib/share_plus.dart @@ -8,7 +8,12 @@ import 'package:meta/meta.dart'; import 'package:share_plus_platform_interface/share_plus_platform_interface.dart'; export 'package:share_plus_platform_interface/share_plus_platform_interface.dart' - show ShareResult, ShareResultStatus, XFile, ShareParams; + show + ShareResult, + ShareResultStatus, + XFile, + ShareParams, + CupertinoActivityType; export 'src/share_plus_linux.dart'; export 'src/share_plus_windows.dart' 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 9164d5e5f2..c4ece10e00 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 @@ -67,6 +67,13 @@ class MethodChannelShare extends SharePlatform { map['mimeTypes'] = mimeTypes; } + if (params.excludedCupertinoActivities != null && + params.excludedCupertinoActivities!.isNotEmpty) { + final excludedActivityTypes = + params.excludedCupertinoActivities!.map((e) => e.value).toList(); + map['excludedCupertinoActivities'] = excludedActivityTypes; + } + return map; } 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 2981a54f6c..40ceee27b6 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 @@ -136,6 +136,12 @@ class ShareParams { /// Parameter ignored on other platforms. final bool mailToFallbackEnabled; + /// Exclude services on iOS and macOS that you think are not suitable for your content. + /// + /// * Supported platforms: iOS, macOS + /// Parameter ignored on other platforms. + final List? excludedCupertinoActivities; + ShareParams({ this.text, this.subject, @@ -147,6 +153,7 @@ class ShareParams { this.fileNameOverrides, this.downloadFallbackEnabled = true, this.mailToFallbackEnabled = true, + this.excludedCupertinoActivities, }); } @@ -203,3 +210,38 @@ enum ShareResultStatus { /// but the user action can not be determined unavailable, } + +/// Represents iOS-supported share activity types, such as AirDrop, Messages, +/// Mail, and others. +/// +/// This enum is used to identify specific activity types in the iOS share sheet, +/// especially when certain types should be excluded from the share options. +/// +/// See also: +/// [UIActivity.ActivityType](https://developer.apple.com/documentation/uikit/uiactivity/activitytype) +enum CupertinoActivityType { + postToFacebook, + postToTwitter, + postToWeibo, + message, + mail, + print, + copyToPasteboard, + assignToContact, + saveToCameraRoll, + addToReadingList, + postToFlickr, + postToVimeo, + postToTencentWeibo, + airDrop, + openInIBooks, + markupAsPDF, + sharePlay, + collaborationInviteWithLink, + collaborationCopyLink, + addToHomeScreen; + + String get value { + return toString().split('.').last; + } +} 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 b30db3be0d..18f22cf889 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 @@ -66,6 +66,7 @@ void main() { ShareParams( uri: Uri.parse('https://pub.dev/packages/share_plus'), sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), + excludedCupertinoActivities: [CupertinoActivityType.airDrop], ), ); verify(mockChannel.invokeMethod('share', { @@ -74,6 +75,7 @@ void main() { 'originY': 2.0, 'originWidth': 3.0, 'originHeight': 4.0, + 'excludedCupertinoActivities': ['airDrop'], })); await sharePlatform.share( @@ -81,6 +83,7 @@ void main() { text: 'some text to share', subject: 'some subject to share', sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), + excludedCupertinoActivities: [], ), ); verify(mockChannel.invokeMethod('share', { @@ -99,6 +102,7 @@ void main() { subject: 'some subject to share', text: 'some text to share', sharePositionOrigin: const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), + excludedCupertinoActivities: null, ), ); verify(mockChannel.invokeMethod(