From 56ca0cade48d20aada40a08b1dd36aa82d75d8ce Mon Sep 17 00:00:00 2001 From: dom Date: Fri, 24 Jul 2026 21:07:26 +0800 Subject: [PATCH] opt ui Signed-off-by: dom --- .../widgets/context_menu/dyn_menu_helper.dart | 1 + .../context_menu/reply_menu_helper.dart | 4 +- .../image_grid/image_grid_builder.dart | 2 +- .../widgets/image_viewer/gallery_viewer.dart | 8 +- .../widgets/video_card/video_card_v.dart | 42 ++----- lib/pages/article/view.dart | 1 + lib/pages/article/widgets/opus_content.dart | 38 +++++- .../dynamics/widgets/rich_node_panel.dart | 32 +++-- lib/pages/episode_panel/view.dart | 5 +- .../video/introduction/ugc/widgets/page.dart | 119 ++++++++++-------- lib/utils/image_utils.dart | 2 +- 11 files changed, 137 insertions(+), 117 deletions(-) diff --git a/lib/common/widgets/context_menu/dyn_menu_helper.dart b/lib/common/widgets/context_menu/dyn_menu_helper.dart index be0ada7ad..be08b37a6 100644 --- a/lib/common/widgets/context_menu/dyn_menu_helper.dart +++ b/lib/common/widgets/context_menu/dyn_menu_helper.dart @@ -26,6 +26,7 @@ Widget dynTextMenuBuilder( }, ), ); + state.addLaunchMenuIfNeeded(buttonItems, index: 5); return AdaptiveTextSelectionToolbar.buttonItems( buttonItems: buttonItems, anchors: state.contextMenuAnchors, diff --git a/lib/common/widgets/context_menu/reply_menu_helper.dart b/lib/common/widgets/context_menu/reply_menu_helper.dart index 62a91e468..3f0091a51 100644 --- a/lib/common/widgets/context_menu/reply_menu_helper.dart +++ b/lib/common/widgets/context_menu/reply_menu_helper.dart @@ -53,9 +53,7 @@ void showReplyCopyDialog( }, ), ); - if (showEmote) { - state.addLaunchMenuIfNeeded(buttonItems, index: 4); - } + state.addLaunchMenuIfNeeded(buttonItems, index: 4); } if (state.isUncollapsed) { buttonItems.add( diff --git a/lib/common/widgets/image_grid/image_grid_builder.dart b/lib/common/widgets/image_grid/image_grid_builder.dart index 0136aeaac..c9e8e1b83 100644 --- a/lib/common/widgets/image_grid/image_grid_builder.dart +++ b/lib/common/widgets/image_grid/image_grid_builder.dart @@ -499,7 +499,7 @@ class ImageGridRenderObjectElement extends RenderObjectElement { List picArr, BoxConstraints layoutInfo, ) { - final maxWidth = layoutInfo.maxWidth; + final maxWidth = math.min(525.0, layoutInfo.maxWidth); double imageWidth; double imageHeight; final length = picArr.length; diff --git a/lib/common/widgets/image_viewer/gallery_viewer.dart b/lib/common/widgets/image_viewer/gallery_viewer.dart index 77d77f5f6..94ca0261f 100644 --- a/lib/common/widgets/image_viewer/gallery_viewer.dart +++ b/lib/common/widgets/image_viewer/gallery_viewer.dart @@ -609,13 +609,13 @@ class _GalleryViewerState extends State items: [ PopupMenuItem( height: 42, - onTap: () => Utils.copyText(item.url), - child: const Text('复制链接', style: TextStyle(fontSize: 14)), + onTap: () => ImageUtils.downloadImg([item.url]), + child: const Text('保存图片', style: TextStyle(fontSize: 14)), ), PopupMenuItem( height: 42, - onTap: () => ImageUtils.downloadImg([item.url]), - child: const Text('保存图片', style: TextStyle(fontSize: 14)), + onTap: () => Utils.copyText(item.url), + child: const Text('复制链接', style: TextStyle(fontSize: 14)), ), PopupMenuItem( height: 42, diff --git a/lib/common/widgets/video_card/video_card_v.dart b/lib/common/widgets/video_card/video_card_v.dart index 1aee0e2ee..09cda2eb0 100644 --- a/lib/common/widgets/video_card/video_card_v.dart +++ b/lib/common/widgets/video_card/video_card_v.dart @@ -5,7 +5,6 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/common/widgets/video_popup_menu.dart'; import 'package:PiliPlus/http/search.dart'; -import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models/home/rcmd/result.dart'; import 'package:PiliPlus/models/model_rec_video_item.dart'; import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; @@ -97,7 +96,7 @@ class VideoCardV extends StatelessWidget { onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ AspectRatio( aspectRatio: Style.aspectRatio, @@ -154,21 +153,19 @@ class VideoCardV extends StatelessWidget { final theme = Theme.of(context); return Expanded( child: Padding( - padding: const EdgeInsets.fromLTRB(6, 5, 6, 5), + padding: const .fromLTRB(6, 5, 6, 5), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ Expanded( child: Text( - "${videoItem.title}\n", + videoItem.title, maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - height: 1.38, - ), + overflow: .ellipsis, + style: const TextStyle(height: 1.38), ), ), - videoStat(context, theme), + videoStat(theme), Row( spacing: 2, children: [ @@ -207,7 +204,7 @@ class VideoCardV extends StatelessWidget { child: Text( videoItem.owner.name.toString(), maxLines: 1, - overflow: TextOverflow.clip, + overflow: .clip, semanticsLabel: 'UP:${videoItem.owner.name}', style: TextStyle( height: 1.5, @@ -228,17 +225,17 @@ class VideoCardV extends StatelessWidget { static final shortFormat = DateFormat('M-d'); static final longFormat = DateFormat('yy-M-d'); - Widget videoStat(BuildContext context, ThemeData theme) { + Widget videoStat(ThemeData theme) { return Row( children: [ StatWidget( - type: StatType.play, + type: .play, value: videoItem.stat.view, ), if (videoItem.goto != 'picture') ...[ const SizedBox(width: 4), StatWidget( - type: StatType.danmaku, + type: .danmaku, value: videoItem.stat.danmu, ), ], @@ -260,23 +257,6 @@ class VideoCardV extends StatelessWidget { ), const SizedBox(width: 2), ], - // deprecated - // else if (videoItem is RcmdVideoItemAppModel && - // videoItem.desc != null && - // videoItem.desc!.contains(' · ')) ...[ - // const Spacer(), - // Text.rich( - // maxLines: 1, - // TextSpan( - // style: TextStyle( - // fontSize: theme.textTheme.labelSmall!.fontSize, - // color: theme.colorScheme.outline.withValues(alpha: 0.8), - // ), - // text: Utils.shortenChineseDateString( - // videoItem.desc!.split(' · ').last)), - // ), - // const SizedBox(width: 2), - // ] ], ); } diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index 59f206839..33bc02725 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -169,6 +169,7 @@ class _ArticlePageState extends CommonDynPageState { opus: controller.opus!, images: controller.images, maxWidth: maxWidth, + opusId: controller.id, ); } else if (controller.opusData?.modules.moduleBlocked case final moduleBlocked?) { diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index 44c16aea5..242c976bb 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; +import 'package:PiliPlus/common/widgets/emote_span.dart'; import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/cached_network_svg_image.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -35,28 +36,32 @@ class OpusContent extends StatelessWidget { final List opus; final ValueGetter> images; final double maxWidth; + final String opusId; const OpusContent({ super.key, required this.opus, required this.images, required this.maxWidth, + required this.opusId, }); static InlineSpan _node2Widget({ required Node item, - required ColorScheme colorScheme, bool isQuote = false, + required String opusId, + required ColorScheme colorScheme, required ValueGetter surfaceLuminance, }) { switch (item.type) { case 'TEXT_NODE_TYPE_RICH' when (item.rich != null): - Rich rich = item.rich!; + final rich = item.rich!; switch (rich.type) { case 'RICH_TEXT_NODE_TYPE_EMOJI': Emoji emoji = rich.emoji!; final size = 20.0 * emoji.size; - return WidgetSpan( + return EmoteSpan( + rawText: rich.origText, child: NetworkImgLayer( width: size, height: size, @@ -64,6 +69,31 @@ class OpusContent extends StatelessWidget { type: ImageType.emote, ), ); + case 'RICH_TEXT_NODE_TYPE_LOTTERY': + return TextSpan( + children: [ + WidgetSpan( + alignment: .middle, + child: Icon( + Icons.redeem_rounded, + size: 16, + color: colorScheme.primary, + ), + ), + TextSpan( + text: '${rich.origText} ', + style: TextStyle(color: colorScheme.primary), + recognizer: NoDeadlineTapGestureRecognizer() + ..onTap = () => Get.toNamed( + '/webview', + parameters: { + 'url': + 'https://www.bilibili.com/h5/lottery/result?business_id=$opusId', + }, + ), + ), + ], + ); default: return TextSpan( text: @@ -186,6 +216,7 @@ class OpusContent extends StatelessWidget { ?.map( (item) => _node2Widget( item: item, + opusId: opusId, colorScheme: colorScheme, surfaceLuminance: getSurfaceLuminance, ), @@ -683,6 +714,7 @@ class OpusContent extends StatelessWidget { .map( (e) => _node2Widget( item: e, + opusId: opusId, colorScheme: colorScheme, surfaceLuminance: getSurfaceLuminance, ), diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart index 26ca52e22..f3082e7c3 100644 --- a/lib/pages/dynamics/widgets/rich_node_panel.dart +++ b/lib/pages/dynamics/widgets/rich_node_panel.dart @@ -9,7 +9,6 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart' show SourceModel; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/dynamics/widgets/vote.dart'; import 'package:PiliPlus/utils/page_utils.dart'; @@ -71,6 +70,21 @@ TextSpan? richNode( } spanChildren.add(TextSpan(text: i.origText)); break; + // 表情 + case 'RICH_TEXT_NODE_TYPE_EMOJI' when (i.emoji != null): + final size = i.emoji!.size * 20.0; + spanChildren.add( + EmoteSpan( + rawText: i.origText, + child: NetworkImgLayer( + src: i.emoji!.url, + type: .emote, + width: size, + height: size, + ), + ), + ); + break; // @用户 case 'RICH_TEXT_NODE_TYPE_AT': spanChildren.add( @@ -157,21 +171,6 @@ TextSpan? richNode( ), ); break; - // 表情 - case 'RICH_TEXT_NODE_TYPE_EMOJI' when (i.emoji != null): - final size = i.emoji!.size * 20.0; - spanChildren.add( - EmoteSpan( - rawText: i.origText, - child: NetworkImgLayer( - src: i.emoji!.url, - type: ImageType.emote, - width: size, - height: size, - ), - ), - ); - break; // 抽奖 case 'RICH_TEXT_NODE_TYPE_LOTTERY': spanChildren @@ -200,7 +199,6 @@ TextSpan? richNode( ), ); break; - case 'RICH_TEXT_NODE_TYPE_GOODS': spanChildren ..add( diff --git a/lib/pages/episode_panel/view.dart b/lib/pages/episode_panel/view.dart index 362f6ea75..72526f0f5 100644 --- a/lib/pages/episode_panel/view.dart +++ b/lib/pages/episode_panel/view.dart @@ -268,7 +268,7 @@ class _EpisodePanelState extends State double _calcItemHeight(ugc.BaseEpisodeItem episode) { if (episode is ugc.EpisodeItem && episode.pages!.length > 1) { - return 157; // 110 + 2 + 10 + 35 + return 167; // 110 + 2 + 10 + 45 } return 112; } @@ -318,7 +318,7 @@ class _EpisodePanelState extends State vertical: 5, ), // 10 child: PagesPanel( - // 35 + // 45 list: isCurrTab && isCurrItem ? null : episode.pages, @@ -433,6 +433,7 @@ class _EpisodePanelState extends State type: .transparency, child: InkWell( onTap: () { + if (isCurrentIndex) return; if (episode.badge == "会员" && Accounts.mainEqVideo && vipStatus != 1) { diff --git a/lib/pages/video/introduction/ugc/widgets/page.dart b/lib/pages/video/introduction/ugc/widgets/page.dart index 5b249cc2e..937c4cb69 100644 --- a/lib/pages/video/introduction/ugc/widgets/page.dart +++ b/lib/pages/video/introduction/ugc/widgets/page.dart @@ -101,24 +101,21 @@ class _PagesPanelState extends State { @override Widget build(BuildContext context) { - final theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); return Column( - children: [ + children: [ if (widget.showEpisodes != null) Padding( - padding: const EdgeInsets.only(top: 8, bottom: 2), + padding: const .only(top: 8, bottom: 2), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: .spaceBetween, children: [ const Text('视频选集 '), Expanded( child: Text( ' 正在播放:${pages[pageIndex].part}', - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 12, - color: theme.colorScheme.outline, - ), + overflow: .ellipsis, + style: TextStyle(fontSize: 12, color: colorScheme.outline), ), ), const SizedBox(width: 10), @@ -126,7 +123,7 @@ class _PagesPanelState extends State { height: 34, child: TextButton( style: const ButtonStyle( - padding: WidgetStatePropertyAll(EdgeInsets.zero), + padding: WidgetStatePropertyAll(.zero), ), onPressed: () => widget.showEpisodes!( null, @@ -146,27 +143,27 @@ class _PagesPanelState extends State { ), ), SizedBox( - height: 35, + height: 45, child: ListView.builder( key: PageStorageKey(widget.bvid), controller: _scrollController, - scrollDirection: Axis.horizontal, + scrollDirection: .horizontal, itemCount: pages.length, itemExtent: 150, - padding: EdgeInsets.zero, - itemBuilder: (BuildContext context, int i) { - bool isCurrentIndex = pageIndex == i; - final item = pages[i]; + padding: .zero, + itemBuilder: (context, index) { + bool isCurrentIndex = pageIndex == index; + final item = pages[index]; return Container( width: 150, - margin: i != pages.length - 1 - ? const EdgeInsets.only(right: 10) + margin: index != pages.length - 1 + ? const .only(right: 10) : null, child: Material( - color: theme.colorScheme.onInverseSurface, - borderRadius: const BorderRadius.all(Radius.circular(6)), + color: colorScheme.onInverseSurface, + borderRadius: const .all(.circular(6)), child: InkWell( - borderRadius: const BorderRadius.all(Radius.circular(6)), + borderRadius: const .all(.circular(6)), onTap: () { if (widget.onDownload case final onDownload?) { if (onDownload(item) && mounted) { @@ -177,6 +174,7 @@ class _PagesPanelState extends State { if (widget.showEpisodes == null) { Get.back(); } + if (isCurrentIndex) return; widget.ugcIntroController.onChangeEpisode( item ..bvid ??= widget.bvid @@ -193,41 +191,52 @@ class _PagesPanelState extends State { } }, child: Padding( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - if (isCurrentIndex) ...[ - Image.asset( - Assets.livingStatic, - color: theme.colorScheme.primary, - height: 12, - cacheHeight: 12.cacheSize(context), - semanticLabel: "正在播放:", - ), - const SizedBox(width: 6), - ], - Expanded( - child: Text( - item.part!, - maxLines: 1, - style: TextStyle( - fontSize: 13, - color: isCurrentIndex - ? theme.colorScheme.primary - : theme.colorScheme.onSurface, - ), - overflow: TextOverflow.ellipsis, - ), + padding: const .symmetric(horizontal: 8), + child: Align( + alignment: .centerLeft, + child: Text.rich( + maxLines: 2, + overflow: .ellipsis, + style: TextStyle( + height: 1.1, + fontSize: 13, + color: isCurrentIndex + ? colorScheme.primary + : colorScheme.onSurface, ), - if (widget.cidSet?.contains(item.cid) ?? false) - Icon( - size: 13, - color: theme.colorScheme.secondary.withValues( - alpha: 0.8, - ), - FontAwesomeIcons.circleDown, - ), - ], + strutStyle: const .new(height: 1.1, fontSize: 13), + TextSpan( + children: [ + if (isCurrentIndex) + WidgetSpan( + alignment: .middle, + child: Padding( + padding: const .only(right: 6), + child: Image.asset( + Assets.livingStatic, + color: colorScheme.primary, + height: 12, + cacheHeight: 12.cacheSize(context), + semanticLabel: "正在播放:", + ), + ), + ) + else if (widget.cidSet?.contains(item.cid) ?? + false) + WidgetSpan( + alignment: .middle, + child: Icon( + size: 13, + color: colorScheme.secondary.withValues( + alpha: .8, + ), + FontAwesomeIcons.circleDown, + ), + ), + TextSpan(text: item.part), + ], + ), + ), ), ), ), diff --git a/lib/utils/image_utils.dart b/lib/utils/image_utils.dart index 8713712a7..d76d97a32 100644 --- a/lib/utils/image_utils.dart +++ b/lib/utils/image_utils.dart @@ -1,4 +1,4 @@ -import 'dart:async'; +import 'dart:async' show FutureOr; import 'dart:io' show File, Platform; import 'dart:math' as math; import 'dart:typed_data' show Uint8List;