From 40a19f276654fd08ba2348c874d61a825b981ce0 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Wed, 6 Aug 2025 17:13:21 +0800 Subject: [PATCH] opt pgc/pugv intro panel Signed-off-by: bggRGjQaUbCoE --- lib/common/widgets/stat/stat.dart | 1 + lib/models_new/pgc/pgc_info_model/brief.dart | 26 + .../pgc/pgc_info_model/cooperator.dart | 20 + lib/models_new/pgc/pgc_info_model/result.dart | 12 + lib/pages/common/multi_select/base.dart | 7 +- lib/pages/mine/view.dart | 1 + lib/pages/video/introduction/pgc/view.dart | 549 ++++++++++-------- 7 files changed, 372 insertions(+), 244 deletions(-) create mode 100644 lib/models_new/pgc/pgc_info_model/brief.dart create mode 100644 lib/models_new/pgc/pgc_info_model/cooperator.dart diff --git a/lib/common/widgets/stat/stat.dart b/lib/common/widgets/stat/stat.dart index 94e05f45..53780027 100644 --- a/lib/common/widgets/stat/stat.dart +++ b/lib/common/widgets/stat/stat.dart @@ -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, diff --git a/lib/models_new/pgc/pgc_info_model/brief.dart b/lib/models_new/pgc/pgc_info_model/brief.dart new file mode 100644 index 00000000..06b067a3 --- /dev/null +++ b/lib/models_new/pgc/pgc_info_model/brief.dart @@ -0,0 +1,26 @@ +class Brief { + List? img; + + Brief({ + this.img, + }); + + factory Brief.fromJson(Map 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 json) => Img( + aspectRatio: json['aspect_ratio'], + url: json['url'] as String?, + ); +} diff --git a/lib/models_new/pgc/pgc_info_model/cooperator.dart b/lib/models_new/pgc/pgc_info_model/cooperator.dart new file mode 100644 index 00000000..583c5196 --- /dev/null +++ b/lib/models_new/pgc/pgc_info_model/cooperator.dart @@ -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 json) => Cooperator( + mid: json['mid'] as int?, + avatar: json['avatar'] as String?, + nickName: json['nick_name'] as String?, + role: json['role'] as String?, + ); +} diff --git a/lib/models_new/pgc/pgc_info_model/result.dart b/lib/models_new/pgc/pgc_info_model/result.dart index 6bdc9da2..7d529e5e 100644 --- a/lib/models_new/pgc/pgc_info_model/result.dart +++ b/lib/models_new/pgc/pgc_info_model/result.dart @@ -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? 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 json) => PgcInfoModel( @@ -164,5 +170,11 @@ class PgcInfoModel { userStatus: json['user_status'] == null ? null : UserStatus.fromJson(json['user_status'] as Map), + cooperators: (json['cooperators'] as List?) + ?.map((e) => Cooperator.fromJson(e)) + .toList(), + brief: json['brief'] == null + ? null + : Brief.fromJson(json['brief'] as Map), ); } diff --git a/lib/pages/common/multi_select/base.dart b/lib/pages/common/multi_select/base.dart index f012f395..fe0ac96c 100644 --- a/lib/pages/common/multi_select/base.dart +++ b/lib/pages/common/multi_select/base.dart @@ -81,13 +81,10 @@ mixin DeleteItemMixin } 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; diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index d3af7019..aef6385a 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -329,6 +329,7 @@ class _MediaPageState extends CommonPageState alpha: 0.4, ), valueColor: AlwaysStoppedAnimation(secondary), + stopIndicatorColor: Colors.transparent, ), ), ], diff --git a/lib/pages/video/introduction/pgc/view.dart b/lib/pages/video/introduction/pgc/view.dart index 26b1fba5..ff8be74c 100644 --- a/lib/pages/video/introduction/pgc/view.dart +++ b/lib/pages/video/introduction/pgc/view.dart @@ -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 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 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 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 ), ); } - - 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, - ), - ); - } }