Files
PiliPlus/lib/pages/bangumi/view.dart
bggRGjQaUbCoE 286193f08f opt: func
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-05-07 14:32:07 +08:00

459 lines
18 KiB
Dart

import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.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/bangumi/list.dart';
import 'package:PiliPlus/models/bangumi/pgc_timeline/result.dart';
import 'package:PiliPlus/models/common/home_tab_type.dart';
import 'package:PiliPlus/pages/bangumi/controller.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_timeline.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/pgc_index/view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class BangumiPage extends CommonPage {
const BangumiPage({
super.key,
required this.tabType,
});
final HomeTabType tabType;
@override
State<BangumiPage> createState() => _BangumiPageState();
}
class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
with AutomaticKeepAliveClientMixin {
@override
late BangumiController controller = Get.put(
BangumiController(tabType: widget.tabType),
tag: widget.tabType.name,
);
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
final ThemeData theme = Theme.of(context);
return refreshIndicator(
onRefresh: controller.onRefresh,
child: CustomScrollView(
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
_buildFollow(theme),
if (controller.showPgcTimeline)
SliverToBoxAdapter(
child: SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(96),
child: Obx(() =>
_buildTimeline(theme, controller.timelineState.value)),
),
),
..._buildRcmd(theme),
],
),
);
}
Widget _buildTimeline(
ThemeData theme, LoadingState<List<Result>?> loadingState) =>
switch (loadingState) {
Loading() => loadingWidget,
Success() => loadingState.response?.isNotEmpty == true
? Builder(builder: (context) {
final initialIndex = max(
0,
loadingState.response!
.indexWhere((item) => item.isToday == 1));
return DefaultTabController(
initialIndex: initialIndex,
length: loadingState.response!.length,
child: Column(
children: [
Row(
children: [
const SizedBox(width: 16),
Text(
'追番时间表',
style: theme.textTheme.titleMedium,
),
const SizedBox(width: 16),
Expanded(
child: Material(
color: Colors.transparent,
child: TabBar(
isScrollable: true,
tabAlignment: TabAlignment.start,
dividerHeight: 0,
overlayColor:
WidgetStateProperty.all(Colors.transparent),
splashFactory: NoSplash.splashFactory,
padding: const EdgeInsets.only(right: 10),
indicatorPadding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 10,
),
indicator: BoxDecoration(
color: theme.colorScheme.secondaryContainer,
borderRadius: const BorderRadius.all(
Radius.circular(20)),
),
indicatorSize: TabBarIndicatorSize.tab,
labelColor:
theme.colorScheme.onSecondaryContainer,
labelStyle: TabBarTheme.of(context)
.labelStyle
?.copyWith(fontSize: 14) ??
const TextStyle(fontSize: 14),
dividerColor: Colors.transparent,
tabs: loadingState.response!.map(
(item) {
return Tab(
text:
'${item.date} ${item.isToday == 1 ? '今天' : '${const [
'',
'',
'',
'',
'',
'',
'',
][item.dayOfWeek! - 1]}'}',
);
},
).toList(),
),
),
),
],
),
Expanded(
child: MediaQuery.removePadding(
context: context,
removeLeft:
context.orientation == Orientation.landscape,
child: TabBarView(
physics: const NeverScrollableScrollPhysics(),
children: loadingState.response!.map((item) {
if (item.episodes!.isNullOrEmpty) {
return const SizedBox.shrink();
}
return ListView.builder(
physics:
const AlwaysScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: item.episodes!.length,
itemBuilder: (context, index) {
return Container(
width: Grid.smallCardWidth / 2,
margin: EdgeInsets.only(
left: StyleString.safeSpace,
right:
index == item.episodes!.length - 1
? StyleString.safeSpace
: 0,
),
child: BangumiCardVTimeline(
item: item.episodes![index],
),
);
},
);
}).toList()),
),
),
],
),
);
})
: const SizedBox.shrink(),
Error() => GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: controller.queryPgcTimeline,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.center,
child: Text(
loadingState.errMsg,
textAlign: TextAlign.center,
),
),
),
};
List<Widget> _buildRcmd(ThemeData theme) => [
_buildRcmdTitle(theme),
SliverPadding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
sliver: Obx(
() => _buildRcmdBody(controller.loadingState.value),
),
),
];
Widget _buildRcmdTitle(ThemeData theme) => SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
top: 10,
bottom: 10,
left: 16,
right: 10,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'推荐',
style: theme.textTheme.titleMedium,
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (widget.tabType == HomeTabType.bangumi) {
Get.to(const PgcIndexPage());
} else {
List<String> titles = const [
'全部',
'电影',
'电视剧',
'纪录片',
'综艺',
];
List<int> types = const [102, 2, 5, 3, 7];
Get.to(
Scaffold(
appBar: AppBar(title: const Text('索引')),
body: DefaultTabController(
length: types.length,
child: Column(
children: [
SafeArea(
top: false,
bottom: false,
child: TabBar(
tabs: titles
.map((title) => Tab(text: title))
.toList()),
),
Expanded(
child: tabBarView(
children: types
.map((type) =>
PgcIndexPage(indexType: type))
.toList()),
)
],
),
),
),
);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'查看更多',
strutStyle: const StrutStyle(leading: 0, height: 1),
style: TextStyle(
height: 1,
color: theme.colorScheme.secondary,
),
),
Icon(
Icons.chevron_right,
color: theme.colorScheme.secondary,
),
],
),
),
),
],
),
),
);
Widget _buildRcmdBody(
LoadingState<List<BangumiListItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => const SliverToBoxAdapter(),
Success() => loadingState.response?.isNotEmpty == true
? SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距
mainAxisSpacing: StyleString.cardSpace,
// 列间距
crossAxisSpacing: StyleString.cardSpace,
// 最大宽度
maxCrossAxisExtent: Grid.smallCardWidth / 3 * 2,
childAspectRatio: 0.75,
mainAxisExtent: MediaQuery.textScalerOf(context).scale(50),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index == loadingState.response!.length - 1) {
controller.onLoadMore();
}
return BangumiCardV(
bangumiItem: loadingState.response![index]);
},
childCount: loadingState.response!.length,
),
)
: HttpError(
onReload: controller.onReload,
),
Error() => HttpError(
errMsg: loadingState.errMsg,
onReload: controller.onReload,
),
};
}
Widget _buildFollow(ThemeData theme) => SliverToBoxAdapter(
child: Obx(
() => controller.isLogin.value
? Column(
children: [
_buildFollowTitle(theme),
SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(50),
child: Obx(
() => _buildFollowBody(controller.followState.value),
),
),
],
)
: const SizedBox.shrink(),
),
);
Widget _buildFollowTitle(ThemeData theme) => Padding(
padding: const EdgeInsets.only(left: 16),
child: Row(
children: [
Obx(
() => Text(
'最近${widget.tabType == HomeTabType.bangumi ? '追番' : '追剧'}${controller.followCount.value == -1 ? '' : ' ${controller.followCount.value}'}',
style: theme.textTheme.titleMedium,
),
),
const Spacer(),
IconButton(
tooltip: '刷新',
onPressed: () {
controller
..followPage = 1
..followEnd = false
..queryBangumiFollow();
},
icon: const Icon(
Icons.refresh,
size: 20,
),
),
Obx(
() => controller.isLogin.value
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Get.toNamed(
'/fav',
arguments:
widget.tabType == HomeTabType.bangumi ? 1 : 2,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'查看全部',
strutStyle:
const StrutStyle(leading: 0, height: 1),
style: TextStyle(
height: 1,
color: theme.colorScheme.secondary,
),
),
Icon(
Icons.chevron_right,
color: theme.colorScheme.secondary,
),
],
),
),
),
)
: const SizedBox.shrink(),
),
],
),
);
Widget _buildFollowBody(
LoadingState<List<BangumiListItemModel>?> loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => loadingState.response?.isNotEmpty == true
? MediaQuery.removePadding(
context: context,
removeLeft: context.orientation == Orientation.landscape,
child: ListView.builder(
controller: controller.followController,
scrollDirection: Axis.horizontal,
itemCount: loadingState.response!.length,
itemBuilder: (context, index) {
if (index == loadingState.response!.length - 1) {
controller.queryBangumiFollow(false);
}
return Container(
width: Grid.smallCardWidth / 2,
margin: EdgeInsets.only(
left: StyleString.safeSpace,
right: index == loadingState.response!.length - 1
? StyleString.safeSpace
: 0,
),
child: BangumiCardV(
bangumiItem: loadingState.response![index],
),
);
},
),
)
: Center(
child: Text(
'还没有${widget.tabType == HomeTabType.bangumi ? '追番' : '追剧'}')),
Error() => Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
alignment: Alignment.center,
child: Text(
loadingState.errMsg,
textAlign: TextAlign.center,
),
),
};
}
}