Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-07-24 21:07:26 +08:00
parent f1b79eeafc
commit 56ca0cade4
11 changed files with 137 additions and 117 deletions

View File

@@ -26,6 +26,7 @@ Widget dynTextMenuBuilder(
}, },
), ),
); );
state.addLaunchMenuIfNeeded(buttonItems, index: 5);
return AdaptiveTextSelectionToolbar.buttonItems( return AdaptiveTextSelectionToolbar.buttonItems(
buttonItems: buttonItems, buttonItems: buttonItems,
anchors: state.contextMenuAnchors, anchors: state.contextMenuAnchors,

View File

@@ -53,9 +53,7 @@ void showReplyCopyDialog(
}, },
), ),
); );
if (showEmote) { state.addLaunchMenuIfNeeded(buttonItems, index: 4);
state.addLaunchMenuIfNeeded(buttonItems, index: 4);
}
} }
if (state.isUncollapsed) { if (state.isUncollapsed) {
buttonItems.add( buttonItems.add(

View File

@@ -499,7 +499,7 @@ class ImageGridRenderObjectElement extends RenderObjectElement {
List<ImageModel> picArr, List<ImageModel> picArr,
BoxConstraints layoutInfo, BoxConstraints layoutInfo,
) { ) {
final maxWidth = layoutInfo.maxWidth; final maxWidth = math.min(525.0, layoutInfo.maxWidth);
double imageWidth; double imageWidth;
double imageHeight; double imageHeight;
final length = picArr.length; final length = picArr.length;

View File

@@ -609,13 +609,13 @@ class _GalleryViewerState extends State<GalleryViewer>
items: [ items: [
PopupMenuItem( PopupMenuItem(
height: 42, height: 42,
onTap: () => Utils.copyText(item.url), onTap: () => ImageUtils.downloadImg([item.url]),
child: const Text('复制链接', style: TextStyle(fontSize: 14)), child: const Text('保存图片', style: TextStyle(fontSize: 14)),
), ),
PopupMenuItem( PopupMenuItem(
height: 42, height: 42,
onTap: () => ImageUtils.downloadImg([item.url]), onTap: () => Utils.copyText(item.url),
child: const Text('保存图片', style: TextStyle(fontSize: 14)), child: const Text('复制链接', style: TextStyle(fontSize: 14)),
), ),
PopupMenuItem( PopupMenuItem(
height: 42, height: 42,

View File

@@ -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/stat/stat.dart';
import 'package:PiliPlus/common/widgets/video_popup_menu.dart'; import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
import 'package:PiliPlus/http/search.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/home/rcmd/result.dart';
import 'package:PiliPlus/models/model_rec_video_item.dart'; import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/models_new/video/video_detail/dimension.dart';
@@ -97,7 +96,7 @@ class VideoCardV extends StatelessWidget {
onLongPress: onLongPress, onLongPress: onLongPress,
onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: Style.aspectRatio, aspectRatio: Style.aspectRatio,
@@ -154,21 +153,19 @@ class VideoCardV extends StatelessWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(6, 5, 6, 5), padding: const .fromLTRB(6, 5, 6, 5),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
"${videoItem.title}\n", videoItem.title,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
style: const TextStyle( style: const TextStyle(height: 1.38),
height: 1.38,
),
), ),
), ),
videoStat(context, theme), videoStat(theme),
Row( Row(
spacing: 2, spacing: 2,
children: [ children: [
@@ -207,7 +204,7 @@ class VideoCardV extends StatelessWidget {
child: Text( child: Text(
videoItem.owner.name.toString(), videoItem.owner.name.toString(),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.clip, overflow: .clip,
semanticsLabel: 'UP${videoItem.owner.name}', semanticsLabel: 'UP${videoItem.owner.name}',
style: TextStyle( style: TextStyle(
height: 1.5, height: 1.5,
@@ -228,17 +225,17 @@ class VideoCardV extends StatelessWidget {
static final shortFormat = DateFormat('M-d'); static final shortFormat = DateFormat('M-d');
static final longFormat = DateFormat('yy-M-d'); static final longFormat = DateFormat('yy-M-d');
Widget videoStat(BuildContext context, ThemeData theme) { Widget videoStat(ThemeData theme) {
return Row( return Row(
children: [ children: [
StatWidget( StatWidget(
type: StatType.play, type: .play,
value: videoItem.stat.view, value: videoItem.stat.view,
), ),
if (videoItem.goto != 'picture') ...[ if (videoItem.goto != 'picture') ...[
const SizedBox(width: 4), const SizedBox(width: 4),
StatWidget( StatWidget(
type: StatType.danmaku, type: .danmaku,
value: videoItem.stat.danmu, value: videoItem.stat.danmu,
), ),
], ],
@@ -260,23 +257,6 @@ class VideoCardV extends StatelessWidget {
), ),
const SizedBox(width: 2), 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),
// ]
], ],
); );
} }

View File

@@ -169,6 +169,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
opus: controller.opus!, opus: controller.opus!,
images: controller.images, images: controller.images,
maxWidth: maxWidth, maxWidth: maxWidth,
opusId: controller.id,
); );
} else if (controller.opusData?.modules.moduleBlocked } else if (controller.opusData?.modules.moduleBlocked
case final moduleBlocked?) { case final moduleBlocked?) {

View File

@@ -2,6 +2,7 @@ import 'dart:math' as math;
import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.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/gesture/tap_gesture_recognizer.dart';
import 'package:PiliPlus/common/widgets/image/cached_network_svg_image.dart'; import 'package:PiliPlus/common/widgets/image/cached_network_svg_image.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
@@ -35,28 +36,32 @@ class OpusContent extends StatelessWidget {
final List<ArticleContentModel> opus; final List<ArticleContentModel> opus;
final ValueGetter<List<SourceModel>> images; final ValueGetter<List<SourceModel>> images;
final double maxWidth; final double maxWidth;
final String opusId;
const OpusContent({ const OpusContent({
super.key, super.key,
required this.opus, required this.opus,
required this.images, required this.images,
required this.maxWidth, required this.maxWidth,
required this.opusId,
}); });
static InlineSpan _node2Widget({ static InlineSpan _node2Widget({
required Node item, required Node item,
required ColorScheme colorScheme,
bool isQuote = false, bool isQuote = false,
required String opusId,
required ColorScheme colorScheme,
required ValueGetter<double> surfaceLuminance, required ValueGetter<double> surfaceLuminance,
}) { }) {
switch (item.type) { switch (item.type) {
case 'TEXT_NODE_TYPE_RICH' when (item.rich != null): case 'TEXT_NODE_TYPE_RICH' when (item.rich != null):
Rich rich = item.rich!; final rich = item.rich!;
switch (rich.type) { switch (rich.type) {
case 'RICH_TEXT_NODE_TYPE_EMOJI': case 'RICH_TEXT_NODE_TYPE_EMOJI':
Emoji emoji = rich.emoji!; Emoji emoji = rich.emoji!;
final size = 20.0 * emoji.size; final size = 20.0 * emoji.size;
return WidgetSpan( return EmoteSpan(
rawText: rich.origText,
child: NetworkImgLayer( child: NetworkImgLayer(
width: size, width: size,
height: size, height: size,
@@ -64,6 +69,31 @@ class OpusContent extends StatelessWidget {
type: ImageType.emote, 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: default:
return TextSpan( return TextSpan(
text: text:
@@ -186,6 +216,7 @@ class OpusContent extends StatelessWidget {
?.map( ?.map(
(item) => _node2Widget( (item) => _node2Widget(
item: item, item: item,
opusId: opusId,
colorScheme: colorScheme, colorScheme: colorScheme,
surfaceLuminance: getSurfaceLuminance, surfaceLuminance: getSurfaceLuminance,
), ),
@@ -683,6 +714,7 @@ class OpusContent extends StatelessWidget {
.map( .map(
(e) => _node2Widget( (e) => _node2Widget(
item: e, item: e,
opusId: opusId,
colorScheme: colorScheme, colorScheme: colorScheme,
surfaceLuminance: getSurfaceLuminance, surfaceLuminance: getSurfaceLuminance,
), ),

View File

@@ -9,7 +9,6 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart' import 'package:PiliPlus/models/common/image_preview_type.dart'
show SourceModel; show SourceModel;
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/dynamics/widgets/vote.dart'; import 'package:PiliPlus/pages/dynamics/widgets/vote.dart';
import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart';
@@ -71,6 +70,21 @@ TextSpan? richNode(
} }
spanChildren.add(TextSpan(text: i.origText)); spanChildren.add(TextSpan(text: i.origText));
break; 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': case 'RICH_TEXT_NODE_TYPE_AT':
spanChildren.add( spanChildren.add(
@@ -157,21 +171,6 @@ TextSpan? richNode(
), ),
); );
break; 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': case 'RICH_TEXT_NODE_TYPE_LOTTERY':
spanChildren spanChildren
@@ -200,7 +199,6 @@ TextSpan? richNode(
), ),
); );
break; break;
case 'RICH_TEXT_NODE_TYPE_GOODS': case 'RICH_TEXT_NODE_TYPE_GOODS':
spanChildren spanChildren
..add( ..add(

View File

@@ -268,7 +268,7 @@ class _EpisodePanelState extends State<EpisodePanel>
double _calcItemHeight(ugc.BaseEpisodeItem episode) { double _calcItemHeight(ugc.BaseEpisodeItem episode) {
if (episode is ugc.EpisodeItem && episode.pages!.length > 1) { if (episode is ugc.EpisodeItem && episode.pages!.length > 1) {
return 157; // 110 + 2 + 10 + 35 return 167; // 110 + 2 + 10 + 45
} }
return 112; return 112;
} }
@@ -318,7 +318,7 @@ class _EpisodePanelState extends State<EpisodePanel>
vertical: 5, vertical: 5,
), // 10 ), // 10
child: PagesPanel( child: PagesPanel(
// 35 // 45
list: isCurrTab && isCurrItem list: isCurrTab && isCurrItem
? null ? null
: episode.pages, : episode.pages,
@@ -433,6 +433,7 @@ class _EpisodePanelState extends State<EpisodePanel>
type: .transparency, type: .transparency,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
if (isCurrentIndex) return;
if (episode.badge == "会员" && if (episode.badge == "会员" &&
Accounts.mainEqVideo && Accounts.mainEqVideo &&
vipStatus != 1) { vipStatus != 1) {

View File

@@ -101,24 +101,21 @@ class _PagesPanelState extends State<PagesPanel> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final colorScheme = ColorScheme.of(context);
return Column( return Column(
children: <Widget>[ children: [
if (widget.showEpisodes != null) if (widget.showEpisodes != null)
Padding( Padding(
padding: const EdgeInsets.only(top: 8, bottom: 2), padding: const .only(top: 8, bottom: 2),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: .spaceBetween,
children: [ children: [
const Text('视频选集 '), const Text('视频选集 '),
Expanded( Expanded(
child: Text( child: Text(
' 正在播放:${pages[pageIndex].part}', ' 正在播放:${pages[pageIndex].part}',
overflow: TextOverflow.ellipsis, overflow: .ellipsis,
style: TextStyle( style: TextStyle(fontSize: 12, color: colorScheme.outline),
fontSize: 12,
color: theme.colorScheme.outline,
),
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
@@ -126,7 +123,7 @@ class _PagesPanelState extends State<PagesPanel> {
height: 34, height: 34,
child: TextButton( child: TextButton(
style: const ButtonStyle( style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero), padding: WidgetStatePropertyAll(.zero),
), ),
onPressed: () => widget.showEpisodes!( onPressed: () => widget.showEpisodes!(
null, null,
@@ -146,27 +143,27 @@ class _PagesPanelState extends State<PagesPanel> {
), ),
), ),
SizedBox( SizedBox(
height: 35, height: 45,
child: ListView.builder( child: ListView.builder(
key: PageStorageKey(widget.bvid), key: PageStorageKey(widget.bvid),
controller: _scrollController, controller: _scrollController,
scrollDirection: Axis.horizontal, scrollDirection: .horizontal,
itemCount: pages.length, itemCount: pages.length,
itemExtent: 150, itemExtent: 150,
padding: EdgeInsets.zero, padding: .zero,
itemBuilder: (BuildContext context, int i) { itemBuilder: (context, index) {
bool isCurrentIndex = pageIndex == i; bool isCurrentIndex = pageIndex == index;
final item = pages[i]; final item = pages[index];
return Container( return Container(
width: 150, width: 150,
margin: i != pages.length - 1 margin: index != pages.length - 1
? const EdgeInsets.only(right: 10) ? const .only(right: 10)
: null, : null,
child: Material( child: Material(
color: theme.colorScheme.onInverseSurface, color: colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all(Radius.circular(6)), borderRadius: const .all(.circular(6)),
child: InkWell( child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(6)), borderRadius: const .all(.circular(6)),
onTap: () { onTap: () {
if (widget.onDownload case final onDownload?) { if (widget.onDownload case final onDownload?) {
if (onDownload(item) && mounted) { if (onDownload(item) && mounted) {
@@ -177,6 +174,7 @@ class _PagesPanelState extends State<PagesPanel> {
if (widget.showEpisodes == null) { if (widget.showEpisodes == null) {
Get.back(); Get.back();
} }
if (isCurrentIndex) return;
widget.ugcIntroController.onChangeEpisode( widget.ugcIntroController.onChangeEpisode(
item item
..bvid ??= widget.bvid ..bvid ??= widget.bvid
@@ -193,41 +191,52 @@ class _PagesPanelState extends State<PagesPanel> {
} }
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const .symmetric(horizontal: 8),
child: Row( child: Align(
children: <Widget>[ alignment: .centerLeft,
if (isCurrentIndex) ...<Widget>[ child: Text.rich(
Image.asset( maxLines: 2,
Assets.livingStatic, overflow: .ellipsis,
color: theme.colorScheme.primary, style: TextStyle(
height: 12, height: 1.1,
cacheHeight: 12.cacheSize(context), fontSize: 13,
semanticLabel: "正在播放:", color: isCurrentIndex
), ? colorScheme.primary
const SizedBox(width: 6), : colorScheme.onSurface,
],
Expanded(
child: Text(
item.part!,
maxLines: 1,
style: TextStyle(
fontSize: 13,
color: isCurrentIndex
? theme.colorScheme.primary
: theme.colorScheme.onSurface,
),
overflow: TextOverflow.ellipsis,
),
), ),
if (widget.cidSet?.contains(item.cid) ?? false) strutStyle: const .new(height: 1.1, fontSize: 13),
Icon( TextSpan(
size: 13, children: [
color: theme.colorScheme.secondary.withValues( if (isCurrentIndex)
alpha: 0.8, WidgetSpan(
), alignment: .middle,
FontAwesomeIcons.circleDown, 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),
],
),
),
), ),
), ),
), ),

View File

@@ -1,4 +1,4 @@
import 'dart:async'; import 'dart:async' show FutureOr;
import 'dart:io' show File, Platform; import 'dart:io' show File, Platform;
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:typed_data' show Uint8List; import 'dart:typed_data' show Uint8List;