opt pgc/pugv intro panel

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-06 17:13:21 +08:00
parent b723529d7f
commit 40a19f2766
7 changed files with 372 additions and 244 deletions

View File

@@ -23,6 +23,7 @@ class StatWidget extends StatelessWidget {
Theme.of(context).colorScheme.outline.withValues(alpha: 0.8);
return Row(
spacing: 2,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
type.iconData,

View File

@@ -0,0 +1,26 @@
class Brief {
List<Img>? img;
Brief({
this.img,
});
factory Brief.fromJson(Map<String, dynamic> json) => Brief(
img: (json['img'] as List?)?.map((e) => Img.fromJson(e)).toList(),
);
}
class Img {
num? aspectRatio;
String? url;
Img({
this.aspectRatio,
this.url,
});
factory Img.fromJson(Map<String, dynamic> json) => Img(
aspectRatio: json['aspect_ratio'],
url: json['url'] as String?,
);
}

View File

@@ -0,0 +1,20 @@
class Cooperator {
int? mid;
String? avatar;
String? nickName;
String? role;
Cooperator({
this.mid,
this.avatar,
this.nickName,
this.role,
});
factory Cooperator.fromJson(Map<String, dynamic> json) => Cooperator(
mid: json['mid'] as int?,
avatar: json['avatar'] as String?,
nickName: json['nick_name'] as String?,
role: json['role'] as String?,
);
}

View File

@@ -1,5 +1,7 @@
import 'package:PiliPlus/models_new/pgc/pgc_info_model/activity.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/area.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/brief.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/cooperator.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/icon_font.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/new_ep.dart';
@@ -53,6 +55,8 @@ class PgcInfoModel {
int? type;
UpInfo? upInfo;
UserStatus? userStatus;
List<Cooperator>? cooperators;
Brief? brief;
PgcInfoModel({
this.activity,
@@ -94,6 +98,8 @@ class PgcInfoModel {
this.type,
this.upInfo,
this.userStatus,
this.cooperators,
this.brief,
});
factory PgcInfoModel.fromJson(Map<String, dynamic> json) => PgcInfoModel(
@@ -164,5 +170,11 @@ class PgcInfoModel {
userStatus: json['user_status'] == null
? null
: UserStatus.fromJson(json['user_status'] as Map<String, dynamic>),
cooperators: (json['cooperators'] as List?)
?.map((e) => Cooperator.fromJson(e))
.toList(),
brief: json['brief'] == null
? null
: Brief.fromJson(json['brief'] as Map<String, dynamic>),
);
}

View File

@@ -81,13 +81,10 @@ mixin DeleteItemMixin<R, T extends MultiSelectData>
} else {
list.removeWhere(removeList.contains);
}
if (list.isNotEmpty) {
loadingState.refresh();
} else if (!isEnd) {
if (!isEnd) {
onReload();
} else {
// empty && end
loadingState.value = const Success(null);
loadingState.refresh();
}
if (enableMultiSelect.value) {
rxCount.value = 0;

View File

@@ -329,6 +329,7 @@ class _MediaPageState extends CommonPageState<MinePage, MineController>
alpha: 0.4,
),
valueColor: AlwaysStoppedAnimation<Color>(secondary),
stopIndicatorColor: Colors.transparent,
),
),
],

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
@@ -6,6 +7,7 @@ import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models/common/stat_type.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart';
import 'package:PiliPlus/pages/video/controller.dart';
@@ -70,6 +72,43 @@ class _PgcIntroPageState extends State<PgcIntroPage>
final ThemeData theme = Theme.of(context);
final item = pgcIntroController.pgcItem;
final isLandscape = context.isLandscape;
Widget sliver = SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
_buildCover(isLandscape, item),
Expanded(child: _buildInfoPanel(isLandscape, theme, item)),
],
),
const SizedBox(height: 6),
// 点赞收藏转发 布局样式2
if (pgcIntroController.isPgc)
actionGrid(theme, item, pgcIntroController),
// 番剧分集
if (item.episodes?.isNotEmpty == true)
PgcPanel(
heroTag: widget.heroTag,
pages: item.episodes!,
cid: videoDetailCtr.cid.value,
onChangeEpisode: pgcIntroController.onChangeEpisode,
showEpisodes: widget.showEpisodes,
newEp: item.newEp,
),
],
),
);
if (!pgcIntroController.isPgc) {
sliver = SliverMainAxisGroup(
slivers: [
sliver,
?_buildBreif(item),
],
);
}
return SliverPadding(
padding: EdgeInsets.only(
left: StyleString.safeSpace,
@@ -77,216 +116,281 @@ class _PgcIntroPageState extends State<PgcIntroPage>
top: StyleString.safeSpace,
bottom: StyleString.safeSpace + MediaQuery.paddingOf(context).bottom,
),
sliver: SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
Stack(
clipBehavior: Clip.none,
children: [
GestureDetector(
onTap: () {
videoDetailCtr.onViewImage();
PageUtils.imageView(
imgList: [
SourceModel(
url: item.cover!,
),
],
onDismissed: videoDetailCtr.onDismissed,
);
},
child: Hero(
tag: item.cover!,
child: NetworkImgLayer(
width: isLandscape ? 115 / 0.75 : 115,
height: isLandscape ? 115 : 115 / 0.75,
src: item.cover!,
semanticsLabel: '封面',
),
),
),
if (item.rating != null)
PBadge(
text: '评分 ${item.rating!.score!}',
top: null,
right: 6,
bottom: 6,
left: null,
),
],
),
Expanded(
child: !pgcIntroController.isPgc
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title!,
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 5),
if (item.subtitle?.isNotEmpty == true)
Text(
item.subtitle!,
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.onSurfaceVariant,
),
),
],
)
: GestureDetector(
onTap: () => widget.showIntroDetail(
item,
pgcIntroController.videoTags.value,
),
behavior: HitTestBehavior.opaque,
child: SizedBox(
height: isLandscape ? 115 : 115 / 0.75,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 20,
children: [
Expanded(
child: Text(
item.title!,
style: const TextStyle(
fontSize: 16,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Obx(
() {
final isFollowed =
pgcIntroController.isFollowed.value;
final followStatus = pgcIntroController
.followStatus
.value;
return FilledButton.tonal(
style: FilledButton.styleFrom(
tapTargetSize: MaterialTapTargetSize
.shrinkWrap,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
visualDensity:
VisualDensity.compact,
foregroundColor: isFollowed
? theme.colorScheme.outline
: null,
backgroundColor: isFollowed
? theme
.colorScheme
.onInverseSurface
: null,
),
onPressed: followStatus == -1
? null
: () {
if (isFollowed) {
showPgcFollowDialog(
context: context,
type: pgcIntroController
.pgcType,
followStatus:
followStatus,
onUpdateStatus:
(followStatus) {
if (followStatus ==
-1) {
pgcIntroController
.pgcDel();
} else {
pgcIntroController
.pgcUpdate(
followStatus,
);
}
},
);
} else {
pgcIntroController.pgcAdd();
}
},
child: Text(
isFollowed
? '${pgcIntroController.pgcType}'
: pgcIntroController.pgcType,
),
);
},
),
],
),
Row(
spacing: 6,
children: [
StatWidget(
type: StatType.play,
value: item.stat!.view,
),
StatWidget(
type: StatType.danmaku,
value: item.stat!.danmaku,
),
if (isLandscape) ...[
areasAndPubTime(theme, item),
newEpDesc(theme, item),
],
],
),
SizedBox(height: isLandscape ? 2 : 6),
if (!isLandscape) ...[
areasAndPubTime(theme, item),
newEpDesc(theme, item),
],
const Spacer(),
Text(
'简介:${item.evaluate}',
maxLines: isLandscape ? 2 : 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.outline,
),
),
],
),
),
),
),
],
sliver: sliver,
);
}
Widget? _buildBreif(PgcInfoModel item) {
final img = item.brief?.img;
if (img != null && img.isNotEmpty) {
return SliverLayoutBuilder(
builder: (context, constraints) {
final maxWidth = constraints.crossAxisExtent;
double padding = max(0, maxWidth - 400);
final imgWidth = maxWidth - padding;
padding = padding / 2;
return SliverPadding(
padding: EdgeInsetsGeometry.only(
top: 10,
left: padding,
right: padding,
),
const SizedBox(height: 6),
// 点赞收藏转发 布局样式2
if (pgcIntroController.isPgc)
actionGrid(theme, item, pgcIntroController),
// 番剧分p
if (item.episodes!.isNotEmpty) ...[
PgcPanel(
heroTag: widget.heroTag,
pages: item.episodes!,
cid: videoDetailCtr.cid.value,
onChangeEpisode: pgcIntroController.onChangeEpisode,
showEpisodes: widget.showEpisodes,
newEp: item.newEp,
sliver: SliverMainAxisGroup(
slivers: img.map((e) {
return SliverToBoxAdapter(
child: NetworkImgLayer(
radius: 0,
src: e.url,
width: imgWidth,
),
);
}).toList(),
),
);
},
);
}
return null;
}
Widget _buildCover(bool isLandscape, PgcInfoModel item) {
return Stack(
clipBehavior: Clip.none,
children: [
GestureDetector(
onTap: () {
videoDetailCtr.onViewImage();
PageUtils.imageView(
imgList: [SourceModel(url: item.cover!)],
onDismissed: videoDetailCtr.onDismissed,
);
},
child: Hero(
tag: item.cover!,
child: NetworkImgLayer(
width: 115,
height: 153,
src: item.cover!,
semanticsLabel: '封面',
),
),
),
if (item.rating != null)
PBadge(
text: '评分 ${item.rating!.score!}',
top: null,
right: 6,
bottom: 6,
left: null,
),
],
);
}
Widget _buildInfoPanel(bool isLandscape, ThemeData theme, PgcInfoModel item) {
if (pgcIntroController.isPgc) {
Widget subBtn() => Obx(
() {
final isFollowed = pgcIntroController.isFollowed.value;
final followStatus = pgcIntroController.followStatus.value;
return FilledButton.tonal(
style: FilledButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
visualDensity: VisualDensity.compact,
foregroundColor: isFollowed ? theme.colorScheme.outline : null,
backgroundColor: isFollowed
? theme.colorScheme.onInverseSurface
: null,
),
onPressed: followStatus == -1
? null
: () {
if (isFollowed) {
showPgcFollowDialog(
context: context,
type: pgcIntroController.pgcType,
followStatus: followStatus,
onUpdateStatus: (followStatus) {
if (followStatus == -1) {
pgcIntroController.pgcDel();
} else {
pgcIntroController.pgcUpdate(
followStatus,
);
}
},
);
} else {
pgcIntroController.pgcAdd();
}
},
child: Text(
isFollowed
? '${pgcIntroController.pgcType}'
: pgcIntroController.pgcType,
),
);
},
);
Widget title() => Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 20,
children: [
Expanded(
child: Text(
item.title!,
style: const TextStyle(fontSize: 16),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
subBtn(),
],
);
List<Widget> desc() => [
Text(
item.newEp!.desc!,
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
Text.rich(
TextSpan(
children: [
if (item.areas?.isNotEmpty == true)
TextSpan(text: '${item.areas!.first.name!} '),
TextSpan(
text: item.publish!.pubTimeShow!,
),
],
],
),
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
),
];
Widget stat() => Wrap(
spacing: 6,
runSpacing: 2,
children: [
StatWidget(
type: StatType.play,
value: item.stat!.view,
),
StatWidget(
type: StatType.danmaku,
value: item.stat!.danmaku,
),
if (isLandscape) ...desc(),
],
);
return GestureDetector(
onTap: () => widget.showIntroDetail(
item,
pgcIntroController.videoTags.value,
),
behavior: HitTestBehavior.opaque,
child: SizedBox(
height: 153,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title(),
stat(),
const SizedBox(height: 5),
if (!isLandscape) ...desc(),
const SizedBox(height: 5),
Expanded(
child: Text(
'简介:${item.evaluate}',
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.outline,
),
),
),
],
),
),
);
}
// pugv
Widget upInfo(int mid, String avatar, String name, {String? role}) =>
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => Get.toNamed('/member?mid=$mid'),
child: Row(
spacing: 8,
mainAxisSize: MainAxisSize.min,
children: [
NetworkImgLayer(
src: avatar,
width: 35,
height: 35,
type: ImageType.avatar,
),
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name),
if (role?.isNotEmpty == true)
Text(
role!,
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
],
),
],
),
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.cooperators?.isNotEmpty == true) ...[
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
spacing: 25,
children: item.cooperators!.map((e) {
return upInfo(e.mid!, e.avatar!, e.nickName!, role: e.role);
}).toList(),
),
),
const SizedBox(height: 6),
] else if (item.upInfo?.mid != null) ...[
upInfo(
item.upInfo!.mid!,
item.upInfo!.avatar!,
item.upInfo!.uname!,
),
const SizedBox(height: 8),
],
Text(
item.title!,
style: const TextStyle(fontSize: 16),
),
if (item.subtitle?.isNotEmpty == true) ...[
const SizedBox(height: 5),
Text(
item.subtitle!,
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.onSurfaceVariant,
),
),
],
],
);
}
@@ -374,37 +478,4 @@ class _PgcIntroPageState extends State<PgcIntroPage>
),
);
}
Widget areasAndPubTime(ThemeData theme, PgcInfoModel item) {
return Row(
spacing: 6,
children: [
if (item.areas?.isNotEmpty == true)
Text(
item.areas!.first.name!,
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
Text(
item.publish!.pubTimeShow!,
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
],
);
}
Widget newEpDesc(ThemeData theme, PgcInfoModel item) {
return Text(
item.newEp!.desc!,
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
);
}
}