diff --git a/lib/http/api.dart b/lib/http/api.dart index 787b036b..04983339 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -604,4 +604,7 @@ class Api { /// 图片上传 static const String uploadImage = '/x/dynamic/feed/draw/upload_bfs'; + + /// 更新追番状态 + static const String updateBangumiStatus = '/pgc/web/follow/status/update'; } diff --git a/lib/http/bangumi.dart b/lib/http/bangumi.dart index 91508682..e9ccce96 100644 --- a/lib/http/bangumi.dart +++ b/lib/http/bangumi.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; import '../models/bangumi/list.dart'; import 'index.dart'; +import 'package:html/parser.dart' as html_parser; +import 'package:html/dom.dart' as html_dom; class BangumiHttp { static Future bangumiList({int? page}) async { @@ -33,4 +36,49 @@ class BangumiHttp { }; } } + + // 获取追番状态 + static Future bangumiStatus({required int seasonId}) async { + var res = await Request() + .get('https://www.bilibili.com/bangumi/play/ss$seasonId'); + html_dom.Document document = html_parser.parse(res.data); + // 查找 id 为 __NEXT_DATA__ 的 script 元素 + html_dom.Element? scriptElement = + document.querySelector('script#\\__NEXT_DATA__'); + if (scriptElement != null) { + // 提取 script 元素的内容 + String scriptContent = scriptElement.text; + final dynamic scriptContentJson = jsonDecode(scriptContent); + Map followState = scriptContentJson['props']['pageProps']['followState']; + return { + 'status': true, + 'data': { + 'isFollowed': followState['isFollowed'], + 'followStatus': followState['followStatus'] + } + }; + } else { + print('Script element with id "__NEXT_DATA__" not found.'); + } + } + + // 更新追番状态 + static Future updateBangumiStatus({ + required int seasonId, + required int status, + }) async { + var res = await Request().post(Api.updateBangumiStatus, data: { + 'season_id': seasonId, + 'status': status, + }); + 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/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart index cf428c28..f2d7807e 100644 --- a/lib/pages/bangumi/introduction/controller.dart +++ b/lib/pages/bangumi/introduction/controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/http/bangumi.dart'; import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/search.dart'; import 'package:pilipala/http/video.dart'; @@ -52,17 +53,27 @@ class BangumiIntroController extends GetxController { Rx favFolderData = FavFolderData().obs; List addMediaIdsNew = []; List delMediaIdsNew = []; - // 关注状态 默认未关注 - RxMap followStatus = {}.obs; + // 追番状态 1想看 2在看 3已看 + RxBool isFollowed = false.obs; + RxInt followStatus = 1.obs; int _tempThemeValue = -1; var userInfo; PersistentBottomSheetController? bottomSheetController; + List> followStatusList = [ + {'title': '标记为 「想看」', 'status': 1}, + {'title': '标记为 「在看」', 'status': 2}, + {'title': '标记为 「已看」', 'status': 3}, + {'title': '取消追番', 'status': -1}, + ]; @override void onInit() { super.onInit(); userInfo = userInfoCache.get('userInfoCache'); userLogin = userInfo != null; + if (userLogin && seasonId != null) { + bangumiStatus(); + } } // 获取番剧简介&选集 @@ -239,15 +250,22 @@ class BangumiIntroController extends GetxController { // 追番 Future bangumiAdd() async { - var result = - await VideoHttp.bangumiAdd(seasonId: bangumiDetail.value.seasonId); + var result = await VideoHttp.bangumiAdd( + seasonId: seasonId ?? bangumiDetail.value.seasonId); + if (result['status']) { + followStatus.value = 2; + isFollowed.value = true; + } SmartDialog.showToast(result['msg']); } // 取消追番 Future bangumiDel() async { - var result = - await VideoHttp.bangumiDel(seasonId: bangumiDetail.value.seasonId); + var result = await VideoHttp.bangumiDel( + seasonId: seasonId ?? bangumiDetail.value.seasonId); + if (result['status']) { + isFollowed.value = false; + } SmartDialog.showToast(result['msg']); } @@ -315,4 +333,35 @@ class BangumiIntroController extends GetxController { hiddenEpisodeBottomSheet() { bottomSheetController?.close(); } + + // 获取追番状态 + Future bangumiStatus() async { + var result = await BangumiHttp.bangumiStatus(seasonId: seasonId!); + if (result['status']) { + followStatus.value = result['data']['followStatus']; + isFollowed.value = result['data']['isFollowed']; + } + return result; + } + + // 更新追番状态 + Future updateBangumiStatus(int status) async { + Get.back(); + if (status == -1) { + bangumiDel(); + } else { + var result = await BangumiHttp.bangumiStatus(seasonId: seasonId!); + if (result['status']) { + followStatus.value = status; + final title = followStatusList.firstWhere( + (e) => e['status'] == status, + orElse: () => {'title': '未知状态'}, + )['title']; + SmartDialog.showToast('追番状态$title'); + } else { + SmartDialog.showToast(result['msg']); + } + return result; + } + } } diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index b79b37b8..b90ef992 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -259,27 +259,11 @@ class _BangumiInfoState extends State { ), ), const SizedBox(width: 20), - SizedBox( - width: 34, - height: 34, - child: IconButton( - style: ButtonStyle( - padding: MaterialStateProperty.all( - EdgeInsets.zero), - backgroundColor: - MaterialStateProperty.resolveWith( - (Set states) { - return t.colorScheme.primaryContainer - .withOpacity(0.7); - }), - ), - onPressed: () => - bangumiIntroController.bangumiAdd(), - icon: Icon( - Icons.favorite_border_rounded, - color: t.colorScheme.primary, - size: 22, - ), + Obx( + () => BangumiStatusWidget( + ctr: bangumiIntroController, + isFollowed: + bangumiIntroController.isFollowed.value, ), ), ], @@ -426,3 +410,97 @@ class _BangumiInfoState extends State { }); } } + +// 追番状态 +class BangumiStatusWidget extends StatelessWidget { + final BangumiIntroController ctr; + final bool isFollowed; + + const BangumiStatusWidget({ + Key? key, + required this.ctr, + required this.isFollowed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + + void updateFollowStatus() { + showModalBottomSheet( + context: context, + useRootNavigator: true, + isScrollControlled: true, + builder: (context) { + return morePanel(context, ctr); + }, + ); + } + + return Obx( + () => SizedBox( + width: 34, + height: 34, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + backgroundColor: + MaterialStateProperty.resolveWith((Set states) { + return ctr.isFollowed.value + ? colorScheme.primaryContainer.withOpacity(0.7) + : colorScheme.outlineVariant.withOpacity(0.7); + }), + ), + onPressed: + isFollowed ? () => updateFollowStatus() : () => ctr.bangumiAdd(), + icon: Icon( + ctr.isFollowed.value + ? Icons.favorite + : Icons.favorite_border_rounded, + color: ctr.isFollowed.value + ? colorScheme.primary + : colorScheme.outline, + size: 22, + ), + ), + ), + ); + } + + Widget morePanel(BuildContext context, BangumiIntroController ctr) { + return Container( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () => Get.back(), + 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))), + ), + ), + ), + ), + ...ctr.followStatusList + .map( + (e) => ListTile( + onTap: () => ctr.updateBangumiStatus(e['status']), + selected: ctr.followStatus == e['status'], + title: Text(e['title']), + ), + ) + .toList(), + const SizedBox(height: 20), + ], + ), + ); + } +} diff --git a/lib/utils/route_push.dart b/lib/utils/route_push.dart index 9ee28846..e639327d 100644 --- a/lib/utils/route_push.dart +++ b/lib/utils/route_push.dart @@ -31,7 +31,7 @@ class RoutePush { }; arguments['heroTag'] = heroTag ?? Utils.makeHeroTag(cid); Get.toNamed( - '/video?bvid=$bvid&cid=$cid&epId=$epId', + '/video?bvid=$bvid&cid=$cid&epId=$epId&seasonId=$seasonId', arguments: arguments, ); } else {