Merge branch 'main' into feature-sponsorBlock

This commit is contained in:
guozhigq
2024-11-24 16:38:43 +08:00
16 changed files with 532 additions and 171 deletions

View File

@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
import '../constants.dart';
import 'skeleton.dart';
class VideoIntroSkeleton extends StatelessWidget {
const VideoIntroSkeleton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
return Skeleton(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 18),
Container(
width: double.infinity,
height: 20,
margin: const EdgeInsets.only(bottom: 6),
color: bgColor,
),
Container(
width: 220,
height: 20,
margin: const EdgeInsets.only(bottom: 12),
color: bgColor,
),
Row(
children: [
Container(
width: 45,
height: 14,
color: bgColor,
),
const SizedBox(width: 8),
Container(
width: 45,
height: 14,
color: bgColor,
),
const SizedBox(width: 8),
Container(
width: 45,
height: 14,
color: bgColor,
),
const Spacer(),
Container(
width: 35,
height: 14,
color: bgColor,
),
const SizedBox(width: 4),
],
),
const SizedBox(height: 30),
LayoutBuilder(builder: (context, constraints) {
// 并列5个正方形
double width = (constraints.maxWidth - 30) / 5;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(5, (index) {
return Container(
width: width - 24,
height: width - 24,
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(16),
),
);
}),
);
}),
const SizedBox(height: 20),
Container(
width: double.infinity,
height: 30,
margin: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(height: 20),
Row(
children: [
ClipOval(
child: Container(
width: 44,
height: 44,
color: bgColor,
),
),
const SizedBox(width: 12),
Container(
width: 50,
height: 14,
color: bgColor,
),
const SizedBox(width: 8),
Container(
width: 35,
height: 14,
color: bgColor,
),
const Spacer(),
Container(
width: 55,
height: 30,
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(16),
),
),
const SizedBox(width: 2)
],
),
const SizedBox(height: 10),
],
),
),
);
}
}

View File

@ -1,8 +1,14 @@
import 'dart:convert';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:pilipala/models/sponsor_block/segment.dart'; import 'package:pilipala/models/sponsor_block/segment.dart';
import 'index.dart'; import 'index.dart';
class CommonHttp { class CommonHttp {
static final RegExp spmPrefixExp =
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
static Future unReadDynamic() async { static Future unReadDynamic() async {
var res = await Request().get(Api.getUnreadDynamic, var res = await Request().get(Api.getUnreadDynamic,
data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0}); data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
@ -43,4 +49,42 @@ class CommonHttp {
}; };
} }
} }
static Future buvidActivate() async {
try {
// 获取 HTML 数据
var html = await Request().get(Api.dynamicSpmPrefix);
// 提取 spmPrefix
String spmPrefix = spmPrefixExp.firstMatch(html.data)?.group(1) ?? '';
// 生成随机 PNG 结束部分
Random rand = Random();
String randPngEnd = base64.encode(
List<int>.generate(32, (_) => rand.nextInt(256))
..addAll(List<int>.filled(4, 0))
..addAll([73, 69, 78, 68])
..addAll(List<int>.generate(4, (_) => rand.nextInt(256))),
);
// 构建 JSON 数据
String jsonData = json.encode({
'3064': 1,
'39c8': '$spmPrefix.fp.risk',
'3c43': {
'adca': 'Linux',
'bfe9': randPngEnd.substring(randPngEnd.length - 50),
},
});
// 发送 POST 请求
await Request().post(
Api.activateBuvidApi,
data: {'payload': jsonData},
options: Options(contentType: 'application/json'),
);
} catch (err) {
debugPrint('buvidActivate error: $err');
}
}
} }

View File

