feat: medialist: continue playing #70

Closes #70

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-31 20:33:03 +08:00
parent 098e2220cc
commit cde0ea244b
9 changed files with 260 additions and 166 deletions

View File

@@ -329,7 +329,10 @@ class VideoDetailController extends GetxController
}
}
void getMediaList([bool isReverse = false]) async {
void getMediaList({
bool isReverse = false,
bool isLoadPrevious = false,
}) async {
if (isReverse.not &&
Get.arguments['count'] != null &&
mediaList.length >= Get.arguments['count']) {
@@ -339,10 +342,31 @@ class VideoDetailController extends GetxController
type: Get.arguments['mediaType'] ?? _mediaType,
bizId: Get.arguments['mediaId'] ?? -1,
ps: 20,
oid: isReverse || mediaList.isEmpty ? null : mediaList.last.id,
otype: isReverse || mediaList.isEmpty ? null : mediaList.last.type,
direction: isLoadPrevious ? true : false,
oid: isReverse
? null
: mediaList.isEmpty
? _mediaType == 1 &&
Get.arguments['mediaType'] == null // member archive
? Get.arguments['oid']
: null
: isLoadPrevious
? mediaList.first.id
: mediaList.last.id,
otype: isReverse
? null
: mediaList.isEmpty
? null
: isLoadPrevious
? mediaList.first.type
: mediaList.last.type,
desc: _mediaDesc,
sortField: Get.arguments['sortField'] ?? 1,
withCurrent: mediaList.isEmpty &&
_mediaType == 1 &&
Get.arguments['mediaType'] == null
? true // init && member archive
: false,
);
if (res['status']) {
if (res['data'].isNotEmpty) {
@@ -364,6 +388,8 @@ class VideoDetailController extends GetxController
}
}
} catch (_) {}
} else if (isLoadPrevious) {
mediaList.insertAll(0, res['data']);
} else {
mediaList.addAll(res['data']);
}
@@ -379,7 +405,12 @@ class VideoDetailController extends GetxController
childKey.currentState?.showBottomSheet(
(context) => MediaListPanel(
mediaList: mediaList,
changeMediaList: changeMediaList,
changeMediaList: (bvid, cid, aid, cover) {
try {
Get.find<VideoIntroController>(tag: heroTag)
.changeSeasonOrbangu(null, bvid, cid, aid, cover);
} catch (_) {}
},
panelTitle: watchLaterTitle,
getBvId: () => bvid,
count: Get.arguments['count'],
@@ -387,8 +418,13 @@ class VideoDetailController extends GetxController
desc: _mediaDesc,
onReverse: () {
_mediaDesc = !_mediaDesc;
getMediaList(true);
getMediaList(isReverse: true);
},
loadPrevious: Get.arguments['isContinuePlaying'] == true
? () {
getMediaList(isLoadPrevious: true);
}
: null,
),
);
}
@@ -1853,4 +1889,14 @@ class VideoDetailController extends GetxController
}
}
}
void updateMediaListHistory(aid) {
if (Get.arguments['sortField'] != null) {
VideoHttp.medialistHistory(
desc: _mediaDesc ? 1 : 0,
oid: aid,
upperMid: Get.arguments['mediaId'],
);
}
}
}

View File

