✨ System Share on iOS
This commit is contained in:
parent
09ad917e5d
commit
89c912a35b
@ -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" />
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -443,5 +443,8 @@
|
||||
"postShare": "分享",
|
||||
"postShareImage": "分享帖图",
|
||||
"appInitializing": "正在初始化",
|
||||
"poweredBy": "由 {} 提供支持"
|
||||
"poweredBy": "由 {} 提供支持",
|
||||
"shareIntent": "分享",
|
||||
"shareIntentDescription": "您想对您分享的内容做些什么?",
|
||||
"shareIntentPostStory": "发布动态"
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -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
115
lib/screens/sharing.dart
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user