Compare commits

..

18 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
9223f40f6d opt: expand ctr
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 13:57:21 +08:00
bggRGjQaUbCoE
34bceeea39 opt: import dialog
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 13:44:43 +08:00
bggRGjQaUbCoE
36ee59c7da fix: after login
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 13:39:56 +08:00
bggRGjQaUbCoE
c23f15b195 feat: import/export login info
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 12:56:37 +08:00
bggRGjQaUbCoE
94c077a4fe mod: long press to clear logs
avoid being unable to clear logs when stuck in logspage

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 12:56:37 +08:00
bggRGjQaUbCoE
23ba9ad8c1 opt: expand intro panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 12:03:53 +08:00
bggRGjQaUbCoE
f29e49dc4c opt: report position
Closes #48

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 11:51:48 +08:00
bggRGjQaUbCoE
7603a72101 mod: update danmaku dep
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 00:14:54 +08:00
bggRGjQaUbCoE
569cf6b4a3 mod: auto expand intro panel
Closes #47

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 00:09:32 +08:00
bggRGjQaUbCoE
e2b30200bf mod: update danmaku dep
Closes #46

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-24 00:07:51 +08:00
bggRGjQaUbCoE
07d8504f91 mod: reply2reply: recheck jump index
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 23:42:43 +08:00
bggRGjQaUbCoE
952f1429eb fix: video tabbar length
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 22:56:58 +08:00
bggRGjQaUbCoE
c79364cef2 mod: playall: auto play next
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 21:11:38 +08:00
bggRGjQaUbCoE
3ee1c9fdcd mod: update danmaku dep
Closes #45

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 21:10:22 +08:00
bggRGjQaUbCoE
385ebd01cc feat: custom show reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 19:44:00 +08:00
bggRGjQaUbCoE
a8d40b4aea feat: custom expand intro panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 19:27:39 +08:00
bggRGjQaUbCoE
dffea51223 fix: whisper page: pass none null mid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 17:43:21 +08:00
bggRGjQaUbCoE
812170ce38 feat: custom show related video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-12-23 17:43:16 +08:00
22 changed files with 511 additions and 235 deletions

View File

