diff --git a/lib/http/bangumi.dart b/lib/http/bangumi.dart index b753d10b..71916e25 100644 --- a/lib/http/bangumi.dart +++ b/lib/http/bangumi.dart @@ -50,7 +50,7 @@ class BangumiHttp { } } - static Future bangumiList({ + static Future?>> bangumiList({ int? page, int? indexType, }) async { @@ -67,7 +67,7 @@ class BangumiHttp { } } - static Future bangumiFollowList({ + static Future> bangumiFollowList({ required dynamic mid, required int type, required int pn, @@ -80,12 +80,8 @@ class BangumiHttp { 'pn': pn, }); if (res.data['code'] == 0) { - BangumiListDataModel data = - BangumiListDataModel.fromJson(res.data['data']); - if (followStatus != null) { - return LoadingState.success(data.list); - } - return LoadingState.success(data); + return LoadingState.success( + BangumiListDataModel.fromJson(res.data['data'])); } else { return LoadingState.error(res.data['message']); } diff --git a/lib/http/black.dart b/lib/http/black.dart index 1dac007e..2b47673e 100644 --- a/lib/http/black.dart +++ b/lib/http/black.dart @@ -4,7 +4,8 @@ import '../models/user/black.dart'; import 'index.dart'; class BlackHttp { - static Future blackList({required int pn, int? ps}) async { + static Future> blackList( + {required int pn, int? ps}) async { var res = await Request().get(Api.blackLst, queryParameters: { 'pn': pn, 'ps': ps ?? 50, diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index 32e9684f..4d4f758c 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -8,7 +8,7 @@ import '../models/dynamics/up.dart'; import 'index.dart'; class DynamicsHttp { - static Future followDynamic({ + static Future> followDynamic({ String? type, String? offset, int? mid, diff --git a/lib/http/fan.dart b/lib/http/fan.dart index 7424e70e..302c5bef 100644 --- a/lib/http/fan.dart +++ b/lib/http/fan.dart @@ -4,7 +4,7 @@ import '../models/fans/result.dart'; import 'index.dart'; class FanHttp { - static Future fans( + static Future> fans( {int? vmid, int? pn, int? ps, String? orderType}) async { var res = await Request().get(Api.fans, queryParameters: { 'vmid': vmid, diff --git a/lib/http/live.dart b/lib/http/live.dart index 9ee8aa41..9c0e530f 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -10,13 +10,13 @@ import 'api.dart'; import 'init.dart'; class LiveHttp { - static Future liveList( + static Future?>> liveList( {int? vmid, int? pn, int? ps, String? orderType}) async { var res = await Request().get(Api.liveList, queryParameters: {'page': pn, 'page_size': 30, 'platform': 'web'}); if (res.data['code'] == 0) { - List list = res.data['data']['list'] - .map((e) => LiveItemModel.fromJson(e)) + List? list = (res.data['data']?['list'] as List?) + ?.map((e) => LiveItemModel.fromJson(e)) .toList(); return LoadingState.success(list); } else { diff --git a/lib/http/member.dart b/lib/http/member.dart index 7a7172b8..e773c2fc 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -6,6 +6,9 @@ import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/space/data.dart'; +import 'package:PiliPlus/models/space_archive/data.dart' as space_archive; +import 'package:PiliPlus/models/space_article/data.dart' as space_article; +import 'package:PiliPlus/models/space/data.dart' as space_; import 'package:PiliPlus/models/space_fav/space_fav.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' show ContributeType; @@ -62,7 +65,7 @@ class MemberHttp { } } - static Future spaceArticle({ + static Future> spaceArticle({ required int mid, required int page, }) async { @@ -138,13 +141,13 @@ class MemberHttp { }, ); if (res.data['code'] == 0) { - return LoadingState.success(res.data['data']['items_lists']); + return LoadingState.success(res.data['data']?['items_lists']); } else { return LoadingState.error(res.data['message']); } } - static Future spaceArchive({ + static Future> spaceArchive({ required ContributeType type, required int? mid, String? aid, @@ -241,7 +244,7 @@ class MemberHttp { } } - static Future space({ + static Future> space({ int? mid, dynamic fromViewAid, }) async { @@ -391,7 +394,7 @@ class MemberHttp { } // 用户动态 - static Future memberDynamic({ + static Future> memberDynamic({ String? offset, int? mid, }) async { @@ -601,7 +604,8 @@ class MemberHttp { } // 最近投币 - static Future getRecentCoinVideo({required int mid}) async { + static Future?>> getRecentCoinVideo( + {required int mid}) async { Map params = await WbiSign.makSign({ 'mid': mid, 'gaia_source': 'main_web', @@ -618,16 +622,18 @@ class MemberHttp { }, ); if (res.data['code'] == 0) { - return LoadingState.success(res.data['data'] - .map((e) => MemberCoinsDataModel.fromJson(e)) - .toList()); + List? list = (res.data['data'] as List?) + ?.map((e) => MemberCoinsDataModel.fromJson(e)) + .toList(); + return LoadingState.success(list); } else { return LoadingState.error(res.data['message']); } } // 最近点赞 - static Future getRecentLikeVideo({required int mid}) async { + static Future?>> getRecentLikeVideo( + {required int mid}) async { Map params = await WbiSign.makSign({ 'mid': mid, 'gaia_source': 'main_web', @@ -644,9 +650,10 @@ class MemberHttp { }, ); if (res.data['code'] == 0) { - return LoadingState.success(res.data['data']['list'] - .map((e) => MemberCoinsDataModel.fromJson(e)) - .toList()); + List? list = (res.data['data']?['list'] as List?) + ?.map((e) => MemberCoinsDataModel.fromJson(e)) + .toList(); + return LoadingState.success(list); } else { return LoadingState.error(res.data['message']); } diff --git a/lib/http/msg.dart b/lib/http/msg.dart index d38e526c..d89ce2cf 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -17,7 +17,7 @@ import 'api.dart'; import 'init.dart'; class MsgHttp { - static Future msgFeedReplyMe( + static Future> msgFeedReplyMe( {int cursor = -1, int cursorTime = -1}) async { var res = await Request().get(Api.msgFeedReply, queryParameters: { 'id': cursor == -1 ? null : cursor, @@ -34,7 +34,7 @@ class MsgHttp { } } - static Future msgFeedAtMe( + static Future> msgFeedAtMe( {int cursor = -1, int cursorTime = -1}) async { var res = await Request().get(Api.msgFeedAt, queryParameters: { 'id': cursor == -1 ? null : cursor, @@ -51,7 +51,7 @@ class MsgHttp { } } - static Future msgFeedLikeMe( + static Future> msgFeedLikeMe( {int cursor = -1, int cursorTime = -1}) async { var res = await Request().get(Api.msgFeedLike, queryParameters: { 'id': cursor == -1 ? null : cursor, @@ -68,7 +68,7 @@ class MsgHttp { } } - static Future msgFeedNotify( + static Future?>> msgFeedNotify( {int cursor = -1, int pageSize = 20}) async { var res = await Request().get(Api.msgSysNotify, queryParameters: { 'cursor': cursor == -1 ? null : cursor, diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 2d049d1f..75cf22f7 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -124,7 +124,7 @@ class ReplyHttp { } } - static Future replyListGrpc({ + static Future> replyListGrpc({ int type = 1, required int oid, required CursorReq cursor, @@ -387,7 +387,8 @@ class ReplyHttp { } } - static Future getEmoteList({String? business}) async { + static Future?>> getEmoteList( + {String? business}) async { var res = await Request().get(Api.myEmote, queryParameters: { 'business': business ?? 'reply', 'web_location': '333.1245', diff --git a/lib/http/search.dart b/lib/http/search.dart index 5d2a4f06..3d8b205a 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -160,7 +160,8 @@ class SearchHttp { } } - static Future bangumiInfoNew({int? seasonId, int? epId}) async { + static Future> bangumiInfoNew( + {int? seasonId, int? epId}) async { final dynamic res = await Request().get( Api.bangumiInfo, queryParameters: { diff --git a/lib/http/user.dart b/lib/http/user.dart index c592c1b5..a270543c 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -46,7 +46,7 @@ class UserHttp { } // 收藏夹 - static Future userfavFolder({ + static Future> userfavFolder({ required int pn, required int ps, required dynamic mid, @@ -170,7 +170,7 @@ class UserHttp { } } - static Future userFavFolderDetail( + static Future> userFavFolderDetail( {required int mediaId, required int pn, required int ps, @@ -195,7 +195,7 @@ class UserHttp { } // 稍后再看 - static Future seeYouLater() async { + static Future> seeYouLater() async { var res = await Request().get(Api.seeYouLater); if (res.data['code'] == 0) { if (res.data['data']['count'] == 0) { @@ -220,7 +220,7 @@ class UserHttp { } // 观看历史 - static Future historyList({ + static Future> historyList({ required String type, int? max, int? viewAt, @@ -426,7 +426,7 @@ class UserHttp { } // 我的订阅 - static Future userSubFolder({ + static Future?>> userSubFolder({ required int mid, required int pn, required int ps, diff --git a/lib/http/video.dart b/lib/http/video.dart index 60bbe468..f38c91b9 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card; import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/member/article.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; @@ -145,7 +146,7 @@ class VideoHttp { } // 最热视频 - static Future hotVideoList( + static Future>> hotVideoList( {required int pn, required int ps}) async { var res = await Request().get( Api.hotList, @@ -345,15 +346,16 @@ class VideoHttp { } // 相关视频 - static Future relatedVideoList({required String bvid}) async { + static Future?>> relatedVideoList( + {required String bvid}) async { var res = await Request().get(Api.relatedList, queryParameters: {'bvid': bvid}); if (res.data['code'] == 0) { - final items = - (res.data['data'] as List).map((i) => HotVideoItemModel.fromJson(i)); + final items = (res.data['data'] as List?) + ?.map((i) => HotVideoItemModel.fromJson(i)); final list = RecommendFilter.applyFilterToRelatedVideos - ? items.where((i) => !RecommendFilter.filterAll(i)).toList() - : items.toList(); + ? items?.where((i) => !RecommendFilter.filterAll(i)).toList() + : items?.toList(); return LoadingState.success(list); } else { return LoadingState.error(res.data['message']); @@ -1046,7 +1048,8 @@ class VideoHttp { } // 视频排行 - static Future getRankVideoList(int rid) async { + static Future>> getRankVideoList( + int rid) async { var rankApi = "${Api.getRankApi}?rid=$rid&type=all"; var res = await Request().get(rankApi); if (res.data['code'] == 0) { @@ -1094,7 +1097,7 @@ class VideoHttp { } } - static Future noteList({ + static Future?>> noteList({ required int page, }) async { var res = await Request().get( @@ -1106,13 +1109,16 @@ class VideoHttp { }, ); if (res.data['code'] == 0) { - return LoadingState.success(res.data['data']?['list']); + List? list = (res.data['data']?['list'] as List?) + ?.map((e) => FavArticleModel.fromJson(e)) + .toList(); + return LoadingState.success(list); } else { return LoadingState.error(res.data['message']); } } - static Future userNoteList({ + static Future?>> userNoteList({ required int page, }) async { var res = await Request().get( @@ -1124,7 +1130,10 @@ class VideoHttp { }, ); if (res.data['code'] == 0) { - return LoadingState.success(res.data['data']?['list']); + List? list = (res.data['data']?['list'] as List?) + ?.map((e) => FavArticleModel.fromJson(e)) + .toList(); + return LoadingState.success(list); } else { return LoadingState.error(res.data['message']); } diff --git a/lib/models/bangumi/list.dart b/lib/models/bangumi/list.dart index f2896728..66e5aa9a 100644 --- a/lib/models/bangumi/list.dart +++ b/lib/models/bangumi/list.dart @@ -1,3 +1,6 @@ +import 'package:PiliPlus/pages/common/multi_select_controller.dart' + show MultiSelectData; + class BangumiListDataModel { BangumiListDataModel({ this.hasNext, @@ -24,7 +27,7 @@ class BangumiListDataModel { } } -class BangumiListItemModel { +class BangumiListItemModel with MultiSelectData { BangumiListItemModel({ this.badge, this.badgeType, @@ -66,8 +69,6 @@ class BangumiListItemModel { Map? newEp; String? progress; - bool? checked; - BangumiListItemModel.fromJson(Map json) { badge = json['badge'] == '' ? null : json['badge']; badgeType = json['badge_type']; diff --git a/lib/models/live/follow.dart b/lib/models/live/follow.dart index ad466d77..83454fe0 100644 --- a/lib/models/live/follow.dart +++ b/lib/models/live/follow.dart @@ -22,9 +22,8 @@ class LiveFollowingModel { LiveFollowingModel.fromJson(Map json) { count = json['count']; list = (json['list'] as List?) - ?.map((item) => LiveFollowingItemModel.fromJson(item)) - .toList() ?? - []; + ?.map((item) => LiveFollowingItemModel.fromJson(item)) + .toList(); liveCount = json['live_count']; neverLivedCount = json['never_lived_count']; neverLivedFaces = json['never_lived_faces']; diff --git a/lib/models/member/article.dart b/lib/models/member/article.dart new file mode 100644 index 00000000..33152a7e --- /dev/null +++ b/lib/models/member/article.dart @@ -0,0 +1,31 @@ +import 'package:PiliPlus/pages/common/multi_select_controller.dart'; + +class FavArticleModel with MultiSelectData { + FavArticleModel({ + this.webUrl, + this.title, + this.summary, + this.message, + this.pic, + this.cvid, + this.noteId, + }); + + String? webUrl; + String? title; + String? summary; + String? message; + String? pic; + dynamic cvid; + dynamic noteId; + + FavArticleModel.fromJson(Map json) { + webUrl = json['web_url']; + title = json['title']; + summary = json['summary']; + message = json['message']; + pic = json['arc']?['pic']; + cvid = json['cvid']; + noteId = json['note_id']; + } +} diff --git a/lib/models/member/tags.dart b/lib/models/member/tags.dart index 33f7c1f8..84202e75 100644 --- a/lib/models/member/tags.dart +++ b/lib/models/member/tags.dart @@ -1,23 +1,23 @@ -class MemberTagItemModel { +import 'package:PiliPlus/pages/common/multi_select_controller.dart' + show MultiSelectData; + +class MemberTagItemModel with MultiSelectData { MemberTagItemModel({ this.count, this.name, this.tagid, this.tip, - this.checked, }); int? count; String? name; int? tagid; String? tip; - bool? checked; MemberTagItemModel.fromJson(Map json) { count = json['count']; name = json['name']; tagid = json['tagid']; tip = json['tip']; - checked = false; } } diff --git a/lib/models/model_hot_video_item.dart b/lib/models/model_hot_video_item.dart index bf75f07b..30894db6 100644 --- a/lib/models/model_hot_video_item.dart +++ b/lib/models/model_hot_video_item.dart @@ -1,9 +1,11 @@ +import 'package:PiliPlus/pages/common/multi_select_controller.dart' + show MultiSelectData; import 'model_owner.dart'; import 'model_rec_video_item.dart'; import 'model_video.dart'; // 稍后再看, 排行榜等网页返回也使用该类 -class HotVideoItemModel extends BaseRecVideoItemModel { +class HotVideoItemModel extends BaseRecVideoItemModel with MultiSelectData { int? videos; int? tid; String? tname; @@ -16,8 +18,6 @@ class HotVideoItemModel extends BaseRecVideoItemModel { String? pgcLabel; String? redirectUrl; - bool? checked; // 手动设置的 - num? progress; HotVideoItemModel.fromJson(Map json) { diff --git a/lib/models/user/fav_detail.dart b/lib/models/user/fav_detail.dart index 27413b23..ebef6c5b 100644 --- a/lib/models/user/fav_detail.dart +++ b/lib/models/user/fav_detail.dart @@ -1,3 +1,6 @@ +import 'package:PiliPlus/pages/common/multi_select_controller.dart' + show MultiSelectData; + import '../model_owner.dart'; import '../model_video.dart'; import 'fav_folder.dart'; @@ -18,7 +21,7 @@ class FavDetailData { } } -class FavDetailItemData extends BaseVideoItemModel { +class FavDetailItemData extends BaseVideoItemModel with MultiSelectData { int? id; int? type; int? page; @@ -31,7 +34,6 @@ class FavDetailItemData extends BaseVideoItemModel { int? favTime; Map? ogv; String? epId; - bool? checked; FavDetailItemData.fromJson(Map json) { id = json['id']; diff --git a/lib/models/user/history.dart b/lib/models/user/history.dart index 092480f6..d18d3ce1 100644 --- a/lib/models/user/history.dart +++ b/lib/models/user/history.dart @@ -1,3 +1,6 @@ +import 'package:PiliPlus/pages/common/multi_select_controller.dart' + show MultiSelectData; + class HistoryData { HistoryData({ this.cursor, @@ -59,7 +62,7 @@ class HisTabItem { } } -class HisListItem { +class HisListItem with MultiSelectData { late String title; String? longTitle; String? cover; @@ -84,7 +87,6 @@ class HisListItem { int? kid; String? tagName; int? liveStatus; - bool? checked; dynamic isFullScreen; HisListItem.fromJson(Map json) { diff --git a/lib/pages/bangumi/controller.dart b/lib/pages/bangumi/controller.dart index d941cc7f..2f26a1f8 100644 --- a/lib/pages/bangumi/controller.dart +++ b/lib/pages/bangumi/controller.dart @@ -1,14 +1,15 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/bangumi/list.dart'; import 'package:PiliPlus/models/common/tab_type.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/bangumi.dart'; import 'package:PiliPlus/utils/storage.dart'; -class BangumiController extends CommonController { +class BangumiController extends CommonListController< + List?, BangumiListItemModel> { BangumiController({required this.tabType}); final TabType tabType; @@ -75,7 +76,8 @@ class BangumiController extends CommonController { } @override - Future customGetData() => BangumiHttp.bangumiList( + Future?>> customGetData() => + BangumiHttp.bangumiList( page: currentPage, indexType: tabType == TabType.cinema ? 102 : null, // TODO: sort ); diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart index b55edef6..4e32f810 100644 --- a/lib/pages/bangumi/introduction/controller.dart +++ b/lib/pages/bangumi/introduction/controller.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/user.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:PiliPlus/pages/dynamics/repost_dyn_panel.dart'; import 'package:PiliPlus/pages/video/detail/introduction/controller.dart'; import 'package:PiliPlus/pages/video/detail/introduction/pay_coins_page.dart'; @@ -26,7 +26,8 @@ import 'package:PiliPlus/utils/storage.dart'; import 'package:html/parser.dart' as html_parser; import 'package:html/dom.dart' as dom; -class BangumiIntroController extends CommonController { +class BangumiIntroController + extends CommonDataController { // 视频bvid String bvid = Get.parameters['bvid'] ?? ''; var seasonId = Get.parameters['seasonId'] != null @@ -125,14 +126,15 @@ class BangumiIntroController extends CommonController { } @override - bool customHandleResponse(Success response) { - epId ??= response.response.episodes!.first.id; + bool customHandleResponse( + bool isRefresh, Success response) { + epId ??= response.response.episodes?.firstOrNull?.id; loadingState.value = response; return true; } @override - Future customGetData() => + Future> customGetData() => SearchHttp.bangumiInfoNew(seasonId: seasonId, epId: epId); // 获取点赞/投币/收藏状态 @@ -147,40 +149,15 @@ class BangumiIntroController extends CommonController { } } - // 获取点赞状态 - // Future queryHasLikeVideo() async { - // var result = await VideoHttp.hasLikeVideo(bvid: bvid); - // // data num 被点赞标志 0:未点赞 1:已点赞 - // hasLike.value = result["data"] == 1 ? true : false; - // } - - // 获取投币状态 - // Future queryHasCoinVideo() async { - // var result = await VideoHttp.hasCoinVideo(bvid: bvid); - // hasCoin.value = result["data"]['multiply'] == 0 ? false : true; - // } - - // 获取收藏状态 - // Future queryHasFavVideo() async { - // var result = await VideoHttp.hasFavVideo(aid: IdUtils.bv2av(bvid)); - // if (result['status']) { - // hasFav.value = result["data"]['favoured']; - // } else { - // hasFav.value = false; - // } - // } - // (取消)点赞 Future actionLikeVideo() async { var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value); if (result['status']) { SmartDialog.showToast(!hasLike.value ? result['data']['toast'] : '取消赞'); - hasLike.value = !hasLike.value; - dynamic bangumiDetail = (loadingState.value as Success).response; + BangumiInfoModel bangumiDetail = (loadingState.value as Success).response; bangumiDetail.stat!['likes'] = bangumiDetail.stat!['likes'] + (!hasLike.value ? 1 : -1); - loadingState.value = LoadingState.success(bangumiDetail); - hasLike.refresh(); + hasLike.value = !hasLike.value; } else { SmartDialog.showToast(result['msg']); } @@ -194,14 +171,13 @@ class BangumiIntroController extends CommonController { ); if (res['status']) { SmartDialog.showToast('投币成功'); - hasCoin.value = true; - dynamic bangumiDetail = (loadingState.value as Success).response; + BangumiInfoModel bangumiDetail = (loadingState.value as Success).response; bangumiDetail.stat!['coins'] = bangumiDetail.stat!['coins'] + coin; if (selectLike && hasLike.value.not) { hasLike.value = true; bangumiDetail.stat!['likes'] = bangumiDetail.stat!['likes'] + 1; } - loadingState.value = LoadingState.success(bangumiDetail); + hasCoin.value = true; } else { SmartDialog.showToast(res['msg']); } @@ -236,60 +212,6 @@ class BangumiIntroController extends CommonController { }, ), ); - // showDialog( - // context: Get.context!, - // builder: (context) { - // return AlertDialog( - // title: const Text('选择投币个数'), - // contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12), - // content: StatefulBuilder(builder: (context, StateSetter setState) { - // return Column( - // mainAxisSize: MainAxisSize.min, - // children: [ - // RadioListTile( - // value: 1, - // title: const Text('1枚'), - // groupValue: _tempThemeValue, - // onChanged: (value) { - // _tempThemeValue = value!; - // Get.appUpdate(); - // }, - // ), - // RadioListTile( - // value: 2, - // title: const Text('2枚'), - // groupValue: _tempThemeValue, - // onChanged: (value) { - // _tempThemeValue = value!; - // Get.appUpdate(); - // }, - // ), - // ], - // ); - // }), - // actions: [ - // TextButton(onPressed: () => Get.back(), child: const Text('取消')), - // TextButton( - // onPressed: () async { - // var res = await VideoHttp.coinVideo( - // bvid: bvid, multiply: _tempThemeValue); - // if (res['status']) { - // SmartDialog.showToast('投币成功'); - // hasCoin.value = true; - // dynamic bangumiDetail = - // (loadingState.value as Success).response; - // bangumiDetail.stat!['coins'] = - // bangumiDetail.stat!['coins'] + _tempThemeValue; - // loadingState.value = LoadingState.success(bangumiDetail); - // } else { - // SmartDialog.showToast(res['msg']); - // } - // Get.back(); - // }, - // child: const Text('确定')) - // ], - // ); - // }); } // (取消)收藏 bangumi @@ -582,8 +504,11 @@ class BangumiIntroController extends CommonController { Get.find(tag: Get.arguments['heroTag']); PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat; - if ((loadingState.value as Success).response.episodes != null) { - episodes = (loadingState.value as Success).response.episodes!; + if ((loadingState.value as Success).response.episodes != + null) { + episodes = (loadingState.value as Success) + .response + .episodes!; } else { if (playRepeat == PlayRepeat.autoPlayRelated) { return playRelated(); diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 865a3372..54df444a 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -131,8 +131,7 @@ class BangumiInfo extends StatefulWidget { State createState() => _BangumiInfoState(); } -class _BangumiInfoState extends State - with TickerProviderStateMixin { +class _BangumiInfoState extends State { late final BangumiIntroController bangumiIntroController; late final VideoDetailController videoDetailCtr; late final BangumiInfoModel? bangumiItem; diff --git a/lib/pages/bangumi/pgc_index/pgc_index_controller.dart b/lib/pages/bangumi/pgc_index/pgc_index_controller.dart index 8cd704b3..b727c855 100644 --- a/lib/pages/bangumi/pgc_index/pgc_index_controller.dart +++ b/lib/pages/bangumi/pgc_index/pgc_index_controller.dart @@ -1,11 +1,10 @@ import 'package:PiliPlus/http/bangumi.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/bangumi/pgc_index/condition.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:get/get.dart' hide Condition; -class PgcIndexController extends CommonController { +class PgcIndexController extends CommonListController { PgcIndexController(this.indexType); int? indexType; Rx conditionState = LoadingState.loading().obs; @@ -51,20 +50,16 @@ class PgcIndexController extends CommonController { ); @override - bool customHandleResponse(Success response) { + List? getDataList(response) { + return response['list']; + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { if (response.response['has_next'] == null || response.response['has_next'] == 0) { isEnd = true; } - if (isEnd.not && (response.response['list'] as List?).isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - response.response['list'] ??= []; - response.response['list']! - .insertAll(0, (loadingState.value as Success).response); - } - loadingState.value = LoadingState.success(response.response['list']); - return true; + return false; } } diff --git a/lib/pages/bangumi/pgc_index/pgc_index_page.dart b/lib/pages/bangumi/pgc_index/pgc_index_page.dart index a43b4f0b..f2d7b7c8 100644 --- a/lib/pages/bangumi/pgc_index/pgc_index_page.dart +++ b/lib/pages/bangumi/pgc_index/pgc_index_page.dart @@ -207,10 +207,10 @@ class _PgcIndexPageState extends State ], ); - Widget _buildList(LoadingState loadingState) { + Widget _buildList(LoadingState?> loadingState) { return switch (loadingState) { Loading() => HttpError(errMsg: '加载中'), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.cardSpace, @@ -221,13 +221,13 @@ class _PgcIndexPageState extends State ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _ctr.onLoadMore(); } return BangumiCardVPgcIndex( - bangumiItem: loadingState.response[index]); + bangumiItem: loadingState.response![index]); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ) : HttpError(callback: _ctr.onReload), diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 73fbc9a0..065fe681 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/bangumi/list.dart'; import 'package:PiliPlus/models/common/tab_type.dart'; import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_page.dart'; import 'package:PiliPlus/pages/common/common_page.dart'; @@ -230,10 +231,10 @@ class _BangumiPageState extends CommonPageState ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => const SliverToBoxAdapter(), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( // 行间距 @@ -247,13 +248,13 @@ class _BangumiPageState extends CommonPageState ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { controller.onLoadMore(); } return BangumiCardV( - bangumiItem: loadingState.response[index]); + bangumiItem: loadingState.response![index]); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ) : HttpError( diff --git a/lib/pages/blacklist/controller.dart b/lib/pages/blacklist/controller.dart new file mode 100644 index 00000000..13f78834 --- /dev/null +++ b/lib/pages/blacklist/controller.dart @@ -0,0 +1,61 @@ +import 'package:PiliPlus/common/widgets/dialog.dart'; +import 'package:PiliPlus/http/black.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/video.dart'; +import 'package:PiliPlus/models/user/black.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +class BlackListController + extends CommonListController { + int pageSize = 50; + RxInt total = 0.obs; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + List? getDataList(BlackListDataModel response) { + return response.list; + } + + @override + void checkIsEnd(int length) { + if (length >= total.value) { + isEnd = true; + } + } + + @override + bool customHandleResponse( + bool isRefresh, Success response) { + total.value = response.response.total ?? 0; + return false; + } + + Future onRemove(BuildContext context, int index, name, mid) async { + showConfirmDialog( + context: context, + title: '确定将 $name 移出黑名单?', + onConfirm: () async { + var result = await VideoHttp.relationMod(mid: mid, act: 6, reSrc: 11); + if (result['status']) { + List list = (loadingState.value as Success).response; + list.removeAt(index); + total.value -= 1; + loadingState.refresh(); + SmartDialog.showToast('操作成功'); + } + }, + ); + } + + @override + Future> customGetData() => + BlackHttp.blackList(pn: currentPage, ps: pageSize); +} diff --git a/lib/pages/blacklist/index.dart b/lib/pages/blacklist/index.dart index b4f0cb3e..6f8270f4 100644 --- a/lib/pages/blacklist/index.dart +++ b/lib/pages/blacklist/index.dart @@ -1,16 +1,11 @@ -import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/user/black.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/pages/blacklist/controller.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; -import 'package:PiliPlus/http/black.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -50,36 +45,37 @@ class _BlackListPageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), controller: _blackListController.scrollController, - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, itemBuilder: (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _blackListController.onLoadMore(); } + final item = loadingState.response![index]; return ListTile( onTap: () { - Get.toNamed( - '/member?mid=${loadingState.response[index].mid}'); + Get.toNamed('/member?mid=${item.mid}'); }, leading: NetworkImgLayer( width: 45, height: 45, type: 'avatar', - src: loadingState.response[index].face, + src: item.face, ), title: Text( - loadingState.response[index].uname!, + item.uname!, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 14), ), subtitle: Text( - Utils.dateFormat(loadingState.response[index].mtime), + Utils.dateFormat(item.mtime), maxLines: 1, style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -87,10 +83,11 @@ class _BlackListPageState extends State { ), dense: true, trailing: TextButton( - onPressed: () => _blackListController.removeBlack( + onPressed: () => _blackListController.onRemove( context, - loadingState.response[index].uname, - loadingState.response[index].mid, + index, + item.uname, + item.mid, ), child: const Text('移除'), ), @@ -108,56 +105,3 @@ class _BlackListPageState extends State { }; } } - -class BlackListController extends CommonController { - int pageSize = 50; - RxInt total = 0.obs; - - @override - void onInit() { - super.onInit(); - queryData(); - } - - @override - bool customHandleResponse(Success response) { - total.value = response.response.total; - if ((response.response.list as List?).isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - response.response.list ??= []; - response.response.list! - .insertAll(0, (loadingState.value as Success).response); - } - if (isEnd.not && response.response.list.length >= total.value) { - isEnd = true; - } - loadingState.value = LoadingState.success(response.response.list.isNotEmpty - ? response.response.list - : []); - return true; - } - - Future removeBlack(context, name, mid) async { - showConfirmDialog( - context: context, - title: '确定将 $name 移出黑名单?', - onConfirm: () async { - var result = await VideoHttp.relationMod(mid: mid, act: 6, reSrc: 11); - if (result['status']) { - List list = (loadingState.value as Success).response; - list.removeWhere((e) => e.mid == mid); - total.value = total.value - 1; - loadingState.value = - LoadingState.success(list.isNotEmpty ? list : []); - SmartDialog.showToast('操作成功'); - } - }, - ); - } - - @override - Future customGetData() => - BlackHttp.blackList(pn: currentPage, ps: pageSize); -} diff --git a/lib/pages/common/common_controller.dart b/lib/pages/common/common_controller.dart index d2a897cf..63e4e7ed 100644 --- a/lib/pages/common/common_controller.dart +++ b/lib/pages/common/common_controller.dart @@ -27,7 +27,7 @@ abstract mixin class ScrollOrRefreshMixin { } } -abstract class CommonController extends GetxController +abstract class CommonController extends GetxController with ScrollOrRefreshMixin { @override final ScrollController scrollController = ScrollController(); @@ -35,13 +35,13 @@ abstract class CommonController extends GetxController late int currentPage = 1; bool isLoading = false; late bool isEnd = false; - Rx loadingState = LoadingState.loading().obs; + Rx get loadingState; - Future customGetData(); + Future> customGetData(); void handleListResponse(List dataList) {} - bool customHandleResponse(Success response) { + bool customHandleResponse(bool isRefresh, Success response) { return false; } @@ -49,27 +49,36 @@ abstract class CommonController extends GetxController return false; } + List? getDataList(R response) { + return response as List?; + } + + void checkIsEnd(int length) {} + Future queryData([bool isRefresh = true]) async { if (isLoading || (isRefresh.not && isEnd)) return; isLoading = true; - LoadingState response = await customGetData(); - if (response is Success) { - if (!customHandleResponse(response)) { - List? dataList = response.response; + LoadingState response = await customGetData(); + if (response is Success) { + if (!customHandleResponse(isRefresh, response)) { + List? dataList = getDataList(response.response); if (dataList.isNullOrEmpty) { isEnd = true; if (isRefresh) { - loadingState.value = response; + loadingState.value = LoadingState?>.success(dataList); } + isLoading = false; return; } handleListResponse(dataList!); if (isRefresh) { - loadingState.value = LoadingState.success(dataList); + checkIsEnd(dataList.length); + loadingState.value = LoadingState?>.success(dataList); } else if (loadingState.value is Success) { - List currentList = (loadingState.value as Success).response ?? []; - currentList.addAll(dataList); - loadingState.value = LoadingState.success(currentList); + List list = (loadingState.value as Success).response; + list.addAll(dataList); + checkIsEnd(list.length); + loadingState.refresh(); } } currentPage++; diff --git a/lib/pages/common/common_data_controller.dart b/lib/pages/common/common_data_controller.dart new file mode 100644 index 00000000..6c79bdee --- /dev/null +++ b/lib/pages/common/common_data_controller.dart @@ -0,0 +1,8 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:get/get.dart'; + +abstract class CommonDataController extends CommonController { + @override + Rx> loadingState = LoadingState.loading().obs; +} diff --git a/lib/pages/common/common_list_controller.dart b/lib/pages/common/common_list_controller.dart new file mode 100644 index 00000000..ea34b671 --- /dev/null +++ b/lib/pages/common/common_list_controller.dart @@ -0,0 +1,9 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:get/get.dart'; + +abstract class CommonListController extends CommonController { + @override + Rx?>> loadingState = + LoadingState?>.loading().obs; +} diff --git a/lib/pages/common/multi_select_controller.dart b/lib/pages/common/multi_select_controller.dart index d0dc6ac6..5933cacb 100644 --- a/lib/pages/common/multi_select_controller.dart +++ b/lib/pages/common/multi_select_controller.dart @@ -1,32 +1,43 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:get/get.dart'; -import 'package:PiliPlus/utils/extension.dart'; -abstract class MultiSelectController extends CommonController { +mixin class MultiSelectData { + bool? checked; +} + +abstract class MultiSelectController + extends CommonListController { late final RxBool enableMultiSelect = false.obs; late final RxInt checkedCount = 0.obs; + late final allSelected = false.obs; - onSelect(int index) { - List list = (loadingState.value as Success).response; - list[index].checked = !(list[index]?.checked ?? false); + void onSelect(int index, [bool disableSelect = true]) { + List list = (loadingState.value as Success).response; + list[index].checked = !(list[index].checked ?? false); checkedCount.value = list.where((item) => item.checked == true).length; - loadingState.value = LoadingState.success(list); - if (checkedCount.value == 0) { - enableMultiSelect.value = false; + loadingState.refresh(); + if (disableSelect) { + if (checkedCount.value == 0) { + enableMultiSelect.value = false; + } + } else { + allSelected.value = checkedCount.value == list.length; } } - void handleSelect([bool checked = false]) { + void handleSelect([bool checked = false, bool disableSelect = true]) { if (loadingState.value is Success) { - List list = (loadingState.value as Success).response; - if (list.isNotEmpty) { - loadingState.value = LoadingState.success( - list.map((item) => item..checked = checked).toList()); + List? list = (loadingState.value as Success).response; + if (list?.isNotEmpty == true) { + for (T item in list!) { + item.checked = checked; + } + loadingState.refresh(); checkedCount.value = checked ? list.length : 0; } } - if (checked.not) { + if (disableSelect && !checked) { enableMultiSelect.value = false; } } diff --git a/lib/pages/common/reply_controller.dart b/lib/pages/common/reply_controller.dart index 3e20b0da..102eaf1e 100644 --- a/lib/pages/common/reply_controller.dart +++ b/lib/pages/common/reply_controller.dart @@ -6,7 +6,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/models/common/reply_type.dart'; import 'package:PiliPlus/models/video/reply/data.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/video/detail/reply_new/reply_page.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/extension.dart'; @@ -16,12 +16,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/models/common/reply_sort_type.dart'; -import 'package:PiliPlus/models/video/reply/item.dart'; import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:get/get_navigation/src/dialog/dialog_route.dart'; -abstract class ReplyController extends CommonController { +abstract class ReplyController extends CommonListController { String nextOffset = ''; RxInt count = (-1).obs; @@ -31,6 +30,7 @@ abstract class ReplyController extends CommonController { late final bool isLogin = Accounts.main.isLogin; + dynamic upMid; CursorReply? cursor; late Rx mode = Mode.MAIN_LIST_HOT.obs; late bool hasUpTop = false; @@ -59,6 +59,29 @@ abstract class ReplyController extends CommonController { } } + @override + void checkIsEnd(int length) { + if (length >= count.value) { + isEnd = true; + } + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { + MainListReply data = response.response; + cursor = data.cursor; + count.value = data.subjectControl.count.toInt(); + if (isRefresh) { + upMid ??= data.subjectControl.upMid; + hasUpTop = data.hasUpTop(); + if (data.hasUpTop()) { + data.replies.insert(0, data.upTop); + } + } + isEnd = data.cursor.isEnd; + return false; + } + @override Future onRefresh() { cursor = null; @@ -66,29 +89,6 @@ abstract class ReplyController extends CommonController { return super.onRefresh(); } - @override - bool customHandleResponse(Success response) { - MainListReply replies = response.response; - if (cursor == null) { - count.value = replies.subjectControl.count.toInt(); - hasUpTop = replies.hasUpTop(); - if (replies.hasUpTop()) { - replies.replies.insert(0, replies.upTop); - } - } - cursor = replies.cursor; - if (currentPage != 1 && loadingState.value is Success) { - replies.replies - .insertAll(0, (loadingState.value as Success).response.replies); - } - isEnd = replies.replies.isEmpty || - replies.cursor.isEnd || - replies.replies.length >= count.value; - loadingState.value = LoadingState.success(replies); - - return true; - } - // 排序搜索评论 queryBySort() { EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () { @@ -168,16 +168,24 @@ abstract class ReplyController extends CommonController { if (res != null) { savedReplies[key] = null; ReplyInfo replyInfo = Utils.replyCast(res); - MainListReply response = loadingState.value is Success - ? (loadingState.value as Success).response - : MainListReply(); - if (oid != null) { - response.replies.insert(hasUpTop ? 1 : 0, replyInfo); + if (loadingState.value is Success) { + List? list = (loadingState.value as Success).response; + if (list == null) { + loadingState.value = LoadingState.success([replyInfo]); + } else { + if (oid != null) { + list.insert(hasUpTop ? 1 : 0, replyInfo); + } else { + list[index].replies.add(replyInfo); + } + loadingState.refresh(); + } } else { - response.replies[index].replies.add(replyInfo); + loadingState.value = LoadingState.success([replyInfo]); } count.value += 1; - loadingState.value = LoadingState.success(response); + + // check reply if (enableCommAntifraud && context.mounted) { checkReply( context: context, @@ -203,63 +211,36 @@ abstract class ReplyController extends CommonController { ); } - onMDelete(rpid, frpid) { - MainListReply response = (loadingState.value as Success).response; - if (frpid == null) { - response.replies.removeWhere((item) { - return item.id.toInt() == rpid; - }); + void onRemove(int index, int? subIndex) { + List list = (loadingState.value as Success).response; + if (subIndex == null) { + list.removeAt(index); } else { - response.replies.map((item) { - if (item.id == frpid) { - return item..replies.removeWhere((reply) => reply.id.toInt() == rpid); - } else { - return item; - } - }).toList(); + list[index].replies.removeAt(subIndex); } count.value -= 1; - loadingState.value = LoadingState.success(response); + loadingState.refresh(); } void onCheckReply(context, item) { try { - if (item is ReplyInfo) { - checkReply( - context: context, - oid: item.oid.toInt(), - rpid: item.hasRoot() ? item.root.toInt() : null, - replyType: item.type.toInt(), - replyId: item.id.toInt(), - message: item.content.message, - // - root: item.root.toInt(), - parent: item.parent.toInt(), - ctime: item.ctime.toInt(), - pictures: - item.content.pictures.map((item) => item.toProto3Json()).toList(), - mid: item.mid.toInt(), - // - isManual: true, - ); - } else if (item is ReplyItemModel) { - checkReply( - context: context, - oid: item.oid, - rpid: item.root == 0 ? null : item.root, - replyType: item.type!, - replyId: item.rpid!, - message: item.content!.message!, - // - root: item.root, - parent: item.parent, - ctime: item.ctime, - pictures: item.content?.pictures, - mid: item.mid, - // - isManual: true, - ); - } + checkReply( + context: context, + oid: item.oid.toInt(), + rpid: item.hasRoot() ? item.root.toInt() : null, + replyType: item.type.toInt(), + replyId: item.id.toInt(), + message: item.content.message, + // + root: item.root.toInt(), + parent: item.parent.toInt(), + ctime: item.ctime.toInt(), + pictures: + item.content.pictures.map((item) => item.toProto3Json()).toList(), + mid: item.mid.toInt(), + // + isManual: true, + ); } catch (e) { SmartDialog.showToast(e.toString()); } @@ -502,16 +483,14 @@ https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? rep isUpTop: isUpTop, ); if (res['status']) { - final data = (loadingState.value as Success).response; - if (data is MainListReply) { - data.replies[index].replyControl.isUpTop = !isUpTop; - if (!isUpTop && index != 0) { - data.replies[0].replyControl.isUpTop = false; - final item = data.replies.removeAt(index); - data.replies.insert(0, item); - } - loadingState.value = LoadingState.success(data); + List list = (loadingState.value as Success).response; + list[index].replyControl.isUpTop = !isUpTop; + if (!isUpTop && index != 0) { + list[0].replyControl.isUpTop = false; + final item = list.removeAt(index); + list.insert(0, item); } + loadingState.refresh(); SmartDialog.showToast('${isUpTop ? '取消' : ''}置顶成功'); } else { SmartDialog.showToast(res['msg']); diff --git a/lib/pages/dynamics/detail/controller.dart b/lib/pages/dynamics/detail/controller.dart index a043b41a..cee22a79 100644 --- a/lib/pages/dynamics/detail/controller.dart +++ b/lib/pages/dynamics/detail/controller.dart @@ -10,7 +10,7 @@ import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/reply.dart'; import 'package:fixnum/fixnum.dart' as $fixnum; -class DynamicDetailController extends ReplyController { +class DynamicDetailController extends ReplyController { DynamicDetailController(this.oid, this.type); int oid; int type; @@ -47,7 +47,13 @@ class DynamicDetailController extends ReplyController { } @override - Future customGetData() => ReplyHttp.replyListGrpc( + List? getDataList(MainListReply response) { + return response.replies; + } + + @override + Future> customGetData() => + ReplyHttp.replyListGrpc( type: type, oid: oid, cursor: CursorReq( diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 1dee0cc0..d8684d1b 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -775,7 +775,7 @@ class _DynamicDetailPageState extends State ); } - Widget replyList(LoadingState loadingState) { + Widget replyList(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverList( delegate: SliverChildBuilderDelegate( @@ -785,11 +785,11 @@ class _DynamicDetailPageState extends State childCount: 8, ), ), - Success() => (loadingState.response.replies as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverList( delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.replies.length) { + if (index == loadingState.response!.length) { _dynamicDetailController.onLoadMore(); return Container( alignment: Alignment.center, @@ -799,7 +799,7 @@ class _DynamicDetailPageState extends State child: Text( _dynamicDetailController.isEnd.not ? '加载中...' - : loadingState.response.replies.isEmpty + : loadingState.response!.isEmpty ? '还没有评论' : '没有更多了', style: TextStyle( @@ -810,19 +810,20 @@ class _DynamicDetailPageState extends State ); } else { return ReplyItemGrpc( - replyItem: loadingState.response.replies[index], + replyItem: loadingState.response![index], replyLevel: '1', replyReply: (replyItem, id) => replyReply(context, replyItem, id), onReply: () { _dynamicDetailController.onReply( context, - replyItem: loadingState.response.replies[index], + replyItem: loadingState.response![index], index: index, ); }, - onDelete: _dynamicDetailController.onMDelete, - upMid: loadingState.response.subjectControl.upMid, + onDelete: (subIndex) => + _dynamicDetailController.onRemove(index, subIndex), + upMid: _dynamicDetailController.upMid, callback: _getImageCallback, onCheckReply: (item) => _dynamicDetailController.onCheckReply(context, item), @@ -837,7 +838,7 @@ class _DynamicDetailPageState extends State ); } }, - childCount: loadingState.response.replies.length + 1, + childCount: loadingState.response!.length + 1, ), ) : HttpError( diff --git a/lib/pages/dynamics/tab/controller.dart b/lib/pages/dynamics/tab/controller.dart index 1186de27..57679c14 100644 --- a/lib/pages/dynamics/tab/controller.dart +++ b/lib/pages/dynamics/tab/controller.dart @@ -1,15 +1,15 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/main/controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import '../../../http/dynamics.dart'; -class DynamicsTabController extends CommonController { +class DynamicsTabController + extends CommonListController { DynamicsTabController({required this.dynamicsType}); final String dynamicsType; String offset = ''; @@ -32,22 +32,20 @@ class DynamicsTabController extends CommonController { } @override - bool customHandleResponse(Success response) { - offset = response.response.offset; - if ((response.response.items as List?).isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - response.response.items ??= []; - response.response.items! - .insertAll(0, (loadingState.value as Success).response); - } - loadingState.value = LoadingState.success(response.response.items); - return true; + List? getDataList(DynamicsDataModel response) { + return response.items; } @override - Future customGetData() => DynamicsHttp.followDynamic( + bool customHandleResponse( + bool isRefresh, Success response) { + offset = response.response.offset ?? ''; + return false; + } + + @override + Future> customGetData() => + DynamicsHttp.followDynamic( type: dynamicsType == "up" ? "all" : dynamicsType, offset: offset, mid: dynamicsType == "up" ? mid : -1, @@ -56,9 +54,9 @@ class DynamicsTabController extends CommonController { Future onRemove(dynamic dynamicId) async { var res = await MsgHttp.removeDynamic(dynamicId); if (res['status']) { - List list = (loadingState.value as Success).response; + List list = (loadingState.value as Success).response; list.removeWhere((item) => item.idStr == dynamicId); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); SmartDialog.showToast('删除成功'); } else { SmartDialog.showToast(res['msg']); diff --git a/lib/pages/dynamics/tab/view.dart b/lib/pages/dynamics/tab/view.dart index 77273200..ac638d55 100644 --- a/lib/pages/dynamics/tab/view.dart +++ b/lib/pages/dynamics/tab/view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/utils/storage.dart'; @@ -137,10 +138,10 @@ class _DynamicsTabPageState ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => skeleton(), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( bottom: MediaQuery.paddingOf(context).bottom + 80, @@ -153,23 +154,23 @@ class _DynamicsTabPageState // mainAxisSpacing: StyleString.cardSpace / 2, lastChildLayoutTypeBuilder: (index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { controller.onLoadMore(); } - return index == loadingState.response.length + return index == loadingState.response!.length ? LastChildLayoutType.foot : LastChildLayoutType.none; }, children: [ if (dynamicsController.tabController.index == 4 && dynamicsController.mid.value != -1) ...[ - for (var i in loadingState.response) + for (var i in loadingState.response!) DynamicPanel( item: i, onRemove: controller.onRemove, ), ] else ...[ - for (var i in loadingState.response) + for (var i in loadingState.response!) if (!dynamicsController.tempBannedList .contains(i.modules?.moduleAuthor?.mid)) DynamicPanel( @@ -187,23 +188,24 @@ class _DynamicsTabPageState sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == + loadingState.response!.length - 1) { controller.onLoadMore(); } + final item = loadingState.response![index]; if ((dynamicsController.tabController.index == 4 && dynamicsController.mid.value != -1) || !dynamicsController.tempBannedList.contains( - loadingState.response[index].modules - ?.moduleAuthor?.mid)) { + item.modules?.moduleAuthor?.mid)) { return DynamicPanel( - item: loadingState.response[index], + item: item, onRemove: controller.onRemove, ); } return const SizedBox.shrink(); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ), diff --git a/lib/pages/dynamics/widgets/author_panel_grpc.dart b/lib/pages/dynamics/widgets/author_panel_grpc.dart deleted file mode 100644 index 186cf040..00000000 --- a/lib/pages/dynamics/widgets/author_panel_grpc.dart +++ /dev/null @@ -1,293 +0,0 @@ -import 'dart:math'; - -import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart' as dyn; -import 'package:PiliPlus/utils/extension.dart'; -import 'package:PiliPlus/utils/storage.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:PiliPlus/common/widgets/network_img_layer.dart'; -import 'package:PiliPlus/http/user.dart'; -import 'package:PiliPlus/utils/feed_back.dart'; -import 'package:PiliPlus/utils/utils.dart'; - -import '../../../http/constants.dart'; -import '../controller.dart'; - -class AuthorPanelGrpc extends StatelessWidget { - final dyn.DynamicItem item; - final Function? addBannedList; - final String? source; - final Function? onRemove; - const AuthorPanelGrpc({ - super.key, - required this.item, - this.addBannedList, - this.source, - this.onRemove, - }); - - @override - Widget build(BuildContext context) { - String heroTag = Utils.makeHeroTag(item.modules.first.moduleAuthor.mid); - return Row( - children: [ - GestureDetector( - onTap: () { - // 番剧 - // if (item.modules.first.moduleAuthor.type == 'AUTHOR_TYPE_PGC') { - // return; - // } - feedBack(); - Get.toNamed( - '/member?mid=${item.modules.first.moduleAuthor.author.mid}', - arguments: { - 'face': item.modules.first.moduleAuthor.author.face, - 'heroTag': heroTag - }, - ); - }, - child: Hero( - tag: heroTag, - child: NetworkImgLayer( - width: 40, - height: 40, - type: 'avatar', - src: item.modules.first.moduleAuthor.author.face, - ), - ), - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - item.modules.first.moduleAuthor.author.name, - // semanticsLabel: "UP主:${item.modules.moduleAuthor.name}", - style: TextStyle( - color: item.modules.first.moduleAuthor.author.vip.status > - 0 && - item.modules.first.moduleAuthor.author.vip.type == 2 - ? context.vipColor - : Theme.of(context).colorScheme.onSurface, - fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, - ), - ), - ], - ), - // DefaultTextStyle.merge( - // style: TextStyle( - // color: Theme.of(context).colorScheme.outline, - // fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, - // ), - // child: Row( - // children: [ - // Text(item.modules.first.moduleAuthor.pubTime), - // if (item.modules.first.moduleAuthor.pubTime != '' && - // item.modules.first.moduleAuthor.pubAction != '') - // const Text(' '), - // Text(item.modules.first.moduleAuthor.pubAction), - // ], - // ), - // ) - ], - ), - const Spacer(), - // if (source != 'detail' && item.modules.first?.moduleTag?.text != null) - // Container( - // padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), - // decoration: BoxDecoration( - // color: Theme.of(context).colorScheme.surface, - // borderRadius: const BorderRadius.all(Radius.circular(4)), - // border: Border.all( - // width: 1.25, - // color: Theme.of(context).colorScheme.primary, - // ), - // ), - // child: Text( - // item.modules.first.moduleTag.text, - // style: TextStyle( - // height: 1, - // fontSize: 12, - // color: Theme.of(context).colorScheme.primary, - // ), - // strutStyle: const StrutStyle( - // leading: 0, - // height: 1, - // fontSize: 12, - // ), - // ), - // ), - SizedBox( - width: 32, - height: 32, - child: IconButton( - tooltip: '更多', - style: ButtonStyle( - padding: WidgetStateProperty.all(EdgeInsets.zero), - ), - onPressed: () { - showModalBottomSheet( - context: context, - useSafeArea: true, - isScrollControlled: true, - constraints: BoxConstraints( - maxWidth: min(640, min(Get.width, Get.height)), - ), - builder: (context) { - return MorePanel( - item: item, - onRemove: onRemove, - ); - }, - ); - }, - icon: const Icon(Icons.more_vert_outlined, size: 18), - ), - ), - ], - ); - } -} - -class MorePanel extends StatelessWidget { - final dynamic item; - final Function? onRemove; - const MorePanel({ - super.key, - required this.item, - this.onRemove, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: Get.back, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(28), - topRight: Radius.circular(28), - ), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.outline, - borderRadius: const BorderRadius.all(Radius.circular(3))), - ), - ), - ), - ), - if (item.type == 'DYNAMIC_TYPE_AV') - ListTile( - onTap: () async { - try { - String bvid = item.modules.moduleDynamic.major.archive.bvid; - var res = await UserHttp.toViewLater(bvid: bvid); - SmartDialog.showToast(res['msg']); - Get.back(); - } catch (err) { - SmartDialog.showToast('出错了:${err.toString()}'); - } - }, - minLeadingWidth: 0, - // dense: true, - leading: const Icon(Icons.watch_later_outlined, size: 19), - title: Text( - '稍后再看', - style: Theme.of(context).textTheme.titleSmall, - ), - ), - ListTile( - title: Text( - '分享动态', - style: Theme.of(context).textTheme.titleSmall, - ), - leading: const Icon(Icons.share_outlined, size: 19), - onTap: () { - Get.back(); - Utils.shareText( - '${HttpString.dynamicShareBaseUrl}/${item.idStr}'); - }, - minLeadingWidth: 0, - ), - ListTile( - title: Text( - '临时屏蔽:${item.modules.moduleAuthor.name}', - style: Theme.of(context).textTheme.titleSmall, - ), - leading: const Icon(Icons.visibility_off_outlined, size: 19), - onTap: () { - Get.back(); - DynamicsController dynamicsController = - Get.find(); - dynamicsController.tempBannedList - .add(item.modules.moduleAuthor.mid); - SmartDialog.showToast( - '已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复'); - }, - minLeadingWidth: 0, - ), - if (item.modules.moduleAuthor.mid == Accounts.main.mid) - ListTile( - onTap: () async { - Get.back(); - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('确定删除该动态?'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () { - Get.back(); - onRemove?.call(item.idStr); - }, - child: const Text('确定'), - ), - ], - )); - }, - minLeadingWidth: 0, - leading: Icon(Icons.delete_outline, - color: Theme.of(context).colorScheme.error, size: 19), - title: Text('删除', - style: Theme.of(context) - .textTheme - .titleSmall! - .copyWith(color: Theme.of(context).colorScheme.error)), - ), - const Divider(thickness: 0.1, height: 1), - ListTile( - onTap: () => Get.back(), - minLeadingWidth: 0, - dense: true, - title: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - textAlign: TextAlign.center, - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/dynamics/widgets/content_panel_grpc.dart b/lib/pages/dynamics/widgets/content_panel_grpc.dart deleted file mode 100644 index 00a3e7da..00000000 --- a/lib/pages/dynamics/widgets/content_panel_grpc.dart +++ /dev/null @@ -1,82 +0,0 @@ -// 内容 -import 'package:PiliPlus/common/widgets/image_view.dart'; -import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart'; -import 'package:flutter/material.dart'; - -import 'rich_node_panel.dart'; - -class ContentGrpc extends StatelessWidget { - final DynamicItem item; - final String? source; - - const ContentGrpc({ - super.key, - required this.item, - this.source, - }); - - InlineSpan picsNodes() { - return WidgetSpan( - child: LayoutBuilder( - builder: (context, constraints) => imageView( - constraints.maxWidth, - item.modules.first.moduleDynamic.dynDraw.items - .map( - (item) => ImageModel( - width: item.width, - height: item.height, - url: item.src, - ), - ) - .toList(), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - TextStyle authorStyle = - TextStyle(color: Theme.of(context).colorScheme.primary); - InlineSpan? richNodes = richNode(item, context); - return Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(12, 0, 12, 6), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (item.modules.first.moduleDynamic.hasDynTopicSet()) ...[ - GestureDetector( - child: Text( - '#${item.modules.first.moduleDynamic.dynTopicSet.topics.first.topicName}', - style: authorStyle, - ), - ), - ], - if (richNodes != null) - IgnorePointer( - // 禁用SelectableRegion的触摸交互功能 - ignoring: source == 'detail' ? false : true, - child: SelectableRegion( - magnifierConfiguration: const TextMagnifierConfiguration(), - focusNode: FocusNode(), - selectionControls: MaterialTextSelectionControls(), - child: Text.rich( - /// fix 默认20px高度 - style: const TextStyle(height: 0), - richNodes, - maxLines: source == 'detail' ? null : 6, - overflow: source == 'detail' ? null : TextOverflow.ellipsis, - ), - ), - ), - if (item.modules.first.moduleDynamic.hasDynDraw()) - Text.rich( - picsNodes(), - // semanticsLabel: '动态图片', - ), - ], - ), - ); - } -} diff --git a/lib/pages/dynamics/widgets/dynamic_panel_grpc.dart b/lib/pages/dynamics/widgets/dynamic_panel_grpc.dart deleted file mode 100644 index bdf44430..00000000 --- a/lib/pages/dynamics/widgets/dynamic_panel_grpc.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart'; -import 'package:PiliPlus/pages/dynamics/widgets/author_panel_grpc.dart'; -import 'package:PiliPlus/pages/dynamics/widgets/content_panel_grpc.dart'; -import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart'; - -class DynamicPanelGrpc extends StatelessWidget { - final DynamicItem item; - final String? source; - final Function? onRemove; - - const DynamicPanelGrpc({ - required this.item, - this.source, - this.onRemove, - super.key, - }); - - @override - Widget build(BuildContext context) { - return Container( - padding: source == 'detail' - ? const EdgeInsets.only(bottom: 12) - : EdgeInsets.zero, - // decoration: BoxDecoration( - // border: Border( - // bottom: BorderSide( - // width: 8, - // color: Theme.of(context).dividerColor.withOpacity(0.05), - // ), - // ), - // ), - child: Material( - elevation: 0, - clipBehavior: Clip.hardEdge, - color: Theme.of(context).cardColor.withOpacity(0.5), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), - child: InkWell( - onTap: source == 'detail' && item.itemType == DynamicType.draw - ? null - : () => Utils.pushDynDetail(item, 1), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 12, 6), - child: AuthorPanelGrpc( - item: item, - source: source, - onRemove: onRemove, - ), - ), - ContentGrpc(item: item, source: source), - // forWard(item, context, _dynamicsController, source), - const SizedBox(height: 2), - // if (source == null) ActionPanel(item: item), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pages/emote/controller.dart b/lib/pages/emote/controller.dart index b34203c4..583e65c5 100644 --- a/lib/pages/emote/controller.dart +++ b/lib/pages/emote/controller.dart @@ -1,11 +1,13 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/models/video/reply/emote.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../http/reply.dart'; -class EmotePanelController extends CommonController +class EmotePanelController + extends CommonListController?, Packages> with GetTickerProviderStateMixin { TabController? tabController; @@ -16,15 +18,17 @@ class EmotePanelController extends CommonController } @override - bool customHandleResponse(Success response) { - tabController = - TabController(length: response.response.length, vsync: this); + bool customHandleResponse(bool isRefresh, Success?> response) { + if (response.response?.isNotEmpty == true) { + tabController = + TabController(length: response.response!.length, vsync: this); + } loadingState.value = response; return true; } @override - Future customGetData() => + Future?>> customGetData() => ReplyHttp.getEmoteList(business: 'reply'); @override diff --git a/lib/pages/emote/view.dart b/lib/pages/emote/view.dart index 4235b6ef..da065866 100644 --- a/lib/pages/emote/view.dart +++ b/lib/pages/emote/view.dart @@ -29,16 +29,16 @@ class _EmotePanelState extends State return Obx(() => _buildBody(_emotePanelController.loadingState.value)); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? Column( children: [ Expanded( child: tabBarView( controller: _emotePanelController.tabController, - children: (loadingState.response as List).map( + children: loadingState.response!.map( (e) { int size = e.emote!.first.meta!.size!; int type = e.type!; @@ -100,7 +100,7 @@ class _EmotePanelState extends State dividerColor: Colors.transparent, dividerHeight: 0, isScrollable: true, - tabs: (loadingState.response as List) + tabs: loadingState.response! .map( (e) => Padding( padding: const EdgeInsets.all(8), diff --git a/lib/pages/fan/controller.dart b/lib/pages/fan/controller.dart index 6b8b5fd8..d04c45df 100644 --- a/lib/pages/fan/controller.dart +++ b/lib/pages/fan/controller.dart @@ -1,12 +1,12 @@ import 'package:PiliPlus/http/fan.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/fans/result.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/utils/storage.dart'; -class FansController extends CommonController { +class FansController + extends CommonListController { int ps = 20; int total = 0; late int? mid; @@ -28,22 +28,12 @@ class FansController extends CommonController { } @override - bool customHandleResponse(Success response) { - if ((currentPage == 1 && response.response.total < ps) || - (response.response.list as List?).isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - response.response.list ??= []; - response.response.list! - .insertAll(0, (loadingState.value as Success).response); - } - loadingState.value = LoadingState.success(response.response.list); - return true; + List? getDataList(FansDataModel response) { + return response.list; } @override - Future customGetData() => FanHttp.fans( + Future> customGetData() => FanHttp.fans( vmid: mid, pn: currentPage, ps: ps, diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart index 4b3a67bf..26ff197d 100644 --- a/lib/pages/fan/view.dart +++ b/lib/pages/fan/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/fans/result.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -48,23 +49,27 @@ class _FansPageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => HttpError(), - Success() => (loadingState.response as List?)?.isNotEmpty == true - ? SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: Grid.smallCardWidth * 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, + Success() => loadingState.response?.isNotEmpty == true + ? SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80), + sliver: SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: Grid.smallCardWidth * 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, + ), ), ) : HttpError( diff --git a/lib/pages/fav/article/controller.dart b/lib/pages/fav/article/controller.dart index 806e08c0..10f3c8a4 100644 --- a/lib/pages/fav/article/controller.dart +++ b/lib/pages/fav/article/controller.dart @@ -1,9 +1,9 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/user.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -class FavArticleController extends CommonController { +class FavArticleController extends CommonListController { @override void onInit() { super.onInit(); @@ -19,7 +19,7 @@ class FavArticleController extends CommonController { if (res['status']) { List list = (loadingState.value as Success).response; list.removeAt(index); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); SmartDialog.showToast('已取消收藏'); } else { SmartDialog.showToast(res['msg']); diff --git a/lib/pages/fav/article/view.dart b/lib/pages/fav/article/view.dart index c37e8ed5..a0cefcf9 100644 --- a/lib/pages/fav/article/view.dart +++ b/lib/pages/fav/article/view.dart @@ -40,7 +40,7 @@ class _FavArticlePageState extends State ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( @@ -55,7 +55,7 @@ class _FavArticlePageState extends State childCount: 10, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( top: StyleString.safeSpace - 5, @@ -69,11 +69,11 @@ class _FavArticlePageState extends State ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _favArticleController.onLoadMore(); } return FavArticleItem( - item: loadingState.response[index], + item: loadingState.response![index], onDelete: () { showConfirmDialog( context: context, @@ -81,13 +81,13 @@ class _FavArticlePageState extends State onConfirm: () { _favArticleController.onRemove( index, - loadingState.response[index]['opus_id'], + loadingState.response![index]['opus_id'], ); }); }, ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ) diff --git a/lib/pages/fav/note/child_view.dart b/lib/pages/fav/note/child_view.dart index b12e0500..70a1a99f 100644 --- a/lib/pages/fav/note/child_view.dart +++ b/lib/pages/fav/note/child_view.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/member/article.dart'; import 'package:PiliPlus/pages/fav/note/controller.dart'; import 'package:PiliPlus/pages/fav/note/widget/item.dart'; import 'package:PiliPlus/utils/grid.dart'; @@ -132,7 +133,7 @@ class _FavNoteChildPageState extends State ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( @@ -147,7 +148,7 @@ class _FavNoteChildPageState extends State childCount: 10, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( bottom: MediaQuery.paddingOf(context).bottom + 80), @@ -159,18 +160,18 @@ class _FavNoteChildPageState extends State ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _favNoteController.onLoadMore(); } return FavNoteItem( - item: loadingState.response[index], + item: loadingState.response![index], ctr: _favNoteController, onSelect: () { _favNoteController.onSelect(index); }, ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ) diff --git a/lib/pages/fav/note/controller.dart b/lib/pages/fav/note/controller.dart index e5d9b6e4..a4be72ed 100644 --- a/lib/pages/fav/note/controller.dart +++ b/lib/pages/fav/note/controller.dart @@ -1,16 +1,15 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/video.dart'; +import 'package:PiliPlus/models/member/article.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -class FavNoteController extends MultiSelectController { +class FavNoteController + extends MultiSelectController?, FavArticleModel> { FavNoteController(this.isPublish); final bool isPublish; - late final allSelected = false.obs; - @override void onInit() { super.onInit(); @@ -18,48 +17,36 @@ class FavNoteController extends MultiSelectController { } @override - onSelect(int index) { - List list = (loadingState.value as Success).response; - list[index]['checked'] = !(list[index]['checked'] ?? false); - checkedCount.value = list.where((item) => item['checked'] == true).length; - loadingState.value = LoadingState.success(list); - allSelected.value = checkedCount.value == list.length; - if (checkedCount.value == 0) { - enableMultiSelect.value = false; - } + onSelect(int index, [bool disableSelect = true]) { + super.onSelect(index, false); } @override - void handleSelect([bool checked = false]) { + void handleSelect([bool checked = false, bool disableSelect = true]) { allSelected.value = checked; - if (loadingState.value is Success) { - List list = (loadingState.value as Success).response; - if (list.isNotEmpty) { - loadingState.value = LoadingState.success( - list.map((item) => item..['checked'] = checked).toList()); - checkedCount.value = checked ? list.length : 0; - } - } + super.handleSelect(checked, false); } @override - Future customGetData() { + Future?>> customGetData() { return isPublish ? VideoHttp.userNoteList(page: currentPage) : VideoHttp.noteList(page: currentPage); } void onRemove() async { - List dataList = (loadingState.value as Success).response as List; - Set removeList = dataList.where((item) => item['checked'] == true).toSet(); + List dataList = (loadingState.value as Success).response; + Set removeList = + dataList.where((item) => item.checked == true).toSet(); final res = await VideoHttp.delNote( isPublish: isPublish, noteIds: removeList - .map((item) => isPublish ? item['cvid'] : item['note_id']) + .map((item) => isPublish ? item.cvid : item.noteId) .toList(), ); if (res['status']) { - List remainList = dataList.toSet().difference(removeList).toList(); + List remainList = + dataList.toSet().difference(removeList).toList(); loadingState.value = LoadingState.success(remainList); enableMultiSelect.value = false; SmartDialog.showToast('删除成功'); diff --git a/lib/pages/fav/note/widget/item.dart b/lib/pages/fav/note/widget/item.dart index 5a319cf7..6aa55c8e 100644 --- a/lib/pages/fav/note/widget/item.dart +++ b/lib/pages/fav/note/widget/item.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; +import 'package:PiliPlus/models/member/article.dart'; import 'package:PiliPlus/pages/fav/note/controller.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -12,7 +13,7 @@ class FavNoteItem extends StatelessWidget { required this.onSelect, }); - final dynamic item; + final FavArticleModel item; final FavNoteController ctr; final VoidCallback onSelect; @@ -26,10 +27,12 @@ class FavNoteItem extends StatelessWidget { onSelect(); return; } - Utils.handleWebview( - item['web_url'], - inApp: true, - ); + if (item.webUrl?.isNotEmpty == true) { + Utils.handleWebview( + item.webUrl!, + inApp: true, + ); + } }, onLongPress: () { if (!ctr.enableMultiSelect.value) { @@ -53,7 +56,7 @@ class FavNoteItem extends StatelessWidget { children: [ Expanded( child: Text( - item['title'], + item.title ?? '', maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -64,14 +67,14 @@ class FavNoteItem extends StatelessWidget { ), ), Text( - item['summary'], + item.summary ?? '', maxLines: 1, style: TextStyle( color: Theme.of(context).colorScheme.outline, ), ), Text( - item['message'], + item.message ?? '', maxLines: 1, style: TextStyle( color: Theme.of(context).colorScheme.outline, @@ -80,7 +83,7 @@ class FavNoteItem extends StatelessWidget { ], ), ), - if (item['arc']?['pic'] != null) ...[ + if (item.pic?.isNotEmpty == true) ...[ const SizedBox(width: 10), AspectRatio( aspectRatio: StyleString.aspectRatio, @@ -91,7 +94,7 @@ class FavNoteItem extends StatelessWidget { clipBehavior: Clip.none, children: [ NetworkImgLayer( - src: item['arc']?['pic'], + src: item.pic, width: boxConstraints.maxWidth, height: boxConstraints.maxHeight, ), @@ -100,7 +103,7 @@ class FavNoteItem extends StatelessWidget { child: LayoutBuilder( builder: (context, constraints) => AnimatedOpacity( - opacity: item['checked'] == true ? 1 : 0, + opacity: item.checked == true ? 1 : 0, duration: const Duration(milliseconds: 200), child: Container( alignment: Alignment.center, @@ -115,7 +118,7 @@ class FavNoteItem extends StatelessWidget { width: 34, height: 34, child: AnimatedScale( - scale: item['checked'] == true ? 1 : 0, + scale: item.checked == true ? 1 : 0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, diff --git a/lib/pages/fav/pgc/child_view.dart b/lib/pages/fav/pgc/child_view.dart index 04c42b67..72595d91 100644 --- a/lib/pages/fav/pgc/child_view.dart +++ b/lib/pages/fav/pgc/child_view.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/bangumi/list.dart'; import 'package:PiliPlus/pages/fav/pgc/controller.dart'; import 'package:PiliPlus/pages/fav/pgc/widget/item.dart'; import 'package:PiliPlus/utils/grid.dart'; @@ -126,7 +127,7 @@ class _FavPgcChildPageState extends State if (_favPgcController.checkedCount.value != 0) { _favPgcController - .onUpdate(item['followStatus']); + .onUpdateList(item['followStatus']); } }, child: Padding( @@ -156,7 +157,7 @@ class _FavPgcChildPageState extends State ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( @@ -171,7 +172,7 @@ class _FavPgcChildPageState extends State childCount: 10, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( bottom: MediaQuery.paddingOf(context).bottom + 80), @@ -183,11 +184,12 @@ class _FavPgcChildPageState extends State ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _favPgcController.onLoadMore(); } + final item = loadingState.response![index]; return FavPgcItem( - item: loadingState.response[index], + item: item, ctr: _favPgcController, onSelect: () { _favPgcController.onSelect(index); @@ -201,13 +203,13 @@ class _FavPgcChildPageState extends State if (followStatus == -1) { _favPgcController.bangumiDel( index, - loadingState.response[index].seasonId, + item.seasonId, ); } else { - _favPgcController.bangumiUpdate( + _favPgcController.onUpdate( index, followStatus, - loadingState.response[index].seasonId, + item.seasonId, ); } }, @@ -215,7 +217,7 @@ class _FavPgcChildPageState extends State }, ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ) diff --git a/lib/pages/fav/pgc/controller.dart b/lib/pages/fav/pgc/controller.dart index e0a1ba5d..51f72467 100644 --- a/lib/pages/fav/pgc/controller.dart +++ b/lib/pages/fav/pgc/controller.dart @@ -8,14 +8,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -class FavPgcController extends MultiSelectController { +class FavPgcController + extends MultiSelectController { final int type; final int followStatus; FavPgcController(this.type, this.followStatus); - late final allSelected = false.obs; - @override void onInit() { super.onInit(); @@ -23,34 +22,24 @@ class FavPgcController extends MultiSelectController { } @override - onSelect(int index) { - List list = (loadingState.value as Success).response; - list[index].checked = !(list[index].checked ?? false); - checkedCount.value = list.where((item) => item.checked == true).length; - loadingState.value = LoadingState.success(list); - allSelected.value = checkedCount.value == list.length; - if (checkedCount.value == 0) { - enableMultiSelect.value = false; - } + onSelect(int index, [bool disableSelect = true]) { + super.onSelect(index, false); } @override - void handleSelect([bool checked = false]) { + void handleSelect([bool checked = false, bool disableSelect = true]) { allSelected.value = checked; - if (loadingState.value is Success) { - List list = - (loadingState.value as Success).response; - if (list.isNotEmpty) { - loadingState.value = LoadingState.success(list - .map((item) => item..checked = checked) - .toList()); - checkedCount.value = checked ? list.length : 0; - } - } + super.handleSelect(checked, false); } @override - Future customGetData() => BangumiHttp.bangumiFollowList( + List? getDataList(BangumiListDataModel response) { + return response.list; + } + + @override + Future> customGetData() => + BangumiHttp.bangumiFollowList( mid: Accounts.main.mid, type: type, followStatus: followStatus, @@ -71,12 +60,12 @@ class FavPgcController extends MultiSelectController { List list = (loadingState.value as Success).response; list.removeAt(index); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); } SmartDialog.showToast(result['msg']); } - Future onUpdate(followStatus) async { + Future onUpdateList(followStatus) async { List dataList = (loadingState.value as Success).response as List; Set updateList = @@ -96,7 +85,7 @@ class FavPgcController extends MultiSelectController { List list = (ctr.loadingState.value as Success).response; list.insertAll(0, updateList.map((item) => item..checked = null)); - ctr.loadingState.value = LoadingState.success(list); + ctr.loadingState.refresh(); ctr.allSelected.value = false; } } catch (e) { @@ -106,7 +95,7 @@ class FavPgcController extends MultiSelectController { SmartDialog.showToast(res['msg']); } - Future bangumiUpdate(index, followStatus, seasonId) async { + Future onUpdate(index, followStatus, seasonId) async { var result = await VideoHttp.bangumiUpdate( seasonId: [seasonId], status: followStatus, @@ -115,14 +104,14 @@ class FavPgcController extends MultiSelectController { List list = (loadingState.value as Success).response; final item = list.removeAt(index); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); try { final ctr = Get.find(tag: '$type$followStatus'); if (ctr.loadingState.value is Success) { List list = (ctr.loadingState.value as Success).response; list.insert(0, item); - ctr.loadingState.value = LoadingState.success(list); + ctr.loadingState.refresh(); ctr.allSelected.value = false; } } catch (e) { diff --git a/lib/pages/fav/video/controller.dart b/lib/pages/fav/video/controller.dart index 3b340ccd..183b463b 100644 --- a/lib/pages/fav/video/controller.dart +++ b/lib/pages/fav/video/controller.dart @@ -1,11 +1,11 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/user/fav_folder.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/http/user.dart'; -import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/storage.dart'; -class FavController extends CommonController { +class FavController + extends CommonListController { late final dynamic mid = Accounts.main.mid; @override @@ -24,22 +24,20 @@ class FavController extends CommonController { } @override - bool customHandleResponse(Success response) { - if (response.response.hasMore == false || - (response.response.list as List?).isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - response.response.list ??= []; - response.response.list! - .insertAll(0, (loadingState.value as Success).response); - } - loadingState.value = LoadingState.success(response.response.list); - return true; + List? getDataList(FavFolderData response) { + return response.list; } @override - Future customGetData() => UserHttp.userfavFolder( + bool customHandleResponse(bool isRefresh, Success response) { + if (response.response.hasMore == false) { + isEnd = true; + } + return false; + } + + @override + Future> customGetData() => UserHttp.userfavFolder( pn: currentPage, ps: 10, mid: mid, diff --git a/lib/pages/fav/video/view.dart b/lib/pages/fav/video/view.dart index cbb46836..c0d10392 100644 --- a/lib/pages/fav/video/view.dart +++ b/lib/pages/fav/video/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/skeleton/video_card_h.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/user/fav_folder.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -44,7 +45,7 @@ class _FavVideoPageState extends State ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( @@ -59,7 +60,7 @@ class _FavVideoPageState extends State childCount: 10, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( top: StyleString.safeSpace - 5, @@ -72,33 +73,31 @@ class _FavVideoPageState extends State childAspectRatio: StyleString.aspectRatio * 2.2, ), delegate: SliverChildBuilderDelegate( - childCount: loadingState.response.length, + childCount: loadingState.response!.length, (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _favController.onLoadMore(); } - String heroTag = - Utils.makeHeroTag(loadingState.response[index].fid); + final item = loadingState.response![index]; + String heroTag = Utils.makeHeroTag(item.fid); return FavItem( heroTag: heroTag, - favFolderItem: loadingState.response[index], + favFolderItem: item, onTap: () async { dynamic res = await Get.toNamed( '/favDetail', - arguments: loadingState.response[index], + arguments: item, parameters: { 'heroTag': heroTag, - 'mediaId': - loadingState.response[index].id.toString(), + 'mediaId': item.id.toString(), }, ); if (res == true) { - List list = + List list = (_favController.loadingState.value as Success) .response; list.removeAt(index); - _favController.loadingState.value = - LoadingState.success(list); + _favController.loadingState.refresh(); } else { Future.delayed(const Duration(milliseconds: 255), () { _favController.onRefresh(); diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index c8f199e8..307c7a0d 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/user/fav_folder.dart'; import 'package:PiliPlus/pages/fav/article/view.dart'; import 'package:PiliPlus/pages/fav/note/view.dart'; import 'package:PiliPlus/pages/fav/pgc/view.dart'; @@ -54,13 +55,13 @@ class _FavPageState extends State with SingleTickerProviderStateMixin { Get.toNamed('/createFav')?.then( (data) { if (data != null) { - List list = _favController.loadingState.value is Success - ? (_favController.loadingState.value as Success) - .response - : []; + List list = + _favController.loadingState.value is Success + ? (_favController.loadingState.value as Success) + .response + : []; list.insert(list.isNotEmpty ? 1 : 0, data); - _favController.loadingState.value = - LoadingState.success(list); + _favController.loadingState.refresh(); } }, ); @@ -81,6 +82,7 @@ class _FavPageState extends State with SingleTickerProviderStateMixin { 'title': item.title, 'count': item.mediaCount, 'searchType': SearchType.fav, + 'isOwner': true, }); } catch (_) {} } diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 6feadd57..e5f8f0c1 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -3,7 +3,6 @@ import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/models/user/fav_detail.dart'; import 'package:PiliPlus/models/user/fav_folder.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -11,7 +10,8 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/video.dart'; -class FavDetailController extends MultiSelectController { +class FavDetailController + extends MultiSelectController { Rx item = FavFolderItemData().obs; int? mediaId; late String heroTag; @@ -35,40 +35,39 @@ class FavDetailController extends MultiSelectController { } @override - bool customHandleResponse(Success response) { + List? getDataList(FavDetailData response) { + return response.list; + } + + @override + void checkIsEnd(int length) { + if (item.value.mediaCount != null && length >= item.value.mediaCount!) { + isEnd = true; + } + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { FavDetailData data = response.response; - if (currentPage == 1) { + if (isRefresh) { item.value = data.info ?? FavFolderItemData(); isOwner.value = data.info?.mid == mid; } - if (data.list.isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - data.list ??= []; - data.list!.insertAll( - 0, - (loadingState.value as Success).response, - ); - } - if (isEnd.not && (data.list?.length ?? 0) >= (data.info?.mediaCount ?? 0)) { - isEnd = true; - } - loadingState.value = LoadingState.success(data.list); - return true; + return false; } - onCancelFav(int id, int type) async { + onCancelFav(int index, int id, int type) async { var result = await VideoHttp.delFav( ids: ['$id:$type'], delIds: mediaId.toString(), ); if (result['status']) { - List dataList = (loadingState.value as Success).response; - dataList.removeWhere((item) => item.id == id); + List dataList = + (loadingState.value as Success).response; item.value.mediaCount = item.value.mediaCount! - 1; item.refresh(); - loadingState.value = LoadingState.success(dataList); + dataList.removeAt(index); + loadingState.refresh(); SmartDialog.showToast('取消收藏'); } else { SmartDialog.showToast(result['msg']); @@ -76,7 +75,8 @@ class FavDetailController extends MultiSelectController { } @override - Future customGetData() => UserHttp.userFavFolderDetail( + Future> customGetData() => + UserHttp.userFavFolderDetail( pn: currentPage, ps: 20, mediaId: mediaId!, @@ -110,8 +110,9 @@ class FavDetailController extends MultiSelectController { delIds: mediaId.toString(), ); if (result['status']) { - List dataList = (loadingState.value as Success).response; - List remainList = + List dataList = + (loadingState.value as Success).response; + List remainList = dataList.toSet().difference(list.toSet()).toList(); item.value.mediaCount = item.value.mediaCount! - list.length; item.refresh(); @@ -137,8 +138,7 @@ class FavDetailController extends MultiSelectController { void toViewPlayAll() { if (loadingState.value is Success) { - List list = List.from( - (loadingState.value as Success).response); + List list = (loadingState.value as Success).response; for (FavDetailItemData element in list) { if (element.cid == null) { continue; diff --git a/lib/pages/fav_detail/fav_sort_page.dart b/lib/pages/fav_detail/fav_sort_page.dart index 110112e3..6a99e138 100644 --- a/lib/pages/fav_detail/fav_sort_page.dart +++ b/lib/pages/fav_detail/fav_sort_page.dart @@ -22,7 +22,7 @@ class _FavSortPageState extends State { FavDetailController get _favDetailController => widget.favDetailController; final GlobalKey _key = GlobalKey(); - late List list = List.from( + late List sortList = List.from( (_favDetailController.loadingState.value as Success).response); List sort = []; @@ -39,7 +39,7 @@ class _FavSortPageState extends State { if (_favDetailController.loadingState.value is Success) { List list = (_favDetailController.loadingState.value as Success).response; - this.list.addAll(list.sublist(this.list.length)); + this.sortList.addAll(list.sublist(this.sortList.length)); if (mounted) { setState(() {}); } @@ -83,7 +83,7 @@ class _FavSortPageState extends State { if (res['status']) { SmartDialog.showToast('排序完成'); _favDetailController.loadingState.value = - LoadingState.success(list); + LoadingState.success(sortList); Get.back(); } else { SmartDialog.showToast(res['msg']); @@ -103,14 +103,14 @@ class _FavSortPageState extends State { newIndex -= 1; } - final oldItem = list[oldIndex]; + final oldItem = sortList[oldIndex]; final newItem = - list.getOrNull(oldIndex > newIndex ? newIndex - 1 : newIndex); + sortList.getOrNull(oldIndex > newIndex ? newIndex - 1 : newIndex); sort.add( '${newItem == null ? '0:0' : '${newItem.id}:${newItem.type}'}:${oldItem.id}:${oldItem.type}'); - final tabsItem = list.removeAt(oldIndex); - list.insert(newIndex, tabsItem); + final tabsItem = sortList.removeAt(oldIndex); + sortList.insert(newIndex, tabsItem); setState(() {}); } @@ -124,7 +124,7 @@ class _FavSortPageState extends State { footer: SizedBox( height: MediaQuery.of(context).padding.bottom + 80, ), - children: list + children: sortList .map( (item) => Stack( key: Key(item.id.toString()), diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 5beb2ab8..6e769a12 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -135,7 +135,8 @@ class _FavDetailPageState extends State { visualDensity: VisualDensity(horizontal: -2, vertical: -2), ), - onPressed: () => Utils.onCopyOrMove( + onPressed: () => + Utils.onCopyOrMove( context: context, isCopy: true, ctr: _favDetailController, @@ -155,7 +156,8 @@ class _FavDetailPageState extends State { visualDensity: VisualDensity(horizontal: -2, vertical: -2), ), - onPressed: () => Utils.onCopyOrMove( + onPressed: () => + Utils.onCopyOrMove( context: context, isCopy: false, ctr: _favDetailController, @@ -188,15 +190,18 @@ class _FavDetailPageState extends State { : [ IconButton( tooltip: '搜索', - onPressed: () => - Get.toNamed('/favSearch', arguments: { - 'type': 0, - 'mediaId': int.parse(mediaId), - 'title': _favDetailController.item.value.title, - 'count': - _favDetailController.item.value.mediaCount, - 'searchType': SearchType.fav, - }), + onPressed: () => Get.toNamed( + '/favSearch', + arguments: { + 'type': 0, + 'mediaId': int.parse(mediaId), + 'title': _favDetailController.item.value.title, + 'count': + _favDetailController.item.value.mediaCount, + 'searchType': SearchType.fav, + 'isOwner': _favDetailController.isOwner.value, + }, + ), icon: const Icon(Icons.search_outlined), ), // IconButton( @@ -416,7 +421,7 @@ class _FavDetailPageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( @@ -431,7 +436,7 @@ class _FavDetailPageState extends State { childCount: 10, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom + 85, @@ -444,7 +449,7 @@ class _FavDetailPageState extends State { ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length) { + if (index == loadingState.response!.length) { _favDetailController.onLoadMore(); return Container( height: 60, @@ -458,25 +463,26 @@ class _FavDetailPageState extends State { ), ); } - FavDetailItemData element = loadingState.response[index]; + FavDetailItemData item = loadingState.response![index]; return Stack( children: [ Positioned.fill( child: FavVideoCardH( - videoItem: element, + videoItem: item, callFn: () => _favDetailController.onCancelFav( - element.id!, - element.type!, + index, + item.id!, + item.type!, ), onViewFav: () { Utils.toViewPage( - 'bvid=${element.bvid}&cid=${element.cid}', + 'bvid=${item.bvid}&cid=${item.cid}', arguments: { - 'videoItem': element, - 'heroTag': Utils.makeHeroTag(element.bvid), + 'videoItem': item, + 'heroTag': Utils.makeHeroTag(item.bvid), 'sourceType': 'fav', 'mediaId': _favDetailController.item.value.id, - 'oid': element.id, + 'oid': item.id, 'favTitle': _favDetailController.item.value.title, 'count': _favDetailController @@ -513,10 +519,7 @@ class _FavDetailPageState extends State { child: LayoutBuilder( builder: (context, constraints) => AnimatedOpacity( - opacity: - loadingState.response[index].checked == true - ? 1 - : 0, + opacity: item.checked == true ? 1 : 0, duration: const Duration(milliseconds: 200), child: Container( alignment: Alignment.center, @@ -531,11 +534,7 @@ class _FavDetailPageState extends State { width: 34, height: 34, child: AnimatedScale( - scale: loadingState - .response[index].checked == - true - ? 1 - : 0, + scale: item.checked == true ? 1 : 0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, @@ -571,7 +570,7 @@ class _FavDetailPageState extends State { ], ); }, - childCount: loadingState.response.length + 1, + childCount: loadingState.response!.length + 1, ), ), ) diff --git a/lib/pages/fav_search/controller.dart b/lib/pages/fav_search/controller.dart index 524f972f..7e3ecc9e 100644 --- a/lib/pages/fav_search/controller.dart +++ b/lib/pages/fav_search/controller.dart @@ -1,6 +1,6 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -9,7 +9,7 @@ import 'package:PiliPlus/http/user.dart'; import '../../http/video.dart'; -class FavSearchController extends CommonController { +class FavSearchController extends CommonListController { final controller = TextEditingController(); final searchFocusNode = FocusNode(); @@ -17,6 +17,7 @@ class FavSearchController extends CommonController { int? mediaId; int? mid; late SearchType searchType; + final bool? isOwner = Get.arguments['isOwner']; @override void onInit() { @@ -45,23 +46,19 @@ class FavSearchController extends CommonController { } @override - bool customHandleResponse(Success response) { - late List currentList = loadingState.value is Success - ? (loadingState.value as Success).response - : []; - List? dataList = currentPage == 1 - ? response.response.list - : response.response.list != null - ? currentList + response.response.list - : currentList; + List? getDataList(response) { + return response.list; + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { isEnd = searchType == SearchType.fav ? response.response.hasMore == false : response.response.list == null || response.response.list.isEmpty; - loadingState.value = LoadingState.success(dataList); - return true; + return false; } - onCancelFav(int id, int type) async { + onCancelFav(int index, int id, int type) async { var result = await VideoHttp.favVideo( aid: id, addIds: '', @@ -70,8 +67,8 @@ class FavSearchController extends CommonController { ); if (result['status']) { List dataList = (loadingState.value as Success).response; - dataList.removeWhere((item) => item.id == id); - loadingState.value = LoadingState.success(dataList); + dataList.removeAt(index); + loadingState.refresh(); SmartDialog.showToast('取消收藏'); } } @@ -104,7 +101,7 @@ class FavSearchController extends CommonController { super.onClose(); } - Future delHistory(kid, business) async { + Future onDelHistory(index, kid, business) async { String resKid = 'archive_$kid'; if (business == 'live') { resKid = 'live_$kid'; @@ -115,8 +112,8 @@ class FavSearchController extends CommonController { var res = await UserHttp.delHistory([resKid]); if (res['status']) { List historyList = (loadingState.value as Success).response; - historyList.removeWhere((e) => e.kid == kid); - loadingState.value = LoadingState.success(historyList); + historyList.removeAt(index); + loadingState.refresh(); SmartDialog.showToast(res['msg']); } } diff --git a/lib/pages/fav_search/view.dart b/lib/pages/fav_search/view.dart index fb74e2ec..96d326cb 100644 --- a/lib/pages/fav_search/view.dart +++ b/lib/pages/fav_search/view.dart @@ -61,12 +61,12 @@ class _FavSearchPageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => errorWidget(), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? switch (_favSearchCtr.searchType) { - SearchType.fav => CustomScrollView( + SearchType.fav || SearchType.history => CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), controller: _favSearchCtr.scrollController, slivers: [ @@ -82,40 +82,53 @@ class _FavSearchPageState extends State { ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _favSearchCtr.onLoadMore(); } - final element = loadingState.response[index]; - return FavVideoCardH( - videoItem: element, - searchType: _favSearchCtr.type, - callFn: _favSearchCtr.type != 1 - ? () { - _favSearchCtr.onCancelFav( - element.id!, - element.type, + final item = loadingState.response![index]; + return _favSearchCtr.searchType == SearchType.fav + ? FavVideoCardH( + videoItem: item, + isOwner: _favSearchCtr.isOwner ?? false, + searchType: _favSearchCtr.type, + callFn: _favSearchCtr.type != 1 + ? () { + _favSearchCtr.onCancelFav( + index, + item.id!, + item.type, + ); + } + : null, + onViewFav: () { + Utils.toViewPage( + 'bvid=${item.bvid}&cid=${item.cid}', + arguments: { + 'videoItem': item, + 'heroTag': + Utils.makeHeroTag(item.bvid), + 'sourceType': 'fav', + 'mediaId': Get.arguments['mediaId'], + 'oid': item.id, + 'favTitle': Get.arguments['title'], + 'count': Get.arguments['count'], + 'desc': true, + 'isContinuePlaying': true, + }, ); - } - : null, - onViewFav: () { - Utils.toViewPage( - 'bvid=${element.bvid}&cid=${element.cid}', - arguments: { - 'videoItem': element, - 'heroTag': Utils.makeHeroTag(element.bvid), - 'sourceType': 'fav', - 'mediaId': Get.arguments['mediaId'], - 'oid': element.id, - 'favTitle': Get.arguments['title'], - 'count': Get.arguments['count'], - 'desc': true, - 'isContinuePlaying': true, - }, - ); - }, - ); + }, + ) + : HistoryItem( + videoItem: item, + ctr: _favSearchCtr, + onChoose: null, + onDelete: (kid, business) { + _favSearchCtr.onDelHistory( + index, kid, business); + }, + ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ), @@ -126,47 +139,16 @@ class _FavSearchPageState extends State { bottom: MediaQuery.of(context).padding.bottom + 80, ), controller: _favSearchCtr.scrollController, - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, itemBuilder: ((context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _favSearchCtr.onLoadMore(); } return FollowItem( - item: loadingState.response[index], + item: loadingState.response![index], ); }), ), - SearchType.history => CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: _favSearchCtr.scrollController, - slivers: [ - SliverPadding( - padding: EdgeInsets.only( - 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 (index == loadingState.response.length - 1) { - _favSearchCtr.onLoadMore(); - } - return HistoryItem( - videoItem: loadingState.response[index], - ctr: _favSearchCtr, - onChoose: null, - ); - }, - childCount: loadingState.response.length, - ), - ), - ), - ], - ), } : errorWidget( callback: _favSearchCtr.onReload, diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart index dcdbad6e..5949fe33 100644 --- a/lib/pages/follow/view.dart +++ b/lib/pages/follow/view.dart @@ -37,12 +37,16 @@ class _FollowPageState extends State { ), actions: [ IconButton( - onPressed: () => Get.toNamed('/favSearch', arguments: { - 'mid': int.parse(mid), - 'searchType': SearchType.follow, - }), - icon: const Icon(Icons.search_outlined), - tooltip: '搜索'), + onPressed: () => Get.toNamed( + '/favSearch', + arguments: { + 'mid': int.parse(mid), + 'searchType': SearchType.follow, + }, + ), + icon: const Icon(Icons.search_outlined), + tooltip: '搜索', + ), PopupMenuButton( icon: const Icon(Icons.more_vert), itemBuilder: (BuildContext context) => [ diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index acaefdfa..db3ab243 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -9,7 +9,7 @@ import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/models/user/history.dart'; import 'package:PiliPlus/utils/storage.dart'; -class HistoryController extends MultiSelectController +class HistoryController extends MultiSelectController with GetTickerProviderStateMixin { HistoryController(this.type); @@ -37,25 +37,27 @@ class HistoryController extends MultiSelectController } @override - onSelect(int index) { - List list = (loadingState.value as Success).response; - list[index].checked = !(list[index]?.checked ?? false); + onSelect(int index, [bool disableSelect = true]) { + List list = (loadingState.value as Success).response; + list[index].checked = !(list[index].checked ?? false); baseCtr.checkedCount.value = list.where((item) => item.checked == true).length; - loadingState.value = LoadingState.success(list); + loadingState.refresh(); if (baseCtr.checkedCount.value == 0) { baseCtr.enableMultiSelect.value = false; } } @override - void handleSelect([bool checked = false]) { + void handleSelect([bool checked = false, bool disableSelect = true]) { if (loadingState.value is Success) { - List list = (loadingState.value as Success).response; - if (list.isNotEmpty) { - loadingState.value = LoadingState.success( - list.map((item) => item..checked = checked).toList()); + List? list = (loadingState.value as Success).response; + if (list?.isNotEmpty == true) { + for (HisListItem item in list!) { + item.checked = checked; + } baseCtr.checkedCount.value = checked ? list.length : 0; + loadingState.refresh(); } } if (checked.not) { @@ -64,26 +66,28 @@ class HistoryController extends MultiSelectController } @override - bool customHandleResponse(Success response) { + List? getDataList(HistoryData response) { + return response.list; + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { HistoryData data = response.response; isEnd = data.list.isNullOrEmpty; max = data.list?.lastOrNull?.history.oid; viewAt = data.list?.lastOrNull?.viewAt; - if (currentPage == 1) { - if (type == null && tabs.isEmpty && data.tab?.isNotEmpty == true) { + + if (isRefresh && type == null) { + if (tabs.isEmpty && data.tab?.isNotEmpty == true) { tabs.value = data.tab!; - tabController = - TabController(length: data.tab!.length + 1, vsync: this); + tabController = TabController( + length: data.tab!.length + 1, + vsync: this, + ); } - } else if (loadingState.value is Success) { - data.list ??= []; - data.list!.insertAll( - 0, - List.from((loadingState.value as Success).response), - ); } - loadingState.value = LoadingState.success(data.list); - return true; + + return false; } // 观看历史暂停状态 @@ -125,10 +129,11 @@ class HistoryController extends MultiSelectController }).toList(); dynamic response = await UserHttp.delHistory(kidList); if (response['status']) { - List remainList = ((loadingState.value as Success).response as List) - .toSet() - .difference(result.toSet()) - .toList(); + List remainList = + ((loadingState.value as Success).response as List) + .toSet() + .difference(result.toSet()) + .toList(); if (remainList.isNotEmpty) { loadingState.value = LoadingState.success(remainList); } else { @@ -179,7 +184,7 @@ class HistoryController extends MultiSelectController } @override - Future customGetData() => + Future> customGetData() => UserHttp.historyList(type: type ?? 'all', max: max, viewAt: viewAt); @override diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 45691c4f..424534d9 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/user/history.dart'; import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType; import 'package:PiliPlus/pages/history/base_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; @@ -261,7 +262,7 @@ class _HistoryPageState extends State ), ); - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( @@ -276,7 +277,7 @@ class _HistoryPageState extends State childCount: 10, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( top: StyleString.safeSpace - 5, @@ -290,18 +291,18 @@ class _HistoryPageState extends State ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _historyController.onLoadMore(); } return HistoryItem( - videoItem: loadingState.response[index], + videoItem: loadingState.response![index], ctr: _historyController.baseCtr, onChoose: () => _historyController.onSelect(index), onDelete: (kid, business) => _historyController.delHistory(kid, business), ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ) diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 76fa2548..8ceb1eba 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -24,14 +24,14 @@ class HistoryItem extends StatelessWidget { final HisListItem videoItem; final dynamic ctr; final Function? onChoose; - final Function? onDelete; + final Function(dynamic kid, dynamic business) onDelete; const HistoryItem({ super.key, required this.videoItem, this.ctr, this.onChoose, - this.onDelete, + required this.onDelete, }); @override @@ -380,10 +380,8 @@ class HistoryItem extends StatelessWidget { ), ), PopupMenuItem( - onTap: () => onDelete != null - ? onDelete!(videoItem.kid, videoItem.history.business) - : ctr.delHistory( - videoItem.kid, videoItem.history.business), + onTap: () => + onDelete(videoItem.kid, videoItem.history.business), height: 35, child: const Row( children: [ diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 46ceea4b..7a13daa4 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -19,7 +19,7 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State - with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { + with AutomaticKeepAliveClientMixin { final HomeController _homeController = Get.put(HomeController()); final MainController _mainController = Get.put(MainController()); diff --git a/lib/pages/hot/controller.dart b/lib/pages/hot/controller.dart index 54c32cdb..f054616d 100644 --- a/lib/pages/hot/controller.dart +++ b/lib/pages/hot/controller.dart @@ -1,10 +1,12 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/http/video.dart'; +import 'package:PiliPlus/models/model_hot_video_item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:get/get.dart'; -class HotController extends CommonController { +class HotController + extends CommonListController, HotVideoItemModel> { // int idx = 0; late RxBool showHotRcmd = GStorage.showHotRcmd.obs; @@ -22,7 +24,8 @@ class HotController extends CommonController { // } @override - Future customGetData() => VideoHttp.hotVideoList( + Future>> customGetData() => + VideoHttp.hotVideoList( pn: currentPage, ps: 20, ); diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index b1c547e3..5038e5d2 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/video_card_h.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/tab_type.dart'; +import 'package:PiliPlus/models/model_hot_video_item.dart'; import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:PiliPlus/pages/rank/view.dart'; import 'package:flutter/material.dart'; @@ -160,10 +161,10 @@ class _HotPageState extends CommonPageState ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => _buildSkeleton(), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: 2, @@ -172,15 +173,15 @@ class _HotPageState extends CommonPageState ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { controller.onLoadMore(); } return VideoCardH( - videoItem: loadingState.response[index], + videoItem: loadingState.response![index], showPubdate: true, ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ) : HttpError( diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart index e1102bc3..4ed20c36 100644 --- a/lib/pages/html/controller.dart +++ b/lib/pages/html/controller.dart @@ -13,7 +13,7 @@ import 'package:PiliPlus/http/html.dart'; import 'package:PiliPlus/http/reply.dart'; import 'package:fixnum/fixnum.dart' as $fixnum; -class HtmlRenderController extends ReplyController { +class HtmlRenderController extends ReplyController { late String id; late String dynamicType; late int type; @@ -91,7 +91,12 @@ class HtmlRenderController extends ReplyController { } @override - Future customGetData() { + List? getDataList(MainListReply response) { + return response.replies; + } + + @override + Future> customGetData() { return ReplyHttp.replyListGrpc( type: type, oid: oid.value, diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index c4b0775d..4773aab1 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -763,7 +763,7 @@ class _HtmlRenderPageState extends State ); } - Widget replyList(LoadingState loadingState) { + Widget replyList(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverList.builder( itemCount: 5, @@ -771,11 +771,11 @@ class _HtmlRenderPageState extends State return const VideoReplySkeleton(); }, ), - Success() => (loadingState.response.replies as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverList.builder( - itemCount: loadingState.response.replies.length + 1, + itemCount: loadingState.response!.length + 1, itemBuilder: (context, index) { - if (index == loadingState.response.replies.length) { + if (index == loadingState.response!.length) { _htmlRenderCtr.onLoadMore(); return Container( alignment: Alignment.center, @@ -785,7 +785,7 @@ class _HtmlRenderPageState extends State child: Text( _htmlRenderCtr.isEnd.not ? '加载中...' - : loadingState.response.replies.isEmpty + : loadingState.response!.isEmpty ? '还没有评论' : '没有更多了', style: TextStyle( @@ -796,19 +796,20 @@ class _HtmlRenderPageState extends State ); } else { return ReplyItemGrpc( - replyItem: loadingState.response.replies[index], + replyItem: loadingState.response![index], replyLevel: '1', replyReply: (replyItem, id) => replyReply(context, replyItem, id), onReply: () { _htmlRenderCtr.onReply( context, - replyItem: loadingState.response.replies[index], + replyItem: loadingState.response![index], index: index, ); }, - onDelete: _htmlRenderCtr.onMDelete, - upMid: loadingState.response.subjectControl.upMid, + onDelete: (subIndex) => + _htmlRenderCtr.onRemove(index, subIndex), + upMid: _htmlRenderCtr.upMid, callback: _getImageCallback, onCheckReply: (item) => _htmlRenderCtr.onCheckReply(context, item), diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index c297e370..ccf3b6e1 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -9,7 +9,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/user.dart'; -class LaterController extends MultiSelectController { +class LaterController extends MultiSelectController { RxInt count = (-1).obs; dynamic mid; @@ -22,25 +22,24 @@ class LaterController extends MultiSelectController { } @override - bool customHandleResponse(Success response) { - count.value = response.response['count']; - if (response.response['list'].isEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - 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; + List? getDataList(response) { + return response['list']; } - Future toViewDel(BuildContext context, {int? aid}) async { + @override + void checkIsEnd(int length) { + if (length >= count.value) { + isEnd = true; + } + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { + count.value = response.response['count']; + return false; + } + + Future toViewDel(BuildContext context, {index, aid}) async { await showDialog( context: context, builder: (context) { @@ -50,7 +49,7 @@ class LaterController extends MultiSelectController { aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'), actions: [ TextButton( - onPressed: () => Get.back(), + onPressed: Get.back, child: Text( '取消', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -62,10 +61,11 @@ class LaterController extends MultiSelectController { await UserHttp.toViewDel(aids: aid != null ? [aid] : null); if (res['status']) { if (aid != null) { - List list = (loadingState.value as Success).response; - list.removeWhere((e) => e.aid == aid); + List list = + (loadingState.value as Success).response; + list.removeAt(index); count.value -= 1; - loadingState.value = LoadingState.success(list); + loadingState.refresh(); } else { onReload(); } @@ -90,7 +90,7 @@ class LaterController extends MultiSelectController { onConfirm: () async { var res = await UserHttp.toViewClear(); if (res['status']) { - loadingState.value = LoadingState.success([]); + loadingState.value = LoadingState.success(null); } SmartDialog.showToast(res['msg']); }, @@ -98,7 +98,7 @@ class LaterController extends MultiSelectController { } @override - Future customGetData() => UserHttp.seeYouLater(); + Future> customGetData() => UserHttp.seeYouLater(); onDelChecked(BuildContext context) { showDialog( @@ -137,9 +137,10 @@ class LaterController extends MultiSelectController { 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()); + Set remainList = + ((loadingState.value as Success).response as List) + .toSet() + .difference(result.toSet()); count.value -= aids.length; loadingState.value = LoadingState.success(remainList.toList()); if (enableMultiSelect.value) { diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index e8df550c..27a915a6 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/model_hot_video_item.dart'; import 'package:PiliPlus/pages/history/view.dart' show AppBarWidget; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -93,7 +94,7 @@ class _LaterPageState extends State { style: TextButton.styleFrom( visualDensity: VisualDensity(horizontal: -2, vertical: -2), ), - onPressed: () => Utils.onCopyOrMove( + onPressed: () => Utils.onCopyOrMove( context: context, isCopy: true, ctr: _laterController, @@ -110,7 +111,7 @@ class _LaterPageState extends State { style: TextButton.styleFrom( visualDensity: VisualDensity(horizontal: -2, vertical: -2), ), - onPressed: () => Utils.onCopyOrMove( + onPressed: () => Utils.onCopyOrMove( context: context, isCopy: false, ctr: _laterController, @@ -171,7 +172,7 @@ class _LaterPageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( @@ -186,7 +187,7 @@ class _LaterPageState extends State { childCount: 10, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: 2, @@ -195,7 +196,7 @@ class _LaterPageState extends State { ), delegate: SliverChildBuilderDelegate( (context, index) { - var videoItem = loadingState.response[index]; + var videoItem = loadingState.response![index]; return Stack( children: [ VideoCardH( @@ -209,7 +210,7 @@ class _LaterPageState extends State { 'oid': videoItem.aid, 'heroTag': Utils.makeHeroTag(videoItem.bvid), 'sourceType': 'watchLater', - 'count': loadingState.response.length, + 'count': loadingState.response!.length, 'favTitle': '稍后再看', 'mediaId': _laterController.mid, 'desc': false, @@ -293,6 +294,7 @@ class _LaterPageState extends State { onPressed: () { _laterController.toViewDel( context, + index: index, aid: videoItem.aid, ); }, @@ -305,7 +307,7 @@ class _LaterPageState extends State { ], ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ) : HttpError( diff --git a/lib/pages/live/controller.dart b/lib/pages/live/controller.dart index ba9e1235..5e622840 100644 --- a/lib/pages/live/controller.dart +++ b/lib/pages/live/controller.dart @@ -1,12 +1,14 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/models/live/follow.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/models/live/item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart'; -class LiveController extends CommonController { +class LiveController + extends CommonListController?, LiveItemModel> { @override void onInit() { super.onInit(); @@ -17,7 +19,8 @@ class LiveController extends CommonController { } @override - Future customGetData() => LiveHttp.liveList(pn: currentPage); + Future?>> customGetData() => + LiveHttp.liveList(pn: currentPage); @override Future onRefresh() { @@ -41,20 +44,34 @@ class LiveController extends CommonController { } dynamic res = await LiveHttp.liveFollowing(pn: followPage, ps: 20); if (res['status']) { - followPage++; - liveCount.value = res['data'].liveCount; - List list = res['data'] - .list - .where((LiveFollowingItemModel item) => + LiveFollowingModel data = res['data']; + liveCount.value = data.liveCount ?? 0; + List? dataList = data.list + ?.where((LiveFollowingItemModel item) => item.liveStatus == 1 && item.recordLiveTime == 0) .toList(); - if (isRefresh.not && followListState.value is Success) { - list.insertAll(0, (followListState.value as Success).response); + if (dataList.isNullOrEmpty) { + followEnd = true; + if (isRefresh) { + followListState.value = LoadingState.success(dataList); + } + return; } - followEnd = list.length >= liveCount.value || - list.isEmpty || - (res['data'].list as List?).isNullOrEmpty; - followListState.value = LoadingState.success(list); + if (isRefresh) { + if (dataList!.length >= liveCount.value) { + followEnd = true; + } + followListState.value = LoadingState.success(dataList); + } else if (loadingState.value is Success) { + List list = + (loadingState.value as Success).response; + list.addAll(dataList!); + if (list.length >= liveCount.value) { + followEnd = true; + } + loadingState.refresh(); + } + followPage++; } else { followListState.value = LoadingState.error(res['msg']); } diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 2787811d..a2ac5657 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -3,6 +3,7 @@ import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/live/item.dart'; import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:PiliPlus/pages/live/controller.dart'; import 'package:PiliPlus/pages/live/widgets/live_item.dart'; @@ -66,17 +67,7 @@ class _LivePageState extends CommonPageState top: StyleString.cardSpace, bottom: MediaQuery.paddingOf(context).bottom + 80, ), - sliver: Obx( - () => controller.loadingState.value is Loading || - controller.loadingState.value is Success - ? contentGrid(controller.loadingState.value) - : HttpError( - errMsg: controller.loadingState.value is Error - ? (controller.loadingState.value as Error).errMsg - : '没有相关数据', - callback: controller.onReload, - ), - ), + sliver: Obx(() => _buildBody(controller.loadingState.value)), ), ], ), @@ -84,33 +75,49 @@ class _LivePageState extends CommonPageState ); } - Widget contentGrid(LoadingState loadingState) { - return SliverGrid( - gridDelegate: SliverGridDelegateWithExtentAndRatio( - // 行间距 - mainAxisSpacing: StyleString.cardSpace, - // 列间距 - crossAxisSpacing: StyleString.cardSpace, - // 最大宽度 - maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, - mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), - ), - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - if (loadingState is Success && - index == loadingState.response.length - 1) { - controller.onLoadMore(); - } - return loadingState is Success - ? LiveCardV( - liveItem: loadingState.response[index], - ) - : const VideoCardVSkeleton(); - }, - childCount: loadingState is Success ? loadingState.response.length : 10, - ), - ); + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + return const VideoCardVSkeleton(); + }, + childCount: 10, + ), + ), + Success() => loadingState.response?.isNotEmpty == true + ? SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == loadingState.response!.length - 1) { + controller.onLoadMore(); + } + return LiveCardV(liveItem: loadingState.response![index]); + }, + childCount: loadingState.response!.length, + ), + ) + : scrollErrorWidget(callback: controller.onReload), + Error() => HttpError( + errMsg: loadingState.errMsg, + callback: controller.onReload, + ), + _ => throw UnimplementedError(), + }; } Widget _buildFollowList() { diff --git a/lib/pages/media/controller.dart b/lib/pages/media/controller.dart index b48110e2..26026c20 100644 --- a/lib/pages/media/controller.dart +++ b/lib/pages/media/controller.dart @@ -1,11 +1,13 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/models/user/fav_folder.dart'; +import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/utils/storage.dart'; -class MediaController extends CommonController { +class MediaController + extends CommonDataController { List list = [ // { // 'icon': Icons.file_download_outlined, @@ -52,14 +54,14 @@ class MediaController extends CommonController { } @override - bool customHandleResponse(Success response) { - count.value = response.response.count; + bool customHandleResponse(bool isRefresh, Success response) { + count.value = response.response.count ?? -1; loadingState.value = response; return true; } @override - Future customGetData() { + Future> customGetData() { mid ??= Accounts.main.mid; return UserHttp.userfavFolder( pn: 1, 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 5843c5d0..9bad7afa 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 @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/space_article/item.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/widget/item.dart'; import 'package:PiliPlus/utils/grid.dart'; @@ -38,10 +39,10 @@ class _MemberArticleState extends State return Obx(() => _buildBody(_controller.loadingState.value)); } - _buildBody(LoadingState loadingState) { + _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? refreshIndicator( onRefresh: () async { await _controller.onRefresh(); @@ -56,14 +57,14 @@ class _MemberArticleState extends State ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _controller.onLoadMore(); } return MemberArticleItem( - item: loadingState.response[index], + item: loadingState.response![index], ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ], diff --git a/lib/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart b/lib/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart index 412b2b49..8707ef49 100644 --- a/lib/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart +++ b/lib/pages/member/new/content/member_contribute/content/article/member_article_ctr.dart @@ -2,10 +2,9 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/models/space_article/item.dart'; import 'package:PiliPlus/models/space_article/data.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; -class MemberArticleCtr extends CommonController { +class MemberArticleCtr extends CommonListController { MemberArticleCtr({ required this.mid, }); @@ -21,24 +20,24 @@ class MemberArticleCtr extends CommonController { } @override - bool customHandleResponse(Success response) { - Data data = response.response; - if (data.item.isNullOrEmpty) { - isEnd = true; - } - count = data.count ?? -1; - if (currentPage != 1 && loadingState.value is Success) { - data.item ??= []; - data.item!.insertAll(0, (loadingState.value as Success).response); - } - if ((data.item?.length ?? -1) >= count) { - isEnd = true; - } - loadingState.value = LoadingState.success(data.item); - return true; + List? getDataList(Data response) { + return response.item; } @override - Future customGetData() => + void checkIsEnd(int length) { + if (length >= count) { + isEnd = true; + } + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { + count = response.response.count ?? -1; + return false; + } + + @override + Future> customGetData() => MemberHttp.spaceArticle(mid: mid, page: currentPage); } 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 f680446d..3bbeb69a 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 @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/space_archive/item.dart'; import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_member_home.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/content/bangumi/member_bangumi_ctr.dart'; import 'package:PiliPlus/utils/grid.dart'; @@ -41,10 +42,10 @@ class _MemberBangumiState extends State return Obx(() => _buildBody(_controller.loadingState.value)); } - _buildBody(LoadingState loadingState) { + _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? refreshIndicator( onRefresh: () async { await _controller.onRefresh(); @@ -70,14 +71,14 @@ class _MemberBangumiState extends State ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _controller.onLoadMore(); } return BangumiCardVMemberHome( - bangumiItem: loadingState.response[index], + bangumiItem: loadingState.response![index], ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ), 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 c95b6782..bc3cdd4d 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 @@ -2,15 +2,14 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/models/space_archive/data.dart'; import 'package:PiliPlus/models/space_archive/item.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' show ContributeType; import 'package:PiliPlus/pages/member/new/controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/models/space/data.dart' as space; -class MemberBangumiCtr extends CommonController { +class MemberBangumiCtr extends CommonListController { MemberBangumiCtr({ required this.mid, required this.heroTag, @@ -37,24 +36,19 @@ class MemberBangumiCtr extends CommonController { } @override - bool customHandleResponse(Success response) { - Data data = response.response; - if (data.item.isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - data.item ??= []; - data.item!.insertAll(0, (loadingState.value as Success).response); - } - if (isEnd.not && count != null && data.item!.length >= count!) { - isEnd = true; - } - loadingState.value = LoadingState.success(data.item); - return true; + List? getDataList(Data response) { + return response.item; } @override - Future customGetData() => MemberHttp.spaceArchive( + void checkIsEnd(int length) { + if (count != null && length >= count!) { + isEnd = true; + } + } + + @override + Future> customGetData() => MemberHttp.spaceArchive( type: ContributeType.bangumi, mid: mid, pn: currentPage, diff --git a/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite_ctr.dart b/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite_ctr.dart index 7c8e7262..2a767082 100644 --- a/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite_ctr.dart +++ b/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite_ctr.dart @@ -4,12 +4,12 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/models/space_fav/datum.dart'; import 'package:PiliPlus/models/space_fav/list.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -class MemberFavoriteCtr extends CommonController { +class MemberFavoriteCtr extends CommonDataController { MemberFavoriteCtr({ required this.mid, }); @@ -39,7 +39,7 @@ class MemberFavoriteCtr extends CommonController { } @override - bool customHandleResponse(Success response) { + bool customHandleResponse(bool isRefresh, Success response) { try { List res = response.response; first.value = res.first; diff --git a/lib/pages/member/new/content/member_contribute/content/season_series/controller.dart b/lib/pages/member/new/content/member_contribute/content/season_series/controller.dart index 7a2e2278..4f4d43fa 100644 --- a/lib/pages/member/new/content/member_contribute/content/season_series/controller.dart +++ b/lib/pages/member/new/content/member_contribute/content/season_series/controller.dart @@ -1,10 +1,11 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; -class SeasonSeriesController extends CommonController { +class SeasonSeriesController extends CommonListController { SeasonSeriesController(this.mid); final int mid; + int? count; @override void onInit() { @@ -13,16 +14,22 @@ class SeasonSeriesController extends CommonController { } @override - bool customHandleResponse(Success response) { - Map data = response.response; - List list = ((data['seasons_list'] as List?) ?? []) + - ((data['series_list'] as List?) ?? []); - if (currentPage != 0 && loadingState.value is Success) { - list.insertAll(0, (loadingState.value as Success).response); + List? getDataList(response) { + return ((response['seasons_list'] as List?) ?? []) + + ((response['series_list'] as List?) ?? []); + } + + @override + void checkIsEnd(int length) { + if (count != null && length >= count!) { + isEnd = true; } - isEnd = list.length >= ((data['page']['total'] as int?) ?? 0); - loadingState.value = LoadingState.success(list); - return true; + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { + count = response.response['page']?['total']; + return false; } @override diff --git a/lib/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart b/lib/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart index 620f837e..4e9cd602 100644 --- a/lib/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart +++ b/lib/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart @@ -39,10 +39,10 @@ class _SeasonSeriesPageState extends State return Obx(() => _buildBody(_controller.loadingState.value)); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? CustomScrollView( slivers: [ SliverPadding( @@ -58,13 +58,13 @@ class _SeasonSeriesPageState extends State ), delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _controller.onLoadMore(); } + dynamic item = loadingState.response![index]; return SeasonSeriesCard( - item: loadingState.response[index], + item: item, onTap: () { - dynamic item = loadingState.response[index]; bool isSeason = item['meta']['season_id'] != null; dynamic id = isSeason ? item['meta']['season_id'] @@ -89,7 +89,7 @@ class _SeasonSeriesPageState extends State }, ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ), 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 28aa91f8..e834580e 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 @@ -60,10 +60,10 @@ class _MemberVideoState extends State return Obx(() => _buildBody(_controller.loadingState.value)); } - _buildBody(LoadingState loadingState) { + _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? Stack( clipBehavior: Clip.none, children: [ @@ -186,17 +186,17 @@ class _MemberVideoState extends State delegate: SliverChildBuilderDelegate( (context, index) { if (widget.type != ContributeType.season && - index == loadingState.response.length - 1) { + index == loadingState.response!.length - 1) { _controller.onLoadMore(); } - final Item item = loadingState.response[index]; + final Item item = loadingState.response![index]; return VideoCardHMemberVideo( key: ValueKey('${item.param}'), videoItem: item, fromViewAid: _controller.fromViewAid, ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ), 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 9e7685b8..1453ffd1 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 @@ -4,7 +4,7 @@ import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models/space_archive/data.dart'; import 'package:PiliPlus/models/space_archive/episodic_button.dart'; import 'package:PiliPlus/models/space_archive/item.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' show ContributeType; import 'package:PiliPlus/utils/extension.dart'; @@ -13,7 +13,7 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -class MemberVideoCtr extends CommonController { +class MemberVideoCtr extends CommonListController { MemberVideoCtr({ required this.type, required this.mid, @@ -70,7 +70,7 @@ class MemberVideoCtr extends CommonController { } @override - bool customHandleResponse(Success response) { + bool customHandleResponse(bool isRefresh, Success response) { Data data = response.response; episodicButton.value = data.episodicButton ?? EpisodicButton(); episodicButton.refresh(); @@ -105,7 +105,7 @@ class MemberVideoCtr extends CommonController { } @override - Future customGetData() => MemberHttp.spaceArchive( + Future> customGetData() => MemberHttp.spaceArchive( type: type, mid: mid, aid: type == ContributeType.video diff --git a/lib/pages/member/new/content/member_contribute/member_contribute_ctr.dart b/lib/pages/member/new/content/member_contribute/member_contribute_ctr.dart index 96444503..18c3ef00 100644 --- a/lib/pages/member/new/content/member_contribute/member_contribute_ctr.dart +++ b/lib/pages/member/new/content/member_contribute/member_contribute_ctr.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/space/tab2.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:PiliPlus/pages/member/new/controller.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter/material.dart'; @@ -10,7 +10,7 @@ import 'package:get/get.dart'; import '../../../../../models/space/item.dart'; -class MemberContributeCtr extends CommonController +class MemberContributeCtr extends CommonDataController with GetTickerProviderStateMixin { MemberContributeCtr({ required this.heroTag, diff --git a/lib/pages/member/new/content/member_dynamic/member_dynamic.dart b/lib/pages/member/new/content/member_dynamic/member_dynamic.dart deleted file mode 100644 index 689dce4d..00000000 --- a/lib/pages/member/new/content/member_dynamic/member_dynamic.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:PiliPlus/common/widgets/loading_widget.dart'; -import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; -import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel_grpc.dart'; -import 'package:PiliPlus/pages/member/new/content/member_dynamic/member_dynamic_ctr.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -@Deprecated('Use MemberDynamicsPage instead') -class MemberDynamic extends StatefulWidget { - const MemberDynamic({ - super.key, - required this.mid, - }); - - final int mid; - - @override - State createState() => _MemberDynamicState(); -} - -class _MemberDynamicState extends State - with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; - - late final _controller = Get.put(MemberDynamicCtr(mid: widget.mid)); - - @override - Widget build(BuildContext context) { - super.build(context); - return Obx(() => _buildBody(_controller.loadingState.value)); - } - - _buildBody(LoadingState loadingState) { - return switch (loadingState) { - Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true - ? refreshIndicator( - onRefresh: () async { - await _controller.onRefresh(); - }, - child: ListView.separated( - itemCount: loadingState.response.length, - itemBuilder: (context, index) { - if (index == loadingState.response.length - 1) { - _controller.onLoadMore(); - } - return DynamicPanelGrpc( - item: loadingState.response[index], - ); - }, - separatorBuilder: (context, index) => - const SizedBox(height: 10), - ), - ) - : scrollErrorWidget( - callback: _controller.onReload, - ), - Error() => scrollErrorWidget( - errMsg: loadingState.errMsg, - callback: _controller.onReload, - ), - LoadingState() => throw UnimplementedError(), - }; - } -} diff --git a/lib/pages/member/new/content/member_dynamic/member_dynamic_ctr.dart b/lib/pages/member/new/content/member_dynamic/member_dynamic_ctr.dart deleted file mode 100644 index 7c381b2a..00000000 --- a/lib/pages/member/new/content/member_dynamic/member_dynamic_ctr.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart'; -import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/http/member.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; - -class MemberDynamicCtr extends CommonController { - MemberDynamicCtr({ - required this.mid, - }); - int mid; - - @override - bool customHandleResponse(Success response) { - DynSpaceRsp res = response.response; - isEnd = !res.hasMore; - if (currentPage != 1 && loadingState.value is Success) { - res.list.insertAll(0, (loadingState.value as Success).response); - } - loadingState.value = LoadingState.success(res.list); - return true; - } - - @override - Future customGetData() => MemberHttp.spaceDynamic( - mid: mid, - page: currentPage, - ); -} diff --git a/lib/pages/member/new/controller.dart b/lib/pages/member/new/controller.dart index d4ec924f..fce40be5 100644 --- a/lib/pages/member/new/controller.dart +++ b/lib/pages/member/new/controller.dart @@ -6,7 +6,7 @@ import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/space/data.dart'; import 'package:PiliPlus/models/space/item.dart'; import 'package:PiliPlus/models/space/tab2.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -20,7 +20,7 @@ extension MemberTabTypeExt on MemberTabType { String get title => ['默认', '首页', '动态', '投稿', '收藏', '番剧'][index]; } -class MemberControllerNew extends CommonController +class MemberControllerNew extends CommonDataController with GetTickerProviderStateMixin { MemberControllerNew({required this.mid}); int mid; @@ -58,7 +58,7 @@ class MemberControllerNew extends CommonController ]; @override - bool customHandleResponse(Success response) { + bool customHandleResponse(bool isRefresh, Success response) { Data data = response.response; username = data.card?.name ?? ''; isFollow.value = data.card?.relation?.isFollow == 1; @@ -138,7 +138,7 @@ class MemberControllerNew extends CommonController } @override - Future customGetData() => MemberHttp.space( + Future> customGetData() => MemberHttp.space( mid: mid, fromViewAid: fromViewAid, ); diff --git a/lib/pages/member/new/member_page.dart b/lib/pages/member/new/member_page.dart index 11fa83bc..a9fccc8f 100644 --- a/lib/pages/member/new/member_page.dart +++ b/lib/pages/member/new/member_page.dart @@ -24,8 +24,7 @@ class MemberPageNew extends StatefulWidget { State createState() => _MemberPageNewState(); } -class _MemberPageNewState extends State - with TickerProviderStateMixin { +class _MemberPageNewState extends State { late final int _mid; late final String _heroTag; late final MemberControllerNew _userController; diff --git a/lib/pages/member_coin/controller.dart b/lib/pages/member_coin/controller.dart index d0c9878b..a8f55857 100644 --- a/lib/pages/member_coin/controller.dart +++ b/lib/pages/member_coin/controller.dart @@ -1,8 +1,10 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/models/member/coin.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; -class MemberCoinController extends CommonController { +class MemberCoinController extends CommonListController< + List?, MemberCoinsDataModel> { final dynamic mid; MemberCoinController({this.mid}); @@ -13,6 +15,6 @@ class MemberCoinController extends CommonController { } @override - Future customGetData() => + Future?>> customGetData() => MemberHttp.getRecentCoinVideo(mid: mid); } diff --git a/lib/pages/member_coin/view.dart b/lib/pages/member_coin/view.dart index c047a425..38467016 100644 --- a/lib/pages/member_coin/view.dart +++ b/lib/pages/member_coin/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/member/coin.dart'; import 'package:PiliPlus/pages/member_coin/controller.dart'; import 'package:PiliPlus/pages/member_coin/widgets/item.dart'; import 'package:PiliPlus/utils/grid.dart'; @@ -41,10 +42,10 @@ class _MemberCoinPageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? GridView.builder( padding: EdgeInsets.only( top: StyleString.safeSpace - 5, @@ -59,9 +60,9 @@ class _MemberCoinPageState extends State { childAspectRatio: StyleString.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(75), ), - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, itemBuilder: (context, index) { - return MemberCoinsItem(coinItem: loadingState.response[index]); + return MemberCoinsItem(coinItem: loadingState.response![index]); }, ) : scrollErrorWidget(callback: _ctr.onReload), diff --git a/lib/pages/member_dynamics/controller.dart b/lib/pages/member_dynamics/controller.dart index 54562777..90b3e4f9 100644 --- a/lib/pages/member_dynamics/controller.dart +++ b/lib/pages/member_dynamics/controller.dart @@ -1,12 +1,13 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/msg.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -class MemberDynamicsController extends CommonController { +class MemberDynamicsController + extends CommonListController { MemberDynamicsController(this.mid); int mid; String offset = ''; @@ -32,22 +33,24 @@ class MemberDynamicsController extends CommonController { } @override - bool customHandleResponse(Success response) { - DynamicsDataModel data = response.response; - offset = data.offset?.isNotEmpty == true ? data.offset! : '-1'; - if (data.hasMore == false || data.items.isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - data.items ??= []; - data.items?.insertAll(0, (loadingState.value as Success).response); - } - loadingState.value = LoadingState.success(data.items); - return true; + List? getDataList(DynamicsDataModel response) { + return response.items; } @override - Future customGetData() => MemberHttp.memberDynamic( + bool customHandleResponse( + bool isRefresh, Success response) { + DynamicsDataModel data = response.response; + offset = data.offset?.isNotEmpty == true ? data.offset! : '-1'; + if (data.hasMore == false) { + isEnd = true; + } + return false; + } + + @override + Future> customGetData() => + MemberHttp.memberDynamic( offset: offset, mid: mid, ); @@ -55,9 +58,9 @@ class MemberDynamicsController extends CommonController { Future onRemove(dynamicId) async { var res = await MsgHttp.removeDynamic(dynamicId); if (res['status']) { - List list = (loadingState.value as Success).response; + List list = (loadingState.value as Success).response; list.removeWhere((item) => item.idStr == dynamicId); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); SmartDialog.showToast('删除成功'); } else { SmartDialog.showToast(res['msg']); diff --git a/lib/pages/member_dynamics/view.dart b/lib/pages/member_dynamics/view.dart index 41d20fc4..b27eeb49 100644 --- a/lib/pages/member_dynamics/view.dart +++ b/lib/pages/member_dynamics/view.dart @@ -1,5 +1,7 @@ +import 'package:PiliPlus/common/skeleton/dynamic_card.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/pages/member_dynamics/index.dart'; @@ -56,26 +58,57 @@ class _MemberDynamicsPageState extends State onRefresh: () async { await _memberDynamicController.onRefresh(); }, - child: Obx( - () => _memberDynamicController.loadingState.value is Loading - ? Center( - child: CircularProgressIndicator(), - ) - : CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - _buildContent(_memberDynamicController.loadingState.value), - ], - ), + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + Obx( + () => _buildContent(_memberDynamicController.loadingState.value), + ) + ], ), ); - _buildContent(LoadingState loadingState) { + Widget skeleton() { + if (!dynamicsWaterfallFlow) { + return SliverCrossAxisGroup( + slivers: [ + const SliverFillRemaining(), + SliverConstrainedCrossAxis( + maxExtent: Grid.smallCardWidth * 2, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return const DynamicCardSkeleton(); + }, + childCount: 10, + ), + ), + ), + const SliverFillRemaining() + ], + ); + } + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + crossAxisSpacing: StyleString.cardSpace / 2, + mainAxisSpacing: StyleString.cardSpace / 2, + maxCrossAxisExtent: Grid.smallCardWidth * 2, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: 50, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + return const DynamicCardSkeleton(); + }, + childCount: 10, + ), + ); + } + + Widget _buildContent(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => HttpError( - callback: _memberDynamicController.onReload, - ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Loading() => skeleton(), + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( bottom: MediaQuery.paddingOf(context).bottom + 80, @@ -83,24 +116,12 @@ class _MemberDynamicsPageState extends State sliver: dynamicsWaterfallFlow ? SliverWaterfallFlow.extent( maxCrossAxisExtent: Grid.smallCardWidth * 2, - //cacheExtent: 0.0, crossAxisSpacing: StyleString.safeSpace, - // mainAxisSpacing: StyleString.safeSpace, - - /// follow max child trailing layout offset and layout with full cross axis extend - /// last child as loadmore item/no more item in [GridView] and [WaterfallFlow] - /// with full cross axis extend - // LastChildLayoutType.fullCrossAxisExtend, - - /// as foot at trailing and layout with full cross axis extend - /// show no more item at trailing when children are not full of viewport - /// if children is full of viewport, it's the same as fullCrossAxisExtend - // LastChildLayoutType.foot, lastChildLayoutTypeBuilder: (index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _memberDynamicController.onLoadMore(); } - return index == loadingState.response.length + return index == loadingState.response!.length ? LastChildLayoutType.foot : LastChildLayoutType.none; }, @@ -121,15 +142,16 @@ class _MemberDynamicsPageState extends State sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) { - if (index == loadingState.response.length - 1) { + if (index == + loadingState.response!.length - 1) { _memberDynamicController.onLoadMore(); } return DynamicPanel( - item: loadingState.response[index], + item: loadingState.response![index], onRemove: _memberDynamicController.onRemove, ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ), diff --git a/lib/pages/member_like/controller.dart b/lib/pages/member_like/controller.dart index 22011dee..8365bf7c 100644 --- a/lib/pages/member_like/controller.dart +++ b/lib/pages/member_like/controller.dart @@ -1,8 +1,10 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/models/member/coin.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; -class MemberLikeController extends CommonController { +class MemberLikeController extends CommonListController< + List?, MemberCoinsDataModel> { final dynamic mid; MemberLikeController({this.mid}); @@ -13,6 +15,6 @@ class MemberLikeController extends CommonController { } @override - Future customGetData() => + Future?>> customGetData() => MemberHttp.getRecentLikeVideo(mid: mid); } diff --git a/lib/pages/member_like/view.dart b/lib/pages/member_like/view.dart index c4bea1d0..60c6b9cd 100644 --- a/lib/pages/member_like/view.dart +++ b/lib/pages/member_like/view.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/member/coin.dart'; import 'package:PiliPlus/pages/member_coin/widgets/item.dart'; import 'package:PiliPlus/pages/member_like/controller.dart'; import 'package:PiliPlus/utils/grid.dart'; @@ -41,10 +42,10 @@ class _MemberLikePageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? GridView.builder( padding: EdgeInsets.only( top: StyleString.safeSpace - 5, @@ -59,9 +60,9 @@ class _MemberLikePageState extends State { childAspectRatio: StyleString.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(75), ), - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, itemBuilder: (context, index) { - return MemberCoinsItem(coinItem: loadingState.response[index]); + return MemberCoinsItem(coinItem: loadingState.response![index]); }, ) : scrollErrorWidget(callback: _ctr.onReload), diff --git a/lib/pages/member_search/controller.dart b/lib/pages/member_search/controller.dart index 395639ff..69eeae63 100644 --- a/lib/pages/member_search/controller.dart +++ b/lib/pages/member_search/controller.dart @@ -91,22 +91,31 @@ class MemberSearchController extends GetxController ); if (res['status']) { DynamicsDataModel data = res['data']; - if (data.hasMore == false || data.items.isNullOrEmpty) { - isEndDynamic = true; - } - if (isRefresh) { - dynamicCount.value = data.total ?? 0; - } + List? items = data.items; + dynamicCount.value = data.total ?? 0; offset = data.offset ?? ''; - if (isRefresh.not && dynamicState.value is Success) { - data.items ??= []; - data.items!.insertAll(0, (dynamicState.value as Success).response); - } - if (!isEndDynamic && (data.items?.length ?? 0) >= dynamicCount.value) { + + if (data.hasMore == false || items.isNullOrEmpty) { isEndDynamic = true; + dynamicState.value = LoadingState.success(items); + return; + } + + if (isRefresh) { + if (items!.length >= dynamicCount.value) { + isEndDynamic = true; + } + dynamicState.value = LoadingState.success(items); + } else if (dynamicState.value is Success) { + List currentList = + (dynamicState.value as Success).response; + currentList.addAll(items!); + if (currentList.length >= dynamicCount.value) { + isEndDynamic = true; + } + dynamicState.refresh(); } dynamicPn++; - dynamicState.value = LoadingState.success(data.items); } else if (isRefresh) { dynamicState.value = LoadingState.error(res['msg']); } @@ -124,24 +133,30 @@ class MemberSearchController extends GetxController ); if (res['status']) { MemberArchiveDataModel data = res['data']; + List? vlist = data.list?.vlist; + archiveCount.value = data.page?['count'] ?? 0; + + if (vlist.isNullOrEmpty) { + isEndArchive = true; + archiveState.value = LoadingState.success(vlist); + return; + } + if (isRefresh) { - archiveCount.value = data.page?['count'] ?? 0; - } - if (data.list == null || data.list!.vlist.isNullOrEmpty) { - isEndArchive = true; - } - if (isRefresh.not && archiveState.value is Success) { - data.list ??= ArchiveListModel(); - data.list!.vlist ??= []; - data.list!.vlist! - .insertAll(0, (archiveState.value as Success).response); - } - if (!isEndArchive && - (data.list?.vlist?.length ?? 0) >= archiveCount.value) { - isEndArchive = true; + if (vlist!.length >= archiveCount.value) { + isEndArchive = true; + } + archiveState.value = LoadingState.success(vlist); + } else if (dynamicState.value is Success) { + List currentList = + (dynamicState.value as Success).response; + currentList.addAll(vlist!); + if (currentList.length >= archiveCount.value) { + isEndDynamic = true; + } + archiveState.refresh(); } archivePn++; - archiveState.value = LoadingState.success(data.list?.vlist); } else if (isRefresh) { archiveState.value = LoadingState.error(res['msg']); } diff --git a/lib/pages/msg_feed_top/at_me/controller.dart b/lib/pages/msg_feed_top/at_me/controller.dart index de18054c..df1cdb5a 100644 --- a/lib/pages/msg_feed_top/at_me/controller.dart +++ b/lib/pages/msg_feed_top/at_me/controller.dart @@ -1,11 +1,10 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/models/msg/msgfeed_at_me.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -class AtMeController extends CommonController { +class AtMeController extends CommonListController { int cursor = -1; int cursorTime = -1; @@ -16,19 +15,19 @@ class AtMeController extends CommonController { } @override - bool customHandleResponse(Success response) { + List? getDataList(MsgFeedAtMe response) { + return response.items; + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { MsgFeedAtMe data = response.response; - if (data.cursor?.isEnd == true || data.items.isNullOrEmpty) { + if (data.cursor?.isEnd == true) { isEnd = true; } cursor = data.cursor?.id ?? -1; cursorTime = data.cursor?.time ?? -1; - if (currentPage != 1 && loadingState.value is Success) { - data.items ??= []; - data.items!.insertAll(0, (loadingState.value as Success).response); - } - loadingState.value = LoadingState.success(data.items); - return true; + return false; } @override @@ -39,16 +38,16 @@ class AtMeController extends CommonController { } @override - Future customGetData() => + Future> customGetData() => MsgHttp.msgFeedAtMe(cursor: cursor, cursorTime: cursorTime); Future onRemove(dynamic id, int index) async { try { var res = await MsgHttp.delMsgfeed(2, id); if (res['status']) { - List list = (loadingState.value as Success).response; + List list = (loadingState.value as Success).response; list.removeAt(index); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); SmartDialog.showToast('删除成功'); } else { SmartDialog.showToast(res['msg']); diff --git a/lib/pages/msg_feed_top/at_me/view.dart b/lib/pages/msg_feed_top/at_me/view.dart index 54cef8c3..c16d2e2b 100644 --- a/lib/pages/msg_feed_top/at_me/view.dart +++ b/lib/pages/msg_feed_top/at_me/view.dart @@ -3,6 +3,7 @@ import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/msg/msgfeed_at_me.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -35,20 +36,20 @@ class _AtMePageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? ListView.separated( - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only( bottom: MediaQuery.paddingOf(context).bottom + 80), itemBuilder: (context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _atMeController.onLoadMore(); } - final item = loadingState.response[index]; + final item = loadingState.response![index]; return ListTile( onTap: () { String? nativeUri = item.item?.nativeUri; @@ -103,10 +104,9 @@ class _AtMePageState extends State { subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if ((item.item?.sourceContent as String?)?.isNotEmpty == - true) ...[ + if (item.item?.sourceContent?.isNotEmpty == true) ...[ const SizedBox(height: 4), - Text(item.item?.sourceContent, + Text(item.item!.sourceContent!, maxLines: 3, overflow: TextOverflow.ellipsis, style: Theme.of(context) diff --git a/lib/pages/msg_feed_top/like_me/controller.dart b/lib/pages/msg_feed_top/like_me/controller.dart index 658b3e8b..5dc19552 100644 --- a/lib/pages/msg_feed_top/like_me/controller.dart +++ b/lib/pages/msg_feed_top/like_me/controller.dart @@ -1,12 +1,12 @@ import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/http/msg.dart'; +import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/models/msg/msgfeed_like_me.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -class LikeMeController extends CommonController { +class LikeMeController extends CommonDataController { int cursor = -1; int cursorTime = -1; @@ -17,7 +17,7 @@ class LikeMeController extends CommonController { } @override - bool customHandleResponse(Success response) { + bool customHandleResponse(bool isRefresh, Success response) { MsgFeedLikeMe data = response.response; if (data.total?.cursor?.isEnd == true || data.total?.items.isNullOrEmpty == true) { @@ -46,7 +46,7 @@ class LikeMeController extends CommonController { } @override - Future customGetData() => + Future> customGetData() => MsgHttp.msgFeedLikeMe(cursor: cursor, cursorTime: cursorTime); Future onRemove(dynamic id, int index, bool isLatest) async { diff --git a/lib/pages/msg_feed_top/reply_me/controller.dart b/lib/pages/msg_feed_top/reply_me/controller.dart index 6c620629..332dd62c 100644 --- a/lib/pages/msg_feed_top/reply_me/controller.dart +++ b/lib/pages/msg_feed_top/reply_me/controller.dart @@ -1,11 +1,11 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -class ReplyMeController extends CommonController { +class ReplyMeController + extends CommonListController { int cursor = -1; int cursorTime = -1; @@ -16,19 +16,19 @@ class ReplyMeController extends CommonController { } @override - bool customHandleResponse(Success response) { - MsgFeedReplyMe data = response.response; - if (data.cursor?.isEnd == true || data.items.isNullOrEmpty) { + List? getDataList(MsgFeedReplyMe response) { + return response.items; + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { + final data = response.response; + if (data.cursor?.isEnd == true) { isEnd = true; } cursor = data.cursor?.id ?? -1; cursorTime = data.cursor?.time ?? -1; - if (currentPage != 1 && loadingState.value is Success) { - data.items ??= []; - data.items!.insertAll(0, (loadingState.value as Success).response); - } - loadingState.value = LoadingState.success(data.items); - return true; + return false; } @override @@ -39,16 +39,16 @@ class ReplyMeController extends CommonController { } @override - Future customGetData() => + Future> customGetData() => MsgHttp.msgFeedReplyMe(cursor: cursor, cursorTime: cursorTime); Future onRemove(dynamic id, int index) async { try { var res = await MsgHttp.delMsgfeed(1, id); if (res['status']) { - List list = (loadingState.value as Success).response; + List list = (loadingState.value as Success).response; list.removeAt(index); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); SmartDialog.showToast('删除成功'); } else { SmartDialog.showToast(res['msg']); diff --git a/lib/pages/msg_feed_top/reply_me/view.dart b/lib/pages/msg_feed_top/reply_me/view.dart index 5f7b78a9..d67ac989 100644 --- a/lib/pages/msg_feed_top/reply_me/view.dart +++ b/lib/pages/msg_feed_top/reply_me/view.dart @@ -34,21 +34,21 @@ class _ReplyMePageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? ListView.separated( - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only( bottom: MediaQuery.paddingOf(context).bottom + 80), itemBuilder: (context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _replyMeController.onLoadMore(); } - ReplyMeItems item = loadingState.response[index]; + ReplyMeItems item = loadingState.response![index]; return ListTile( onTap: () { String? nativeUri = item.item?.nativeUri; @@ -121,12 +121,8 @@ class _ReplyMePageState extends State { Text(item.item?.sourceContent ?? "", style: Theme.of(context).textTheme.bodyMedium), const SizedBox(height: 4), - if (loadingState - .response[index].item?.targetReplyContent != - null && - loadingState - .response[index].item?.targetReplyContent != - "") + if (item.item?.targetReplyContent != null && + item.item?.targetReplyContent != "") Text("| ${item.item?.targetReplyContent}", maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/pages/msg_feed_top/sys_msg/controller.dart b/lib/pages/msg_feed_top/sys_msg/controller.dart index a2fa1326..1b23651c 100644 --- a/lib/pages/msg_feed_top/sys_msg/controller.dart +++ b/lib/pages/msg_feed_top/sys_msg/controller.dart @@ -1,10 +1,12 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/models/msg/msgfeed_sys_msg.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:PiliPlus/http/msg.dart'; -class SysMsgController extends CommonController { +class SysMsgController + extends CommonListController?, SystemNotifyList> { final pageSize = 20; int cursor = -1; @@ -41,9 +43,9 @@ class SysMsgController extends CommonController { try { var res = await MsgHttp.delSysMsg(id); if (res['status']) { - List list = (loadingState.value as Success).response; + List list = (loadingState.value as Success).response; list.removeAt(index); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); SmartDialog.showToast('删除成功'); } else { SmartDialog.showToast(res['msg']); @@ -52,6 +54,6 @@ class SysMsgController extends CommonController { } @override - Future customGetData() => + Future?>> customGetData() => MsgHttp.msgFeedNotify(cursor: cursor, pageSize: pageSize); } diff --git a/lib/pages/msg_feed_top/sys_msg/view.dart b/lib/pages/msg_feed_top/sys_msg/view.dart index 30a9e0a9..62c58033 100644 --- a/lib/pages/msg_feed_top/sys_msg/view.dart +++ b/lib/pages/msg_feed_top/sys_msg/view.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/msg/msgfeed_sys_msg.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -39,21 +40,21 @@ class _SysMsgPageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? ListView.separated( - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only( bottom: MediaQuery.paddingOf(context).bottom + 80), itemBuilder: (context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _sysMsgController.onLoadMore(); } - final item = loadingState.response[index]; + final item = loadingState.response![index]; String? content = item.content; if (content != null) { try { diff --git a/lib/pages/rank/view.dart b/lib/pages/rank/view.dart index af60513f..b34e9d12 100644 --- a/lib/pages/rank/view.dart +++ b/lib/pages/rank/view.dart @@ -10,7 +10,7 @@ class RankPage extends StatefulWidget { } class _RankPageState extends State - with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { + with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin { final RankController _rankController = Get.put(RankController()); @override diff --git a/lib/pages/rank/zone/controller.dart b/lib/pages/rank/zone/controller.dart index 455e6d06..1c85e257 100644 --- a/lib/pages/rank/zone/controller.dart +++ b/lib/pages/rank/zone/controller.dart @@ -1,8 +1,10 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/video.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/models/model_hot_video_item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; -class ZoneController extends CommonController { +class ZoneController + extends CommonListController, HotVideoItemModel> { ZoneController({required this.zoneID}); int zoneID; @@ -13,5 +15,6 @@ class ZoneController extends CommonController { } @override - Future customGetData() => VideoHttp.getRankVideoList(zoneID); + Future>> customGetData() => + VideoHttp.getRankVideoList(zoneID); } diff --git a/lib/pages/rank/zone/view.dart b/lib/pages/rank/zone/view.dart index f80fdf91..2c91d662 100644 --- a/lib/pages/rank/zone/view.dart +++ b/lib/pages/rank/zone/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/model_hot_video_item.dart'; import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -47,18 +48,7 @@ class _ZonePageState extends CommonPageState top: StyleString.safeSpace - 5, bottom: MediaQuery.of(context).padding.bottom + 80, ), - sliver: Obx( - () => controller.loadingState.value is Loading - ? _buildSkeleton() - : controller.loadingState.value is Success - ? _buildBody(controller.loadingState.value as Success) - : HttpError( - errMsg: controller.loadingState.value is Error - ? (controller.loadingState.value as Error).errMsg - : '没有相关数据', - callback: controller.onReload, - ), - ), + sliver: Obx(() => _buildBody(controller.loadingState.value)), ), ], ), @@ -81,22 +71,32 @@ class _ZonePageState extends CommonPageState ); } - Widget _buildBody(Success loadingState) { - return SliverGrid( - gridDelegate: SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: 2, - maxCrossAxisExtent: Grid.mediumCardWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.2, - ), - delegate: SliverChildBuilderDelegate( - (context, index) { - return VideoCardH( - videoItem: loadingState.response[index], - showPubdate: true, - ); - }, - childCount: loadingState.response.length, - ), - ); + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => _buildSkeleton(), + Success() => loadingState.response?.isNotEmpty == true + ? SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: 2, + maxCrossAxisExtent: Grid.mediumCardWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.2, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + return VideoCardH( + videoItem: loadingState.response![index], + showPubdate: true, + ); + }, + childCount: loadingState.response!.length, + ), + ) + : HttpError(callback: controller.onReload), + Error() => HttpError( + errMsg: loadingState.errMsg, + callback: controller.onReload, + ), + _ => throw UnimplementedError(), + }; } } diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index a578a078..bf696109 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -1,9 +1,9 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/video.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/utils/storage.dart'; -class RcmdController extends CommonController { +class RcmdController extends CommonListController { late bool enableSaveLastData = GStorage.setting .get(SettingBoxKey.enableSaveLastData, defaultValue: false); late bool appRcmd = true; diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 7b6c43d7..b90a3ccc 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -49,17 +49,7 @@ class _RcmdPageState extends CommonPageState top: StyleString.cardSpace, bottom: MediaQuery.paddingOf(context).bottom, ), - sliver: Obx( - () => controller.loadingState.value is Loading || - controller.loadingState.value is Success - ? contentGrid(controller.loadingState.value) - : HttpError( - errMsg: controller.loadingState.value is Error - ? (controller.loadingState.value as Error).errMsg - : '没有相关数据', - callback: controller.onReload, - ), - ), + sliver: Obx(() => _buildBody(controller.loadingState.value)), ), ], ), @@ -67,81 +57,107 @@ class _RcmdPageState extends CommonPageState ); } - Widget contentGrid(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => _buildSkeleton(), + Success() => loadingState.response?.isNotEmpty == true + ? SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.cardSpace, + maxCrossAxisExtent: Grid.smallCardWidth, + childAspectRatio: StyleString.aspectRatio, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), + ), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (index == loadingState.response!.length - 1) { + controller.onLoadMore(); + } + if (controller.lastRefreshAt != null) { + if (controller.lastRefreshAt == index) { + return GestureDetector( + onTap: () { + controller.animateToTop(); + controller.onRefresh(); + }, + child: Card( + margin: EdgeInsets.zero, + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Text( + '上次看到这里\n点击刷新', + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ), + ), + ); + } + int actualIndex = controller.lastRefreshAt == null + ? index + : index > controller.lastRefreshAt! + ? index - 1 + : index; + return VideoCardV( + videoItem: loadingState.response![actualIndex], + onRemove: () { + if (controller.lastRefreshAt != null && + actualIndex < controller.lastRefreshAt!) { + controller.lastRefreshAt = + controller.lastRefreshAt! - 1; + } + ((controller.loadingState.value as Success).response + as List) + .removeAt(actualIndex); + controller.loadingState.refresh(); + }, + ); + } else { + return VideoCardV( + videoItem: loadingState.response![index], + onRemove: () { + ((controller.loadingState.value as Success).response + as List) + .removeAt(index); + controller.loadingState.refresh(); + }, + ); + } + }, + childCount: controller.lastRefreshAt != null + ? loadingState.response!.length + 1 + : loadingState.response!.length, + ), + ) + : HttpError(callback: controller.onReload), + Error() => HttpError( + errMsg: loadingState.errMsg, + callback: controller.onReload, + ), + LoadingState() => throw UnimplementedError(), + }; + } + + Widget _buildSkeleton() { return SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( - // 行间距 mainAxisSpacing: StyleString.cardSpace, - // 列间距 crossAxisSpacing: StyleString.cardSpace, - // 最大宽度 maxCrossAxisExtent: Grid.smallCardWidth, childAspectRatio: StyleString.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), ), delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - if (loadingState is Success && - index == loadingState.response.length - 1) { - controller.onLoadMore(); - } - if (loadingState is Success) { - if (controller.lastRefreshAt != null) { - if (controller.lastRefreshAt == index) { - return GestureDetector( - onTap: () { - controller.animateToTop(); - controller.onRefresh(); - }, - child: Card( - margin: EdgeInsets.zero, - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Text( - '上次看到这里\n点击刷新', - textAlign: TextAlign.center, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - ), - ); - } - int actualIndex = controller.lastRefreshAt == null - ? index - : index > controller.lastRefreshAt! - ? index - 1 - : index; - return VideoCardV( - videoItem: loadingState.response[actualIndex], - onRemove: () { - if (controller.lastRefreshAt != null && - actualIndex < controller.lastRefreshAt!) { - controller.lastRefreshAt = controller.lastRefreshAt! - 1; - } - controller.loadingState.value = LoadingState.success( - (loadingState.response as List)..removeAt(actualIndex)); - }, - ); - } else { - return VideoCardV( - videoItem: loadingState.response[index], - onRemove: () { - controller.loadingState.value = LoadingState.success( - (loadingState.response as List)..removeAt(index)); - }, - ); - } - } + (context, index) { return const VideoCardVSkeleton(); }, - childCount: loadingState is Success - ? controller.lastRefreshAt != null - ? loadingState.response.length + 1 - : loadingState.response.length - : 10, + childCount: 10, ), ); } diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index ba9fbae0..c0c52d78 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/search_result/controller.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:flutter/material.dart'; @@ -7,7 +7,7 @@ import 'package:get/get.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models/common/search_type.dart'; -class SearchPanelController extends CommonController { +class SearchPanelController extends CommonListController { SearchPanelController({ required this.keyword, required this.searchType, @@ -28,9 +28,14 @@ class SearchPanelController extends CommonController { int? pubEnd; bool? hasJump2Video; + SearchResultController? searchResultController; + @override void onInit() { super.onInit(); + try { + searchResultController = Get.find(tag: tag); + } catch (_) {} if (searchType == SearchType.video) { jump2Video(); } else if (searchType == SearchType.article) { @@ -40,31 +45,21 @@ class SearchPanelController extends CommonController { } @override - bool customHandleResponse(Success response) { - try { - Get.find(tag: tag).count[searchType.index] = - response.response.numResults; - } catch (_) {} - 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); - } - loadingState.value = LoadingState.success(response.response.list); - if (searchType == SearchType.video && - hasJump2Video != true && - currentPage == 1) { - hasJump2Video = true; - onPushDetail(response.response.list); - } - } else { - isEnd = true; - if (currentPage == 1) { - loadingState.value = LoadingState.success([]); - } + List? getDataList(response) { + return response.list; + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { + searchResultController?.count[searchType.index] = + response.response.numResults; + + if (searchType == SearchType.video && hasJump2Video != true && isRefresh) { + hasJump2Video = true; + onPushDetail(response.response.list); } - return true; + + return false; } void jump2Video() { @@ -102,10 +97,12 @@ class SearchPanelController extends CommonController { } void onPushDetail(resultList) async { - int? aid = int.tryParse(keyword); - if (aid != null && resultList.first.aid == aid) { - PiliScheme.videoPush(aid, null, showDialog: false); - } + try { + int? aid = int.tryParse(keyword); + if (aid != null && resultList.first.aid == aid) { + PiliScheme.videoPush(aid, null, showDialog: false); + } + } catch (_) {} } @override diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index b4419fd0..45c823d8 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -61,7 +61,7 @@ class _SearchPanelState extends State ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { if (loadingState is Loading) { return CustomScrollView( physics: const AlwaysScrollableScrollPhysics(), diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index b01cf603..f3a08444 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -16,8 +16,10 @@ import 'package:PiliPlus/utils/utils.dart'; import '../../../utils/grid.dart'; -Widget searchArticlePanel(BuildContext context, - SearchPanelController searchPanelCtr, LoadingState loadingState) { +Widget searchArticlePanel( + BuildContext context, + SearchPanelController searchPanelCtr, + LoadingState?> loadingState) { TextStyle textStyle = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); @@ -80,7 +82,7 @@ Widget searchArticlePanel(BuildContext context, ), switch (loadingState) { Loading() => errorWidget(), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( bottom: StyleString.safeSpace + @@ -94,26 +96,26 @@ Widget searchArticlePanel(BuildContext context, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { searchPanelCtr.onLoadMore(); } + final item = loadingState.response![index]; return InkWell( onTap: () { Get.toNamed('/htmlRender', parameters: { - 'url': - 'www.bilibili.com/read/cv${loadingState.response[index].id}', - 'title': loadingState.response[index].subTitle, - 'id': 'cv${loadingState.response[index].id}', + 'url': 'www.bilibili.com/read/cv${item.id}', + 'title': item.subTitle, + 'id': 'cv${item.id}', 'dynamicType': 'read' }); }, onLongPress: () => imageSaveDialog( context: context, - title: (loadingState.response[index].title as List?) + title: (item.title as List?) ?.map((item) => item['text']) .join() ?? '', - cover: loadingState.response[index].imageUrls.first, + cover: item.imageUrls.first, ), child: Padding( padding: const EdgeInsets.symmetric( @@ -135,11 +137,8 @@ Widget searchArticlePanel(BuildContext context, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (loadingState - .response[index].imageUrls != - null && - loadingState.response[index].imageUrls - .isNotEmpty) + if (item.imageUrls != null && + item.imageUrls.isNotEmpty) AspectRatio( aspectRatio: StyleString.aspectRatio, child: LayoutBuilder( @@ -151,8 +150,7 @@ Widget searchArticlePanel(BuildContext context, return NetworkImgLayer( width: maxWidth, height: maxHeight, - src: loadingState.response[index] - .imageUrls.first, + src: item.imageUrls.first, ); }), ), @@ -167,8 +165,7 @@ Widget searchArticlePanel(BuildContext context, maxLines: 2, TextSpan( children: [ - for (var i in loadingState - .response[index].title) ...[ + for (var i in item.title) ...[ TextSpan( text: i['text'], style: TextStyle( @@ -187,19 +184,15 @@ Widget searchArticlePanel(BuildContext context, ), const Spacer(), Text( - Utils.dateFormat( - loadingState - .response[index].pubTime, + Utils.dateFormat(item.pubTime, formatType: 'detail'), style: textStyle), Row( children: [ - Text( - '${loadingState.response[index].view}浏览', + Text('${item.view}浏览', style: textStyle), Text(' • ', style: textStyle), - Text( - '${loadingState.response[index].reply}评论', + Text('${item.reply}评论', style: textStyle), ], ), @@ -214,7 +207,7 @@ Widget searchArticlePanel(BuildContext context, ), ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ) diff --git a/lib/pages/search_panel/widgets/live_panel.dart b/lib/pages/search_panel/widgets/live_panel.dart index 7396ea69..49c5639f 100644 --- a/lib/pages/search_panel/widgets/live_panel.dart +++ b/lib/pages/search_panel/widgets/live_panel.dart @@ -8,10 +8,11 @@ import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import '../../../utils/grid.dart'; -Widget searchLivePanel(BuildContext context, ctr, LoadingState loadingState) { +Widget searchLivePanel( + BuildContext context, ctr, LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? GridView.builder( padding: EdgeInsets.only( left: StyleString.safeSpace, @@ -27,12 +28,12 @@ Widget searchLivePanel(BuildContext context, ctr, LoadingState loadingState) { childAspectRatio: StyleString.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(80), ), - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, itemBuilder: (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { ctr.onLoadMore(); } - return LiveItem(liveItem: loadingState.response[index]); + return LiveItem(liveItem: loadingState.response![index]); }, ) : errorWidget( diff --git a/lib/pages/search_panel/widgets/media_bangumi_panel.dart b/lib/pages/search_panel/widgets/media_bangumi_panel.dart index 4d7fefb9..d9d56e07 100644 --- a/lib/pages/search_panel/widgets/media_bangumi_panel.dart +++ b/lib/pages/search_panel/widgets/media_bangumi_panel.dart @@ -9,11 +9,12 @@ import 'package:PiliPlus/utils/utils.dart'; import '../../../utils/grid.dart'; -Widget searchBangumiPanel(context, ctr, LoadingState loadingState) { +Widget searchBangumiPanel( + context, ctr, LoadingState?> loadingState) { late TextStyle style = TextStyle(fontSize: 13); return switch (loadingState) { Loading() => loadingWidget, - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? CustomScrollView( controller: ctr.scrollController, slivers: [ @@ -28,10 +29,10 @@ Widget searchBangumiPanel(context, ctr, LoadingState loadingState) { ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { ctr.onLoadMore(); } - var i = loadingState.response[index]; + var i = loadingState.response![index]; return InkWell( onTap: () { Utils.viewBangumi(seasonId: i.seasonId); @@ -139,7 +140,7 @@ Widget searchBangumiPanel(context, ctr, LoadingState loadingState) { ), ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ), diff --git a/lib/pages/search_panel/widgets/user_panel.dart b/lib/pages/search_panel/widgets/user_panel.dart index a981d60b..3b39bce8 100644 --- a/lib/pages/search_panel/widgets/user_panel.dart +++ b/lib/pages/search_panel/widgets/user_panel.dart @@ -14,8 +14,10 @@ import 'package:PiliPlus/utils/utils.dart'; import '../../../utils/grid.dart'; -Widget searchUserPanel(BuildContext context, - SearchPanelController searchPanelCtr, LoadingState loadingState) { +Widget searchUserPanel( + BuildContext context, + SearchPanelController searchPanelCtr, + LoadingState?> loadingState) { TextStyle style = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); @@ -78,7 +80,7 @@ Widget searchUserPanel(BuildContext context, ), switch (loadingState) { Loading() => errorWidget(), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom + 80, @@ -90,10 +92,10 @@ Widget searchUserPanel(BuildContext context, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { searchPanelCtr.onLoadMore(); } - var i = loadingState.response[index]; + var i = loadingState.response![index]; String heroTag = Utils.makeHeroTag(i!.mid); return InkWell( onTap: () => Get.toNamed('/member?mid=${i.mid}', @@ -170,7 +172,7 @@ Widget searchUserPanel(BuildContext context, ), ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ) diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index 1b4f1066..ac2a4258 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -16,8 +16,10 @@ import 'package:intl/intl.dart'; import '../../../common/constants.dart'; import '../../../utils/grid.dart'; -Widget searchVideoPanel(BuildContext context, - SearchPanelController searchPanelCtr, LoadingState loadingState) { +Widget searchVideoPanel( + BuildContext context, + SearchPanelController searchPanelCtr, + LoadingState?> loadingState) { final controller = Get.put(VideoPanelController(), tag: searchPanelCtr.tag); return CustomScrollView( controller: searchPanelCtr.scrollController, @@ -90,7 +92,7 @@ Widget searchVideoPanel(BuildContext context, ), switch (loadingState) { Loading() => errorWidget(), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverPadding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).padding.bottom + 80, @@ -103,15 +105,15 @@ Widget searchVideoPanel(BuildContext context, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { searchPanelCtr.onLoadMore(); } return VideoCardH( - videoItem: loadingState.response[index], + videoItem: loadingState.response![index], showPubdate: true, ); }, - childCount: loadingState.response.length, + childCount: loadingState.response!.length, ), ), ) diff --git a/lib/pages/search_result/view.dart b/lib/pages/search_result/view.dart index e0bb8a7c..210852f3 100644 --- a/lib/pages/search_result/view.dart +++ b/lib/pages/search_result/view.dart @@ -15,7 +15,7 @@ class SearchResultPage extends StatefulWidget { } class _SearchResultPageState extends State - with TickerProviderStateMixin { + with SingleTickerProviderStateMixin { late SearchResultController _searchResultController; late TabController _tabController; final String _tag = DateTime.now().millisecondsSinceEpoch.toString(); diff --git a/lib/pages/subscription/controller.dart b/lib/pages/subscription/controller.dart index 2bdbde93..9bf5e861 100644 --- a/lib/pages/subscription/controller.dart +++ b/lib/pages/subscription/controller.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -8,7 +8,8 @@ import 'package:PiliPlus/utils/storage.dart'; import '../../models/user/sub_folder.dart'; -class SubController extends CommonController { +class SubController + extends CommonListController?, SubFolderItemData> { dynamic mid; @override @@ -49,9 +50,10 @@ class SubController extends CommonController { var res = await UserHttp.cancelSub( id: subFolderItem.id!, type: subFolderItem.type!); if (res['status']) { - List list = (loadingState.value as Success).response; + List list = + (loadingState.value as Success).response; list.remove(subFolderItem); - loadingState.value = LoadingState.success(list); + loadingState.refresh(); SmartDialog.showToast('取消订阅成功'); } else { SmartDialog.showToast(res['msg']); @@ -66,7 +68,8 @@ class SubController extends CommonController { } @override - Future customGetData() => UserHttp.userSubFolder( + Future?>> customGetData() => + UserHttp.userSubFolder( pn: currentPage, ps: 20, mid: mid, diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index 7cf3f234..37c16f80 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/user/sub_folder.dart'; import 'package:PiliPlus/pages/subscription/widgets/item.dart'; import 'package:PiliPlus/utils/grid.dart'; import 'package:flutter/material.dart'; @@ -41,7 +42,7 @@ class _SubPageState extends State { ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( @@ -54,7 +55,7 @@ class _SubPageState extends State { childCount: 10, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: 2, @@ -62,13 +63,13 @@ class _SubPageState extends State { childAspectRatio: StyleString.aspectRatio * 2.2, ), delegate: SliverChildBuilderDelegate( - childCount: loadingState.response.length, + childCount: loadingState.response!.length, (BuildContext context, int index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _subController.onLoadMore(); } return SubItem( - subFolderItem: loadingState.response[index], + subFolderItem: loadingState.response![index], cancelSub: _subController.cancelSub, ); }, diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 112a8653..bffa6ad5 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -111,7 +111,7 @@ class VideoInfo extends StatefulWidget { State createState() => _VideoInfoState(); } -class _VideoInfoState extends State with TickerProviderStateMixin { +class _VideoInfoState extends State { late final VideoDetailController videoDetailCtr; Map get videoItem => videoIntroController.videoItem; diff --git a/lib/pages/video/detail/member/controller.dart b/lib/pages/video/detail/member/controller.dart index 13ddfa09..c931e517 100644 --- a/lib/pages/video/detail/member/controller.dart +++ b/lib/pages/video/detail/member/controller.dart @@ -2,13 +2,13 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/models/space_archive/data.dart'; import 'package:PiliPlus/models/space_archive/item.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/pages/common/common_data_controller.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart' show ContributeType; import 'package:PiliPlus/utils/utils.dart'; import 'package:get/get.dart'; -class HorizontalMemberPageController extends CommonController { +class HorizontalMemberPageController extends CommonDataController { HorizontalMemberPageController({this.mid, required this.lastAid}); dynamic mid; @@ -52,7 +52,7 @@ class HorizontalMemberPageController extends CommonController { } @override - bool customHandleResponse(Success response) { + bool customHandleResponse(bool isRefresh, Success response) { Data data = response.response; count.value = data.count ?? -1; if (currentPage == 0 || isLoadPrevious) { diff --git a/lib/pages/video/detail/note/note_list_page.dart b/lib/pages/video/detail/note/note_list_page.dart index dd387a6d..121b359b 100644 --- a/lib/pages/video/detail/note/note_list_page.dart +++ b/lib/pages/video/detail/note/note_list_page.dart @@ -130,7 +130,7 @@ class _NoteListPageState extends CommonSlidePageState { ), ); - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => CustomScrollView( physics: const NeverScrollableScrollPhysics(), @@ -145,7 +145,7 @@ class _NoteListPageState extends CommonSlidePageState { ) ], ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? refreshIndicator( onRefresh: () async { await _controller.onRefresh(); @@ -156,12 +156,13 @@ class _NoteListPageState extends CommonSlidePageState { slivers: [ SliverList.separated( itemBuilder: (context, index) { - if (index == loadingState.response.length - 1) { + if (index == loadingState.response!.length - 1) { _controller.onLoadMore(); } - return _itemWidget(context, loadingState.response[index]); + return _itemWidget( + context, loadingState.response![index]); }, - itemCount: loadingState.response.length, + itemCount: loadingState.response!.length, separatorBuilder: (context, index) => Divider( height: 1, color: Theme.of(context) diff --git a/lib/pages/video/detail/note/note_list_page_ctr.dart b/lib/pages/video/detail/note/note_list_page_ctr.dart index 3cfaaacb..11c99cf3 100644 --- a/lib/pages/video/detail/note/note_list_page_ctr.dart +++ b/lib/pages/video/detail/note/note_list_page_ctr.dart @@ -1,10 +1,9 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/video.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; -import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:get/get.dart'; -class NoteListPageCtr extends CommonController { +class NoteListPageCtr extends CommonListController { NoteListPageCtr({this.oid, this.upperMid}); final dynamic oid; final dynamic upperMid; @@ -18,21 +17,22 @@ class NoteListPageCtr extends CommonController { } @override - bool customHandleResponse(Success response) { + List? getDataList(response) { + return response['list']; + } + + @override + void checkIsEnd(int length) { + if (count.value != -1 && length >= count.value) { + isEnd = true; + } + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { dynamic data = response.response; count.value = data['page']?['total'] ?? -1; - if ((data['list'] as List?).isNullOrEmpty) { - isEnd = true; - } - if (currentPage != 1 && loadingState.value is Success) { - data['list'] ??= []; - data['list'].insertAll(0, (loadingState.value as Success).response); - } - if (isEnd.not && count.value != -1 && data['list'].length >= count.value) { - isEnd = true; - } - loadingState.value = LoadingState.success(data['list']); - return true; + return false; } @override diff --git a/lib/pages/video/detail/related/controller.dart b/lib/pages/video/detail/related/controller.dart index 1e6790f6..10ad4901 100644 --- a/lib/pages/video/detail/related/controller.dart +++ b/lib/pages/video/detail/related/controller.dart @@ -1,9 +1,11 @@ import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/pages/common/common_controller.dart'; +import 'package:PiliPlus/models/model_hot_video_item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/http/video.dart'; -class RelatedController extends CommonController { +class RelatedController + extends CommonListController?, HotVideoItemModel> { // 视频aid String bvid = Get.parameters['bvid'] ?? ""; @@ -14,6 +16,6 @@ class RelatedController extends CommonController { } @override - Future customGetData() => + Future?>> customGetData() => VideoHttp.relatedVideoList(bvid: bvid); } diff --git a/lib/pages/video/detail/related/view.dart b/lib/pages/video/detail/related/view.dart index 30f23d6e..c69c216b 100644 --- a/lib/pages/video/detail/related/view.dart +++ b/lib/pages/video/detail/related/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/model_hot_video_item.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart'; @@ -32,7 +33,7 @@ class _RelatedVideoPanelState extends State ); } - Widget _buildBody(LoadingState loadingState) { + Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { Loading() => SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( @@ -47,7 +48,7 @@ class _RelatedVideoPanelState extends State childCount: 5, ), ), - Success() => (loadingState.response as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: 2, @@ -55,17 +56,17 @@ class _RelatedVideoPanelState extends State childAspectRatio: StyleString.aspectRatio * 2.2, ), delegate: SliverChildBuilderDelegate((context, index) { - if (index == loadingState.response.length) { + if (index == loadingState.response!.length) { return SizedBox( height: MediaQuery.of(context).padding.bottom, ); } else { return VideoCardH( - videoItem: loadingState.response[index], + videoItem: loadingState.response![index], showPubdate: true, ); } - }, childCount: loadingState.response.length + 1), + }, childCount: loadingState.response!.length + 1), ) : SliverToBoxAdapter(), Error() => HttpError( diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index 6957191a..4ba58aaf 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -7,7 +7,7 @@ import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:flutter/material.dart'; import 'package:get/get_state_manager/src/rx_flutter/rx_ticket_provider_mixin.dart'; -class VideoReplyController extends ReplyController +class VideoReplyController extends ReplyController with GetTickerProviderStateMixin { VideoReplyController({required this.aid}); // 视频aid 请求时使用的oid @@ -36,7 +36,13 @@ class VideoReplyController extends ReplyController } @override - Future customGetData() => ReplyHttp.replyListGrpc( + List? getDataList(MainListReply response) { + return response.replies; + } + + @override + Future> customGetData() => + ReplyHttp.replyListGrpc( oid: aid, cursor: CursorReq( next: cursor?.next ?? $fixnum.Int64(0), diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 40ee1ff4..95162dfa 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -45,7 +45,7 @@ class VideoReplyPanel extends StatefulWidget { } class _VideoReplyPanelState extends State - with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { + with AutomaticKeepAliveClientMixin { late VideoReplyController _videoReplyController; String get heroTag => widget.heroTag; @@ -202,12 +202,12 @@ class _VideoReplyPanelState extends State childCount: 5, ), ), - Success() => (loadingState.response.replies as List?)?.isNotEmpty == true + Success() => loadingState.response?.isNotEmpty == true ? SliverList( delegate: SliverChildBuilderDelegate( (BuildContext context, index) { double bottom = MediaQuery.of(context).padding.bottom; - if (index == loadingState.response.replies.length) { + if (index == loadingState.response.length) { _videoReplyController.onLoadMore(); return Container( alignment: Alignment.center, @@ -216,7 +216,7 @@ class _VideoReplyPanelState extends State child: Text( _videoReplyController.isEnd.not ? '加载中...' - : loadingState.response.replies.isEmpty + : loadingState.response.isEmpty ? '还没有评论' : '没有更多了', style: TextStyle( @@ -227,18 +227,19 @@ class _VideoReplyPanelState extends State ); } else { return ReplyItemGrpc( - replyItem: loadingState.response.replies[index], + replyItem: loadingState.response[index], replyLevel: widget.replyLevel, replyReply: widget.replyReply, onReply: () { _videoReplyController.onReply( context, - replyItem: loadingState.response.replies[index], + replyItem: loadingState.response[index], index: index, ); }, - onDelete: _videoReplyController.onMDelete, - upMid: loadingState.response.subjectControl.upMid, + onDelete: (subIndex) => + _videoReplyController.onRemove(index, subIndex), + upMid: _videoReplyController.upMid, getTag: () => heroTag, onViewImage: widget.onViewImage, onDismissed: widget.onDismissed, @@ -256,7 +257,7 @@ class _VideoReplyPanelState extends State ); } }, - childCount: loadingState.response.replies.length + 1, + childCount: loadingState.response.length + 1, ), ) : HttpError( 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 87cd229a..33e9b4e5 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item_grpc.dart @@ -49,7 +49,7 @@ class ReplyItemGrpc extends StatelessWidget { final Function(ReplyInfo replyItem, int? rpid)? replyReply; final bool needDivider; final VoidCallback? onReply; - final Function(dynamic rpid, dynamic frpid)? onDelete; + final ValueChanged? onDelete; final dynamic upMid; final VoidCallback? showDialogue; final Function? getTag; @@ -88,8 +88,8 @@ class ReplyItemGrpc extends StatelessWidget { return morePanel( context: context, item: replyItem, - onDelete: (rpid) { - onDelete?.call(rpid, null); + onDelete: () { + onDelete?.call(null); }, isSubReply: false, ); @@ -549,8 +549,8 @@ class ReplyItemGrpc extends StatelessWidget { return morePanel( context: context, item: replyItem.replies[i], - onDelete: (rpid) { - onDelete?.call(rpid, replyItem.id.toInt()); + onDelete: () { + onDelete?.call(i); }, isSubReply: true, ); @@ -1116,7 +1116,7 @@ class ReplyItemGrpc extends StatelessWidget { Options(contentType: Headers.formUrlEncodedContentType), ); if (res.data['code'] == 0) { - onDelete?.call(item.id.toInt()); + onDelete?.call(); } return res.data as Map; }, @@ -1199,7 +1199,7 @@ class ReplyItemGrpc extends StatelessWidget { SmartDialog.dismiss(); if (result['status']) { SmartDialog.showToast('删除成功'); - onDelete?.call(item.id.toInt()); + onDelete?.call(); } else { SmartDialog.showToast('删除失败, ${result["msg"]}'); } diff --git a/lib/pages/video/detail/reply_reply/controller.dart b/lib/pages/video/detail/reply_reply/controller.dart index c6311d69..de9263f9 100644 --- a/lib/pages/video/detail/reply_reply/controller.dart +++ b/lib/pages/video/detail/reply_reply/controller.dart @@ -31,8 +31,6 @@ class VideoReplyReplyController extends ReplyController int rpid; ReplyType replyType; // = ReplyType.video; - int? upMid; - dynamic firstFloor; int? index; @@ -58,17 +56,26 @@ class VideoReplyReplyController extends ReplyController } @override - bool customHandleResponse(Success response) { - dynamic replies = response.response; + List? getDataList(response) { + return isDialogue ? response.replies : response.root.replies; + } + + @override + bool customHandleResponse(bool isRefresh, Success response) { + final data = response.response; + + upMid ??= data.subjectControl.upMid.toInt(); + cursor = data.cursor; + isEnd = cursor?.isEnd ?? false; + // reply2Reply // isDialogue.not - if (replies is DetailListReply) { - count.value = replies.root.count.toInt(); + if (data is DetailListReply) { + count.value = data.root.count.toInt(); if (cursor == null && firstFloor == null) { - firstFloor = replies.root; + firstFloor = data.root; } if (id != null) { - index = - replies.root.replies.indexWhere((item) => item.id.toInt() == id); + index = data.root.replies.indexWhere((item) => item.id.toInt() == id); if (index == -1) { index = null; } else { @@ -93,43 +100,8 @@ class VideoReplyReplyController extends ReplyController id = null; } } - upMid ??= replies.subjectControl.upMid.toInt(); - cursor = replies.cursor; - if (isDialogue) { - if (replies.replies.isEmpty) { - isEnd = true; - } - } else { - if (replies.root.replies.isEmpty) { - isEnd = true; - } - } - if (currentPage != 1) { - List list = loadingState.value is Success - ? (loadingState.value as Success).response - : []; - if (isDialogue) { - replies.replies.insertAll(0, list); - } else { - replies.root.replies.insertAll(0, list); - } - } - if (isDialogue) { - if (replies.cursor.isEnd || replies.replies.length >= count.value) { - isEnd = true; - } - } else { - if (replies.cursor.isEnd || replies.root.replies.length >= count.value) { - isEnd = true; - } - } - if (isDialogue) { - loadingState.value = LoadingState.success(replies.replies); - } else { - loadingState.value = LoadingState.success(replies.root.replies); - } - return true; + return false; } @override diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart index bf0742e7..07f5c05f 100644 --- a/lib/pages/video/detail/reply_reply/view.dart +++ b/lib/pages/video/detail/reply_reply/view.dart @@ -358,8 +358,7 @@ class _VideoReplyReplyPanelState : []; list.insert(index + 1, replyInfo); _videoReplyReplyController.count.value += 1; - _videoReplyReplyController.loadingState.value = - LoadingState.success(list); + _videoReplyReplyController.loadingState.refresh(); if (_videoReplyReplyController.enableCommAntifraud && mounted) { _videoReplyReplyController.checkReply( context: context, @@ -456,13 +455,12 @@ class _VideoReplyReplyPanelState onReply: () { _onReply(replyItem, index); }, - onDelete: (rpid, frpid) { - List list = + onDelete: (subIndex) { + List list = (_videoReplyReplyController.loadingState.value as Success).response; - list = list.where((item) => item.id != rpid).toList(); + list.removeAt(index); _videoReplyReplyController.count.value -= 1; - _videoReplyReplyController.loadingState.value = - LoadingState.success(list); + _videoReplyReplyController.loadingState.refresh(); }, upMid: _videoReplyReplyController.upMid, showDialogue: () { diff --git a/lib/pages/video/detail/view_v.dart b/lib/pages/video/detail/view_v.dart index 972d515f..111e503b 100644 --- a/lib/pages/video/detail/view_v.dart +++ b/lib/pages/video/detail/view_v.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/common/widgets/episode_panel.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/main.dart'; +import 'package:PiliPlus/models/bangumi/info.dart'; import 'package:PiliPlus/models/common/reply_type.dart'; import 'package:PiliPlus/pages/bangumi/introduction/widgets/intro_detail.dart' as bangumi; @@ -140,7 +141,7 @@ class _VideoDetailPageVState extends State _listenerLoadingState = bangumiIntroController.loadingState.listen((value) { if (!context.mounted) return; - if (value is Success) { + if (value is Success) { videoPlayerServiceHandler.onVideoDetailChange( value.response, videoDetailController.cid.value, heroTag); } diff --git a/lib/pages/video/detail/widgets/expandable_section.dart b/lib/pages/video/detail/widgets/expandable_section.dart index 967b0fe5..8d8b2c42 100644 --- a/lib/pages/video/detail/widgets/expandable_section.dart +++ b/lib/pages/video/detail/widgets/expandable_section.dart @@ -30,16 +30,6 @@ class _ExpandedSectionState extends State _runExpandCheck(); } - ///Setting up the animation - // void prepareAnimations() { - // expandController = AnimationController( - // vsync: this, duration: const Duration(milliseconds: 500)); - // animation = CurvedAnimation( - // parent: expandController, - // curve: Curves.fastOutSlowIn, - // ); - // } - void prepareAnimations() { expandController = AnimationController( vsync: this, duration: const Duration(milliseconds: 400)); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 179893ae..6aff7c2b 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -498,7 +498,7 @@ class Utils { dynamic res = await DynamicsHttp.dynamicDetail(id: id); if (res['status']) { final ctr = Get.find(tag: 'all'); - List list = ctr.loadingState.value is Success + List list = ctr.loadingState.value is Success ? (ctr.loadingState.value as Success).response : []; list.insert(0, res['data']); @@ -665,7 +665,7 @@ class Utils { ); } - static void onCopyOrMove({ + static void onCopyOrMove({ required BuildContext context, required bool isCopy, required dynamic ctr, @@ -735,9 +735,9 @@ class Utils { if (res['status']) { ctr.handleSelect(false); if (isCopy.not) { - List dataList = + List dataList = (ctr.loadingState.value as Success).response; - List remainList = dataList + List remainList = dataList .toSet() .difference(resources.toSet()) .toList();