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); Theme.of(context).colorScheme.outline.withValues(alpha: 0.8);
return Row( return Row(
spacing: 2, spacing: 2,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon( Icon(
type.iconData, 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/activity.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/area.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/episode.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/icon_font.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'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/new_ep.dart';
@@ -53,6 +55,8 @@ class PgcInfoModel {
int? type; int? type;
UpInfo? upInfo; UpInfo? upInfo;
UserStatus? userStatus; UserStatus? userStatus;
List<Cooperator>? cooperators;
Brief? brief;
PgcInfoModel({ PgcInfoModel({
this.activity, this.activity,
@@ -94,6 +98,8 @@ class PgcInfoModel {
this.type, this.type,
this.upInfo, this.upInfo,
this.userStatus, this.userStatus,
this.cooperators,
this.brief,
}); });
factory PgcInfoModel.fromJson(Map<String, dynamic> json) => PgcInfoModel( factory PgcInfoModel.fromJson(Map<String, dynamic> json) => PgcInfoModel(
@@ -164,5 +170,11 @@ class PgcInfoModel {
userStatus: json['user_status'] == null userStatus: json['user_status'] == null
? null ? null
: UserStatus.fromJson(json['user_status'] as Map<String, dynamic>), : 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 { } else {
list.removeWhere(removeList.contains); list.removeWhere(removeList.contains);
} }
if (list.isNotEmpty) { if (!isEnd) {
loadingState.refresh();
} else if (!isEnd) {
onReload(); onReload();
} else { } else {
// empty && end loadingState.refresh();
loadingState.value = const Success(null);
} }
if (enableMultiSelect.value) { if (enableMultiSelect.value) {
rxCount.value = 0; rxCount.value = 0;

View File

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

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.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/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/common/image_preview_type.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/common/stat_type.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart';
import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/pages/video/controller.dart';
@@ -70,14 +72,7 @@ class _PgcIntroPageState extends State<PgcIntroPage>
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final item = pgcIntroController.pgcItem; final item = pgcIntroController.pgcItem;
final isLandscape = context.isLandscape; final isLandscape = context.isLandscape;
return SliverPadding( Widget sliver = SliverToBoxAdapter(
padding: EdgeInsets.only(
left: StyleString.safeSpace,
right: StyleString.safeSpace,
top: StyleString.safeSpace,
bottom: StyleString.safeSpace + MediaQuery.paddingOf(context).bottom,
),
sliver: SliverToBoxAdapter(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -85,26 +80,96 @@ class _PgcIntroPageState extends State<PgcIntroPage>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10, spacing: 10,
children: [ children: [
Stack( _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,
right: StyleString.safeSpace,
top: StyleString.safeSpace,
bottom: StyleString.safeSpace + MediaQuery.paddingOf(context).bottom,
),
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,
),
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, clipBehavior: Clip.none,
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
videoDetailCtr.onViewImage(); videoDetailCtr.onViewImage();
PageUtils.imageView( PageUtils.imageView(
imgList: [ imgList: [SourceModel(url: item.cover!)],
SourceModel(
url: item.cover!,
),
],
onDismissed: videoDetailCtr.onDismissed, onDismissed: videoDetailCtr.onDismissed,
); );
}, },
child: Hero( child: Hero(
tag: item.cover!, tag: item.cover!,
child: NetworkImgLayer( child: NetworkImgLayer(
width: isLandscape ? 115 / 0.75 : 115, width: 115,
height: isLandscape ? 115 : 115 / 0.75, height: 153,
src: item.cover!, src: item.cover!,
semanticsLabel: '封面', semanticsLabel: '封面',
), ),
@@ -119,77 +184,26 @@ class _PgcIntroPageState extends State<PgcIntroPage>
left: null, left: null,
), ),
], ],
), );
Expanded( }
child: !pgcIntroController.isPgc
? Column( Widget _buildInfoPanel(bool isLandscape, ThemeData theme, PgcInfoModel item) {
crossAxisAlignment: CrossAxisAlignment.start, if (pgcIntroController.isPgc) {
children: [ Widget subBtn() => Obx(
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 = final isFollowed = pgcIntroController.isFollowed.value;
pgcIntroController.isFollowed.value; final followStatus = pgcIntroController.followStatus.value;
final followStatus = pgcIntroController
.followStatus
.value;
return FilledButton.tonal( return FilledButton.tonal(
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
tapTargetSize: MaterialTapTargetSize tapTargetSize: MaterialTapTargetSize.shrinkWrap,
.shrinkWrap,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 20,
vertical: 10, vertical: 10,
), ),
visualDensity: visualDensity: VisualDensity.compact,
VisualDensity.compact, foregroundColor: isFollowed ? theme.colorScheme.outline : null,
foregroundColor: isFollowed
? theme.colorScheme.outline
: null,
backgroundColor: isFollowed backgroundColor: isFollowed
? theme ? theme.colorScheme.onInverseSurface
.colorScheme
.onInverseSurface
: null, : null,
), ),
onPressed: followStatus == -1 onPressed: followStatus == -1
@@ -198,19 +212,13 @@ class _PgcIntroPageState extends State<PgcIntroPage>
if (isFollowed) { if (isFollowed) {
showPgcFollowDialog( showPgcFollowDialog(
context: context, context: context,
type: pgcIntroController type: pgcIntroController.pgcType,
.pgcType, followStatus: followStatus,
followStatus: onUpdateStatus: (followStatus) {
followStatus, if (followStatus == -1) {
onUpdateStatus: pgcIntroController.pgcDel();
(followStatus) {
if (followStatus ==
-1) {
pgcIntroController
.pgcDel();
} else { } else {
pgcIntroController pgcIntroController.pgcUpdate(
.pgcUpdate(
followStatus, followStatus,
); );
} }
@@ -227,11 +235,49 @@ class _PgcIntroPageState extends State<PgcIntroPage>
), ),
); );
}, },
);
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!,
), ),
], ],
), ),
Row( style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
),
),
];
Widget stat() => Wrap(
spacing: 6, spacing: 6,
runSpacing: 2,
children: [ children: [
StatWidget( StatWidget(
type: StatType.play, type: StatType.play,
@@ -241,52 +287,110 @@ class _PgcIntroPageState extends State<PgcIntroPage>
type: StatType.danmaku, type: StatType.danmaku,
value: item.stat!.danmaku, value: item.stat!.danmaku,
), ),
if (isLandscape) ...[ if (isLandscape) ...desc(),
areasAndPubTime(theme, item),
newEpDesc(theme, item),
],
], ],
);
return GestureDetector(
onTap: () => widget.showIntroDetail(
item,
pgcIntroController.videoTags.value,
), ),
SizedBox(height: isLandscape ? 2 : 6), behavior: HitTestBehavior.opaque,
if (!isLandscape) ...[ child: SizedBox(
areasAndPubTime(theme, item), height: 153,
newEpDesc(theme, item), child: Column(
], crossAxisAlignment: CrossAxisAlignment.start,
const Spacer(), children: [
Text( title(),
stat(),
const SizedBox(height: 5),
if (!isLandscape) ...desc(),
const SizedBox(height: 5),
Expanded(
child: Text(
'简介:${item.evaluate}', '简介:${item.evaluate}',
maxLines: isLandscape ? 2 : 3,
overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: theme.colorScheme.outline, 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), const SizedBox(height: 6),
// 点赞收藏转发 布局样式2 ] else if (item.upInfo?.mid != null) ...[
if (pgcIntroController.isPgc) upInfo(
actionGrid(theme, item, pgcIntroController), item.upInfo!.mid!,
// 番剧分p item.upInfo!.avatar!,
if (item.episodes!.isNotEmpty) ...[ item.upInfo!.uname!,
PgcPanel( ),
heroTag: widget.heroTag, const SizedBox(height: 8),
pages: item.episodes!, ],
cid: videoDetailCtr.cid.value, Text(
onChangeEpisode: pgcIntroController.onChangeEpisode, item.title!,
showEpisodes: widget.showEpisodes, style: const TextStyle(fontSize: 16),
newEp: item.newEp, ),
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,
),
);
}
} }