diff --git a/lib/http/api.dart b/lib/http/api.dart index 06c9fd9b..0f29db31 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -104,6 +104,9 @@ class Api { // 楼中楼 static const String replyReplyList = '/x/v2/reply/reply'; + // 评论点赞 + static const String likeReply = '/x/v2/reply/action'; + // 发表评论 // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/action.md static const String replyAdd = '/x/v2/reply/add'; @@ -142,6 +145,10 @@ class Api { // https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?host_mid=548196587&offset=&page=1&features=itemOpusStyle static const String followDynamic = '/x/polymer/web-dynamic/v1/feed/all'; + // 动态点赞 + static const String likeDynamic = + 'https://api.vc.bilibili.com/dynamic_like/v1/dynamic_like/thumb'; + // 获取稍后再看 static const String seeYouLater = '/x/v2/history/toview'; diff --git a/lib/http/constants.dart b/lib/http/constants.dart index 619060a4..1f5319fb 100644 --- a/lib/http/constants.dart +++ b/lib/http/constants.dart @@ -1,4 +1,5 @@ class HttpString { static const String baseUrl = 'https://www.bilibili.com'; static const String baseApiUrl = 'https://api.bilibili.com'; + static const String tUrl = 'https://api.vc.bilibili.com'; } diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 510e0ff2..8ec7bc3d 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -50,4 +50,31 @@ class DynamicsHttp { }; } } + + // 动态点赞 + static Future likeDynamic({ + required String? dynamicId, + required int? up, + }) async { + var res = await Request().post( + Api.likeDynamic, + queryParameters: { + 'dynamic_id': dynamicId, + 'up': up, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/http/init.dart b/lib/http/init.dart index 08c73b39..b8fb87de 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -38,6 +38,8 @@ class Request { dio.interceptors.add(cookieManager); var cookie = await cookieManager.cookieJar .loadForRequest(Uri.parse(HttpString.baseUrl)); + var cookie2 = await cookieManager.cookieJar + .loadForRequest(Uri.parse(HttpString.tUrl)); if (cookie.isEmpty) { try { await Request().get(HttpString.baseUrl); @@ -45,6 +47,13 @@ class Request { log("setCookie, ${e.toString()}"); } } + if (cookie2.isEmpty) { + try { + await Request().get(HttpString.tUrl); + } catch (e) { + log("setCookie, ${e.toString()}"); + } + } } // 移除cookie @@ -99,7 +108,6 @@ class Request { options.headers['x-bili-mid'] = user.get(UserBoxKey.userMid).toString(); } dio.options = options; - //添加拦截器 dio.interceptors ..add(ApiInterceptor()) diff --git a/lib/http/reply.dart b/lib/http/reply.dart index e69e6599..9c40a357 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -70,4 +70,32 @@ class ReplyHttp { }; } } + + // 评论点赞 + static Future likeReply({ + required int type, + required int oid, + required int rpid, + required int action, + }) async { + var res = await Request().post( + Api.likeReply, + queryParameters: { + 'type': type, + 'oid': oid, + 'rpid': rpid, + 'action': action, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart index 4f291454..c5297c1d 100644 --- a/lib/pages/dynamics/controller.dart +++ b/lib/pages/dynamics/controller.dart @@ -46,9 +46,9 @@ class DynamicsController extends GetxController { RxInt initialValue = 1.obs; Future queryFollowDynamic({type = 'init'}) async { - // if (type == 'init') { - // dynamicsList!.value = []; - // } + if (type == 'init') { + dynamicsList.clear(); + } var res = await DynamicsHttp.followDynamic( page: type == 'init' ? 1 : page, type: dynamicsType.value.values, diff --git a/lib/pages/dynamics/deatil/controller.dart b/lib/pages/dynamics/deatil/controller.dart index 22022ce2..773bf1b2 100644 --- a/lib/pages/dynamics/deatil/controller.dart +++ b/lib/pages/dynamics/deatil/controller.dart @@ -42,7 +42,6 @@ class DynamicDetailController extends GetxController { sort: sortType.index, ); if (res['status']) { - res['data'] = ReplyData.fromJson(res['data']); acount.value = res['data'].page.acount; if (res['data'].replies.isNotEmpty) { currentPage = currentPage + 1; diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index 0319f59b..b8165176 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -236,7 +236,7 @@ class _DynamicsPageState extends State List list = _dynamicsController.dynamicsList; return Obx( - () => list.length == 1 + () => list.isEmpty ? skeleton() : SliverList( delegate: diff --git a/lib/pages/dynamics/widgets/action_panel.dart b/lib/pages/dynamics/widgets/action_panel.dart index 2c737f08..82bd096a 100644 --- a/lib/pages/dynamics/widgets/action_panel.dart +++ b/lib/pages/dynamics/widgets/action_panel.dart @@ -1,55 +1,118 @@ // 操作栏 import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; +import 'package:pilipala/http/dynamics.dart'; import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/pages/dynamics/index.dart'; -final DynamicsController _dynamicsController = Get.put(DynamicsController()); +class ActionPanel extends StatefulWidget { + ActionPanel({ + super.key, + this.item, + }); + var item; -Widget action(item, context) { - ModuleStatModel stat = item.modules.moduleStat; - - return Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - TextButton.icon( - onPressed: () {}, - icon: const Icon( - FontAwesomeIcons.shareFromSquare, - size: 16, - ), - style: TextButton.styleFrom( - padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), - foregroundColor: Theme.of(context).colorScheme.outline, - ), - label: Text(stat.forward!.count ?? '转发'), - ), - TextButton.icon( - onPressed: () => - _dynamicsController.pushDetail(item, 1, action: 'comment'), - icon: const Icon( - FontAwesomeIcons.comment, - size: 16, - ), - style: TextButton.styleFrom( - padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), - foregroundColor: Theme.of(context).colorScheme.outline, - ), - label: Text(stat.comment!.count ?? '评论'), - ), - TextButton.icon( - onPressed: () {}, - icon: const Icon( - FontAwesomeIcons.thumbsUp, - size: 16, - ), - style: TextButton.styleFrom( - padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), - foregroundColor: Theme.of(context).colorScheme.outline, - ), - label: Text(stat.like!.count ?? '点赞'), - ) - ], - ); + @override + State createState() => _ActionPanelState(); +} + +class _ActionPanelState extends State { + final DynamicsController _dynamicsController = Get.put(DynamicsController()); + late ModuleStatModel stat; + + @override + void initState() { + super.initState(); + stat = widget.item!.modules.moduleStat; + } + + // 动态点赞 + onLikeDynamic() async { + var item = widget.item!; + String dynamicId = item.idStr!; + // 1 已点赞 2 不喜欢 0 未操作 + Like like = item.modules.moduleStat.like; + int count = int.parse(like.count!); + 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 { + item.modules.moduleStat.like.count = (count - 1).toString(); + item.modules.moduleStat.like.status = false; + } + setState(() {}); + } else { + SmartDialog.showToast(res['msg']); + } + } + + @override + Widget build(BuildContext context) { + var color = Theme.of(context).colorScheme.outline; + var primary = Theme.of(context).colorScheme.primary; + return Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton.icon( + onPressed: () {}, + icon: const Icon( + FontAwesomeIcons.shareFromSquare, + size: 16, + ), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), + foregroundColor: Theme.of(context).colorScheme.outline, + ), + label: Text(stat.forward!.count ?? '转发'), + ), + TextButton.icon( + onPressed: () => + _dynamicsController.pushDetail(widget.item, 1, action: 'comment'), + icon: const Icon( + FontAwesomeIcons.comment, + size: 16, + ), + style: TextButton.styleFrom( + padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), + foregroundColor: Theme.of(context).colorScheme.outline, + ), + label: Text(stat.comment!.count ?? '评论'), + ), + TextButton.icon( + onPressed: () => onLikeDynamic(), + icon: Icon( + stat.like!.status! + ? FontAwesomeIcons.solidThumbsUp + : FontAwesomeIcons.thumbsUp, + size: 16, + color: stat.like!.status! ? primary : color, + ), + 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( + stat.like!.count ?? '点赞', + key: ValueKey(stat.like!.count!), + style: TextStyle( + color: stat.like!.status! ? primary : color, + ), + ), + ), + ) + ], + ); + } } diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index 5fa50afd..3dcf2315 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -42,7 +42,7 @@ class DynamicPanel extends StatelessWidget { content(item, context, source), forWard(item, context, _dynamicsController, source), const SizedBox(height: 2), - if (source == null) action(item, context), + if (source == null) ActionPanel(item: item), ], ), ), diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 656eab23..f15a1eee 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -9,6 +9,8 @@ import 'package:pilipala/pages/video/detail/controller.dart'; import 'package:pilipala/pages/video/detail/replyNew/index.dart'; import 'package:pilipala/utils/utils.dart'; +import 'zan.dart'; + class ReplyItem extends StatelessWidget { ReplyItem({ super.key, @@ -282,29 +284,7 @@ class ReplyItem extends StatelessWidget { }, ), ), - SizedBox( - height: 32, - child: TextButton( - child: Row( - children: [ - Icon( - FontAwesomeIcons.thumbsUp, - size: 16, - color: color, - ), - const SizedBox(width: 4), - Text( - replyItem!.like.toString(), - style: TextStyle( - color: color, - fontSize: - Theme.of(context).textTheme.labelSmall!.fontSize), - ), - ], - ), - onPressed: () {}, - ), - ), + ZanButton(replyItem: replyItem, replyType: replyType), const SizedBox(width: 5) ], ); diff --git a/lib/pages/video/detail/reply/widgets/zan.dart b/lib/pages/video/detail/reply/widgets/zan.dart new file mode 100644 index 00000000..ee125abc --- /dev/null +++ b/lib/pages/video/detail/reply/widgets/zan.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:pilipala/http/reply.dart'; +import 'package:pilipala/models/common/reply_type.dart'; +import 'package:pilipala/models/video/reply/item.dart'; + +class ZanButton extends StatefulWidget { + ZanButton({ + super.key, + this.replyItem, + this.replyType, + }); + + ReplyItemModel? replyItem; + final ReplyType? replyType; + + @override + State createState() => _ZanButtonState(); +} + +class _ZanButtonState extends State { + // 评论点赞 + onLikeReply() async { + ReplyItemModel replyItem = widget.replyItem!; + int oid = replyItem.oid!; + int rpid = replyItem.rpid!; + // 1 已点赞 2 不喜欢 0 未操作 + int action = replyItem.action == 0 ? 1 : 0; + var res = await ReplyHttp.likeReply( + type: widget.replyType!.index, oid: oid, rpid: rpid, action: action); + if (res['status']) { + SmartDialog.showToast(replyItem.action == 0 ? '点赞成功' : '取消赞'); + if (action == 1) { + replyItem.like = replyItem.like! + 1; + replyItem.action = 1; + } else { + replyItem.like = replyItem.like! - 1; + replyItem.action = 0; + } + setState(() {}); + } else { + SmartDialog.showToast(res['msg']); + } + } + + @override + Widget build(BuildContext context) { + var color = Theme.of(context).colorScheme.outline; + var primary = Theme.of(context).colorScheme.primary; + return SizedBox( + height: 32, + child: TextButton( + child: Row( + children: [ + Icon( + widget.replyItem!.action == 1 + ? FontAwesomeIcons.solidThumbsUp + : FontAwesomeIcons.thumbsUp, + size: 16, + color: widget.replyItem!.action == 1 ? primary : color, + ), + const SizedBox(width: 4), + AnimatedSwitcher( + duration: const Duration(milliseconds: 400), + transitionBuilder: (Widget child, Animation animation) { + return ScaleTransition(scale: animation, child: child); + }, + child: Text(widget.replyItem!.like.toString(), + key: ValueKey(widget.replyItem!.like!), + style: TextStyle( + color: widget.replyItem!.action == 1 ? primary : color, + fontSize: + Theme.of(context).textTheme.labelSmall!.fontSize)), + ), + ], + ), + onPressed: () => onLikeReply(), + ), + ); + } +} diff --git a/lib/pages/webview/controller.dart b/lib/pages/webview/controller.dart index 0186feb0..d9e555af 100644 --- a/lib/pages/webview/controller.dart +++ b/lib/pages/webview/controller.dart @@ -58,10 +58,13 @@ class WebviewController extends GetxController { try { var cookies = await WebviewCookieManager().getCookies(HttpString.baseUrl); - var apiCookies = - await WebviewCookieManager().getCookies(HttpString.baseUrl); + var apiCookies = await WebviewCookieManager() + .getCookies(HttpString.baseApiUrl); + var tCookies = + await WebviewCookieManager().getCookies(HttpString.tUrl); await SetCookie.onSet(cookies, HttpString.baseUrl); await SetCookie.onSet(apiCookies, HttpString.baseApiUrl); + await SetCookie.onSet(tCookies, HttpString.tUrl); await UserHttp.userInfo(); var result = await UserHttp.userInfo(); print('网页登录: $result');