From ad7a34ec18f37485730d1217a19288505d4455f7 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 13 Oct 2024 23:12:23 +0800 Subject: [PATCH] :sparkles: Share via image --- assets/locales/en_us.json | 4 +- assets/locales/zh_cn.json | 4 +- lib/screens/account/audit_log.dart | 4 +- lib/screens/account/profile_page.dart | 2 - lib/screens/dashboard.dart | 3 - lib/screens/explore.dart | 2 +- lib/widgets/posts/post_action.dart | 43 +++++++++- lib/widgets/posts/post_list.dart | 3 - lib/widgets/posts/post_share.dart | 92 ++++++++++++++++++++++ lib/widgets/posts/post_single_display.dart | 1 - pubspec.lock | 24 ++++++ pubspec.yaml | 2 + 12 files changed, 165 insertions(+), 19 deletions(-) create mode 100644 lib/widgets/posts/post_share.dart diff --git a/assets/locales/en_us.json b/assets/locales/en_us.json index e7eb83f..c51d4b8 100644 --- a/assets/locales/en_us.json +++ b/assets/locales/en_us.json @@ -482,5 +482,7 @@ "authPreferencesDesc": "Set the security behavior of your account", "authMaximumAuthSteps": "Maximum authentication steps", "authMaximumAuthStepsDesc": "The maximum number of authentication steps when logging in, higher value is more secure, lower value is more convenient; default is 2", - "auditLog": "Audit log" + "auditLog": "Audit log", + "shareImage": "Share as image", + "shareImageFooter": "See more interesting posts on Solar Network" } diff --git a/assets/locales/zh_cn.json b/assets/locales/zh_cn.json index ea0ef37..4d0f5b4 100644 --- a/assets/locales/zh_cn.json +++ b/assets/locales/zh_cn.json @@ -478,5 +478,7 @@ "authPreferencesDesc": "调整账号的安全行为模式", "authMaximumAuthSteps": "最大认证步数", "authMaximumAuthStepsDesc": "登陆时最多的验证步数,值越高则越安全,反之则会相对方便;默认设置为 2", - "auditLog": "活动日志" + "auditLog": "活动日志", + "shareImage": "分享图片", + "shareImageFooter": "上 Solar Network 看更多有趣帖子" } diff --git a/lib/screens/account/audit_log.dart b/lib/screens/account/audit_log.dart index 7747520..4012c70 100644 --- a/lib/screens/account/audit_log.dart +++ b/lib/screens/account/audit_log.dart @@ -21,8 +21,7 @@ class AuditLogScreen extends StatefulWidget { class _AuditLogScreenState extends State { bool _isBusy = true; - int _totalEvent = 0; - List _events = List.empty(growable: true); + final List _events = List.empty(growable: true); Future _getEvents() async { if (!_isBusy) setState(() => _isBusy = true); @@ -38,7 +37,6 @@ class _AuditLogScreenState extends State { final result = PaginationResult.fromJson(resp.body); setState(() { - _totalEvent = result.count; _events.addAll( result.data?.map((x) => AuditEvent.fromJson(x)).toList() ?? List.empty(), diff --git a/lib/screens/account/profile_page.dart b/lib/screens/account/profile_page.dart index 5e5581f..f060d38 100644 --- a/lib/screens/account/profile_page.dart +++ b/lib/screens/account/profile_page.dart @@ -588,8 +588,6 @@ class _AccountProfilePageState extends State { color: Theme.of(context).colorScheme.surfaceContainerLow, child: PostListEntryWidget( - backgroundColor: - Theme.of(context).colorScheme.surfaceContainerLow, item: element, isClickable: true, isNestedClickable: true, diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 4b71306..10ef854 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -393,9 +393,6 @@ class _DashboardScreenState extends State { vertical: 8, horizontal: 4, ), - backgroundColor: Theme.of(context) - .colorScheme - .surfaceContainerLow, ), ), ), diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 217ab79..a017c00 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -160,13 +160,13 @@ class _ExploreScreenState extends State toolbarHeight: AppTheme.toolbarHeight(context), leading: AppBarLeadingButton.adaptive(context), actions: [ + const BackgroundStateWidget(), IconButton( icon: const Icon(Icons.search), onPressed: () { AppRouter.instance.pushNamed('postSearch'); }, ), - const BackgroundStateWidget(), const NotificationButton(), SizedBox( width: AppTheme.isLargeScreen(context) ? 8 : 16, diff --git a/lib/widgets/posts/post_action.dart b/lib/widgets/posts/post_action.dart index 83db4de..e1a2feb 100644 --- a/lib/widgets/posts/post_action.dart +++ b/lib/widgets/posts/post_action.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:math'; import 'package:firebase_analytics/firebase_analytics.dart'; @@ -5,6 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:screenshot/screenshot.dart'; import 'package:share_plus/share_plus.dart'; import 'package:solian/exts.dart'; import 'package:solian/models/post.dart'; @@ -12,6 +15,7 @@ import 'package:solian/platform.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/router.dart'; import 'package:solian/screens/posts/post_editor.dart'; +import 'package:solian/widgets/posts/post_share.dart'; import 'package:solian/widgets/reports/abuse_report.dart'; class PostAction extends StatefulWidget { @@ -84,6 +88,24 @@ class _PostActionState extends State { } } + Future _shareImage() async { + final screenshot = ScreenshotController(); + final image = await screenshot.captureFromWidget( + PostShareImage(item: widget.item), + context: context, + ); + final directory = await getApplicationDocumentsDirectory(); + final imageFile = await File( + '${directory.path}/temporary_share_image.png', + ).create(); + await imageFile.writeAsBytes(image); + + final file = XFile(imageFile.path); + await Share.shareXFiles([file]); + + await imageFile.delete(); + } + @override void initState() { super.initState(); @@ -135,16 +157,29 @@ class _PostActionState extends State { contentPadding: const EdgeInsets.symmetric(horizontal: 24), leading: const Icon(Icons.share), title: Text('share'.tr), - trailing: PlatformInfo.isIOS || PlatformInfo.isAndroid - ? IconButton( + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (PlatformInfo.isIOS || PlatformInfo.isAndroid) + IconButton( icon: const Icon(Icons.link_off), tooltip: 'shareNoUri'.tr, onPressed: () async { await _doShare(noUri: true); Navigator.pop(context); }, - ) - : null, + ), + if (PlatformInfo.isIOS || PlatformInfo.isAndroid) + IconButton( + icon: const Icon(Icons.image), + tooltip: 'shareImage'.tr, + onPressed: () async { + await _shareImage(); + Navigator.pop(context); + }, + ), + ], + ), onTap: () async { await _doShare(); Navigator.pop(context); diff --git a/lib/widgets/posts/post_list.dart b/lib/widgets/posts/post_list.dart index 05b881c..e8d716c 100644 --- a/lib/widgets/posts/post_list.dart +++ b/lib/widgets/posts/post_list.dart @@ -41,7 +41,6 @@ class PostListWidget extends StatelessWidget { isClickable: isClickable, showFeaturedReply: true, item: item, - backgroundColor: backgroundColor, onUpdate: () { controller.refresh(); }, @@ -60,7 +59,6 @@ class PostListEntryWidget extends StatelessWidget { final bool isClickable; final bool showFeaturedReply; final Post item; - final Color? backgroundColor; final EdgeInsets? padding; final Function onUpdate; @@ -71,7 +69,6 @@ class PostListEntryWidget extends StatelessWidget { required this.isClickable, required this.showFeaturedReply, required this.item, - this.backgroundColor, this.padding, required this.onUpdate, }); diff --git a/lib/widgets/posts/post_share.dart b/lib/widgets/posts/post_share.dart new file mode 100644 index 0000000..22be5c1 --- /dev/null +++ b/lib/widgets/posts/post_share.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:get/get.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:solian/models/post.dart'; +import 'package:solian/widgets/posts/post_item.dart'; +import 'package:solian/widgets/root_container.dart'; + +class PostShareImage extends StatelessWidget { + final Post item; + + const PostShareImage({super.key, required this.item}); + + @override + Widget build(BuildContext context) { + final textColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.3); + return RootContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Gap(8), + Material( + color: Colors.transparent, + child: Card( + child: PostItem( + item: item, + isShowEmbed: true, + isClickable: false, + showFeaturedReply: false, + isReactable: false, + isShowReply: false, + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + onComment: () {}, + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Image.asset( + 'assets/logo.png', + width: 48, + height: 48, + ), + ), + const Gap(16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'shareImageFooter'.tr, + style: TextStyle( + fontSize: 13, + color: textColor, + ), + ), + Text( + 'Solsynth LLC © ${DateTime.now().year}', + style: TextStyle( + fontSize: 11, + color: textColor, + ), + ), + ], + ), + ], + ), + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Material( + color: Theme.of(context).colorScheme.surface, + child: QrImageView( + data: 'https://solsynth.dev/posts/${item.id}', + version: QrVersions.auto, + padding: const EdgeInsets.all(4), + size: 48, + ), + ), + ), + ], + ), + ], + ).paddingSymmetric(horizontal: 36, vertical: 24), + ); + } +} diff --git a/lib/widgets/posts/post_single_display.dart b/lib/widgets/posts/post_single_display.dart index fdfc428..dbcc9a2 100644 --- a/lib/widgets/posts/post_single_display.dart +++ b/lib/widgets/posts/post_single_display.dart @@ -25,7 +25,6 @@ class PostSingleDisplay extends StatelessWidget { isNestedClickable: true, showFeaturedReply: true, onUpdate: onUpdate, - backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, ), ), ), diff --git a/pubspec.lock b/pubspec.lock index e03dc3b..c99b3fb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1685,6 +1685,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" recase: dependency: transitive description: @@ -1757,6 +1773,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3" + screenshot: + dependency: "direct main" + description: + name: screenshot + sha256: "63817697a7835e6ce82add4228e15d233b74d42975c143ad8cfe07009fab866b" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sdp_transform: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3e47b9e..c6659fc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,6 +85,8 @@ dependencies: syntax_highlight: ^0.4.0 flutter_udid: ^3.0.0 timeline_tile: ^2.0.0 + screenshot: ^3.0.0 + qr_flutter: ^4.1.0 dev_dependencies: flutter_test: