✨ System Share on iOS
This commit is contained in:
parent
09ad917e5d
commit
89c912a35b
@ -49,26 +49,6 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/*" />
|
<data android:mimeType="text/*" />
|
||||||
</intent-filter>
|
</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>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
@ -445,5 +445,8 @@
|
|||||||
"postShare": "Share",
|
"postShare": "Share",
|
||||||
"postShareImage": "Share via Image",
|
"postShareImage": "Share via Image",
|
||||||
"appInitializing": "Initializing",
|
"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": "分享",
|
"postShare": "分享",
|
||||||
"postShareImage": "分享帖图",
|
"postShareImage": "分享帖图",
|
||||||
"appInitializing": "正在初始化",
|
"appInitializing": "正在初始化",
|
||||||
"poweredBy": "由 {} 提供支持"
|
"poweredBy": "由 {} 提供支持",
|
||||||
|
"shareIntent": "分享",
|
||||||
|
"shareIntentDescription": "您想对您分享的内容做些什么?",
|
||||||
|
"shareIntentPostStory": "发布动态"
|
||||||
}
|
}
|
||||||
|
@ -592,7 +592,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
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 */ = {
|
43B5CF57FD79BC21654EE037 /* [CP] Copy Pods Resources */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
@ -11,14 +11,14 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionActivationSupportsText</key>
|
<key>NSExtensionActivationSupportsText</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||||
<integer>100</integer>
|
<integer>100</integer>
|
||||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||||
<integer>100</integer>
|
<integer>100</integer>
|
||||||
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||||
<integer>100</integer>
|
<integer>100</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
@ -32,5 +32,7 @@
|
|||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
<string>com.apple.share-services</string>
|
<string>com.apple.share-services</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>AppGroupId</key>
|
||||||
|
<string>group.solsynth.solian</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
@ -12,9 +11,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:home_widget/home_widget.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:styled_widget/styled_widget.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/dialog.dart';
|
||||||
import 'package:surface/widgets/version_label.dart';
|
import 'package:surface/widgets/version_label.dart';
|
||||||
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await EasyLocalization.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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initialize();
|
_initialize();
|
||||||
_listenShareIntent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -28,6 +28,7 @@ import 'package:surface/screens/realm.dart';
|
|||||||
import 'package:surface/screens/realm/manage.dart';
|
import 'package:surface/screens/realm/manage.dart';
|
||||||
import 'package:surface/screens/realm/realm_detail.dart';
|
import 'package:surface/screens/realm/realm_detail.dart';
|
||||||
import 'package:surface/screens/settings.dart';
|
import 'package:surface/screens/settings.dart';
|
||||||
|
import 'package:surface/screens/sharing.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/about.dart';
|
import 'package:surface/widgets/about.dart';
|
||||||
import 'package:surface/widgets/navigation/app_background.dart';
|
import 'package:surface/widgets/navigation/app_background.dart';
|
||||||
@ -69,6 +70,7 @@ final _appRoutes = [
|
|||||||
postRepostId: int.tryParse(
|
postRepostId: int.tryParse(
|
||||||
state.uri.queryParameters['reposting'] ?? '',
|
state.uri.queryParameters['reposting'] ?? '',
|
||||||
),
|
),
|
||||||
|
extraProps: state.extra as PostEditorExtraProps?,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -315,7 +317,9 @@ final appRouter = GoRouter(
|
|||||||
routes: [
|
routes: [
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
routes: _appRoutes,
|
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:surface/widgets/dialog.dart';
|
||||||
import 'package:provider/provider.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 {
|
class PostEditorScreen extends StatefulWidget {
|
||||||
final String mode;
|
final String mode;
|
||||||
final int? postEditId;
|
final int? postEditId;
|
||||||
final int? postReplyId;
|
final int? postReplyId;
|
||||||
final int? postRepostId;
|
final int? postRepostId;
|
||||||
|
final PostEditorExtraProps? extraProps;
|
||||||
|
|
||||||
const PostEditorScreen({
|
const PostEditorScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@ -35,6 +50,7 @@ class PostEditorScreen extends StatefulWidget {
|
|||||||
required this.postEditId,
|
required this.postEditId,
|
||||||
required this.postReplyId,
|
required this.postReplyId,
|
||||||
required this.postRepostId,
|
required this.postRepostId,
|
||||||
|
this.extraProps,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -130,6 +146,12 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
replying: widget.postReplyId,
|
replying: widget.postReplyId,
|
||||||
reposting: widget.postRepostId,
|
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
|
@override
|
||||||
@ -150,15 +172,15 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(),
|
text: _writeController.title.isNotEmpty ? _writeController.title : 'untitled'.tr(),
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const TextSpan(text: '\n'),
|
const TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: PostWriteController.kTitleMap[widget.mode]!.tr(),
|
text: PostWriteController.kTitleMap[widget.mode]!.tr(),
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
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