@@ -18,6 +18,7 @@ import 'api.dart';
import 'constants.dart';
import 'interceptor.dart';
import 'interceptor_anonymity.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
class Request {
static final Request _instance = Request._internal();
@@ -43,8 +44,19 @@ 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 List<Cookie> cookies = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl));
for (Cookie item in cookies) {
await web.CookieManager().setCookie(
url: web.WebUri(item.domain ?? ''),
name: item.name,
value: item.value,
path: item.path ?? '',
domain: item.domain,
isSecure: item.secure,
isHttpOnly: item.httpOnly,
);
}
final userInfo = userInfoCache.get('userInfoCache');
if (userInfo != null && userInfo.mid != null) {
final List<Cookie> cookie2 = await cookieManager.cookieJar
@@ -65,7 +77,7 @@ class Request {
log("setCookie, ${e.toString()}");
}
// final String cookieString = cookie
// final String cookieString = cookies
// .map((Cookie cookie) => '${cookie.name}=${cookie.value}')
// .join('; ');
// dio.options.headers['cookie'] = cookieString;

View File

@@ -1,3 +1,9 @@
import 'dart:convert';
import 'package:PiliPalaX/http/constants.dart';
import 'package:PiliPalaX/services/loggeer.dart';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -167,6 +173,9 @@ class _AboutPageState extends State<AboutPage> {
onTap: () {
Get.toNamed('/logs');
},
onLongPress: () {
clearLogs();
},
leading: const Icon(Icons.bug_report_outlined),
title: const Text('错误日志'),
trailing: Icon(Icons.arrow_forward, size: 16, color: outline),
@@ -185,12 +194,112 @@ class _AboutPageState extends State<AboutPage> {
),
),
),
ListTile(
title: const Text('导入/导出登录信息'),
leading: const Icon(Icons.import_export_outlined),
onTap: () {
showDialog(
context: context,
builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
children: [
ListTile(
title: const Text('导出'),
onTap: () async {
dynamic accessKey = GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {});
dynamic cookies = (await CookieManager(PersistCookieJar(
ignoreExpires: true,
storage: FileStorage(await Utils.getCookiePath()),
))
.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl)))
.map(
(Cookie cookie) => {
'name': cookie.name,
'value': cookie.value,
},
)
.toList();
dynamic res = jsonEncode({
'accessKey': accessKey,
'cookies': cookies,
});
Utils.copyText('$res');
if (context.mounted) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: SelectableText('$res'),
),
);
}
},
),
ListTile(
title: const Text('导入'),
onTap: () async {
Get.back();
ClipboardData? data =
await Clipboard.getData('text/plain');
if (data == null ||
data.text == null ||
data.text!.isEmpty) {
SmartDialog.showToast('剪贴板无数据');
return;
}
if (!context.mounted) return;
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('是否导入以下登录信息?'),
content: SingleChildScrollView(
child: Text(data.text!),
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color:
Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () async {
Get.back();
try {
dynamic res = jsonDecode(data.text!);
Utils.afterLoginByApp(
res['accessKey'],
{'cookies': res['cookies']},
);
} catch (e) {
SmartDialog.showToast('导入失败:$e');
}
},
child: const Text('确定'),
),
],
);
},
);
},
),
],
),
);
},
),
ListTile(
title: const Text('导入/导出设置'),
dense: false,
leading: const Icon(Icons.import_export_outlined),
onTap: () async {
await showDialog(
onTap: () {
showDialog(
context: context,
builder: (context) {
return SimpleDialog(
@@ -224,13 +333,20 @@ class _AboutPageState extends State<AboutPage> {
builder: (context) {
return AlertDialog(
title: const Text('是否导入如下设置?'),
content: Text(data.text!),
content: SingleChildScrollView(
child: Text(data.text!),
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: const Text('取消'),
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline,
),
),
),
TextButton(
onPressed: () async {

View File

@@ -387,14 +387,18 @@ class BangumiIntroController extends CommonController {
if (cover is String && cover.isNotEmpty) {
videoDetailCtr.videoItem['pic'] = cover;
}
// 重新请求评论
try {
/// 未渲染回复组件时可能异常
VideoReplyController videoReplyCtr =
Get.find<VideoReplyController>(tag: Get.arguments['heroTag']);
videoReplyCtr.aid = aid;
videoReplyCtr.onRefresh();
} catch (_) {}
if (videoDetailCtr.showReply) {
try {
/// 未渲染回复组件时可能异常
VideoReplyController videoReplyCtr =
Get.find<VideoReplyController>(tag: Get.arguments['heroTag']);
videoReplyCtr.aid = aid;
videoReplyCtr.onRefresh();
} catch (_) {}
}
if (userLogin) {
queryBangumiLikeCoinFav();
}

View File

@@ -59,7 +59,7 @@ class _DynamicsPageState extends State<DynamicsPage>
}),
),
onPressed: () {
if (GStorage.userInfo.get('userInfoCache') != null) {
if (GStorage.isLogin) {
showModalBottomSheet(
context: context,
useSafeArea: true,

View File

@@ -97,9 +97,9 @@ class _UpPanelState extends State<UpPanel> {
upItemBuild(UpItem(face: '', uname: '全部动态', mid: -1), 0),
upItemBuild(
UpItem(
face: userInfo.face,
face: userInfo?.face,
uname: '',
mid: userInfo.mid,
mid: userInfo?.mid,
),
1),
for (int i = 0; i < upList.length; i++) ...[

View File

@@ -59,7 +59,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
}
// 更新登录状态
void updateLoginStatus(val) async {
Future updateLoginStatus(val) async {
userInfo = await userInfoCache.get('userInfoCache');
userLogin.value = val ?? false;
if (val) return;

View File

@@ -2,22 +2,13 @@ import 'dart:async';
import 'dart:io';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/http/login.dart';
import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart';
import 'package:PiliPalaX/models/login/index.dart';
import '../../utils/login.dart';
import 'package:hive/hive.dart';
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
import '../../http/constants.dart';
import '../../http/init.dart';
import '../../http/user.dart';
import '../../utils/storage.dart';
import '../home/controller.dart';
import '../media/controller.dart';
class LoginPageController extends GetxController
with GetSingleTickerProviderStateMixin {
@@ -86,7 +77,7 @@ class LoginPageController extends GetxController
if (value['status']) {
t.cancel();
statusQRCode.value = '扫码成功';
await afterLoginByApp(
await Utils.afterLoginByApp(
value['data'], value['data']['cookie_info']);
Get.back();
} else if (value['code'] == 86038) {
@@ -111,58 +102,6 @@ class LoginPageController extends GetxController
}
}
Future afterLoginByApp(Map<String, dynamic> token_info, cookie_info) async {
try {
Box localCache = GStorage.localCache;
localCache.put(LocalCacheKey.accessKey, {
'mid': token_info['mid'],
'value': token_info['access_token'],
'refresh': token_info['refresh_token']
});
List<dynamic> cookieInfo = cookie_info['cookies'];
List<Cookie> cookies = [];
String cookieStrings = cookieInfo.map((cookie) {
String cstr =
'${cookie['name']}=${cookie['value']};Domain=.bilibili.com;Path=/;';
cookies.add(Cookie.fromSetCookieValue(cstr));
return cstr;
}).join('');
List<String> Urls = [
HttpString.baseUrl,
HttpString.apiBaseUrl,
HttpString.tUrl
];
for (var url in Urls) {
await Request.cookieManager.cookieJar
.saveFromResponse(Uri.parse(url), cookies);
}
Request.dio.options.headers['cookie'] = cookieStrings;
await WebviewCookieManager().setCookies(cookies);
} catch (e) {
SmartDialog.showToast('设置登录态失败,$e');
}
final result = await UserHttp.userInfo();
if (result['status'] && result['data'].isLogin) {
SmartDialog.showToast('登录成功,当前采用「'
'${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web')}'
'端」推荐');
await GStorage.userInfo.put('userInfoCache', result['data']);
try {
final HomeController homeCtr = Get.find<HomeController>();
homeCtr.updateLoginStatus(true);
homeCtr.userFace.value = result['data'].face;
final MediaController mediaCtr = Get.find<MediaController>();
mediaCtr.mid = result['data'].mid;
} catch (_) {}
await LoginUtils.refreshLoginStatus(true);
} else {
// 获取用户信息失败
SmartDialog.showNotify(
msg: '登录失败请检查cookie是否正确${result['message']}',
notifyType: NotifyType.warning);
}
}
// 申请极验验证码
Future getCaptcha(geeGt, geeChallenge, onSuccess) async {
var registerData = Gt3RegisterData(
@@ -415,7 +354,8 @@ class LoginPageController extends GetxController
return;
}
SmartDialog.showToast('正在保存身份信息');
await afterLoginByApp(data['token_info'], data['cookie_info']);
await Utils.afterLoginByApp(
data['token_info'], data['cookie_info']);
Get.back();
Get.back();
},
@@ -432,7 +372,7 @@ class LoginPageController extends GetxController
return;
}
SmartDialog.showToast('正在保存身份信息');
await afterLoginByApp(data['token_info'], data['cookie_info']);
await Utils.afterLoginByApp(data['token_info'], data['cookie_info']);
Get.back();
} else {
// handle login result
@@ -495,7 +435,7 @@ class LoginPageController extends GetxController
if (res['status']) {
SmartDialog.showToast('登录成功');
var data = res['data'];
await afterLoginByApp(data['token_info'], data['cookie_info']);
await Utils.afterLoginByApp(data['token_info'], data['cookie_info']);
Get.back();
} else {
SmartDialog.showToast(res['msg']);

View File

@@ -64,6 +64,7 @@ class _MediaPageState extends State<MediaPage>
toolbarHeight: 30,
),
body: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: mediaController.scrollController,
child: Column(
children: [
@@ -183,6 +184,9 @@ class _MediaPageState extends State<MediaPage>
height: MediaQuery.textScalerOf(context).scale(200),
child: Obx(() => _buildBody(mediaController.loadingState.value)),
),
SizedBox(
height: MediaQuery.paddingOf(context).bottom + 100,
)
],
);
}

View File

@@ -223,6 +223,36 @@ class _ExtraSettingState extends State<ExtraSetting> {
setKey: SettingBoxKey.showViewPoints,
defaultVal: true,
),
SetSwitchItem(
title: '视频页显示相关视频',
leading: Icon(Icons.recommend_outlined),
setKey: SettingBoxKey.showRelatedVideo,
defaultVal: true,
),
SetSwitchItem(
title: '显示视频评论',
leading: Icon(Icons.reply_all),
setKey: SettingBoxKey.showVideoReply,
defaultVal: true,
),
SetSwitchItem(
title: '显示番剧评论',
leading: Icon(Icons.reply_all),
setKey: SettingBoxKey.showBangumiReply,
defaultVal: true,
),
SetSwitchItem(
title: '默认展开视频简介',
leading: Icon(Icons.expand_more),
setKey: SettingBoxKey.alwaysExapndIntroPanel,
defaultVal: false,
),
SetSwitchItem(
title: '横屏自动展开视频简介',
leading: Icon(Icons.expand_more),
setKey: SettingBoxKey.exapndIntroPanelH,
defaultVal: false,
),
Obx(
() => ListTile(
enableFeedback: true,

View File

@@ -216,6 +216,13 @@ class VideoDetailController extends GetxController
late String cacheSecondDecode;
late int cacheAudioQa;
late final showRelatedVideo = GStorage.showRelatedVideo;
late final _showVideoReply = GStorage.showVideoReply;
late final _showBangumiReply = GStorage.showBangumiReply;
bool get showReply =>
videoType == SearchType.video ? _showVideoReply : _showBangumiReply;
late final bool enableSponsorBlock;
PlayerStatus? playerStatus;
StreamSubscription<Duration>? positionSubscription;
@@ -236,6 +243,8 @@ class VideoDetailController extends GetxController
RxString sourceType = 'normal'.obs;
List<MediaVideoItemModel> mediaList = <MediaVideoItemModel>[];
RxString watchLaterTitle = ''.obs;
bool get isPlayAll =>
sourceType.value == 'watchLater' || sourceType.value == 'fav';
@override
void onInit() {
@@ -357,18 +366,22 @@ class VideoDetailController extends GetxController
videoItem['pic'] = cover;
queryVideoUrl();
Get.find<VideoReplyController>(tag: heroTag)
..aid = aid
..onRefresh();
if (showReply) {
Get.find<VideoReplyController>(tag: heroTag)
..aid = aid
..onRefresh();
}
Get.find<VideoIntroController>(tag: heroTag)
..lastPlayCid.value = cid
..bvid = bvid
..queryVideoIntro();
Get.find<RelatedController>(tag: heroTag)
..bvid = bvid
..onRefresh();
if (showRelatedVideo) {
Get.find<RelatedController>(tag: heroTag)
..bvid = bvid
..onRefresh();
}
} catch (_) {}
}

View File

@@ -3,6 +3,7 @@ import 'dart:math';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -79,6 +80,8 @@ class VideoIntroController extends GetxController
Rx<Map<String, dynamic>> queryVideoIntroData =
Rx<Map<String, dynamic>>({"status": true});
ExpandableController? expandableCtr;
@override
void onInit() {
super.onInit();
@@ -549,20 +552,25 @@ class VideoIntroController extends GetxController
}
videoDetailCtr.danmakuCid.value = cid;
videoDetailCtr.queryVideoUrl();
// 重新请求相关视频
try {
final RelatedController relatedCtr =
Get.find<RelatedController>(tag: heroTag);
relatedCtr.bvid = bvid;
relatedCtr.queryData();
} catch (_) {}
if (videoDetailCtr.showRelatedVideo) {
try {
Get.find<RelatedController>(tag: heroTag)
..bvid = bvid
..queryData();
} catch (_) {}
}
// 重新请求评论
try {
final VideoReplyController videoReplyCtr =
Get.find<VideoReplyController>(tag: heroTag);
videoReplyCtr.aid = aid;
videoReplyCtr.onRefresh();
} catch (_) {}
if (videoDetailCtr.showReply) {
try {
Get.find<VideoReplyController>(tag: heroTag)
..aid = aid
..onRefresh();
} catch (_) {}
}
this.bvid = bvid;
lastPlayCid.value = cid;
queryVideoIntro();
@@ -604,6 +612,8 @@ class VideoIntroController extends GetxController
@override
void onClose() {
canelTimer();
expandableCtr?.dispose();
expandableCtr = null;
super.onClose();
}
@@ -652,8 +662,7 @@ class VideoIntroController extends GetxController
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
if (videoDetailController.sourceType.value == 'watchLater' ||
videoDetailController.sourceType.value == 'fav') {
if (videoDetailController.isPlayAll) {
episodes.addAll(videoDetailCtr.mediaList);
} else if ((videoDetail.value.pages?.length ?? 0) > 1) {
isPages = true;
@@ -671,7 +680,8 @@ class VideoIntroController extends GetxController
final PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
if (episodes.isEmpty) {
if (platRepeat == PlayRepeat.autoPlayRelated) {
if (platRepeat == PlayRepeat.autoPlayRelated &&
videoDetailCtr.showRelatedVideo) {
return playRelated();
}
return false;
@@ -692,7 +702,8 @@ class VideoIntroController extends GetxController
if (nextIndex >= episodes.length) {
if (platRepeat == PlayRepeat.listCycle) {
nextIndex = 0;
} else if (platRepeat == PlayRepeat.autoPlayRelated) {
} else if (platRepeat == PlayRepeat.autoPlayRelated &&
videoDetailCtr.showRelatedVideo) {
return playRelated();
} else {
return false;

View File

@@ -73,12 +73,6 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
});
}
@override
void dispose() {
videoIntroController.onClose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
@@ -158,7 +152,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late final _coinKey = GlobalKey<ActionItemState>();
late final _favKey = GlobalKey<ActionItemState>();
final _expandableCtr = ExpandableController(initialExpanded: false);
@override
void initState() {
@@ -170,12 +163,23 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
loadingStatus = widget.loadingStatus;
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
}
@override
void dispose() {
_expandableCtr.dispose();
super.dispose();
if (videoIntroController.expandableCtr == null) {
bool alwaysExapndIntroPanel = GStorage.alwaysExapndIntroPanel;
videoIntroController.expandableCtr = ExpandableController(
initialExpanded: alwaysExapndIntroPanel,
);
if (alwaysExapndIntroPanel.not && GStorage.exapndIntroPanelH) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.orientation == Orientation.landscape &&
videoIntroController.expandableCtr?.expanded == false) {
videoIntroController.expandableCtr?.toggle();
}
});
}
}
}
void _showFavBottomSheet() => showModalBottomSheet(
@@ -234,7 +238,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}
feedBack();
// widget.showIntroDetail();
_expandableCtr.toggle();
videoIntroController.expandableCtr?.toggle();
}
// 用户主页
@@ -397,7 +401,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
behavior: HitTestBehavior.translucent,
onTap: showIntroDetail,
child: ExpandablePanel(
controller: _expandableCtr,
controller: videoIntroController.expandableCtr,
collapsed: GestureDetector(
onLongPress: () {
feedBack();
@@ -525,7 +529,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
behavior: HitTestBehavior.translucent,
onTap: showIntroDetail,
child: ExpandablePanel(
controller: _expandableCtr,
controller: videoIntroController.expandableCtr,
collapsed: const SizedBox.shrink(),
expanded: Column(
mainAxisSize: MainAxisSize.min,
@@ -538,7 +542,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
'${videoIntroController.videoDetail.value.bvid}');
},
child: Text(
'${videoIntroController.videoDetail.value.bvid}',
videoIntroController.videoDetail.value.bvid ?? '',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.primary,

View File

@@ -62,6 +62,7 @@ class VideoReplyReplyController extends CommonController
).then((res) {
if (res['status'] && (res['data']?.mid ?? -1) > 0) {
firstFloor = res['data'];
hasRoot = true;
}
});
}
@@ -85,7 +86,7 @@ class VideoReplyReplyController extends CommonController
.map((item) => item.id.toInt())
.toList()
.indexOf(id!);
if (index != -1) {
if (index != null && index != -1) {
controller = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
@@ -95,13 +96,15 @@ class VideoReplyReplyController extends CommonController
end: Theme.of(Get.context!).colorScheme.surface,
).animate(controller!);
WidgetsBinding.instance.addPostFrameCallback((_) async {
itemScrollCtr.jumpTo(
index: hasRoot ? index! + 3 : index! + 1,
alignment: 0.25,
);
await Future.delayed(const Duration(milliseconds: 800));
await controller?.forward();
index = null;
if (index != null) {
itemScrollCtr.jumpTo(
index: hasRoot ? index! + 3 : index! + 1,
alignment: 0.25,
);
await Future.delayed(const Duration(milliseconds: 800));
await controller?.forward();
index = null;
}
});
}
id = null;
@@ -146,6 +149,7 @@ class VideoReplyReplyController extends CommonController
} else {
if (response.response.root != null) {
firstFloor = response.response.root;
hasRoot = true;
}
List<ReplyItemModel> replies = response.response.replies;
count.value = response.response.page.count;

View File

@@ -100,9 +100,11 @@ class _VideoDetailPageState extends State<VideoDetailPage>
}
videoDetailController = Get.put(VideoDetailController(), tag: heroTag);
_videoReplyController = Get.put(
VideoReplyController(videoDetailController.oid.value, '0', '1'),
tag: heroTag);
if (videoDetailController.showReply) {
_videoReplyController = Get.put(
VideoReplyController(videoDetailController.oid.value, '0', '1'),
tag: heroTag);
}
videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
videoIntroController.videoDetail.listen((value) {
if (!context.mounted) return;
@@ -179,7 +181,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
// 获取视频资源,初始化播放器
Future<void> videoSourceInit() async {
_futureBuilderFuture = videoDetailController.queryVideoUrl();
_videoReplyController.queryData();
if (videoDetailController.showReply) {
_videoReplyController.queryData();
}
if (videoDetailController.autoPlay.value) {
plPlayerController = videoDetailController.plPlayerController;
plPlayerController!.addStatusLister(playerListener);
@@ -222,7 +226,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
bool notExitFlag = false;
/// 顺序播放 列表循环
if (plPlayerController!.playRepeat != PlayRepeat.pause &&
if (videoDetailController.isPlayAll) {
notExitFlag = videoIntroController.nextPlay();
} else if (plPlayerController!.playRepeat != PlayRepeat.pause &&
plPlayerController!.playRepeat != PlayRepeat.singleCycle) {
if (videoDetailController.videoType == SearchType.video) {
notExitFlag = videoIntroController.nextPlay();
@@ -561,14 +567,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
resizeToAvoidBottomInset: false,
body: Column(
children: [
tabbarBuild(),
buildTabbar(
showReply: videoDetailController.showReply,
),
Expanded(
child: TabBarView(
physics: const ClampingScrollPhysics(),
controller: videoDetailController.tabCtr,
children: [
videoIntro(),
videoReplyPanel,
if (videoDetailController.showReply)
videoReplyPanel,
],
),
),
@@ -610,14 +619,15 @@ class _VideoDetailPageState extends State<VideoDetailPage>
resizeToAvoidBottomInset: false,
body: Column(
children: [
tabbarBuild(),
buildTabbar(showReply: videoDetailController.showReply),
Expanded(
child: TabBarView(
physics: const ClampingScrollPhysics(),
controller: videoDetailController.tabCtr,
children: [
videoIntro(),
videoReplyPanel,
if (videoDetailController.showReply)
videoReplyPanel,
],
),
),
@@ -653,14 +663,16 @@ class _VideoDetailPageState extends State<VideoDetailPage>
resizeToAvoidBottomInset: false,
body: Column(
children: [
tabbarBuild(false),
buildTabbar(
needIndicator: false,
showReply: videoDetailController.showReply,
),
Expanded(
child: Row(
children: [
Expanded(child: videoIntro()),
Expanded(
child: videoReplyPanel,
)
if (videoDetailController.showReply)
Expanded(child: videoReplyPanel)
],
),
)
@@ -699,8 +711,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
resizeToAvoidBottomInset: false,
body: Column(
children: [
tabbarBuild(false, '', true),
Expanded(child: videoReplyPanel),
buildTabbar(
showIntro: false,
showReply: videoDetailController.showReply,
),
if (videoDetailController.showReply)
Expanded(child: videoReplyPanel),
],
),
),
@@ -790,12 +806,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
resizeToAvoidBottomInset: false,
body: Column(
children: [
tabbarBuild(
videoDetailController.videoType !=
SearchType.media_bangumi,
'相关视频',
videoDetailController.videoType ==
SearchType.media_bangumi,
buildTabbar(
introText: '相关视频',
showIntro: videoDetailController.videoType ==
SearchType.video &&
videoDetailController.showRelatedVideo,
showReply: videoDetailController.showReply,
),
Expanded(
child: TabBarView(
@@ -803,14 +819,16 @@ class _VideoDetailPageState extends State<VideoDetailPage>
controller: videoDetailController.tabCtr,
children: <Widget>[
if (videoDetailController.videoType ==
SearchType.video)
SearchType.video &&
videoDetailController.showRelatedVideo)
CustomScrollView(
controller: _introController,
slivers: [
RelatedVideoPanel(heroTag: heroTag),
],
),
videoReplyPanel,
if (videoDetailController.showReply)
videoReplyPanel,
],
),
)
@@ -1131,15 +1149,58 @@ class _VideoDetailPageState extends State<VideoDetailPage>
);
}
Widget tabbarBuild([
Widget buildTabbar({
bool needIndicator = true,
String introText = '简介',
bool isSingle = false,
]) {
if (videoDetailController.tabCtr.length != (isSingle ? 1 : 2)) {
videoDetailController.tabCtr =
TabController(length: isSingle ? 1 : 2, vsync: this);
bool showIntro = true,
bool showReply = true,
}) {
int length = (showIntro ? 1 : 0) + (showReply ? 1 : 0);
if (videoDetailController.tabCtr.length != length) {
videoDetailController.tabCtr = TabController(length: length, vsync: this);
}
Widget tabbar() => TabBar(
labelColor: needIndicator.not || length == 1
? Theme.of(context).colorScheme.onSurface
: null,
indicatorColor:
needIndicator.not || length == 1 ? Colors.transparent : null,
padding: EdgeInsets.zero,
controller: videoDetailController.tabCtr,
labelStyle: const TextStyle(fontSize: 13),
labelPadding:
const EdgeInsets.symmetric(horizontal: 10.0), // 设置每个标签的宽度
dividerColor: Colors.transparent,
onTap: (value) {
void animToTop() {
if (value == 0) {
if (showIntro) {
_introController.animToTop();
} else if (showReply) {
_videoReplyController.animateToTop();
}
} else {
_videoReplyController.animateToTop();
}
}
if (needIndicator.not || length == 1) {
animToTop();
} else if (videoDetailController.tabCtr.indexIsChanging.not) {
animToTop();
}
},
tabs: [
if (showIntro) Tab(text: introText),
if (showReply)
Tab(
text:
'评论${_videoReplyController.count.value == -1 ? '' : ' ${_videoReplyController.count.value}'}',
),
],
);
return Container(
width: double.infinity,
height: 45,
@@ -1154,42 +1215,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
child: Material(
child: Row(
children: [
Flexible(
flex: 1,
child: Obx(
() => TabBar(
labelColor: needIndicator
? null
: Theme.of(context).colorScheme.onSurface,
indicatorColor: needIndicator ? null : Colors.transparent,
padding: EdgeInsets.zero,
controller: videoDetailController.tabCtr,
labelStyle: const TextStyle(fontSize: 13),
labelPadding:
const EdgeInsets.symmetric(horizontal: 10.0), // 设置每个标签的宽度
dividerColor: Colors.transparent,
onTap: (value) {
if (isSingle) {
_videoReplyController.animateToTop();
} else if (!needIndicator ||
!videoDetailController.tabCtr.indexIsChanging) {
if (value == 0) {
_introController.animToTop();
} else {
_videoReplyController.animateToTop();
}
}
},
tabs: [
if (!isSingle) Tab(text: introText),
Tab(
text:
'评论${_videoReplyController.count.value == -1 ? '' : ' ${_videoReplyController.count.value}'}',
),
],
),
if (length == 0)
const Spacer()
else
Flexible(
flex: 1,
child: showReply ? Obx(() => tabbar()) : tabbar(),
),
),
Flexible(
flex: 1,
child: Center(
@@ -1287,7 +1319,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
Widget videoIntro([bool needRelated = true]) {
Widget introPanel() => CustomScrollView(
controller: _introController,
controller: needRelated ? _introController : null,
slivers: [
if (videoDetailController.videoType == SearchType.video) ...[
VideoIntroPanel(
@@ -1296,7 +1328,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
showIntroDetail: showIntroDetail,
showEpisodes: showEpisodes,
),
if (needRelated) ...[
if (needRelated && videoDetailController.showRelatedVideo) ...[
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
@@ -1323,12 +1355,18 @@ class _VideoDetailPageState extends State<VideoDetailPage>
),
),
SliverToBoxAdapter(
child: SizedBox(height: MediaQuery.paddingOf(context).bottom),
child: SizedBox(
height: MediaQuery.paddingOf(context).bottom +
(videoDetailController.isPlayAll &&
MediaQuery.orientationOf(context) ==
Orientation.landscape
? 75
: 0),
),
)
],
);
if (videoDetailController.sourceType.value == 'watchLater' ||
videoDetailController.sourceType.value == 'fav') {
if (videoDetailController.isPlayAll) {
return Stack(
children: [
introPanel(),

View File

@@ -189,22 +189,6 @@ class _HeaderControlState extends State<HeaderControl> {
// ),
// ),
// if (widget.videoDetailCtr?.userInfo != null)
ListTile(
dense: true,
onTap: () {
if (widget.videoDetailCtr?.userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
Get.back();
Get.toNamed('/webviewnew', parameters: {
'url':
'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(widget.videoDetailCtr!.bvid)}&bvid=${widget.videoDetailCtr!.bvid}'
});
},
leading: const Icon(Icons.error_outline, size: 20),
title: const Text('举报', style: titleStyle),
),
ListTile(
dense: true,
onTap: () async {
@@ -514,6 +498,22 @@ class _HeaderControlState extends State<HeaderControl> {
);
},
),
ListTile(
dense: true,
onTap: () {
if (widget.videoDetailCtr?.userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
Get.back();
Get.toNamed('/webviewnew', parameters: {
'url':
'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(widget.videoDetailCtr!.bvid)}&bvid=${widget.videoDetailCtr!.bvid}'
});
},
leading: const Icon(Icons.error_outline, size: 20),
title: const Text('举报', style: titleStyle),
),
const SizedBox(height: 14),
],
),

View File

@@ -249,10 +249,12 @@ class _WhisperPageState extends State<WhisperPage> {
sessionList[i].accountInfo.name,
'face':
sessionList[i].accountInfo.face,
'mid': sessionList[i]
.accountInfo
.mid
.toString(),
if (sessionList[i].accountInfo.mid !=
null)
'mid': sessionList[i]
.accountInfo
.mid
.toString(),
},
);
},

View File

@@ -26,7 +26,7 @@ class WhisperDetailController extends GetxController {
talkerId = int.parse(Get.parameters['talkerId']!);
name = Get.parameters['name']!;
face = Get.parameters['face']!;
mid = Get.parameters['mid']!;
mid = Get.parameters['mid'];
querySessionMsg();
}

View File

@@ -1351,6 +1351,10 @@ class PlPlayerController {
}
static void updatePlayCount() {
_instance?._playerCount.value -= 1;
if (_instance?._playerCount.value == 1) {
_instance?.dispose();
} else {
_instance?._playerCount.value -= 1;
}
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:math';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -14,20 +15,18 @@ class LoginUtils {
static Future refreshLoginStatus(bool status) async {
try {
// 更改我的页面登录状态
await Get.find<MineController>().resetUserInfo();
if (status.not) {
await Get.find<MineController>().resetUserInfo();
}
// 更改主页登录状态
HomeController homeCtr = Get.find<HomeController>();
homeCtr.updateLoginStatus(status);
Get.find<HomeController>().updateLoginStatus(status);
MineController mineCtr = Get.find<MineController>();
mineCtr.userLogin.value = status;
Get.find<MineController>().userLogin.value = status;
DynamicsController dynamicsCtr = Get.find<DynamicsController>();
dynamicsCtr.userLogin.value = status;
Get.find<DynamicsController>().userLogin.value = status;
MediaController mediaCtr = Get.find<MediaController>();
mediaCtr.userLogin.value = status;
Get.find<MediaController>().userLogin.value = status;
} catch (err) {
// SmartDialog.showToast('refreshLoginStatus error: ${err.toString()}');
debugPrint('refreshLoginStatus error: $err');

View File

@@ -22,6 +22,8 @@ class GStorage {
static late final Box<dynamic> setting;
static late final Box<dynamic> video;
static bool get isLogin => userInfo.get('userInfoCache') != null;
static List<double> get speedList => List<double>.from(
video.get(
VideoBoxKey.speedsList,
@@ -113,6 +115,21 @@ class GStorage {
static bool get showViewPoints =>
setting.get(SettingBoxKey.showViewPoints, defaultValue: true);
static bool get showRelatedVideo =>
setting.get(SettingBoxKey.showRelatedVideo, defaultValue: true);
static bool get showVideoReply =>
setting.get(SettingBoxKey.showVideoReply, defaultValue: true);
static bool get showBangumiReply =>
setting.get(SettingBoxKey.showBangumiReply, defaultValue: true);
static bool get alwaysExapndIntroPanel =>
setting.get(SettingBoxKey.alwaysExapndIntroPanel, defaultValue: false);
static bool get exapndIntroPanelH =>
setting.get(SettingBoxKey.exapndIntroPanelH, defaultValue: false);
static List<double> get dynamicDetailRatio =>
setting.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]);
@@ -306,6 +323,11 @@ class SettingBoxKey {
schemeVariant = 'schemeVariant',
grpcReply = 'grpcReply',
showViewPoints = 'showViewPoints',
showRelatedVideo = 'showRelatedVideo',
showVideoReply = 'showVideoReply',
showBangumiReply = 'showBangumiReply',
alwaysExapndIntroPanel = 'alwaysExapndIntroPanel',
exapndIntroPanelH = 'exapndIntroPanelH',
// Sponsor Block
enableSponsorBlock = 'enableSponsorBlock',

View File

@@ -3,13 +3,20 @@ import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPalaX/http/constants.dart';
import 'package:PiliPalaX/http/init.dart';
import 'package:PiliPalaX/http/member.dart';
import 'package:PiliPalaX/http/search.dart';
import 'package:PiliPalaX/http/user.dart';
import 'package:PiliPalaX/http/video.dart';
import 'package:PiliPalaX/models/bangumi/info.dart';
import 'package:PiliPalaX/models/common/search_type.dart';
import 'package:PiliPalaX/pages/home/controller.dart';
import 'package:PiliPalaX/pages/media/controller.dart';
import 'package:PiliPalaX/pages/video/detail/introduction/widgets/group_panel.dart';
import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/utils/login.dart';
import 'package:PiliPalaX/utils/storage.dart';
import 'package:crypto/crypto.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
@@ -18,10 +25,76 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
class Utils {
static final Random random = Random();
static Future afterLoginByApp(
Map<String, dynamic> token_info, cookie_info) async {
try {
GStorage.localCache.put(LocalCacheKey.accessKey, {
'mid': token_info['mid'],
'value': token_info['access_token'] ?? token_info['value'],
'refresh': token_info['refresh_token'] ?? token_info['refresh']
});
List<dynamic> cookieInfo = cookie_info['cookies'];
List<Cookie> cookies = [];
String cookieStrings = cookieInfo.map((cookie) {
String cstr =
'${cookie['name']}=${cookie['value']};Domain=.bilibili.com;Path=/;';
cookies.add(Cookie.fromSetCookieValue(cstr));
return cstr;
}).join('');
List<String> urls = [
HttpString.baseUrl,
HttpString.apiBaseUrl,
HttpString.tUrl
];
for (var url in urls) {
await Request.cookieManager.cookieJar
.saveFromResponse(Uri.parse(url), cookies);
}
Request.dio.options.headers['cookie'] = cookieStrings;
await WebviewCookieManager().setCookies(cookies);
for (Cookie item in cookies) {
await web.CookieManager().setCookie(
url: web.WebUri(item.domain ?? ''),
name: item.name,
value: item.value,
path: item.path ?? '',
domain: item.domain,
isSecure: item.secure,
isHttpOnly: item.httpOnly,
);
}
} catch (e) {
SmartDialog.showToast('设置登录态失败,$e');
}
final result = await UserHttp.userInfo();
if (result['status'] && result['data'].isLogin) {
SmartDialog.showToast('登录成功,当前采用「'
'${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web')}'
'端」推荐');
await GStorage.userInfo.put('userInfoCache', result['data']);
try {
final HomeController homeCtr = Get.find<HomeController>();
homeCtr.updateLoginStatus(true);
homeCtr.userFace.value = result['data'].face;
final MediaController mediaCtr = Get.find<MediaController>();
mediaCtr.mid = result['data'].mid;
} catch (_) {}
await LoginUtils.refreshLoginStatus(true);
} else {
// 获取用户信息失败
SmartDialog.showNotify(
msg: '登录失败请检查cookie是否正确${result['message']}',
notifyType: NotifyType.warning);
}
}
static bool isStringNumeric(str) {
RegExp numericRegex = RegExp(r'^[\d\.]+$');
return numericRegex.hasMatch(str.toString());

View File

@@ -252,7 +252,7 @@ packages:
description:
path: "."
ref: main
resolved-ref: "7861474d44739adcf494dd3881cc64cb1a36ed72"
resolved-ref: "cfa1b7cc7fbd7276d58307ea515533af3807ac7d"
url: "https://github.com/bggRGjQaUbCoE/canvas_danmaku.git"
source: git
version: "0.2.5"
@@ -2078,4 +2078,4 @@ packages:
version: "3.1.3"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.5"
flutter: ">=3.24.5"