feat: pgc timeline

Closes #653

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-13 19:54:21 +08:00
parent d9c6c31a4d
commit 68df173558
19 changed files with 791 additions and 378 deletions

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class PBadge extends StatelessWidget { class PBadge extends StatelessWidget {
@@ -17,7 +18,7 @@ class PBadge extends StatelessWidget {
const PBadge({ const PBadge({
super.key, super.key,
this.text, required this.text,
this.top, this.top,
this.right, this.right,
this.bottom, this.bottom,
@@ -34,15 +35,19 @@ class PBadge extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (text.isNullOrEmpty) {
return const SizedBox.shrink();
}
ColorScheme t = Theme.of(context).colorScheme; ColorScheme t = Theme.of(context).colorScheme;
// 背景色 // 背景色
Color bgColor = t.primary; Color bgColor = t.primary;
// 前景色 // 前景色
Color color = t.onPrimary; Color color = t.onPrimary;
// 边框色 // 边框色
Color borderColor = Colors.transparent; Color? borderColor;
if (type == 'gray') { if (type == 'gray') {
bgColor = Colors.black54.withOpacity(0.45); bgColor = Colors.black45;
color = Colors.white; color = Colors.white;
} else if (type == 'color') { } else if (type == 'color') {
bgColor = t.secondaryContainer.withOpacity(0.5); bgColor = t.secondaryContainer.withOpacity(0.5);
@@ -72,10 +77,10 @@ class PBadge extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: br, borderRadius: br,
color: bgColor, color: bgColor,
border: Border.all(color: borderColor), border: borderColor != null ? Border.all(color: borderColor) : null,
), ),
child: Text( child: Text(
text ?? "", text!,
textScaler: textScaleFactor != null textScaler: textScaleFactor != null
? TextScaler.linear(textScaleFactor!) ? TextScaler.linear(textScaleFactor!)
: null, : null,

View File

@@ -141,11 +141,7 @@ class VideoCardH extends StatelessWidget {
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
if (videoItem is HotVideoItemModel && if (videoItem is HotVideoItemModel)
(videoItem as HotVideoItemModel)
.pgcLabel
?.isNotEmpty ==
true)
PBadge( PBadge(
text: text:
(videoItem as HotVideoItemModel).pgcLabel, (videoItem as HotVideoItemModel).pgcLabel,

View File

@@ -760,4 +760,6 @@ class Api {
static const String getLiveEmoticons = static const String getLiveEmoticons =
'${HttpString.liveBaseUrl}/xlive/web-ucenter/v2/emoticon/GetEmoticons'; '${HttpString.liveBaseUrl}/xlive/web-ucenter/v2/emoticon/GetEmoticons';
static const String pgcTimeline = '/pgc/web/timeline';
} }

View File

@@ -1,4 +1,6 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/pgc_timeline/pgc_timeline.dart';
import 'package:PiliPlus/models/bangumi/pgc_timeline/result.dart';
import '../models/bangumi/list.dart'; import '../models/bangumi/list.dart';
import '../models/bangumi/pgc_index/condition.dart'; import '../models/bangumi/pgc_index/condition.dart';
@@ -86,4 +88,24 @@ class BangumiHttp {
return LoadingState.error(res.data['message']); return LoadingState.error(res.data['message']);
} }
} }
static Future<LoadingState<List<Result>?>> pgcTimeline({
int types = 1, // 1`番剧`<br />3`电影`<br />4`国创` |
required int before,
required int after,
}) async {
var res = await Request().get(
Api.pgcTimeline,
queryParameters: {
'types': types,
'before': before,
'after': after,
},
);
if (res.data['code'] == 0) {
return LoadingState.success(PgcTimeline.fromJson(res.data).result);
} else {
return LoadingState.error(res.data['message']);
}
}
} }

View File

@@ -0,0 +1,91 @@
import 'icon_font.dart';
class Episode {
String? cover;
int? delay;
int? delayId;
String? delayIndex;
String? delayReason;
bool? enableVt;
String? epCover;
int? episodeId;
int? follow;
String? follows;
IconFont? iconFont;
String? plays;
String? pubIndex;
String? pubTime;
int? pubTs;
int? published;
int? seasonId;
String? squareCover;
String? title;
Episode({
this.cover,
this.delay,
this.delayId,
this.delayIndex,
this.delayReason,
this.enableVt,
this.epCover,
this.episodeId,
this.follow,
this.follows,
this.iconFont,
this.plays,
this.pubIndex,
this.pubTime,
this.pubTs,
this.published,
this.seasonId,
this.squareCover,
this.title,
});
factory Episode.fromJson(Map<String, dynamic> json) => Episode(
cover: json['cover'] as String?,
delay: json['delay'] as int?,
delayId: json['delay_id'] as int?,
delayIndex: json['delay_index'] as String?,
delayReason: json['delay_reason'] as String?,
enableVt: json['enable_vt'] as bool?,
epCover: json['ep_cover'] as String?,
episodeId: json['episode_id'] as int?,
follow: json['follow'] as int?,
follows: json['follows'] as String?,
iconFont: json['icon_font'] == null
? null
: IconFont.fromJson(json['icon_font'] as Map<String, dynamic>),
plays: json['plays'] as String?,
pubIndex: json['pub_index'] as String?,
pubTime: json['pub_time'] as String?,
pubTs: json['pub_ts'] as int?,
published: json['published'] as int?,
seasonId: json['season_id'] as int?,
squareCover: json['square_cover'] as String?,
title: json['title'] as String?,
);
Map<String, dynamic> toJson() => {
'cover': cover,
'delay': delay,
'delay_id': delayId,
'delay_index': delayIndex,
'delay_reason': delayReason,
'enable_vt': enableVt,
'ep_cover': epCover,
'episode_id': episodeId,
'follow': follow,
'follows': follows,
'icon_font': iconFont?.toJson(),
'plays': plays,
'pub_index': pubIndex,
'pub_time': pubTime,
'pub_ts': pubTs,
'published': published,
'season_id': seasonId,
'square_cover': squareCover,
'title': title,
};
}

View File

@@ -0,0 +1,16 @@
class IconFont {
String? name;
String? text;
IconFont({this.name, this.text});
factory IconFont.fromJson(Map<String, dynamic> json) => IconFont(
name: json['name'] as String?,
text: json['text'] as String?,
);
Map<String, dynamic> toJson() => {
'name': name,
'text': text,
};
}

View File

@@ -0,0 +1,23 @@
import 'result.dart';
class PgcTimeline {
int? code;
String? message;
List<Result>? result;
PgcTimeline({this.code, this.message, this.result});
factory PgcTimeline.fromJson(Map<String, dynamic> json) => PgcTimeline(
code: json['code'] as int?,
message: json['message'] as String?,
result: (json['result'] as List<dynamic>?)
?.map((e) => Result.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> toJson() => {
'code': code,
'message': message,
'result': result?.map((e) => e.toJson()).toList(),
};
}

View File

@@ -0,0 +1,35 @@
import 'episode.dart';
class Result {
String? date;
int? dateTs;
int? dayOfWeek;
List<Episode>? episodes;
int? isToday;
Result({
this.date,
this.dateTs,
this.dayOfWeek,
this.episodes,
this.isToday,
});
factory Result.fromJson(Map<String, dynamic> json) => Result(
date: json['date'] as String?,
dateTs: json['date_ts'] as int?,
dayOfWeek: json['day_of_week'] as int?,
episodes: (json['episodes'] as List<dynamic>?)
?.map((e) => Episode.fromJson(e as Map<String, dynamic>))
.toList(),
isToday: json['is_today'] as int?,
);
Map<String, dynamic> toJson() => {
'date': date,
'date_ts': dateTs,
'day_of_week': dayOfWeek,
'episodes': episodes?.map((e) => e.toJson()).toList(),
'is_today': isToday,
};
}

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/list.dart'; import 'package:PiliPlus/models/bangumi/list.dart';
import 'package:PiliPlus/models/bangumi/pgc_timeline/result.dart';
import 'package:PiliPlus/models/common/tab_type.dart'; import 'package:PiliPlus/models/common/tab_type.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
@@ -24,6 +25,9 @@ class BangumiController extends CommonListController<
queryData(); queryData();
queryBangumiFollow(); queryBangumiFollow();
if (tabType == TabType.bangumi) {
queryPgcTimeline();
}
if (isLogin.value) { if (isLogin.value) {
followController = ScrollController(); followController = ScrollController();
} }
@@ -36,16 +40,30 @@ class BangumiController extends CommonListController<
followEnd = false; followEnd = false;
} }
queryBangumiFollow(); queryBangumiFollow();
if (tabType == TabType.bangumi) {
queryPgcTimeline();
}
return super.onRefresh(); return super.onRefresh();
} }
// follow
late int followPage = 1; late int followPage = 1;
late RxInt followCount = (-1).obs; late RxInt followCount = (-1).obs;
late bool followLoading = false; late bool followLoading = false;
late bool followEnd = false; late bool followEnd = false;
late Rx<LoadingState> followState = LoadingState.loading().obs; late Rx<LoadingState<List<BangumiListItemModel>?>> followState =
LoadingState<List<BangumiListItemModel>?>.loading().obs;
ScrollController? followController; ScrollController? followController;
// timeline
late Rx<LoadingState<List<Result>?>> timelineState =
LoadingState<List<Result>?>.loading().obs;
Future queryPgcTimeline() async {
final res = await BangumiHttp.pgcTimeline(types: 1, before: 6, after: 6);
timelineState.value = res;
}
// 我的订阅 // 我的订阅
Future queryBangumiFollow([bool isRefresh = true]) async { Future queryBangumiFollow([bool isRefresh = true]) async {
if (isLogin.value.not || followLoading || (isRefresh.not && followEnd)) { if (isLogin.value.not || followLoading || (isRefresh.not && followEnd)) {

View File

@@ -1,12 +1,15 @@
import 'dart:async'; import 'dart:math';
import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/bangumi/list.dart'; import 'package:PiliPlus/models/bangumi/list.dart';
import 'package:PiliPlus/models/bangumi/pgc_timeline/result.dart';
import 'package:PiliPlus/models/common/tab_type.dart'; import 'package:PiliPlus/models/common/tab_type.dart';
import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_page.dart'; import 'package:PiliPlus/pages/bangumi/pgc_index/pgc_index_page.dart';
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_card_v_timeline.dart';
import 'package:PiliPlus/pages/common/common_page.dart'; import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
@@ -45,108 +48,148 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
super.build(context); super.build(context);
return refreshIndicator( return refreshIndicator(
onRefresh: () async { onRefresh: () async {
await Future.wait([ await controller.onRefresh();
controller.onRefresh(),
controller.queryBangumiFollow(),
]);
}, },
child: CustomScrollView( child: CustomScrollView(
controller: controller.scrollController, controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
_buildFollow,
if (widget.tabType == TabType.bangumi)
SliverToBoxAdapter( SliverToBoxAdapter(
child: Obx( child: Container(
() => controller.isLogin.value margin: const EdgeInsets.only(top: 10),
? Column( height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(110),
child:
Obx(() => _buildTimeline(controller.timelineState.value)),
),
),
..._buildRcmd,
],
),
);
}
late final List<String> weekList = [
'',
'',
'',
'',
'',
'',
'',
];
Widget _buildTimeline(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: [ children: [
Padding( Row(
padding: const EdgeInsets.only(left: 16),
child: Row(
children: [ children: [
Obx( const SizedBox(width: 16),
() => Text( Text(
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${controller.followCount.value == -1 ? '' : ' ${controller.followCount.value}'}', '追番时间表',
style: style: Theme.of(context).textTheme.titleMedium,
Theme.of(context).textTheme.titleMedium, ),
const SizedBox(width: 16),
Expanded(
child: Material(
color: Colors.transparent,
child: TabBar(
isScrollable: true,
tabAlignment: TabAlignment.start,
tabs: loadingState.response!
.map(
(item) => Tab(
text:
'${item.date} ${item.isToday == 1 ? '今天' : '${weekList[item.dayOfWeek! - 1]}'}',
),
)
.toList(),
), ),
), ),
const Spacer(),
IconButton(
tooltip: '刷新',
onPressed: () {
controller
..followPage = 1
..followEnd = false
..queryBangumiFollow();
},
icon: const Icon(
Icons.refresh,
size: 20,
), ),
],
),
const SizedBox(height: 10),
Expanded(
child: TabBarView(
physics: const NeverScrollableScrollPhysics(),
children: loadingState.response!.map((item) {
if (item.episodes!.isNullOrEmpty) {
return const SizedBox.shrink();
}
return MediaQuery.removePadding(
context: context,
removeLeft: context.orientation ==
Orientation.landscape,
child: 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],
), ),
Obx(
() => controller.isLogin.value
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Get.toNamed(
'/fav',
arguments: widget.tabType ==
TabType.bangumi
? 1
: 2,
); );
}, },
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'查看全部',
strutStyle: StrutStyle(
leading: 0, height: 1),
style: TextStyle(
height: 1,
color: Theme.of(context)
.colorScheme
.secondary,
), ),
), );
Icon( }).toList()),
Icons.chevron_right,
color: Theme.of(context)
.colorScheme
.secondary,
), ),
], ],
), ),
), );
), })
)
: const SizedBox.shrink(), : 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,
), ),
), ),
SizedBox( ),
height: Grid.smallCardWidth / 2 / 0.75 + LoadingState() => throw UnimplementedError(),
MediaQuery.textScalerOf(context).scale(50), };
child: Obx(
() => List<Widget> get _buildRcmd => [
_buildFollowBody(controller.followState.value), _buildRcmdTitle,
SliverPadding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
sliver: Obx(
() => _buildRcmdBody(controller.loadingState.value),
), ),
), ),
], ];
)
: const SizedBox.shrink(), Widget get _buildRcmdTitle => SliverToBoxAdapter(
),
),
SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 10, top: 10,
@@ -224,20 +267,10 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
], ],
), ),
), ),
),
SliverPadding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
sliver: Obx(
() => _buildBody(controller.loadingState.value),
),
),
],
),
); );
}
Widget _buildBody(LoadingState<List<BangumiListItemModel>?> loadingState) { Widget _buildRcmdBody(
LoadingState<List<BangumiListItemModel>?> loadingState) {
return switch (loadingState) { return switch (loadingState) {
Loading() => const SliverToBoxAdapter(), Loading() => const SliverToBoxAdapter(),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
@@ -274,36 +307,122 @@ class _BangumiPageState extends CommonPageState<BangumiPage, BangumiController>
}; };
} }
Widget _buildFollowList(Success loadingState) { Widget get _buildFollow => SliverToBoxAdapter(
return ListView.builder( child: Obx(
() => controller.isLogin.value
? Column(
children: [
_buildFollowTitle,
SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(50),
child: Obx(
() => _buildFollowBody(controller.followState.value),
),
),
],
)
: const SizedBox.shrink(),
),
);
Widget get _buildFollowTitle => Padding(
padding: const EdgeInsets.only(left: 16),
child: Row(
children: [
Obx(
() => Text(
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${controller.followCount.value == -1 ? '' : ' ${controller.followCount.value}'}',
style: Theme.of(context).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 == TabType.bangumi ? 1 : 2,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'查看全部',
strutStyle: StrutStyle(leading: 0, height: 1),
style: TextStyle(
height: 1,
color:
Theme.of(context).colorScheme.secondary,
),
),
Icon(
Icons.chevron_right,
color: Theme.of(context).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, controller: controller.followController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: loadingState.response.length, itemCount: loadingState.response!.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response!.length - 1) {
controller.queryBangumiFollow(false); controller.queryBangumiFollow(false);
} }
return Container( return Container(
width: Grid.smallCardWidth / 2, width: Grid.smallCardWidth / 2,
margin: EdgeInsets.only( margin: EdgeInsets.only(
left: StyleString.safeSpace, left: StyleString.safeSpace,
right: index == loadingState.response.length - 1 right: index == loadingState.response!.length - 1
? StyleString.safeSpace ? StyleString.safeSpace
: 0, : 0,
), ),
child: BangumiCardV( child: BangumiCardV(
bangumiItem: loadingState.response[index], bangumiItem: loadingState.response![index],
), ),
); );
}, },
); ),
} )
Widget _buildFollowBody(LoadingState loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
? _buildFollowList(loadingState)
: Center( : Center(
child: Text( child: Text(
'还没有${widget.tabType == TabType.bangumi ? '追番' : '追剧'}')), '还没有${widget.tabType == TabType.bangumi ? '追番' : '追剧'}')),

View File

@@ -31,6 +31,7 @@ class BangumiCardV extends StatelessWidget {
Utils.viewBangumi(seasonId: seasonId); Utils.viewBangumi(seasonId: seasonId);
}, },
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: StyleString.mdRadius, borderRadius: StyleString.mdRadius,
@@ -49,7 +50,6 @@ class BangumiCardV extends StatelessWidget {
height: maxHeight, height: maxHeight,
), ),
), ),
if (bangumiItem.badge != null)
PBadge( PBadge(
text: bangumiItem.badge, text: bangumiItem.badge,
top: 6, top: 6,
@@ -97,10 +97,7 @@ class BangumiCardV extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Text(
children: [
Expanded(
child: Text(
bangumiItem.title, bangumiItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
@@ -108,8 +105,6 @@ class BangumiCardV extends StatelessWidget {
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
)),
],
), ),
const SizedBox(height: 1), const SizedBox(height: 1),
if (bangumiItem.indexShow != null) if (bangumiItem.indexShow != null)

View File

@@ -31,6 +31,7 @@ class BangumiCardVMemberHome extends StatelessWidget {
cover: bangumiItem.cover, cover: bangumiItem.cover,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
@@ -94,10 +95,7 @@ Widget bangumiContent(Item bangumiItem) {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Text(
children: [
Expanded(
child: Text(
bangumiItem.title, bangumiItem.title,
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
@@ -105,8 +103,6 @@ Widget bangumiContent(Item bangumiItem) {
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
)),
],
), ),
const SizedBox(height: 1), const SizedBox(height: 1),
// if (bangumiItem.indexShow != null) // if (bangumiItem.indexShow != null)

View File

@@ -29,6 +29,7 @@ class BangumiCardVPgcIndex extends StatelessWidget {
Utils.viewBangumi(seasonId: bangumiItem['season_id']); Utils.viewBangumi(seasonId: bangumiItem['season_id']);
}, },
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: StyleString.mdRadius, borderRadius: StyleString.mdRadius,
@@ -44,8 +45,6 @@ class BangumiCardVPgcIndex extends StatelessWidget {
width: maxWidth, width: maxWidth,
height: maxHeight, height: maxHeight,
), ),
if (bangumiItem['badge'] != null &&
bangumiItem['badge'] != '')
PBadge( PBadge(
text: bangumiItem['badge'], text: bangumiItem['badge'],
top: 6, top: 6,
@@ -53,8 +52,6 @@ class BangumiCardVPgcIndex extends StatelessWidget {
bottom: null, bottom: null,
left: null, left: null,
), ),
if (bangumiItem['order'] != null &&
bangumiItem['order'] != '')
PBadge( PBadge(
text: bangumiItem['order'], text: bangumiItem['order'],
top: null, top: null,
@@ -86,10 +83,7 @@ class BangumiCardVPgcIndex extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Text(
children: [
Expanded(
child: Text(
bangumiItem['title'], bangumiItem['title'],
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
@@ -97,8 +91,6 @@ class BangumiCardVPgcIndex extends StatelessWidget {
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
)),
],
), ),
const SizedBox(height: 1), const SizedBox(height: 1),
if (bangumiItem['index_show'] != null) if (bangumiItem['index_show'] != null)

View File

@@ -0,0 +1,105 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/models/bangumi/pgc_timeline/episode.dart';
import 'package:flutter/material.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局
class BangumiCardVTimeline extends StatelessWidget {
const BangumiCardVTimeline({
super.key,
required this.item,
});
final Episode item;
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: InkWell(
onLongPress: () => imageSaveDialog(
context: context,
title: item.title,
cover: item.cover,
),
onTap: () async {
Utils.viewBangumi(
seasonId: item.seasonId,
epId: item.episodeId,
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: StyleString.mdRadius,
child: AspectRatio(
aspectRatio: 0.75,
child: LayoutBuilder(builder: (context, boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: item.cover,
width: maxWidth,
height: maxHeight,
),
if (item.follow == 1)
PBadge(
text: '已追番',
right: 6,
top: 6,
),
PBadge(
text: '${item.pubTime}',
left: 6,
bottom: 6,
type: 'gray',
),
],
);
}),
),
),
bagumiContent(context)
],
),
),
);
}
Widget bagumiContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title ?? '',
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
item.pubIndex ?? '',
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
),
);
}
}

