opt ugc intro

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-07-22 20:20:54 +08:00
parent e770e39c8f
commit 77a444b896
9 changed files with 166 additions and 170 deletions

View File

@@ -1,4 +1,3 @@
import 'package:PiliPlus/pages/member_video/controller.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/material.dart';
@@ -46,14 +45,18 @@ class CustomTabBarViewClampingScrollPhysics extends ClampingScrollPhysics {
SpringDescription get spring => CustomSpringDescription();
}
class MemberVideoScrollPhysics extends AlwaysScrollableScrollPhysics {
const MemberVideoScrollPhysics({super.parent, required this.controller});
mixin ReloadMixin {
late bool reload = false;
}
final MemberVideoCtr controller;
class ReloadScrollPhysics extends AlwaysScrollableScrollPhysics {
const ReloadScrollPhysics({super.parent, required this.controller});
final ReloadMixin controller;
@override
MemberVideoScrollPhysics applyTo(ScrollPhysics? ancestor) {
return MemberVideoScrollPhysics(
ReloadScrollPhysics applyTo(ScrollPhysics? ancestor) {
return ReloadScrollPhysics(
parent: buildParent(ancestor), controller: controller);
}

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/search.dart';
@@ -14,7 +15,8 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class MemberVideoCtr
extends CommonListController<SpaceArchiveData, SpaceArchiveItem> {
extends CommonListController<SpaceArchiveData, SpaceArchiveItem>
with ReloadMixin {
MemberVideoCtr({
required this.type,
required this.mid,
@@ -213,8 +215,6 @@ class MemberVideoCtr
}
}
bool reload = false;
@override
Future<void> onReload() {
reload = true;

View File

@@ -65,7 +65,7 @@ class _MemberVideoState extends State<MemberVideo>
refreshIndicator(
onRefresh: _controller.onRefresh,
child: CustomScrollView(
physics: MemberVideoScrollPhysics(controller: _controller),
physics: ReloadScrollPhysics(controller: _controller),
slivers: [
SliverPadding(
padding: EdgeInsets.only(

View File

@@ -88,10 +88,11 @@ class _SavePanelState extends State<SavePanel> {
try {
final heroTag = Get.arguments?['heroTag'];
late final ctr = Get.find<VideoIntroController>(tag: heroTag);
cover = ctr.videoDetail.value.pic;
title = ctr.videoDetail.value.title;
pubdate = ctr.videoDetail.value.pubdate;
uname = ctr.videoDetail.value.owner?.name;
final videoDetail = ctr.videoDetail.value;
cover = videoDetail.pic;
title = videoDetail.title;
pubdate = videoDetail.pubdate;
uname = videoDetail.owner?.name;
} catch (_) {}
uri =
'bilibili://video/${_item.oid}?comment_root_id=${hasRoot ? _item.root : _item.id}${hasRoot ? '&comment_secondary_id=${_item.id}' : ''}';

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/fav.dart';
@@ -43,7 +44,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class VideoIntroController extends CommonIntroController {
class VideoIntroController extends CommonIntroController with ReloadMixin {
String heroTag = '';
late ExpandableController expandableCtr;
@@ -177,11 +178,11 @@ class VideoIntroController extends CommonIntroController {
}
});
} else {
if (videoDetail.value.owner == null) {
final mid = videoDetail.value.owner?.mid;
if (mid == null) {
return;
}
var result =
await MemberHttp.memberCardInfo(mid: videoDetail.value.owner!.mid!);
var result = await MemberHttp.memberCardInfo(mid: mid);
if (result['status']) {
userStat.value = result['data'];
}
@@ -402,6 +403,7 @@ class VideoIntroController extends CommonIntroController {
showDialog(
context: context,
builder: (_) {
final videoDetail = this.videoDetail.value;
String videoUrl = '${HttpString.baseUrl}/video/$bvid';
return AlertDialog(
clipBehavior: Clip.hardEdge,
@@ -439,8 +441,8 @@ class VideoIntroController extends CommonIntroController {
),
onTap: () {
Get.back();
Utils.shareText('${videoDetail.value.title} '
'UP主: ${videoDetail.value.owner!.name!}'
Utils.shareText('${videoDetail.title} '
'UP主: ${videoDetail.owner!.name!}'
' - $videoUrl');
},
),
@@ -457,11 +459,11 @@ class VideoIntroController extends CommonIntroController {
isScrollControlled: true,
useSafeArea: true,
builder: (context) => RepostPanel(
rid: videoDetail.value.aid,
rid: videoDetail.aid,
dynType: 8,
pic: videoDetail.value.pic,
title: videoDetail.value.title,
uname: videoDetail.value.owner?.name,
pic: videoDetail.pic,
title: videoDetail.title,
uname: videoDetail.owner?.name,
),
);
},
@@ -478,13 +480,13 @@ class VideoIntroController extends CommonIntroController {
PageUtils.pmShare(
context,
content: {
"id": videoDetail.value.aid!.toString(),
"title": videoDetail.value.title!,
"headline": videoDetail.value.title!,
"id": videoDetail.aid!.toString(),
"title": videoDetail.title!,
"headline": videoDetail.title!,
"source": 5,
"thumb": videoDetail.value.pic!,
"author": videoDetail.value.owner!.name!,
"author_id": videoDetail.value.owner!.mid!.toString(),
"thumb": videoDetail.pic!,
"author": videoDetail.owner!.name!,
"author_id": videoDetail.owner!.mid!.toString(),
},
);
} catch (e) {
@@ -599,6 +601,7 @@ class VideoIntroController extends CommonIntroController {
..queryVideoUrl();
if (this.bvid != bvid) {
reload = true;
aiConclusionResult = null;
if (cover is String && cover.isNotEmpty) {
@@ -675,15 +678,16 @@ class VideoIntroController extends CommonIntroController {
bool isPages = false;
final videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
final videoDetail = this.videoDetail.value;
if (!skipPages && (videoDetail.value.pages?.length ?? 0) > 1) {
if (!skipPages && (videoDetail.pages?.length ?? 0) > 1) {
isPages = true;
final List<Part> pages = videoDetail.value.pages!;
final List<Part> pages = videoDetail.pages!;
episodes.addAll(pages);
} else if (videoDetailCtr.isPlayAll) {
episodes.addAll(videoDetailCtr.mediaList);
} else if (videoDetail.value.ugcSeason != null) {
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
} else if (videoDetail.ugcSeason != null) {
final UgcSeason ugcSeason = videoDetail.ugcSeason!;
final List<SectionItem> sections = ugcSeason.sections!;
for (int i = 0; i < sections.length; i++) {
final List<EpisodeItem> episodesList = sections[i].episodes!;
@@ -694,9 +698,9 @@ class VideoIntroController extends CommonIntroController {
final int currentIndex = episodes.indexWhere((e) =>
e.cid ==
(skipPages
? videoDetail.value.isPageReversed == true
? videoDetail.value.pages!.last.cid
: videoDetail.value.pages!.first.cid
? videoDetail.isPageReversed == true
? videoDetail.pages!.last.cid
: videoDetail.pages!.first.cid
: lastPlayCid.value));
int prevIndex = currentIndex - 1;
final PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat;
@@ -704,7 +708,7 @@ class VideoIntroController extends CommonIntroController {
// 列表循环
if (prevIndex < 0) {
if (isPages &&
(videoDetailCtr.isPlayAll || videoDetail.value.ugcSeason != null)) {
(videoDetailCtr.isPlayAll || videoDetail.ugcSeason != null)) {
return prevPlay(true);
}
if (playRepeat == PlayRepeat.listCycle) {
@@ -726,16 +730,17 @@ class VideoIntroController extends CommonIntroController {
final List episodes = [];
bool isPages = false;
final videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
final videoDetail = this.videoDetail.value;
// part -> playall -> season
if (!skipPages && (videoDetail.value.pages?.length ?? 0) > 1) {
if (!skipPages && (videoDetail.pages?.length ?? 0) > 1) {
isPages = true;
final List<Part> pages = videoDetail.value.pages!;
final List<Part> pages = videoDetail.pages!;
episodes.addAll(pages);
} else if (videoDetailCtr.isPlayAll) {
episodes.addAll(videoDetailCtr.mediaList);
} else if (videoDetail.value.ugcSeason != null) {
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
} else if (videoDetail.ugcSeason != null) {
final UgcSeason ugcSeason = videoDetail.ugcSeason!;
final List<SectionItem> sections = ugcSeason.sections!;
for (int i = 0; i < sections.length; i++) {
final List<EpisodeItem> episodesList = sections[i].episodes!;
@@ -757,9 +762,9 @@ class VideoIntroController extends CommonIntroController {
final int currentIndex = episodes.indexWhere((e) =>
e.cid ==
(skipPages
? videoDetail.value.isPageReversed == true
? videoDetail.value.pages!.last.cid
: videoDetail.value.pages!.first.cid
? videoDetail.isPageReversed == true
? videoDetail.pages!.last.cid
: videoDetail.pages!.first.cid
: videoDetailCtr.cid.value));
int nextIndex = currentIndex + 1;
@@ -773,7 +778,7 @@ class VideoIntroController extends CommonIntroController {
// 列表循环
if (nextIndex >= episodes.length) {
if (isPages &&
(videoDetailCtr.isPlayAll || videoDetail.value.ugcSeason != null)) {
(videoDetailCtr.isPlayAll || videoDetail.ugcSeason != null)) {
return nextPlay(true);
}

View File

@@ -3,7 +3,7 @@ import 'dart:async';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models/common/stat_type.dart';
@@ -96,6 +96,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
() {
VideoDetailData videoDetail = introController.videoDetail.value;
bool isLoading = videoDetail.bvid == null;
int? mid = videoDetail.owner?.mid;
return SliverToBoxAdapter(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
@@ -121,7 +122,6 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
child: _buildAvatar(
theme,
() {
int? mid = videoDetail.owner?.mid;
if (mid != null) {
feedBack();
if (context.orientation ==
@@ -142,16 +142,16 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
followButton(context, theme),
] else
Expanded(
child: SelfSizedHorizontalList(
gapSize: 25,
itemCount: videoDetail.staff!.length,
childBuilder: (index) {
return _buildStaff(
theme,
videoDetail.owner?.mid,
videoDetail.staff![index],
);
},
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: ReloadScrollPhysics(
controller: introController),
child: Row(
spacing: 25,
children: videoDetail.staff!
.map((e) => _buildStaff(theme, mid, e))
.toList(),
),
),
),
if (isHorizontal) ...[
@@ -194,9 +194,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
if (introController.enableAi) _aiBtn,
],
),
if (introController.videoDetail.value.argueInfo?.argueMsg
?.isNotEmpty ==
true &&
if (videoDetail.argueInfo?.argueMsg?.isNotEmpty == true &&
introController.showArgueMsg) ...[
const SizedBox(height: 2),
Text.rich(
@@ -212,8 +210,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
),
const WidgetSpan(child: SizedBox(width: 2)),
TextSpan(
text:
'${introController.videoDetail.value.argueInfo!.argueMsg}',
text: '${videoDetail.argueInfo!.argueMsg}',
)
],
),
@@ -232,19 +229,17 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
children: [
const SizedBox(height: 8),
GestureDetector(
onTap: () => Utils.copyText(
'${introController.videoDetail.value.bvid}'),
onTap: () =>
Utils.copyText('${videoDetail.bvid}'),
child: Text(
introController.videoDetail.value.bvid ?? '',
videoDetail.bvid ?? '',
style: TextStyle(
fontSize: 14,
color: theme.colorScheme.secondary,
),
),
),
if (introController
.videoDetail.value.descV2?.isNotEmpty ==
true) ...[
if (videoDetail.descV2?.isNotEmpty == true) ...[
const SizedBox(height: 8),
SelectableText.rich(
style: const TextStyle(
@@ -252,8 +247,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
),
TextSpan(
children: [
buildContent(theme,
introController.videoDetail.value),
buildContent(theme, videoDetail),
],
),
),

View File

@@ -1831,90 +1831,35 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
);
}
Widget get seasonPanel => Column(
children: [
if ((videoIntroController.videoDetail.value.pages?.length ?? 0) > 1)
if (videoIntroController.videoDetail.value.ugcSeason != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: PagesPanel(
heroTag: heroTag,
videoIntroController: videoIntroController,
bvid: videoIntroController.bvid,
showEpisodes: showEpisodes,
),
)
else
Expanded(
child: Obx(
() => EpisodePanel(
heroTag: heroTag,
enableSlide: false,
videoIntroController: videoIntroController,
type: EpisodeType.part,
list: [videoIntroController.videoDetail.value.pages!],
cover: videoDetailController.cover.value,
bvid: videoDetailController.bvid,
aid: IdUtils.bv2av(videoDetailController.bvid),
cid: videoDetailController.cid.value,
isReversed:
videoIntroController.videoDetail.value.isPageReversed,
changeFucCall: videoDetailController.videoType ==
SearchType.media_bangumi
? pgcIntroController.changeSeasonOrbangu
: videoIntroController.changeSeasonOrbangu,
showTitle: false,
isSupportReverse: videoDetailController.videoType !=
SearchType.media_bangumi,
onReverse: () => onReversePlay(
bvid: videoDetailController.bvid,
aid: IdUtils.bv2av(videoDetailController.bvid),
isSeason: false,
),
),
),
),
if (videoIntroController.videoDetail.value.ugcSeason != null) ...[
if ((videoIntroController.videoDetail.value.pages?.length ?? 0) >
1) ...[
const SizedBox(height: 8),
Divider(
height: 1,
color: themeData.colorScheme.outline.withValues(alpha: 0.1),
),
],
Widget get seasonPanel {
final videoDetail = videoIntroController.videoDetail.value;
return Column(
children: [
if ((videoDetail.pages?.length ?? 0) > 1)
if (videoDetail.ugcSeason != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: SeasonPanel(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: PagesPanel(
heroTag: heroTag,
onTap: false,
changeFuc: videoIntroController.changeSeasonOrbangu,
showEpisodes: showEpisodes,
videoIntroController: videoIntroController,
bvid: videoIntroController.bvid,
showEpisodes: showEpisodes,
),
),
)
else
Expanded(
child: Obx(
() => EpisodePanel(
heroTag: heroTag,
enableSlide: false,
videoIntroController: videoIntroController,
type: EpisodeType.season,
initialTabIndex: videoDetailController.seasonIndex.value,
type: EpisodeType.part,
list: [videoDetail.pages!],
cover: videoDetailController.cover.value,
seasonId:
videoIntroController.videoDetail.value.ugcSeason!.id,
list: videoIntroController
.videoDetail.value.ugcSeason!.sections!,
bvid: videoDetailController.bvid,
aid: IdUtils.bv2av(videoDetailController.bvid),
cid: videoDetailController.seasonCid ?? 0,
isReversed: videoIntroController
.videoDetail
.value
.ugcSeason!
.sections![videoDetailController.seasonIndex.value]
.isReversed,
cid: videoDetailController.cid.value,
isReversed: videoDetail.isPageReversed,
changeFucCall: videoDetailController.videoType ==
SearchType.media_bangumi
? pgcIntroController.changeSeasonOrbangu
@@ -1925,14 +1870,68 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
onReverse: () => onReversePlay(
bvid: videoDetailController.bvid,
aid: IdUtils.bv2av(videoDetailController.bvid),
isSeason: true,
isSeason: false,
),
),
),
),
if (videoDetail.ugcSeason != null) ...[
if ((videoDetail.pages?.length ?? 0) > 1) ...[
const SizedBox(height: 8),
Divider(
height: 1,
color: themeData.colorScheme.outline.withValues(alpha: 0.1),
),
],
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: SeasonPanel(
heroTag: heroTag,
onTap: false,
changeFuc: videoIntroController.changeSeasonOrbangu,
showEpisodes: showEpisodes,
videoIntroController: videoIntroController,
),
),
Expanded(
child: Obx(
() => EpisodePanel(
heroTag: heroTag,
enableSlide: false,
videoIntroController: videoIntroController,
type: EpisodeType.season,
initialTabIndex: videoDetailController.seasonIndex.value,
cover: videoDetailController.cover.value,
seasonId: videoDetail.ugcSeason!.id,
list: videoDetail.ugcSeason!.sections!,
bvid: videoDetailController.bvid,
aid: IdUtils.bv2av(videoDetailController.bvid),
cid: videoDetailController.seasonCid ?? 0,
isReversed: videoIntroController
.videoDetail
.value
.ugcSeason!
.sections![videoDetailController.seasonIndex.value]
.isReversed,
changeFucCall:
videoDetailController.videoType == SearchType.media_bangumi
? pgcIntroController.changeSeasonOrbangu
: videoIntroController.changeSeasonOrbangu,
showTitle: false,
isSupportReverse:
videoDetailController.videoType != SearchType.media_bangumi,
onReverse: () => onReversePlay(
bvid: videoDetailController.bvid,
aid: IdUtils.bv2av(videoDetailController.bvid),
isSeason: true,
),
),
),
),
],
);
],
);
}
Widget videoReplyPanel([bool needCtr = true]) => Obx(
() => VideoReplyPanel(
@@ -2108,10 +2107,11 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
);
}
final videoDetail = videoIntroController.videoDetail.value;
if (isSeason) {
// reverse season
final item = videoIntroController.videoDetail.value.ugcSeason!
.sections![videoDetailController.seasonIndex.value];
final item = videoDetail
.ugcSeason!.sections![videoDetailController.seasonIndex.value];
item
..isReversed = !item.isReversed
..episodes = item.episodes!.reversed.toList();
@@ -2136,16 +2136,15 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
}
} else {
// reverse part
final item = videoIntroController.videoDetail.value;
item
..isPageReversed = !item.isPageReversed
..pages = item.pages!.reversed.toList();
videoDetail
..isPageReversed = !videoDetail.isPageReversed
..pages = videoDetail.pages!.reversed.toList();
if (!videoDetailController.plPlayerController.reverseFromFirst) {
// keep current episode
videoDetailController.cid.refresh();
} else {
// switch to first episode
var episode = videoIntroController.videoDetail.value.pages!.first;
var episode = videoDetail.pages!.first;
if (episode.cid != videoDetailController.cid.value) {
changeEpisode(episode);
} else {

View File

@@ -1876,20 +1876,16 @@ class HeaderControlState extends State<HeaderControl> {
return Obx(
() {
String title;
if (videoIntroController
.videoDetail.value.videos ==
1) {
title = videoIntroController
.videoDetail.value.title!;
final videoDetail =
videoIntroController.videoDetail.value;
if (videoDetail.videos == 1) {
title = videoDetail.title!;
} else {
title = videoIntroController
.videoDetail.value.pages
title = videoDetail.pages
?.firstWhereOrNull((e) =>
e.cid == videoDetailCtr.cid.value)
?.pagePart ??
videoIntroController
.videoDetail.value.title!;
videoDetail.title!;
}
final textPainter = TextPainter(
text: TextSpan(

View File

@@ -246,9 +246,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
// 动态构建底部控制条
Widget buildBottomControl() {
bool isSeason = videoIntroController?.videoDetail.value.ugcSeason != null;
bool isPage = videoIntroController?.videoDetail.value.pages != null &&
videoIntroController!.videoDetail.value.pages!.length > 1;
final videoDetail = videoIntroController?.videoDetail.value;
bool isSeason = videoDetail?.ugcSeason != null;
bool isPage = videoDetail?.pages != null && videoDetail!.pages!.length > 1;
bool isPgc = pgcIntroController != null;
bool anySeason = isSeason || isPage || isPgc;
double widgetWidth =
@@ -465,9 +465,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
int currentCid = plPlayerController.cid;
String bvid = plPlayerController.bvid;
List episodes = [];
final videoDetail = videoIntroController!.videoDetail.value;
if (isSeason) {
final List<SectionItem> sections =
videoIntroController!.videoDetail.value.ugcSeason!.sections!;
videoDetail.ugcSeason!.sections!;
for (int i = 0; i < sections.length; i++) {
final List<EpisodeItem> episodesList = sections[i].episodes!;
for (int j = 0; j < episodesList.length; j++) {
@@ -479,17 +480,14 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
}
}
} else if (isPage) {
final List<Part> pages =
videoIntroController!.videoDetail.value.pages!;
final List<Part> pages = videoDetail.pages!;
episodes = pages;
} else if (isPgc) {
episodes = pgcIntroController!.pgcItem.episodes!;
}
widget.showEpisodes?.call(
index,
isSeason
? videoIntroController?.videoDetail.value.ugcSeason!
: null,
isSeason ? videoDetail.ugcSeason! : null,
isSeason ? null : episodes,
bvid,
IdUtils.bv2av(bvid),