From e1ab9e19cbcb227a38df595958d8162762e4d555 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sun, 6 Apr 2025 11:38:16 +0800 Subject: [PATCH] feat: save reply Closes #614 opt: more panel Signed-off-by: bggRGjQaUbCoE --- lib/pages/dynamics/widgets/author_panel.dart | 335 +++++++-------- .../dynamics/widgets/author_panel_grpc.dart | 232 ++++++----- lib/pages/login/view.dart | 43 +- lib/pages/setting/pages/play_speed_set.dart | 43 +- .../detail/reply/widgets/reply_item.dart | 8 +- .../detail/reply/widgets/reply_item_grpc.dart | 227 ++++++----- .../detail/reply/widgets/reply_save.dart | 385 ++++++++++++++++++ .../video/detail/reply_reply/controller.dart | 6 +- lib/utils/app_scheme.dart | 51 ++- lib/utils/download.dart | 2 +- lib/utils/utils.dart | 25 +- pubspec.lock | 16 +- pubspec.yaml | 3 +- 13 files changed, 926 insertions(+), 450 deletions(-) create mode 100644 lib/pages/video/detail/reply/widgets/reply_save.dart diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 83127d44..106dd811 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -234,197 +234,200 @@ class AuthorPanel extends StatelessWidget { ); Widget morePanel(context) { - return Container( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), - // clipBehavior: Clip.hardEdge, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: Get.back, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), + return MediaQuery.removePadding( + context: context, + removeLeft: true, + removeRight: true, + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: Get.back, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + child: Container( + height: 35, + padding: const EdgeInsets.only(bottom: 2), + child: Center( + child: Container( + width: 32, + height: 3, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.outline, + borderRadius: + const BorderRadius.all(Radius.circular(3))), + ), ), ), ), - ), - if (item.type == 'DYNAMIC_TYPE_AV') + if (item.type == 'DYNAMIC_TYPE_AV') + ListTile( + onTap: () async { + try { + String bvid = item.modules.moduleDynamic.major.archive.bvid; + var res = await UserHttp.toViewLater(bvid: bvid); + SmartDialog.showToast(res['msg']); + Get.back(); + } catch (err) { + SmartDialog.showToast('出错了:${err.toString()}'); + } + }, + minLeadingWidth: 0, + // dense: true, + leading: const Icon(Icons.watch_later_outlined, size: 19), + title: Text( + '稍后再看', + style: Theme.of(context).textTheme.titleSmall, + ), + ), ListTile( - onTap: () async { - try { - String bvid = item.modules.moduleDynamic.major.archive.bvid; - var res = await UserHttp.toViewLater(bvid: bvid); - SmartDialog.showToast(res['msg']); - Get.back(); - } catch (err) { - SmartDialog.showToast('出错了:${err.toString()}'); - } - }, - minLeadingWidth: 0, - // dense: true, - leading: const Icon(Icons.watch_later_outlined, size: 19), title: Text( - '稍后再看', + '分享动态', style: Theme.of(context).textTheme.titleSmall, ), - ), - ListTile( - title: Text( - '分享动态', - style: Theme.of(context).textTheme.titleSmall, - ), - leading: const Icon(Icons.share_outlined, size: 19), - onTap: () { - Get.back(); - Utils.shareText( - '${HttpString.dynamicShareBaseUrl}/${item.idStr}'); - }, - minLeadingWidth: 0, - ), - ListTile( - title: Text( - '临时屏蔽:${item.modules.moduleAuthor.name}', - style: Theme.of(context).textTheme.titleSmall, - ), - leading: const Icon(Icons.visibility_off_outlined, size: 19), - onTap: () { - Get.back(); - DynamicsController dynamicsController = - Get.find(); - dynamicsController.tempBannedList - .add(item.modules.moduleAuthor.mid); - SmartDialog.showToast( - '已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复'); - }, - minLeadingWidth: 0, - ), - if (item.modules.moduleAuthor.mid == Accounts.main.mid) ...[ - ListTile( + leading: const Icon(Icons.share_outlined, size: 19), onTap: () { Get.back(); - Utils.checkCreatedDyn(id: item.idStr, isManual: true); + Utils.shareText( + '${HttpString.dynamicShareBaseUrl}/${item.idStr}'); }, minLeadingWidth: 0, - leading: Stack( - alignment: Alignment.center, - children: [ - const Icon(Icons.shield_outlined, size: 19), - const Icon(Icons.published_with_changes_sharp, size: 12), - ], - ), - title: - Text('检查动态', style: Theme.of(context).textTheme.titleSmall!), ), - if (onRemove != null) + ListTile( + title: Text( + '临时屏蔽:${item.modules.moduleAuthor.name}', + style: Theme.of(context).textTheme.titleSmall, + ), + leading: const Icon(Icons.visibility_off_outlined, size: 19), + onTap: () { + Get.back(); + DynamicsController dynamicsController = + Get.find(); + dynamicsController.tempBannedList + .add(item.modules.moduleAuthor.mid); + SmartDialog.showToast( + '已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复'); + }, + minLeadingWidth: 0, + ), + if (item.modules.moduleAuthor.mid == Accounts.main.mid) ...[ ListTile( onTap: () { Get.back(); - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('确定删除该动态?'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, + Utils.checkCreatedDyn(id: item.idStr, isManual: true); + }, + minLeadingWidth: 0, + leading: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.shield_outlined, size: 19), + const Icon(Icons.published_with_changes_sharp, size: 12), + ], + ), + title: Text('检查动态', + style: Theme.of(context).textTheme.titleSmall!), + ), + if (onRemove != null) + ListTile( + onTap: () { + Get.back(); + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('确定删除该动态?'), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), ), ), + TextButton( + onPressed: () { + Get.back(); + onRemove?.call(item.idStr); + }, + child: const Text('确定'), + ), + ], + ), + ); + }, + minLeadingWidth: 0, + leading: Icon(Icons.delete_outline, + color: Theme.of(context).colorScheme.error, size: 19), + title: Text('删除', + style: Theme.of(context).textTheme.titleSmall!.copyWith( + color: Theme.of(context).colorScheme.error)), + ), + ], + if (Accounts.main.isLogin) + ListTile( + title: Text( + '举报', + style: Theme.of(context).textTheme.titleSmall!.copyWith( + color: Theme.of(context).colorScheme.error, + ), + ), + leading: Icon( + Icons.error_outline_outlined, + size: 19, + color: Theme.of(context).colorScheme.error, + ), + onTap: () { + Get.back(); + autoWrapReportDialog( + context, + ReportOptions.dynamicReport, + (reasonType, reasonDesc, banUid) async { + if (banUid) { + VideoHttp.relationMod( + mid: item.modules.moduleAuthor.mid, + act: 5, + reSrc: 11, + ); + } + final res = await Request().post( + '/x/dynamic/feed/dynamic_report/add', + queryParameters: { + 'csrf': await Request.getCsrf(), + }, + data: { + "accused_uid": item.modules.moduleAuthor.mid, + "dynamic_id": item.idStr, + "reason_type": reasonType, + "reason_desc": reasonType == 0 ? reasonDesc : null, + }, + options: Options( + contentType: Headers.formUrlEncodedContentType, ), - TextButton( - onPressed: () { - Get.back(); - onRemove?.call(item.idStr); - }, - child: const Text('确定'), - ), - ], - ), + ); + return res.data as Map; + }, ); }, minLeadingWidth: 0, - leading: Icon(Icons.delete_outline, - color: Theme.of(context).colorScheme.error, size: 19), - title: Text('删除', - style: Theme.of(context) - .textTheme - .titleSmall! - .copyWith(color: Theme.of(context).colorScheme.error)), ), - ], - if (Accounts.main.isLogin) + const Divider(thickness: 0.1, height: 1), ListTile( - title: Text( - '举报', - style: Theme.of(context).textTheme.titleSmall!.copyWith( - color: Theme.of(context).colorScheme.error, - ), - ), - leading: Icon( - Icons.error_outline_outlined, - size: 19, - color: Theme.of(context).colorScheme.error, - ), - onTap: () { - Get.back(); - autoWrapReportDialog( - context, - ReportOptions.dynamicReport, - (reasonType, reasonDesc, banUid) async { - if (banUid) { - VideoHttp.relationMod( - mid: item.modules.moduleAuthor.mid, - act: 5, - reSrc: 11, - ); - } - final res = await Request().post( - '/x/dynamic/feed/dynamic_report/add', - queryParameters: { - 'csrf': await Request.getCsrf(), - }, - data: { - "accused_uid": item.modules.moduleAuthor.mid, - "dynamic_id": item.idStr, - "reason_type": reasonType, - "reason_desc": reasonType == 0 ? reasonDesc : null, - }, - options: Options( - contentType: Headers.formUrlEncodedContentType, - ), - ); - return res.data as Map; - }, - ); - }, + onTap: Get.back, minLeadingWidth: 0, + dense: true, + title: Text( + '取消', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + textAlign: TextAlign.center, + ), ), - const Divider(thickness: 0.1, height: 1), - ListTile( - onTap: Get.back, - minLeadingWidth: 0, - dense: true, - title: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - textAlign: TextAlign.center, - ), - ), - ], + ], + ), ), ); } diff --git a/lib/pages/dynamics/widgets/author_panel_grpc.dart b/lib/pages/dynamics/widgets/author_panel_grpc.dart index 8e86c2a9..34329448 100644 --- a/lib/pages/dynamics/widgets/author_panel_grpc.dart +++ b/lib/pages/dynamics/widgets/author_panel_grpc.dart @@ -158,131 +158,137 @@ class MorePanel extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), - // clipBehavior: Clip.hardEdge, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: () => Get.back(), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), + return MediaQuery.removePadding( + context: context, + removeLeft: true, + removeRight: true, + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () => Get.back(), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + child: Container( + height: 35, + padding: const EdgeInsets.only(bottom: 2), + child: Center( + child: Container( + width: 32, + height: 3, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.outline, + borderRadius: + const BorderRadius.all(Radius.circular(3))), + ), ), ), ), - ), - if (item.type == 'DYNAMIC_TYPE_AV') + if (item.type == 'DYNAMIC_TYPE_AV') + ListTile( + onTap: () async { + try { + String bvid = item.modules.moduleDynamic.major.archive.bvid; + var res = await UserHttp.toViewLater(bvid: bvid); + SmartDialog.showToast(res['msg']); + Get.back(); + } catch (err) { + SmartDialog.showToast('出错了:${err.toString()}'); + } + }, + minLeadingWidth: 0, + // dense: true, + leading: const Icon(Icons.watch_later_outlined, size: 19), + title: Text( + '稍后再看', + style: Theme.of(context).textTheme.titleSmall, + ), + ), ListTile( - onTap: () async { - try { - String bvid = item.modules.moduleDynamic.major.archive.bvid; - var res = await UserHttp.toViewLater(bvid: bvid); - SmartDialog.showToast(res['msg']); - Get.back(); - } catch (err) { - SmartDialog.showToast('出错了:${err.toString()}'); - } - }, - minLeadingWidth: 0, - // dense: true, - leading: const Icon(Icons.watch_later_outlined, size: 19), title: Text( - '稍后再看', + '分享动态', style: Theme.of(context).textTheme.titleSmall, ), - ), - ListTile( - title: Text( - '分享动态', - style: Theme.of(context).textTheme.titleSmall, - ), - leading: const Icon(Icons.share_outlined, size: 19), - onTap: () { - Get.back(); - Utils.shareText( - '${HttpString.dynamicShareBaseUrl}/${item.idStr}'); - }, - minLeadingWidth: 0, - ), - ListTile( - title: Text( - '临时屏蔽:${item.modules.moduleAuthor.name}', - style: Theme.of(context).textTheme.titleSmall, - ), - leading: const Icon(Icons.visibility_off_outlined, size: 19), - onTap: () { - Get.back(); - DynamicsController dynamicsController = - Get.find(); - dynamicsController.tempBannedList - .add(item.modules.moduleAuthor.mid); - SmartDialog.showToast( - '已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复'); - }, - minLeadingWidth: 0, - ), - if (item.modules.moduleAuthor.mid == Accounts.main.mid) - ListTile( - onTap: () async { + leading: const Icon(Icons.share_outlined, size: 19), + onTap: () { Get.back(); - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('确定删除该动态?'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () { - Get.back(); - onRemove?.call(item.idStr); - }, - child: const Text('确定'), - ), - ], - )); + Utils.shareText( + '${HttpString.dynamicShareBaseUrl}/${item.idStr}'); }, minLeadingWidth: 0, - leading: Icon(Icons.delete_outline, - color: Theme.of(context).colorScheme.error, size: 19), - title: Text('删除', - style: Theme.of(context) - .textTheme - .titleSmall! - .copyWith(color: Theme.of(context).colorScheme.error)), ), - const Divider(thickness: 0.1, height: 1), - ListTile( - onTap: () => Get.back(), - minLeadingWidth: 0, - dense: true, - title: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - textAlign: TextAlign.center, + ListTile( + title: Text( + '临时屏蔽:${item.modules.moduleAuthor.name}', + style: Theme.of(context).textTheme.titleSmall, + ), + leading: const Icon(Icons.visibility_off_outlined, size: 19), + onTap: () { + Get.back(); + DynamicsController dynamicsController = + Get.find(); + dynamicsController.tempBannedList + .add(item.modules.moduleAuthor.mid); + SmartDialog.showToast( + '已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复'); + }, + minLeadingWidth: 0, ), - ), - ], + if (item.modules.moduleAuthor.mid == Accounts.main.mid) + ListTile( + onTap: () async { + Get.back(); + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('确定删除该动态?'), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: + Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + onRemove?.call(item.idStr); + }, + child: const Text('确定'), + ), + ], + )); + }, + minLeadingWidth: 0, + leading: Icon(Icons.delete_outline, + color: Theme.of(context).colorScheme.error, size: 19), + title: Text('删除', + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith(color: Theme.of(context).colorScheme.error)), + ), + const Divider(thickness: 0.1, height: 1), + ListTile( + onTap: () => Get.back(), + minLeadingWidth: 0, + dense: true, + title: Text( + '取消', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + textAlign: TextAlign.center, + ), + ), + ], + ), ), ); } diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index 7c83b86d..6819c577 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -8,7 +8,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:qr_flutter/qr_flutter.dart'; +import 'package:pretty_qr_code/pretty_qr_code.dart'; import 'package:saver_gallery/saver_gallery.dart'; import 'controller.dart'; @@ -52,7 +52,7 @@ class _LoginPageState extends State { SmartDialog.showLoading(msg: '正在生成截图'); RenderRepaintBoundary boundary = globalKey.currentContext! .findRenderObject()! as RenderRepaintBoundary; - var image = await boundary.toImage(); + var image = await boundary.toImage(pixelRatio: 3); ByteData? byteData = await image.toByteData(format: ImageByteFormat.png); Uint8List pngBytes = byteData!.buffer.asUint8List(); @@ -93,20 +93,35 @@ class _LoginPageState extends State { ), ); } - return QrImageView( - backgroundColor: Colors.white, - eyeStyle: QrEyeStyle( - eyeShape: QrEyeShape.square, - color: Colors.black87, + return Container( + width: 200, + height: 200, + color: Colors.white, + padding: const EdgeInsets.all(8), + child: PrettyQrView.data( + data: _loginPageCtr.codeInfo.value['data']!['url']!, + decoration: PrettyQrDecoration( + shape: PrettyQrRoundedSymbol( + color: Colors.black87, + borderRadius: BorderRadius.circular(0), + ), + ), ), - dataModuleStyle: QrDataModuleStyle( - dataModuleShape: QrDataModuleShape.square, - color: Colors.black87, - ), - data: _loginPageCtr.codeInfo.value['data']!['url']!, - size: 200, - semanticsLabel: '二维码', ); + // return QrImageView( + // backgroundColor: Colors.white, + // eyeStyle: QrEyeStyle( + // eyeShape: QrEyeShape.square, + // color: Colors.black87, + // ), + // dataModuleStyle: QrDataModuleStyle( + // dataModuleShape: QrDataModuleShape.square, + // color: Colors.black87, + // ), + // data: _loginPageCtr.codeInfo.value['data']!['url']!, + // size: 200, + // semanticsLabel: '二维码', + // ); }), ), const SizedBox(height: 10), diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index bc3fc35d..d93803e1 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -135,27 +135,32 @@ class _PlaySpeedPageState extends State { clipBehavior: Clip.hardEdge, backgroundColor: Theme.of(context).colorScheme.surface, builder: (context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 10), - ...sheetMenu.map( - (item) => ListTile( - onTap: () { - Navigator.pop(context); - menuAction(index, item['id']); - }, - minLeadingWidth: 0, - iconColor: Theme.of(context).colorScheme.onSurface, - leading: item['leading'], - title: Text( - item['title'], - style: Theme.of(context).textTheme.titleSmall, + return MediaQuery.removePadding( + context: context, + removeLeft: true, + removeRight: true, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 10), + ...sheetMenu.map( + (item) => ListTile( + onTap: () { + Navigator.pop(context); + menuAction(index, item['id']); + }, + minLeadingWidth: 0, + iconColor: Theme.of(context).colorScheme.onSurface, + leading: item['leading'], + title: Text( + item['title'], + style: Theme.of(context).textTheme.titleSmall, + ), ), ), - ), - SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom), - ], + SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom), + ], + ), ); }, ); diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index b7c8e85c..3b02229e 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -1140,12 +1140,8 @@ class ReplyItem extends StatelessWidget { Color errorColor = Theme.of(context).colorScheme.error; return Padding( - padding: EdgeInsets.only( - bottom: MediaQueryData.fromView( - WidgetsBinding.instance.platformDispatcher.views.single) - .padding - .bottom + - 20), + padding: + EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom + 20), child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart index 87d22eca..93ec4ce2 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; +import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_save.dart'; import 'package:PiliPlus/pages/video/detail/reply/widgets/zan_grpc.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/global_data.dart'; @@ -43,8 +44,8 @@ class ReplyItemGrpc extends StatelessWidget { this.onViewImage, this.onDismissed, this.callback, - required this.onCheckReply, - required this.onToggleTop, + this.onCheckReply, + this.onToggleTop, }); final ReplyInfo replyItem; final String? replyLevel; @@ -60,8 +61,8 @@ class ReplyItemGrpc extends StatelessWidget { final VoidCallback? onViewImage; final ValueChanged? onDismissed; final Function(List, int)? callback; - final ValueChanged onCheckReply; - final Function(bool isUpTop, int rpid) onToggleTop; + final ValueChanged? onCheckReply; + final Function(bool isUpTop, int rpid)? onToggleTop; @override Widget build(BuildContext context) { @@ -277,7 +278,6 @@ class ReplyItemGrpc extends StatelessWidget { }, child: Row( crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, children: [ lfAvtar(context), const SizedBox(width: 12), @@ -344,8 +344,12 @@ class ReplyItemGrpc extends StatelessWidget { ), // title Padding( - padding: - const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4), + padding: EdgeInsets.only( + top: 10, + left: replyLevel == '' ? 6 : 45, + right: 6, + bottom: 4, + ), child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { String text = replyItem.content.message; @@ -400,12 +404,12 @@ class ReplyItemGrpc extends StatelessWidget { ), ), // 操作区域 - buttonAction(context, replyItem.replyControl), + if (replyLevel != '') buttonAction(context, replyItem.replyControl), // 一楼的评论 - if (( //replyItem.replyControl!.isShow! || + if (showReplyRow && + ( //replyItem.replyControl!.isShow! || replyItem.replies.isNotEmpty || - replyItem.replyControl.subReplyEntryText.isNotEmpty) && - showReplyRow) ...[ + replyItem.replyControl.subReplyEntryText.isNotEmpty)) ...[ Padding( padding: const EdgeInsets.only(top: 5, bottom: 12), child: replyItemRow( @@ -1195,11 +1199,31 @@ class ReplyItemGrpc extends StatelessWidget { break; case 'checkReply': Get.back(); - onCheckReply(item); + onCheckReply?.call(item); break; case 'top': Get.back(); - onToggleTop(item.replyControl.isUpTop, item.id.toInt()); + onToggleTop?.call(item.replyControl.isUpTop, item.id.toInt()); + break; + case 'saveReply': + Get.back(); + Get.generalDialog( + barrierLabel: '', + barrierDismissible: true, + pageBuilder: (context, animation, secondaryAnimation) { + return ReplySavePanel(replyItem: item); + }, + transitionDuration: const Duration(milliseconds: 255), + transitionBuilder: (context, animation, secondaryAnimation, child) { + var tween = Tween(begin: 0, end: 1) + .chain(CurveTween(curve: Curves.easeInOut)); + return FadeTransition( + opacity: animation.drive(tween), + child: child, + ); + }, + routeSettings: RouteSettings(arguments: Get.arguments), + ); break; default: } @@ -1207,93 +1231,108 @@ class ReplyItemGrpc extends StatelessWidget { Color errorColor = Theme.of(context).colorScheme.error; - return Padding( - padding: EdgeInsets.only( - bottom: MediaQueryData.fromView( - WidgetsBinding.instance.platformDispatcher.views.single) - .padding - .bottom + - 20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: Get.back, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), + return MediaQuery.removePadding( + context: context, + removeLeft: true, + removeRight: true, + child: Padding( + padding: + EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom + 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: Get.back, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + child: Container( + height: 35, + padding: const EdgeInsets.only(bottom: 2), + child: Center( + child: Container( + width: 32, + height: 3, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.outline, + borderRadius: + const BorderRadius.all(Radius.circular(3))), + ), ), ), ), - ), - if (ownerMid == upMid.toInt() || ownerMid == item.member.mid.toInt()) - ListTile( - onTap: () => menuActionHandler('delete'), - minLeadingWidth: 0, - leading: Icon(Icons.delete_outlined, color: errorColor, size: 19), - title: Text('删除', - style: Theme.of(context) - .textTheme - .titleSmall! - .copyWith(color: errorColor)), - ), - if (ownerMid != 0) - ListTile( - onTap: () => menuActionHandler('report'), - minLeadingWidth: 0, - leading: Icon(Icons.error_outline, color: errorColor, size: 19), - title: Text('举报', - style: Theme.of(context) - .textTheme - .titleSmall! - .copyWith(color: errorColor)), - ), - if (replyLevel == '1' && isSubReply.not && ownerMid == upMid.toInt()) - ListTile( - onTap: () => menuActionHandler('top'), - minLeadingWidth: 0, - leading: Icon(Icons.vertical_align_top, size: 19), - title: Text('${replyItem.replyControl.isUpTop ? '取消' : ''}置顶', - style: Theme.of(context).textTheme.titleSmall!), - ), - ListTile( - onTap: () => menuActionHandler('copyAll'), - minLeadingWidth: 0, - leading: const Icon(Icons.copy_all_outlined, size: 19), - title: Text('复制全部', style: Theme.of(context).textTheme.titleSmall), - ), - ListTile( - onTap: () => menuActionHandler('copyFreedom'), - minLeadingWidth: 0, - leading: const Icon(Icons.copy_outlined, size: 19), - title: Text('自由复制', style: Theme.of(context).textTheme.titleSmall), - ), - if (item.mid.toInt() == ownerMid) - ListTile( - onTap: () => menuActionHandler('checkReply'), - minLeadingWidth: 0, - leading: Stack( - alignment: Alignment.center, - children: [ - const Icon(Icons.shield_outlined, size: 19), - const Icon(Icons.reply, size: 12), - ], + if (ownerMid == upMid.toInt() || + ownerMid == item.member.mid.toInt()) + ListTile( + onTap: () => menuActionHandler('delete'), + minLeadingWidth: 0, + leading: + Icon(Icons.delete_outlined, color: errorColor, size: 19), + title: Text('删除', + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith(color: errorColor)), ), + if (ownerMid != 0) + ListTile( + onTap: () => menuActionHandler('report'), + minLeadingWidth: 0, + leading: Icon(Icons.error_outline, color: errorColor, size: 19), + title: Text('举报', + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith(color: errorColor)), + ), + if (replyLevel == '1' && + isSubReply.not && + ownerMid == upMid.toInt()) + ListTile( + onTap: () => menuActionHandler('top'), + minLeadingWidth: 0, + leading: Icon(Icons.vertical_align_top, size: 19), + title: Text('${replyItem.replyControl.isUpTop ? '取消' : ''}置顶', + style: Theme.of(context).textTheme.titleSmall!), + ), + ListTile( + onTap: () => menuActionHandler('copyAll'), + minLeadingWidth: 0, + leading: const Icon(Icons.copy_all_outlined, size: 19), title: - Text('检查评论', style: Theme.of(context).textTheme.titleSmall), + Text('复制全部', style: Theme.of(context).textTheme.titleSmall), ), - ], + ListTile( + onTap: () => menuActionHandler('copyFreedom'), + minLeadingWidth: 0, + leading: const Icon(Icons.copy_outlined, size: 19), + title: + Text('自由复制', style: Theme.of(context).textTheme.titleSmall), + ), + ListTile( + onTap: () => menuActionHandler('saveReply'), + minLeadingWidth: 0, + leading: const Icon(Icons.save_alt, size: 19), + title: + Text('保存评论', style: Theme.of(context).textTheme.titleSmall), + ), + if (item.mid.toInt() == ownerMid) + ListTile( + onTap: () => menuActionHandler('checkReply'), + minLeadingWidth: 0, + leading: Stack( + alignment: Alignment.center, + children: [ + const Icon(Icons.shield_outlined, size: 19), + const Icon(Icons.reply, size: 12), + ], + ), + title: + Text('检查评论', style: Theme.of(context).textTheme.titleSmall), + ), + ], + ), ), ); } diff --git a/lib/pages/video/detail/reply/widgets/reply_save.dart b/lib/pages/video/detail/reply/widgets/reply_save.dart new file mode 100644 index 00000000..e7e8a61a --- /dev/null +++ b/lib/pages/video/detail/reply/widgets/reply_save.dart @@ -0,0 +1,385 @@ +import 'dart:math'; +import 'dart:typed_data'; +import 'dart:ui'; +import 'package:PiliPlus/common/widgets/icon_button.dart'; +import 'package:PiliPlus/common/widgets/network_img_layer.dart'; +import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; +import 'package:PiliPlus/models/dynamics/result.dart'; +import 'package:PiliPlus/pages/video/detail/introduction/controller.dart'; +import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:pretty_qr_code/pretty_qr_code.dart'; +import 'package:saver_gallery/saver_gallery.dart'; +import 'package:share_plus/share_plus.dart'; + +class ReplySavePanel extends StatefulWidget { + const ReplySavePanel({required this.replyItem, super.key}); + + final ReplyInfo replyItem; + + @override + State createState() => _ReplySavePanelState(); +} + +class _ReplySavePanelState extends State { + final boundaryKey = GlobalKey(); + + void _onSaveOrSharePic([bool isShare = false]) async { + SmartDialog.showLoading(); + try { + RenderRepaintBoundary boundary = boundaryKey.currentContext! + .findRenderObject() as RenderRepaintBoundary; + var image = await boundary.toImage(pixelRatio: 3); + ByteData? byteData = await image.toByteData(format: ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + String picName = + "plpl_reply_${DateTime.now().toString().replaceAll(RegExp(r'[- :]'), '').split('.').first}"; + if (isShare) { + Get.back(); + SmartDialog.dismiss(); + Share.shareXFiles( + [ + XFile.fromData( + pngBytes, + name: picName, + mimeType: 'image/png', + ) + ], + sharePositionOrigin: await Utils.isIpad() + ? Rect.fromLTWH(0, 0, Get.width, Get.height / 2) + : null, + ); + } else { + final result = await SaverGallery.saveImage( + pngBytes, + fileName: '$picName.png', + androidRelativePath: "Pictures/PiliPlus", + skipIfExists: false, + ); + SmartDialog.dismiss(); + if (result.isSuccess) { + Get.back(); + SmartDialog.showToast('保存成功'); + } else if (result.errorMessage?.isNotEmpty == true) { + SmartDialog.showToast(result.errorMessage!); + } + } + } catch (e) { + debugPrint('on save/share reply: $e'); + SmartDialog.dismiss(); + } + } + + @override + Widget build(BuildContext context) { + String? cover; + String? title; + int? pubdate; + String? uname; + String uri = ''; + + final currentRoute = Get.currentRoute; + late final hasRoot = widget.replyItem.hasRoot(); + + if (currentRoute.startsWith('/video')) { + try { + final heroTag = Get.arguments?['heroTag']; + late final ctr = Get.find(tag: heroTag); + cover = ctr.videoDetail.value.pic; + title = ctr.videoDetail.value.title; + pubdate = ctr.videoDetail.value.pubdate; + uname = ctr.videoDetail.value.owner?.name; + } catch (_) {} + uri = + 'bilibili://video/${widget.replyItem.oid}?comment_root_id=${hasRoot ? widget.replyItem.root : widget.replyItem.id}${hasRoot ? '&comment_secondary_id=${widget.replyItem.id}' : ''}'; + } else if (currentRoute.startsWith('/dynamicDetail')) { + try { + DynamicItemModel item = Get.arguments['item']; + uname = item.modules?.moduleAuthor?.name; + final type = widget.replyItem.type.toInt(); + late final oid = item.idStr; + late final rootId = + hasRoot ? widget.replyItem.root : widget.replyItem.id; + late final anchor = hasRoot ? 'anchor=${widget.replyItem.id}&' : ''; + late final enterUri = 'bilibili://following/detail/$oid'; + uri = switch (type) { + 1 => + 'bilibili://video/${item.basic!['rid_str']}?comment_root_id=${hasRoot ? widget.replyItem.root : widget.replyItem.id}${hasRoot ? '&comment_secondary_id=${widget.replyItem.id}' : ''}', + 11 || + 12 => + 'bilibili://comment/detail/$type/${item.basic!['rid_str']}/$rootId/?${anchor}enterUri=$enterUri', + _ => + 'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri', + }; + } catch (_) {} + } else if (currentRoute.startsWith('/Scaffold')) { + try { + final type = widget.replyItem.type.toInt(); + late final oid = Get.arguments['oid']; + late final rootId = + hasRoot ? widget.replyItem.root : widget.replyItem.id; + late final anchor = hasRoot ? 'anchor=${widget.replyItem.id}&' : ''; + late final enterUri = 'bilibili://following/detail/$oid'; + uri = switch (type) { + 1 => + 'bilibili://video/$oid?comment_root_id=${hasRoot ? widget.replyItem.root : widget.replyItem.id}${hasRoot ? '&comment_secondary_id=${widget.replyItem.id}' : ''}', + 11 || + 12 => + 'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=${Get.arguments['enterUri']}', + _ => + 'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri', + }; + } catch (_) {} + } else if (currentRoute.startsWith('/htmlRender')) { + try { + final type = widget.replyItem.type.toInt(); + late final oid = widget.replyItem.oid; + late final rootId = + hasRoot ? widget.replyItem.root : widget.replyItem.id; + late final anchor = hasRoot ? 'anchor=${widget.replyItem.id}&' : ''; + late final enterUri = + 'bilibili://following/detail/${Get.parameters['id']}'; + uri = + 'bilibili://comment/detail/$type/$oid/$rootId/?${anchor}enterUri=$enterUri'; + } catch (_) {} + } + + debugPrint(uri); + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Get.back(); + }, + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.only(top: 12, bottom: 80), + child: SafeArea( + child: GestureDetector( + onTap: () {}, + child: Container( + width: min(Get.width, Get.height), + margin: const EdgeInsets.symmetric(horizontal: 12), + child: RepaintBoundary( + key: boundaryKey, + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IgnorePointer( + child: ReplyItemGrpc( + replyItem: widget.replyItem, + showReplyRow: false, + replyLevel: '', + needDivider: false, + ), + ), + if (cover?.isNotEmpty == true && + title?.isNotEmpty == true) + Container( + height: 81, + clipBehavior: Clip.hardEdge, + margin: + const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .onInverseSurface, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + NetworkImgLayer( + radius: 6, + src: cover!, + height: MediaQuery.textScalerOf(context) + .scale(65), + width: MediaQuery.textScalerOf(context) + .scale(65) * + 16 / + 9, + quality: 100, + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '$title\n', + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (pubdate != null) ...[ + const Spacer(), + Text( + Utils.dateFormat( + pubdate, + formatType: 'detail', + ), + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline, + ), + ), + ], + ], + ), + ), + ], + ), + ), + Row( + children: [ + Image.asset( + 'assets/images/logo/logo_2.png', + width: 100, + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + if (uri.isNotEmpty) ...[ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (uname?.isNotEmpty == true) ...[ + Text( + '@$uname', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .primary, + ), + ), + const SizedBox(height: 4), + ], + Text( + '识别二维码,查看评论', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + const SizedBox(height: 4), + Text( + DateTime.now() + .toString() + .split('.') + .first, + style: TextStyle( + fontSize: 13, + color: Theme.of(context) + .colorScheme + .outline, + ), + ), + ], + ), + ), + Container( + width: 100, + height: 100, + padding: const EdgeInsets.all(12), + child: Container( + color: Get.isDarkMode + ? Colors.white + : Theme.of(context).colorScheme.surface, + padding: const EdgeInsets.all(3), + child: PrettyQrView.data( + data: uri, + decoration: const PrettyQrDecoration( + shape: PrettyQrRoundedSymbol( + borderRadius: BorderRadius.zero, + ), + ), + ), + ), + ), + ], + ], + ), + ], + ), + ), + ), + ), + ), + ), + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Colors.black87, + Colors.transparent, + ], + ), + ), + padding: const EdgeInsets.only(bottom: 25, top: 10), + child: SafeArea( + top: false, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + iconButton( + size: 42, + tooltip: '关闭', + context: context, + icon: Icons.clear, + onPressed: Get.back, + bgColor: Theme.of(context).colorScheme.onInverseSurface, + iconColor: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 40), + iconButton( + size: 42, + tooltip: '分享', + context: context, + icon: Icons.share, + onPressed: () => _onSaveOrSharePic(true), + ), + const SizedBox(width: 40), + iconButton( + size: 42, + tooltip: '保存', + context: context, + icon: Icons.save_alt, + onPressed: () => _onSaveOrSharePic(), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/video/detail/reply_reply/controller.dart b/lib/pages/video/detail/reply_reply/controller.dart index 5cf12072..c6311d69 100644 --- a/lib/pages/video/detail/reply_reply/controller.dart +++ b/lib/pages/video/detail/reply_reply/controller.dart @@ -67,10 +67,8 @@ class VideoReplyReplyController extends ReplyController firstFloor = replies.root; } if (id != null) { - index = replies.root.replies - .map((item) => item.id.toInt()) - .toList() - .indexOf(id!); + index = + replies.root.replies.indexWhere((item) => item.id.toInt() == id); if (index == -1) { index = null; } else { diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index d606fa66..0f443975 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -110,6 +110,12 @@ class PiliScheme { int? rpid = int.tryParse(queryParameters['comment_root_id']!); if (oid != null && rpid != null) { Get.to( + arguments: { + 'oid': oid, + 'rpid': rpid, + 'type': ReplyType.video.index, + 'id': queryParameters['comment_secondary_id'], + }, () => Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( @@ -240,16 +246,24 @@ class PiliScheme { if (path.startsWith("/detail/")) { // bilibili://comment/detail/17/832703053858603029/238686570016/?subType=0&anchor=238686628816&showEnter=1&extraIntentId=0&scene=1&enterName=%E6%9F%A5%E7%9C%8B%E5%8A%A8%E6%80%81%E8%AF%A6%E6%83%85&enterUri=bilibili://following/detail/832703053858603029 List pathSegments = uri.pathSegments; + Map queryParameters = uri.queryParameters; int type = int.parse(pathSegments[1]); // business_id int oid = int.parse(pathSegments[2]); // subject_id int rootId = int.parse(pathSegments[3]); // root_id // target_id - int? rpId = uri.queryParameters['anchor'] != null // source_id - ? int.tryParse(uri.queryParameters['anchor']!) + int? rpId = queryParameters['anchor'] != null // source_id + ? int.tryParse(queryParameters['anchor']!) : null; - // int subType = int.parse(value.queryParameters['subType'] ?? '0'); + // int subType = int.parse(queryParameters['subType'] ?? '0'); // int extraIntentId = - // int.parse(value.queryParameters['extraIntentId'] ?? '0'); + // int.parse(queryParameters['extraIntentId'] ?? '0'); Get.to( + arguments: { + 'oid': oid, + 'rpid': rootId, + 'id': rpId, + 'type': type, + 'enterUri': queryParameters['enterUri'], + }, () => Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( @@ -258,7 +272,7 @@ class PiliScheme { IconButton( tooltip: '前往', onPressed: () { - String? enterUri = uri.queryParameters['enterUri']; + String? enterUri = queryParameters['enterUri']; if (enterUri != null) { routePush(Uri.parse(enterUri)); } else { @@ -289,6 +303,11 @@ class PiliScheme { int oid = int.parse(pathSegments[2]); // subject_id int rpId = int.parse(pathSegments[3]); // source_id Get.to( + arguments: { + 'oid': oid, + 'rpid': rpId, + 'type': type, + }, () => Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( @@ -328,6 +347,22 @@ class PiliScheme { // businessId == 17 => dynId == oid // bilibili://following/detail/832703053858603029 (dynId) // bilibili://following/detail/12345678?comment_root_id=654321\u0026comment_on=1 + String? cvid = RegExp(r'^/detail/cv(\d+)', caseSensitive: false) + .firstMatch(path) + ?.group(1); + if (cvid != null) { + Utils.toDupNamed( + '/htmlRender', + parameters: { + 'url': 'https://www.bilibili.com/read/cv$cvid', + 'title': '', + 'id': 'cv$cvid', + 'dynamicType': 'read' + }, + off: off, + ); + return true; + } if ((oid != null || businessId == 17) && path.startsWith("/detail/")) { final queryParameters = uri.queryParameters; @@ -337,6 +372,12 @@ class PiliScheme { int? rpid = int.tryParse(commentRootId); if (dynId != null && rpid != null) { Get.to( + arguments: { + 'oid': oid ?? dynId, + 'rpid': rpid, + 'type': businessId ?? ReplyType.dynamics.index, + 'id': queryParameters['comment_secondary_id'], + }, () => Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( diff --git a/lib/utils/download.dart b/lib/utils/download.dart index 70cd0b82..5f367314 100644 --- a/lib/utils/download.dart +++ b/lib/utils/download.dart @@ -29,7 +29,7 @@ class DownloadUtils { File(path).writeAsBytesSync(response.data); Rect? sharePositionOrigin; - if (Platform.isIOS && (await Utils.isIpad())) { + if (await Utils.isIpad()) { sharePositionOrigin = Rect.fromLTWH(0, 0, Get.width, Get.height / 2); } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 1b0c165f..38ed4c0c 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -408,7 +408,7 @@ class Utils { RegExp(r'(@(\d+[a-z]_?)*)(\..*)?$', caseSensitive: false); static String thumbnailImgUrl(String? src, [int? quality]) { - if (src != null) { + if (src != null && quality != 100) { bool hasMatch = false; src = src.splitMapJoin( regExp, @@ -430,7 +430,10 @@ class Utils { static bool? _isIpad; - static Future isIpad() async { + static FutureOr isIpad() async { + if (Platform.isIOS.not) { + return false; + } if (_isIpad != null) { return _isIpad!; } @@ -443,7 +446,7 @@ class Utils { static void shareText(String text) async { try { Rect? sharePositionOrigin; - if (Platform.isIOS && (await isIpad())) { + if (await isIpad()) { sharePositionOrigin = Rect.fromLTWH(0, 0, Get.width, Get.height / 2); } Share.share( @@ -1866,22 +1869,6 @@ class Utils { } } - static double getSheetHeight(BuildContext context) { - double height = context.height.abs(); - double width = context.width.abs(); - if (height > width) { - //return height * 0.7; - double paddingTop = MediaQueryData.fromView( - WidgetsBinding.instance.platformDispatcher.views.single) - .padding - .top; - paddingTop += width * 9 / 16; - return height - paddingTop; - } - //横屏状态 - return height; - } - static void appSign(Map params, [String appkey = Constants.appKey, String appsec = Constants.appSec]) { params['appkey'] = appkey; diff --git a/pubspec.lock b/pubspec.lock index f4b644d8..1fac8205 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1440,6 +1440,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.1" + pretty_qr_code: + dependency: "direct main" + description: + name: pretty_qr_code + sha256: cbdb4af29da1c1fa21dd76f809646c591320ab9e435d3b0eab867492d43607d5 + url: "https://pub.dev" + source: hosted + version: "3.3.0" protobuf: dependency: "direct main" description: @@ -1472,14 +1480,6 @@ packages: 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" rxdart: dependency: "direct overridden" description: diff --git a/pubspec.yaml b/pubspec.yaml index f5ca1e8f..95b75822 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,7 +54,8 @@ dependencies: saver_gallery: ^4.0.1 # QRCode - qr_flutter: ^4.1.0 + # qr_flutter: ^4.1.0 + pretty_qr_code: ^3.3.0 # 存储 path_provider: ^2.1.5