mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: show all episodes on listsheet
This commit is contained in:
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
## feat
|
## feat
|
||||||
|
|
||||||
|
- [x] 显示视频完整合集
|
||||||
- [x] 三连动画
|
- [x] 三连动画
|
||||||
- [x] 番剧三连
|
- [x] 番剧三连
|
||||||
- [x] 带图评论
|
- [x] 带图评论
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:PiliPalaX/common/constants.dart';
|
import 'package:PiliPalaX/common/constants.dart';
|
||||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||||
import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi;
|
import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi;
|
||||||
@@ -12,6 +14,8 @@ import '../../utils/utils.dart';
|
|||||||
|
|
||||||
class ListSheet {
|
class ListSheet {
|
||||||
ListSheet({
|
ListSheet({
|
||||||
|
this.index,
|
||||||
|
this.sections,
|
||||||
required this.episodes,
|
required this.episodes,
|
||||||
this.bvid,
|
this.bvid,
|
||||||
this.aid,
|
this.aid,
|
||||||
@@ -21,6 +25,8 @@ class ListSheet {
|
|||||||
this.scaffoldState,
|
this.scaffoldState,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final dynamic index;
|
||||||
|
final dynamic sections;
|
||||||
final dynamic episodes;
|
final dynamic episodes;
|
||||||
final String? bvid;
|
final String? bvid;
|
||||||
final int? aid;
|
final int? aid;
|
||||||
@@ -32,6 +38,8 @@ class ListSheet {
|
|||||||
late PersistentBottomSheetController bottomSheetController;
|
late PersistentBottomSheetController bottomSheetController;
|
||||||
|
|
||||||
Widget get listSheetContent => ListSheetContent(
|
Widget get listSheetContent => ListSheetContent(
|
||||||
|
index: index,
|
||||||
|
sections: sections,
|
||||||
episodes: episodes,
|
episodes: episodes,
|
||||||
bvid: bvid,
|
bvid: bvid,
|
||||||
aid: aid,
|
aid: aid,
|
||||||
@@ -54,6 +62,8 @@ class ListSheet {
|
|||||||
class ListSheetContent extends StatefulWidget {
|
class ListSheetContent extends StatefulWidget {
|
||||||
const ListSheetContent({
|
const ListSheetContent({
|
||||||
super.key,
|
super.key,
|
||||||
|
this.index = 0,
|
||||||
|
this.sections,
|
||||||
required this.episodes,
|
required this.episodes,
|
||||||
this.bvid,
|
this.bvid,
|
||||||
this.aid,
|
this.aid,
|
||||||
@@ -62,6 +72,8 @@ class ListSheetContent extends StatefulWidget {
|
|||||||
required this.onClose,
|
required this.onClose,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final int index;
|
||||||
|
final dynamic sections;
|
||||||
final dynamic episodes;
|
final dynamic episodes;
|
||||||
final String? bvid;
|
final String? bvid;
|
||||||
final int? aid;
|
final int? aid;
|
||||||
@@ -73,24 +85,53 @@ class ListSheetContent extends StatefulWidget {
|
|||||||
State<ListSheetContent> createState() => _ListSheetContentState();
|
State<ListSheetContent> createState() => _ListSheetContentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ListSheetContentState extends State<ListSheetContent> {
|
class _ListSheetContentState extends State<ListSheetContent>
|
||||||
final ItemScrollController itemScrollController = ItemScrollController();
|
with TickerProviderStateMixin {
|
||||||
|
late List<ItemScrollController> itemScrollController = [];
|
||||||
late final int currentIndex =
|
late final int currentIndex =
|
||||||
widget.episodes!.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
|
widget.episodes!.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
|
||||||
0;
|
0;
|
||||||
bool reverse = false;
|
late List<bool> reverse;
|
||||||
|
|
||||||
|
bool get _isList => widget.sections is List && widget.sections.length > 1;
|
||||||
|
TabController? _ctr;
|
||||||
|
StreamController? _indexStream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
if (_isList) {
|
||||||
|
_indexStream = StreamController<int>();
|
||||||
|
_ctr = TabController(
|
||||||
|
vsync: this,
|
||||||
|
length: widget.sections.length,
|
||||||
|
initialIndex: widget.index,
|
||||||
|
)..addListener(() {
|
||||||
|
_indexStream?.add(_ctr?.index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
itemScrollController = _isList
|
||||||
|
? List.generate(widget.sections.length, (_) => ItemScrollController())
|
||||||
|
: [ItemScrollController()];
|
||||||
|
reverse =
|
||||||
|
_isList ? List.generate(widget.sections.length, (_) => false) : [false];
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
itemScrollController.jumpTo(index: currentIndex);
|
itemScrollController[widget.index].jumpTo(index: currentIndex);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_indexStream?.close();
|
||||||
|
_ctr?.removeListener(() {});
|
||||||
|
_ctr?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildEpisodeListItem(
|
Widget buildEpisodeListItem(
|
||||||
dynamic episode,
|
dynamic episode,
|
||||||
int index,
|
int index,
|
||||||
|
int length,
|
||||||
bool isCurrentIndex,
|
bool isCurrentIndex,
|
||||||
) {
|
) {
|
||||||
Color primary = Theme.of(context).colorScheme.primary;
|
Color primary = Theme.of(context).colorScheme.primary;
|
||||||
@@ -200,7 +241,7 @@ class _ListSheetContentState extends State<ListSheetContent> {
|
|||||||
],
|
],
|
||||||
if (!(episode.runtimeType.toString() == 'EpisodeItem' &&
|
if (!(episode.runtimeType.toString() == 'EpisodeItem' &&
|
||||||
(episode.longTitle != null && episode.longTitle != '')))
|
(episode.longTitle != null && episode.longTitle != '')))
|
||||||
Text('${index + 1}/${widget.episodes!.length}'),
|
Text('${index + 1}/$length'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -219,15 +260,19 @@ class _ListSheetContentState extends State<ListSheetContent> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'合集(${widget.episodes!.length})',
|
'合集(${_isList ? List.generate(widget.sections.length, (index) => widget.sections[index].episodes.length).reduce((value, element) => value + element) : widget.episodes!.length})',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: '跳至顶部',
|
tooltip: '跳至顶部',
|
||||||
icon: const Icon(Icons.vertical_align_top),
|
icon: const Icon(Icons.vertical_align_top),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
itemScrollController.scrollTo(
|
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||||
index: !reverse ? 0 : widget.episodes!.length - 1,
|
index: !reverse[_ctr?.index ?? 0]
|
||||||
|
? 0
|
||||||
|
: _isList
|
||||||
|
? widget.sections[_ctr?.index].episodes.length - 1
|
||||||
|
: widget.episodes.length - 1,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -236,32 +281,47 @@ class _ListSheetContentState extends State<ListSheetContent> {
|
|||||||
tooltip: '跳至底部',
|
tooltip: '跳至底部',
|
||||||
icon: const Icon(Icons.vertical_align_bottom),
|
icon: const Icon(Icons.vertical_align_bottom),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
itemScrollController.scrollTo(
|
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||||
index: !reverse ? widget.episodes!.length - 1 : 0,
|
index: !reverse[_ctr?.index ?? 0]
|
||||||
|
? _isList
|
||||||
|
? widget.sections[_ctr?.index].episodes.length - 1
|
||||||
|
: widget.episodes.length - 1
|
||||||
|
: 0,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
tooltip: '跳至当前',
|
||||||
icon: const Icon(Icons.my_location),
|
icon: const Icon(Icons.my_location),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
itemScrollController.scrollTo(
|
if (_ctr != null && _ctr?.index != widget.index) {
|
||||||
|
_ctr?.animateTo(widget.index);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 225));
|
||||||
|
}
|
||||||
|
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||||
index: currentIndex,
|
index: currentIndex,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
StreamBuilder(
|
||||||
tooltip: '反序',
|
stream: _indexStream?.stream,
|
||||||
icon: Icon(!reverse
|
initialData: 0,
|
||||||
? MdiIcons.sortAscending
|
builder: (_, snapshot) => IconButton(
|
||||||
: MdiIcons.sortDescending),
|
tooltip: reverse[snapshot.data] ? '正序' : '反序',
|
||||||
onPressed: () {
|
icon: Icon(
|
||||||
setState(() {
|
!reverse[snapshot.data]
|
||||||
reverse = !reverse;
|
? MdiIcons.sortAscending
|
||||||
});
|
: MdiIcons.sortDescending,
|
||||||
},
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: '关闭',
|
tooltip: '关闭',
|
||||||
@@ -275,30 +335,55 @@ class _ListSheetContentState extends State<ListSheetContent> {
|
|||||||
height: 1,
|
height: 1,
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
),
|
),
|
||||||
Expanded(
|
if (_isList)
|
||||||
child: Material(
|
TabBar(
|
||||||
child: ScrollablePositionedList.separated(
|
controller: _ctr,
|
||||||
padding: EdgeInsets.only(
|
isScrollable: true,
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 20),
|
tabs: (widget.sections as List)
|
||||||
reverse: reverse,
|
.map((item) => Tab(text: item.title))
|
||||||
itemCount: widget.episodes!.length,
|
.toList(),
|
||||||
itemBuilder: (BuildContext context, int index) {
|
dividerHeight: 1,
|
||||||
return buildEpisodeListItem(
|
dividerColor: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
widget.episodes![index],
|
|
||||||
index,
|
|
||||||
currentIndex == index,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
itemScrollController: itemScrollController,
|
|
||||||
separatorBuilder: (_, index) => Divider(
|
|
||||||
height: 1,
|
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _isList
|
||||||
|
? TabBarView(
|
||||||
|
controller: _ctr,
|
||||||
|
children: List.generate(
|
||||||
|
widget.sections.length,
|
||||||
|
(index) =>
|
||||||
|
_buildBody(index, widget.sections[index].episodes),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _buildBody(null, widget.episodes),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildBody(i, episodes) => ScrollablePositionedList.separated(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 20,
|
||||||
|
),
|
||||||
|
reverse: reverse[i ?? 0],
|
||||||
|
itemCount: episodes.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return buildEpisodeListItem(
|
||||||
|
episodes[index],
|
||||||
|
index,
|
||||||
|
episodes.length,
|
||||||
|
i != null
|
||||||
|
? i == widget.index
|
||||||
|
? currentIndex == index
|
||||||
|
: false
|
||||||
|
: currentIndex == index,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemScrollController: itemScrollController[i ?? 0],
|
||||||
|
separatorBuilder: (_, index) => Divider(
|
||||||
|
height: 1,
|
||||||
|
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,10 +118,13 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||||
),
|
),
|
||||||
onPressed: () => widget.showEpisodes(
|
onPressed: () => widget.showEpisodes(
|
||||||
widget.pages,
|
null,
|
||||||
widget.pages[currentIndex].bvid,
|
null,
|
||||||
widget.pages[currentIndex].aid,
|
widget.pages,
|
||||||
cid),
|
widget.pages[currentIndex].bvid,
|
||||||
|
widget.pages[currentIndex].aid,
|
||||||
|
cid,
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'全${widget.pages.length}话',
|
'全${widget.pages.length}话',
|
||||||
style: const TextStyle(fontSize: 13),
|
style: const TextStyle(fontSize: 13),
|
||||||
|
|||||||
@@ -523,14 +523,16 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
if (!loadingStatus &&
|
if (!loadingStatus &&
|
||||||
widget.videoDetail?.pages != null &&
|
widget.videoDetail?.pages != null &&
|
||||||
widget.videoDetail!.pages!.length > 1) ...[
|
widget.videoDetail!.pages!.length > 1) ...[
|
||||||
Obx(() => PagesPanel(
|
Obx(
|
||||||
heroTag: heroTag,
|
() => PagesPanel(
|
||||||
pages: widget.videoDetail!.pages!,
|
heroTag: heroTag,
|
||||||
cid: videoIntroController.lastPlayCid.value,
|
pages: widget.videoDetail!.pages!,
|
||||||
bvid: videoIntroController.bvid,
|
cid: videoIntroController.lastPlayCid.value,
|
||||||
changeFuc: videoIntroController.changeSeasonOrbangu,
|
bvid: videoIntroController.bvid,
|
||||||
showEpisodes: widget.showEpisodes,
|
changeFuc: videoIntroController.changeSeasonOrbangu,
|
||||||
))
|
showEpisodes: widget.showEpisodes,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -96,7 +96,13 @@ class _PagesPanelState extends State<PagesPanel> {
|
|||||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||||
),
|
),
|
||||||
onPressed: () => widget.showEpisodes(
|
onPressed: () => widget.showEpisodes(
|
||||||
episodes, widget.bvid, IdUtils.bv2av(widget.bvid), cid),
|
null,
|
||||||
|
null,
|
||||||
|
episodes,
|
||||||
|
widget.bvid,
|
||||||
|
IdUtils.bv2av(widget.bvid),
|
||||||
|
cid,
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'共${widget.pages.length}集',
|
'共${widget.pages.length}集',
|
||||||
style: const TextStyle(fontSize: 13),
|
style: const TextStyle(fontSize: 13),
|
||||||
|
|||||||
@@ -25,32 +25,20 @@ class SeasonPanel extends StatefulWidget {
|
|||||||
class _SeasonPanelState extends State<SeasonPanel> {
|
class _SeasonPanelState extends State<SeasonPanel> {
|
||||||
List<EpisodeItem>? episodes;
|
List<EpisodeItem>? episodes;
|
||||||
late int cid;
|
late int cid;
|
||||||
|
int? _index;
|
||||||
int currentIndex = 0;
|
int currentIndex = 0;
|
||||||
// final String heroTag = Get.arguments['heroTag'];
|
|
||||||
late final String heroTag;
|
|
||||||
late VideoDetailController _videoDetailController;
|
late VideoDetailController _videoDetailController;
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
cid = widget.cid!;
|
cid = widget.cid!;
|
||||||
heroTag = widget.heroTag;
|
_videoDetailController =
|
||||||
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
|
Get.find<VideoDetailController>(tag: widget.heroTag);
|
||||||
|
|
||||||
/// 根据 cid 找到对应集,找到对应 episodes
|
/// 根据 cid 找到对应集,找到对应 episodes
|
||||||
/// 有多个episodes时,只显示其中一个
|
/// 有多个episodes时,只显示其中一个
|
||||||
/// TODO 同时显示多个合集
|
_findEpisode();
|
||||||
final List<SectionItem> sections = widget.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++) {
|
|
||||||
if (episodesList[j].cid == cid) {
|
|
||||||
episodes = episodesList;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (episodes == null) {
|
if (episodes == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -62,6 +50,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
currentIndex = episodes!.indexWhere((EpisodeItem e) => e.cid == cid);
|
currentIndex = episodes!.indexWhere((EpisodeItem e) => e.cid == cid);
|
||||||
_videoDetailController.cid.listen((int p0) {
|
_videoDetailController.cid.listen((int p0) {
|
||||||
cid = p0;
|
cid = p0;
|
||||||
|
_findEpisode();
|
||||||
currentIndex = episodes!.indexWhere((EpisodeItem e) => e.cid == cid);
|
currentIndex = episodes!.indexWhere((EpisodeItem e) => e.cid == cid);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@@ -79,12 +68,6 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
// setState(() {});
|
// setState(() {});
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (episodes == null) {
|
if (episodes == null) {
|
||||||
@@ -103,7 +86,14 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => widget.showEpisodes(episodes, null, null, cid),
|
onTap: () => widget.showEpisodes(
|
||||||
|
_index,
|
||||||
|
widget.ugcSeason.sections,
|
||||||
|
episodes,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
cid,
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
|
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -143,4 +133,18 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _findEpisode() {
|
||||||
|
final List<SectionItem> sections = widget.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++) {
|
||||||
|
if (episodesList[j].cid == cid) {
|
||||||
|
_index = i;
|
||||||
|
episodes = episodesList;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1312,8 +1312,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
showEpisodes(episodes, bvid, aid, cid) {
|
showEpisodes(index, sections, episodes, bvid, aid, cid) {
|
||||||
ListSheet(
|
ListSheet(
|
||||||
|
index: index,
|
||||||
|
sections: sections,
|
||||||
episodes: episodes,
|
episodes: episodes,
|
||||||
bvid: bvid,
|
bvid: bvid,
|
||||||
aid: aid,
|
aid: aid,
|
||||||
|
|||||||
Reference in New Issue
Block a user