feat: locate last viewed video

Closes #453

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-03-30 18:53:49 +08:00
parent e89bd2fedf
commit 40429021be
8 changed files with 228 additions and 128 deletions

View File

@@ -17,10 +17,12 @@ class VideoCardHMemberVideo extends StatelessWidget {
required this.videoItem,
this.onTap,
this.bvid,
this.fromViewAid,
});
final Item videoItem;
final VoidCallback? onTap;
final dynamic bvid;
final String? fromViewAid;
@override
Widget build(BuildContext context) {
@@ -82,6 +84,21 @@ class VideoCardHMemberVideo extends StatelessWidget {
width: maxWidth,
height: maxHeight,
),
if (fromViewAid == videoItem.param)
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black54,
),
child: Center(
child: const Text(
'上次观看',
style: TextStyle(fontSize: 15),
),
),
),
),
if (videoItem.badges?.isNotEmpty == true)
PBadge(
text: videoItem.badges!

View File

@@ -153,6 +153,7 @@ class MemberHttp {
int? next,
int? seasonId,
int? seriesId,
includeCursor,
}) async {
Map<String, String> data = {
if (aid != null) 'aid': aid.toString(),
@@ -170,6 +171,7 @@ class MemberHttp {
'qn': type == ContributeType.video ? '80' : '32',
if (order != null) 'order': order,
if (sort != null) 'sort': sort,
if (includeCursor != null) 'include_cursor': includeCursor.toString(),
'statistics': Constants.statistics,
'vmid': mid.toString(),
};
@@ -240,6 +242,7 @@ class MemberHttp {
static Future<LoadingState> space({
int? mid,
dynamic fromViewAid,
}) async {
Map<String, String> data = {
'build': '1462100',
@@ -248,6 +251,7 @@ class MemberHttp {
'mobi_app': 'android_hd',
'platform': 'android',
's_locale': 'zh_CN',
if (fromViewAid != null) 'from_view_aid': fromViewAid,
'statistics': Constants.statistics,
'vmid': mid.toString(),
};

View File

@@ -32,9 +32,9 @@ abstract class CommonController extends GetxController
@override
final ScrollController scrollController = ScrollController();
int currentPage = 1;
late int currentPage = 1;
bool isLoading = false;
bool isEnd = false;
late bool isEnd = false;
Rx<LoadingState> loadingState = LoadingState.loading().obs;
Future<LoadingState> customGetData();

View File

@@ -62,7 +62,10 @@ class _MemberVideoState extends State<MemberVideo>
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
? refreshIndicator(
? Stack(
clipBehavior: Clip.none,
children: [
refreshIndicator(
onRefresh: () async {
await _controller.onRefresh();
},
@@ -91,15 +94,18 @@ class _MemberVideoState extends State<MemberVideo>
),
),
Obx(
() => _controller.episodicButton.value.uri != null
() => _controller.episodicButton.value.uri !=
null
? Container(
height: 35,
padding: EdgeInsets.only(
left: _controller.count.value != -1
left:
_controller.count.value != -1
? 6
: 0),
child: TextButton.icon(
onPressed: _controller.toViewPlayAll,
onPressed:
_controller.toViewPlayAll,
icon: Icon(
Icons.play_circle_outline_rounded,
size: 16,
@@ -108,8 +114,8 @@ class _MemberVideoState extends State<MemberVideo>
.secondary,
),
label: Text(
_controller
.episodicButton.value.text ??
_controller.episodicButton.value
.text ??
'播放全部',
style: TextStyle(
fontSize: 13,
@@ -130,13 +136,15 @@ class _MemberVideoState extends State<MemberVideo>
icon: Icon(
Icons.sort,
size: 16,
color:
Theme.of(context).colorScheme.secondary,
color: Theme.of(context)
.colorScheme
.secondary,
),
label: Obx(
() => Text(
widget.type == ContributeType.video
? _controller.order.value == 'pubdate'
? _controller.order.value ==
'pubdate'
? '最新发布'
: '最多播放'
: _controller.sort.value == 'desc'
@@ -177,6 +185,7 @@ class _MemberVideoState extends State<MemberVideo>
}
return VideoCardHMemberVideo(
videoItem: loadingState.response[index],
fromViewAid: _controller.fromViewAid,
);
},
childCount: loadingState.response.length,
@@ -185,6 +194,30 @@ class _MemberVideoState extends State<MemberVideo>
),
],
),
),
if (widget.type == ContributeType.video &&
_controller.fromViewAid?.isNotEmpty == true &&
_controller.isLocating != true)
Positioned(
right: 15,
bottom: 15,
child: SafeArea(
top: false,
left: false,
child: FloatingActionButton.extended(
onPressed: () {
_controller
..isLocating = true
..lastAid = _controller.fromViewAid
..currentPage = 0
..loadingState.value = LoadingState.loading()
..queryData();
},
label: const Text('定位至上次观看'),
),
),
),
],
)
: scrollErrorWidget(
callback: _controller.onReload,

View File

@@ -27,7 +27,6 @@ class MemberVideoCtr extends CommonController {
int? seasonId;
int? seriesId;
final int mid;
String? aid;
late RxString order = 'pubdate'.obs;
late RxString sort = 'desc'.obs;
RxInt count = (-1).obs;
@@ -36,18 +35,36 @@ class MemberVideoCtr extends CommonController {
final String? username;
final String? title;
String? firstAid;
String? lastAid;
String? fromViewAid;
bool? isLocating;
bool? isLoadPrevious;
bool? hasPrev;
@override
Future onRefresh() async {
aid = null;
next = null;
currentPage = 0;
isEnd = false;
if (isLocating == true) {
if (hasPrev == true) {
isLoadPrevious = true;
await queryData();
}
} else {
firstAid = null;
lastAid = null;
next = null;
isEnd = false;
currentPage = 0;
await queryData();
}
}
@override
void onInit() {
super.onInit();
if (type == ContributeType.video) {
fromViewAid = Get.parameters['from_view_aid'];
}
currentPage = 0;
queryData();
}
@@ -58,20 +75,31 @@ class MemberVideoCtr extends CommonController {
episodicButton.value = data.episodicButton ?? EpisodicButton();
episodicButton.refresh();
next = data.next;
aid = data.item?.lastOrNull?.param;
if (currentPage == 0 || isLoadPrevious == true) {
hasPrev = data.hasPrev;
}
if (currentPage == 0 || isLoadPrevious != true) {
if ((type == ContributeType.video
? data.hasNext == false
: data.next == 0) ||
data.item.isNullOrEmpty) {
isEnd = true;
}
}
count.value = type == ContributeType.season
? (data.item?.length ?? -1)
: (data.count ?? -1);
if (currentPage != 0 && loadingState.value is Success) {
data.item ??= <Item>[];
if (isLoadPrevious == true) {
data.item!.addAll((loadingState.value as Success).response);
} else {
data.item!.insertAll(0, (loadingState.value as Success).response);
}
}
firstAid = data.item?.firstOrNull?.param;
lastAid = data.item?.lastOrNull?.param;
isLoadPrevious = null;
loadingState.value = LoadingState.success(data.item);
return true;
}
@@ -80,17 +108,27 @@ class MemberVideoCtr extends CommonController {
Future<LoadingState> customGetData() => MemberHttp.spaceArchive(
type: type,
mid: mid,
aid: type == ContributeType.video ? aid : null,
aid: type == ContributeType.video
? isLoadPrevious == true
? firstAid
: lastAid
: null,
order: type == ContributeType.video ? order.value : null,
sort: type == ContributeType.video ? null : sort.value,
sort: type == ContributeType.video
? isLoadPrevious == true
? 'asc'
: null
: sort.value,
pn: type == ContributeType.charging ? currentPage : null,
next: next,
seasonId: seasonId,
seriesId: seriesId,
includeCursor: isLocating == true && currentPage == 0 ? true : null,
);
queryBySort() {
if (type == ContributeType.video) {
isLocating = null;
order.value = order.value == 'pubdate' ? 'click' : 'pubdate';
} else {
sort.value = sort.value == 'desc' ? 'asc' : 'desc';

View File

@@ -35,6 +35,7 @@ class MemberControllerNew extends CommonController
RxInt contributeInitialIndex = 0.obs;
double? top;
bool? hasSeasonOrSeries;
final fromViewAid = Get.parameters['from_view_aid'];
@override
void onInit() {
@@ -95,7 +96,7 @@ class MemberControllerNew extends CommonController
}
if (initialIndex == -1) {
if (data.defaultTab == 'video') {
data.defaultTab = 'dynamic';
data.defaultTab = 'contribute';
}
initialIndex = tab2!.indexWhere((item) {
return item.param == data.defaultTab;
@@ -137,7 +138,10 @@ class MemberControllerNew extends CommonController
}
@override
Future<LoadingState> customGetData() => MemberHttp.space(mid: mid);
Future<LoadingState> customGetData() => MemberHttp.space(
mid: mid,
fromViewAid: fromViewAid,
);
Future blockUser(BuildContext context) async {
if (ownerMid == null) {

View File

@@ -258,7 +258,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// ? videoDetail.owner!.face
// : videoItem['owner'].face;
Get.toNamed(
'/member?mid=$mid',
'/member?mid=$mid&from_view_aid=${videoDetailCtr.oid.value}',
// arguments: {
// 'face': face,
// 'heroTag': memberHeroTag,

View File

@@ -50,8 +50,12 @@ class HorizontalMemberPageController extends CommonController {
@override
bool customHandleResponse(Success response) {
final data = response.response;
if (currentPage == 0 || isLoadPrevious == true) {
hasPrev = data['page']['has_prev'];
}
if (currentPage == 0 || isLoadPrevious != true) {
hasNext = data['page']['has_next'];
}
if (currentPage != 0 && loadingState.value is Success) {
data['items'] ??= [];