diff --git a/lib/common/widgets/video_card_h_member_video.dart b/lib/common/widgets/video_card_h_member_video.dart index 29ddd2fc..32bbb86b 100644 --- a/lib/common/widgets/video_card_h_member_video.dart +++ b/lib/common/widgets/video_card_h_member_video.dart @@ -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! diff --git a/lib/http/member.dart b/lib/http/member.dart index 60a26295..eddd29db 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -153,6 +153,7 @@ class MemberHttp { int? next, int? seasonId, int? seriesId, + includeCursor, }) async { Map 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 space({ int? mid, + dynamic fromViewAid, }) async { Map 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(), }; diff --git a/lib/pages/common/common_controller.dart b/lib/pages/common/common_controller.dart index a1b6e966..d2664ec3 100644 --- a/lib/pages/common/common_controller.dart +++ b/lib/pages/common/common_controller.dart @@ -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.loading().obs; Future customGetData(); diff --git a/lib/pages/member/new/content/member_contribute/content/video/member_video.dart b/lib/pages/member/new/content/member_contribute/content/video/member_video.dart index 280244e4..0e4db0aa 100644 --- a/lib/pages/member/new/content/member_contribute/content/video/member_video.dart +++ b/lib/pages/member/new/content/member_contribute/content/video/member_video.dart @@ -62,129 +62,162 @@ class _MemberVideoState extends State return switch (loadingState) { Loading() => loadingWidget, Success() => (loadingState.response as List?)?.isNotEmpty == true - ? refreshIndicator( - onRefresh: () async { - await _controller.onRefresh(); - }, - child: CustomScrollView( - slivers: [ - SliverPersistentHeader( - pinned: false, - floating: true, - delegate: CustomSliverPersistentHeaderDelegate( - extent: 40, - bgColor: Theme.of(context).colorScheme.surface, - child: SizedBox( - height: 40, - child: Row( - children: [ - const SizedBox(width: 8), - Obx( - () => Padding( - padding: const EdgeInsets.only(left: 6), - child: Text( - _controller.count.value != -1 - ? '共${_controller.count.value}视频' - : '', - style: const TextStyle(fontSize: 13), + ? Stack( + clipBehavior: Clip.none, + children: [ + refreshIndicator( + onRefresh: () async { + await _controller.onRefresh(); + }, + child: CustomScrollView( + slivers: [ + SliverPersistentHeader( + pinned: false, + floating: true, + delegate: CustomSliverPersistentHeaderDelegate( + extent: 40, + bgColor: Theme.of(context).colorScheme.surface, + child: SizedBox( + height: 40, + child: Row( + children: [ + const SizedBox(width: 8), + Obx( + () => Padding( + padding: const EdgeInsets.only(left: 6), + child: Text( + _controller.count.value != -1 + ? '共${_controller.count.value}视频' + : '', + style: const TextStyle(fontSize: 13), + ), + ), ), - ), - ), - Obx( - () => _controller.episodicButton.value.uri != null - ? Container( - height: 35, - padding: EdgeInsets.only( - left: _controller.count.value != -1 - ? 6 - : 0), - child: TextButton.icon( - onPressed: _controller.toViewPlayAll, - icon: Icon( - Icons.play_circle_outline_rounded, - size: 16, - color: Theme.of(context) - .colorScheme - .secondary, - ), - label: Text( - _controller - .episodicButton.value.text ?? - '播放全部', - style: TextStyle( - fontSize: 13, - color: Theme.of(context) - .colorScheme - .secondary, + Obx( + () => _controller.episodicButton.value.uri != + null + ? Container( + height: 35, + padding: EdgeInsets.only( + left: + _controller.count.value != -1 + ? 6 + : 0), + child: TextButton.icon( + onPressed: + _controller.toViewPlayAll, + icon: Icon( + Icons.play_circle_outline_rounded, + size: 16, + color: Theme.of(context) + .colorScheme + .secondary, + ), + label: Text( + _controller.episodicButton.value + .text ?? + '播放全部', + style: TextStyle( + fontSize: 13, + color: Theme.of(context) + .colorScheme + .secondary, + ), + ), ), - ), - ), - ) - : const SizedBox.shrink(), - ), - const Spacer(), - SizedBox( - height: 35, - child: TextButton.icon( - onPressed: _controller.queryBySort, - icon: Icon( - Icons.sort, - size: 16, - color: - Theme.of(context).colorScheme.secondary, + ) + : const SizedBox.shrink(), ), - label: Obx( - () => Text( - widget.type == ContributeType.video - ? _controller.order.value == 'pubdate' - ? '最新发布' - : '最多播放' - : _controller.sort.value == 'desc' - ? '默认' - : '倒序', - style: TextStyle( - fontSize: 13, + const Spacer(), + SizedBox( + height: 35, + child: TextButton.icon( + onPressed: _controller.queryBySort, + icon: Icon( + Icons.sort, + size: 16, color: Theme.of(context) .colorScheme .secondary, ), + label: Obx( + () => Text( + widget.type == ContributeType.video + ? _controller.order.value == + 'pubdate' + ? '最新发布' + : '最多播放' + : _controller.sort.value == 'desc' + ? '默认' + : '倒序', + style: TextStyle( + fontSize: 13, + color: Theme.of(context) + .colorScheme + .secondary, + ), + ), + ), ), ), - ), + const SizedBox(width: 8), + ], ), - const SizedBox(width: 8), - ], + ), ), ), - ), - ), - SliverPadding( - padding: EdgeInsets.only( - top: StyleString.safeSpace - 5, - bottom: MediaQuery.of(context).padding.bottom + 80, - ), - sliver: SliverGrid( - gridDelegate: SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: 2, - maxCrossAxisExtent: Grid.mediumCardWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.2, + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + bottom: MediaQuery.of(context).padding.bottom + 80, + ), + sliver: SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: 2, + maxCrossAxisExtent: Grid.mediumCardWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.2, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (widget.type != ContributeType.season && + index == loadingState.response.length - 1) { + _controller.onLoadMore(); + } + return VideoCardHMemberVideo( + videoItem: loadingState.response[index], + fromViewAid: _controller.fromViewAid, + ); + }, + childCount: loadingState.response.length, + ), + ), ), - delegate: SliverChildBuilderDelegate( - (context, index) { - if (widget.type != ContributeType.season && - index == loadingState.response.length - 1) { - _controller.onLoadMore(); - } - return VideoCardHMemberVideo( - videoItem: loadingState.response[index], - ); + ], + ), + ), + 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(); }, - childCount: loadingState.response.length, + label: const Text('定位至上次观看'), ), ), ), - ], - ), + ], ) : scrollErrorWidget( callback: _controller.onReload, diff --git a/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart b/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart index 05d18470..3b6b941f 100644 --- a/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart +++ b/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart @@ -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; - await queryData(); + 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 ((type == ContributeType.video - ? data.hasNext == false - : data.next == 0) || - data.item.isNullOrEmpty) { - isEnd = true; + 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 ??= []; - data.item!.insertAll(0, (loadingState.value as Success).response); + 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 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'; diff --git a/lib/pages/member/new/controller.dart b/lib/pages/member/new/controller.dart index 70104da1..8b7315c8 100644 --- a/lib/pages/member/new/controller.dart +++ b/lib/pages/member/new/controller.dart @@ -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 customGetData() => MemberHttp.space(mid: mid); + Future customGetData() => MemberHttp.space( + mid: mid, + fromViewAid: fromViewAid, + ); Future blockUser(BuildContext context) async { if (ownerMid == null) { diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index a97cbbce..2221b497 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -258,7 +258,7 @@ class _VideoInfoState extends State 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, diff --git a/lib/pages/video/detail/member/controller.dart b/lib/pages/video/detail/member/controller.dart index 83a5b519..6c4d528c 100644 --- a/lib/pages/video/detail/member/controller.dart +++ b/lib/pages/video/detail/member/controller.dart @@ -50,8 +50,12 @@ class HorizontalMemberPageController extends CommonController { @override bool customHandleResponse(Success response) { final data = response.response; - hasPrev = data['page']['has_prev']; - hasNext = data['page']['has_next']; + 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'] ??= [];