@@ -556,6 +556,7 @@ class VideoIntroController extends GetxController
// 重新获取视频资源
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
videoDetailCtr.updateMediaListHistory(aid);
videoDetailCtr.vttSubtitlesIndex = null;
videoDetailCtr.bvid = bvid;
videoDetailCtr.oid.value = aid ?? IdUtils.bv2av(bvid);
@@ -673,12 +674,13 @@ class VideoIntroController extends GetxController
bool isPages = false;
final videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
if (videoDetailCtr.isPlayAll) {
episodes.addAll(videoDetailCtr.mediaList);
} else if ((videoDetail.value.pages?.length ?? 0) > 1) {
// part -> playall -> season
if ((videoDetail.value.pages?.length ?? 0) > 1) {
isPages = true;
final List<Part> pages = videoDetail.value.pages!;
episodes.addAll(pages);
} else if (videoDetailCtr.isPlayAll) {
episodes.addAll(videoDetailCtr.mediaList);
} else if (videoDetail.value.ugcSeason != null) {
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
final List<SectionItem> sections = ugcSeason.sections!;

View File

@@ -24,6 +24,7 @@ class MediaListPanel extends StatefulWidget {
required this.count,
required this.desc,
required this.onReverse,
required this.loadPrevious,
});
final List<MediaVideoItemModel> mediaList;
@@ -34,6 +35,7 @@ class MediaListPanel extends StatefulWidget {
final int? count;
final bool? desc;
final VoidCallback onReverse;
final Function? loadPrevious;
@override
State<MediaListPanel> createState() => _MediaListPanelState();
@@ -89,152 +91,152 @@ class _MediaListPanelState extends State<MediaListPanel> {
Expanded(
child: Material(
color: Theme.of(context).colorScheme.surface,
child: Obx(
() => ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: widget.mediaList.length,
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 25,
),
itemBuilder: ((context, index) {
var item = widget.mediaList[index];
if (index == widget.mediaList.length - 1 &&
(widget.count == null ||
widget.mediaList.length < widget.count!)) {
widget.loadMoreMedia();
}
return InkWell(
onTap: () async {
if (item.type != 2) {
SmartDialog.showToast('不支持播放该类型视频');
return;
}
Get.back();
String bvid = item.bvid!;
int? aid = item.id;
String cover = item.cover ?? '';
final int cid = item.cid ??
await SearchHttp.ab2c(aid: aid, bvid: bvid);
widget.changeMediaList?.call(bvid, cid, aid, cover);
child: widget.loadPrevious != null
? RefreshIndicator(
onRefresh: () async {
await widget.loadPrevious!();
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
child: LayoutBuilder(
builder: (context, boxConstraints) {
const double width = 120;
return Container(
constraints: const BoxConstraints(minHeight: 88),
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints boxConstraints) {
final double maxWidth =
boxConstraints.maxWidth;
final double maxHeight =
boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: item.cover ?? '',
width: maxWidth,
height: maxHeight,
),
PBadge(
text: Utils.timeFormat(
item.duration!),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
],
);
},
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(
10, 0, 6, 0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
item.title as String,
textAlign: TextAlign.start,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight:
item.bvid == widget.getBvId()
? FontWeight.bold
: null,
color:
item.bvid == widget.getBvId()
? Theme.of(context)
.colorScheme
.primary
: null,
),
),
const Spacer(),
Text(
item.upper?.name as String,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize,
color: Theme.of(context)
.colorScheme
.outline,
),
),
const SizedBox(height: 2),
Row(
children: [
statView(
context: context,
theme: 'gray',
view: item.cntInfo!['play']
as int,
),
const SizedBox(width: 8),
statDanMu(
context: context,
theme: 'gray',
danmu: item.cntInfo!['danmaku']
as int,
),
],
),
],
),
),
)
],
),
);
},
),
),
);
}),
),
),
child: _buildList,
)
: _buildList,
),
),
],
),
);
}
Widget get _buildList => Obx(
() => ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: widget.mediaList.length,
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
itemBuilder: ((context, index) {
var item = widget.mediaList[index];
if (index == widget.mediaList.length - 1 &&
(widget.count == null ||
widget.mediaList.length < widget.count!)) {
widget.loadMoreMedia();
}
return InkWell(
onTap: () async {
if (item.type != 2) {
SmartDialog.showToast('不支持播放该类型视频');
return;
}
Get.back();
String bvid = item.bvid!;
int? aid = item.id;
String cover = item.cover ?? '';
final int cid =
item.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
widget.changeMediaList?.call(bvid, cid, aid, cover);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
child: LayoutBuilder(
builder: (context, boxConstraints) {
const double width = 120;
return Container(
constraints: const BoxConstraints(minHeight: 88),
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (BuildContext context,
BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight =
boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: item.cover ?? '',
width: maxWidth,
height: maxHeight,
),
PBadge(
text: Utils.timeFormat(item.duration!),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
],
);
},
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title as String,
textAlign: TextAlign.start,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: item.bvid == widget.getBvId()
? FontWeight.bold
: null,
color: item.bvid == widget.getBvId()
? Theme.of(context)
.colorScheme
.primary
: null,
),
),
const Spacer(),
Text(
item.upper?.name as String,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize,
color:
Theme.of(context).colorScheme.outline,
),
),
const SizedBox(height: 2),
Row(
children: [
statView(
context: context,
theme: 'gray',
view: item.cntInfo!['play'] as int,
),
const SizedBox(width: 8),
statDanMu(
context: context,
theme: 'gray',
danmu: item.cntInfo!['danmaku'] as int,
),
],
),
],
),
),
)
],
),
);
},
),
),
);
}),
),
);
}