System Share on iOS

This commit is contained in:
LittleSheep 2024-12-15 12:59:18 +08:00
parent 09ad917e5d
commit 89c912a35b
9 changed files with 166 additions and 46 deletions

View File

@ -49,26 +49,6 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />

View File

@ -445,5 +445,8 @@
"postShare": "Share",
"postShareImage": "Share via Image",
"appInitializing": "Initializing",
"poweredBy": "Powered by {}"
"poweredBy": "Powered by {}",
"shareIntent": "Share",
"shareIntentDescription": "What do you want to do with the content you are sharing?",
"shareIntentPostStory": "Post a Story"
}

View File

@ -443,5 +443,8 @@
"postShare": "分享",
"postShareImage": "分享帖图",
"appInitializing": "正在初始化",
"poweredBy": "由 {} 提供支持"
"poweredBy": "由 {} 提供支持",
"shareIntent": "分享",
"shareIntentDescription": "您想对您分享的内容做些什么?",
"shareIntentPostStory": "发布动态"
}

View File

@ -592,7 +592,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
};
43B5CF57FD79BC21654EE037 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;

View File

@ -11,14 +11,14 @@
<dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>100</integer>
</dict>
<key>NSExtension</key>
<dict>
@ -32,5 +32,7 @@
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
<key>AppGroupId</key>
<string>group.solsynth.solian</string>
</dict>
</plist>

View File

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:bitsdojo_window/bitsdojo_window.dart';
@ -12,9 +11,7 @@ import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:home_widget/home_widget.dart';
import 'package:provider/provider.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:relative_time/relative_time.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:styled_widget/styled_widget.dart';
@ -40,6 +37,7 @@ import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/version_label.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
@ -191,17 +189,10 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
}
}
void _listenShareIntent() async {
_shareIntentSubscription = ReceiveSharingIntent.instance.getMediaStream().listen((value) {}, onError: (err) {
log("[ShareIntent] Unable to subscribe: $err");
});
}
@override
void initState() {
super.initState();
_initialize();
_listenShareIntent();
}
@override

View File

@ -28,6 +28,7 @@ import 'package:surface/screens/realm.dart';
import 'package:surface/screens/realm/manage.dart';
import 'package:surface/screens/realm/realm_detail.dart';
import 'package:surface/screens/settings.dart';
import 'package:surface/screens/sharing.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/about.dart';
import 'package:surface/widgets/navigation/app_background.dart';
@ -69,6 +70,7 @@ final _appRoutes = [
postRepostId: int.tryParse(
state.uri.queryParameters['reposting'] ?? '',
),
extraProps: state.extra as PostEditorExtraProps?,
),
),
),
@ -315,7 +317,9 @@ final appRouter = GoRouter(
routes: [
ShellRoute(
routes: _appRoutes,
builder: (context, state, child) => AppRootScaffold(body: child),
builder: (context, state, child) => AppRootScaffold(
body: AppSharingListener(child: child),
),
),
],
);

View File

@ -23,11 +23,26 @@ import 'package:surface/widgets/post/post_meta_editor.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:provider/provider.dart';
class PostEditorExtraProps {
final String? text;
final String? title;
final String? description;
final List<PostWriteMedia>? attachments;
const PostEditorExtraProps({
this.text,
this.title,
this.description,
this.attachments,
});
}
class PostEditorScreen extends StatefulWidget {
final String mode;
final int? postEditId;
final int? postReplyId;
final int? postRepostId;
final PostEditorExtraProps? extraProps;
const PostEditorScreen({
super.key,
@ -35,6 +50,7 @@ class PostEditorScreen extends StatefulWidget {
required this.postEditId,
required this.postReplyId,
required this.postRepostId,
this.extraProps,
});
@override
@ -130,6 +146,12 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
replying: widget.postReplyId,
reposting: widget.postRepostId,
);
if (widget.extraProps != null) {
_writeController.contentController.text = widget.extraProps!.text ?? '';
_writeController.titleController.text = widget.extraProps!.title ?? '';
_writeController.descriptionController.text = widget.extraProps!.description ?? '';
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
}
}
@override
@ -150,15 +172,15 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
TextSpan(
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(),
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
const TextSpan(text: '\n'),
TextSpan(
text: PostWriteController.kTitleMap[widget.mode]!.tr(),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
]),
),

115
lib/screens/sharing.dart Normal file
View File

@ -0,0 +1,115 @@
import 'dart:async';
import 'dart:developer';
import 'package:cross_file/cross_file.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/screens/post/post_editor.dart';
import 'package:surface/widgets/dialog.dart';
class AppSharingListener extends StatefulWidget {
final Widget child;
const AppSharingListener({super.key, required this.child});
@override
State<AppSharingListener> createState() => _AppSharingListenerState();
}
class _AppSharingListenerState extends State<AppSharingListener> {
late StreamSubscription _shareIntentSubscription;
void _gotoPost(Iterable<SharedMediaFile> value) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('shareIntent').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('shareIntentDescription').tr(),
const Gap(8),
Card(
child: Column(
children: [
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
leading: Icon(Icons.post_add),
trailing: const Icon(Icons.chevron_right),
title: Text('shareIntentPostStory').tr(),
onTap: () {
GoRouter.of(context).pushNamed(
'postEditor',
pathParameters: {
'mode': 'stories',
},
extra: PostEditorExtraProps(
attachments: value.map((e) => PostWriteMedia.fromFile(XFile(e.path))).toList(),
),
);
Navigator.pop(context);
},
),
],
),
)
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('dialogDismiss').tr(),
)
],
),
);
});
}
void _initialize() async {
_shareIntentSubscription = ReceiveSharingIntent.instance.getMediaStream().listen((value) {
if (value.isEmpty) return;
if (mounted) {
_gotoPost(value);
}
}, onError: (err) {
log("[ShareIntent] Unable to subscribe: $err");
});
}
void _initialHandle() {
ReceiveSharingIntent.instance.getInitialMedia().then((value) {
if (value.isEmpty) return;
if (mounted) {
_gotoPost(value);
}
ReceiveSharingIntent.instance.reset();
});
}
@override
void initState() {
super.initState();
_initialize();
_initialHandle();
}
@override
void dispose() {
_shareIntentSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}