View File

@@ -516,14 +516,11 @@ Widget forWard(bool isSave, item, BuildContext context, source, callback,
src: item.modules.moduleDynamic.major.medialist['cover'], src: item.modules.moduleDynamic.major.medialist['cover'],
), ),
), ),
if (item.modules.moduleDynamic.major.medialist['badge']
?['text'] !=
null)
PBadge( PBadge(
right: 6, right: 6,
top: 6, top: 6,
text: item.modules.moduleDynamic.major.medialist['badge'] text: item.modules.moduleDynamic.major.medialist['badge']
['text'], ?['text'],
) )
], ],
), ),

View File

@@ -65,7 +65,6 @@ class FavPgcItem extends StatelessWidget {
width: boxConstraints.maxWidth, width: boxConstraints.maxWidth,
height: boxConstraints.maxHeight, height: boxConstraints.maxHeight,
), ),
if (item.badge?.isNotEmpty == true)
PBadge( PBadge(
right: 4, right: 4,
top: 4, top: 4,

View File

@@ -123,9 +123,8 @@ class FavVideoCardH extends StatelessWidget {
bottom: 6.0, bottom: 6.0,
type: 'gray', type: 'gray',
), ),
if (videoItem.ogv != null)
PBadge( PBadge(
text: videoItem.ogv!['type_name'], text: videoItem.ogv?['type_name'],
top: 6.0, top: 6.0,
right: 6.0, right: 6.0,
bottom: null, bottom: null,

View File

@@ -186,7 +186,10 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
child: loadingWidget, child: loadingWidget,
), ),
Success() => (loadingState.response as List?)?.isNotEmpty == true Success() => (loadingState.response as List?)?.isNotEmpty == true
? SelfSizedHorizontalList( ? MediaQuery.removePadding(
context: context,
removeLeft: context.orientation == Orientation.landscape,
child: SelfSizedHorizontalList(
gapSize: 5, gapSize: 5,
childBuilder: (index) { childBuilder: (index) {
if (index == loadingState.response.length - 1) { if (index == loadingState.response.length - 1) {
@@ -233,12 +236,12 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
src: loadingState.response[index].face, src: loadingState.response[index].face,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 4),
Text( Text(
loadingState.response[index].uname, loadingState.response[index].uname,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 11), style: TextStyle(fontSize: 12),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -247,6 +250,7 @@ class _LivePageState extends CommonPageState<LivePage, LiveController>
); );
}, },
itemCount: loadingState.response.length, itemCount: loadingState.response.length,
),
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
Error() => GestureDetector( Error() => GestureDetector(

View File

@@ -60,7 +60,6 @@ Widget searchBangumiPanel(
height: 148, height: 148,
src: i.cover, src: i.cover,
), ),
if (i.seasonTypeName?.isNotEmpty == true)
PBadge( PBadge(
text: i.seasonTypeName, text: i.seasonTypeName,
top: 6.0, top: 6.0,