@ -1,9 +1,7 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'dart:math' show Random;
import 'package:cookie_jar/cookie_jar.dart'; import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
@ -13,7 +11,6 @@ import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/id_utils.dart';
import '../utils/storage.dart'; import '../utils/storage.dart';
import '../utils/utils.dart'; import '../utils/utils.dart';
import 'api.dart';
import 'constants.dart'; import 'constants.dart';
import 'interceptor.dart'; import 'interceptor.dart';
@ -27,8 +24,6 @@ class Request {
late bool enableSystemProxy; late bool enableSystemProxy;
late String systemProxyHost; late String systemProxyHost;
late String systemProxyPort; late String systemProxyPort;
static final RegExp spmPrefixExp =
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
static String? buvid; static String? buvid;
/// 设置cookie /// 设置cookie
@ -62,11 +57,6 @@ class Request {
baseUrlType = 'bangumi'; baseUrlType = 'bangumi';
} }
setBaseUrl(type: baseUrlType); setBaseUrl(type: baseUrlType);
try {
await buvidActivate();
} catch (e) {
log("setCookie, ${e.toString()}");
}
final String cookieString = cookie final String cookieString = cookie
.map((Cookie cookie) => '${cookie.name}=${cookie.value}') .map((Cookie cookie) => '${cookie.name}=${cookie.value}')
@ -122,30 +112,6 @@ class Request {
dio.options.headers['referer'] = 'https://www.bilibili.com/'; dio.options.headers['referer'] = 'https://www.bilibili.com/';
} }
static Future buvidActivate() async {
var html = await Request().get(Api.dynamicSpmPrefix);
String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
Random rand = Random();
String rand_png_end = base64.encode(
List<int>.generate(32, (_) => rand.nextInt(256)) +
List<int>.filled(4, 0) +
[73, 69, 78, 68] +
List<int>.generate(4, (_) => rand.nextInt(256)));
String jsonData = json.encode({
'3064': 1,
'39c8': '${spmPrefix}.fp.risk',
'3c43': {
'adca': 'Linux',
'bfe9': rand_png_end.substring(rand_png_end.length - 50),
},
});
await Request().post(Api.activateBuvidApi,
data: {'payload': jsonData},
options: Options(contentType: 'application/json'));
}
/* /*
* config it and create * config it and create
*/ */

View File

