mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
showDialogs adds a requestFocus
parameter. (#162928)
Fixes: #162920 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
01fe4e262a
commit
a6ff677120
@ -1277,6 +1277,10 @@ class CupertinoModalPopupRoute<T> extends PopupRoute<T> {
|
||||
/// [StatefulBuilder] or a custom [StatefulWidget] if the widget needs to
|
||||
/// update dynamically.
|
||||
///
|
||||
/// The [requestFocus] parameter is used to specify whether the popup should
|
||||
/// request focus when shown.
|
||||
/// {@macro flutter.widgets.navigator.Route.requestFocus}
|
||||
///
|
||||
/// {@macro flutter.widgets.RawDialogRoute}
|
||||
///
|
||||
/// Returns a `Future` that resolves to the value that was passed to
|
||||
@ -1318,6 +1322,7 @@ Future<T?> showCupertinoModalPopup<T>({
|
||||
bool semanticsDismissible = false,
|
||||
RouteSettings? routeSettings,
|
||||
Offset? anchorPoint,
|
||||
bool? requestFocus,
|
||||
}) {
|
||||
return Navigator.of(context, rootNavigator: useRootNavigator).push(
|
||||
CupertinoModalPopupRoute<T>(
|
||||
@ -1328,6 +1333,7 @@ Future<T?> showCupertinoModalPopup<T>({
|
||||
semanticsDismissible: semanticsDismissible,
|
||||
settings: routeSettings,
|
||||
anchorPoint: anchorPoint,
|
||||
requestFocus: requestFocus,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1361,6 +1367,9 @@ Widget _buildCupertinoDialogTransitions(
|
||||
/// By default, `useRootNavigator` is `true` and the dialog route created by
|
||||
/// this method is pushed to the root navigator.
|
||||
///
|
||||
/// {@macro flutter.material.dialog.requestFocus}
|
||||
/// {@macro flutter.widgets.navigator.Route.requestFocus}
|
||||
///
|
||||
/// {@macro flutter.widgets.RawDialogRoute}
|
||||
///
|
||||
/// If the application has multiple [Navigator] objects, it may be necessary to
|
||||
@ -1405,6 +1414,7 @@ Future<T?> showCupertinoDialog<T>({
|
||||
bool barrierDismissible = false,
|
||||
RouteSettings? routeSettings,
|
||||
Offset? anchorPoint,
|
||||
bool? requestFocus,
|
||||
}) {
|
||||
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(
|
||||
CupertinoDialogRoute<T>(
|
||||
@ -1415,6 +1425,7 @@ Future<T?> showCupertinoDialog<T>({
|
||||
barrierColor: CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context),
|
||||
settings: routeSettings,
|
||||
anchorPoint: anchorPoint,
|
||||
requestFocus: requestFocus,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1202,6 +1202,10 @@ class ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
||||
/// The [sheetAnimationStyle] parameter is used to override the modal bottom sheet
|
||||
/// animation duration and reverse animation duration.
|
||||
///
|
||||
/// The [requestFocus] parameter is used to specify whether the bottom sheet should
|
||||
/// request focus when shown.
|
||||
/// {@macro flutter.widgets.navigator.Route.requestFocus}
|
||||
///
|
||||
/// If [AnimationStyle.duration] is provided, it will be used to override
|
||||
/// the modal bottom sheet animation duration in the underlying
|
||||
/// [BottomSheet.createAnimationController].
|
||||
@ -1254,6 +1258,7 @@ Future<T?> showModalBottomSheet<T>({
|
||||
AnimationController? transitionAnimationController,
|
||||
Offset? anchorPoint,
|
||||
AnimationStyle? sheetAnimationStyle,
|
||||
bool? requestFocus,
|
||||
}) {
|
||||
assert(debugCheckHasMediaQuery(context));
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
@ -1282,6 +1287,7 @@ Future<T?> showModalBottomSheet<T>({
|
||||
anchorPoint: anchorPoint,
|
||||
useSafeArea: useSafeArea,
|
||||
sheetAnimationStyle: sheetAnimationStyle,
|
||||
requestFocus: requestFocus,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1395,6 +1395,12 @@ Widget _buildMaterialDialogTransitions(
|
||||
/// [TraversalEdgeBehavior.closedLoop] is used, because it's typical for dialogs
|
||||
/// to allow users to cycle through dialog widgets without leaving the dialog.
|
||||
///
|
||||
/// {@template flutter.material.dialog.requestFocus}
|
||||
/// The `requestFocus` argument is used to specify whether the dialog should
|
||||
/// request focus when shown.
|
||||
/// {@endtemplate}
|
||||
/// {@macro flutter.widgets.navigator.Route.requestFocus}
|
||||
///
|
||||
/// {@macro flutter.widgets.RawDialogRoute}
|
||||
///
|
||||
/// If the application has multiple [Navigator] objects, it may be necessary to
|
||||
@ -1459,6 +1465,7 @@ Future<T?> showDialog<T>({
|
||||
RouteSettings? routeSettings,
|
||||
Offset? anchorPoint,
|
||||
TraversalEdgeBehavior? traversalEdgeBehavior,
|
||||
bool? requestFocus,
|
||||
}) {
|
||||
assert(_debugIsActive(context));
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
@ -1484,6 +1491,7 @@ Future<T?> showDialog<T>({
|
||||
themes: themes,
|
||||
anchorPoint: anchorPoint,
|
||||
traversalEdgeBehavior: traversalEdgeBehavior ?? TraversalEdgeBehavior.closedLoop,
|
||||
requestFocus: requestFocus,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1507,6 +1515,7 @@ Future<T?> showAdaptiveDialog<T>({
|
||||
RouteSettings? routeSettings,
|
||||
Offset? anchorPoint,
|
||||
TraversalEdgeBehavior? traversalEdgeBehavior,
|
||||
bool? requestFocus,
|
||||
}) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
switch (theme.platform) {
|
||||
@ -1525,6 +1534,7 @@ Future<T?> showAdaptiveDialog<T>({
|
||||
routeSettings: routeSettings,
|
||||
anchorPoint: anchorPoint,
|
||||
traversalEdgeBehavior: traversalEdgeBehavior,
|
||||
requestFocus: requestFocus,
|
||||
);
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
@ -1536,6 +1546,7 @@ Future<T?> showAdaptiveDialog<T>({
|
||||
useRootNavigator: useRootNavigator,
|
||||
anchorPoint: anchorPoint,
|
||||
routeSettings: routeSettings,
|
||||
requestFocus: requestFocus,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -164,8 +164,10 @@ abstract class Route<T> extends _RoutePlaceholder {
|
||||
/// If the [settings] are not provided, an empty [RouteSettings] object is
|
||||
/// used instead.
|
||||
///
|
||||
/// {@template flutter.widgets.navigator.Route.requestFocus}
|
||||
/// If [requestFocus] is not provided, the value of [Navigator.requestFocus] is
|
||||
/// used instead.
|
||||
/// {@endtemplate}
|
||||
Route({RouteSettings? settings, bool? requestFocus})
|
||||
: _settings = settings ?? const RouteSettings(),
|
||||
_requestFocus = requestFocus {
|
||||
|
@ -2631,6 +2631,9 @@ class RawDialogRoute<T> extends PopupRoute<T> {
|
||||
/// The `routeSettings` will be used in the construction of the dialog's route.
|
||||
/// See [RouteSettings] for more details.
|
||||
///
|
||||
/// {@macro flutter.material.dialog.requestFocus}
|
||||
/// {@macro flutter.widgets.navigator.Route.requestFocus}
|
||||
///
|
||||
/// {@macro flutter.widgets.RawDialogRoute}
|
||||
///
|
||||
/// Returns a [Future] that resolves to the value (if any) that was passed to
|
||||
@ -2672,6 +2675,7 @@ Future<T?> showGeneralDialog<T extends Object?>({
|
||||
bool useRootNavigator = true,
|
||||
RouteSettings? routeSettings,
|
||||
Offset? anchorPoint,
|
||||
bool? requestFocus,
|
||||
}) {
|
||||
assert(!barrierDismissible || barrierLabel != null);
|
||||
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(
|
||||
@ -2684,6 +2688,7 @@ Future<T?> showGeneralDialog<T extends Object?>({
|
||||
transitionBuilder: transitionBuilder,
|
||||
settings: routeSettings,
|
||||
anchorPoint: anchorPoint,
|
||||
requestFocus: requestFocus,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -3111,6 +3111,76 @@ void main() {
|
||||
expect(focusScopeNode.hasFocus, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('requestFocus works correctly in showCupertinoModalPopup.', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
addTearDown(focusNode.dispose);
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(navigatorKey: navigatorKey, home: CupertinoTextField(focusNode: focusNode)),
|
||||
);
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showCupertinoModalPopup<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: true,
|
||||
builder: (BuildContext context) => const Text('popup'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('popup'))).hasFocus, true);
|
||||
expect(focusNode.hasFocus, false);
|
||||
|
||||
navigatorKey.currentState!.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showCupertinoModalPopup<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: false,
|
||||
builder: (BuildContext context) => const Text('popup'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('popup'))).hasFocus, false);
|
||||
expect(focusNode.hasFocus, true);
|
||||
});
|
||||
|
||||
testWidgets('requestFocus works correctly in showCupertinoDialog.', (WidgetTester tester) async {
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
addTearDown(focusNode.dispose);
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(navigatorKey: navigatorKey, home: CupertinoTextField(focusNode: focusNode)),
|
||||
);
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showCupertinoDialog<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: true,
|
||||
builder: (BuildContext context) => const Text('dialog'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, true);
|
||||
expect(focusNode.hasFocus, false);
|
||||
|
||||
navigatorKey.currentState!.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showCupertinoDialog<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: false,
|
||||
builder: (BuildContext context) => const Text('dialog'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, false);
|
||||
expect(focusNode.hasFocus, true);
|
||||
});
|
||||
}
|
||||
|
||||
class MockNavigatorObserver extends NavigatorObserver {
|
||||
|
@ -2750,6 +2750,43 @@ void main() {
|
||||
expect(getTextFieldFocusNode()?.hasFocus, true);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('requestFocus works correctly in showModalBottomSheet.', (WidgetTester tester) async {
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
addTearDown(focusNode.dispose);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
home: Scaffold(body: TextField(focusNode: focusNode)),
|
||||
),
|
||||
);
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: true,
|
||||
builder: (BuildContext context) => const Text('BottomSheet'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('BottomSheet'))).hasFocus, true);
|
||||
expect(focusNode.hasFocus, false);
|
||||
|
||||
navigatorKey.currentState!.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: false,
|
||||
builder: (BuildContext context) => const Text('BottomSheet'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('BottomSheet'))).hasFocus, false);
|
||||
expect(focusNode.hasFocus, true);
|
||||
});
|
||||
}
|
||||
|
||||
class _TestPage extends StatelessWidget {
|
||||
|
@ -2820,6 +2820,43 @@ void main() {
|
||||
expect(find.text(dialogText), findsOneWidget);
|
||||
expect(getTextFieldFocusNode()?.hasFocus, true);
|
||||
});
|
||||
|
||||
testWidgets('requestFocus works correctly in showDialog.', (WidgetTester tester) async {
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
addTearDown(focusNode.dispose);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
home: Scaffold(body: TextField(focusNode: focusNode)),
|
||||
),
|
||||
);
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showDialog<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: true,
|
||||
builder: (BuildContext context) => const Text('dialog'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, true);
|
||||
expect(focusNode.hasFocus, false);
|
||||
|
||||
navigatorKey.currentState!.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showDialog<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: false,
|
||||
builder: (BuildContext context) => const Text('dialog'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, false);
|
||||
expect(focusNode.hasFocus, true);
|
||||
});
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
|
@ -2656,6 +2656,53 @@ void main() {
|
||||
expect(focusScope.directionalTraversalEdgeBehavior, element);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('requestFocus works correctly in showGeneralDialog.', (WidgetTester tester) async {
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
addTearDown(focusNode.dispose);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
navigatorKey: navigatorKey,
|
||||
home: Scaffold(body: TextField(focusNode: focusNode)),
|
||||
),
|
||||
);
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showGeneralDialog<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: true,
|
||||
pageBuilder:
|
||||
(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) => const Text('dialog'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, true);
|
||||
expect(focusNode.hasFocus, false);
|
||||
|
||||
navigatorKey.currentState!.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasFocus, true);
|
||||
|
||||
showGeneralDialog<void>(
|
||||
context: navigatorKey.currentContext!,
|
||||
requestFocus: false,
|
||||
pageBuilder:
|
||||
(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) => const Text('dialog'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, false);
|
||||
expect(focusNode.hasFocus, true);
|
||||
});
|
||||
}
|
||||
|
||||
double _getOpacity(GlobalKey key, WidgetTester tester) {
|
||||
|
Loading…
Reference in New Issue
Block a user