✨ Share the post via image
This commit is contained in:
		| @@ -403,6 +403,7 @@ | ||||
|   "accountStatusOffline": "Offline", | ||||
|   "accountStatusLastSeen": "Last seen at {}", | ||||
|   "postArticle": "Article on the Solar Network", | ||||
|   "postStory": "Story on the Solar Network", | ||||
|   "articleWrittenAt": "Written at {}", | ||||
|   "articleEditedAt": "Edited at {}", | ||||
|   "attachmentSaved": "Saved to album", | ||||
| @@ -436,5 +437,7 @@ | ||||
|   "publisherBlockHint": "Block {}", | ||||
|   "publisherBlockHintDescription": "You are going to block this publisher's maintainer, this will also block publishers that run by the same user.", | ||||
|   "userUnblocked": "{} has been unblocked.", | ||||
|   "userBlocked": "{} has been blocked." | ||||
|   "userBlocked": "{} has been blocked.", | ||||
|   "postSharingViaPicture": "Capturing post as picture, please stand by...", | ||||
|   "postImageShareAds": "Explore posts on the Solar Network" | ||||
| } | ||||
|   | ||||
| @@ -401,6 +401,7 @@ | ||||
|   "accountStatusOffline": "离线", | ||||
|   "accountStatusLastSeen": "最后一次在 {} 上线", | ||||
|   "postArticle": "Solar Network 上的文章", | ||||
|   "postStory": "Solar Network 上的故事", | ||||
|   "articleWrittenAt": "发表于 {}", | ||||
|   "articleEditedAt": "编辑于 {}", | ||||
|   "attachmentSaved": "已保存到相册", | ||||
| @@ -434,5 +435,7 @@ | ||||
|   "publisherBlockHint": "屏蔽 {}", | ||||
|   "publisherBlockHintDescription": "你正要屏蔽此发布者的运营者,该操作也将屏蔽由同一用户运营的发布者。", | ||||
|   "userUnblocked": "已解除屏蔽用户 {}", | ||||
|   "userBlocked": "已屏蔽用户 {}" | ||||
|   "userBlocked": "已屏蔽用户 {}", | ||||
|   "postSharingViaPicture": "正在生成帖子截图,请稍等片刻……", | ||||
|   "postImageShareAds": "来 Solar Network 探索更多有趣帖子" | ||||
| } | ||||
|   | ||||
| @@ -53,6 +53,11 @@ class SnPostContentProvider { | ||||
|     if (out.body['thumbnail'] != null) { | ||||
|       rids.add(out.body['thumbnail']); | ||||
|     } | ||||
|     if (out.repostId != null) { | ||||
|       out = out.copyWith( | ||||
|         repostTo: await _preloadRelatedDataSingle(out.repostTo!), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     final attachments = await _attach.getMultiple(rids.toList()); | ||||
|     out = out.copyWith( | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'dart:math' as math; | ||||
|  | ||||
| import 'package:collection/collection.dart'; | ||||
| import 'package:dismissible_page/dismissible_page.dart'; | ||||
| import 'package:flutter/gestures.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @@ -14,19 +15,21 @@ class AttachmentList extends StatefulWidget { | ||||
|   final List<SnAttachment?> data; | ||||
|   final bool bordered; | ||||
|   final bool noGrow; | ||||
|   final bool isFlatted; | ||||
|   final double? maxHeight; | ||||
|   final EdgeInsets? listPadding; | ||||
|  | ||||
|   const AttachmentList({ | ||||
|     super.key, | ||||
|     required this.data, | ||||
|     this.bordered = false, | ||||
|     this.noGrow = false, | ||||
|     this.isFlatted = false, | ||||
|     this.maxHeight, | ||||
|     this.listPadding, | ||||
|   }); | ||||
|  | ||||
|   static const BorderRadius kDefaultRadius = | ||||
|       BorderRadius.all(Radius.circular(8)); | ||||
|   static const BorderRadius kDefaultRadius = BorderRadius.all(Radius.circular(8)); | ||||
|  | ||||
|   @override | ||||
|   State<AttachmentList> createState() => _AttachmentListState(); | ||||
| @@ -44,9 +47,8 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|   Widget build(BuildContext context) { | ||||
|     return LayoutBuilder( | ||||
|       builder: (context, layoutConstraints) { | ||||
|         final borderSide = widget.bordered | ||||
|             ? BorderSide(width: 1, color: Theme.of(context).dividerColor) | ||||
|             : BorderSide.none; | ||||
|         final borderSide = | ||||
|             widget.bordered ? BorderSide(width: 1, color: Theme.of(context).dividerColor) : BorderSide.none; | ||||
|         final backgroundColor = Theme.of(context).colorScheme.surfaceContainer; | ||||
|         final constraints = BoxConstraints( | ||||
|           minWidth: 80, | ||||
| @@ -56,14 +58,13 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|  | ||||
|         if (widget.data.isEmpty) return const SizedBox.shrink(); | ||||
|         if (widget.data.length == 1) { | ||||
|           final singleAspectRatio = | ||||
|               widget.data[0]?.metadata['ratio']?.toDouble() ?? | ||||
|                   switch (widget.data[0]?.mimetype.split('/').firstOrNull) { | ||||
|                     'audio' => 16 / 9, | ||||
|                     'video' => 16 / 9, | ||||
|                     _ => 1, | ||||
|                   } | ||||
|                       .toDouble(); | ||||
|           final singleAspectRatio = widget.data[0]?.metadata['ratio']?.toDouble() ?? | ||||
|               switch (widget.data[0]?.mimetype.split('/').firstOrNull) { | ||||
|                 'audio' => 16 / 9, | ||||
|                 'video' => 16 / 9, | ||||
|                 _ => 1, | ||||
|               } | ||||
|                   .toDouble(); | ||||
|  | ||||
|           return Container( | ||||
|             constraints: ResponsiveBreakpoints.of(context).largerThan(MOBILE) | ||||
| @@ -79,8 +80,7 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|               child: GestureDetector( | ||||
|                 child: Builder( | ||||
|                   builder: (context) { | ||||
|                     if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) || | ||||
|                         widget.noGrow) { | ||||
|                     if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) || widget.noGrow) { | ||||
|                       return Padding( | ||||
|                         // Single child list-like displaying | ||||
|                         padding: widget.listPadding ?? EdgeInsets.zero, | ||||
| @@ -129,6 +129,37 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         if (widget.isFlatted) { | ||||
|           return Wrap( | ||||
|             spacing: 4, | ||||
|             runSpacing: 4, | ||||
|             children: widget.data | ||||
|                 .mapIndexed( | ||||
|                   (idx, ele) => AspectRatio( | ||||
|                     aspectRatio: (ele?.metadata['ratio'] ?? 1).toDouble(), | ||||
|                     child: Container( | ||||
|                       decoration: BoxDecoration( | ||||
|                         color: backgroundColor, | ||||
|                         border: Border( | ||||
|                           top: borderSide, | ||||
|                           bottom: borderSide, | ||||
|                         ), | ||||
|                         borderRadius: AttachmentList.kDefaultRadius, | ||||
|                       ), | ||||
|                       child: ClipRRect( | ||||
|                         borderRadius: AttachmentList.kDefaultRadius, | ||||
|                         child: AttachmentItem( | ||||
|                           data: ele, | ||||
|                           heroTag: heroTags[idx], | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|                   ), | ||||
|                 ) | ||||
|                 .toList(), | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         return AspectRatio( | ||||
|           aspectRatio: (widget.data.firstOrNull?.metadata['ratio'] ?? 1).toDouble(), | ||||
|           child: Container( | ||||
| @@ -147,9 +178,7 @@ class _AttachmentListState extends State<AttachmentList> { | ||||
|                         onTap: () { | ||||
|                           context.pushTransparentRoute( | ||||
|                             AttachmentZoomView( | ||||
|                               data: widget.data | ||||
|                                   .where((ele) => ele != null) | ||||
|                                   .cast(), | ||||
|                               data: widget.data.where((ele) => ele != null).cast(), | ||||
|                               initialIndex: idx, | ||||
|                               heroTags: heroTags, | ||||
|                             ), | ||||
|   | ||||
| @@ -5,10 +5,15 @@ import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:path_provider/path_provider.dart'; | ||||
| import 'package:popover/popover.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:qr_flutter/qr_flutter.dart'; | ||||
| import 'package:relative_time/relative_time.dart'; | ||||
| import 'package:responsive_framework/responsive_framework.dart'; | ||||
| import 'package:screenshot/screenshot.dart'; | ||||
| import 'package:share_plus/share_plus.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| @@ -56,6 +61,9 @@ class PostItem extends StatelessWidget { | ||||
|   Widget build(BuildContext context) { | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|  | ||||
|     final ua = context.read<UserProvider>(); | ||||
|     final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user!.id; | ||||
|  | ||||
|     // Article headline preview | ||||
|     if (!showFullPost && data.type == 'article') { | ||||
|       return Container( | ||||
| @@ -65,6 +73,7 @@ class PostItem extends StatelessWidget { | ||||
|           children: [ | ||||
|             _PostContentHeader( | ||||
|               data: data, | ||||
|               isAuthor: isAuthor, | ||||
|               onDeleted: () { | ||||
|                 if (onDeleted != null) {} | ||||
|               }, | ||||
| @@ -191,6 +200,118 @@ class PostItem extends StatelessWidget { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class PostShareImage extends StatelessWidget { | ||||
|   const PostShareImage({ | ||||
|     super.key, | ||||
|     required this.data, | ||||
|   }); | ||||
|  | ||||
|   final SnPost data; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SizedBox( | ||||
|       width: 480, | ||||
|       child: Column( | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         mainAxisSize: MainAxisSize.min, | ||||
|         children: [ | ||||
|           _PostContentHeader( | ||||
|             data: data, | ||||
|             onDeleted: () {}, | ||||
|             showMenu: false, | ||||
|             isRelativeDate: false, | ||||
|           ).padding(horizontal: 16, bottom: 8), | ||||
|           _PostHeadline( | ||||
|             data: data, | ||||
|             isEnlarge: data.type == 'article', | ||||
|           ).padding(horizontal: 16, bottom: 8), | ||||
|           _PostContentBody( | ||||
|             data: data, | ||||
|             isEnlarge: data.type == 'article', | ||||
|           ).padding(horizontal: 16, bottom: 8), | ||||
|           if (data.repostTo != null) | ||||
|             _PostQuoteContent( | ||||
|               child: data.repostTo!, | ||||
|               isRelativeDate: false, | ||||
|               isFlatted: true, | ||||
|             ).padding(horizontal: 16, bottom: 8), | ||||
|           if (data.type != 'article' && (data.preload?.attachments?.isNotEmpty ?? false)) | ||||
|             AttachmentList( | ||||
|               data: data.preload!.attachments!, | ||||
|               isFlatted: true, | ||||
|             ).padding(horizontal: 16, bottom: 8), | ||||
|           _PostBottomAction( | ||||
|             data: data, | ||||
|             showComments: true, | ||||
|             showReactions: true, | ||||
|             onChanged: (SnPost data) {}, | ||||
|           ).padding(left: 8, right: 14), | ||||
|           const Divider(height: 1), | ||||
|           const Gap(12), | ||||
|           SizedBox( | ||||
|             height: 100, | ||||
|             child: Row( | ||||
|               mainAxisAlignment: MainAxisAlignment.center, | ||||
|               children: [ | ||||
|                 Expanded( | ||||
|                   child: Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       Expanded( | ||||
|                         child: Column( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           children: [ | ||||
|                             Text( | ||||
|                               '${data.aliasPrefix} / ${data.alias ?? '#${data.id}'}', | ||||
|                               style: GoogleFonts.robotoMono(fontSize: 17), | ||||
|                             ), | ||||
|                             const Gap(2), | ||||
|                             Text( | ||||
|                               switch (data.type) { | ||||
|                                 'article' => 'postArticle'.tr(), | ||||
|                                 _ => 'postStory'.tr(), | ||||
|                               }, | ||||
|                               style: GoogleFonts.robotoMono(fontSize: 12), | ||||
|                             ), | ||||
|                           ], | ||||
|                         ), | ||||
|                       ), | ||||
|                       Text( | ||||
|                         'postImageShareAds', | ||||
|                         style: GoogleFonts.robotoMono(fontSize: 13), | ||||
|                       ).tr(), | ||||
|                     ], | ||||
|                   ), | ||||
|                 ), | ||||
|                 QrImageView( | ||||
|                   padding: EdgeInsets.zero, | ||||
|                   data: 'https://solsynth.dev/posts/${data.id}', | ||||
|                   version: QrVersions.auto, | ||||
|                   size: 100, | ||||
|                   gapless: true, | ||||
|                   embeddedImage: AssetImage('assets/icon/icon-light-radius.png'), | ||||
|                   embeddedImageStyle: QrEmbeddedImageStyle( | ||||
|                     size: Size(32, 32), | ||||
|                   ), | ||||
|                   eyeStyle: QrEyeStyle( | ||||
|                     eyeShape: QrEyeShape.circle, | ||||
|                     color: Theme.of(context).colorScheme.onSurface, | ||||
|                   ), | ||||
|                   dataModuleStyle: QrDataModuleStyle( | ||||
|                     dataModuleShape: QrDataModuleShape.square, | ||||
|                     color: Theme.of(context).colorScheme.onSurface, | ||||
|                   ), | ||||
|                 ) | ||||
|               ], | ||||
|             ), | ||||
|           ).padding(left: 16, right: 32, vertical: 8), | ||||
|         ], | ||||
|       ).padding(vertical: 16), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _PostBottomAction extends StatelessWidget { | ||||
|   final SnPost data; | ||||
|   final bool showComments; | ||||
| @@ -204,17 +325,57 @@ class _PostBottomAction extends StatelessWidget { | ||||
|     required this.onChanged, | ||||
|   }); | ||||
|  | ||||
|   void _doShare() { | ||||
|   void _doShare(BuildContext context) { | ||||
|     final box = context.findRenderObject() as RenderBox?; | ||||
|     final url = 'https://solsynth.dev/posts/${data.id}'; | ||||
|     if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { | ||||
|       Share.shareUri(Uri.parse(url)); | ||||
|       Share.shareUri(Uri.parse(url), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); | ||||
|     } else { | ||||
|       Share.share(url); | ||||
|       Share.share(url, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _doShareViaPicture() { | ||||
|   void _doShareViaPicture(BuildContext context) async { | ||||
|     final box = context.findRenderObject() as RenderBox?; | ||||
|     context.showSnackbar('postSharingViaPicture'.tr()); | ||||
|  | ||||
|     final controller = ScreenshotController(); | ||||
|     final capturedImage = await controller.captureFromLongWidget( | ||||
|       InheritedTheme.captureAll( | ||||
|         context, | ||||
|         MediaQuery( | ||||
|           data: MediaQuery.of(context), | ||||
|           child: Material( | ||||
|             child: MultiProvider( | ||||
|               providers: [ | ||||
|                 Provider<SnNetworkProvider>(create: (_) => context.read()), | ||||
|               ], | ||||
|               child: ResponsiveBreakpoints.builder( | ||||
|                 breakpoints: ResponsiveBreakpoints.of(context).breakpoints, | ||||
|                 child: PostShareImage(data: data), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|       pixelRatio: 3, | ||||
|       context: context, | ||||
|     ); | ||||
|  | ||||
|     if (kIsWeb) return; | ||||
|  | ||||
|     final directory = await getTemporaryDirectory(); | ||||
|     final imagePath = await File( | ||||
|       '${directory.path}/sn-share-via-image-${DateTime.now().millisecondsSinceEpoch}.png', | ||||
|     ).create(); | ||||
|     await imagePath.writeAsBytes(capturedImage); | ||||
|  | ||||
|     await Share.shareXFiles( | ||||
|       [XFile(imagePath.path)], | ||||
|       sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, | ||||
|     ); | ||||
|  | ||||
|     await imagePath.delete(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -301,8 +462,8 @@ class _PostBottomAction extends StatelessWidget { | ||||
|               ..removeLast(), | ||||
|           ), | ||||
|         InkWell( | ||||
|           onTap: _doShare, | ||||
|           onLongPress: _doShareViaPicture, | ||||
|           onTap: () => _doShare(context), | ||||
|           onLongPress: () => _doShareViaPicture(context), | ||||
|           child: Icon( | ||||
|             Symbols.share, | ||||
|             size: 20, | ||||
| @@ -410,13 +571,17 @@ class _PostHeadline extends StatelessWidget { | ||||
|  | ||||
| class _PostContentHeader extends StatelessWidget { | ||||
|   final SnPost data; | ||||
|   final bool isAuthor; | ||||
|   final bool isCompact; | ||||
|   final bool isRelativeDate; | ||||
|   final bool showMenu; | ||||
|   final Function onDeleted; | ||||
|  | ||||
|   const _PostContentHeader({ | ||||
|     required this.data, | ||||
|     this.isAuthor = false, | ||||
|     this.isCompact = false, | ||||
|     this.isRelativeDate = true, | ||||
|     this.showMenu = true, | ||||
|     required this.onDeleted, | ||||
|   }); | ||||
| @@ -446,9 +611,6 @@ class _PostContentHeader extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ua = context.read<UserProvider>(); | ||||
|     final isAuthor = ua.isAuthorized && data.publisher.accountId == ua.user!.id; | ||||
|  | ||||
|     return Row( | ||||
|       children: [ | ||||
|         GestureDetector( | ||||
| @@ -484,9 +646,11 @@ class _PostContentHeader extends StatelessWidget { | ||||
|                 children: [ | ||||
|                   Text('@${data.publisher.name}').fontSize(13), | ||||
|                   const Gap(4), | ||||
|                   Text(RelativeTime(context).format( | ||||
|                     data.publishedAt ?? data.createdAt, | ||||
|                   )).fontSize(13), | ||||
|                   Text( | ||||
|                     isRelativeDate | ||||
|                         ? RelativeTime(context).format(data.publishedAt ?? data.createdAt) | ||||
|                         : DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt), | ||||
|                   ).fontSize(13), | ||||
|                 ], | ||||
|               ).opacity(0.8), | ||||
|             ], | ||||
| @@ -501,9 +665,11 @@ class _PostContentHeader extends StatelessWidget { | ||||
|                   children: [ | ||||
|                     Text('@${data.publisher.name}').fontSize(13), | ||||
|                     const Gap(4), | ||||
|                     Text(RelativeTime(context).format( | ||||
|                       data.publishedAt ?? data.createdAt, | ||||
|                     )).fontSize(13), | ||||
|                     Text( | ||||
|                       isRelativeDate | ||||
|                           ? RelativeTime(context).format(data.publishedAt ?? data.createdAt) | ||||
|                           : DateFormat('y/M/d HH:mm').format(data.publishedAt ?? data.createdAt), | ||||
|                     ).fontSize(13), | ||||
|                   ], | ||||
|                 ).opacity(0.8), | ||||
|               ], | ||||
| @@ -628,8 +794,15 @@ class _PostContentBody extends StatelessWidget { | ||||
|  | ||||
| class _PostQuoteContent extends StatelessWidget { | ||||
|   final SnPost child; | ||||
|   final bool isRelativeDate; | ||||
|   final bool isFlatted; | ||||
|  | ||||
|   const _PostQuoteContent({super.key, required this.child}); | ||||
|   const _PostQuoteContent({ | ||||
|     super.key, | ||||
|     this.isRelativeDate = true, | ||||
|     this.isFlatted = false, | ||||
|     required this.child, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| @@ -650,6 +823,7 @@ class _PostQuoteContent extends StatelessWidget { | ||||
|                 _PostContentHeader( | ||||
|                   data: child, | ||||
|                   isCompact: true, | ||||
|                   isRelativeDate: isRelativeDate, | ||||
|                   showMenu: false, | ||||
|                   onDeleted: () {}, | ||||
|                 ).padding(bottom: 4), | ||||
| @@ -665,12 +839,15 @@ class _PostQuoteContent extends StatelessWidget { | ||||
|                 ), | ||||
|                 child: AttachmentList( | ||||
|                   data: child.preload!.attachments!, | ||||
|                   isFlatted: isFlatted, | ||||
|                   listPadding: const EdgeInsets.symmetric(horizontal: 12), | ||||
|                 ), | ||||
|               ).padding( | ||||
|                 top: 8, | ||||
|                 bottom: (child.preload?.attachments?.length ?? 0) > 1 ? 12 : 0, | ||||
|               ), | ||||
|               ) | ||||
|             else | ||||
|               const Gap(8), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|   | ||||
							
								
								
									
										44
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -266,10 +266,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: connectivity_plus | ||||
|       sha256: "876849631b0c7dc20f8b471a2a03142841b482438e3b707955464f5ffca3e4c3" | ||||
|       sha256: e0817759ec6d2d8e57eb234e6e57d2173931367a865850c7acea40d4b4f9c27d | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.1.0" | ||||
|     version: "6.1.1" | ||||
|   connectivity_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -362,18 +362,18 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: device_info_plus | ||||
|       sha256: f545ffbadee826f26f2e1a0f0cbd667ae9a6011cc0f77c0f8f00a969655e6e95 | ||||
|       sha256: "4fa68e53e26ab17b70ca39f072c285562cfc1589df5bb1e9295db90f6645f431" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "11.1.1" | ||||
|     version: "11.2.0" | ||||
|   device_info_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: device_info_plus_platform_interface | ||||
|       sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" | ||||
|       sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "7.0.1" | ||||
|     version: "7.0.2" | ||||
|   dio: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1190,18 +1190,18 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: package_info_plus | ||||
|       sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce | ||||
|       sha256: "70c421fe9d9cc1a9a7f3b05ae56befd469fe4f8daa3b484823141a55442d858d" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "8.1.1" | ||||
|     version: "8.1.2" | ||||
|   package_info_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: package_info_plus_platform_interface | ||||
|       sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 | ||||
|       sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.0.1" | ||||
|     version: "3.0.2" | ||||
|   pasteboard: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1402,6 +1402,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" | ||||
|   relative_time: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1502,18 +1518,18 @@ packages: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: share_plus | ||||
|       sha256: "9c9bafd4060728d7cdb2464c341743adbd79d327cb067ec7afb64583540b47c8" | ||||
|       sha256: "6327c3f233729374d0abaafd61f6846115b2a481b4feddd8534211dc10659400" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "10.1.2" | ||||
|     version: "10.1.3" | ||||
|   share_plus_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: share_plus_platform_interface | ||||
|       sha256: c57c0bbfec7142e3a0f55633be504b796af72e60e3c791b44d5a017b985f7a48 | ||||
|       sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "5.0.1" | ||||
|     version: "5.0.2" | ||||
|   shared_preferences: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|   | ||||
| @@ -99,6 +99,7 @@ dependencies: | ||||
|   package_info_plus: ^8.1.1 | ||||
|   intl: ^0.19.0 | ||||
|   screenshot: ^3.0.0 | ||||
|   qr_flutter: ^4.1.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user