From 738a4402cb12b0d725050c12f4e88632a4024eb3 Mon Sep 17 00:00:00 2001 From: orz12 Date: Tue, 23 Jan 2024 15:11:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=97=A0=E7=97=95?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/video_card_h.dart | 110 +----------------- lib/common/widgets/video_card_v.dart | 102 +---------------- lib/common/widgets/video_popup_menu.dart | 139 +++++++++++++++++++++++ lib/http/init.dart | 2 + lib/http/interceptor_anonymity.dart | 51 +++++++++ lib/http/video.dart | 3 +- lib/pages/mine/controller.dart | 12 ++ lib/pages/mine/view.dart | 12 ++ lib/pages/setting/privacy_setting.dart | 24 ++++ lib/plugin/pl_player/controller.dart | 3 +- 10 files changed, 250 insertions(+), 208 deletions(-) create mode 100644 lib/common/widgets/video_popup_menu.dart create mode 100644 lib/http/interceptor_anonymity.dart diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index c78643db..2a1bedf2 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -2,14 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import '../../http/search.dart'; -import '../../http/user.dart'; -import '../../http/video.dart'; import '../../utils/utils.dart'; import '../constants.dart'; import 'badge.dart'; import 'network_img_layer.dart'; import 'stat/danmu.dart'; import 'stat/view.dart'; +import 'video_popup_menu.dart'; // 视频卡片 - 水平布局 class VideoCardH extends StatelessWidget { @@ -240,113 +239,14 @@ class VideoContent extends StatelessWidget { ), const Spacer(), - // SizedBox( - // width: 20, - // height: 20, - // child: IconButton( - // tooltip: '稍后再看', - // style: ButtonStyle( - // padding: MaterialStateProperty.all(EdgeInsets.zero), - // ), - // onPressed: () async { - // var res = - // await UserHttp.toViewLater(bvid: videoItem.bvid); - // SmartDialog.showToast(res['msg']); - // }, - // icon: Icon( - // Icons.more_vert_outlined, - // color: Theme.of(context).colorScheme.outline, - // size: 14, - // ), - // ), - // ), if (source == 'normal') SizedBox( width: 24, height: 24, - child: PopupMenuButton( - padding: EdgeInsets.zero, - icon: Icon( - Icons.more_vert_outlined, - color: Theme.of(context).colorScheme.outline, - size: 14, - ), - position: PopupMenuPosition.under, - // constraints: const BoxConstraints(maxHeight: 35), - onSelected: (String type) {}, - itemBuilder: (BuildContext context) => - >[ - PopupMenuItem( - onTap: () async { - var res = await UserHttp.toViewLater( - bvid: videoItem.bvid as String); - SmartDialog.showToast(res['msg']); - }, - value: 'pause', - height: 40, - child: const Row( - children: [ - Icon(Icons.watch_later_outlined, size: 16), - SizedBox(width: 6), - Text('稍后再看', style: TextStyle(fontSize: 13)) - ], - ), - ), - const PopupMenuDivider(), - PopupMenuItem( - onTap: () async { - SmartDialog.show( - useSystem: true, - animationType: - SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('提示'), - content: Text( - '确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' - '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'), - actions: [ - TextButton( - onPressed: () => SmartDialog.dismiss(), - child: Text( - '点错了', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), - ), - ), - TextButton( - onPressed: () async { - var res = await VideoHttp.relationMod( - mid: videoItem.owner.mid, - act: 5, - reSrc: 11, - ); - SmartDialog.dismiss(); - SmartDialog.showToast(res['code'] == 0 - ? '成功' - : res['msg']); - }, - child: const Text('确认'), - ) - ], - ); - }, - ); - }, - value: 'pause', - height: 40, - child: Row( - children: [ - const Icon(Icons.block, size: 16), - const SizedBox(width: 6), - Text('拉黑:${videoItem.owner.name}', - style: const TextStyle(fontSize: 13)) - ], - ), - ), - ], + child: VideoPopupMenu( + size: 32, + iconSize: 18, + videoItem: videoItem, ), ), ], diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 8a401a0b..bfd11ce7 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -6,14 +6,13 @@ import 'stat/danmu.dart'; import 'stat/view.dart'; import '../../http/dynamics.dart'; import '../../http/search.dart'; -import '../../http/user.dart'; -import '../../http/video.dart'; import '../../models/common/search_type.dart'; import '../../utils/id_utils.dart'; import '../../utils/utils.dart'; import '../constants.dart'; import 'badge.dart'; import 'network_img_layer.dart'; +import 'video_popup_menu.dart'; // 视频卡片 - 垂直布局 class VideoCardV extends StatelessWidget { @@ -354,102 +353,3 @@ class VideoStat extends StatelessWidget { ); } } - -class VideoPopupMenu extends StatelessWidget { - final double? size; - final double? iconSize; - final dynamic videoItem; - - const VideoPopupMenu({ - Key? key, - required this.size, - required this.iconSize, - required this.videoItem, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: size, - height: size, - child: PopupMenuButton( - padding: EdgeInsets.zero, - icon: Icon( - Icons.more_vert_outlined, - color: Theme.of(context).colorScheme.outline, - size: iconSize, - ), - position: PopupMenuPosition.under, - // constraints: const BoxConstraints(maxHeight: 35), - onSelected: (String type) {}, - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - onTap: () async { - var res = - await UserHttp.toViewLater(bvid: videoItem.bvid as String); - SmartDialog.showToast(res['msg']); - }, - value: 'pause', - height: 40, - child: const Row( - children: [ - Icon(Icons.watch_later_outlined, size: 16), - SizedBox(width: 6), - Text('稍后再看', style: TextStyle(fontSize: 13)) - ], - ), - ), - const PopupMenuDivider(), - PopupMenuItem( - onTap: () async { - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('提示'), - content: Text( - '确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' - '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'), - actions: [ - TextButton( - onPressed: () => SmartDialog.dismiss(), - child: Text( - '点错了', - style: TextStyle( - color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () async { - var res = await VideoHttp.relationMod( - mid: videoItem.owner.mid, - act: 5, - reSrc: 11, - ); - SmartDialog.dismiss(); - SmartDialog.showToast(res['msg'] ?? '成功'); - }, - child: const Text('确认'), - ) - ], - ); - }, - ); - }, - value: 'pause', - height: 40, - child: Row( - children: [ - const Icon(Icons.block, size: 16), - const SizedBox(width: 6), - Text('拉黑:${videoItem.owner.name}', - style: const TextStyle(fontSize: 13)) - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart new file mode 100644 index 00000000..7fb990fd --- /dev/null +++ b/lib/common/widgets/video_popup_menu.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +import '../../http/user.dart'; +import '../../http/video.dart'; +import '../../pages/mine/controller.dart'; + +class VideoPopupMenu extends StatelessWidget { + final double? size; + final double? iconSize; + final dynamic videoItem; + final double menuItemHeight = 50; + + const VideoPopupMenu({ + Key? key, + required this.size, + required this.iconSize, + required this.videoItem, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: size, + height: size, + child: PopupMenuButton( + padding: EdgeInsets.zero, + icon: Icon( + Icons.more_vert_outlined, + color: Theme.of(context).colorScheme.outline, + size: iconSize, + ), + position: PopupMenuPosition.under, + onSelected: (String type) {}, + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + onTap: () async { + var res = + await UserHttp.toViewLater(bvid: videoItem.bvid as String); + SmartDialog.showToast(res['msg']); + }, + value: 'pause', + height: menuItemHeight, + child: const Row( + children: [ + Icon(Icons.watch_later_outlined, size: 16), + SizedBox(width: 6), + Text('稍后再看', style: TextStyle(fontSize: 13)) + ], + ), + ), + PopupMenuItem( + onTap: () async { + SmartDialog.show( + useSystem: true, + animationType: SmartAnimationType.centerFade_otherSlide, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('提示'), + content: Text( + '确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' + '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'), + actions: [ + TextButton( + onPressed: () => SmartDialog.dismiss(), + child: Text( + '点错了', + style: TextStyle( + color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + onPressed: () async { + var res = await VideoHttp.relationMod( + mid: videoItem.owner.mid, + act: 5, + reSrc: 11, + ); + SmartDialog.dismiss(); + SmartDialog.showToast(res['msg'] ?? '成功'); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + }, + value: 'block', + height: menuItemHeight, + child: Row( + children: [ + const Icon(Icons.block, size: 16), + const SizedBox(width: 6), + Text('拉黑:${videoItem.owner.name}', + style: const TextStyle(fontSize: 13)) + ], + ), + ), + // PopupMenuItem( + // onTap: () async { + // SmartDialog.showToast("还没做"); + // }, + // value: 'anonymize', + // height: menuItemHeight, + // child: const Row( + // children: [ + // Icon(Icons.visibility_off_outlined, size: 16), + // SizedBox(width: 6), + // Text('无痕播放', + // style: TextStyle(fontSize: 13)) + // ], + // ), + // ), + PopupMenuItem( + onTap: () { + MineController.onChangeAnonymity(); + }, + value: 'anonymous', + height: menuItemHeight, + child: Row( + children: [ + Icon( + MineController.anonymity + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + size: 16, + ), + const SizedBox(width: 6), + Text(MineController.anonymity ? '退出无痕模式' : '进入无痕模式', + style: const TextStyle(fontSize: 13)) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/http/init.dart b/lib/http/init.dart index 7840499f..7c5bef3e 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -12,6 +12,7 @@ import '../utils/storage.dart'; import '../utils/utils.dart'; import 'constants.dart'; import 'interceptor.dart'; +import 'interceptor_anonymity.dart'; class Request { static final Request _instance = Request._internal(); @@ -34,6 +35,7 @@ class Request { ); cookieManager = CookieManager(cookieJar); dio.interceptors.add(cookieManager); + dio.interceptors.add(AnonymityInterceptor()); final List cookie = await cookieManager.cookieJar .loadForRequest(Uri.parse(HttpString.baseUrl)); final userInfo = userInfoCache.get('userInfoCache'); diff --git a/lib/http/interceptor_anonymity.dart b/lib/http/interceptor_anonymity.dart new file mode 100644 index 00000000..c6856a0a --- /dev/null +++ b/lib/http/interceptor_anonymity.dart @@ -0,0 +1,51 @@ +// ignore_for_file: avoid_print + +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import '../pages/mine/controller.dart'; +import 'api.dart'; + +class AnonymityInterceptor extends Interceptor { + static const List anonymityList = [ + Api.videoUrl, + Api.videoIntro, + Api.relatedList, + Api.replyList, + Api.replyReplyList, + Api.searchSuggest, + Api.searchByType, + Api.heartBeat, + Api.ab2c, + Api.bangumiInfo, + Api.liveRoomInfo, + Api.onlineTotal, + Api.webDanmaku, + Api.dynamicDetail, + Api.aiConclusion, + Api.getMemberViewApi, + Api.getSeasonDetailApi, + ]; + + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + if (MineController.anonymity) { + String uri = options.uri.toString(); + for (var i in anonymityList) { + // 如果请求的url包含无痕列表中的url,则清空cookie + // 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤 + int index = uri.indexOf(i); + if (index == -1) continue; + if (uri.lastIndexOf('/') >= index + i.length) continue; + //SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}'); + options.headers[HttpHeaders.cookieHeader] = ""; + if(options.data != null && options.data.csrf != null) { + options.data.csrf = ""; + } + break; + } + } + handler.next(options); + } +} diff --git a/lib/http/video.dart b/lib/http/video.dart index a48dd11b..d228b639 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -12,6 +12,7 @@ import '../models/video_detail_res.dart'; import '../utils/recommend_filter.dart'; import '../utils/storage.dart'; import '../utils/wbi_sign.dart'; +import '../pages/mine/controller.dart'; import 'api.dart'; import 'init.dart'; @@ -154,7 +155,7 @@ class VideoHttp { } // 免登录查看1080p - if (userInfoCache.get('userInfoCache') == null && + if ((userInfoCache.get('userInfoCache') == null || MineController.anonymity) && setting.get(SettingBoxKey.p1080, defaultValue: true)) { data['try_look'] = 1; } diff --git a/lib/pages/mine/controller.dart b/lib/pages/mine/controller.dart index d1e17a83..41016ceb 100644 --- a/lib/pages/mine/controller.dart +++ b/lib/pages/mine/controller.dart @@ -17,6 +17,7 @@ class MineController extends GetxController { Box userInfoCache = GStrorage.userInfo; Box setting = GStrorage.setting; Rx themeType = ThemeType.system.obs; + static bool anonymity = false; @override onInit() { @@ -85,6 +86,17 @@ class MineController extends GetxController { userStat.value = UserStat(); userInfoCache.delete('userInfoCache'); userLogin.value = false; + anonymity = false; + } + + static onChangeAnonymity() { + anonymity = !anonymity; + SmartDialog.showToast(anonymity + ? '已进入全局无痕模式\n\n' + '将模拟未登录用户搜索、观看视频或直播,不产生查询与播放记录\n\n' + '点赞等其它操作不受影响\n\n' + '仅本次启动有效,可随时退出' + : '已退出无痕模式'); } onChangeTheme() { diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 06c375da..6bf9df5c 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -54,6 +54,18 @@ class _MinePageState extends State { ), ), actions: [ + IconButton( + onPressed: () { + MineController.onChangeAnonymity(); + setState(() {}); + }, + icon: Icon( + MineController.anonymity + ? Icons.visibility_off_outlined + : Icons.visibility_outlined, + size: 22, + ), + ), IconButton( onPressed: () => mineController.onChangeTheme(), icon: Icon( diff --git a/lib/pages/setting/privacy_setting.dart b/lib/pages/setting/privacy_setting.dart index 07a02318..38692d9f 100644 --- a/lib/pages/setting/privacy_setting.dart +++ b/lib/pages/setting/privacy_setting.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/interceptor_anonymity.dart'; import 'package:pilipala/http/member.dart'; import 'package:pilipala/utils/storage.dart'; @@ -64,6 +65,29 @@ class _PrivacySettingState extends State { dense: false, title: Text('刷新access_key', style: titleStyle), ), + ListTile( + onTap: () { + SmartDialog.show( + builder: (context) { + return AlertDialog( + title: const Text('查看详情'), + content: Text(AnonymityInterceptor.anonymityList.join('\n')), + actions: [ + TextButton( + onPressed: () async { + SmartDialog.dismiss(); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + }, + dense: false, + title: Text('了解无痕模式', style: titleStyle), + subtitle: Text('查看无痕模式作用的API列表', style: subTitleStyle), + ), ], ), ); diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index d35987f8..5fd10406 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -13,6 +13,7 @@ import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:ns_danmaku/ns_danmaku.dart'; import 'package:pilipala/http/video.dart'; +import 'package:pilipala/pages/mine/controller.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/services/service_locator.dart'; @@ -1020,7 +1021,7 @@ class PlPlayerController { // 记录播放记录 Future makeHeartBeat(int progress, {type = 'playing'}) async { - if (!_enableHeart) { + if (!_enableHeart || MineController.anonymity) { return false; } if (videoType.value == 'live') {