opt context menu

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-07-21 20:44:14 +08:00
parent 8f48ddb42a
commit 0164e32b70
6 changed files with 140 additions and 81 deletions

View File

@@ -11,7 +11,7 @@ Widget dynTextMenuBuilder(
ContextMenuButtonItem( ContextMenuButtonItem(
label: '文本', label: '文本',
onPressed: () { onPressed: () {
state.hideToolbar(); state.hideAndClear();
_showTextDialog(text); _showTextDialog(text);
}, },
), ),
@@ -21,9 +21,7 @@ Widget dynTextMenuBuilder(
ContextMenuButtonItem( ContextMenuButtonItem(
label: '表情', label: '表情',
onPressed: () { onPressed: () {
state state.hideAndClear();
..hideToolbar()
..clearSelection();
_showEmoteDialog(moduleDynamic); _showEmoteDialog(moduleDynamic);
}, },
), ),

View File

@@ -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,
);
}

View File

@@ -46,9 +46,7 @@ void showReplyCopyDialog(
ContextMenuButtonItem( ContextMenuButtonItem(
label: showEmote ? '文本' : '表情', label: showEmote ? '文本' : '表情',
onPressed: () { onPressed: () {
state state.hideAndClear();
..hideToolbar()
..clearSelection();
showEmote = !showEmote; showEmote = !showEmote;
(context as Element).markNeedsBuild(); (context as Element).markNeedsBuild();
}, },
@@ -58,55 +56,49 @@ void showReplyCopyDialog(
state.addLaunchMenuIfNeeded(buttonItems, index: 4); state.addLaunchMenuIfNeeded(buttonItems, index: 4);
} }
} }
try { if (state.isUncollapsed) {
if (state.selectionEndpoints.first != buttonItems.add(
state.selectionEndpoints[1]) { ContextMenuButtonItem(
buttonItems.add( onPressed: () {
ContextMenuButtonItem( String text = RegExp.escape(state.selectedText!);
onPressed: () { if (ReplyGrpc.enableFilter) text = '|$text';
final selectedText = (state as dynamic).selectable
?.getSelectedContent()
?.plainText;
String text = RegExp.escape(selectedText);
if (ReplyGrpc.enableFilter) text = '|$text';
showConfirmDialog( showConfirmDialog(
context: context, context: context,
title: const Text('是否确认评论过滤的变更:'), title: const Text('是否确认评论过滤的变更:'),
content: Text.rich( content: Text.rich(
TextSpan( TextSpan(
text: ReplyGrpc.replyRegExp.pattern, text: ReplyGrpc.replyRegExp.pattern,
children: [ children: [
TextSpan( TextSpan(
text: text, text: text,
style: const TextStyle( style: const TextStyle(
color: Colors.green, color: Colors.green,
fontWeight: .bold, fontWeight: .bold,
),
), ),
], ),
), ],
), ),
onConfirm: () { ),
final filter = ReplyGrpc.replyRegExp.pattern + text; onConfirm: () {
ReplyGrpc.replyRegExp = RegExp( final filter = ReplyGrpc.replyRegExp.pattern + text;
filter, ReplyGrpc.replyRegExp = RegExp(
caseSensitive: true, filter,
); caseSensitive: true,
ReplyGrpc.enableFilter = true; );
GStorage.setting.put( ReplyGrpc.enableFilter = true;
SettingBoxKey.banWordForReply, GStorage.setting.put(
filter, SettingBoxKey.banWordForReply,
); filter,
SmartDialog.showToast('已保存'); );
}, SmartDialog.showToast('已保存');
); },
}, );
label: '加入过滤', },
), label: '加入过滤',
); ),
} );
} catch (_) {} }
return AdaptiveTextSelectionToolbar.buttonItems( return AdaptiveTextSelectionToolbar.buttonItems(
buttonItems: buttonItems, buttonItems: buttonItems,
anchors: state.contextMenuAnchors, anchors: state.contextMenuAnchors,

View File

@@ -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/common/widgets/selection_text.dart';
import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart';
import 'package:PiliPlus/pages/member/widget/medal_widget.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/color_utils.dart';
import 'package:PiliPlus/utils/date_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/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/platform_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:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
part 'package:PiliPlus/common/widgets/context_menu/live_menu_helper.dart';
class SuperChatCard extends StatefulWidget { class SuperChatCard extends StatefulWidget {
const SuperChatCard({ const SuperChatCard({
super.key, super.key,
@@ -250,6 +255,7 @@ class _SuperChatCardState extends State<SuperChatCard> {
), ),
child: SelectionText( child: SelectionText(
item.message, item.message,
contextMenuBuilder: scMenuBuilder,
style: TextStyle( style: TextStyle(
color: ColourUtils.parseColor(item.messageFontColor), color: ColourUtils.parseColor(item.messageFontColor),
// decoration: widget.persistentSC && item.deleted // decoration: widget.persistentSC && item.deleted

View File

@@ -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 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 --- a/packages/flutter/lib/src/widgets/selectable_region.dart
+++ b/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<SelectableRegion> @@ -402,8 +402,10 @@ class SelectableRegionState extends State<SelectableRegion>
final LayerLink _endHandleLayerLink = LayerLink();
final LayerLink _toolbarLayerLink = LayerLink();
final StaticSelectionContainerDelegate _selectionDelegate = StaticSelectionContainerDelegate(); final StaticSelectionContainerDelegate _selectionDelegate = StaticSelectionContainerDelegate();
+ StaticSelectionContainerDelegate get selectionDelegate => _selectionDelegate;
// there should only ever be one selectable, which is the SelectionContainer. // there should only ever be one selectable, which is the SelectionContainer.
Selectable? _selectable; Selectable? _selectable;
+ Selectable? get selectable => _selectable; + Selectable? get selectable => _selectable;
bool get _hasSelectionOverlayGeometry => bool get _hasSelectionOverlayGeometry =>
_selectionDelegate.value.startSelectionPoint != null || _selectionDelegate.value.startSelectionPoint != null ||
@@ -599,48 +600,32 @@ class SelectableRegionState extends State<SelectableRegion> @@ -599,48 +601,32 @@ class SelectableRegionState extends State<SelectableRegion>
// //
// This method should be used in all instances when details.consecutiveTapCount // This method should be used in all instances when details.consecutiveTapCount
// would be used. // would be used.
@@ -94,7 +97,7 @@ index 59de8bae20b..525bb76c064 100644
} }
} }
@@ -782,22 +767,8 @@ class SelectableRegionState extends State<SelectableRegion> @@ -782,22 +768,8 @@ class SelectableRegionState extends State<SelectableRegion>
_selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing;
} }
case 3: case 3:
@@ -119,7 +122,7 @@ index 59de8bae20b..525bb76c064 100644
} }
_updateSelectedContentIfNeeded(); _updateSelectedContentIfNeeded();
} }
@@ -1049,10 +1020,10 @@ class SelectableRegionState extends State<SelectableRegion> @@ -1049,10 +1021,10 @@ class SelectableRegionState extends State<SelectableRegion>
case TargetPlatform.windows: case TargetPlatform.windows:
// If _lastSecondaryTapDownPosition is within the current selection then // If _lastSecondaryTapDownPosition is within the current selection then
// keep the current selection, if not then collapse it. // 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 // Restore _lastSecondaryTapDownPosition since it may be cleared if a user
// accesses contextMenuAnchors. // accesses contextMenuAnchors.
_lastSecondaryTapDownPosition = details.globalPosition; _lastSecondaryTapDownPosition = details.globalPosition;
@@ -1957,6 +1928,8 @@ class SelectableRegionState extends State<SelectableRegion> @@ -1957,6 +1929,8 @@ class SelectableRegionState extends State<SelectableRegion>
// the region on non-web platforms. // the region on non-web platforms.
if (kIsWeb) { if (kIsWeb) {
_focusNode.unfocus(); _focusNode.unfocus();
@@ -143,7 +146,7 @@ index 59de8bae20b..525bb76c064 100644
} }
}, },
child: CompositedTransformTarget( child: CompositedTransformTarget(
@@ -2822,7 +2795,10 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai @@ -2822,7 +2796,10 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
return null; return null;
} }
final buffer = StringBuffer(); final buffer = StringBuffer();

