diff --git a/lib/http/api.dart b/lib/http/api.dart index 9fa9306c..bc9359e9 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -328,6 +328,8 @@ class Api { static const String spaceFav = '/x/v3/fav/folder/space'; + static const String seasonSeries = '/x/polymer/web-space/seasons_series_list'; + // 用户名片信息 static const String memberCardInfo = '/x/web-interface/card'; diff --git a/lib/http/member.dart b/lib/http/member.dart index eb104489..a3e36b3e 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -154,6 +154,25 @@ class MemberHttp { } } + static Future seasonSeriesList({ + required int? mid, + required int pn, + }) async { + dynamic res = await Request().get( + Api.seasonSeries, + queryParameters: { + 'mid': mid, + 'page_num': pn, + 'page_size': 10, + }, + ); + if (res.data['code'] == 0) { + return LoadingState.success(res.data['data']['items_lists']); + } else { + return LoadingState.error(res.data['message']); + } + } + static Future spaceArchive({ required ContributeType type, required int? mid, diff --git a/lib/models/space/data.dart b/lib/models/space/data.dart index 8e0aa373..ae26e6a8 100644 --- a/lib/models/space/data.dart +++ b/lib/models/space/data.dart @@ -53,6 +53,7 @@ class Data { dynamic digitalButton; dynamic entry; dynamic live; + UgcSeason? ugcSeason; Data({ this.relation, @@ -79,9 +80,22 @@ class Data { this.digitalButton, this.entry, this.live, + this.ugcSeason, }); factory Data.fromJson(Map json) => _$DataFromJson(json); Map toJson() => _$DataToJson(this); } + +class UgcSeason { + int? count; + + UgcSeason({ + this.count, + }); + + UgcSeason.fromJson(Map json) { + count = json['count']; + } +} diff --git a/lib/models/space/data.g.dart b/lib/models/space/data.g.dart index 29af30f0..ba1e21fc 100644 --- a/lib/models/space/data.g.dart +++ b/lib/models/space/data.g.dart @@ -62,6 +62,9 @@ Data _$DataFromJson(Map json) => Data( digitalButton: json['digital_button'], entry: json['entry'], live: json['live'], + ugcSeason: json['ugc_season'] != null + ? UgcSeason.fromJson(json['ugc_season']) + : null, ); Map _$DataToJson(Data instance) => { 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 new file mode 100644 index 00000000..7a2e2278 --- /dev/null +++ b/lib/pages/member/new/content/member_contribute/content/season_series/controller.dart @@ -0,0 +1,33 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/member.dart'; +import 'package:PiliPlus/pages/common/common_controller.dart'; + +class SeasonSeriesController extends CommonController { + SeasonSeriesController(this.mid); + final int mid; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @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); + } + isEnd = list.length >= ((data['page']['total'] as int?) ?? 0); + loadingState.value = LoadingState.success(list); + return true; + } + + @override + Future customGetData() => MemberHttp.seasonSeriesList( + mid: mid, + pn: currentPage, + ); +} 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 new file mode 100644 index 00000000..37c511bc --- /dev/null +++ b/lib/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart @@ -0,0 +1,107 @@ +import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/controller.dart'; +import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/widget/season_series_card.dart'; +import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart'; +import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'; +import 'package:PiliPlus/utils/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class SeasonSeriesPage extends StatefulWidget { + const SeasonSeriesPage({ + super.key, + required this.mid, + this.heroTag, + }); + + final int mid; + final String? heroTag; + + @override + State createState() => _SeasonSeriesPageState(); +} + +class _SeasonSeriesPageState extends State { + late final _controller = Get.put( + SeasonSeriesController(widget.mid), + tag: widget.heroTag, + ); + + @override + Widget build(BuildContext context) { + return Obx(() => _buildBody(_controller.loadingState.value)); + } + + Widget _buildBody(LoadingState loadingState) { + return switch (loadingState) { + Loading() => loadingWidget, + Success() => (loadingState.response as List?)?.isNotEmpty == true + ? CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: StyleString.safeSpace - 5, + bottom: MediaQuery.paddingOf(context).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) { + _controller.onLoadMore(); + } + return SeasonSeriesCard( + item: loadingState.response[index], + onTap: () { + dynamic item = loadingState.response[index]; + bool isSeason = item['meta']['season_id'] != null; + dynamic id = isSeason + ? item['meta']['season_id'] + : item['meta']['series_id']; + Get.to( + Scaffold( + appBar: AppBar( + title: Text(item['meta']['name']), + ), + body: MemberVideo( + type: isSeason + ? ContributeType.season + : ContributeType.series, + heroTag: widget.heroTag, + mid: widget.mid, + seasonId: isSeason ? id : null, + seriesId: isSeason ? null : id, + title: item['meta']['name'], + ), + ), + ); + }, + ); + }, + childCount: loadingState.response.length, + ), + ), + ), + ], + ) + : scrollErrorWidget( + callback: () { + _controller.onReload(); + }, + ), + Error() => scrollErrorWidget( + errMsg: loadingState.errMsg, + callback: () { + _controller.onReload(); + }, + ), + LoadingState() => throw UnimplementedError(), + }; + } +} diff --git a/lib/pages/member/new/content/member_contribute/content/season_series/widget/season_series_card.dart b/lib/pages/member/new/content/member_contribute/content/season_series/widget/season_series_card.dart new file mode 100644 index 00000000..debbde6d --- /dev/null +++ b/lib/pages/member/new/content/member_contribute/content/season_series/widget/season_series_card.dart @@ -0,0 +1,106 @@ +import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/image_save.dart'; +import 'package:PiliPlus/common/widgets/network_img_layer.dart'; +import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart'; +import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class SeasonSeriesCard extends StatelessWidget { + const SeasonSeriesCard({ + super.key, + required this.item, + required this.onTap, + }); + final dynamic item; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return InkWell( + onLongPress: () { + imageSaveDialog( + context: context, + title: item['meta']['name'], + cover: item['meta']['cover'], + ); + }, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: StyleString.safeSpace, + vertical: 5, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints boxConstraints) { + final double maxWidth = boxConstraints.maxWidth; + final double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + NetworkImgLayer( + src: item['meta']['cover'], + width: maxWidth, + height: maxHeight, + ), + PBadge( + text: + '${item['meta']['season_id'] != null ? '合集' : '列表'}: ${item['meta']['total']}', + bottom: 6.0, + right: 6.0, + ), + ], + ); + }, + ), + ), + videoContent(context) + ], + ), + ), + ); + } + + Widget videoContent(context) { + return Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 6, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item['meta']['name'], + textAlign: TextAlign.start, + style: TextStyle( + fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize, + height: 1.42, + letterSpacing: 0.3, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const Spacer(), + Text( + Utils.dateFormat(item['meta']['ptime']), + maxLines: 1, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, + height: 1, + color: Theme.of(context).colorScheme.outline, + overflow: TextOverflow.clip, + ), + ), + const Spacer(), + ], + ), + ), + ); + } +} diff --git a/lib/pages/member/new/content/member_contribute/member_contribute.dart b/lib/pages/member/new/content/member_contribute/member_contribute.dart index 571c4a54..7a4b2258 100644 --- a/lib/pages/member/new/content/member_contribute/member_contribute.dart +++ b/lib/pages/member/new/content/member_contribute/member_contribute.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/member_article.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/content/audio/member_audio.dart'; +import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute_ctr.dart'; import 'package:flutter/material.dart'; @@ -110,6 +111,10 @@ class _MemberContributeState extends State seriesId: item.seriesId, title: item.title, ), + 'ugcSeason' => SeasonSeriesPage( + mid: widget.mid, + heroTag: widget.heroTag, + ), _ => Center(child: Text(item.title!)) }, ) 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 041e9c11..a1155701 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 @@ -32,6 +32,18 @@ class MemberContributeCtr extends CommonController if (contribute.items?.isNullOrEmpty == false && contribute.items!.length > 1) { items = contribute.items; + if (_ctr.ugcSeasonCount != null) { + int currentSeasonCount = + items!.where((item) => item.param == 'season_video').length; + if (currentSeasonCount < _ctr.ugcSeasonCount!) { + items!.add( + Item( + param: 'ugcSeason', + title: '全部合集/列表', + ), + ); + } + } tabs = items!.map((item) => Tab(text: item.title)).toList(); tabController = TabController( vsync: this, diff --git a/lib/pages/member/new/controller.dart b/lib/pages/member/new/controller.dart index 9907c886..640c41e2 100644 --- a/lib/pages/member/new/controller.dart +++ b/lib/pages/member/new/controller.dart @@ -31,6 +31,7 @@ class MemberControllerNew extends CommonController List? tab2; RxInt contributeInitialIndex = 0.obs; double? top; + int? ugcSeasonCount; @override void onInit() { @@ -54,6 +55,7 @@ class MemberControllerNew extends CommonController tab2 = response.response.tab2; live = response.response?.live; silence = response.response?.card?.silence; + ugcSeasonCount = response.response?.ugcSeason?.count; if (response.response?.card?.endTime != null) { if (response.response.card.endTime == 0) { endTime = ': 永久封禁';