feat: show total season/series

Closes #164

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-01-14 15:22:56 +08:00
parent ba8d7b871c
commit 5983670c83
10 changed files with 303 additions and 0 deletions

View File

@@ -328,6 +328,8 @@ class Api {
static const String spaceFav = '/x/v3/fav/folder/space'; 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'; static const String memberCardInfo = '/x/web-interface/card';

View File

@@ -154,6 +154,25 @@ class MemberHttp {
} }
} }
static Future<LoadingState> 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<LoadingState> spaceArchive({ static Future<LoadingState> spaceArchive({
required ContributeType type, required ContributeType type,
required int? mid, required int? mid,

View File

@@ -53,6 +53,7 @@ class Data {
dynamic digitalButton; dynamic digitalButton;
dynamic entry; dynamic entry;
dynamic live; dynamic live;
UgcSeason? ugcSeason;
Data({ Data({
this.relation, this.relation,
@@ -79,9 +80,22 @@ class Data {
this.digitalButton, this.digitalButton,
this.entry, this.entry,
this.live, this.live,
this.ugcSeason,
}); });
factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json); factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);
Map<String, dynamic> toJson() => _$DataToJson(this); Map<String, dynamic> toJson() => _$DataToJson(this);
} }
class UgcSeason {
int? count;
UgcSeason({
this.count,
});
UgcSeason.fromJson(Map<String, dynamic> json) {
count = json['count'];
}
}

View File

@@ -62,6 +62,9 @@ Data _$DataFromJson(Map<String, dynamic> json) => Data(
digitalButton: json['digital_button'], digitalButton: json['digital_button'],
entry: json['entry'], entry: json['entry'],
live: json['live'], live: json['live'],
ugcSeason: json['ugc_season'] != null
? UgcSeason.fromJson(json['ugc_season'])
: null,
); );
Map<String, dynamic> _$DataToJson(Data instance) => <String, dynamic>{ Map<String, dynamic> _$DataToJson(Data instance) => <String, dynamic>{

View File

@@ -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<LoadingState> customGetData() => MemberHttp.seasonSeriesList(
mid: mid,
pn: currentPage,
);
}

View File

@@ -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<SeasonSeriesPage> createState() => _SeasonSeriesPageState();
}
class _SeasonSeriesPageState extends State<SeasonSeriesPage> {
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(),
};
}
}

View File

@@ -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(),
],
),
),
);
}
}

View File

@@ -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/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/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/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute_ctr.dart'; import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute_ctr.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -110,6 +111,10 @@ class _MemberContributeState extends State<MemberContribute>
seriesId: item.seriesId, seriesId: item.seriesId,
title: item.title, title: item.title,
), ),
'ugcSeason' => SeasonSeriesPage(
mid: widget.mid,
heroTag: widget.heroTag,
),
_ => Center(child: Text(item.title!)) _ => Center(child: Text(item.title!))
}, },
) )

View File

@@ -32,6 +32,18 @@ class MemberContributeCtr extends CommonController
if (contribute.items?.isNullOrEmpty == false && if (contribute.items?.isNullOrEmpty == false &&
contribute.items!.length > 1) { contribute.items!.length > 1) {
items = contribute.items; 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(); tabs = items!.map((item) => Tab(text: item.title)).toList();
tabController = TabController( tabController = TabController(
vsync: this, vsync: this,

View File

@@ -31,6 +31,7 @@ class MemberControllerNew extends CommonController
List<Tab2>? tab2; List<Tab2>? tab2;
RxInt contributeInitialIndex = 0.obs; RxInt contributeInitialIndex = 0.obs;
double? top; double? top;
int? ugcSeasonCount;
@override @override
void onInit() { void onInit() {
@@ -54,6 +55,7 @@ class MemberControllerNew extends CommonController
tab2 = response.response.tab2; tab2 = response.response.tab2;
live = response.response?.live; live = response.response?.live;
silence = response.response?.card?.silence; silence = response.response?.card?.silence;
ugcSeasonCount = response.response?.ugcSeason?.count;
if (response.response?.card?.endTime != null) { if (response.response?.card?.endTime != null) {
if (response.response.card.endTime == 0) { if (response.response.card.endTime == 0) {
endTime = ': 永久封禁'; endTime = ': 永久封禁';