View File

@@ -1,32 +1,52 @@
import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart';
import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show Selectable;
extension SelectableRegionStateExt on SelectableRegionState { extension SelectableRegionStateExt on SelectableRegionState {
void addLaunchMenuIfNeeded( void addLaunchMenuIfNeeded(
List<ContextMenuButtonItem> buttonItems, { List<ContextMenuButtonItem> buttonItems, {
required int index, required int index,
}) { }) {
try { if (isUncollapsed) {
if (selectionEndpoints.first != selectionEndpoints[1]) { buttonItems.insertOrAdd(
buttonItems.insertOrAdd( index,
index, ContextMenuButtonItem(
ContextMenuButtonItem( label: '打开',
label: '打开', onPressed: () {
onPressed: () { onMenuPressed(
final text = (this as dynamic).selectable PageUtils.launchURL,
?.getSelectedContent() content: () => selectedText?.trim(),
?.plainText );
.trim(); },
hideToolbar(); ),
clearSelection(); );
if (text != null && text.isNotEmpty) { }
PageUtils.launchURL(text); }
}
}, String? get selectedText => ((this as dynamic).selectable as Selectable?)
), ?.getSelectedContent()
); ?.plainText;
}
} catch (_) {} bool get isUncollapsed =>
((this as dynamic).selectionDelegate as StaticSelectionContainerDelegate)
.value
.status ==
.uncollapsed;
void onMenuPressed(
ValueChanged<String> callback, {
ValueGetter<String?>? content,
}) {
final text = content?.call() ?? selectedText;
hideAndClear();
if (text != null && text.isNotEmpty) {
callback(text);
}
}
void hideAndClear() {
hideToolbar();
clearSelection();
} }
} }