feat: subsribe season

This commit is contained in:
bggRGjQaUbCoE
2024-10-03 10:47:28 +08:00
parent 097b389a5b
commit f5202beb44
8 changed files with 155 additions and 37 deletions

View File

@@ -41,6 +41,7 @@
## feat ## feat
- [x] 取消/订阅合集
- [x] SponsorBlock - [x] SponsorBlock
- [x] 显示视频完整合集 - [x] 显示视频完整合集
- [x] 三连动画 - [x] 三连动画

View File

@@ -2,6 +2,7 @@ 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/http/video.dart';
import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi; import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi;
import 'package:PiliPalaX/models/video_detail_res.dart' as video; import 'package:PiliPalaX/models/video_detail_res.dart' as video;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -15,7 +16,7 @@ import '../../utils/utils.dart';
class ListSheet { class ListSheet {
ListSheet({ ListSheet({
this.index, this.index,
this.sections, this.season,
required this.episodes, required this.episodes,
this.bvid, this.bvid,
this.aid, this.aid,
@@ -26,7 +27,7 @@ class ListSheet {
}); });
final dynamic index; final dynamic index;
final dynamic sections; final dynamic season;
final dynamic episodes; final dynamic episodes;
final String? bvid; final String? bvid;
final int? aid; final int? aid;
@@ -39,7 +40,7 @@ class ListSheet {
Widget get listSheetContent => ListSheetContent( Widget get listSheetContent => ListSheetContent(
index: index, index: index,
sections: sections, season: season,
episodes: episodes, episodes: episodes,
bvid: bvid, bvid: bvid,
aid: aid, aid: aid,
@@ -62,8 +63,8 @@ class ListSheet {
class ListSheetContent extends StatefulWidget { class ListSheetContent extends StatefulWidget {
const ListSheetContent({ const ListSheetContent({
super.key, super.key,
this.index = 0, this.index,
this.sections, this.season,
required this.episodes, required this.episodes,
this.bvid, this.bvid,
this.aid, this.aid,
@@ -73,7 +74,7 @@ class ListSheetContent extends StatefulWidget {
}); });
final dynamic index; final dynamic index;
final dynamic sections; final dynamic season;
final dynamic episodes; final dynamic episodes;
final String? bvid; final String? bvid;
final int? aid; final int? aid;
@@ -94,9 +95,14 @@ class _ListSheetContentState extends State<ListSheetContent>
late List<bool> reverse; late List<bool> reverse;
int get _index => widget.index ?? 0; int get _index => widget.index ?? 0;
bool get _isList => widget.sections is List && widget.sections.length > 1; bool get _isList =>
widget.season != null &&
widget.season?.sections is List &&
widget.season.sections.length > 1;
TabController? _ctr; TabController? _ctr;
StreamController? _indexStream; StreamController? _indexStream;
int? _seasonFav;
StreamController? _favStream;
@override @override
void initState() { void initState() {
@@ -105,24 +111,37 @@ class _ListSheetContentState extends State<ListSheetContent>
_indexStream = StreamController<int>(); _indexStream = StreamController<int>();
_ctr = TabController( _ctr = TabController(
vsync: this, vsync: this,
length: widget.sections.length, length: widget.season.sections.length,
initialIndex: _index, initialIndex: _index,
)..addListener(() { )..addListener(() {
_indexStream?.add(_ctr?.index); _indexStream?.add(_ctr?.index);
}); });
} }
itemScrollController = _isList itemScrollController = _isList
? List.generate(widget.sections.length, (_) => ItemScrollController()) ? List.generate(
widget.season.sections.length, (_) => ItemScrollController())
: [ItemScrollController()]; : [ItemScrollController()];
reverse = reverse = _isList
_isList ? List.generate(widget.sections.length, (_) => false) : [false]; ? List.generate(widget.season.sections.length, (_) => false)
: [false];
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
itemScrollController[_index].jumpTo(index: currentIndex); itemScrollController[_index].jumpTo(index: currentIndex);
}); });
if (widget.bvid != null && widget.season != null) {
_favStream = StreamController<int>();
() async {
dynamic result = await VideoHttp.videoRelation(bvid: widget.bvid);
if (result['status']) {
_seasonFav = result['data']['season_fav'] ? 1 : 0;
_favStream?.add(_seasonFav);
}
}();
}
} }
@override @override
void dispose() { void dispose() {
_favStream?.close();
_indexStream?.close(); _indexStream?.close();
_ctr?.removeListener(() {}); _ctr?.removeListener(() {});
_ctr?.dispose(); _ctr?.dispose();
@@ -261,40 +280,67 @@ class _ListSheetContentState extends State<ListSheetContent>
child: Row( child: Row(
children: [ children: [
Text( Text(
'合集(${_isList ? List.generate(widget.sections.length, (index) => widget.sections[index].episodes.length).reduce((value, element) => value + element) : widget.episodes!.length}', '合集(${_isList ? widget.season.epCount : widget.episodes!.length}',
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
IconButton( StreamBuilder(
stream: _favStream?.stream,
builder: (_, snapshot) => snapshot.hasData
? _mediumButton(
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
icon: _seasonFav == 1 ? Icons.alarm_off : Icons.alarm,
onPressed: () async {
dynamic result = await VideoHttp.seasonFav(
isFav: _seasonFav == 1,
seasonId: widget.season.id,
);
if (result['status']) {
SmartDialog.showToast(
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
_seasonFav = _seasonFav == 1 ? 0 : 1;
_favStream?.add(_seasonFav);
} else {
SmartDialog.showToast(result['msg']);
}
},
)
: const SizedBox.shrink(),
),
_mediumButton(
tooltip: '跳至顶部', tooltip: '跳至顶部',
icon: const Icon(Icons.vertical_align_top), icon: Icons.vertical_align_top,
onPressed: () { onPressed: () {
itemScrollController[_ctr?.index ?? 0].scrollTo( itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0] index: !reverse[_ctr?.index ?? 0]
? 0 ? 0
: _isList : _isList
? widget.sections[_ctr?.index].episodes.length - 1 ? widget.season.sections[_ctr?.index].episodes
.length -
1
: widget.episodes.length - 1, : widget.episodes.length - 1,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
); );
}, },
), ),
IconButton( _mediumButton(
tooltip: '跳至底部', tooltip: '跳至底部',
icon: const Icon(Icons.vertical_align_bottom), icon: Icons.vertical_align_bottom,
onPressed: () { onPressed: () {
itemScrollController[_ctr?.index ?? 0].scrollTo( itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0] index: !reverse[_ctr?.index ?? 0]
? _isList ? _isList
? widget.sections[_ctr?.index].episodes.length - 1 ? widget.season.sections[_ctr?.index].episodes
.length -
1
: widget.episodes.length - 1 : widget.episodes.length - 1
: 0, : 0,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
); );
}, },
), ),
IconButton( _mediumButton(
tooltip: '跳至当前', tooltip: '跳至当前',
icon: const Icon(Icons.my_location), icon: Icons.my_location,
onPressed: () async { onPressed: () async {
if (_ctr != null && _ctr?.index != (_index)) { if (_ctr != null && _ctr?.index != (_index)) {
_ctr?.animateTo(_index); _ctr?.animateTo(_index);
@@ -310,13 +356,11 @@ class _ListSheetContentState extends State<ListSheetContent>
StreamBuilder( StreamBuilder(
stream: _indexStream?.stream, stream: _indexStream?.stream,
initialData: 0, initialData: 0,
builder: (_, snapshot) => IconButton( builder: (_, snapshot) => _mediumButton(
tooltip: reverse[snapshot.data] ? '正序' : '反序', tooltip: reverse[snapshot.data] ? '正序' : '反序',
icon: Icon( icon: !reverse[snapshot.data]
!reverse[snapshot.data] ? MdiIcons.sortAscending
? MdiIcons.sortAscending : MdiIcons.sortDescending,
: MdiIcons.sortDescending,
),
onPressed: () { onPressed: () {
setState(() { setState(() {
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0]; reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
@@ -324,9 +368,9 @@ class _ListSheetContentState extends State<ListSheetContent>
}, },
), ),
), ),
IconButton( _mediumButton(
tooltip: '关闭', tooltip: '关闭',
icon: const Icon(Icons.close), icon: Icons.close,
onPressed: widget.onClose, onPressed: widget.onClose,
), ),
], ],
@@ -340,7 +384,7 @@ class _ListSheetContentState extends State<ListSheetContent>
TabBar( TabBar(
controller: _ctr, controller: _ctr,
isScrollable: true, isScrollable: true,
tabs: (widget.sections as List) tabs: (widget.season.sections as List)
.map((item) => Tab(text: item.title)) .map((item) => Tab(text: item.title))
.toList(), .toList(),
dividerHeight: 1, dividerHeight: 1,
@@ -351,9 +395,9 @@ class _ListSheetContentState extends State<ListSheetContent>
? TabBarView( ? TabBarView(
controller: _ctr, controller: _ctr,
children: List.generate( children: List.generate(
widget.sections.length, widget.season.sections.length,
(index) => (index) => _buildBody(
_buildBody(index, widget.sections[index].episodes), index, widget.season.sections[index].episodes),
), ),
) )
: _buildBody(null, widget.episodes), : _buildBody(null, widget.episodes),
@@ -363,6 +407,25 @@ class _ListSheetContentState extends State<ListSheetContent>
); );
} }
Widget _mediumButton({
String? tooltip,
IconData? icon,
VoidCallback? onPressed,
}) {
return SizedBox(
width: 34,
height: 34,
child: IconButton(
tooltip: tooltip,
icon: Icon(icon),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: onPressed,
),
);
}
Widget _buildBody(i, episodes) => Material( Widget _buildBody(i, episodes) => Material(
child: ScrollablePositionedList.separated( child: ScrollablePositionedList.separated(
padding: EdgeInsets.only( padding: EdgeInsets.only(

View File

@@ -633,4 +633,8 @@ class Api {
static const String removeDynamic = '/dynamic_svr/v1/dynamic_svr/rm_dynamic'; static const String removeDynamic = '/dynamic_svr/v1/dynamic_svr/rm_dynamic';
static const String uploadBfs = '/x/dynamic/feed/draw/upload_bfs'; static const String uploadBfs = '/x/dynamic/feed/draw/upload_bfs';
static const String videoRelation = '/x/web-interface/archive/relation';
static const String seasonFav = '/x/v3/fav/season/'; // + fav unfav
} }

View File

@@ -274,6 +274,54 @@ class VideoHttp {
} }
} }
static Future seasonFav({
required bool isFav,
required dynamic seasonId,
}) async {
var res = await Request().post(
Api.seasonFav + (isFav ? 'unfav' : 'fav'),
data: {
'platform': 'web',
'season_id': seasonId,
'csrf': await Request.getCsrf(),
},
options: Options(
contentType: Headers.formUrlEncodedContentType,
),
);
if (res.data['code'] == 0) {
return {
'status': true,
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
}
static Future videoRelation({required dynamic bvid}) async {
var res = await Request().get(
Api.videoRelation,
data: {
'aid': IdUtils.bv2av(bvid),
'bvid': bvid,
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
}
// 相关视频 // 相关视频
static Future<LoadingState> relatedVideoList({required String bvid}) async { static Future<LoadingState> relatedVideoList({required String bvid}) async {
var res = await Request().get(Api.relatedList, data: {'bvid': bvid}); var res = await Request().get(Api.relatedList, data: {'bvid': bvid});

View File

@@ -659,6 +659,7 @@ class SectionItem {
this.episodes, this.episodes,
}); });
int? epCount;
int? seasonId; int? seasonId;
int? id; int? id;
String? title; String? title;
@@ -666,6 +667,7 @@ class SectionItem {
List<EpisodeItem>? episodes; List<EpisodeItem>? episodes;
SectionItem.fromJson(Map<String, dynamic> json) { SectionItem.fromJson(Map<String, dynamic> json) {
epCount = json['ep_count'];
seasonId = json['season_id']; seasonId = json['season_id'];
id = json['id']; id = json['id'];
title = json['title']; title = json['title'];

View File

@@ -88,9 +88,9 @@ class _SeasonPanelState extends State<SeasonPanel> {
child: InkWell( child: InkWell(
onTap: () => widget.showEpisodes( onTap: () => widget.showEpisodes(
_index, _index,
widget.ugcSeason.sections, widget.ugcSeason,
episodes, episodes,
null, _videoDetailController.bvid,
null, null,
cid, cid,
), ),

View File

@@ -1315,10 +1315,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
); );
} }
showEpisodes(index, sections, episodes, bvid, aid, cid) { showEpisodes(index, season, episodes, bvid, aid, cid) {
ListSheet( ListSheet(
index: index, index: index,
sections: sections, season: season,
episodes: episodes, episodes: episodes,
bvid: bvid, bvid: bvid,
aid: aid, aid: aid,

View File

@@ -374,7 +374,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (widget.showEpisodes != null) { if (widget.showEpisodes != null) {
widget.showEpisodes!( widget.showEpisodes!(
index, index,
videoIntroController?.videoDetail.value.ugcSeason?.sections, videoIntroController?.videoDetail.value.ugcSeason,
episodes, episodes,
bvid, bvid,
IdUtils.bv2av(bvid), IdUtils.bv2av(bvid),