diff --git a/lib/common/widgets/icon_button.dart b/lib/common/widgets/icon_button.dart index 69f4c7d56..81af3504d 100644 --- a/lib/common/widgets/icon_button.dart +++ b/lib/common/widgets/icon_button.dart @@ -6,6 +6,8 @@ Widget iconButton({ required IconData icon, required VoidCallback? onPressed, double size = 36, + Color? bgColor, + Color? iconColor, }) { return SizedBox( width: size, @@ -16,11 +18,12 @@ Widget iconButton({ icon: Icon( icon, size: size / 2, - color: Theme.of(context).colorScheme.onSecondaryContainer, + color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer, ), style: IconButton.styleFrom( padding: EdgeInsets.all(0), - backgroundColor: Theme.of(context).colorScheme.secondaryContainer, + backgroundColor: + bgColor ?? Theme.of(context).colorScheme.secondaryContainer, ), ), ); diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 0d75248b9..09dd9ac5e 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -23,6 +23,7 @@ class VideoCardH extends StatelessWidget { this.showView = true, this.showDanmaku = true, this.showPubdate = false, + this.onTap, }); final dynamic videoItem; final Function()? longPress; @@ -32,6 +33,7 @@ class VideoCardH extends StatelessWidget { final bool showView; final bool showDanmaku; final bool showPubdate; + final GestureTapCallback? onTap; @override Widget build(BuildContext context) { @@ -57,6 +59,10 @@ class VideoCardH extends StatelessWidget { borderRadius: BorderRadius.circular(12), onLongPress: longPress, onTap: () async { + if (onTap != null) { + onTap?.call(); + return; + } if (type == 'ketang') { SmartDialog.showToast('课堂视频暂不支持播放'); return; diff --git a/lib/http/api.dart b/lib/http/api.dart index 720995975..31a8f4ed1 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -92,7 +92,9 @@ class Api { // https://api.bilibili.com/x/v3/fav/resource/deal static const String favVideo = '/x/v3/fav/resource/deal'; - static const String favBangumi = '/x/v3/fav/resource/batch-deal'; + // static const String favBangumi = '/x/v3/fav/resource/batch-deal'; + + static const String delFav = '/x/v3/fav/resource/batch-del'; // 判断视频是否被收藏(双端)GET /// aid diff --git a/lib/http/user.dart b/lib/http/user.dart index f98675f94..eed599eb3 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -158,8 +158,10 @@ class UserHttp { for (var i in res.data['data']['list']) { list.add(HotVideoItemModel.fromJson(i)); } - return LoadingState.success( - {'list': list, 'count': res.data['data']['count']}); + return LoadingState.success({ + 'list': list, + 'count': res.data['data']['count'], + }); } else { return LoadingState.error(res.data['message']); } @@ -239,16 +241,18 @@ class UserHttp { } // 移除已观看 - static Future toViewDel({int? aid}) async { + static Future toViewDel({ + List? aids, + }) async { final Map params = { 'jsonp': 'jsonp', 'csrf': await Request.getCsrf(), + if (aids != null) 'aid': aids.join(',') else 'viewed': true }; - - params[aid != null ? 'aid' : 'viewed'] = aid ?? true; - var res = await Request().post( + dynamic res = await Request().post( Api.toViewDel, - queryParameters: params, + data: params, + options: Options(contentType: Headers.formUrlEncodedContentType), ); if (res.data['code'] == 0) { return {'status': true, 'msg': 'yeah!成功移除'}; diff --git a/lib/http/video.dart b/lib/http/video.dart index 4dd688d26..8f389328b 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -563,45 +563,19 @@ class VideoHttp { } // (取消)收藏 - static Future favVideo({ - required int aid, - String? addIds, - String? delIds, - int? type, - }) async { - var res = await Request().post(Api.favVideo, queryParameters: { - 'rid': aid, - 'type': type ?? 2, - 'add_media_ids': addIds ?? '', - 'del_media_ids': delIds ?? '', - 'csrf': await Request.getCsrf(), - }); - if (res.data['code'] == 0) { - return {'status': true, 'data': res.data['data']}; - } else { - return {'status': false, 'data': [], 'msg': res.data['message']}; - } - } - - // (取消)收藏 bangumi - static Future favBangumi({ - required dynamic epId, - String? addIds, + static Future delFav({ + List? ids, String? delIds, }) async { var res = await Request().post( - Api.favBangumi, + Api.delFav, data: { - 'resources': '$epId:24', - 'add_media_ids': addIds ?? '', - 'del_media_ids': delIds ?? '', + 'resources': ids?.join(','), + 'media_id': delIds, + 'platform': 'web', 'csrf': await Request.getCsrf(), }, - options: Options( - headers: { - 'Content-Type': Headers.formUrlEncodedContentType, - }, - ), + options: Options(contentType: Headers.formUrlEncodedContentType), ); if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; @@ -610,6 +584,58 @@ class VideoHttp { } } + // (取消)收藏 + static Future favVideo({ + int? aid, + String? addIds, + String? delIds, + int? type, + }) async { + var res = await Request().post( + Api.favVideo, + data: { + 'rid': aid, + 'type': type ?? 2, + 'add_media_ids': addIds ?? '', + 'del_media_ids': delIds ?? '', + 'csrf': await Request.getCsrf(), + }, + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + + // (取消)收藏 bangumi + // static Future favBangumi({ + // required dynamic epId, + // String? addIds, + // String? delIds, + // }) async { + // var res = await Request().post( + // Api.favBangumi, + // data: { + // 'resources': '$epId:24', + // 'add_media_ids': addIds ?? '', + // 'del_media_ids': delIds ?? '', + // 'csrf': await Request.getCsrf(), + // }, + // options: Options( + // headers: { + // 'Content-Type': Headers.formUrlEncodedContentType, + // }, + // ), + // ); + // if (res.data['code'] == 0) { + // return {'status': true, 'data': res.data['data']}; + // } else { + // return {'status': false, 'msg': res.data['message']}; + // } + // } + // 查看视频被收藏在哪个文件夹 static Future videoInFolder({ dynamic mid, diff --git a/lib/models/model_hot_video_item.dart b/lib/models/model_hot_video_item.dart index 650165e1c..e9f475605 100644 --- a/lib/models/model_hot_video_item.dart +++ b/lib/models/model_hot_video_item.dart @@ -27,6 +27,7 @@ class HotVideoItemModel { this.seasontype, this.isOgv, this.rcmdReason, + required this.checked, }); int? aid; @@ -54,6 +55,7 @@ class HotVideoItemModel { int? seasontype; bool? isOgv; RcmdReason? rcmdReason; + late bool checked; HotVideoItemModel.fromJson(Map json) { aid = json["aid"]; @@ -83,6 +85,7 @@ class HotVideoItemModel { rcmdReason = json['rcmd_reason'] != '' && json['rcmd_reason'] != null ? RcmdReason.fromJson(json['rcmd_reason']) : null; + checked = false; } } diff --git a/lib/models/user/fav_detail.dart b/lib/models/user/fav_detail.dart index a95817dee..7d031374d 100644 --- a/lib/models/user/fav_detail.dart +++ b/lib/models/user/fav_detail.dart @@ -47,6 +47,7 @@ class FavDetailItemData { this.stat, this.cid, this.epId, + required this.checked, }); int? id; @@ -69,6 +70,7 @@ class FavDetailItemData { Stat? stat; int? cid; String? epId; + late bool checked; FavDetailItemData.fromJson(Map json) { id = json['id']; @@ -93,6 +95,7 @@ class FavDetailItemData { if (json['link'] != null && json['link'].contains('/bangumi')) { epId = resolveEpId(json['link']); } + checked = false; } String resolveEpId(url) { diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart index da9719735..099726fff 100644 --- a/lib/pages/bangumi/introduction/controller.dart +++ b/lib/pages/bangumi/introduction/controller.dart @@ -289,18 +289,20 @@ class BangumiIntroController extends CommonController { } } } catch (_) {} - var result = await VideoHttp.favBangumi( - epId: epId, + var result = await VideoHttp.favVideo( + aid: epId, + type: 24, addIds: addMediaIdsNew.join(','), delIds: delMediaIdsNew.join(','), ); if (result['status']) { addMediaIdsNew = []; delMediaIdsNew = []; - // 重新获取收藏状态 - queryBangumiLikeCoinFav(); SmartDialog.showToast('操作成功'); Get.back(); + Future.delayed(const Duration(milliseconds: 500), () { + queryBangumiLikeCoinFav(); + }); } else { SmartDialog.showToast(result['msg']); } diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 936d2a22d..29f97c801 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:PiliPalaX/common/widgets/refresh_indicator.dart'; import 'package:PiliPalaX/http/loading_state.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; @@ -39,14 +38,6 @@ class _BangumiPageState extends State Get.find().searchBarStream; _bangumiController.scrollController.addListener( () async { - if (_bangumiController.scrollController.position.pixels >= - _bangumiController.scrollController.position.maxScrollExtent - - 200) { - EasyThrottle.throttle('my-throttler', const Duration(seconds: 1), () { - _bangumiController.onLoadMore(); - }); - } - final ScrollDirection direction = _bangumiController.scrollController.position.userScrollDirection; if (direction == ScrollDirection.forward) { @@ -162,6 +153,9 @@ class _BangumiPageState extends State ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { + if (index == loadingState.response.length - 1) { + _bangumiController.onLoadMore(); + } return BangumiCardV( bangumiItem: loadingState.response[index]); }, @@ -188,10 +182,11 @@ class _BangumiPageState extends State width: Grid.maxRowWidth / 2, height: Grid.maxRowWidth * 1, margin: EdgeInsets.only( - left: StyleString.safeSpace, - right: index == loadingState.response.length - 1 - ? StyleString.safeSpace - : 0), + left: StyleString.safeSpace, + right: index == loadingState.response.length - 1 + ? StyleString.safeSpace + : 0, + ), child: BangumiCardV( bangumiItem: loadingState.response[index], ), diff --git a/lib/pages/blacklist/index.dart b/lib/pages/blacklist/index.dart index 9bbee6985..a46d94be6 100644 --- a/lib/pages/blacklist/index.dart +++ b/lib/pages/blacklist/index.dart @@ -5,7 +5,6 @@ import 'package:PiliPalaX/pages/common/common_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/http/black.dart'; import 'package:PiliPalaX/utils/storage.dart'; @@ -20,30 +19,14 @@ class BlackListPage extends StatefulWidget { class _BlackListPageState extends State { final _blackListController = Get.put(BlackListController()); - Box localCache = GStorage.localCache; - - @override - void initState() { - super.initState(); - _blackListController.scrollController.addListener( - () async { - if (_blackListController.scrollController.position.pixels >= - _blackListController.scrollController.position.maxScrollExtent - - 200) { - await _blackListController.onLoadMore(); - } - }, - ); - } @override void dispose() { List list = _blackListController.loadingState.value is Success ? (_blackListController.loadingState.value as Success).response : []; - localCache.put(LocalCacheKey.blackMidsList, + GStorage.localCache.put(LocalCacheKey.blackMidsList, list.isNotEmpty ? list.map((e) => e.mid!).toList() : list); - _blackListController.scrollController.removeListener(() {}); super.dispose(); } @@ -70,6 +53,9 @@ class _BlackListPageState extends State { controller: _blackListController.scrollController, itemCount: loadingState.response.length, itemBuilder: (BuildContext context, int index) { + if (index == loadingState.response.length - 1) { + _blackListController.onLoadMore(); + } return ListTile( onTap: () {}, leading: NetworkImgLayer( @@ -125,6 +111,7 @@ class BlackListController extends CommonController { @override bool customHandleResponse(Success response) { total.value = response.response.total; + isEnd = response.response.list.isEmpty; if (currentPage != 1 && loadingState.value is Success) { response.response.list ?.insertAll(0, (loadingState.value as Success).response); diff --git a/lib/pages/common/common_controller.dart b/lib/pages/common/common_controller.dart index 678b9f31d..cfc6ff9ae 100644 --- a/lib/pages/common/common_controller.dart +++ b/lib/pages/common/common_controller.dart @@ -21,7 +21,7 @@ abstract class CommonController extends GetxController { return false; } - void handleSuccess(List currentList, List dataList) {} + // void handleSuccess(List currentList, List dataList) {} Future queryData([bool isRefresh = true]) async { if (isLoading || (isRefresh.not && isEnd)) return; @@ -29,6 +29,7 @@ abstract class CommonController extends GetxController { LoadingState response = await customGetData(); if (response is Success) { if (!customHandleResponse(response)) { + isEnd = response.response.isEmpty; List currentList = loadingState.value is Success ? (loadingState.value as Success).response : []; @@ -38,7 +39,7 @@ abstract class CommonController extends GetxController { ? LoadingState.success(handleList) : response : LoadingState.success(currentList + response.response); - handleSuccess(currentList, response.response); + // handleSuccess(currentList, response.response); } currentPage++; } else { diff --git a/lib/pages/common/multi_select_controller.dart b/lib/pages/common/multi_select_controller.dart new file mode 100644 index 000000000..6f7dd1e60 --- /dev/null +++ b/lib/pages/common/multi_select_controller.dart @@ -0,0 +1,31 @@ +import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:PiliPalaX/pages/common/common_controller.dart'; +import 'package:get/get.dart'; +import 'package:PiliPalaX/utils/extension.dart'; + +abstract class MultiSelectController extends CommonController { + RxBool enableMultiSelect = false.obs; + RxInt checkedCount = 0.obs; + + onSelect(int index) { + List list = (loadingState.value as Success).response; + list[index].checked = !list[index].checked; + checkedCount.value = list.where((item) => item.checked).length; + loadingState.value = LoadingState.success(list); + if (checkedCount.value == 0) { + enableMultiSelect.value = false; + } + } + + void handleSelect([bool checked = false]) { + List? list = (loadingState.value as Success?)?.response; + if (list.isNullOrEmpty.not) { + loadingState.value = LoadingState.success( + list!.map((item) => item..checked = checked).toList()); + checkedCount.value = checked ? list.length : 0; + } + if (checked.not) { + enableMultiSelect.value = false; + } + } +} diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 7673fc3b1..0e853486b 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -6,7 +6,6 @@ import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/video/detail/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/utils.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; @@ -134,15 +133,6 @@ class _DynamicDetailPageState extends State void scrollListener() { _dynamicDetailController.scrollController.addListener( () { - // 分页加载 - if (_dynamicDetailController.scrollController.position.pixels >= - _dynamicDetailController.scrollController.position.maxScrollExtent - - 300) { - EasyThrottle.throttle('replylist', const Duration(seconds: 2), () { - _dynamicDetailController.onLoadMore(); - }); - } - // 标题 if (_dynamicDetailController.scrollController.offset > 55 && !_visibleTitle) { @@ -377,6 +367,7 @@ class _DynamicDetailPageState extends State delegate: SliverChildBuilderDelegate( (context, index) { if (index == loadingState.response.replies.length) { + _dynamicDetailController.onLoadMore(); return Container( alignment: Alignment.center, padding: EdgeInsets.only( diff --git a/lib/pages/dynamics/tab/controller.dart b/lib/pages/dynamics/tab/controller.dart index 00a480aef..30135b6e5 100644 --- a/lib/pages/dynamics/tab/controller.dart +++ b/lib/pages/dynamics/tab/controller.dart @@ -21,17 +21,18 @@ class DynamicsTabController extends CommonController { } @override - Future onRefresh() async { + Future onRefresh() { if (dynamicsType == 'all') { mainController.setCount(); } offset = ''; - await queryData(); + return super.onRefresh(); } @override bool customHandleResponse(Success response) { offset = response.response.offset; + isEnd = response.response.items.isEmpty; if (currentPage != 1 && loadingState.value is Success) { response.response.items .insertAll(0, (loadingState.value as Success).response); diff --git a/lib/pages/dynamics/tab/view.dart b/lib/pages/dynamics/tab/view.dart index 40f6cf66d..f74428f8e 100644 --- a/lib/pages/dynamics/tab/view.dart +++ b/lib/pages/dynamics/tab/view.dart @@ -47,12 +47,6 @@ class _DynamicsTabPageState extends State tag: widget.dynamicsType, ); _dynamicsTabController.scrollController.addListener(() { - if (_dynamicsTabController.scrollController.position.pixels >= - _dynamicsTabController.scrollController.position.maxScrollExtent - - 200) { - _dynamicsTabController.onLoadMore(); - } - StreamController mainStream = Get.find().bottomBarStream; StreamController searchBarStream = @@ -155,10 +149,14 @@ class _DynamicsTabPageState extends State crossAxisSpacing: StyleString.cardSpace / 2, mainAxisSpacing: StyleString.cardSpace / 2, - lastChildLayoutTypeBuilder: (index) => - index == loadingState.response.length - ? LastChildLayoutType.foot - : LastChildLayoutType.none, + lastChildLayoutTypeBuilder: (index) { + if (index == loadingState.response.length - 1) { + _dynamicsTabController.onLoadMore(); + } + return index == loadingState.response.length + ? LastChildLayoutType.foot + : LastChildLayoutType.none; + }, children: [ if (dynamicsController.tabController.index == 4 && dynamicsController.mid.value != -1) ...[ @@ -186,6 +184,9 @@ class _DynamicsTabPageState extends State sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) { + if (index == loadingState.response.length - 1) { + _dynamicsTabController.onLoadMore(); + } if ((dynamicsController.tabController.index == 4 && dynamicsController.mid.value != -1) || !dynamicsController.tempBannedList.contains( @@ -196,7 +197,7 @@ class _DynamicsTabPageState extends State onRemove: _dynamicsTabController.onRemove, ); } - return const SizedBox(); + return const SizedBox.shrink(); }, childCount: loadingState.response.length, ), diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart index ae8e26dc1..b0f8a89f4 100644 --- a/lib/pages/fan/view.dart +++ b/lib/pages/fan/view.dart @@ -1,7 +1,6 @@ import 'package:PiliPalaX/common/widgets/refresh_indicator.dart'; import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/utils/utils.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/widgets/http_error.dart'; @@ -27,22 +26,6 @@ class _FansPageState extends State { super.initState(); mid = Get.parameters['mid']!; _fansController = Get.put(FansController(), tag: Utils.makeHeroTag(mid)); - _fansController.scrollController.addListener( - () async { - if (_fansController.scrollController.position.pixels >= - _fansController.scrollController.position.maxScrollExtent - 200) { - EasyThrottle.throttle('follow', const Duration(seconds: 1), () { - _fansController.onLoadMore(); - }); - } - }, - ); - } - - @override - void dispose() { - _fansController.scrollController.removeListener(() {}); - super.dispose(); } @override @@ -74,12 +57,16 @@ class _FansPageState extends State { Success() => (loadingState.response as List?)?.isNotEmpty == true ? SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - mainAxisExtent: 56), + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: 56, + ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { + if (index == loadingState.response.length - 1) { + _fansController.onLoadMore(); + } return fanItem(item: loadingState.response[index]); }, childCount: loadingState.response.length, diff --git a/lib/pages/fav/controller.dart b/lib/pages/fav/controller.dart index 48806490a..136f35e86 100644 --- a/lib/pages/fav/controller.dart +++ b/lib/pages/fav/controller.dart @@ -33,6 +33,7 @@ class FavController extends CommonController { @override bool customHandleResponse(Success response) { hasMore = response.response.hasMore; + isEnd = response.response.list.isEmpty; if (currentPage != 1 && loadingState.value is Success) { response.response.list .insertAll(0, (loadingState.value as Success).response); diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 5801848ab..26fd5eba4 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -3,7 +3,6 @@ import 'package:PiliPalaX/common/widgets/refresh_indicator.dart'; import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/fav_search/view.dart'; import 'package:PiliPalaX/utils/utils.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/widgets/http_error.dart'; @@ -23,27 +22,6 @@ class FavPage extends StatefulWidget { class _FavPageState extends State { final FavController _favController = Get.put(FavController()); - @override - void initState() { - super.initState(); - _favController.scrollController.addListener( - () { - if (_favController.scrollController.position.pixels >= - _favController.scrollController.position.maxScrollExtent - 300) { - EasyThrottle.throttle('history', const Duration(seconds: 1), () { - _favController.onLoadMore(); - }); - } - }, - ); - } - - @override - void dispose() { - _favController.scrollController.removeListener(() {}); - super.dispose(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -124,14 +102,18 @@ class _FavPageState extends State { bottom: 80 + MediaQuery.paddingOf(context).bottom), sliver: SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.4, - mainAxisExtent: 0), + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.4, + mainAxisExtent: 0, + ), delegate: SliverChildBuilderDelegate( childCount: loadingState.response.length, (BuildContext context, int index) { + if (index == loadingState.response.length - 1) { + _favController.onLoadMore(); + } String heroTag = Utils.makeHeroTag(loadingState.response[index].fid); return FavItem( @@ -155,7 +137,7 @@ class _FavPageState extends State { _favController.loadingState.value = LoadingState.success(list); } else { - Future.delayed(const Duration(milliseconds: 150), () { + Future.delayed(const Duration(milliseconds: 255), () { _favController.onRefresh(); }); } diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 41f641ec1..30b0dcbeb 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -1,19 +1,20 @@ import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/user.dart'; +import 'package:PiliPalaX/models/user/fav_detail.dart'; import 'package:PiliPalaX/models/user/fav_folder.dart'; -import 'package:PiliPalaX/pages/common/common_controller.dart'; -import 'package:PiliPalaX/utils/extension.dart'; +import 'package:PiliPalaX/pages/common/multi_select_controller.dart'; import 'package:PiliPalaX/utils/storage.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/http/video.dart'; -class FavDetailController extends CommonController { +class FavDetailController extends MultiSelectController { Rx item = FavFolderItemData().obs; int? mediaId; late String heroTag; - RxString loadingText = '加载中...'.obs; RxBool isOwner = false.obs; + RxBool titleCtr = false.obs; @override void onInit() { @@ -27,20 +28,6 @@ class FavDetailController extends CommonController { queryData(); } - @override - Future onRefresh() { - loadingText.value = '加载中...'; - return super.onRefresh(); - } - - @override - Future queryData([bool isRefresh = true]) { - if (isRefresh.not && loadingText.value == '没有更多了') { - return Future.value(); - } - return super.queryData(isRefresh); - } - @override bool customHandleResponse(Success response) { if (currentPage == 1) { @@ -49,28 +36,30 @@ class FavDetailController extends CommonController { GStorage.userInfo.get('userInfoCache')?.mid; } if (currentPage != 1 && loadingState.value is Success) { - response.response.medias - .insertAll(0, (loadingState.value as Success).response); + response.response.medias?.insertAll( + 0, + List.from((loadingState.value as Success).response), + ); } loadingState.value = LoadingState.success(response.response.medias); if (response.response.medias.length >= response.response.info.mediaCount) { - loadingText.value = '没有更多了'; + isEnd = true; } return true; } onCancelFav(int id, int type) async { - var result = await VideoHttp.favVideo( - aid: id, - addIds: '', + var result = await VideoHttp.delFav( + ids: ['$id:$type'], delIds: mediaId.toString(), - type: type, ); if (result['status']) { List dataList = (loadingState.value as Success).response; - dataList = dataList.where((item) => item.id != id).toList(); + dataList.removeWhere((item) => item.id == id); loadingState.value = LoadingState.success(dataList); SmartDialog.showToast('取消收藏'); + } else { + SmartDialog.showToast(result['msg']); } } @@ -80,4 +69,51 @@ class FavDetailController extends CommonController { ps: 20, mediaId: mediaId!, ); + + onDelChecked(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: const Text('确认删除所选收藏吗?'), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () async { + Get.back(); + List list = ((loadingState.value as Success).response as List) + .where((e) => e.checked) + .toList(); + dynamic result = await VideoHttp.delFav( + ids: list.map((item) => '${item.id}:${item.type}').toList(), + delIds: mediaId.toString(), + ); + if (result['status']) { + List dataList = (loadingState.value as Success).response; + Set remainList = dataList.toSet().difference(list.toSet()); + loadingState.value = + LoadingState.success(remainList.toList()); + SmartDialog.showToast('取消收藏'); + checkedCount.value = 0; + enableMultiSelect.value = false; + } else { + SmartDialog.showToast(result['msg']); + } + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + } } diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 3d706ff09..65d994354 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -1,11 +1,9 @@ -import 'dart:async'; - import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/user.dart'; import 'package:PiliPalaX/models/user/fav_folder.dart'; import 'package:PiliPalaX/pages/fav_search/view.dart' show SearchType; +import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/utils.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -28,28 +26,18 @@ class FavDetailPage extends StatefulWidget { class _FavDetailPageState extends State { final FavDetailController _favDetailController = Get.put(FavDetailController()); - late StreamController titleStreamC; // a late String mediaId; @override void initState() { super.initState(); mediaId = Get.parameters['mediaId']!; - titleStreamC = StreamController(); _favDetailController.scrollController.addListener( () { if (_favDetailController.scrollController.offset > 160) { - titleStreamC.add(true); + _favDetailController.titleCtr.value = true; } else if (_favDetailController.scrollController.offset <= 160) { - titleStreamC.add(false); - } - - if (_favDetailController.scrollController.position.pixels >= - _favDetailController.scrollController.position.maxScrollExtent - - 200) { - EasyThrottle.throttle('favDetail', const Duration(seconds: 1), () { - _favDetailController.onLoadMore(); - }); + _favDetailController.titleCtr.value = false; } }, ); @@ -57,200 +45,235 @@ class _FavDetailPageState extends State { @override void dispose() { - titleStreamC.close(); _favDetailController.scrollController.removeListener(() {}); super.dispose(); } @override Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: _favDetailController.scrollController, - slivers: [ - SliverAppBar( - expandedHeight: 220 - MediaQuery.of(context).padding.top, - pinned: true, - title: StreamBuilder( - stream: titleStreamC.stream, - initialData: false, - builder: (context, AsyncSnapshot snapshot) { - return AnimatedOpacity( - opacity: snapshot.data ? 1 : 0, - curve: Curves.easeOut, - duration: const Duration(milliseconds: 500), - child: Row( - children: [ - Obx( - () => Column( + return Obx( + () => PopScope( + canPop: _favDetailController.enableMultiSelect.value.not, + onPopInvokedWithResult: (didPop, result) { + if (_favDetailController.enableMultiSelect.value) { + _favDetailController.handleSelect(); + } + }, + child: Scaffold( + body: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _favDetailController.scrollController, + slivers: [ + SliverAppBar( + leading: _favDetailController.enableMultiSelect.value + ? IconButton( + tooltip: '取消', + onPressed: _favDetailController.handleSelect, + icon: const Icon(Icons.close_outlined), + ) + : null, + expandedHeight: 220 - MediaQuery.of(context).padding.top, + pinned: true, + title: _favDetailController.enableMultiSelect.value + ? Text( + '已选择${_favDetailController.checkedCount.value}项', + ) + : Obx( + () => AnimatedOpacity( + opacity: _favDetailController.titleCtr.value ? 1 : 0, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 500), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _favDetailController.item.value.title ?? '', + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + '共${_favDetailController.item.value.mediaCount}条视频', + style: Theme.of(context).textTheme.labelMedium, + ) + ], + ), + ), + ), + actions: _favDetailController.enableMultiSelect.value + ? [ + TextButton( + onPressed: () => + _favDetailController.handleSelect(true), + child: const Text('全选'), + ), + TextButton( + onPressed: () => + _favDetailController.onDelChecked(context), + child: Text( + '删除', + style: TextStyle( + color: Theme.of(context).colorScheme.error), + ), + ), + const SizedBox(width: 6), + ] + : [ + IconButton( + tooltip: '搜索', + onPressed: () => + Get.toNamed('/favSearch', arguments: { + 'type': 0, + 'mediaId': int.parse(mediaId), + 'searchType': SearchType.fav, + }), + icon: const Icon(Icons.search_outlined), + ), + // IconButton( + // onPressed: () {}, + // icon: const Icon(Icons.more_vert), + // ), + Obx( + () => _favDetailController.isOwner.value + ? PopupMenuButton( + icon: const Icon(Icons.more_vert), + itemBuilder: (context) => [ + PopupMenuItem( + onTap: () { + Get.toNamed( + '/createFav', + parameters: {'mediaId': mediaId}, + )?.then((res) { + if (res is FavFolderItemData) { + _favDetailController.item.value = + res; + } + }); + }, + child: Text('编辑信息'), + ), + if (!Utils.isDefault( + _favDetailController.item.value.attr ?? + 0)) + PopupMenuItem( + onTap: () { + UserHttp.deleteFolder( + mediaIds: [mediaId]).then((data) { + if (data['status']) { + SmartDialog.showToast('删除成功'); + Get.back(result: true); + } else { + SmartDialog.showToast( + data['msg']); + } + }); + }, + child: Text('删除'), + ), + ], + ) + : const SizedBox.shrink(), + ), + const SizedBox(width: 6), + ], + flexibleSpace: FlexibleSpaceBar( + background: Container( + padding: EdgeInsets.only( + top: kTextTabBarHeight + + MediaQuery.of(context).padding.top + + 10, + left: 14, + right: 20), + child: SizedBox( + height: 110, + child: Obx( + () => Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - _favDetailController.item.value.title ?? '', - style: Theme.of(context).textTheme.titleMedium, + Hero( + tag: _favDetailController.heroTag, + child: NetworkImgLayer( + width: 180, + height: 110, + src: _favDetailController.item.value.cover, + ), + ), + const SizedBox(width: 14), + Expanded( + child: SizedBox( + height: 110, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text( + _favDetailController.item.value.title ?? + '', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleMedium! + .fontSize, + fontWeight: FontWeight.bold), + ), + if (_favDetailController + .item.value.intro?.isNotEmpty == + true) + Text( + _favDetailController.item.value.intro ?? + '', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline), + ), + const SizedBox(height: 4), + Text( + _favDetailController + .item.value.upper?.name ?? + '', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline), + ), + const Spacer(), + if (_favDetailController.item.value.attr != + null) + Text( + '共${_favDetailController.item.value.mediaCount}条视频 · ${Utils.isPublicText(_favDetailController.item.value.attr ?? 0)}', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline), + ), + ], + ), + ), ), - Text( - '共${_favDetailController.item.value.mediaCount}条视频', - style: Theme.of(context).textTheme.labelMedium, - ) ], ), ), - ], - ), - ); - }, - ), - actions: [ - IconButton( - tooltip: '搜索', - onPressed: () => Get.toNamed('/favSearch', arguments: { - 'type': 0, - 'mediaId': int.parse(mediaId), - 'searchType': SearchType.fav, - }), - icon: const Icon(Icons.search_outlined), - ), - // IconButton( - // onPressed: () {}, - // icon: const Icon(Icons.more_vert), - // ), - Obx( - () => _favDetailController.isOwner.value - ? PopupMenuButton( - icon: const Icon(Icons.more_vert), - itemBuilder: (context) => [ - PopupMenuItem( - onTap: () { - Get.toNamed( - '/createFav', - parameters: {'mediaId': mediaId}, - )?.then((res) { - if (res is FavFolderItemData) { - _favDetailController.item.value = res; - } - }); - }, - child: Text('编辑信息'), - ), - if (!Utils.isDefault( - _favDetailController.item.value.attr ?? 0)) - PopupMenuItem( - onTap: () { - UserHttp.deleteFolder(mediaIds: [mediaId]) - .then((data) { - if (data['status']) { - SmartDialog.showToast('删除成功'); - Get.back(result: true); - } else { - SmartDialog.showToast(data['msg']); - } - }); - }, - child: Text('删除'), - ), - ], - ) - : const SizedBox.shrink(), - ), - const SizedBox(width: 6), - ], - flexibleSpace: FlexibleSpaceBar( - background: Container( - padding: EdgeInsets.only( - top: kTextTabBarHeight + - MediaQuery.of(context).padding.top + - 10, - left: 14, - right: 20), - child: SizedBox( - height: 110, - child: Obx( - () => Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Hero( - tag: _favDetailController.heroTag, - child: NetworkImgLayer( - width: 180, - height: 110, - src: _favDetailController.item.value.cover, - ), - ), - const SizedBox(width: 14), - Expanded( - child: SizedBox( - height: 110, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text( - _favDetailController.item.value.title ?? '', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleMedium! - .fontSize, - fontWeight: FontWeight.bold), - ), - if (_favDetailController - .item.value.intro?.isNotEmpty == - true) - Text( - _favDetailController.item.value.intro ?? '', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: Theme.of(context) - .colorScheme - .outline), - ), - const SizedBox(height: 4), - Text( - _favDetailController.item.value.upper?.name ?? - '', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: Theme.of(context) - .colorScheme - .outline), - ), - const Spacer(), - if (_favDetailController.item.value.attr != - null) - Text( - '共${_favDetailController.item.value.mediaCount}条视频 · ${Utils.isPublicText(_favDetailController.item.value.attr ?? 0)}', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: Theme.of(context) - .colorScheme - .outline), - ), - ], - ), - ), - ), - ], ), ), ), ), - ), + Obx(() => _buildBody(_favDetailController.loadingState.value)), + ], ), - Obx(() => _buildBody(_favDetailController.loadingState.value)), - ], + ), ), ); } @@ -287,25 +310,103 @@ class _FavDetailPageState extends State { delegate: SliverChildBuilderDelegate( (context, index) { if (index == loadingState.response.length) { + _favDetailController.onLoadMore(); return Container( height: 60, alignment: Alignment.center, - child: Obx( - () => Text( - _favDetailController.loadingText.value, - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - fontSize: 13), + child: Text( + _favDetailController.isEnd.not ? '加载中...' : '没有更多了', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + fontSize: 13, ), ), ); } - return FavVideoCardH( - videoItem: loadingState.response[index], - callFn: () => _favDetailController.onCancelFav( - loadingState.response[index].id, - loadingState.response[index].type, - ), + return Stack( + children: [ + FavVideoCardH( + videoItem: loadingState.response[index], + callFn: () => _favDetailController.onCancelFav( + loadingState.response[index].id, + loadingState.response[index].type, + ), + onTap: + _favDetailController.enableMultiSelect.value.not + ? null + : () { + _favDetailController.onSelect(index); + }, + onLongPress: () { + if (_favDetailController + .enableMultiSelect.value.not) { + _favDetailController.enableMultiSelect.value = + true; + _favDetailController.onSelect(index); + } + }, + ), + Positioned( + top: 0, + left: 12, + bottom: 0, + child: IgnorePointer( + child: LayoutBuilder( + builder: (_, constraints) => AnimatedOpacity( + opacity: loadingState.response[index].checked + ? 1 + : 0, + duration: const Duration(milliseconds: 200), + child: Container( + alignment: Alignment.center, + height: constraints.maxHeight, + width: constraints.maxHeight * + StyleString.aspectRatio, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.black.withOpacity(0.6), + ), + child: SizedBox( + width: 34, + height: 34, + child: AnimatedScale( + scale: + loadingState.response[index].checked + ? 1 + : 0, + duration: + const Duration(milliseconds: 250), + curve: Curves.easeInOut, + child: IconButton( + style: ButtonStyle( + padding: WidgetStateProperty.all( + EdgeInsets.zero), + backgroundColor: + WidgetStateProperty.resolveWith( + (states) { + return Theme.of(context) + .colorScheme + .surface + .withOpacity(0.8); + }, + ), + ), + onPressed: null, + icon: Icon( + Icons.done_all_outlined, + color: Theme.of(context) + .colorScheme + .primary, + ), + ), + ), + ), + ), + ), + ), + ), + ), + ], ); }, childCount: loadingState.response.length + 1, diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 51896bdcc..9d50d5c0d 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -17,12 +17,16 @@ class FavVideoCardH extends StatelessWidget { final dynamic videoItem; final Function? callFn; final int? searchType; + final GestureTapCallback? onTap; + final GestureLongPressCallback? onLongPress; const FavVideoCardH({ super.key, required this.videoItem, this.callFn, this.searchType, + this.onTap, + this.onLongPress, }); @override @@ -32,6 +36,10 @@ class FavVideoCardH extends StatelessWidget { String heroTag = Utils.makeHeroTag(id); return InkWell( onTap: () async { + if (onTap != null) { + onTap!(); + return; + } int? seasonId; String? epId; if (videoItem.ogv != null && @@ -65,6 +73,7 @@ class FavVideoCardH extends StatelessWidget { epId != null ? SearchType.media_bangumi : SearchType.video, }); }, + onLongPress: onLongPress, child: Column( children: [ Padding( @@ -213,14 +222,15 @@ class FavVideoCardH extends StatelessWidget { content: const Text('要取消收藏吗?'), actions: [ TextButton( - onPressed: () => Get.back(), - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), - )), + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline), + ), + ), TextButton( onPressed: () async { await callFn?.call(); diff --git a/lib/pages/fav_search/controller.dart b/lib/pages/fav_search/controller.dart index 26b6cadf8..7fb55f566 100644 --- a/lib/pages/fav_search/controller.dart +++ b/lib/pages/fav_search/controller.dart @@ -2,7 +2,6 @@ import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/member.dart'; import 'package:PiliPalaX/pages/common/common_controller.dart'; import 'package:PiliPalaX/pages/fav_search/view.dart' show SearchType; -import 'package:PiliPalaX/utils/extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -16,12 +15,10 @@ class FavSearchController extends CommonController { RxString searchKeyWord = ''.obs; // 搜索词 String hintText = '搜索'; // 默认 RxString loadingText = '加载中...'.obs; // 加载提示 - bool hasMore = false; int? type; int? mediaId; int? mid; late SearchType searchType; - RxBool enableMultiple = false.obs; int count = 0; // 总数 @@ -49,7 +46,6 @@ class FavSearchController extends CommonController { if (controller.value.text.isEmpty) { return Future.value(); } - hasMore = true; return super.onRefresh(); } @@ -57,14 +53,6 @@ class FavSearchController extends CommonController { searchKeyWord.value = value; } - @override - Future queryData([bool isRefresh = true]) { - if (isRefresh.not && hasMore.not) { - return Future.value(); - } - return super.queryData(isRefresh); - } - @override bool customHandleResponse(Success response) { List currentList = loadingState.value is Success @@ -78,9 +66,9 @@ class FavSearchController extends CommonController { ? response.response.list : currentList + response.response.list); loadingState.value = LoadingState.success(dataList); - hasMore = searchType == SearchType.fav - ? response.response.hasMore - : response.response.list.isNotEmpty; + isEnd = searchType == SearchType.fav + ? !response.response.hasMore + : response.response.list.isEmpty; return true; } diff --git a/lib/pages/fav_search/view.dart b/lib/pages/fav_search/view.dart index 8b8f78698..8f9799149 100644 --- a/lib/pages/fav_search/view.dart +++ b/lib/pages/fav_search/view.dart @@ -4,7 +4,6 @@ import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/follow/widgets/follow_item.dart'; import 'package:PiliPalaX/pages/history/widgets/item.dart'; import 'package:PiliPalaX/utils/grid.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/pages/fav_detail/widget/fav_video_card.dart'; @@ -23,27 +22,6 @@ class FavSearchPage extends StatefulWidget { class _FavSearchPageState extends State { final FavSearchController _favSearchCtr = Get.put(FavSearchController()); - @override - void initState() { - super.initState(); - _favSearchCtr.scrollController.addListener( - () { - if (_favSearchCtr.scrollController.position.pixels >= - _favSearchCtr.scrollController.position.maxScrollExtent - 300) { - EasyThrottle.throttle('fav', const Duration(seconds: 1), () { - _favSearchCtr.onLoadMore(); - }); - } - }, - ); - } - - @override - void dispose() { - _favSearchCtr.scrollController.removeListener(() {}); - super.dispose(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -93,6 +71,7 @@ class _FavSearchPageState extends State { itemCount: loadingState.response.length + 1, itemBuilder: (context, index) { if (index == loadingState.response.length) { + _favSearchCtr.onLoadMore(); return Container( height: MediaQuery.of(context).padding.bottom + 60, padding: EdgeInsets.only( diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index 8ce86f797..d5ea540ef 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -1,5 +1,5 @@ import 'package:PiliPalaX/http/loading_state.dart'; -import 'package:PiliPalaX/pages/common/common_controller.dart'; +import 'package:PiliPalaX/pages/common/multi_select_controller.dart'; import 'package:PiliPalaX/utils/extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -8,12 +8,9 @@ import 'package:PiliPalaX/http/user.dart'; import 'package:PiliPalaX/models/user/history.dart'; import 'package:PiliPalaX/utils/storage.dart'; -class HistoryController extends CommonController { +class HistoryController extends MultiSelectController { RxBool pauseStatus = false.obs; - RxBool enableMultiple = false.obs; - RxInt checkedCount = 0.obs; - int? max; int? viewAt; @@ -38,7 +35,10 @@ class HistoryController extends CommonController { max = data.list?.lastOrNull?.history?.oid; viewAt = data.list?.lastOrNull?.viewAt; if (currentPage != 1 && loadingState.value is Success) { - data.list?.insertAll(0, (loadingState.value as Success).response); + data.list?.insertAll( + 0, + List.from((loadingState.value as Success).response), + ); } loadingState.value = LoadingState.success(data.list); return true; @@ -107,7 +107,7 @@ class HistoryController extends CommonController { SmartDialog.showToast('清空观看历史'); } Get.back(); - loadingState.value = LoadingState.success([]); + loadingState.value = LoadingState.success([]); }, child: const Text('确认清空'), ) @@ -119,7 +119,7 @@ class HistoryController extends CommonController { // 删除某条历史记录 Future delHistory(kid, business) async { - _onDelete(((loadingState.value as Success).response as List) + _onDelete(((loadingState.value as Success).response as List) .where((e) => e.kid == kid) .toList()); } @@ -127,10 +127,9 @@ class HistoryController extends CommonController { // 删除已看历史记录 void onDelHistory() { if (loadingState.value is Success) { - List list = - ((loadingState.value as Success).response as List) - .where((e) => e.progress == -1) - .toList(); + List list = ((loadingState.value as Success).response as List) + .where((e) => e.progress == -1) + .toList(); if (list.isNotEmpty) { _onDelete(list); } else { @@ -139,21 +138,20 @@ class HistoryController extends CommonController { } } - void _onDelete(List result) async { + void _onDelete(List result) async { SmartDialog.showLoading(msg: '请求中'); List kidList = result.map((item) { return '${item.history?.business}_${item.kid}'; }).toList(); dynamic response = await UserHttp.delHistory(kidList); if (response['status']) { - Set remainList = - ((loadingState.value as Success).response as List) - .toSet() - .difference(result.toSet()); + Set remainList = ((loadingState.value as Success).response as List) + .toSet() + .difference(result.toSet()); loadingState.value = LoadingState.success(remainList.toList()); - if (enableMultiple.value) { + if (enableMultiSelect.value) { checkedCount.value = 0; - enableMultiple.value = false; + enableMultiSelect.value = false; } } SmartDialog.dismiss(); @@ -161,8 +159,8 @@ class HistoryController extends CommonController { } // 删除选中的记录 - Future onDelCheckedHistory(BuildContext context) async { - await showDialog( + void onDelCheckedHistory(BuildContext context) { + showDialog( context: context, builder: (context) { return AlertDialog( @@ -181,8 +179,7 @@ class HistoryController extends CommonController { TextButton( onPressed: () async { Get.back(); - _onDelete(((loadingState.value as Success).response - as List) + _onDelete(((loadingState.value as Success).response as List) .where((e) => e.checked) .toList()); }, diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index a294b1390..5a9816ee0 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -1,10 +1,8 @@ import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/common/widgets/refresh_indicator.dart'; import 'package:PiliPalaX/http/loading_state.dart'; -import 'package:PiliPalaX/models/user/history.dart'; import 'package:PiliPalaX/pages/fav_search/view.dart' show SearchType; import 'package:PiliPalaX/utils/extension.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/skeleton/video_card_h.dart'; @@ -24,54 +22,19 @@ class HistoryPage extends StatefulWidget { class _HistoryPageState extends State { final _historyController = Get.put(HistoryController()); - @override - void initState() { - super.initState(); - _historyController.scrollController.addListener( - () { - if (_historyController.scrollController.position.pixels >= - _historyController.scrollController.position.maxScrollExtent - - 300) { - EasyThrottle.throttle('history', const Duration(seconds: 1), () { - _historyController.onLoadMore(); - }); - } - }, - ); - } - - // 选中 - onChoose(index) { - List list = - (_historyController.loadingState.value as Success).response; - list[index].checked = list[index].checked.not; - _historyController.checkedCount.value = - list.where((item) => item.checked).length; - _historyController.loadingState.value = LoadingState.success(list); - if (_historyController.checkedCount.value == 0) { - _historyController.enableMultiple.value = false; - } - } - - @override - void dispose() { - _historyController.scrollController.removeListener(() {}); - super.dispose(); - } - @override Widget build(BuildContext context) { return Obx( () => PopScope( - canPop: _historyController.enableMultiple.value.not, + canPop: _historyController.enableMultiSelect.value.not, onPopInvokedWithResult: (didPop, result) { - if (_historyController.enableMultiple.value) { - _handleSelect(); + if (_historyController.enableMultiSelect.value) { + _historyController.handleSelect(); } }, child: Scaffold( appBar: AppBarWidget( - visible: _historyController.enableMultiple.value, + visible: _historyController.enableMultiSelect.value, child1: AppBar( title: Text('观看记录'), actions: [ @@ -99,7 +62,7 @@ class _HistoryPageState extends State { _historyController.onDelHistory(); break; case 'multiple': - _historyController.enableMultiple.value = true; + _historyController.enableMultiSelect.value = true; break; } }, @@ -135,7 +98,7 @@ class _HistoryPageState extends State { child2: AppBar( leading: IconButton( tooltip: '取消', - onPressed: _handleSelect, + onPressed: _historyController.handleSelect, icon: const Icon(Icons.close_outlined), ), title: Obx( @@ -145,7 +108,7 @@ class _HistoryPageState extends State { ), actions: [ TextButton( - onPressed: () => _handleSelect(true), + onPressed: () => _historyController.handleSelect(true), child: const Text('全选'), ), TextButton( @@ -211,10 +174,13 @@ class _HistoryPageState extends State { ), delegate: SliverChildBuilderDelegate( (context, index) { + if (index == loadingState.response.length - 1) { + _historyController.onLoadMore(); + } return HistoryItem( videoItem: loadingState.response[index], ctr: _historyController, - onChoose: () => onChoose(index), + onChoose: () => _historyController.onSelect(index), ); }, childCount: loadingState.response.length, @@ -230,19 +196,6 @@ class _HistoryPageState extends State { LoadingState() => throw UnimplementedError(), }; } - - void _handleSelect([bool checked = false]) { - List? list = - (_historyController.loadingState.value as Success?)?.response; - if (list.isNullOrEmpty.not) { - _historyController.loadingState.value = LoadingState.success( - list!.map((item) => item..checked = checked).toList()); - _historyController.checkedCount.value = checked ? list.length : 0; - } - if (checked.not) { - _historyController.enableMultiple.value = false; - } - } } class AppBarWidget extends StatelessWidget implements PreferredSizeWidget { diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index ba87a1964..2100f6642 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -33,7 +33,7 @@ class HistoryItem extends StatelessWidget { String heroTag = Utils.makeHeroTag(aid); return InkWell( onTap: () async { - if (ctr!.enableMultiple.value) { + if (ctr!.enableMultiSelect.value) { feedBack(); onChoose?.call(); return; @@ -153,9 +153,9 @@ class HistoryItem extends StatelessWidget { if (ctr is FavSearchController) { return; } - if (!ctr!.enableMultiple.value) { + if (!ctr!.enableMultiSelect.value) { feedBack(); - ctr!.enableMultiple.value = true; + ctr!.enableMultiSelect.value = true; onChoose?.call(); } }, @@ -229,43 +229,41 @@ class HistoryItem extends StatelessWidget { opacity: videoItem.checked ? 1 : 0, duration: const Duration(milliseconds: 200), child: Container( + alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: Colors.black.withOpacity(0.6), ), - child: Center( - child: SizedBox( - width: 34, - height: 34, - child: AnimatedScale( - scale: videoItem.checked ? 1 : 0, - duration: - const Duration(milliseconds: 250), - curve: Curves.easeInOut, - child: IconButton( - tooltip: '取消选择', - style: ButtonStyle( - padding: WidgetStateProperty.all( - EdgeInsets.zero), - backgroundColor: - WidgetStateProperty.resolveWith( - (states) { - return Theme.of(context) - .colorScheme - .surface - .withOpacity(0.8); - }, - ), - ), - onPressed: () { - feedBack(); - onChoose?.call(); - }, - icon: Icon(Icons.done_all_outlined, - color: Theme.of(context) + child: SizedBox( + width: 34, + height: 34, + child: AnimatedScale( + scale: videoItem.checked ? 1 : 0, + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + child: IconButton( + tooltip: '取消选择', + style: ButtonStyle( + padding: WidgetStateProperty.all( + EdgeInsets.zero), + backgroundColor: + WidgetStateProperty.resolveWith( + (states) { + return Theme.of(context) .colorScheme - .primary), + .surface + .withOpacity(0.8); + }, + ), ), + onPressed: () { + feedBack(); + onChoose?.call(); + }, + icon: Icon(Icons.done_all_outlined, + color: Theme.of(context) + .colorScheme + .primary), ), ), ), diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index 04993f5e9..752eae48d 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -38,11 +38,6 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { Get.find().searchBarStream; _hotController.scrollController.addListener( () { - if (_hotController.scrollController.position.pixels >= - _hotController.scrollController.position.maxScrollExtent - 200) { - _hotController.onLoadMore(); - } - final ScrollDirection direction = _hotController.scrollController.position.userScrollDirection; if (direction == ScrollDirection.forward) { @@ -82,17 +77,7 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { MediaQuery.of(context).padding.bottom + 10, ), sliver: Obx( - () => _hotController.loadingState.value is Loading - ? _buildSkeleton() - : _hotController.loadingState.value is Success - ? _buildBody(_hotController.loadingState.value as Success) - : HttpError( - errMsg: _hotController.loadingState.value is Error - ? (_hotController.loadingState.value as Error) - .errMsg - : '没有相关数据', - callback: _hotController.onReload, - ), + () => _buildBody(_hotController.loadingState.value), ), ), ], @@ -126,30 +111,46 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { ); } - Widget _buildBody(Success loadingState) { - return SliverGrid( - gridDelegate: SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.safeSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.4, - mainAxisExtent: 0, - ), - delegate: SliverChildBuilderDelegate( - (context, index) { - return VideoCardH( - videoItem: loadingState.response[index], - showPubdate: true, - longPress: () { - _hotController.popupDialog - .add(_createPopupDialog(loadingState.response[index])); - Overlay.of(context).insert(_hotController.popupDialog.last!); - }, - longPressEnd: _hotController.removePopupDialog, - ); - }, - childCount: loadingState.response.length, - ), - ); + Widget _buildBody(LoadingState loadingState) { + return switch (loadingState) { + Loading() => _buildSkeleton(), + Success() => (loadingState.response as List?)?.isNotEmpty == true + ? SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.4, + mainAxisExtent: 0, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == loadingState.response.length - 1) { + _hotController.onLoadMore(); + } + return VideoCardH( + videoItem: loadingState.response[index], + showPubdate: true, + longPress: () { + _hotController.popupDialog.add( + _createPopupDialog(loadingState.response[index])); + Overlay.of(context) + .insert(_hotController.popupDialog.last!); + }, + longPressEnd: _hotController.removePopupDialog, + ); + }, + childCount: loadingState.response.length, + ), + ) + : HttpError( + callback: _hotController.onReload, + ), + Error() => HttpError( + errMsg: loadingState.errMsg, + callback: _hotController.onReload, + ), + LoadingState() => throw UnimplementedError(), + }; } } diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index 4c8616325..a5f4c9603 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -5,7 +5,6 @@ import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/video/detail/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPalaX/utils/extension.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; @@ -66,14 +65,6 @@ class _HtmlRenderPageState extends State void scrollListener() { _htmlRenderCtr.scrollController.addListener( () { - // 分页加载 - if (_htmlRenderCtr.scrollController.position.pixels >= - _htmlRenderCtr.scrollController.position.maxScrollExtent - 300) { - EasyThrottle.throttle('replylist', const Duration(seconds: 2), () { - _htmlRenderCtr.onLoadMore(); - }); - } - // 标题 // if (scrollController.offset > 55 && !_visibleTitle) { // _visibleTitle = true; @@ -335,6 +326,7 @@ class _HtmlRenderPageState extends State itemCount: loadingState.response.replies.length + 1, itemBuilder: (context, index) { if (index == loadingState.response.replies.length) { + _htmlRenderCtr.onLoadMore(); return Container( alignment: Alignment.center, padding: EdgeInsets.only( diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index 993674886..5efc0615c 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -1,11 +1,12 @@ import 'package:PiliPalaX/http/loading_state.dart'; -import 'package:PiliPalaX/pages/common/common_controller.dart'; +import 'package:PiliPalaX/models/model_hot_video_item.dart'; +import 'package:PiliPalaX/pages/common/multi_select_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/http/user.dart'; -class LaterController extends CommonController { +class LaterController extends MultiSelectController { RxInt count = (-1).obs; @override @@ -20,8 +21,13 @@ class LaterController extends CommonController { count.value = response.response['count']; } if (currentPage != 1 && loadingState.value is Success) { - response.response['list'] - .insertAll(0, (loadingState.value as Success).response); + response.response['list'].insertAll( + 0, + List.from((loadingState.value as Success).response), + ); + } + if (response.response['list'].length >= count.value) { + isEnd = true; } loadingState.value = LoadingState.success(response.response['list']); return true; @@ -45,7 +51,7 @@ class LaterController extends CommonController { ), TextButton( onPressed: () async { - var res = await UserHttp.toViewDel(aid: aid); + var res = await UserHttp.toViewDel(aids: [aid]); if (res['status']) { if (aid != null) { List list = (loadingState.value as Success).response; @@ -103,4 +109,54 @@ class LaterController extends CommonController { @override Future customGetData() => UserHttp.seeYouLater(); + + onDelChecked(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: const Text('确认删除所选稍后再看吗?'), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () async { + Get.back(); + _onDelete(((loadingState.value as Success).response as List) + .where((e) => e.checked) + .toList()); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + } + + void _onDelete(List result) async { + SmartDialog.showLoading(msg: '请求中'); + List aids = result.map((item) => item.aid).toList(); + dynamic res = await UserHttp.toViewDel(aids: aids); + if (res['status']) { + Set remainList = ((loadingState.value as Success).response as List) + .toSet() + .difference(result.toSet()); + loadingState.value = LoadingState.success(remainList.toList()); + if (enableMultiSelect.value) { + checkedCount.value = 0; + enableMultiSelect.value = false; + } + } + SmartDialog.dismiss(); + SmartDialog.showToast(res['msg']); + } } diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index b5356669d..7f884ce8b 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -1,4 +1,7 @@ +import 'package:PiliPalaX/common/widgets/icon_button.dart'; import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:PiliPalaX/pages/history/view.dart' show AppBarWidget; +import 'package:PiliPalaX/utils/extension.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/skeleton/video_card_h.dart'; @@ -21,53 +24,94 @@ class _LaterPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Obx( - () => Text( - '稍后再看${_laterController.count.value == -1 ? '' : ' (${_laterController.count.value})'}', + return Obx( + () => PopScope( + canPop: _laterController.enableMultiSelect.value.not, + onPopInvokedWithResult: (didPop, result) { + if (_laterController.enableMultiSelect.value) { + _laterController.handleSelect(); + } + }, + child: Scaffold( + appBar: AppBarWidget( + visible: _laterController.enableMultiSelect.value, + child1: AppBar( + title: Obx( + () => Text( + '稍后再看${_laterController.count.value == -1 ? '' : ' (${_laterController.count.value})'}', + ), + ), + actions: [ + Obx( + () => _laterController.count.value != -1 + ? TextButton( + onPressed: () => _laterController.toViewDel(context), + child: const Text('移除已看'), + ) + : const SizedBox(), + ), + Obx( + () => _laterController.count.value != -1 + ? IconButton( + tooltip: '一键清空', + onPressed: () => + _laterController.toViewClear(context), + icon: Icon( + Icons.clear_all_outlined, + size: 21, + color: Theme.of(context).colorScheme.primary, + ), + ) + : const SizedBox(), + ), + const SizedBox(width: 8), + ], + ), + child2: AppBar( + leading: IconButton( + tooltip: '取消', + onPressed: _laterController.handleSelect, + icon: const Icon(Icons.close_outlined), + ), + title: Obx( + () => Text( + '已选择${_laterController.checkedCount.value}项', + ), + ), + actions: [ + TextButton( + onPressed: () => _laterController.handleSelect(true), + child: const Text('全选'), + ), + TextButton( + onPressed: () => _laterController.onDelChecked(context), + child: Text( + '移除', + style: + TextStyle(color: Theme.of(context).colorScheme.error), + ), + ), + const SizedBox(width: 6), + ], + ), + ), + body: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + controller: _laterController.scrollController, + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + bottom: MediaQuery.of(context).padding.bottom + 10, + ), + sliver: Obx( + () => _buildBody(_laterController.loadingState.value), + ), + ), + ], ), ), - actions: [ - Obx( - () => _laterController.count.value != -1 - ? TextButton( - onPressed: () => _laterController.toViewDel(context), - child: const Text('移除已看'), - ) - : const SizedBox(), - ), - Obx( - () => _laterController.count.value != -1 - ? IconButton( - tooltip: '一键清空', - onPressed: () => _laterController.toViewClear(context), - icon: Icon( - Icons.clear_all_outlined, - size: 21, - color: Theme.of(context).colorScheme.primary, - ), - ) - : const SizedBox(), - ), - const SizedBox(width: 8), - ], - ), - body: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: _laterController.scrollController, - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - bottom: MediaQuery.of(context).padding.bottom + 10, - ), - sliver: Obx( - () => _buildBody(_laterController.loadingState.value), - ), - ), - ], ), ); } @@ -101,11 +145,99 @@ class _LaterPageState extends State { delegate: SliverChildBuilderDelegate( (context, index) { var videoItem = loadingState.response[index]; - return VideoCardH( - videoItem: videoItem, - source: 'later', - longPress: () => _laterController.toViewDel(context, - aid: videoItem.aid)); + return Stack( + children: [ + VideoCardH( + videoItem: videoItem, + source: 'later', + onTap: _laterController.enableMultiSelect.value.not + ? null + : () { + _laterController.onSelect(index); + }, + longPress: () { + if (_laterController.enableMultiSelect.value.not) { + _laterController.enableMultiSelect.value = true; + _laterController.onSelect(index); + } + }, + ), + Positioned( + top: 0, + left: 0, + bottom: 0, + child: IgnorePointer( + child: LayoutBuilder( + builder: (_, constraints) => AnimatedOpacity( + opacity: videoItem.checked ? 1 : 0, + duration: const Duration(milliseconds: 200), + child: Container( + alignment: Alignment.center, + height: constraints.maxHeight, + width: constraints.maxHeight * + StyleString.aspectRatio, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.black.withOpacity(0.6), + ), + child: SizedBox( + width: 34, + height: 34, + child: AnimatedScale( + scale: videoItem.checked ? 1 : 0, + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + child: IconButton( + tooltip: '取消选择', + style: ButtonStyle( + padding: WidgetStateProperty.all( + EdgeInsets.zero), + backgroundColor: + WidgetStateProperty.resolveWith( + (states) { + return Theme.of(context) + .colorScheme + .surface + .withOpacity(0.8); + }, + ), + ), + onPressed: null, + icon: Icon( + Icons.done_all_outlined, + color: Theme.of(context) + .colorScheme + .primary, + ), + ), + ), + ), + ), + ), + ), + ), + ), + Positioned( + right: 0, + bottom: 0, + child: iconButton( + size: 30, + tooltip: '移除', + context: context, + onPressed: () { + _laterController.toViewDel( + context, + aid: videoItem.aid, + ); + }, + icon: Icons.clear, + iconColor: + Theme.of(context).colorScheme.onSurfaceVariant, + bgColor: Colors.transparent, + ), + ), + ], + ); }, childCount: loadingState.response.length, ), diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 3695746a3..6a340008b 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -78,6 +78,11 @@ class _LiveRoomPageState extends State _updateFontSize(); } }); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.orientation == Orientation.landscape) { + plPlayerController.triggerFullScreen(status: true); + } + }); } void _updateFontSize() async { @@ -337,10 +342,10 @@ class _LiveRoomPageState extends State onPopInvokedWithResult: (bool didPop, Object? result) { if (plPlayerController.isFullScreen.value == true) { plPlayerController.triggerFullScreen(status: false); - if (MediaQuery.of(context).orientation == - Orientation.landscape) { - verticalScreenForTwoSeconds(); - } + // if (MediaQuery.of(context).orientation == + // Orientation.landscape) { + // verticalScreenForTwoSeconds(); + // } } }, child: Listener( diff --git a/lib/pages/member/new/content/member_contribute/content/article/member_article.dart b/lib/pages/member/new/content/member_contribute/content/article/member_article.dart index 909780e49..263f8f2ea 100644 --- a/lib/pages/member/new/content/member_contribute/content/article/member_article.dart +++ b/lib/pages/member/new/content/member_contribute/content/article/member_article.dart @@ -6,7 +6,6 @@ import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/models/space_article/item.dart'; import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart'; import 'package:PiliPalaX/utils/app_scheme.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -55,11 +54,7 @@ class _MemberArticleState extends State itemCount: loadingState.response.length, itemBuilder: (_, index) { if (index == loadingState.response.length - 1) { - EasyThrottle.throttle( - 'memberArticle', const Duration(milliseconds: 500), - () { - _controller.onLoadMore(); - }); + _controller.onLoadMore(); } Item item = loadingState.response[index]; return ListTile( diff --git a/lib/pages/member/new/content/member_contribute/content/bangumi/member_bangumi.dart b/lib/pages/member/new/content/member_contribute/content/bangumi/member_bangumi.dart index 51c41884b..b5d48e7cd 100644 --- a/lib/pages/member/new/content/member_contribute/content/bangumi/member_bangumi.dart +++ b/lib/pages/member/new/content/member_contribute/content/bangumi/member_bangumi.dart @@ -5,7 +5,6 @@ import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/bangumi/widgets/bangumi_card_v_member_home.dart'; import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart'; import 'package:PiliPalaX/utils/grid.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -72,10 +71,7 @@ class _MemberBangumiState extends State delegate: SliverChildBuilderDelegate( (context, index) { if (index == loadingState.response.length - 1) { - EasyThrottle.throttle('memberBangumi', - const Duration(milliseconds: 500), () { - _controller.onLoadMore(); - }); + _controller.onLoadMore(); } return BangumiCardVMemberHome( bangumiItem: loadingState.response[index], diff --git a/lib/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart b/lib/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart index cbc8465f8..799252910 100644 --- a/lib/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart +++ b/lib/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart @@ -28,12 +28,6 @@ class MemberBangumiCtr extends CommonController { isEnd = res.item!.length >= count; } - @override - Future onRefresh() async { - currentPage = 1; - await queryData(); - } - @override bool customHandleResponse(Success response) { Data data = response.response; 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 568dcba08..328a0a6b7 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 @@ -29,7 +29,7 @@ class MemberVideoCtr extends CommonController { aid = null; next = null; currentPage = 0; - await queryData(); + return super.onRefresh(); } @override diff --git a/lib/pages/member/new/content/member_dynamic/member_dynamic.dart b/lib/pages/member/new/content/member_dynamic/member_dynamic.dart index 669e93c6c..adbfd557f 100644 --- a/lib/pages/member/new/content/member_dynamic/member_dynamic.dart +++ b/lib/pages/member/new/content/member_dynamic/member_dynamic.dart @@ -3,7 +3,6 @@ import 'package:PiliPalaX/common/widgets/refresh_indicator.dart'; import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/dynamics/widgets/dynamic_panel_grpc.dart'; import 'package:PiliPalaX/pages/member/new/content/member_dynamic/member_dynamic_ctr.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -44,10 +43,7 @@ class _MemberDynamicState extends State itemCount: loadingState.response.length, itemBuilder: (_, index) { if (index == loadingState.response.length - 1) { - EasyThrottle.throttle( - 'memberDynamic', const Duration(milliseconds: 500), () { - _controller.onLoadMore(); - }); + _controller.onLoadMore(); } return DynamicPanelGrpc( item: loadingState.response[index], diff --git a/lib/pages/member_dynamics/controller.dart b/lib/pages/member_dynamics/controller.dart index ea091f8cb..3dbe3a719 100644 --- a/lib/pages/member_dynamics/controller.dart +++ b/lib/pages/member_dynamics/controller.dart @@ -8,7 +8,6 @@ class MemberDynamicsController extends CommonController { MemberDynamicsController(this.mid); int mid; String offset = ''; - bool hasMore = true; @override void onInit() async { @@ -19,13 +18,12 @@ class MemberDynamicsController extends CommonController { @override Future onRefresh() { offset = ''; - hasMore = true; return super.onRefresh(); } @override Future queryData([bool isRefresh = true]) { - if (isRefresh.not && (hasMore.not || offset == '-1')) { + if (isRefresh.not && (isEnd || offset == '-1')) { return Future.value(); } return super.queryData(isRefresh); @@ -35,7 +33,7 @@ class MemberDynamicsController extends CommonController { bool customHandleResponse(Success response) { DynamicsDataModel data = response.response; offset = data.offset?.isNotEmpty == true ? data.offset! : '-1'; - hasMore = data.hasMore ?? false; + isEnd = !(data.hasMore ?? false); if (currentPage != 1 && loadingState.value is Success) { data.items?.insertAll(0, (loadingState.value as Success).response); } diff --git a/lib/pages/member_dynamics/view.dart b/lib/pages/member_dynamics/view.dart index 228ce9106..f3de3055c 100644 --- a/lib/pages/member_dynamics/view.dart +++ b/lib/pages/member_dynamics/view.dart @@ -1,6 +1,5 @@ import 'package:PiliPalaX/common/widgets/refresh_indicator.dart'; import 'package:PiliPalaX/http/loading_state.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/pages/member_dynamics/index.dart'; @@ -99,10 +98,7 @@ class _MemberDynamicsPageState extends State // LastChildLayoutType.foot, lastChildLayoutTypeBuilder: (index) { if (index == loadingState.response.length - 1) { - EasyThrottle.throttle('member_dynamics', - const Duration(milliseconds: 1000), () { - _memberDynamicController.onLoadMore(); - }); + _memberDynamicController.onLoadMore(); } return index == loadingState.response.length ? LastChildLayoutType.foot @@ -121,10 +117,7 @@ class _MemberDynamicsPageState extends State delegate: SliverChildBuilderDelegate( (context, index) { if (index == loadingState.response.length - 1) { - EasyThrottle.throttle('member_dynamics', - const Duration(milliseconds: 1000), () { - _memberDynamicController.onLoadMore(); - }); + _memberDynamicController.onLoadMore(); } return DynamicPanel( item: loadingState.response[index]); diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index 0f7c34a4a..5fad679c9 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -43,17 +43,8 @@ class RcmdController extends PopupController { } @override - void handleSuccess(List currentList, List dataList) { - if (dataList.length > 1 && currentList.length < 24) { - Future.delayed(const Duration(milliseconds: 300), () { - if (currentList.length < 24) queryData(false); - }); - } - } - - @override - Future onRefresh() async { + Future onRefresh() { currentPage = 0; - await queryData(); + return super.onRefresh(); } } diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 8ed5233bc..2aa28540c 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -6,7 +6,6 @@ import 'package:PiliPalaX/models/common/tab_type.dart'; import 'package:PiliPalaX/pages/common/popup_controller.dart'; import 'package:PiliPalaX/pages/live/controller.dart'; import 'package:PiliPalaX/pages/live/widgets/live_item.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; @@ -48,13 +47,6 @@ class _RcmdPageState extends State Get.find().searchBarStream; _controller.scrollController.addListener( () { - if (_controller.scrollController.position.pixels >= - _controller.scrollController.position.maxScrollExtent - 200) { - EasyThrottle.throttle( - 'my-throttler', const Duration(milliseconds: 200), () { - _controller.onLoadMore(); - }); - } final ScrollDirection direction = _controller.scrollController.position.userScrollDirection; if (direction == ScrollDirection.forward) { @@ -135,6 +127,10 @@ class _RcmdPageState extends State ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { + if (loadingState is Success && + index == loadingState.response.length - 1) { + _controller.onLoadMore(); + } return loadingState is Success ? widget.tabType == TabType.rcmd ? VideoCardV( diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index 539ceb03c..3e705466b 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -36,6 +36,7 @@ class SearchPanelController extends CommonController { searchResultController.count[SearchType.values.indexOf(searchType!)] = response.response.numResults; if (response.response.list != null) { + isEnd = response.response.list.isEmpty; if (currentPage != 1 && loadingState.value is Success) { response.response.list ?.insertAll(0, (loadingState.value as Success).response); @@ -45,6 +46,7 @@ class SearchPanelController extends CommonController { onPushDetail(response.response.list); } } else { + isEnd = true; if (currentPage == 1) { loadingState.value = LoadingState.success([]); } diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index efb785f55..f4016da6e 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -1,7 +1,6 @@ import 'package:PiliPalaX/common/widgets/refresh_indicator.dart'; import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/search_panel/widgets/video_panel.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/skeleton/media_bangumi.dart'; @@ -49,21 +48,6 @@ class _SearchPanelState extends State ), tag: widget.searchType!.type + widget.keyword!, ); - _searchPanelController.scrollController.addListener(() async { - if (_searchPanelController.scrollController.position.pixels >= - _searchPanelController.scrollController.position.maxScrollExtent - - 100) { - EasyThrottle.throttle('history', const Duration(seconds: 1), () { - _searchPanelController.onLoadMore(); - }); - } - }); - } - - @override - void dispose() { - _searchPanelController.scrollController.removeListener(() {}); - super.dispose(); } @override @@ -142,7 +126,7 @@ class _SearchPanelState extends State loadingState, ); default: - return const SizedBox(); + return const SizedBox.shrink(); } } } diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index 0e561ad3f..c6f743abb 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -92,6 +92,9 @@ Widget searchArticlePanel(context, searchPanelCtr, LoadingState loadingState) { ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { + if (index == loadingState.response.length - 1) { + searchPanelCtr.onLoadMore(); + } return InkWell( onTap: () { Get.toNamed('/htmlRender', parameters: { diff --git a/lib/pages/search_panel/widgets/live_panel.dart b/lib/pages/search_panel/widgets/live_panel.dart index 58232d58c..97571a9d8 100644 --- a/lib/pages/search_panel/widgets/live_panel.dart +++ b/lib/pages/search_panel/widgets/live_panel.dart @@ -29,6 +29,9 @@ Widget searchLivePanel(BuildContext context, ctr, LoadingState loadingState) { ), itemCount: loadingState.response.length, itemBuilder: (context, index) { + if (index == loadingState.response.length - 1) { + ctr.onLoadMore(); + } return LiveItem(liveItem: loadingState.response[index]); }, ) diff --git a/lib/pages/search_panel/widgets/media_bangumi_panel.dart b/lib/pages/search_panel/widgets/media_bangumi_panel.dart index 7feb3efc6..e4145aa3f 100644 --- a/lib/pages/search_panel/widgets/media_bangumi_panel.dart +++ b/lib/pages/search_panel/widgets/media_bangumi_panel.dart @@ -31,6 +31,9 @@ Widget searchBangumiPanel(context, ctr, LoadingState loadingState) { ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { + if (index == loadingState.response.length - 1) { + ctr.onLoadMore(); + } var i = loadingState.response[index]; return InkWell( onTap: () { diff --git a/lib/pages/search_panel/widgets/user_panel.dart b/lib/pages/search_panel/widgets/user_panel.dart index 0ece85ef4..1d7df53c2 100644 --- a/lib/pages/search_panel/widgets/user_panel.dart +++ b/lib/pages/search_panel/widgets/user_panel.dart @@ -91,6 +91,9 @@ Widget searchUserPanel(context, searchPanelCtr, LoadingState loadingState) { ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { + if (index == loadingState.response.length - 1) { + searchPanelCtr.onLoadMore(); + } var i = loadingState.response[index]; String heroTag = Utils.makeHeroTag(i!.mid); return InkWell( diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index 16051f960..ed3b286a2 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -102,6 +102,9 @@ Widget searchVideoPanel(context, ctr, LoadingState loadingState) { ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { + if (index == loadingState.response.length - 1) { + ctr.onLoadMore(); + } return VideoCardH( videoItem: loadingState.response[index], showPubdate: true, diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 9bfe31c06..a12d77a40 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -3,7 +3,6 @@ import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/video/detail/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPalaX/utils/extension.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; @@ -211,10 +210,7 @@ class _VideoReplyPanelState extends State (BuildContext context, index) { double bottom = MediaQuery.of(context).padding.bottom; if (index == loadingState.response.replies.length) { - EasyThrottle.throttle( - 'replylist', const Duration(milliseconds: 200), () { - _videoReplyController.onLoadMore(); - }); + _videoReplyController.onLoadMore(); return Container( alignment: Alignment.center, padding: EdgeInsets.only(bottom: bottom), diff --git a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart index 8437342f2..ff6243876 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart @@ -770,20 +770,6 @@ class ReplyItemGrpc extends StatelessWidget { : null, ), ); - } else if (matchStr.startsWith('https://b23.tv/')) { - spanChildren.add( - TextSpan( - text: ' $matchStr ', - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - recognizer: TapGestureRecognizer() - ..onTap = () => Get.toNamed( - '/webviewnew', - parameters: {'url': matchStr}, - ), - ), - ); } else { String appUrlSchema = ''; final bool enableWordRe = GStorage.setting @@ -921,6 +907,20 @@ class ReplyItemGrpc extends StatelessWidget { }, ), ); + } else if (matchStr.startsWith('https://b23.tv/')) { + spanChildren.add( + TextSpan( + text: ' $matchStr ', + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + recognizer: TapGestureRecognizer() + ..onTap = () => Get.toNamed( + '/webviewnew', + parameters: {'url': matchStr}, + ), + ), + ); } else { addPlainTextSpan(matchStr); } diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart index b4adc900f..f4cdf517a 100644 --- a/lib/pages/video/detail/reply_reply/view.dart +++ b/lib/pages/video/detail/reply_reply/view.dart @@ -5,7 +5,6 @@ import 'package:PiliPalaX/pages/video/detail/reply/widgets/reply_item_grpc.dart' import 'package:PiliPalaX/pages/video/detail/reply_new/reply_page.dart'; import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/utils.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/skeleton/video_reply.dart'; @@ -313,10 +312,7 @@ class _VideoReplyReplyPanelState extends State { ), Success() => () { if (index == loadingState.response.length) { - EasyThrottle.throttle( - 'replylist', const Duration(milliseconds: 200), () { - _videoReplyReplyController.onLoadMore(); - }); + _videoReplyReplyController.onLoadMore(); return Container( alignment: Alignment.center, padding: EdgeInsets.only( diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 0ae5e156f..dad854fdc 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -16,6 +16,7 @@ import 'package:PiliPalaX/pages/video/detail/widgets/ai_detail.dart'; import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/id_utils.dart'; import 'package:auto_orientation/auto_orientation.dart'; +import 'package:easy_debounce/easy_throttle.dart'; import 'package:floating/floating.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -1312,18 +1313,20 @@ class _VideoDetailPageState extends State // 展示二级回复 void replyReply(replyItem, id, isTop) { - videoDetailController.childKey.currentState?.showBottomSheet( - (context) => VideoReplyReplyPanel( - id: id, - // rcount: replyItem.rcount, - oid: replyItem.oid.toInt(), - rpid: replyItem.id.toInt(), - firstFloor: replyItem, - replyType: ReplyType.video, - source: 'videoDetail', - isTop: isTop, - ), - ); + EasyThrottle.throttle('replyReply', const Duration(milliseconds: 500), () { + videoDetailController.childKey.currentState?.showBottomSheet( + (context) => VideoReplyReplyPanel( + id: id, + // rcount: replyItem.rcount, + oid: replyItem.oid.toInt(), + rpid: replyItem.id.toInt(), + firstFloor: replyItem, + replyType: ReplyType.video, + source: 'videoDetail', + isTop: isTop, + ), + ); + }); } // ai总结