diff --git a/lib/common/widgets/context_menu/dyn_menu_helper.dart b/lib/common/widgets/context_menu/dyn_menu_helper.dart index b7c744f1d..9f53fd7a5 100644 --- a/lib/common/widgets/context_menu/dyn_menu_helper.dart +++ b/lib/common/widgets/context_menu/dyn_menu_helper.dart @@ -11,7 +11,7 @@ Widget dynTextMenuBuilder( ContextMenuButtonItem( label: '文本', onPressed: () { - state.hideToolbar(); + state.hideAndClear(); _showTextDialog(text); }, ), @@ -21,9 +21,7 @@ Widget dynTextMenuBuilder( ContextMenuButtonItem( label: '表情', onPressed: () { - state - ..hideToolbar() - ..clearSelection(); + state.hideAndClear(); _showEmoteDialog(moduleDynamic); }, ), diff --git a/lib/common/widgets/context_menu/live_menu_helper.dart b/lib/common/widgets/context_menu/live_menu_helper.dart new file mode 100644 index 000000000..e3df98322 --- /dev/null +++ b/lib/common/widgets/context_menu/live_menu_helper.dart @@ -0,0 +1,40 @@ +part of 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart'; + +Widget scMenuBuilder( + BuildContext context, + SelectableRegionState state, +) { + final buttonItems = state.contextMenuButtonItems; + if (state.isUncollapsed) { + buttonItems + ..insertOrAdd( + 3, + ContextMenuButtonItem( + label: '视频', + onPressed: () { + state.onMenuPressed( + (text) => PiliScheme.videoPush(null, text), + ); + }, + ), + ) + ..insertOrAdd( + 4, + ContextMenuButtonItem( + label: '搜索', + onPressed: () { + state.onMenuPressed( + (text) => Get.toNamed( + '/searchResult', + parameters: {'keyword': text}, + ), + ); + }, + ), + ); + } + 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 f8820cb24..37d6a9be8 100644 --- a/lib/common/widgets/context_menu/reply_menu_helper.dart +++ b/lib/common/widgets/context_menu/reply_menu_helper.dart @@ -46,9 +46,7 @@ void showReplyCopyDialog( ContextMenuButtonItem( label: showEmote ? '文本' : '表情', onPressed: () { - state - ..hideToolbar() - ..clearSelection(); + state.hideAndClear(); showEmote = !showEmote; (context as Element).markNeedsBuild(); }, @@ -58,55 +56,49 @@ void showReplyCopyDialog( state.addLaunchMenuIfNeeded(buttonItems, index: 4); } } - try { - if (state.selectionEndpoints.first != - state.selectionEndpoints[1]) { - buttonItems.add( - ContextMenuButtonItem( - onPressed: () { - final selectedText = (state as dynamic).selectable - ?.getSelectedContent() - ?.plainText; - String text = RegExp.escape(selectedText); - if (ReplyGrpc.enableFilter) text = '|$text'; + if (state.isUncollapsed) { + buttonItems.add( + ContextMenuButtonItem( + onPressed: () { + String text = RegExp.escape(state.selectedText!); + if (ReplyGrpc.enableFilter) text = '|$text'; - showConfirmDialog( - context: context, - title: const Text('是否确认评论过滤的变更:'), - content: Text.rich( - TextSpan( - text: ReplyGrpc.replyRegExp.pattern, - children: [ - TextSpan( - text: text, - style: const TextStyle( - color: Colors.green, - fontWeight: .bold, - ), + showConfirmDialog( + context: context, + title: const Text('是否确认评论过滤的变更:'), + content: Text.rich( + TextSpan( + text: ReplyGrpc.replyRegExp.pattern, + children: [ + TextSpan( + text: text, + style: const TextStyle( + color: Colors.green, + fontWeight: .bold, ), - ], - ), + ), + ], ), - onConfirm: () { - final filter = ReplyGrpc.replyRegExp.pattern + text; - ReplyGrpc.replyRegExp = RegExp( - filter, - caseSensitive: true, - ); - ReplyGrpc.enableFilter = true; - GStorage.setting.put( - SettingBoxKey.banWordForReply, - filter, - ); - SmartDialog.showToast('已保存'); - }, - ); - }, - label: '加入过滤', - ), - ); - } - } catch (_) {} + ), + onConfirm: () { + final filter = ReplyGrpc.replyRegExp.pattern + text; + ReplyGrpc.replyRegExp = RegExp( + filter, + caseSensitive: true, + ); + ReplyGrpc.enableFilter = true; + GStorage.setting.put( + SettingBoxKey.banWordForReply, + filter, + ); + SmartDialog.showToast('已保存'); + }, + ); + }, + label: '加入过滤', + ), + ); + } return AdaptiveTextSelectionToolbar.buttonItems( buttonItems: buttonItems, anchors: state.contextMenuAnchors, diff --git a/lib/pages/live_room/superchat/superchat_card.dart b/lib/pages/live_room/superchat/superchat_card.dart index 614f731c6..b77d80ad0 100644 --- a/lib/pages/live_room/superchat/superchat_card.dart +++ b/lib/pages/live_room/superchat/superchat_card.dart @@ -4,8 +4,11 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/selection_text.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; import 'package:PiliPlus/pages/member/widget/medal_widget.dart'; +import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/color_utils.dart'; import 'package:PiliPlus/utils/date_utils.dart'; +import 'package:PiliPlus/utils/extension/iterable_ext.dart'; +import 'package:PiliPlus/utils/extension/selectable_region_ext.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -15,6 +18,8 @@ import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +part 'package:PiliPlus/common/widgets/context_menu/live_menu_helper.dart'; + class SuperChatCard extends StatefulWidget { const SuperChatCard({ super.key, @@ -250,6 +255,7 @@ class _SuperChatCardState extends State { ), child: SelectionText( item.message, + contextMenuBuilder: scMenuBuilder, style: TextStyle( color: ColourUtils.parseColor(item.messageFontColor), // decoration: widget.persistentSC && item.deleted diff --git a/lib/scripts/selectable_region.patch b/lib/scripts/selectable_region.patch index f2649777f..3bea63f28 100644 --- a/lib/scripts/selectable_region.patch +++ b/lib/scripts/selectable_region.patch @@ -15,18 +15,21 @@ index 97a41dafb8d..70c55832625 100644 } } diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart -index 59de8bae20b..525bb76c064 100644 +index 59de8bae20b..17220a77d04 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart -@@ -404,6 +404,7 @@ class SelectableRegionState extends State +@@ -402,8 +402,10 @@ class SelectableRegionState extends State + final LayerLink _endHandleLayerLink = LayerLink(); + final LayerLink _toolbarLayerLink = LayerLink(); final StaticSelectionContainerDelegate _selectionDelegate = StaticSelectionContainerDelegate(); ++ StaticSelectionContainerDelegate get selectionDelegate => _selectionDelegate; // there should only ever be one selectable, which is the SelectionContainer. Selectable? _selectable; + Selectable? get selectable => _selectable; bool get _hasSelectionOverlayGeometry => _selectionDelegate.value.startSelectionPoint != null || -@@ -599,48 +600,32 @@ class SelectableRegionState extends State +@@ -599,48 +601,32 @@ class SelectableRegionState extends State // // This method should be used in all instances when details.consecutiveTapCount // would be used. @@ -94,7 +97,7 @@ index 59de8bae20b..525bb76c064 100644 } } -@@ -782,22 +767,8 @@ class SelectableRegionState extends State +@@ -782,22 +768,8 @@ class SelectableRegionState extends State _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; } case 3: @@ -119,7 +122,7 @@ index 59de8bae20b..525bb76c064 100644 } _updateSelectedContentIfNeeded(); } -@@ -1049,10 +1020,10 @@ class SelectableRegionState extends State +@@ -1049,10 +1021,10 @@ class SelectableRegionState extends State case TargetPlatform.windows: // If _lastSecondaryTapDownPosition is within the current selection then // keep the current selection, if not then collapse it. @@ -134,7 +137,7 @@ index 59de8bae20b..525bb76c064 100644 // Restore _lastSecondaryTapDownPosition since it may be cleared if a user // accesses contextMenuAnchors. _lastSecondaryTapDownPosition = details.globalPosition; -@@ -1957,6 +1928,8 @@ class SelectableRegionState extends State +@@ -1957,6 +1929,8 @@ class SelectableRegionState extends State // the region on non-web platforms. if (kIsWeb) { _focusNode.unfocus(); @@ -143,7 +146,7 @@ index 59de8bae20b..525bb76c064 100644 } }, child: CompositedTransformTarget( -@@ -2822,7 +2795,10 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai +@@ -2822,7 +2796,10 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai return null; } final buffer = StringBuffer(); diff --git a/lib/utils/extension/selectable_region_ext.dart b/lib/utils/extension/selectable_region_ext.dart index 5c0dc2227..e41b24495 100644 --- a/lib/utils/extension/selectable_region_ext.dart +++ b/lib/utils/extension/selectable_region_ext.dart @@ -1,32 +1,52 @@ import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' show Selectable; extension SelectableRegionStateExt on SelectableRegionState { void addLaunchMenuIfNeeded( List buttonItems, { required int index, }) { - try { - if (selectionEndpoints.first != selectionEndpoints[1]) { - buttonItems.insertOrAdd( - index, - ContextMenuButtonItem( - label: '打开', - onPressed: () { - final text = (this as dynamic).selectable - ?.getSelectedContent() - ?.plainText - .trim(); - hideToolbar(); - clearSelection(); - if (text != null && text.isNotEmpty) { - PageUtils.launchURL(text); - } - }, - ), - ); - } - } catch (_) {} + if (isUncollapsed) { + buttonItems.insertOrAdd( + index, + ContextMenuButtonItem( + label: '打开', + onPressed: () { + onMenuPressed( + PageUtils.launchURL, + content: () => selectedText?.trim(), + ); + }, + ), + ); + } + } + + String? get selectedText => ((this as dynamic).selectable as Selectable?) + ?.getSelectedContent() + ?.plainText; + + bool get isUncollapsed => + ((this as dynamic).selectionDelegate as StaticSelectionContainerDelegate) + .value + .status == + .uncollapsed; + + void onMenuPressed( + ValueChanged callback, { + ValueGetter? content, + }) { + final text = content?.call() ?? selectedText; + hideAndClear(); + if (text != null && text.isNotEmpty) { + callback(text); + } + } + + void hideAndClear() { + hideToolbar(); + clearSelection(); } }