@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/custom_toast.dart'; import 'package:pilipala/common/widgets/custom_toast.dart';
import 'package:pilipala/http/common.dart';
import 'package:pilipala/http/init.dart'; import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/common/color_type.dart'; import 'package:pilipala/models/common/color_type.dart';
import 'package:pilipala/models/common/theme_type.dart'; import 'package:pilipala/models/common/theme_type.dart';
@ -66,6 +67,7 @@ void main() async {
PiliSchame.init(); PiliSchame.init();
await GlobalDataCache.initialize(); await GlobalDataCache.initialize();
CommonHttp.buvidActivate();
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {

View File

@ -0,0 +1,18 @@
enum CommentRangeType {
video,
bangumi,
// dynamic,
}
extension ActionTypeExtension on CommentRangeType {
String get value => [
'video',
'bangumi',
// 'dynamic',
][index];
String get label => [
'视频',
'番剧',
// '动态',
][index];
}

View File

@ -34,6 +34,7 @@ class _UpPanelState extends State<UpPanel> {
List<LiveUserItem> liveList = []; List<LiveUserItem> liveList = [];
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0); static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
late MyInfo userInfo; late MyInfo userInfo;
RxBool showLiveUser = false.obs;
void listFormat() { void listFormat() {
userInfo = widget.upData.myInfo!; userInfo = widget.upData.myInfo!;
@ -131,21 +132,70 @@ class _UpPanelState extends State<UpPanel> {
children: [ children: [
const SizedBox(width: 10), const SizedBox(width: 10),
if (liveList.isNotEmpty) ...[ if (liveList.isNotEmpty) ...[
for (int i = 0; i < liveList.length; i++) ...[ Obx(
upItemBuild(liveList[i], i) () => AnimatedSwitcher(
], duration: const Duration(milliseconds: 300),
VerticalDivider( transitionBuilder: (Widget child,
indent: 20, Animation<double> animation) {
endIndent: 40, return FadeTransition(
width: 26, opacity: animation, child: child);
color: Theme.of(context) },
.colorScheme child: showLiveUser.value
.primary ? Row(
.withOpacity(0.5), key: ValueKey<bool>(showLiveUser.value),
children: [
for (int i = 0;
i < liveList.length;
i++)
UpItemWidget(
data: liveList[i],
index: i,
currentMid: currentMid,
onClickUp: onClickUp,
onClickUpAni: onClickUpAni,
itemPadding: itemPadding,
contentWidth: contentWidth,
)
],
)
: SizedBox.shrink(
key: ValueKey<bool>(showLiveUser.value),
),
),
),
Obx(
() => IconButton(
onPressed: () {
showLiveUser.value = !showLiveUser.value;
},
icon: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child,
Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Icon(
!showLiveUser.value
? Icons.arrow_forward_ios_rounded
: Icons.arrow_back_ios_rounded,
key: ValueKey<bool>(showLiveUser.value),
size: 18,
),
),
),
), ),
], ],
for (int i = 0; i < upList.length; i++) ...[ for (int i = 0; i < upList.length; i++) ...[
upItemBuild(upList[i], i) UpItemWidget(
data: upList[i],
index: i,
currentMid: currentMid,
onClickUp: onClickUp,
onClickUpAni: onClickUpAni,
itemPadding: itemPadding,
contentWidth: contentWidth,
)
], ],
const SizedBox(width: 10), const SizedBox(width: 10),
], ],
@ -165,8 +215,93 @@ class _UpPanelState extends State<UpPanel> {
)), )),
); );
} }
}
Widget upItemBuild(data, i) { class _SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
_SliverHeaderDelegate({required this.height, required this.child});
final double height;
final Widget child;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return child;
}
@override
double get maxExtent => height;
@override
double get minExtent => height;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
true;
}
class UpPanelSkeleton extends StatelessWidget {
const UpPanelSkeleton({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
itemCount: 10,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: BorderRadius.circular(50),
),
),
Container(
margin: const EdgeInsets.only(top: 6),
width: 45,
height: 12,
color: Theme.of(context).colorScheme.onInverseSurface,
),
],
),
);
}),
);
}
}
class UpItemWidget extends StatelessWidget {
final dynamic data;
final int index;
final RxInt currentMid;
final Function(dynamic, int) onClickUp;
final Function(dynamic, int) onClickUpAni;
// final Function() feedBack;
final EdgeInsets itemPadding;
final double contentWidth;
const UpItemWidget({
Key? key,
required this.data,
required this.index,
required this.currentMid,
required this.onClickUp,
required this.onClickUpAni,
// required this.feedBack,
required this.itemPadding,
required this.contentWidth,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell( return InkWell(
onTap: () { onTap: () {
feedBack(); feedBack();
@ -174,9 +309,9 @@ class _UpPanelState extends State<UpPanel> {
EasyThrottle.throttle('follow', const Duration(milliseconds: 300), EasyThrottle.throttle('follow', const Duration(milliseconds: 300),
() { () {
if (GlobalDataCache.enableDynamicSwitch) { if (GlobalDataCache.enableDynamicSwitch) {
onClickUp(data, i); onClickUp(data, index);
} else { } else {
onClickUpAni(data, i); onClickUpAni(data, index);
} }
}); });
} else if (data.type == 'live') { } else if (data.type == 'live') {
@ -251,13 +386,12 @@ class _UpPanelState extends State<UpPanel> {
softWrap: false, softWrap: false,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: currentMid.value == data.mid color: currentMid.value == data.mid
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline, : Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context) fontSize:
.textTheme Theme.of(context).textTheme.labelMedium!.fontSize,
.labelMedium! ),
.fontSize),
), ),
), ),
), ),
@ -269,64 +403,3 @@ class _UpPanelState extends State<UpPanel> {
); );
} }
} }
class _SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
_SliverHeaderDelegate({required this.height, required this.child});
final double height;
final Widget child;
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return child;
}
@override
double get maxExtent => height;
@override
double get minExtent => height;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
true;
}
class UpPanelSkeleton extends StatelessWidget {
const UpPanelSkeleton({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
itemCount: 10,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: BorderRadius.circular(50),
),
),
Container(
margin: const EdgeInsets.only(top: 6),
width: 45,
height: 12,
color: Theme.of(context).colorScheme.onInverseSurface,
),
],
),
);
}),
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/models/common/comment_range_type.dart';
import 'package:pilipala/models/common/dynamics_type.dart'; import 'package:pilipala/models/common/dynamics_type.dart';
import 'package:pilipala/models/common/reply_sort_type.dart'; import 'package:pilipala/models/common/reply_sort_type.dart';
import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
@ -27,6 +28,8 @@ class _ExtraSettingState extends State<ExtraSetting> {
late String defaultSystemProxyHost; late String defaultSystemProxyHost;
late String defaultSystemProxyPort; late String defaultSystemProxyPort;
bool userLogin = false; bool userLogin = false;
// 记录每个选项是否被选中的状态
late List<String> enableComment;
@override @override
void initState() { void initState() {
@ -47,6 +50,8 @@ class _ExtraSettingState extends State<ExtraSetting> {
localCache.get(LocalCacheKey.systemProxyHost, defaultValue: ''); localCache.get(LocalCacheKey.systemProxyHost, defaultValue: '');
defaultSystemProxyPort = defaultSystemProxyPort =
localCache.get(LocalCacheKey.systemProxyPort, defaultValue: ''); localCache.get(LocalCacheKey.systemProxyPort, defaultValue: '');
enableComment = setting
.get(SettingBoxKey.enableComment, defaultValue: ['video', 'bangumi']);
} }
// 设置代理 // 设置代理
@ -211,6 +216,82 @@ class _ExtraSettingState extends State<ExtraSetting> {
ListTile( ListTile(
dense: false, dense: false,
title: Text('评论展示', style: titleStyle), title: Text('评论展示', style: titleStyle),
onTap: () async {
List<String> tempEnableComment = List.from(enableComment);
int? result = await showDialog(
context: context,
builder: (context) {
// 带多选框的list
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return AlertDialog(
title: const Text('评论展示'),
contentPadding: const EdgeInsets.fromLTRB(0, 24, 0, 24),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
itemCount: CommentRangeType.values.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return CheckboxListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 0),
title: Text(
'${CommentRangeType.values[index].label}评论'),
value: tempEnableComment.contains(
CommentRangeType.values[index].value),
onChanged: (bool? value) {
setState(() {
if (value == true) {
tempEnableComment.add(
CommentRangeType.values[index].value);
} else {
tempEnableComment.remove(
CommentRangeType.values[index].value);
}
});
},
);
},
),
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
enableComment = tempEnableComment;
setting.put(
SettingBoxKey.enableComment, enableComment);
GlobalDataCache.enableComment = enableComment;
SmartDialog.showToast('操作成功');
Navigator.of(context).pop();
},
child: const Text('确认'),
)
],
);
},
);
},
);
if (result != null) {
defaultReplySort = result;
setting.put(SettingBoxKey.replySortType, result);
setState(() {});
}
},
),
ListTile(
dense: false,
title: Text('评论排序', style: titleStyle),
subtitle: Text( subtitle: Text(
'当前优先展示「${ReplySortType.values[defaultReplySort].titles}', '当前优先展示「${ReplySortType.values[defaultReplySort].titles}',
style: subTitleStyle, style: subTitleStyle,
@ -220,7 +301,7 @@ class _ExtraSettingState extends State<ExtraSetting> {
context: context, context: context,
builder: (context) { builder: (context) {
return SelectDialog<int>( return SelectDialog<int>(
title: '评论展示', title: '评论排序',
value: defaultReplySort, value: defaultReplySort,
values: ReplySortType.values.map((e) { values: ReplySortType.values.map((e) {
return {'title': e.titles, 'value': e.index}; return {'title': e.titles, 'value': e.index};

View File

@ -141,8 +141,16 @@ class VideoDetailController extends GetxController
} else if (argMap.containsKey('pic')) { } else if (argMap.containsKey('pic')) {
updateCover(argMap['pic']); updateCover(argMap['pic']);
} }
tabs.value = <String>[
tabCtr = TabController(length: 2, vsync: this); '简介',
if (videoType == SearchType.video &&
GlobalDataCache.enableComment.contains('video'))
'评论',
if (videoType == SearchType.media_bangumi &&
GlobalDataCache.enableComment.contains('bangumi'))
'评论'
];
tabCtr = TabController(length: tabs.length, vsync: this);
autoPlay.value = autoPlay.value =
setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true); setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true);
enableHA.value = setting.get(SettingBoxKey.enableHA, defaultValue: false); enableHA.value = setting.get(SettingBoxKey.enableHA, defaultValue: false);
@ -481,6 +489,15 @@ class VideoDetailController extends GetxController
getDanmaku(subtitles); getDanmaku(subtitles);
} }
} }
headerControl = HeaderControl(
controller: plPlayerController,
videoDetailCtr: this,
floating: floating,
bvid: bvid,
videoType: videoType,
showSubtitleBtn: result['status'] && result['data'].subtitles.isNotEmpty,
);
plPlayerController.setHeaderControl(headerControl);
} }
// 获取弹幕 // 获取弹幕

View File

@ -17,6 +17,7 @@ import 'package:pilipala/pages/video/detail/controller.dart';
import 'package:pilipala/pages/video/detail/reply/index.dart'; import 'package:pilipala/pages/video/detail/reply/index.dart';
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
@ -99,7 +100,11 @@ class VideoIntroController extends GetxController {
} }
final VideoDetailController videoDetailCtr = final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag); Get.find<VideoDetailController>(tag: heroTag);
videoDetailCtr.tabs.value = ['简介', '评论 ${result['data']?.stat?.reply}']; videoDetailCtr.tabs.value = [
'简介',
if (GlobalDataCache.enableComment.contains('video'))
'评论 ${result['data']?.stat?.reply}'
];
videoDetailCtr.cover.value = cover ?? result['data'].pic ?? ''; videoDetailCtr.cover.value = cover ?? result['data'].pic ?? '';
// 获取到粉丝数再返回 // 获取到粉丝数再返回
await queryUserStat(); await queryUserStat();
@ -469,10 +474,12 @@ class VideoIntroController extends GetxController {
// 重新请求评论 // 重新请求评论
try { try {
/// 未渲染回复组件时可能异常 /// 未渲染回复组件时可能异常
final VideoReplyController videoReplyCtr = if (GlobalDataCache.enableComment.contains('video')) {
Get.find<VideoReplyController>(tag: heroTag); final VideoReplyController videoReplyCtr =
videoReplyCtr.aid = aid; Get.find<VideoReplyController>(tag: heroTag);
videoReplyCtr.queryReplyList(type: 'init'); videoReplyCtr.aid = aid;
videoReplyCtr.queryReplyList(type: 'init');
}
} catch (_) {} } catch (_) {}
this.bvid = bvid; this.bvid = bvid;
await queryVideoIntro(cover: cover); await queryVideoIntro(cover: cover);

View File

@ -6,8 +6,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:lottie/lottie.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/skeleton/video_intro.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
@ -76,10 +76,8 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) { Map? data = snapshot.data;
return const SliverToBoxAdapter(child: SizedBox()); if (data != null && data['status']) {
}
if (snapshot.data['status']) {
// 请求成功 // 请求成功
return Obx( return Obx(
() => VideoInfo( () => VideoInfo(
@ -91,25 +89,16 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
} else { } else {
// 请求错误 // 请求错误
return HttpError( return HttpError(
errMsg: snapshot.data['msg'], errMsg: data?['msg'] ?? '请求异常',
btnText: snapshot.data['code'] == -404 || btnText: (data?['code'] == -404 || data?['code'] == 62002)
snapshot.data['code'] == 62002
? '返回上一页' ? '返回上一页'
: null, : null,
fn: () => Get.back(), fn: () => Get.back(),
); );
} }
} else { } else {
return SliverToBoxAdapter( return const SliverToBoxAdapter(
child: SizedBox( child: VideoIntroSkeleton(),
height: 100,
child: Center(
child: Lottie.asset(
'assets/loading.json',
width: 200,
),
),
),
); );
} }
}, },

