From 81713a6bc4619e3c83164460da37e796965c132a Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sun, 16 Feb 2025 14:58:27 +0800 Subject: [PATCH] mod: article: add action panel related #235 Signed-off-by: bggRGjQaUbCoE --- lib/pages/dynamics/detail/controller.dart | 34 --- lib/pages/dynamics/detail/view.dart | 19 +- lib/pages/html/controller.dart | 27 ++- lib/pages/html/view.dart | 276 ++++++++++++++++++++-- lib/utils/utils.dart | 30 +++ 5 files changed, 328 insertions(+), 58 deletions(-) diff --git a/lib/pages/dynamics/detail/controller.dart b/lib/pages/dynamics/detail/controller.dart index 84c89c2ea..87a67ad52 100644 --- a/lib/pages/dynamics/detail/controller.dart +++ b/lib/pages/dynamics/detail/controller.dart @@ -1,13 +1,8 @@ import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; -import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; -import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/storage.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/reply.dart'; @@ -63,33 +58,4 @@ class DynamicDetailController extends ReplyController { page: currentPage, banWordForReply: banWordForReply, ); - - // 动态点赞 - Future onLikeDynamic(VoidCallback callback) async { - feedBack(); - String dynamicId = item.idStr!; - // 1 已点赞 2 不喜欢 0 未操作 - Like like = item.modules.moduleStat.like; - int count = like.count == '点赞' ? 0 : int.parse(like.count ?? '0'); - bool status = like.status!; - int up = status ? 2 : 1; - var res = await DynamicsHttp.likeDynamic(dynamicId: dynamicId, up: up); - if (res['status']) { - SmartDialog.showToast(!status ? '点赞成功' : '取消赞'); - if (up == 1) { - item.modules.moduleStat.like.count = (count + 1).toString(); - item.modules.moduleStat.like.status = true; - } else { - if (count == 1) { - item.modules.moduleStat.like.count = '点赞'; - } else { - item.modules.moduleStat.like.count = (count - 1).toString(); - } - item.modules.moduleStat.like.status = false; - } - callback(); - } else { - SmartDialog.showToast(res['msg']); - } - } } diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 3929ec65f..cc55161e7 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -358,7 +358,10 @@ class _DynamicDetailPageState extends State physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverPadding( - padding: EdgeInsets.only(left: padding / 4), + padding: EdgeInsets.only( + left: padding / 4, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), sliver: SliverToBoxAdapter( child: DynamicPanel( item: _dynamicDetailController.item, @@ -541,12 +544,14 @@ class _DynamicDetailPageState extends State Expanded( child: Builder( builder: (context) => TextButton.icon( - onPressed: () => - _dynamicDetailController.onLikeDynamic(() { - if (context.mounted) { - (context as Element?)?.markNeedsBuild(); - } - }), + onPressed: () => Utils.onLikeDynamic( + _dynamicDetailController.item, + () { + if (context.mounted) { + (context as Element?)?.markNeedsBuild(); + } + }, + ), icon: Icon( _dynamicDetailController .item.modules.moduleStat.like!.status! diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart index af2d125e9..0d6479a90 100644 --- a/lib/pages/html/controller.dart +++ b/lib/pages/html/controller.dart @@ -1,8 +1,12 @@ import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; +import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/storage.dart'; +import 'package:PiliPlus/utils/url_utils.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/reply.dart'; @@ -16,6 +20,8 @@ class HtmlRenderController extends ReplyController { late Map response; int? floor; + Rx item = DynamicItemModel().obs; + RxBool loaded = false.obs; late final horizontalPreview = GStorage.horizontalPreview; @@ -26,10 +32,29 @@ class HtmlRenderController extends ReplyController { id = Get.parameters['id']!; dynamicType = Get.parameters['dynamicType']!; type = dynamicType == 'picture' ? 11 : 12; - + if (RegExp(r'^cv', caseSensitive: false).hasMatch(id)) { + UrlUtils.parseRedirectUrl('https://www.bilibili.com/read/$id/') + .then((url) { + if (url != null) { + _queryDyn(url.split('/').last); + } + }); + } else { + _queryDyn(id); + } reqHtml(); } + _queryDyn(id) { + DynamicsHttp.dynamicDetail(id: id).then((res) { + if (res['status']) { + item.value = res['data']; + } else { + debugPrint('${res['msg']}'); + } + }); + } + // 请求动态内容 Future reqHtml() async { late dynamic res; diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index c16df1fbe..bd3103311 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -3,8 +3,10 @@ import 'dart:math'; import 'package:PiliPlus/common/widgets/article_content.dart'; import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/reply_sort_type.dart'; +import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart'; import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item.dart'; import 'package:PiliPlus/pages/video/detail/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/utils/extension.dart'; @@ -14,6 +16,7 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/common/skeleton/video_reply.dart'; import 'package:PiliPlus/common/widgets/html_render.dart'; @@ -323,7 +326,11 @@ class _HtmlRenderPageState extends State SliverPadding( padding: orientation == Orientation.portrait ? EdgeInsets.symmetric(horizontal: padding) - : EdgeInsets.only(left: padding / 4), + : EdgeInsets.only( + left: padding / 4, + bottom: + MediaQuery.paddingOf(context).bottom + 80, + ), sliver: _buildContent, ), if (orientation == Orientation.portrait) ...[ @@ -388,28 +395,265 @@ class _HtmlRenderPageState extends State }, ), Positioned( - bottom: MediaQuery.of(context).padding.bottom + 14, - right: 14, + left: 0, + right: 0, + bottom: 0, child: SlideTransition( position: Tween( - begin: const Offset(0, 2), + begin: const Offset(0, 1), end: const Offset(0, 0), ).animate(CurvedAnimation( parent: fabAnimationCtr, curve: Curves.easeInOut, )), - child: FloatingActionButton( - heroTag: null, - onPressed: () { - feedBack(); - _htmlRenderCtr.onReply( - context, - oid: _htmlRenderCtr.oid.value, - replyType: ReplyType.values[type], - ); - }, - tooltip: '评论动态', - child: const Icon(Icons.reply), + child: Obx( + () => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: EdgeInsets.only( + right: 14, + bottom: 14 + + (_htmlRenderCtr.item.value.idStr != null + ? 0 + : MediaQuery.of(context).padding.bottom), + ), + child: FloatingActionButton( + heroTag: null, + onPressed: () { + feedBack(); + _htmlRenderCtr.onReply( + context, + oid: _htmlRenderCtr.oid.value, + replyType: ReplyType.values[type], + ); + }, + tooltip: '评论动态', + child: const Icon(Icons.reply), + ), + ), + _htmlRenderCtr.item.value.idStr != null + ? Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + border: Border( + top: BorderSide( + color: Theme.of(context) + .colorScheme + .outline + .withOpacity(0.08), + ), + ), + ), + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Expanded( + child: Builder( + builder: (btnContext) => TextButton.icon( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + useSafeArea: true, + builder: (context) => RepostPanel( + item: _htmlRenderCtr.item.value, + callback: () { + int count = int.tryParse( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward + ?.count ?? + '0') ?? + 0; + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count = + (count + 1).toString(); + if (btnContext.mounted) { + (btnContext as Element?) + ?.markNeedsBuild(); + } + }, + ), + ); + }, + icon: Icon( + FontAwesomeIcons.shareFromSquare, + size: 16, + color: Theme.of(context) + .colorScheme + .outline, + semanticLabel: "转发", + ), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB( + 15, 0, 15, 0), + foregroundColor: Theme.of(context) + .colorScheme + .outline, + ), + label: Text( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count != + null + ? Utils.numFormat(_htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.forward! + .count) + : '转发', + ), + ), + ), + ), + Expanded( + child: TextButton.icon( + onPressed: () { + Share.share( + '${HttpString.dynamicShareBaseUrl}/${_htmlRenderCtr.item.value.idStr}'); + }, + icon: Icon( + FontAwesomeIcons.shareNodes, + size: 16, + color: + Theme.of(context).colorScheme.outline, + semanticLabel: "分享", + ), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB( + 15, 0, 15, 0), + foregroundColor: + Theme.of(context).colorScheme.outline, + ), + label: const Text('分享'), + ), + ), + Expanded( + child: Builder( + builder: (context) => TextButton.icon( + onPressed: () => Utils.onLikeDynamic( + _htmlRenderCtr.item.value, + () { + if (context.mounted) { + (context as Element?) + ?.markNeedsBuild(); + } + }, + ), + icon: Icon( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? FontAwesomeIcons.solidThumbsUp + : FontAwesomeIcons.thumbsUp, + size: 16, + color: _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .outline, + semanticLabel: _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? "已赞" + : "点赞", + ), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB( + 15, 0, 15, 0), + foregroundColor: Theme.of(context) + .colorScheme + .outline, + ), + label: AnimatedSwitcher( + duration: + const Duration(milliseconds: 400), + transitionBuilder: (Widget child, + Animation animation) { + return ScaleTransition( + scale: animation, child: child); + }, + child: Text( + _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.count != + null + ? Utils.numFormat(_htmlRenderCtr + .item + .value + .modules! + .moduleStat! + .like! + .count) + : '点赞', + style: TextStyle( + color: _htmlRenderCtr + .item + .value + .modules + ?.moduleStat + ?.like + ?.status == + true + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .outline, + ), + ), + ), + ), + ), + ), + ], + ), + ) + : const SizedBox.shrink(), + ], + ), ), ), ), diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 57e1b65f8..b03182da2 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/common/widgets/radio_widget.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/constants.dart'; +import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; @@ -45,6 +46,35 @@ class Utils { static const channel = MethodChannel("PiliPlus"); + // 动态点赞 + static Future onLikeDynamic(item, VoidCallback callback) async { + feedBack(); + String dynamicId = item.idStr!; + // 1 已点赞 2 不喜欢 0 未操作 + Like like = item.modules.moduleStat.like; + int count = like.count == '点赞' ? 0 : int.parse(like.count ?? '0'); + bool status = like.status!; + int up = status ? 2 : 1; + var res = await DynamicsHttp.likeDynamic(dynamicId: dynamicId, up: up); + if (res['status']) { + SmartDialog.showToast(!status ? '点赞成功' : '取消赞'); + if (up == 1) { + item.modules.moduleStat.like.count = (count + 1).toString(); + item.modules.moduleStat.like.status = true; + } else { + if (count == 1) { + item.modules.moduleStat.like.count = '点赞'; + } else { + item.modules.moduleStat.like.count = (count - 1).toString(); + } + item.modules.moduleStat.like.status = false; + } + callback(); + } else { + SmartDialog.showToast(res['msg']); + } + } + static void showFSSheet({ required Widget child, required bool isFullScreen,