diff --git a/lib/common/widgets/video_card/video_card_v.dart b/lib/common/widgets/video_card/video_card_v.dart index a20a5c5c3..915a67208 100644 --- a/lib/common/widgets/video_card/video_card_v.dart +++ b/lib/common/widgets/video_card/video_card_v.dart @@ -22,7 +22,7 @@ import 'package:intl/intl.dart'; // 视频卡片 - 垂直布局 class VideoCardV extends StatelessWidget { - final BaseRecVideoItemModel videoItem; + final BaseRcmdVideoItemModel videoItem; final VoidCallback? onRemove; const VideoCardV({ @@ -229,7 +229,7 @@ class VideoCardV extends StatelessWidget { value: videoItem.stat.danmu, ), ], - if (videoItem is RecVideoItemModel) ...[ + if (videoItem is RcmdVideoItemModel) ...[ const Spacer(), Text.rich( maxLines: 1, @@ -248,7 +248,7 @@ class VideoCardV extends StatelessWidget { const SizedBox(width: 2), ], // deprecated - // else if (videoItem is RecVideoItemAppModel && + // else if (videoItem is RcmdVideoItemAppModel && // videoItem.desc != null && // videoItem.desc!.contains(' · ')) ...[ // const Spacer(), diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart index 60f001617..75deb6243 100644 --- a/lib/common/widgets/video_popup_menu.dart +++ b/lib/common/widgets/video_popup_menu.dart @@ -135,7 +135,7 @@ class VideoPopupMenu extends StatelessWidget { SmartDialog.showToast("请退出账号后重新登录"); return; } - if (videoItem case final RecVideoItemAppModel item) { + if (videoItem case final RcmdVideoItemAppModel item) { ThreePoint? tp = item.threePoint; if (tp == null) { SmartDialog.showToast("未能获取threePoint"); diff --git a/lib/http/api.dart b/lib/http/api.dart index 372c2e70a..903e4e2be 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -649,14 +649,9 @@ abstract final class Api { /// mid static const getMemberViewApi = '/x/space/upstat'; - /// 查询某个专栏 - /// mid - /// season_id - /// sort_reverse - /// page_num - /// page_size - static const getSeasonDetailApi = - '/x/polymer/web-space/seasons_archives_list'; + static const seasonArchives = '/x/polymer/web-space/seasons_archives_list'; + + static const seriesArchives = '/x/series/archives'; /// 获取未读动态数 static const getUnreadDynamic = '/x/web-interface/dynamic/entrance'; diff --git a/lib/http/error_msg.dart b/lib/http/error_msg.dart new file mode 100644 index 000000000..e78230cee --- /dev/null +++ b/lib/http/error_msg.dart @@ -0,0 +1,3 @@ +const errorMsg = { + -352: '风控校验失败,请检查登录状态', +}; diff --git a/lib/http/fan.dart b/lib/http/fan.dart index 337732343..947eabf45 100644 --- a/lib/http/fan.dart +++ b/lib/http/fan.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/http/api.dart'; +import 'package:PiliPlus/http/error_msg.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/follow/data.dart'; @@ -23,7 +24,7 @@ abstract final class FanHttp { if (res.data['code'] == 0) { return Success(FollowData.fromJson(res.data['data'])); } else { - return Error(res.data['message']); + return Error(errorMsg[res.data['code']] ?? res.data['message']); } } } diff --git a/lib/http/follow.dart b/lib/http/follow.dart index 880a9adfe..d3ed1c715 100644 --- a/lib/http/follow.dart +++ b/lib/http/follow.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/http/api.dart'; +import 'package:PiliPlus/http/error_msg.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/follow/data.dart'; @@ -23,7 +24,7 @@ abstract final class FollowHttp { if (res.data['code'] == 0) { return Success(FollowData.fromJson(res.data['data'])); } else { - return Error(res.data['message']); + return Error(errorMsg[res.data['code']] ?? res.data['message']); } } } diff --git a/lib/http/member.dart b/lib/http/member.dart index 69bc23205..df8ae83ce 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -4,9 +4,14 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/browser_ua.dart'; import 'package:PiliPlus/http/constants.dart'; +import 'package:PiliPlus/http/error_msg.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_app.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_web.dart'; +import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart'; import 'package:PiliPlus/models/common/member/contribute_type.dart'; +import 'package:PiliPlus/models/common/member/web_ss_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/member/info.dart'; import 'package:PiliPlus/models/member/tags.dart'; @@ -14,6 +19,7 @@ import 'package:PiliPlus/models_new/follow/data.dart'; import 'package:PiliPlus/models_new/follow/list.dart'; import 'package:PiliPlus/models_new/member/coin_like_arc/data.dart'; import 'package:PiliPlus/models_new/member/search_archive/data.dart'; +import 'package:PiliPlus/models_new/member/season_web/data.dart'; import 'package:PiliPlus/models_new/member_card_info/data.dart'; import 'package:PiliPlus/models_new/space/space/data.dart'; import 'package:PiliPlus/models_new/space/space_archive/data.dart'; @@ -113,8 +119,8 @@ abstract final class MemberHttp { required ContributeType type, required int? mid, String? aid, - String? order, - String? sort, + ArchiveOrderTypeApp? order, + ArchiveSortTypeApp? sort, int? pn, int? next, int? seasonId, @@ -135,9 +141,9 @@ abstract final class MemberHttp { 'next': ?next, 'season_id': ?seasonId, 'series_id': ?seriesId, - 'qn': type == ContributeType.video ? 80 : 32, - 'order': ?order, - 'sort': ?sort, + 'qn': type == .video ? 80 : 32, + 'order': ?order?.name, + 'sort': ?sort?.name, 'include_cursor': ?includeCursor, 'statistics': Constants.statisticsApp, 'vmid': mid, @@ -341,12 +347,12 @@ abstract final class MemberHttp { static Future> searchArchive({ required Object mid, + int tid = 0, // e.g. pugv: 196 int ps = 30, - int tid = 0, - int? pn, + required int pn, String? keyword, - String order = 'pubdate', - bool orderAvoided = true, + String? specialType, // e.g. 'charging' + ArchiveOrderTypeWeb order = .pubdate, }) async { String dmImgStr = Utils.base64EncodeRandomString(16, 64); String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128); @@ -354,12 +360,13 @@ abstract final class MemberHttp { 'mid': mid, 'ps': ps, 'tid': tid, - 'pn': ?pn, + 'pn': pn, 'keyword': ?keyword, - 'order': order, + 'special_type': ?specialType, + 'order': order.name, 'platform': 'web', - 'web_location': 1550101, - 'order_avoided': orderAvoided, + 'web_location': 333.1387, + 'order_avoided': true, 'dm_img_list': '[]', 'dm_img_str': dmImgStr, 'dm_cover_img_str': dmCoverImgStr, @@ -379,10 +386,50 @@ abstract final class MemberHttp { if (res.data['code'] == 0) { return Success(SearchArchiveData.fromJson(res.data['data'])); } else { - Map errMap = const { - -352: '风控校验失败,请检查登录状态', - }; - return Error(errMap[res.data['code']] ?? res.data['message']); + return Error(errorMsg[res.data['code']] ?? res.data['message']); + } + } + + static Future> seasonSeriesWeb({ + required WebSsType type, + required Object mid, + required Object id, + int ps = 30, + required int pn, + ArchiveSortTypeApp sort = .desc, + }) async { + final res = await Request().get( + type.api, + queryParameters: switch (type) { + .season => { + 'mid': mid, + 'season_id': id, + 'sort_reverse': sort == .asc, + 'page_size': ps, + 'page_num': pn, + 'web_location': 333.1387, + }, + .series => { + 'mid': mid, + 'series_id': id, + 'sort': sort.name, + 'ps': ps, + 'pn': pn, + 'web_location': 333.1387, + }, + }, + options: Options( + headers: { + HttpHeaders.userAgentHeader: BrowserUa.pc, + HttpHeaders.refererHeader: '${HttpString.spaceBaseUrl}/$mid', + 'origin': HttpString.spaceBaseUrl, + }, + ), + ); + if (res.data['code'] == 0) { + return Success(SeasonWebData.fromJson(res.data['data'])); + } else { + return Error(errorMsg[res.data['code']] ?? res.data['message']); } } @@ -430,10 +477,7 @@ abstract final class MemberHttp { return Error('$e\n\n$s'); } } else { - Map errMap = const { - -352: '风控校验失败,请检查登录状态', - }; - return Error(errMap[res.data['code']] ?? res.data['message']); + return Error(errorMsg[res.data['code']] ?? res.data['message']); } } @@ -544,7 +588,7 @@ abstract final class MemberHttp { ), ); } else { - return Error(res.data['message']); + return Error(errorMsg[res.data['code']] ?? res.data['message']); } } diff --git a/lib/http/video.dart b/lib/http/video.dart index 5eae19f60..737806c8e 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -49,7 +49,7 @@ abstract final class VideoHttp { static bool enableFilter = zoneRegExp.pattern.isNotEmpty; // 首页推荐视频 - static Future>> rcmdVideoList({ + static Future>> rcmdVideoList({ required int ps, required int freshIdx, }) async { @@ -66,13 +66,13 @@ abstract final class VideoHttp { }), ); if (res.data['code'] == 0) { - List list = []; + List list = []; for (final i in res.data['data']['item']) { //过滤掉live与ad,以及拉黑用户 if (i['goto'] == 'av' && (i['owner'] != null && !GlobalData().blackMids.contains(i['owner']['mid']))) { - RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i); + RcmdVideoItemModel videoItem = RcmdVideoItemModel.fromJson(i); if (!RecommendFilter.filter(videoItem)) { list.add(videoItem); } @@ -85,7 +85,7 @@ abstract final class VideoHttp { } // 添加额外的loginState变量模拟未登录状态 - static Future>> rcmdVideoListApp({ + static Future>> rcmdVideoListApp({ required int freshIdx, }) async { final params = { @@ -139,7 +139,7 @@ abstract final class VideoHttp { ), ); if (res.data['code'] == 0) { - List list = []; + List list = []; for (final i in res.data['data']['items']) { // 屏蔽推广和拉黑用户 if (i['card_goto'] != 'ad_av' && @@ -152,7 +152,7 @@ abstract final class VideoHttp { zoneRegExp.hasMatch(i['args']['tname'])) { continue; } - RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i); + RcmdVideoItemAppModel videoItem = RcmdVideoItemAppModel.fromJson(i); if (!RecommendFilter.filter(videoItem)) { list.add(videoItem); } diff --git a/lib/models/common/member/archive_order_type_app.dart b/lib/models/common/member/archive_order_type_app.dart new file mode 100644 index 000000000..0182f8e8f --- /dev/null +++ b/lib/models/common/member/archive_order_type_app.dart @@ -0,0 +1,11 @@ +import 'package:PiliPlus/models/common/enum_with_label.dart'; + +enum ArchiveOrderTypeApp with EnumWithLabel { + pubdate('最新发布'), + click('最多播放'), + ; + + @override + final String label; + const ArchiveOrderTypeApp(this.label); +} diff --git a/lib/models/common/member/archive_order_type_web.dart b/lib/models/common/member/archive_order_type_web.dart new file mode 100644 index 000000000..040a68c4e --- /dev/null +++ b/lib/models/common/member/archive_order_type_web.dart @@ -0,0 +1,12 @@ +import 'package:PiliPlus/models/common/enum_with_label.dart'; + +enum ArchiveOrderTypeWeb with EnumWithLabel { + pubdate('最新发布'), + click('最多播放'), + stow('最多收藏'), + ; + + @override + final String label; + const ArchiveOrderTypeWeb(this.label); +} diff --git a/lib/models/common/member/archive_sort_type_app.dart b/lib/models/common/member/archive_sort_type_app.dart new file mode 100644 index 000000000..dd8e6084d --- /dev/null +++ b/lib/models/common/member/archive_sort_type_app.dart @@ -0,0 +1,11 @@ +import 'package:PiliPlus/models/common/enum_with_label.dart'; + +enum ArchiveSortTypeApp with EnumWithLabel { + desc('默认'), + asc('倒序'), + ; + + @override + final String label; + const ArchiveSortTypeApp(this.label); +} diff --git a/lib/models/common/member/web_ss_type.dart b/lib/models/common/member/web_ss_type.dart new file mode 100644 index 000000000..e4d161624 --- /dev/null +++ b/lib/models/common/member/web_ss_type.dart @@ -0,0 +1,10 @@ +import 'package:PiliPlus/http/api.dart'; + +enum WebSsType { + season(Api.seasonArchives), + series(Api.seriesArchives), + ; + + final String api; + const WebSsType(this.api); +} diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index 5788c13ae..62585f9ba 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -3,14 +3,14 @@ import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/num_utils.dart'; -class RecVideoItemAppModel extends BaseRecVideoItemModel { +class RcmdVideoItemAppModel extends BaseRcmdVideoItemModel { int? get id => aid; String? talkBack; String? cardType; ThreePoint? threePoint; - RecVideoItemAppModel.fromJson(Map json) { + RcmdVideoItemAppModel.fromJson(Map json) { aid = json['player_args']?['aid'] ?? int.tryParse(json['param'] ?? '0'); bvid = json['bvid'] ?? IdUtils.av2bv(aid!); cid = json['player_args']?['cid']; diff --git a/lib/models/model_hot_video_item.dart b/lib/models/model_hot_video_item.dart index 9a48e78e9..dcdccec6e 100644 --- a/lib/models/model_hot_video_item.dart +++ b/lib/models/model_hot_video_item.dart @@ -5,7 +5,7 @@ import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart'; // 稍后再看, 排行榜等网页返回也使用该类 -class HotVideoItemModel extends BaseRecVideoItemModel with MultiSelectData { +class HotVideoItemModel extends BaseRcmdVideoItemModel with MultiSelectData { int? videos; int? tid; String? tname; diff --git a/lib/models/model_rec_video_item.dart b/lib/models/model_rec_video_item.dart index a0e916c08..c91bc7163 100644 --- a/lib/models/model_rec_video_item.dart +++ b/lib/models/model_rec_video_item.dart @@ -1,7 +1,7 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models/model_video.dart'; -abstract class BaseRecVideoItemModel extends BaseVideoItemModel { +abstract class BaseRcmdVideoItemModel extends BaseVideoItemModel { String? goto; String? uri; String? rcmdReason; @@ -11,8 +11,8 @@ abstract class BaseRecVideoItemModel extends BaseVideoItemModel { String? pgcBadge; } -class RecVideoItemModel extends BaseRecVideoItemModel { - RecVideoItemModel.fromJson(Map json) { +class RcmdVideoItemModel extends BaseRcmdVideoItemModel { + RcmdVideoItemModel.fromJson(Map json) { aid = json["id"]; bvid = json["bvid"]; cid = json["cid"]; diff --git a/lib/models_new/member/search_archive/data.dart b/lib/models_new/member/search_archive/data.dart index 1408b99ae..7ef866208 100644 --- a/lib/models_new/member/search_archive/data.dart +++ b/lib/models_new/member/search_archive/data.dart @@ -1,22 +1,13 @@ -import 'package:PiliPlus/models_new/member/search_archive/episodic_button.dart'; import 'package:PiliPlus/models_new/member/search_archive/list.dart'; import 'package:PiliPlus/models_new/member/search_archive/page.dart'; class SearchArchiveData { SearchArchiveList? list; Page? page; - EpisodicButton? episodicButton; - bool? isRisk; - int? gaiaResType; - dynamic gaiaData; SearchArchiveData({ this.list, this.page, - this.episodicButton, - this.isRisk, - this.gaiaResType, - this.gaiaData, }); factory SearchArchiveData.fromJson(Map json) => @@ -27,13 +18,5 @@ class SearchArchiveData { page: json['page'] == null ? null : Page.fromJson(json['page'] as Map), - episodicButton: json['episodic_button'] == null - ? null - : EpisodicButton.fromJson( - json['episodic_button'] as Map, - ), - isRisk: json['is_risk'] as bool?, - gaiaResType: json['gaia_res_type'] as int?, - gaiaData: json['gaia_data'] as dynamic, ); } diff --git a/lib/models_new/member/search_archive/episodic_button.dart b/lib/models_new/member/search_archive/episodic_button.dart deleted file mode 100644 index c53c5184f..000000000 --- a/lib/models_new/member/search_archive/episodic_button.dart +++ /dev/null @@ -1,13 +0,0 @@ -class EpisodicButton { - String? text; - String? uri; - - EpisodicButton({this.text, this.uri}); - - factory EpisodicButton.fromJson(Map json) { - return EpisodicButton( - text: json['text'] as String?, - uri: json['uri'] as String?, - ); - } -} diff --git a/lib/models_new/member/search_archive/list.dart b/lib/models_new/member/search_archive/list.dart index 3af512933..60eace95c 100644 --- a/lib/models_new/member/search_archive/list.dart +++ b/lib/models_new/member/search_archive/list.dart @@ -1,14 +1,26 @@ +import 'package:PiliPlus/models_new/member/search_archive/slist.dart'; import 'package:PiliPlus/models_new/member/search_archive/vlist.dart'; class SearchArchiveList { + List? tags; List? vlist; - SearchArchiveList({this.vlist}); - - factory SearchArchiveList.fromJson(Map json) => - SearchArchiveList( - vlist: (json['vlist'] as List?) - ?.map((e) => VListItemModel.fromJson(e as Map)) - .toList(), - ); + SearchArchiveList.fromJson(Map json) { + vlist = (json['vlist'] as List?) + ?.map((e) => VListItemModel.fromJson(e as Map)) + .toList(); + tags = (json['slist'] as List?) + ?.map((e) => ListTag.fromJson(e as Map)) + .toList(); + (json['tlist'] as Map?)?.forEach((k, v) { + if (k == '196') { + if (tags == null) { + tags = [ListTag.fromJson(v)]; + } else { + tags!.add(ListTag.fromJson(v)); + } + return; + } + }); + } } diff --git a/lib/models_new/member/search_archive/slist.dart b/lib/models_new/member/search_archive/slist.dart new file mode 100644 index 000000000..418255249 --- /dev/null +++ b/lib/models_new/member/search_archive/slist.dart @@ -0,0 +1,15 @@ +class ListTag { + int? tid; + int? count; + String? name; + String? specialType; + + ListTag({this.tid, this.count, this.name, this.specialType}); + + factory ListTag.fromJson(Map json) => ListTag( + tid: json['tid'] as int?, + count: json['count'] as int?, + name: json['name'] as String?, + specialType: json['special_type'] as String?, + ); +} diff --git a/lib/models_new/member/search_archive/vlist.dart b/lib/models_new/member/search_archive/vlist.dart index 2d67c43cc..95f93d1ff 100644 --- a/lib/models_new/member/search_archive/vlist.dart +++ b/lib/models_new/member/search_archive/vlist.dart @@ -2,31 +2,16 @@ import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; class VListItemModel extends BaseVideoItemModel { - int? comment; - int? typeid; - String? subtitle; - String? copyright; - int? review; - bool? hideClick; - bool? isChargingSrc; - VListItemModel.fromJson(Map json) { - comment = json['comment']; - typeid = json['typeid']; cover = json['pic']; - subtitle = json['subtitle']; desc = json['description']; - copyright = json['copyright']; title = json['title']; - review = json['review']; pubdate = json['created']; if (json['length'] != null) { duration = DurationUtils.parseDuration(json['length']); } aid = json['aid']; bvid = json['bvid']; - hideClick = json['hide_click']; - isChargingSrc = json['is_charging_arc']; stat = VListStat.fromJson(json); owner = VListOwner.fromJson(json); } diff --git a/lib/models_new/member/season_web/archive.dart b/lib/models_new/member/season_web/archive.dart new file mode 100644 index 000000000..4d11ab98a --- /dev/null +++ b/lib/models_new/member/season_web/archive.dart @@ -0,0 +1,28 @@ +import 'package:PiliPlus/models/model_video.dart'; + +class SeasonArchive extends BaseVideoItemModel { + SeasonArchive.fromJson(Map json) { + aid = json['aid']; + bvid = json['bvid']; + cover = json['pic']; + title = json['title']; + pubdate = json['pubdate']; + duration = json['duration']; + stat = ArchiveStat.fromJson(json['stat']); + owner = ArchiveOwner.fromJson(json); + } +} + +class ArchiveOwner extends BaseOwner { + ArchiveOwner.fromJson(Map json) { + mid = json['upMid']; + name = ''; + } +} + +class ArchiveStat extends BaseStat { + ArchiveStat.fromJson(Map json) { + view = json['view']; + danmu = json['danmaku']; + } +} diff --git a/lib/models_new/member/season_web/data.dart b/lib/models_new/member/season_web/data.dart new file mode 100644 index 000000000..c6fbfde32 --- /dev/null +++ b/lib/models_new/member/season_web/data.dart @@ -0,0 +1,18 @@ +import 'package:PiliPlus/models_new/member/season_web/archive.dart'; +import 'package:PiliPlus/models_new/member/season_web/page.dart'; + +class SeasonWebData { + List? archives; + Page? page; + + SeasonWebData({this.archives, this.page}); + + factory SeasonWebData.fromJson(Map json) => SeasonWebData( + archives: (json['archives'] as List?) + ?.map((e) => SeasonArchive.fromJson(e as Map)) + .toList(), + page: json['page'] == null + ? null + : Page.fromJson(json['page'] as Map), + ); +} diff --git a/lib/models_new/member/season_web/page.dart b/lib/models_new/member/season_web/page.dart new file mode 100644 index 000000000..84f03b43a --- /dev/null +++ b/lib/models_new/member/season_web/page.dart @@ -0,0 +1,13 @@ +class Page { + int? pageNum; + int? pageSize; + int? total; + + Page({this.pageNum, this.pageSize, this.total}); + + factory Page.fromJson(Map json) => Page( + pageNum: json['page_num'] ?? json['num'], + pageSize: json['page_size'] ?? json['size'], + total: json['total'] as int?, + ); +} diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index f5e7e009c..8b64e936f 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -43,6 +43,8 @@ class MemberController extends CommonDataController bool? hasSeasonOrSeries; + late bool hasCharge = false; + final fromViewAid = Get.parameters['from_view_aid']; final key = GlobalKey(); @@ -58,6 +60,7 @@ class MemberController extends CommonDataController final data = response.response; username = data.card?.name ?? ''; isFollowed = data.card?.relation?.isFollowed; + hasCharge = (data.elec?.total ?? 0) > 0; if (data.relation == -1) { relation.value = 128; } else { diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 67deb5d7e..ab90f70cd 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -17,12 +17,15 @@ import 'package:PiliPlus/pages/member/controller.dart'; import 'package:PiliPlus/pages/member/widget/medal_wall.dart'; import 'package:PiliPlus/pages/member/widget/user_info_card.dart'; import 'package:PiliPlus/pages/member_cheese/view.dart'; +import 'package:PiliPlus/pages/member_contribute/controller.dart'; import 'package:PiliPlus/pages/member_contribute/view.dart'; import 'package:PiliPlus/pages/member_dynamics/view.dart'; import 'package:PiliPlus/pages/member_favorite/view.dart'; import 'package:PiliPlus/pages/member_home/view.dart'; import 'package:PiliPlus/pages/member_pgc/view.dart'; import 'package:PiliPlus/pages/member_shop/view.dart'; +import 'package:PiliPlus/pages/member_video_web/archive/view.dart'; +import 'package:PiliPlus/pages/member_video_web/season_series/view.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -215,22 +218,35 @@ class _MemberPageState extends State { ], ), ), - PopupMenuItem( - onTap: () => Get.toNamed( - '/upowerRank', - parameters: { - 'mid': _userController.mid.toString(), - }, + if (_userController.hasCharge) + PopupMenuItem( + onTap: () => Get.toNamed( + '/upowerRank', + parameters: { + 'mid': _userController.mid.toString(), + }, + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.electric_bolt, size: 19), + SizedBox(width: 10), + Text('充电排行榜'), + ], + ), ), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.electric_bolt, size: 19), - SizedBox(width: 10), - Text('充电排行榜'), - ], + if (Get.isRegistered(tag: _heroTag)) + PopupMenuItem( + onTap: _toWebArchive, + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.extension_outlined, size: 19), + SizedBox(width: 10), + Text('网页投稿'), + ], + ), ), - ), if (_userController.account.isLogin) if (_userController.mid == _userController.account.mid) ...[ if ((_userController @@ -456,4 +472,29 @@ class _MemberPageState extends State { res.toast(); } } + + void _toWebArchive() { + try { + final ctr = Get.find(tag: _heroTag); + final item = ctr.items?[ctr.tabController?.index ?? 0]; + if (item != null) { + final id = item.seasonId ?? item.seriesId; + if (id != null) { + MemberSSWeb.toMemberSSWeb( + type: item.seasonId != null ? .season : .series, + id: id, + mid: _mid, + name: _userController.username ?? '', + ); + return; + } + } + MemberVideoWeb.toMemberVideoWeb( + mid: _mid, + name: _userController.username ?? '', + ); + } catch (e) { + SmartDialog.showToast(e.toString()); + } + } } diff --git a/lib/pages/member_search/child/controller.dart b/lib/pages/member_search/child/controller.dart index 9b26c849c..52dbec9e1 100644 --- a/lib/pages/member_search/child/controller.dart +++ b/lib/pages/member_search/child/controller.dart @@ -62,7 +62,6 @@ class MemberSearchChildController extends CommonListController { mid: controller.mid, pn: page, keyword: controller.editingController.text, - order: 'pubdate', ), MemberSearchType.dynamic => MemberHttp.dynSearch( mid: controller.mid, diff --git a/lib/pages/member_search/child/view.dart b/lib/pages/member_search/child/view.dart index 611ab56cc..1a4cf5bf6 100644 --- a/lib/pages/member_search/child/view.dart +++ b/lib/pages/member_search/child/view.dart @@ -71,47 +71,43 @@ class _MemberSearchChildPageState extends State Loading() => _buildLoading, Success(:final response) => response != null && response.isNotEmpty - ? Builder( - builder: (context) { - return switch (widget.searchType) { - MemberSearchType.archive => SliverGrid.builder( - gridDelegate: gridDelegate, - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return VideoCardH( - videoItem: response[index], - ); - }, - itemCount: response.length, - ), - MemberSearchType.dynamic => - GlobalData().dynamicsWaterfallFlow - ? SliverWaterfallFlow( - gridDelegate: dynGridDelegate, - delegate: SliverChildBuilderDelegate( - (_, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return DynamicPanel(item: response[index]); - }, - childCount: response.length, - ), - ) - : SliverList.builder( - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return DynamicPanel(item: response[index]); - }, - itemCount: response.length, - ), - }; - }, - ) + ? switch (widget.searchType) { + MemberSearchType.archive => SliverGrid.builder( + gridDelegate: gridDelegate, + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return VideoCardH( + videoItem: response[index], + ); + }, + itemCount: response.length, + ), + MemberSearchType.dynamic => + GlobalData().dynamicsWaterfallFlow + ? SliverWaterfallFlow( + gridDelegate: dynGridDelegate, + delegate: SliverChildBuilderDelegate( + (_, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return DynamicPanel(item: response[index]); + }, + childCount: response.length, + ), + ) + : SliverList.builder( + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return DynamicPanel(item: response[index]); + }, + itemCount: response.length, + ), + } : HttpError(onReload: _controller.onReload), Error(:final errMsg) => HttpError( errMsg: errMsg, diff --git a/lib/pages/member_video/controller.dart b/lib/pages/member_video/controller.dart index 32ae7968b..f4c620118 100644 --- a/lib/pages/member_video/controller.dart +++ b/lib/pages/member_video/controller.dart @@ -2,6 +2,8 @@ import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/http/search.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_app.dart'; +import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart'; import 'package:PiliPlus/models/common/member/contribute_type.dart'; import 'package:PiliPlus/models/common/video/source_type.dart'; import 'package:PiliPlus/models_new/space/space_archive/data.dart'; @@ -23,17 +25,18 @@ class MemberVideoCtr required this.seriesId, this.username, this.title, - }); + }) : isVideo = type == .video; - ContributeType type; + final ContributeType type; + final bool isVideo; int? seasonId; int? seriesId; final int mid; - late RxString order = 'pubdate'.obs; - late RxString sort = 'desc'.obs; - RxInt count = (-1).obs; + late ArchiveOrderTypeApp order = .pubdate; + late ArchiveSortTypeApp sort = .desc; + int? count; int? next; - Rx episodicButton = EpisodicButton().obs; + EpisodicButton? episodicButton; final String? username; final String? title; @@ -65,7 +68,7 @@ class MemberVideoCtr @override void onInit() { super.onInit(); - if (type == ContributeType.video) { + if (isVideo) { fromViewAid = Get.parameters['from_view_aid']; } page = 0; @@ -78,24 +81,18 @@ class MemberVideoCtr Success response, ) { final data = response.response; - episodicButton - ..value = data.episodicButton ?? EpisodicButton() - ..refresh(); + episodicButton = data.episodicButton; next = data.next; if (page == 0 || isLoadPrevious) { hasPrev = data.hasPrev; } if (page == 0 || !isLoadPrevious) { - if ((type == ContributeType.video - ? data.hasNext == false - : data.next == 0) || + if ((isVideo ? data.hasNext == false : data.next == 0) || data.item.isNullOrEmpty) { isEnd = true; } } - count.value = type == ContributeType.season - ? (data.item?.length ?? -1) - : (data.count ?? -1); + count = type == .season ? data.item?.length : data.count; if (page != 0) { if (loadingState.value case Success(:final response)) { data.item ??= []; @@ -118,18 +115,18 @@ class MemberVideoCtr MemberHttp.spaceArchive( type: type, mid: mid, - aid: type == ContributeType.video + aid: isVideo ? isLoadPrevious ? firstAid : lastAid : null, - order: type == ContributeType.video ? order.value : null, - sort: type == ContributeType.video + order: isVideo ? order : null, + sort: isVideo ? isLoadPrevious - ? 'asc' + ? .asc : null - : sort.value, - pn: type == ContributeType.charging ? page : null, + : sort, + pn: type == .charging ? page : null, next: next, seasonId: seasonId, seriesId: seriesId, @@ -138,17 +135,17 @@ class MemberVideoCtr void queryBySort() { if (isLoading) return; - if (type == ContributeType.video) { + if (isVideo) { isLocating.value = false; - order.value = order.value == 'pubdate' ? 'click' : 'pubdate'; + order = order == .pubdate ? .click : .pubdate; } else { - sort.value = sort.value == 'desc' ? 'asc' : 'desc'; + sort = sort == .desc ? .asc : .desc; } onReload(); } Future toViewPlayAll() async { - final episodicButton = this.episodicButton.value; + final episodicButton = this.episodicButton!; if (episodicButton.text == '继续播放' && episodicButton.uri?.isNotEmpty == true) { final params = Uri.parse(episodicButton.uri!).queryParameters; @@ -167,7 +164,7 @@ class MemberVideoCtr 'oid': oid, 'favTitle': '$username: ${title ?? episodicButton.text ?? '播放全部'}', - if (seriesId == null) 'count': count.value, + if (seriesId == null) 'count': ?count, if (seasonId != null || seriesId != null) 'mediaType': params['page_type'], 'desc': params['desc'] == '1', @@ -190,9 +187,7 @@ class MemberVideoCtr bool desc = seasonId != null ? false : true; desc = (seasonId != null || seriesId != null) && - (type == ContributeType.video - ? order.value == 'click' - : sort.value == 'asc') + (isVideo ? order == .click : sort == .asc) ? !desc : desc; PageUtils.toVideoPage( @@ -206,14 +201,13 @@ class MemberVideoCtr 'oid': IdUtils.bv2av(element.bvid!), 'favTitle': '$username: ${title ?? episodicButton.text ?? '播放全部'}', - if (seriesId == null) 'count': count.value, + if (seriesId == null) 'count': ?count, if (seasonId != null || seriesId != null) 'mediaType': Uri.parse( episodicButton.uri!, ).queryParameters['page_type'], 'desc': desc, - if (type == ContributeType.video) - 'sortField': order.value == 'click' ? 2 : 1, + if (isVideo) 'sortField': order == .click ? 2 : 1, }, ); break; diff --git a/lib/pages/member_video/view.dart b/lib/pages/member_video/view.dart index 145b433f3..1ec37c9d5 100644 --- a/lib/pages/member_video/view.dart +++ b/lib/pages/member_video/view.dart @@ -111,8 +111,7 @@ class _MemberVideoState extends State ], ), ); - if (widget.type == ContributeType.video && - _controller.fromViewAid?.isNotEmpty == true) { + if (_controller.isVideo && _controller.fromViewAid?.isNotEmpty == true) { if (_index == null) { _scrollController = PrimaryScrollController.of(this.context) @@ -174,85 +173,11 @@ class _MemberVideoState extends State response != null && response.isNotEmpty ? SliverMainAxisGroup( slivers: [ - SliverFloatingHeaderWidget( - backgroundColor: theme.colorScheme.surface, - child: Padding( - padding: const EdgeInsets.fromLTRB(14, 2.5, 8, 2.5), - child: Row( - children: [ - Obx( - () { - final count = _controller.count.value; - return Text( - count != -1 ? '共$count视频' : '', - style: const TextStyle(fontSize: 13), - ); - }, - ), - Obx( - () { - final episodicButton = - _controller.episodicButton.value; - return episodicButton.uri?.isNotEmpty == true - ? Padding( - padding: EdgeInsets.only( - left: _controller.count.value != -1 - ? 6 - : 0, - ), - child: TextButton.icon( - style: Style.buttonStyle, - onPressed: _controller.toViewPlayAll, - icon: Icon( - Icons.play_circle_outline_rounded, - size: 16, - color: theme.colorScheme.secondary, - ), - label: Text( - episodicButton.text ?? '播放全部', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.secondary, - ), - ), - ), - ) - : const SizedBox.shrink(); - }, - ), - const Spacer(), - TextButton.icon( - style: Style.buttonStyle, - onPressed: _controller.queryBySort, - icon: Icon( - Icons.sort, - size: 16, - color: theme.colorScheme.secondary, - ), - label: Obx( - () => Text( - widget.type == ContributeType.video - ? _controller.order.value == 'pubdate' - ? '最新发布' - : '最多播放' - : _controller.sort.value == 'desc' - ? '默认' - : '倒序', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.secondary, - ), - ), - ), - ), - ], - ), - ), - ), + _buildHeader(theme), SliverGrid.builder( gridDelegate: gridDelegate, itemBuilder: (context, index) { - if (widget.type != ContributeType.season && + if (widget.type != .season && index == response.length - 1) { _controller.onLoadMore(); } @@ -272,4 +197,79 @@ class _MemberVideoState extends State ), }; } + + Widget _buildHeader(ThemeData theme) { + return SliverFloatingHeaderWidget( + backgroundColor: theme.colorScheme.surface, + child: Padding( + padding: const EdgeInsets.fromLTRB(14, 2.5, 8, 2.5), + child: Row( + children: [ + ?_buildCount(), + ?_buildEpisodeBtn(theme), + const Spacer(), + _buildSortBtn(theme), + ], + ), + ), + ); + } + + Widget? _buildCount() { + final count = _controller.count; + if (count != null) { + return Text( + '共$count视频', + style: const TextStyle(fontSize: 13), + ); + } + return null; + } + + Widget? _buildEpisodeBtn(ThemeData theme) { + final episodicButton = _controller.episodicButton; + if (episodicButton?.uri?.isNotEmpty ?? false) { + return Padding( + padding: EdgeInsets.only( + left: _controller.count != null ? 6 : 0, + ), + child: TextButton.icon( + style: Style.buttonStyle, + onPressed: _controller.toViewPlayAll, + icon: Icon( + Icons.play_circle_outline_rounded, + size: 16, + color: theme.colorScheme.secondary, + ), + label: Text( + episodicButton?.text ?? '播放全部', + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.secondary, + ), + ), + ), + ); + } + return null; + } + + Widget _buildSortBtn(ThemeData theme) { + return TextButton.icon( + style: Style.buttonStyle, + onPressed: _controller.queryBySort, + icon: Icon( + Icons.sort, + size: 16, + color: theme.colorScheme.secondary, + ), + label: Text( + _controller.isVideo ? _controller.order.label : _controller.sort.label, + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.secondary, + ), + ), + ); + } } diff --git a/lib/pages/member_video_web/archive/controller.dart b/lib/pages/member_video_web/archive/controller.dart new file mode 100644 index 000000000..104d8274d --- /dev/null +++ b/lib/pages/member_video_web/archive/controller.dart @@ -0,0 +1,63 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/member.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_web.dart'; +import 'package:PiliPlus/models_new/member/search_archive/data.dart'; +import 'package:PiliPlus/models_new/member/search_archive/slist.dart'; +import 'package:PiliPlus/models_new/member/search_archive/vlist.dart'; +import 'package:PiliPlus/pages/member_video_web/base/controller.dart'; +import 'package:get/get.dart'; + +class MemberVideoWebCtr + extends + BaseVideoWebCtr< + SearchArchiveData, + VListItemModel, + ArchiveOrderTypeWeb + > { + int? _totalCount; + @override + final Rx order = Rx(.pubdate); + + int tid = 0; + String? specialType; + List? tags; + + @override + List? getDataList(SearchArchiveData response) { + return response.list?.vlist; + } + + @override + bool customHandleResponse( + bool isRefresh, + Success response, + ) { + if (isRefresh) { + final data = response.response; + if (data.page?.count case final count?) { + if (tid == 0 && specialType == null) { + _totalCount = count; + } + this.count = count; + totalPage = (count / ps).ceil(); + } + final tags = data.list?.tags; + if (tags?.isNotEmpty ?? false) { + this.tags = tags! + ..insert(0, ListTag(tid: 0, name: '全部类型', count: _totalCount)); + } + } + return false; + } + + @override + Future> customGetData() => + MemberHttp.searchArchive( + mid: mid, + ps: ps, + pn: page, + order: order.value, + tid: tid, + specialType: specialType, + ); +} diff --git a/lib/pages/member_video_web/archive/view.dart b/lib/pages/member_video_web/archive/view.dart new file mode 100644 index 000000000..128c63f10 --- /dev/null +++ b/lib/pages/member_video_web/archive/view.dart @@ -0,0 +1,87 @@ +import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; +import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_web.dart'; +import 'package:PiliPlus/models_new/member/search_archive/data.dart'; +import 'package:PiliPlus/models_new/member/search_archive/vlist.dart'; +import 'package:PiliPlus/pages/member_video_web/archive/controller.dart'; +import 'package:PiliPlus/pages/member_video_web/base/view.dart'; +import 'package:PiliPlus/pages/search/widgets/search_text.dart'; +import 'package:PiliPlus/utils/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class MemberVideoWeb extends StatefulWidget { + const MemberVideoWeb({super.key}); + + @override + State createState() => _MemberVideoWebState(); + + static Future? toMemberVideoWeb({ + required Object mid, + required String name, + }) { + return Get.toNamed( + '/videoWeb', + arguments: { + 'mid': mid, + 'name': name, + }, + ); + } +} + +class _MemberVideoWebState + extends + BaseVideoWebState< + MemberVideoWeb, + SearchArchiveData, + VListItemModel, + ArchiveOrderTypeWeb + > + with GridMixin { + @override + late final MemberVideoWebCtr controller; + + @override + void initState() { + super.initState(); + controller = Get.put(MemberVideoWebCtr(), tag: name); + } + + @override + List get values => ArchiveOrderTypeWeb.values; + + @override + Widget? buildTags(ColorScheme colorScheme) { + if (controller.tags case final tags?) { + return SliverPinnedHeader( + backgroundColor: colorScheme.surface, + child: SelfSizedHorizontalList( + itemCount: tags.length, + padding: const .fromLTRB(10, 0, 10, 8), + itemBuilder: (context, index) { + final item = tags[index]; + final isCurr = controller.specialType != null + ? item.specialType == controller.specialType + : item.tid == controller.tid; + return SearchText( + padding: const .symmetric(horizontal: 8, vertical: 4), + text: '${item.name!} ${item.count}', + bgColor: isCurr ? colorScheme.secondaryContainer : null, + textColor: isCurr ? colorScheme.onSecondaryContainer : null, + onTap: (_) { + if (isCurr) return; + controller + ..tid = item.tid ?? 0 + ..specialType = item.specialType + ..onReload(); + }, + ); + }, + separatorBuilder: (_, _) => const SizedBox(width: 10), + ), + ); + } + return null; + } +} diff --git a/lib/pages/member_video_web/base/controller.dart b/lib/pages/member_video_web/base/controller.dart new file mode 100644 index 000000000..344b6e68f --- /dev/null +++ b/lib/pages/member_video_web/base/controller.dart @@ -0,0 +1,50 @@ +import 'package:PiliPlus/common/widgets/scroll_physics.dart' show ReloadMixin; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; +import 'package:get/get.dart'; + +const int ps = 30; + +abstract class BaseVideoWebCtr extends CommonListController + with ReloadMixin { + final Object mid = Get.arguments['mid']; + + int? totalPage; + int? count; + Rx get order; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + void checkIsEnd(int length) { + if (totalPage != null && page >= totalPage!) { + isEnd = true; + } else if (count != null && length >= count!) { + isEnd = true; + } + } + + void queryBySort(V value) { + if (isLoading) return; + order.value = value; + onReload(); + } + + void jumpToPage(int page) { + isEnd = false; + reload = true; + this.page = page; + loadingState.value = LoadingState.loading(); + queryData(); + } + + @override + Future onReload() { + reload = true; + return super.onReload(); + } +} diff --git a/lib/pages/member_video_web/base/view.dart b/lib/pages/member_video_web/base/view.dart new file mode 100644 index 000000000..ab8675826 --- /dev/null +++ b/lib/pages/member_video_web/base/view.dart @@ -0,0 +1,217 @@ +import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scroll_physics.dart'; +import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; +import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/common/enum_with_label.dart'; +import 'package:PiliPlus/models/model_video.dart'; +import 'package:PiliPlus/pages/member_video_web/base/controller.dart'; +import 'package:PiliPlus/pages/search/widgets/search_text.dart'; +import 'package:PiliPlus/utils/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +abstract class BaseVideoWebState< + S extends StatefulWidget, + R, + T extends BaseVideoItemModel, + V extends EnumWithLabel +> + extends State + with GridMixin { + late final String name; + BaseVideoWebCtr get controller; + + @override + void initState() { + super.initState(); + final args = Get.arguments; + name = args['name']; + } + + List get values; + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + return Scaffold( + appBar: AppBar( + title: Text(name), + actions: [ + Obx( + () { + final order = controller.order.value; + return PopupMenuButton( + tooltip: '排序', + icon: const Icon(Icons.sort), + initialValue: order, + onSelected: controller.queryBySort, + itemBuilder: (_) => values + .map((e) => PopupMenuItem(value: e, child: Text(e.label))) + .toList(), + ); + }, + ), + const SizedBox(width: 6), + ], + ), + body: refreshIndicator( + onRefresh: controller.onRefresh, + child: CustomScrollView( + physics: ReloadScrollPhysics(controller: controller), + slivers: [ + SliverPadding( + padding: .only( + bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + ), + sliver: Obx( + () => buildBody(colorScheme, controller.loadingState.value), + ), + ), + ], + ), + ), + ); + } + + Widget buildBody( + ColorScheme colorScheme, + LoadingState?> loadingState, + ) { + return switch (loadingState) { + Loading() => gridSkeleton, + Success(:final response) => + response != null && response.isNotEmpty + ? SliverMainAxisGroup( + slivers: [ + buildHeader(colorScheme), + ?buildTags(colorScheme), + SliverGrid.builder( + gridDelegate: gridDelegate, + itemCount: response.length, + itemBuilder: (context, index) { + if (index == response.length - 1) { + controller.onLoadMore(); + } + return VideoCardH(videoItem: response[index]); + }, + ), + ], + ) + : HttpError(onReload: controller.onReload), + Error(:final errMsg) => HttpError( + errMsg: errMsg, + onReload: controller.onReload, + ), + }; + } + + Widget? buildTags(ColorScheme colorScheme) => null; + + Widget buildHeader(ColorScheme colorScheme) { + return SliverPinnedHeader( + backgroundColor: colorScheme.surface, + child: Padding( + padding: const .fromLTRB(14, 0, 8, 4), + child: Stack( + alignment: .centerLeft, + children: [ + ?buildCount(), + Center(child: buildPageBtn(colorScheme)), + ], + ), + ), + ); + } + + Widget? buildCount() { + final count = controller.count; + if (count == null) return null; + return Text( + '共 $count 视频', + style: const TextStyle(height: 1), + strutStyle: const StrutStyle(leading: 0, height: 1), + ); + } + + Widget? buildPageBtn(ColorScheme colorScheme) { + final totalPage = controller.totalPage; + if (totalPage == null) return null; + final page = controller.page - 1; + final canBackward = page > 1; + final canForward = page < totalPage; + const size = 30.0; + const iconSize = 24.0; + + final backwardBtn = iconButton( + size: size, + iconSize: iconSize, + tooltip: canBackward ? '上一页' : null, + icon: const Icon(Icons.keyboard_arrow_left), + onPressed: canBackward ? () => controller.jumpToPage(page - 1) : null, + ); + + final forwardBtn = iconButton( + size: size, + iconSize: iconSize, + tooltip: canForward ? '下一页' : null, + icon: const Icon(Icons.keyboard_arrow_right), + onPressed: canForward ? () => controller.jumpToPage(page + 1) : null, + ); + + final pageIndicator = SearchText( + height: 1, + text: '$page / $totalPage', + borderRadius: const .all(.circular(4)), + padding: const .symmetric(horizontal: 10, vertical: 5), + onTap: (_) => showJumpDialog(page), + ); + + return Row( + spacing: 6, + mainAxisSize: .min, + children: [ + backwardBtn, + pageIndicator, + forwardBtn, + ], + ); + } + + void showJumpDialog(int page) { + var pageStr = page.toString(); + + void onSubmit([_]) { + try { + controller.jumpToPage(int.parse(pageStr)); + } catch (e) { + SmartDialog.showToast(e.toString()); + } + } + + showConfirmDialog( + context: context, + title: const Text('跳至: '), + content: TextFormField( + autofocus: true, + initialValue: pageStr, + onChanged: (value) => pageStr = value, + decoration: const InputDecoration( + labelText: '页数', + border: OutlineInputBorder(), + ), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + onFieldSubmitted: (_) { + Get.back(); + onSubmit(); + }, + ), + onConfirm: onSubmit, + ); + } +} diff --git a/lib/pages/member_video_web/season_series/controller.dart b/lib/pages/member_video_web/season_series/controller.dart new file mode 100644 index 000000000..b3a14a3e4 --- /dev/null +++ b/lib/pages/member_video_web/season_series/controller.dart @@ -0,0 +1,55 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/member.dart'; +import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart'; +import 'package:PiliPlus/models/common/member/web_ss_type.dart'; +import 'package:PiliPlus/models_new/member/season_web/archive.dart'; +import 'package:PiliPlus/models_new/member/season_web/data.dart'; +import 'package:PiliPlus/pages/member_video_web/base/controller.dart'; +import 'package:get/get.dart'; + +class MemberSSWebCtr + extends BaseVideoWebCtr { + @override + final Rx order = Rx(.desc); + late final WebSsType _type; + late final Object _id; + + @override + void onInit() { + final args = Get.arguments; + _type = args['type']; + _id = args['id']; + super.onInit(); + } + + @override + List? getDataList(SeasonWebData response) { + return response.archives; + } + + @override + bool customHandleResponse( + bool isRefresh, + Success response, + ) { + if (isRefresh) { + final data = response.response; + if (data.page?.total case final total?) { + count = total; + totalPage = (total / ps).ceil(); + } + } + return false; + } + + @override + Future> customGetData() => + MemberHttp.seasonSeriesWeb( + type: _type, + mid: mid, + id: _id, + ps: ps, + pn: page, + sort: order.value, + ); +} diff --git a/lib/pages/member_video_web/season_series/view.dart b/lib/pages/member_video_web/season_series/view.dart new file mode 100644 index 000000000..f563f1c39 --- /dev/null +++ b/lib/pages/member_video_web/season_series/view.dart @@ -0,0 +1,55 @@ +import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart'; +import 'package:PiliPlus/models/common/member/web_ss_type.dart'; +import 'package:PiliPlus/models_new/member/season_web/archive.dart'; +import 'package:PiliPlus/models_new/member/season_web/data.dart'; +import 'package:PiliPlus/pages/member_video_web/base/view.dart'; +import 'package:PiliPlus/pages/member_video_web/season_series/controller.dart'; +import 'package:PiliPlus/utils/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class MemberSSWeb extends StatefulWidget { + const MemberSSWeb({super.key}); + + @override + State createState() => _MemberSSWebState(); + + static Future? toMemberSSWeb({ + required WebSsType type, + required Object id, + required Object mid, + required String name, + }) { + return Get.toNamed( + '/ssWeb', + arguments: { + 'type': type, + 'id': id, + 'mid': mid, + 'name': name, + }, + ); + } +} + +class _MemberSSWebState + extends + BaseVideoWebState< + MemberSSWeb, + SeasonWebData, + SeasonArchive, + ArchiveSortTypeApp + > + with GridMixin { + @override + late final MemberSSWebCtr controller; + + @override + void initState() { + super.initState(); + controller = Get.put(MemberSSWebCtr(), tag: name); + } + + @override + List get values => ArchiveSortTypeApp.values; +} diff --git a/lib/pages/video/member/controller.dart b/lib/pages/video/member/controller.dart index 93e589f81..a65de2f06 100644 --- a/lib/pages/video/member/controller.dart +++ b/lib/pages/video/member/controller.dart @@ -1,6 +1,6 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; -import 'package:PiliPlus/models/common/member/contribute_type.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_app.dart'; import 'package:PiliPlus/models/member/info.dart'; import 'package:PiliPlus/models_new/space/space_archive/data.dart'; import 'package:PiliPlus/models_new/space/space_archive/item.dart'; @@ -55,7 +55,7 @@ class HorizontalMemberPageController @override bool customHandleResponse(bool isRefresh, Success response) { SpaceArchiveData data = response.response; - count.value = data.count ?? -1; + count = data.count; if (isRefresh) { if (isLoadPrevious) { hasPrev = data.hasPrev ?? false; @@ -83,8 +83,8 @@ class HorizontalMemberPageController String? currAid; String? firstAid; String? lastAid; - RxString order = 'pubdate'.obs; - RxInt count = (-1).obs; + ArchiveOrderTypeApp order = .pubdate; + int? count; bool isLoadPrevious = false; bool hasPrev = true; bool hasNext = true; @@ -92,15 +92,15 @@ class HorizontalMemberPageController @override Future> customGetData() => MemberHttp.spaceArchive( - type: ContributeType.video, + type: .video, mid: mid, aid: page == 1 ? currAid : isLoadPrevious ? firstAid : lastAid, - order: order.value, - sort: page != 1 && isLoadPrevious ? 'asc' : null, + order: order, + sort: page != 1 && isLoadPrevious ? .asc : null, pn: null, next: null, seasonId: null, @@ -131,7 +131,7 @@ class HorizontalMemberPageController void queryBySort() { if (isLoading) return; - order.value = order.value == 'pubdate' ? 'click' : 'pubdate'; + order = order == .pubdate ? .click : .pubdate; onReload(); } } diff --git a/lib/pages/video/member/view.dart b/lib/pages/video/member/view.dart index f3715661f..d74b1d415 100644 --- a/lib/pages/video/member/view.dart +++ b/lib/pages/video/member/view.dart @@ -128,40 +128,45 @@ class _HorizontalMemberPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Obx( - () { - final count = _controller.count.value; - return Text( - count != -1 ? '共$count视频' : '', - style: const TextStyle(fontSize: 13), - ); - }, - ), - TextButton.icon( - style: Style.buttonStyle, - onPressed: () => _controller - ..lastAid = widget.videoDetailController.aid.toString() - ..queryBySort(), - icon: Icon( - Icons.sort, - size: 16, - color: theme.colorScheme.secondary, - ), - label: Obx( - () => Text( - _controller.order.value == 'pubdate' ? '最新发布' : '最多播放', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.secondary, - ), - ), - ), - ), + ?_buildCount(), + _buildSortBtn(theme), ], ), ); } + Widget? _buildCount() { + final count = _controller.count; + if (count != null) { + return Text( + '共$count视频', + style: const TextStyle(fontSize: 13), + ); + } + return null; + } + + Widget _buildSortBtn(ThemeData theme) { + return TextButton.icon( + style: Style.buttonStyle, + onPressed: () => _controller + ..lastAid = widget.videoDetailController.aid.toString() + ..queryBySort(), + icon: Icon( + Icons.sort, + size: 16, + color: theme.colorScheme.secondary, + ), + label: Text( + _controller.order.label, + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.secondary, + ), + ), + ); + } + Widget _buildVideoList( ThemeData theme, LoadingState?> loadingState, diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 25164d77c..64df48521 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -37,6 +37,8 @@ import 'package:PiliPlus/pages/member_dynamics/view.dart'; import 'package:PiliPlus/pages/member_profile/view.dart'; import 'package:PiliPlus/pages/member_search/view.dart'; import 'package:PiliPlus/pages/member_upower_rank/view.dart'; +import 'package:PiliPlus/pages/member_video_web/archive/view.dart'; +import 'package:PiliPlus/pages/member_video_web/season_series/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/at_me/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/like_detail/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/like_me/view.dart'; @@ -192,5 +194,7 @@ class Routes { GetPage(name: '/download', page: () => const DownloadPage()), GetPage(name: '/dlna', page: () => const DLNAPage()), GetPage(name: '/myReply', page: () => const MyReply()), + GetPage(name: '/videoWeb', page: () => const MemberVideoWeb()), + GetPage(name: '/ssWeb', page: () => const MemberSSWeb()), ]; } diff --git a/lib/utils/accounts/api_type.dart b/lib/utils/accounts/api_type.dart index 18076e94b..9551f8b0e 100644 --- a/lib/utils/accounts/api_type.dart +++ b/lib/utils/accounts/api_type.dart @@ -31,7 +31,8 @@ abstract final class ApiType { Api.onlineTotal, Api.dynamicDetail, Api.aiConclusion, - Api.getSeasonDetailApi, + Api.seasonArchives, + Api.seriesArchives, Api.liveRoomDmToken, Api.liveRoomDmPrefetch, Api.superChatMsg,