View File

@ -89,9 +89,12 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
return AppBar( return AppBar(
toolbarHeight: 45, toolbarHeight: 45,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: Text( title: Padding(
'评论详情', padding: const EdgeInsets.only(left: 14),
style: Theme.of(context).textTheme.titleSmall, child: Text(
'评论详情',
style: Theme.of(context).textTheme.titleSmall,
),
), ),
actions: [ actions: [
IconButton( IconButton(
@ -102,7 +105,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
const SizedBox(width: 14), const SizedBox(width: 12),
], ],
); );
} }

View File

@ -24,6 +24,7 @@ import 'package:pilipala/pages/video/detail/related/index.dart';
import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/global_data_cache.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:status_bar_control/status_bar_control.dart'; import 'package:status_bar_control/status_bar_control.dart';
@ -779,13 +780,20 @@ class _VideoDetailPageState extends State<VideoDetailPage>
); );
}, },
), ),
Obx( if ((vdCtr.videoType == SearchType.media_bangumi &&
() => VideoReplyPanel( GlobalDataCache.enableComment
bvid: vdCtr.bvid, .contains('bangumi')) ||
oid: vdCtr.oid.value, (vdCtr.videoType == SearchType.video &&
onControllerCreated: vdCtr.onControllerCreated, GlobalDataCache.enableComment
), .contains('video'))) ...[
) Obx(
() => VideoReplyPanel(
bvid: vdCtr.bvid,
oid: vdCtr.oid.value,
onControllerCreated: vdCtr.onControllerCreated,
),
)
],
], ],
), ),
), ),
@ -909,6 +917,21 @@ class _VideoDetailPageState extends State<VideoDetailPage>
icon: const Icon(FontAwesomeIcons.arrowLeft, size: 15), icon: const Icon(FontAwesomeIcons.arrowLeft, size: 15),
fuc: () => Get.back(), fuc: () => Get.back(),
), ),
const SizedBox(width: 8),
ComBtn(
icon: const Icon(
FontAwesomeIcons.house,
size: 15,
color: Colors.white,
),
fuc: () async {
await vdCtr.plPlayerController.dispose(type: 'all');
if (mounted) {
Navigator.popUntil(
context, (Route<dynamic> route) => route.isFirst);
}
},
),
const Spacer(), const Spacer(),
ComBtn( ComBtn(
icon: const Icon(Icons.history_outlined, size: 22), icon: const Icon(Icons.history_outlined, size: 22),

View File

@ -31,7 +31,7 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
this.floating, this.floating,
this.bvid, this.bvid,
this.videoType, this.videoType,
this.showSubtitleBtn, this.showSubtitleBtn = true,
super.key, super.key,
}); });
final PlPlayerController? controller; final PlPlayerController? controller;
@ -39,7 +39,7 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
final Floating? floating; final Floating? floating;
final String? bvid; final String? bvid;
final SearchType? videoType; final SearchType? videoType;
final bool? showSubtitleBtn; final bool showSubtitleBtn;
@override @override
State<HeaderControl> createState() => _HeaderControlState(); State<HeaderControl> createState() => _HeaderControlState();
@ -1329,7 +1329,7 @@ class _HeaderControlState extends State<HeaderControl> {
], ],
/// 字幕 /// 字幕
if (widget.showSubtitleBtn ?? true) if (widget.showSubtitleBtn)
ComBtn( ComBtn(
icon: const Icon( icon: const Icon(
Icons.closed_caption_off, Icons.closed_caption_off,

View File

@ -928,6 +928,11 @@ class PlPlayerController {
showControls.value = !val; showControls.value = !val;
} }
/// 设置/更新顶部控制栏
void setHeaderControl(PreferredSizeWidget? widget) {
headerControl = widget;
}
void toggleFullScreen(bool val) { void toggleFullScreen(bool val) {
_isFullScreen.value = val; _isFullScreen.value = val;
} }

View File

@ -5,7 +5,7 @@ import 'package:pilipala/plugin/pl_player/models/play_speed.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import '../models/common/index.dart'; import '../models/common/index.dart';
Box setting = GStorage.setting; Box settingBox = GStorage.setting;
Box localCache = GStorage.localCache; Box localCache = GStorage.localCache;
Box videoStorage = GStorage.video; Box videoStorage = GStorage.video;
Box userInfoCache = GStorage.userInfo; Box userInfoCache = GStorage.userInfo;
@ -57,6 +57,8 @@ class GlobalDataCache {
static bool enableDlna = false; static bool enableDlna = false;
// sponsorBlock开关 // sponsorBlock开关
static bool enableSponsorBlock = false; static bool enableSponsorBlock = false;
// 视频评论开关
static List<String> enableComment = ['video', 'bangumi'];
// 私有构造函数 // 私有构造函数
GlobalDataCache._(); GlobalDataCache._();
@ -69,18 +71,18 @@ class GlobalDataCache {
// 异步初始化方法 // 异步初始化方法
static Future<void> initialize() async { static Future<void> initialize() async {
imgQuality = await setting.get(SettingBoxKey.defaultPicQa, imgQuality = await settingBox.get(SettingBoxKey.defaultPicQa,
defaultValue: 10); // 设置全局变量 defaultValue: 10); // 设置全局变量
fullScreenGestureMode = FullScreenGestureMode.values[setting.get( fullScreenGestureMode = FullScreenGestureMode.values[settingBox.get(
SettingBoxKey.fullScreenGestureMode, SettingBoxKey.fullScreenGestureMode,
defaultValue: FullScreenGestureMode.fromBottomtoTop.index)]; defaultValue: FullScreenGestureMode.fromBottomtoTop.index)];
enablePlayerControlAnimation = setting enablePlayerControlAnimation = settingBox
.get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true); .get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true);
actionTypeSort = await setting.get(SettingBoxKey.actionTypeSort, actionTypeSort = await settingBox.get(SettingBoxKey.actionTypeSort,
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']); defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
isOpenDanmu = isOpenDanmu = await settingBox.get(SettingBoxKey.enableShowDanmaku,
await setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); defaultValue: false);
blockTypes = blockTypes =
await localCache.get(LocalCacheKey.danmakuBlockType, defaultValue: []); await localCache.get(LocalCacheKey.danmakuBlockType, defaultValue: []);
showArea = showArea =
@ -101,7 +103,7 @@ class GlobalDataCache {
.firstWhere((e) => e.value == defaultPlayRepeat); .firstWhere((e) => e.value == defaultPlayRepeat);
playbackSpeed = playbackSpeed =
await videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0); await videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0);
enableAutoLongPressSpeed = await setting enableAutoLongPressSpeed = await settingBox
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
if (!enableAutoLongPressSpeed) { if (!enableAutoLongPressSpeed) {
longPressSpeed = await videoStorage.get(VideoBoxKey.longPressSpeedDefault, longPressSpeed = await videoStorage.get(VideoBoxKey.longPressSpeedDefault,
@ -119,13 +121,17 @@ class GlobalDataCache {
sheetHeight = localCache.get('sheetHeight', defaultValue: 0.0); sheetHeight = localCache.get('sheetHeight', defaultValue: 0.0);
historyCacheList = localCache.get('cacheList', defaultValue: []); historyCacheList = localCache.get('cacheList', defaultValue: []);
enableSearchSuggest = enableSearchSuggest =
setting.get(SettingBoxKey.enableSearchSuggest, defaultValue: true); settingBox.get(SettingBoxKey.enableSearchSuggest, defaultValue: true);
enableAutoExpand = enableAutoExpand =
setting.get(SettingBoxKey.enableAutoExpand, defaultValue: false); settingBox.get(SettingBoxKey.enableAutoExpand, defaultValue: false);
enableDynamicSwitch = enableDynamicSwitch =
setting.get(SettingBoxKey.enableDynamicSwitch, defaultValue: true); settingBox.get(SettingBoxKey.enableDynamicSwitch, defaultValue: true);
enableDlna = setting.get(SettingBoxKey.enableDlna, defaultValue: false); enableDlna = settingBox.get(SettingBoxKey.enableDlna, defaultValue: false);
enableSponsorBlock = enableSponsorBlock =
setting.get(SettingBoxKey.enableSponsorBlock, defaultValue: false); settingBox.get(SettingBoxKey.enableSponsorBlock, defaultValue: false);
settingBox.get(SettingBoxKey.enableDynamicSwitch, defaultValue: true);
enableDlna = settingBox.get(SettingBoxKey.enableDlna, defaultValue: false);
enableComment = settingBox
.get(SettingBoxKey.enableComment, defaultValue: ['video', 'bangumi']);
} }
} }

View File

@ -117,7 +117,8 @@ class SettingBoxKey {
defaultHomePage = 'defaultHomePage', defaultHomePage = 'defaultHomePage',
enableRelatedVideo = 'enableRelatedVideo', enableRelatedVideo = 'enableRelatedVideo',
enableDlna = 'enableDlna', enableDlna = 'enableDlna',
enableSponsorBlock = 'enableSponsorBlock'; enableSponsorBlock = 'enableSponsorBlock',
enableComment = 'enableComment';
/// 外观 /// 外观
static const String themeMode = 'themeMode', static const String themeMode = 'themeMode',