feat: 新增无痕播放模式

This commit is contained in:
orz12
2024-01-23 15:11:20 +08:00
parent 2612126897
commit 738a4402cb
10 changed files with 250 additions and 208 deletions

View File

@@ -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<String>(
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) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
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<String>(
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,
),
),
],

View File

@@ -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<String>(
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) => <PopupMenuEntry<String>>[
PopupMenuItem<String>(
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<String>(
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))
],
),
),
],
),
);
}
}

View File

@@ -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<String>(
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) => <PopupMenuEntry<String>>[
PopupMenuItem<String>(
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<String>(
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<String>(
// 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<String>(
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))
],
),
),
],
),
);
}
}

View File

@@ -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> cookie = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl));
final userInfo = userInfoCache.get('userInfoCache');

View File

@@ -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<String> 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);
}
}

View File

@@ -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;
}

View File

@@ -17,6 +17,7 @@ class MineController extends GetxController {
Box userInfoCache = GStrorage.userInfo;
Box setting = GStrorage.setting;
Rx<ThemeType> 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() {

View File

@@ -54,6 +54,18 @@ class _MinePageState extends State<MinePage> {
),
),
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(

View File

@@ -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<PrivacySetting> {
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),
),
],
),
);

View File

@@ -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') {