diff --git a/lib/common/skeleton/video_intro.dart b/lib/common/skeleton/video_intro.dart
new file mode 100644
index 00000000..b7a5ec74
--- /dev/null
+++ b/lib/common/skeleton/video_intro.dart
@@ -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),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/http/common.dart b/lib/http/common.dart
index 2f5f0e84..9644d142 100644
--- a/lib/http/common.dart
+++ b/lib/http/common.dart
@@ -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 'index.dart';
class CommonHttp {
+ static final RegExp spmPrefixExp =
+ RegExp(r'');
static Future unReadDynamic() async {
var res = await Request().get(Api.getUnreadDynamic,
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.generate(32, (_) => rand.nextInt(256))
+ ..addAll(List.filled(4, 0))
+ ..addAll([73, 69, 78, 68])
+ ..addAll(List.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');
+ }
+ }
}
diff --git a/lib/http/init.dart b/lib/http/init.dart
index 8a11034c..03de43b7 100644
--- a/lib/http/init.dart
+++ b/lib/http/init.dart
@@ -1,9 +1,7 @@
// ignore_for_file: avoid_print
import 'dart:async';
-import 'dart:convert';
import 'dart:developer';
import 'dart:io';
-import 'dart:math' show Random;
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.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 '../utils/storage.dart';
import '../utils/utils.dart';
-import 'api.dart';
import 'constants.dart';
import 'interceptor.dart';
@@ -27,8 +24,6 @@ class Request {
late bool enableSystemProxy;
late String systemProxyHost;
late String systemProxyPort;
- static final RegExp spmPrefixExp =
- RegExp(r'');
static String? buvid;
/// 设置cookie
@@ -62,11 +57,6 @@ class Request {
baseUrlType = 'bangumi';
}
setBaseUrl(type: baseUrlType);
- try {
- await buvidActivate();
- } catch (e) {
- log("setCookie, ${e.toString()}");
- }
final String cookieString = cookie
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
@@ -122,30 +112,6 @@ class Request {
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.generate(32, (_) => rand.nextInt(256)) +
- List.filled(4, 0) +
- [73, 69, 78, 68] +
- List.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
*/
diff --git a/lib/main.dart b/lib/main.dart
index 5a163f49..e5166a4c 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/custom_toast.dart';
+import 'package:pilipala/http/common.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/common/color_type.dart';
import 'package:pilipala/models/common/theme_type.dart';
@@ -66,6 +67,7 @@ void main() async {
PiliSchame.init();
await GlobalDataCache.initialize();
+ CommonHttp.buvidActivate();
}
class MyApp extends StatelessWidget {
diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart
index 607b6af7..dedb51bb 100644
--- a/lib/pages/dynamics/widgets/up_panel.dart
+++ b/lib/pages/dynamics/widgets/up_panel.dart
@@ -34,6 +34,7 @@ class _UpPanelState extends State {
List liveList = [];
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
late MyInfo userInfo;
+ RxBool showLiveUser = false.obs;
void listFormat() {
userInfo = widget.upData.myInfo!;
@@ -131,21 +132,70 @@ class _UpPanelState extends State {
children: [
const SizedBox(width: 10),
if (liveList.isNotEmpty) ...[
- for (int i = 0; i < liveList.length; i++) ...[
- upItemBuild(liveList[i], i)
- ],
- VerticalDivider(
- indent: 20,
- endIndent: 40,
- width: 26,
- color: Theme.of(context)
- .colorScheme
- .primary
- .withOpacity(0.5),
+ Obx(
+ () => AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ transitionBuilder: (Widget child,
+ Animation animation) {
+ return FadeTransition(
+ opacity: animation, child: child);
+ },
+ child: showLiveUser.value
+ ? Row(
+ key: ValueKey(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(showLiveUser.value),
+ ),
+ ),
+ ),
+ Obx(
+ () => IconButton(
+ onPressed: () {
+ showLiveUser.value = !showLiveUser.value;
+ },
+ icon: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ transitionBuilder: (Widget child,
+ Animation animation) {
+ return ScaleTransition(
+ scale: animation, child: child);
+ },
+ child: Icon(
+ !showLiveUser.value
+ ? Icons.arrow_forward_ios_rounded
+ : Icons.arrow_back_ios_rounded,
+ key: ValueKey(showLiveUser.value),
+ size: 18,
+ ),
+ ),
+ ),
),
],
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),
],
@@ -165,8 +215,93 @@ class _UpPanelState extends State {
)),
);
}
+}
- 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(
onTap: () {
feedBack();
@@ -174,9 +309,9 @@ class _UpPanelState extends State {
EasyThrottle.throttle('follow', const Duration(milliseconds: 300),
() {
if (GlobalDataCache.enableDynamicSwitch) {
- onClickUp(data, i);
+ onClickUp(data, index);
} else {
- onClickUpAni(data, i);
+ onClickUpAni(data, index);
}
});
} else if (data.type == 'live') {
@@ -251,13 +386,12 @@ class _UpPanelState extends State {
softWrap: false,
textAlign: TextAlign.center,
style: TextStyle(
- color: currentMid.value == data.mid
- ? Theme.of(context).colorScheme.primary
- : Theme.of(context).colorScheme.outline,
- fontSize: Theme.of(context)
- .textTheme
- .labelMedium!
- .fontSize),
+ color: currentMid.value == data.mid
+ ? Theme.of(context).colorScheme.primary
+ : Theme.of(context).colorScheme.outline,
+ fontSize:
+ Theme.of(context).textTheme.labelMedium!.fontSize,
+ ),
),
),
),
@@ -269,64 +403,3 @@ class _UpPanelState extends State {
);
}
}
-
-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,
- ),
- ],
- ),
- );
- }),
- );
- }
-}
diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart
index da3b0cbc..ca5a73be 100644
--- a/lib/pages/video/detail/controller.dart
+++ b/lib/pages/video/detail/controller.dart
@@ -480,6 +480,15 @@ class VideoDetailController extends GetxController
getDanmaku(subtitles);
}
}
+ headerControl = HeaderControl(
+ controller: plPlayerController,
+ videoDetailCtr: this,
+ floating: floating,
+ bvid: bvid,
+ videoType: videoType,
+ showSubtitleBtn: result['status'] && result['data'].subtitles.isNotEmpty,
+ );
+ plPlayerController.setHeaderControl(headerControl);
}
// 获取弹幕
diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart
index 417548d5..e1655d47 100644
--- a/lib/pages/video/detail/introduction/view.dart
+++ b/lib/pages/video/detail/introduction/view.dart
@@ -6,8 +6,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
-import 'package:lottie/lottie.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/pages/video/detail/index.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
@@ -76,10 +76,8 @@ class _VideoIntroPanelState extends State
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
- if (snapshot.data == null) {
- return const SliverToBoxAdapter(child: SizedBox());
- }
- if (snapshot.data['status']) {
+ Map? data = snapshot.data;
+ if (data != null && data['status']) {
// 请求成功
return Obx(
() => VideoInfo(
@@ -91,25 +89,16 @@ class _VideoIntroPanelState extends State
} else {
// 请求错误
return HttpError(
- errMsg: snapshot.data['msg'],
- btnText: snapshot.data['code'] == -404 ||
- snapshot.data['code'] == 62002
+ errMsg: data?['msg'] ?? '请求异常',
+ btnText: (data?['code'] == -404 || data?['code'] == 62002)
? '返回上一页'
: null,
fn: () => Get.back(),
);
}
} else {
- return SliverToBoxAdapter(
- child: SizedBox(
- height: 100,
- child: Center(
- child: Lottie.asset(
- 'assets/loading.json',
- width: 200,
- ),
- ),
- ),
+ return const SliverToBoxAdapter(
+ child: VideoIntroSkeleton(),
);
}
},
diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart
index c697349d..cf72c3a4 100644
--- a/lib/pages/video/detail/reply_reply/view.dart
+++ b/lib/pages/video/detail/reply_reply/view.dart
@@ -89,9 +89,12 @@ class _VideoReplyReplyPanelState extends State {
return AppBar(
toolbarHeight: 45,
automaticallyImplyLeading: false,
- title: Text(
- '评论详情',
- style: Theme.of(context).textTheme.titleSmall,
+ title: Padding(
+ padding: const EdgeInsets.only(left: 14),
+ child: Text(
+ '评论详情',
+ style: Theme.of(context).textTheme.titleSmall,
+ ),
),
actions: [
IconButton(
@@ -102,7 +105,7 @@ class _VideoReplyReplyPanelState extends State {
Navigator.pop(context);
},
),
- const SizedBox(width: 14),
+ const SizedBox(width: 12),
],
);
}
diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart
index 0fb97066..488ed14d 100644
--- a/lib/pages/video/detail/view.dart
+++ b/lib/pages/video/detail/view.dart
@@ -909,6 +909,21 @@ class _VideoDetailPageState extends State
icon: const Icon(FontAwesomeIcons.arrowLeft, size: 15),
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 route) => route.isFirst);
+ }
+ },
+ ),
const Spacer(),
ComBtn(
icon: const Icon(Icons.history_outlined, size: 22),
diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart
index bc9167d5..5b2c51ae 100644
--- a/lib/pages/video/detail/widgets/header_control.dart
+++ b/lib/pages/video/detail/widgets/header_control.dart
@@ -31,7 +31,7 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
this.floating,
this.bvid,
this.videoType,
- this.showSubtitleBtn,
+ this.showSubtitleBtn = true,
super.key,
});
final PlPlayerController? controller;
@@ -39,7 +39,7 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
final Floating? floating;
final String? bvid;
final SearchType? videoType;
- final bool? showSubtitleBtn;
+ final bool showSubtitleBtn;
@override
State createState() => _HeaderControlState();
@@ -1329,7 +1329,7 @@ class _HeaderControlState extends State {
],
/// 字幕
- if (widget.showSubtitleBtn ?? true)
+ if (widget.showSubtitleBtn)
ComBtn(
icon: const Icon(
Icons.closed_caption_off,
diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart
index b69d96bb..2013d5b2 100644
--- a/lib/plugin/pl_player/controller.dart
+++ b/lib/plugin/pl_player/controller.dart
@@ -928,6 +928,11 @@ class PlPlayerController {
showControls.value = !val;
}
+ /// 设置/更新顶部控制栏
+ void setHeaderControl(PreferredSizeWidget? widget) {
+ headerControl = widget;
+ }
+
void toggleFullScreen(bool val) {
_isFullScreen.value = val;
}