SearchBar context menu (#154833)
SearchBar and SearchAnchor can now control their context menu. They both received new contextMenuBuilder parameters. See the docs for EditableText.contextMenuBuilder for how to use this, including how to use the native context menu on iOS and to control the browser's context menu on web.
This commit is contained in:
parent
f964f15dcb
commit
24d0b1db0a
@ -8,6 +8,7 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'adaptive_text_selection_toolbar.dart';
|
||||||
import 'back_button.dart';
|
import 'back_button.dart';
|
||||||
import 'button_style.dart';
|
import 'button_style.dart';
|
||||||
import 'color_scheme.dart';
|
import 'color_scheme.dart';
|
||||||
@ -186,6 +187,7 @@ class SearchAnchor extends StatefulWidget {
|
|||||||
TextInputAction? textInputAction,
|
TextInputAction? textInputAction,
|
||||||
TextInputType? keyboardType,
|
TextInputType? keyboardType,
|
||||||
EdgeInsets scrollPadding,
|
EdgeInsets scrollPadding,
|
||||||
|
EditableTextContextMenuBuilder contextMenuBuilder,
|
||||||
}) = _SearchAnchorWithSearchBar;
|
}) = _SearchAnchorWithSearchBar;
|
||||||
|
|
||||||
/// Whether the search view grows to fill the entire screen when the
|
/// Whether the search view grows to fill the entire screen when the
|
||||||
@ -1053,6 +1055,7 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
|
|||||||
super.textInputAction,
|
super.textInputAction,
|
||||||
super.keyboardType,
|
super.keyboardType,
|
||||||
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
|
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
|
||||||
|
EditableTextContextMenuBuilder contextMenuBuilder = SearchBar._defaultContextMenuBuilder,
|
||||||
}) : super(
|
}) : super(
|
||||||
viewHintText: viewHintText ?? barHintText,
|
viewHintText: viewHintText ?? barHintText,
|
||||||
headerHeight: viewHeaderHeight,
|
headerHeight: viewHeaderHeight,
|
||||||
@ -1087,6 +1090,7 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
|
|||||||
textInputAction: textInputAction,
|
textInputAction: textInputAction,
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
scrollPadding: scrollPadding,
|
scrollPadding: scrollPadding,
|
||||||
|
contextMenuBuilder: contextMenuBuilder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -1208,6 +1212,7 @@ class SearchBar extends StatefulWidget {
|
|||||||
this.textInputAction,
|
this.textInputAction,
|
||||||
this.keyboardType,
|
this.keyboardType,
|
||||||
this.scrollPadding = const EdgeInsets.all(20.0),
|
this.scrollPadding = const EdgeInsets.all(20.0),
|
||||||
|
this.contextMenuBuilder = _defaultContextMenuBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Controls the text being edited in the search bar's text field.
|
/// Controls the text being edited in the search bar's text field.
|
||||||
@ -1356,6 +1361,23 @@ class SearchBar extends StatefulWidget {
|
|||||||
/// {@macro flutter.widgets.editableText.scrollPadding}
|
/// {@macro flutter.widgets.editableText.scrollPadding}
|
||||||
final EdgeInsets scrollPadding;
|
final EdgeInsets scrollPadding;
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.EditableText.contextMenuBuilder}
|
||||||
|
///
|
||||||
|
/// If not provided, will build a default menu based on the platform.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [AdaptiveTextSelectionToolbar], which is built by default.
|
||||||
|
/// * [BrowserContextMenu], which allows the browser's context menu on web to
|
||||||
|
/// be disabled and Flutter-rendered context menus to appear.
|
||||||
|
final EditableTextContextMenuBuilder? contextMenuBuilder;
|
||||||
|
|
||||||
|
static Widget _defaultContextMenuBuilder(BuildContext context, EditableTextState editableTextState) {
|
||||||
|
return AdaptiveTextSelectionToolbar.editableText(
|
||||||
|
editableTextState: editableTextState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SearchBar> createState() => _SearchBarState();
|
State<SearchBar> createState() => _SearchBarState();
|
||||||
}
|
}
|
||||||
@ -1497,6 +1519,7 @@ class _SearchBarState extends State<SearchBar> {
|
|||||||
textInputAction: widget.textInputAction,
|
textInputAction: widget.textInputAction,
|
||||||
keyboardType: widget.keyboardType,
|
keyboardType: widget.keyboardType,
|
||||||
scrollPadding: widget.scrollPadding,
|
scrollPadding: widget.scrollPadding,
|
||||||
|
contextMenuBuilder: widget.contextMenuBuilder,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -838,6 +838,8 @@ class TextField extends StatefulWidget {
|
|||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [AdaptiveTextSelectionToolbar], which is built by default.
|
/// * [AdaptiveTextSelectionToolbar], which is built by default.
|
||||||
|
/// * [BrowserContextMenu], which allows the browser's context menu on web to
|
||||||
|
/// be disabled and Flutter-rendered context menus to appear.
|
||||||
final EditableTextContextMenuBuilder? contextMenuBuilder;
|
final EditableTextContextMenuBuilder? contextMenuBuilder;
|
||||||
|
|
||||||
/// Determine whether this text field can request the primary focus.
|
/// Determine whether this text field can request the primary focus.
|
||||||
|
@ -1902,10 +1902,10 @@ class EditableText extends StatefulWidget {
|
|||||||
/// The [TextSelectionToolbarLayoutDelegate] class may be particularly useful
|
/// The [TextSelectionToolbarLayoutDelegate] class may be particularly useful
|
||||||
/// in honoring the preferred anchor positions.
|
/// in honoring the preferred anchor positions.
|
||||||
///
|
///
|
||||||
/// For backwards compatibility, when [selectionControls] is set to an object
|
/// For backwards compatibility, when [EditableText.selectionControls] is set
|
||||||
/// that does not mix in [TextSelectionHandleControls], [contextMenuBuilder]
|
/// to an object that does not mix in [TextSelectionHandleControls],
|
||||||
/// is ignored and the [TextSelectionControls.buildToolbar] method is used
|
/// [contextMenuBuilder] is ignored and the
|
||||||
/// instead.
|
/// [TextSelectionControls.buildToolbar] method is used instead.
|
||||||
///
|
///
|
||||||
/// {@tool dartpad}
|
/// {@tool dartpad}
|
||||||
/// This example shows how to customize the menu, in this case by keeping the
|
/// This example shows how to customize the menu, in this case by keeping the
|
||||||
|
@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import '../widgets/semantics_tester.dart';
|
import '../widgets/semantics_tester.dart';
|
||||||
@ -3361,6 +3362,63 @@ void main() {
|
|||||||
final EditableText editableText = tester.widget(find.byType(EditableText));
|
final EditableText editableText = tester.widget(find.byType(EditableText));
|
||||||
expect(editableText.scrollPadding, scrollPadding);
|
expect(editableText.scrollPadding, scrollPadding);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('contextMenuBuilder', () {
|
||||||
|
setUp(() async {
|
||||||
|
if (!kIsWeb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
|
SystemChannels.contextMenu,
|
||||||
|
(MethodCall call) {
|
||||||
|
// Just complete successfully, so that BrowserContextMenu thinks that
|
||||||
|
// the engine successfully received its call.
|
||||||
|
return Future<void>.value();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await BrowserContextMenu.disableContextMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
if (!kIsWeb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await BrowserContextMenu.enableContextMenu();
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.contextMenu, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SearchAnchor.bar.contextMenuBuilder is passed through to EditableText', (WidgetTester tester) async {
|
||||||
|
Widget contextMenuBuilder(BuildContext context, EditableTextState editableTextState) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: SearchAnchor.bar(
|
||||||
|
suggestionsBuilder: (BuildContext context, SearchController controller) {
|
||||||
|
return <Widget>[];
|
||||||
|
},
|
||||||
|
contextMenuBuilder: contextMenuBuilder,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byType(EditableText), findsOneWidget);
|
||||||
|
final EditableText editableText = tester.widget(find.byType(EditableText));
|
||||||
|
expect(editableText.contextMenuBuilder, contextMenuBuilder);
|
||||||
|
|
||||||
|
expect(find.byType(Placeholder), findsNothing);
|
||||||
|
|
||||||
|
await tester.tap(
|
||||||
|
find.byType(SearchBar),
|
||||||
|
buttons: kSecondaryButton,
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(Placeholder), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkSearchBarDefaults(WidgetTester tester, ColorScheme colorScheme, Material material) async {
|
Future<void> checkSearchBarDefaults(WidgetTester tester, ColorScheme colorScheme, Material material) async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user