opt intro controller

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-02 14:07:29 +08:00
parent 199ddc0e7e
commit 3c964787df
19 changed files with 507 additions and 446 deletions

View File

@@ -43,13 +43,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class VideoIntroController extends CommonIntroController with ReloadMixin {
String heroTag = '';
class UgcIntroController extends CommonIntroController with ReloadMixin {
late ExpandableController expandableCtr;
final RxBool status = true.obs;
final Rx<VideoDetailData> videoDetail = VideoDetailData().obs;
// up主粉丝数
final Rx<MemberCardInfoData> userStat = MemberCardInfoData().obs;
@@ -60,16 +57,6 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
// 是否点踩
final RxBool hasDislike = false.obs;
// 是否稍后再看
final RxBool hasLater = false.obs;
final RxInt lastPlayCid = 0.obs;
// 同时观看
final bool isShowOnlineTotal = Pref.enableOnlineTotal;
late final RxString total = '1'.obs;
Timer? timer;
late final showArgueMsg = Pref.showArgueMsg;
late final enableAi = Pref.enableAi;
late final horizontalMemberPage = Pref.horizontalMemberPage;
@@ -92,11 +79,6 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
});
}
try {
if (heroTag.isEmpty) {
heroTag = Get.arguments['heroTag'];
}
} catch (_) {}
try {
var videoItem = Get.arguments['videoItem'];
if (videoItem != null) {
@@ -110,12 +92,10 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
}
}
} catch (_) {}
lastPlayCid.value = int.parse(Get.parameters['cid']!);
startTimer();
queryVideoIntro();
}
// 获取视频简介&分p
@override
Future<void> queryVideoIntro() async {
queryVideoTags();
var res = await VideoHttp.videoIntro(bvid: bvid);
@@ -152,8 +132,8 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
}
} catch (_) {}
final pages = videoDetail.value.pages;
if (pages != null && pages.isNotEmpty && lastPlayCid.value == 0) {
lastPlayCid.value = pages.first.cid!;
if (pages != null && pages.isNotEmpty && cid.value == 0) {
cid.value = pages.first.cid!;
}
queryUserStat(data.staff);
} else {
@@ -298,14 +278,6 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
}
}
Future<void> viewLater() async {
var res = await (hasLater.value
? UserHttp.toViewDel(aids: [IdUtils.bv2av(bvid)])
: await UserHttp.toViewLater(bvid: bvid));
if (res['status']) hasLater.value = !hasLater.value;
SmartDialog.showToast(res['msg']);
}
// 投币
Future<void> actionCoinVideo() async {
if (!accountService.isLogin.value) {
@@ -493,7 +465,7 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
}
// 修改分P或番剧分集
bool changeSeasonOrbangu(dynamic epid, bvid, cid, aid, cover, [isStein]) {
bool onChangeEpisode(dynamic epid, bvid, cid, aid, cover, [isStein]) {
// 重新获取视频资源
final videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
@@ -519,7 +491,6 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
..bvid = bvid
..oid.value = aid ?? IdUtils.bv2av(bvid)
..cid.value = cid
..danmakuCid.value = cid
..queryVideoUrl();
if (this.bvid != bvid) {
@@ -553,48 +524,19 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
queryVideoIntro();
}
lastPlayCid.value = cid;
this.cid.value = cid;
queryOnlineTotal();
return true;
}
void startTimer() {
if (isShowOnlineTotal) {
queryOnlineTotal();
timer ??= Timer.periodic(const Duration(seconds: 10), (Timer timer) {
queryOnlineTotal();
});
}
}
void canelTimer() {
timer?.cancel();
timer = null;
}
// 查看同时在看人数
Future<void> queryOnlineTotal() async {
if (!isShowOnlineTotal) {
return;
}
var result = await VideoHttp.onlineTotal(
aid: IdUtils.bv2av(bvid),
bvid: bvid,
cid: lastPlayCid.value,
);
if (result['status']) {
total.value = result['data'];
}
}
@override
void onClose() {
canelTimer();
expandableCtr.dispose();
super.onClose();
}
/// 播放上一个
@override
bool prevPlay([bool skipPages = false]) {
final List episodes = [];
bool isPages = false;
@@ -624,7 +566,7 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
? videoDetail.isPageReversed == true
? videoDetail.pages!.last.cid
: videoDetail.pages!.first.cid
: lastPlayCid.value),
: this.cid.value),
);
int prevIndex = currentIndex - 1;
final PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat;
@@ -644,11 +586,12 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
final int cid = episodes[prevIndex].cid!;
final String rBvid = isPages ? bvid : episodes[prevIndex].bvid;
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[prevIndex].aid!;
changeSeasonOrbangu(null, rBvid, cid, rAid, null);
onChangeEpisode(null, rBvid, cid, rAid, null);
return true;
}
/// 列表循环或者顺序播放时,自动播放下一个
@override
bool nextPlay([bool skipPages = false]) {
try {
final List episodes = [];
@@ -731,7 +674,7 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
final String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
changeSeasonOrbangu(null, rBvid, cid, rAid, null);
onChangeEpisode(null, rBvid, cid, rAid, null);
return true;
} catch (_) {
return false;
@@ -762,7 +705,7 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
final firstItem = relatedCtr.loadingState.value.data!.first;
try {
if (firstItem.cid != null) {
changeSeasonOrbangu(
onChangeEpisode(
null,
firstItem.bvid,
firstItem.cid,
@@ -771,7 +714,7 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
);
} else {
SearchHttp.ab2c(aid: firstItem.aid, bvid: firstItem.bvid).then(
(cid) => changeSeasonOrbangu(
(cid) => onChangeEpisode(
null,
firstItem.bvid,
cid,
@@ -791,7 +734,7 @@ class VideoIntroController extends CommonIntroController with ReloadMixin {
SmartDialog.showLoading(msg: '正在获取AI总结');
final res = await VideoHttp.aiConclusion(
bvid: bvid,
cid: lastPlayCid.value,
cid: cid.value,
upMid: videoDetail.value.owner?.mid,
);
SmartDialog.dismiss();

View File

@@ -35,8 +35,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
class VideoIntroPanel extends StatefulWidget {
const VideoIntroPanel({
class UgcIntroPanel extends StatefulWidget {
const UgcIntroPanel({
super.key,
required this.heroTag,
required this.showAiBottomSheet,
@@ -49,12 +49,12 @@ class VideoIntroPanel extends StatefulWidget {
final ValueChanged<int?> onShowMemberPage;
@override
State<VideoIntroPanel> createState() => _VideoIntroPanelState();
State<UgcIntroPanel> createState() => _UgcIntroPanelState();
}
class _VideoIntroPanelState extends State<VideoIntroPanel>
class _UgcIntroPanelState extends State<UgcIntroPanel>
with AutomaticKeepAliveClientMixin {
late VideoIntroController introController;
late UgcIntroController ugcIntroController;
late final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: widget.heroTag);
@@ -66,7 +66,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
@override
void initState() {
super.initState();
introController = Get.put(VideoIntroController(), tag: widget.heroTag)
ugcIntroController = Get.put(UgcIntroController(), tag: widget.heroTag)
..heroTag = widget.heroTag;
}
@@ -96,7 +96,8 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
),
sliver: Obx(
() {
VideoDetailData videoDetail = introController.videoDetail.value;
VideoDetailData videoDetail =
ugcIntroController.videoDetail.value;
bool isLoading = videoDetail.bvid == null;
int? mid = videoDetail.owner?.mid;
return SliverToBoxAdapter(
@@ -107,7 +108,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
return;
}
feedBack();
introController.expandableCtr.toggle();
ugcIntroController.expandableCtr.toggle();
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -127,7 +128,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
if (mid != null) {
feedBack();
if (!isPortrait &&
introController
ugcIntroController
.horizontalMemberPage) {
widget.onShowMemberPage(mid);
} else {
@@ -146,7 +147,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: ReloadScrollPhysics(
controller: introController,
controller: ugcIntroController,
),
child: Row(
spacing: 25,
@@ -170,7 +171,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
context,
isLoading,
videoDetail,
introController,
ugcIntroController,
),
),
],
@@ -182,7 +183,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
_buildVideoTitle(theme, videoDetail)
else
ExpandablePanel(
controller: introController.expandableCtr,
controller: ugcIntroController.expandableCtr,
collapsed: GestureDetector(
onLongPress: () {
Feedback.forLongPress(context);
@@ -208,11 +209,11 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
clipBehavior: Clip.none,
children: [
_buildInfo(theme, videoDetail),
if (introController.enableAi) _aiBtn,
if (ugcIntroController.enableAi) _aiBtn,
],
),
if (videoDetail.argueInfo?.argueMsg?.isNotEmpty == true &&
introController.showArgueMsg) ...[
ugcIntroController.showArgueMsg) ...[
const SizedBox(height: 2),
Text.rich(
TextSpan(
@@ -238,7 +239,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
),
],
ExpandablePanel(
controller: introController.expandableCtr,
controller: ugcIntroController.expandableCtr,
collapsed: const SizedBox.shrink(),
expanded: Column(
mainAxisSize: MainAxisSize.min,
@@ -270,7 +271,8 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
),
],
Obx(() {
final videoTags = introController.videoTags.value;
final videoTags =
ugcIntroController.videoTags.value;
if (videoTags.isNullOrEmpty) {
return const SizedBox.shrink();
}
@@ -281,14 +283,15 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
theme: expandTheme,
),
Obx(
() => introController.status.value
() => ugcIntroController.status.value
? const SizedBox.shrink()
: Center(
child: TextButton.icon(
icon: const Icon(Icons.refresh),
onPressed: () {
introController.status.value = true;
introController.queryVideoIntro();
ugcIntroController
..status.value = true
..queryVideoIntro();
if (videoDetailCtr.videoUrl.isNullOrEmpty &&
!videoDetailCtr.isQuerying) {
videoDetailCtr.queryVideoUrl();
@@ -305,7 +308,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
context,
isLoading,
videoDetail,
introController,
ugcIntroController,
),
],
// 合集
@@ -317,9 +320,9 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
.horizontalSeasonPanel))
SeasonPanel(
heroTag: widget.heroTag,
changeFuc: introController.changeSeasonOrbangu,
onChangeEpisode: ugcIntroController.onChangeEpisode,
showEpisodes: widget.showEpisodes,
videoIntroController: introController,
ugcIntroController: ugcIntroController,
),
if (!isLoading &&
videoDetail.pages != null &&
@@ -330,8 +333,8 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
.horizontalSeasonPanel)) ...[
PagesPanel(
heroTag: widget.heroTag,
videoIntroController: introController,
bvid: introController.bvid,
ugcIntroController: ugcIntroController,
bvid: ugcIntroController.bvid,
showEpisodes: widget.showEpisodes,
),
],
@@ -484,9 +487,9 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
Widget followButton(BuildContext context, ThemeData t) {
return Obx(
() {
int attr = introController.followStatus['attribute'] ?? 0;
int attr = ugcIntroController.followStatus['attribute'] ?? 0;
return TextButton(
onPressed: () => introController.actionRelationMod(context),
onPressed: () => ugcIntroController.actionRelationMod(context),
style: TextButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: const VisualDensity(vertical: -2.8),
@@ -517,7 +520,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
BuildContext context,
bool isLoading,
VideoDetailData videoDetail,
VideoIntroController videoIntroController,
UgcIntroController ugcIntroController,
) {
return SizedBox(
height: 48,
@@ -527,19 +530,18 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: () => handleState(videoIntroController.actionLikeVideo),
onLongPress: () =>
handleState(videoIntroController.actionOneThree),
selectStatus: videoIntroController.hasLike.value,
onTap: () => handleState(ugcIntroController.actionLikeVideo),
onLongPress: () => handleState(ugcIntroController.actionOneThree),
selectStatus: ugcIntroController.hasLike.value,
semanticsLabel: '点赞',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.like)
: null,
needAnim: true,
hasTriple:
videoIntroController.hasLike.value &&
videoIntroController.hasCoin &&
videoIntroController.hasFav.value,
ugcIntroController.hasLike.value &&
ugcIntroController.hasCoin &&
ugcIntroController.hasFav.value,
callBack: (start) {
if (start) {
HapticFeedback.lightImpact();
@@ -556,8 +558,8 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsDown),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
onTap: () => handleState(videoIntroController.actionDislikeVideo),
selectStatus: videoIntroController.hasDislike.value,
onTap: () => handleState(ugcIntroController.actionDislikeVideo),
selectStatus: ugcIntroController.hasDislike.value,
semanticsLabel: '点踩',
text: "点踩",
),
@@ -567,8 +569,8 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
key: _coinKey,
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: () => handleState(videoIntroController.actionCoinVideo),
selectStatus: videoIntroController.hasCoin,
onTap: () => handleState(ugcIntroController.actionCoinVideo),
selectStatus: ugcIntroController.hasCoin,
semanticsLabel: '投币',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.coin)
@@ -581,12 +583,12 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
key: _favKey,
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => videoIntroController.showFavBottomSheet(context),
onLongPress: () => videoIntroController.showFavBottomSheet(
onTap: () => ugcIntroController.showFavBottomSheet(context),
onLongPress: () => ugcIntroController.showFavBottomSheet(
context,
isLongPress: true,
),
selectStatus: videoIntroController.hasFav.value,
selectStatus: ugcIntroController.hasFav.value,
semanticsLabel: '收藏',
text: !isLoading
? NumUtil.numFormat(videoDetail.stat!.favorite)
@@ -598,15 +600,15 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
() => ActionItem(
icon: const Icon(FontAwesomeIcons.clock),
selectIcon: const Icon(FontAwesomeIcons.solidClock),
onTap: () => handleState(videoIntroController.viewLater),
selectStatus: videoIntroController.hasLater.value,
onTap: () => handleState(ugcIntroController.viewLater),
selectStatus: ugcIntroController.hasLater.value,
semanticsLabel: '再看',
text: '再看',
),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(context),
onTap: () => ugcIntroController.actionShareVideo(context),
selectStatus: false,
semanticsLabel: '分享',
text: !isLoading
@@ -717,7 +719,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
onTap: () {
if (item.mid == ownerMid &&
!isPortrait &&
introController.horizontalMemberPage) {
ugcIntroController.horizontalMemberPage) {
widget.onShowMemberPage(ownerMid);
} else {
Get.toNamed(
@@ -761,8 +763,9 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
right: -6,
child: Obx(
() =>
introController.staffRelations['status'] == true &&
introController.staffRelations['${item.mid}'] == null
ugcIntroController.staffRelations['status'] == true &&
ugcIntroController.staffRelations['${item.mid}'] ==
null
? Material(
type: MaterialType.transparency,
shape: const CircleBorder(),
@@ -773,7 +776,8 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
mid: item.mid,
isFollow: false,
callback: (val) {
introController.staffRelations['${item.mid}'] =
ugcIntroController
.staffRelations['${item.mid}'] =
true;
},
),
@@ -834,7 +838,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
behavior: HitTestBehavior.opaque,
child: Obx(
() {
final userStat = introController.userStat.value;
final userStat = ugcIntroController.userStat.value;
final isVip = (userStat.card?.vip?.status ?? 0) > 0;
return Row(
mainAxisSize: MainAxisSize.min,
@@ -904,10 +908,10 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
color: theme.colorScheme.outline,
semanticLabel: '无痕',
),
if (introController.isShowOnlineTotal)
if (ugcIntroController.isShowOnlineTotal)
Obx(
() => Text(
'${introController.total.value}人在看',
'${ugcIntroController.total.value}人在看',
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
@@ -926,15 +930,15 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
label: 'AI总结',
child: GestureDetector(
onTap: () async {
if (introController.aiConclusionResult == null) {
await introController.aiConclusion();
if (ugcIntroController.aiConclusionResult == null) {
await ugcIntroController.aiConclusion();
}
if (introController.aiConclusionResult == null) {
if (ugcIntroController.aiConclusionResult == null) {
return;
}
if (introController.aiConclusionResult!.summary?.isNotEmpty ==
if (ugcIntroController.aiConclusionResult!.summary?.isNotEmpty ==
true ||
introController.aiConclusionResult!.outline?.isNotEmpty ==
ugcIntroController.aiConclusionResult!.outline?.isNotEmpty ==
true) {
widget.showAiBottomSheet();
} else {

View File

@@ -16,7 +16,7 @@ class PagesPanel extends StatefulWidget {
required this.bvid,
required this.heroTag,
this.showEpisodes,
required this.videoIntroController,
required this.ugcIntroController,
});
final List<Part>? list;
@@ -25,7 +25,7 @@ class PagesPanel extends StatefulWidget {
final String bvid;
final String heroTag;
final Function? showEpisodes;
final VideoIntroController videoIntroController;
final UgcIntroController ugcIntroController;
@override
State<PagesPanel> createState() => _PagesPanelState();
@@ -39,7 +39,7 @@ class _PagesPanelState extends State<PagesPanel> {
StreamSubscription? _listener;
List<Part> get pages =>
widget.list ?? widget.videoIntroController.videoDetail.value.pages!;
widget.list ?? widget.ugcIntroController.videoDetail.value.pages!;
@override
void initState() {
@@ -48,7 +48,7 @@ class _PagesPanelState extends State<PagesPanel> {
tag: widget.heroTag,
);
if (widget.list == null) {
cid = widget.videoIntroController.lastPlayCid.value;
cid = widget.ugcIntroController.cid.value;
pageIndex = pages.indexWhere((Part e) => e.cid == cid);
_listener = _videoDetailController.cid.listen((int cid) {
this.cid = cid;
@@ -156,7 +156,7 @@ class _PagesPanelState extends State<PagesPanel> {
if (widget.showEpisodes == null) {
Get.back();
}
widget.videoIntroController.changeSeasonOrbangu(
widget.ugcIntroController.onChangeEpisode(
null,
widget.bvid,
item.cid,
@@ -165,7 +165,7 @@ class _PagesPanelState extends State<PagesPanel> {
);
if (widget.list != null &&
widget
.videoIntroController
.ugcIntroController
.videoDetail
.value
.ugcSeason !=

View File

@@ -11,17 +11,17 @@ import 'package:get/get.dart';
class SeasonPanel extends StatefulWidget {
const SeasonPanel({
super.key,
required this.changeFuc,
required this.onChangeEpisode,
required this.heroTag,
required this.showEpisodes,
this.onTap,
required this.videoIntroController,
required this.ugcIntroController,
});
final Function changeFuc;
final Function onChangeEpisode;
final String heroTag;
final Function showEpisodes;
final bool? onTap;
final VideoIntroController videoIntroController;
final UgcIntroController ugcIntroController;
@override
State<SeasonPanel> createState() => _SeasonPanelState();
@@ -33,9 +33,9 @@ class _SeasonPanelState extends State<SeasonPanel> {
StreamSubscription? _listener;
List<EpisodeItem> episodes = <EpisodeItem>[];
VideoIntroController get videoIntroController => widget.videoIntroController;
UgcIntroController get ugcIntroController => widget.ugcIntroController;
VideoDetailData get videoDetail =>
widget.videoIntroController.videoDetail.value;
widget.ugcIntroController.videoDetail.value;
@override
void initState() {
@@ -44,13 +44,12 @@ class _SeasonPanelState extends State<SeasonPanel> {
tag: widget.heroTag,
);
_videoDetailController.seasonCid =
videoIntroController.lastPlayCid.value != 0
_videoDetailController.seasonCid = ugcIntroController.cid.value != 0
? (videoDetail.pages?.isNotEmpty == true
? videoDetail.isPageReversed
? videoDetail.pages!.last.cid
: videoDetail.pages!.first.cid
: videoIntroController.lastPlayCid.value)
: ugcIntroController.cid.value)
: videoDetail.isPageReversed
? videoDetail.pages!.last.cid
: videoDetail.pages!.first.cid;