Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
5801f0e5
Unverified
Commit
5801f0e5
authored
Jan 23, 2021
by
Shi-Hao Hong
Committed by
GitHub
Jan 23, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Expose DialogRoutes for state restoration support (#73829)
parent
ea3aa674
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
679 additions
and
64 deletions
+679
-64
route.dart
packages/flutter/lib/src/cupertino/route.dart
+137
-10
dialog.dart
packages/flutter/lib/src/material/dialog.dart
+154
-19
restoration.dart
packages/flutter/lib/src/services/restoration.dart
+28
-0
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+99
-4
dialog_test.dart
packages/flutter/test/cupertino/dialog_test.dart
+103
-0
route_test.dart
packages/flutter/test/cupertino/route_test.dart
+1
-1
about_test.dart
packages/flutter/test/material/about_test.dart
+1
-1
date_picker_test.dart
packages/flutter/test/material/date_picker_test.dart
+1
-1
dialog_test.dart
packages/flutter/test/material/dialog_test.dart
+93
-24
time_picker_test.dart
packages/flutter/test/material/time_picker_test.dart
+1
-1
routes_test.dart
packages/flutter/test/widgets/routes_test.dart
+61
-3
No files found.
packages/flutter/lib/src/cupertino/route.dart
View file @
5801f0e5
...
...
@@ -1125,6 +1125,78 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// ### State Restoration in Dialogs
///
/// Using this method will not enable state restoration for the dialog. In order
/// to enable state restoration for a dialog, use [Navigator.restorablePush]
/// or [Navigator.restorablePushNamed] with [CupertinoDialogRoute].
///
/// For more information about state restoration, see [RestorationManager].
///
/// {@tool sample --template=freeform}
///
/// This sample demonstrates how to create a restorable Cupertino dialog. This is
/// accomplished by enabling state restoration by specifying
/// [CupertinoApp.restorationScopeId] and using [Navigator.restorablePush] to
/// push [CupertinoDialogRoute] when the [CupertinoButton] is tapped.
///
/// {@macro flutter.widgets.RestorationManager}
///
/// ```dart imports
/// import 'package:flutter/cupertino.dart';
/// ```
///
/// ```dart
/// void main() {
/// runApp(MyApp());
/// }
///
/// class MyApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoApp(
/// restorationScopeId: 'app',
/// home: MyHomePage(),
/// );
/// }
/// }
///
/// class MyHomePage extends StatelessWidget {
/// static Route<Object?> _dialogBuilder(BuildContext context, Object? arguments) {
/// return CupertinoDialogRoute<void>(
/// context: context,
/// builder: (BuildContext context) {
/// return const CupertinoAlertDialog(
/// title: Text('Title'),
/// content: Text('Content'),
/// actions: <Widget>[
/// CupertinoDialogAction(child: Text('Yes')),
/// CupertinoDialogAction(child: Text('No')),
/// ],
/// );
/// },
/// );
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoPageScaffold(
/// navigationBar: const CupertinoNavigationBar(
/// middle: Text('Home'),
/// ),
/// child: Center(child: CupertinoButton(
/// onPressed: () {
/// Navigator.of(context).restorablePush(_dialogBuilder);
/// },
/// child: const Text('Open Dialog'),
/// )),
/// );
/// }
/// }
/// ```
///
/// {@end-tool}
///
/// See also:
///
/// * [CupertinoAlertDialog], an iOS-style alert dialog.
...
...
@@ -1134,24 +1206,79 @@ Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double>
Future
<
T
?>
showCupertinoDialog
<
T
>({
required
BuildContext
context
,
required
WidgetBuilder
builder
,
String
?
barrierLabel
,
bool
useRootNavigator
=
true
,
bool
barrierDismissible
=
false
,
RouteSettings
?
routeSettings
,
})
{
assert
(
builder
!=
null
);
assert
(
useRootNavigator
!=
null
);
return
showGeneralDialog
(
return
Navigator
.
of
(
context
,
rootNavigator:
useRootNavigator
).
push
<
T
>(
CupertinoDialogRoute
<
T
>(
builder:
builder
,
context:
context
,
barrierDismissible:
barrierDismissible
,
barrierLabel:
CupertinoLocalizations
.
of
(
context
).
modalBarrierDismiss
Label
,
barrierLabel:
barrier
Label
,
barrierColor:
CupertinoDynamicColor
.
resolve
(
_kModalBarrierColor
,
context
),
settings:
routeSettings
,
));
}
/// A dialog route that shows an iOS-style dialog.
///
/// It is used internally by [showCupertinoDialog] or can be directly pushed
/// onto the [Navigator] stack to enable state restoration. See
/// [showCupertinoDialog] for a state restoration app example.
///
/// This function takes a `builder` which typically builds a [Dialog] widget.
/// Content below the dialog is dimmed with a [ModalBarrier]. The widget
/// returned by the `builder` does not share a context with the location that
/// `showDialog` is originally called from. Use a [StatefulBuilder] or a
/// custom [StatefulWidget] if the dialog needs to update dynamically.
///
/// The `context` argument is used to look up
/// [CupertinoLocalizations.modalBarrierDismissLabel], which provides the
/// modal with a localized accessibility label that will be used for the
/// modal's barrier. However, a custom `barrierLabel` can be passed in as well.
///
/// The `barrierDismissible` argument is used to indicate whether tapping on the
/// barrier will dismiss the dialog. It is `true` by default and cannot be `null`.
///
/// The `barrierColor` argument is used to specify the color of the modal
/// barrier that darkens everything below the dialog. If `null`, then
/// [CupertinoDynamicColor.resolve] is used to compute the modal color.
///
/// The `settings` argument define the settings for this route. See
/// [RouteSettings] for details.
///
/// See also:
///
/// * [showCupertinoDialog], which is a way to display
/// an iOS-style dialog.
/// * [showGeneralDialog], which allows for customization of the dialog popup.
/// * [showDialog], which displays a Material dialog.
class
CupertinoDialogRoute
<
T
>
extends
RawDialogRoute
<
T
>
{
/// A dialog route that shows an iOS-style dialog.
CupertinoDialogRoute
({
required
WidgetBuilder
builder
,
required
BuildContext
context
,
bool
barrierDismissible
=
true
,
Color
?
barrierColor
,
String
?
barrierLabel
,
// This transition duration was eyeballed comparing with iOS
transitionDuration:
const
Duration
(
milliseconds:
250
),
pageBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
return
builder
(
context
);
},
transitionBuilder:
_buildCupertinoDialogTransitions
,
useRootNavigator:
useRootNavigator
,
routeSettings:
routeSettings
,
);
Duration
transitionDuration
=
const
Duration
(
milliseconds:
250
),
RouteTransitionsBuilder
?
transitionBuilder
=
_buildCupertinoDialogTransitions
,
RouteSettings
?
settings
,
})
:
assert
(
barrierDismissible
!=
null
),
super
(
pageBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
return
builder
(
context
);
},
barrierDismissible:
barrierDismissible
,
barrierLabel:
barrierLabel
??
CupertinoLocalizations
.
of
(
context
).
modalBarrierDismissLabel
,
barrierColor:
barrierColor
??
CupertinoDynamicColor
.
resolve
(
_kModalBarrierColor
,
context
),
transitionDuration:
transitionDuration
,
transitionBuilder:
transitionBuilder
,
settings:
settings
,
);
}
packages/flutter/lib/src/material/dialog.dart
View file @
5801f0e5
...
...
@@ -950,6 +950,69 @@ Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> a
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// ### State Restoration in Dialogs
///
/// Using this method will not enable state restoration for the dialog. In order
/// to enable state restoration for a dialog, use [Navigator.restorablePush]
/// or [Navigator.restorablePushNamed] with [DialogRoute].
///
/// For more information about state restoration, see [RestorationManager].
///
/// {@tool sample --template=freeform}
///
/// This sample demonstrates how to create a restorable Material dialog. This is
/// accomplished by enabling state restoration by specifying
/// [MaterialApp.restorationScopeId] and using [Navigator.restorablePush] to
/// push [DialogRoute] when the button is tapped.
///
/// {@macro flutter.widgets.RestorationManager}
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart
/// void main() {
/// runApp(MyApp());
/// }
///
/// class MyApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// restorationScopeId: 'app',
/// title: 'Restorable Routes Demo',
/// home: MyHomePage(),
/// );
/// }
/// }
///
/// class MyHomePage extends StatelessWidget {
/// static Route<Object?> _dialogBuilder(BuildContext context, Object? arguments) {
/// return DialogRoute<void>(
/// context: context,
/// builder: (BuildContext context) => const AlertDialog(title: Text('Material Alert!')),
/// );
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: Center(
/// child: OutlinedButton(
/// onPressed: () {
/// Navigator.of(context).restorablePush(_dialogBuilder);
/// },
/// child: const Text('Open Dialog'),
/// ),
/// ),
/// );
/// }
/// }
/// ```
///
/// {@end-tool}
///
/// See also:
///
/// * [AlertDialog], for dialogs that have a row of buttons below a body.
...
...
@@ -961,9 +1024,10 @@ Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> a
/// * <https://material.io/design/components/dialogs.html>
Future
<
T
?>
showDialog
<
T
>({
required
BuildContext
context
,
WidgetBuilder
?
builder
,
required
WidgetBuilder
builder
,
bool
barrierDismissible
=
true
,
Color
?
barrierColor
,
Color
?
barrierColor
=
Colors
.
black54
,
String
?
barrierLabel
,
bool
useSafeArea
=
true
,
bool
useRootNavigator
=
true
,
RouteSettings
?
routeSettings
,
...
...
@@ -974,25 +1038,96 @@ Future<T?> showDialog<T>({
assert
(
useRootNavigator
!=
null
);
assert
(
debugCheckHasMaterialLocalizations
(
context
));
final
CapturedThemes
themes
=
InheritedTheme
.
capture
(
from:
context
,
to:
Navigator
.
of
(
context
,
rootNavigator:
useRootNavigator
).
context
);
return
showGeneralDialog
(
final
CapturedThemes
themes
=
InheritedTheme
.
capture
(
from:
context
,
to:
Navigator
.
of
(
context
,
rootNavigator:
useRootNavigator
,
).
context
,
);
return
Navigator
.
of
(
context
,
rootNavigator:
useRootNavigator
).
push
<
T
>(
DialogRoute
<
T
>(
context:
context
,
pageBuilder:
(
BuildContext
buildContext
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
final
Widget
pageChild
=
Builder
(
builder:
builder
!);
Widget
dialog
=
themes
.
wrap
(
pageChild
);
if
(
useSafeArea
)
{
dialog
=
SafeArea
(
child:
dialog
);
}
return
dialog
;
},
builder:
builder
,
barrierColor:
barrierColor
,
barrierDismissible:
barrierDismissible
,
barrierLabel:
MaterialLocalizations
.
of
(
context
).
modalBarrierDismissLabel
,
barrierColor:
barrierColor
??
Colors
.
black54
,
transitionDuration:
const
Duration
(
milliseconds:
150
),
transitionBuilder:
_buildMaterialDialogTransitions
,
useRootNavigator:
useRootNavigator
,
routeSettings:
routeSettings
,
);
barrierLabel:
barrierLabel
,
useSafeArea:
useSafeArea
,
settings:
routeSettings
,
themes:
themes
,
));
}
/// A dialog route with Material entrance and exit animations,
/// modal barrier color, and modal barrier behavior (dialog is dismissible
/// with a tap on the barrier).
///
/// It is used internally by [showDialog] or can be directly pushed
/// onto the [Navigator] stack to enable state restoration. See
/// [showDialog] for a state restoration app example.
///
/// This function takes a `builder` which typically builds a [Dialog] widget.
/// Content below the dialog is dimmed with a [ModalBarrier]. The widget
/// returned by the `builder` does not share a context with the location that
/// `showDialog` is originally called from. Use a [StatefulBuilder] or a
/// custom [StatefulWidget] if the dialog needs to update dynamically.
///
/// The `context` argument is used to look up
/// [MaterialLocalizations.modalBarrierDismissLabel], which provides the
/// modal with a localized accessibility label that will be used for the
/// modal's barrier. However, a custom `barrierLabel` can be passed in as well.
///
/// The `barrierDismissible` argument is used to indicate whether tapping on the
/// barrier will dismiss the dialog. It is `true` by default and cannot be `null`.
///
/// The `barrierColor` argument is used to specify the color of the modal
/// barrier that darkens everything below the dialog. If `null`, the default
/// color `Colors.black54` is used.
///
/// The `useSafeArea` argument is used to indicate if the dialog should only
/// display in 'safe' areas of the screen not used by the operating system
/// (see [SafeArea] for more details). It is `true` by default, which means
/// the dialog will not overlap operating system areas. If it is set to `false`
/// the dialog will only be constrained by the screen size. It can not be `null`.
///
/// The `settings` argument define the settings for this route. See
/// [RouteSettings] for details.
///
/// See also:
///
/// * [showDialog], which is a way to display a DialogRoute.
/// * [showGeneralDialog], which allows for customization of the dialog popup.
/// * [showCupertinoDialog], which displays an iOS-style dialog.
class
DialogRoute
<
T
>
extends
RawDialogRoute
<
T
>
{
/// A dialog route with Material entrance and exit animations,
/// modal barrier color, and modal barrier behavior (dialog is dismissible
/// with a tap on the barrier).
DialogRoute
({
required
BuildContext
context
,
required
WidgetBuilder
builder
,
CapturedThemes
?
themes
,
Color
?
barrierColor
=
Colors
.
black54
,
bool
barrierDismissible
=
true
,
String
?
barrierLabel
,
bool
useSafeArea
=
true
,
RouteSettings
?
settings
,
})
:
assert
(
barrierDismissible
!=
null
),
super
(
pageBuilder:
(
BuildContext
buildContext
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
final
Widget
pageChild
=
Builder
(
builder:
builder
);
Widget
dialog
=
themes
?.
wrap
(
pageChild
)
??
pageChild
;
if
(
useSafeArea
)
{
dialog
=
SafeArea
(
child:
dialog
);
}
return
dialog
;
},
barrierDismissible:
barrierDismissible
,
barrierColor:
barrierColor
,
barrierLabel:
barrierLabel
??
MaterialLocalizations
.
of
(
context
).
modalBarrierDismissLabel
,
transitionDuration:
const
Duration
(
milliseconds:
150
),
transitionBuilder:
_buildMaterialDialogTransitions
,
settings:
settings
,
);
}
double
_paddingScaleFactor
(
double
textScaleFactor
)
{
...
...
packages/flutter/lib/src/services/restoration.dart
View file @
5801f0e5
...
...
@@ -115,6 +115,34 @@ typedef _BucketVisitor = void Function(RestorationBucket bucket);
/// fully re-compile your application (e.g. by re-executing `flutter run`) after
/// making a change.
///
/// ## Testing State Restoration
///
/// {@template flutter.widgets.RestorationManager}
/// To test state restoration on Android:
/// 1. Turn on "Don't keep activities", which destroys the Android activity
/// as soon as the user leaves it. This option should become available
/// when Developer Options are turned on for the device.
/// 2. Run the code sample on an Android device.
/// 3. Create some in-memory state in the app on the phone,
/// e.g. by navigating to a different screen.
/// 4. Background the Flutter app, then return to it. It will restart
/// and restore its state.
///
/// To test state restoration on iOS:
/// 1. Open `ios/Runner.xcworkspace/` in Xcode.
/// 2. (iOS 14+ only): Switch to build in profile or release mode, as
/// launching an app from the home screen is not supported in debug
/// mode.
/// 2. Press the Play button in Xcode to build and run the app.
/// 3. Create some in-memory state in the app on the phone,
/// e.g. by navigating to a different screen.
/// 4. Background the app on the phone, e.g. by going back to the home screen.
/// 5. Press the Stop button in Xcode to terminate the app while running in
/// the background.
/// 6. Open the app again on the phone (not via Xcode). It will restart
/// and restore its state.
/// {@endtemplate}
///
/// See also:
///
/// * [ServicesBinding.restorationManager], which holds the singleton instance
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
5801f0e5
...
...
@@ -1747,12 +1747,40 @@ abstract class RouteAware {
void
didPushNext
()
{
}
}
class
_DialogRoute
<
T
>
extends
PopupRoute
<
T
>
{
_DialogRoute
({
/// A general dialog route which allows for customization of the dialog popup.
///
/// It is used internally by [showGeneralDialog] or can be directly pushed
/// onto the [Navigator] stack to enable state restoration. See
/// [showGeneralDialog] for a state restoration app example.
///
/// This function takes a `pageBuilder`, which typically builds a dialog.
/// Content below the dialog is dimmed with a [ModalBarrier]. The widget
/// returned by the `builder` does not share a context with the location that
/// `showDialog` is originally called from. Use a [StatefulBuilder] or a
/// custom [StatefulWidget] if the dialog needs to update dynamically.
///
/// The `barrierDismissible` argument is used to indicate whether tapping on the
/// barrier will dismiss the dialog. It is `true` by default and cannot be `null`.
///
/// The `barrierColor` argument is used to specify the color of the modal
/// barrier that darkens everything below the dialog. If `null`, the default
/// color `Colors.black54` is used.
///
/// The `settings` argument define the settings for this route. See
/// [RouteSettings] for details.
///
/// See also:
///
/// * [showGeneralDialog], which is a way to display a RawDialogRoute.
/// * [showDialog], which is a way to display a DialogRoute.
/// * [showCupertinoDialog], which displays an iOS-style dialog.
class
RawDialogRoute
<
T
>
extends
PopupRoute
<
T
>
{
/// A general dialog route which allows for customization of the dialog popup.
RawDialogRoute
({
required
RoutePageBuilder
pageBuilder
,
bool
barrierDismissible
=
true
,
String
?
barrierLabel
,
Color
?
barrierColor
=
const
Color
(
0x80000000
),
String
?
barrierLabel
,
Duration
transitionDuration
=
const
Duration
(
milliseconds:
200
),
RouteTransitionsBuilder
?
transitionBuilder
,
RouteSettings
?
settings
,
...
...
@@ -1858,6 +1886,73 @@ class _DialogRoute<T> extends PopupRoute<T> {
/// Returns a [Future] that resolves to the value (if any) that was passed to
/// [Navigator.pop] when the dialog was closed.
///
/// ### State Restoration in Dialogs
///
/// Using this method will not enable state restoration for the dialog. In order
/// to enable state restoration for a dialog, use [Navigator.restorablePush]
/// or [Navigator.restorablePushNamed] with [RawDialogRoute].
///
/// For more information about state restoration, see [RestorationManager].
///
/// {@tool sample --template=freeform}
///
/// This sample demonstrates how to create a restorable dialog. This is
/// accomplished by enabling state restoration by specifying
/// [WidgetsApp.restorationScopeId] and using [Navigator.restorablePush] to
/// push [RawDialogRoute] when the button is tapped.
///
/// {@macro flutter.widgets.RestorationManager}
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart
/// void main() {
/// runApp(MyApp());
/// }
///
/// class MyApp extends StatelessWidget {
/// @override
/// Widget build(BuildContext context) {
/// return MaterialApp(
/// restorationScopeId: 'app',
/// home: MyHomePage(),
/// );
/// }
/// }
///
/// class MyHomePage extends StatelessWidget {
/// static Route<Object?> _dialogBuilder(BuildContext context, Object? arguments) {
/// return RawDialogRoute<void>(
/// pageBuilder: (
/// BuildContext context,
/// Animation<double> animation,
/// Animation<double> secondaryAnimation,
/// ) {
/// return const AlertDialog(title: Text('Alert!'));
/// },
/// );
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// body: Center(
/// child: OutlinedButton(
/// onPressed: () {
/// Navigator.of(context).restorablePush(_dialogBuilder);
/// },
/// child: const Text('Open Dialog'),
/// ),
/// ),
/// );
/// }
/// }
/// ```
///
/// {@end-tool}
///
/// See also:
///
/// * [showDialog], which displays a Material-style dialog.
...
...
@@ -1876,7 +1971,7 @@ Future<T?> showGeneralDialog<T extends Object?>({
assert
(
pageBuilder
!=
null
);
assert
(
useRootNavigator
!=
null
);
assert
(!
barrierDismissible
||
barrierLabel
!=
null
);
return
Navigator
.
of
(
context
,
rootNavigator:
useRootNavigator
).
push
<
T
>(
_
DialogRoute
<
T
>(
return
Navigator
.
of
(
context
,
rootNavigator:
useRootNavigator
).
push
<
T
>(
Raw
DialogRoute
<
T
>(
pageBuilder:
pageBuilder
,
barrierDismissible:
barrierDismissible
,
barrierLabel:
barrierLabel
,
...
...
packages/flutter/test/cupertino/dialog_test.dart
View file @
5801f0e5
...
...
@@ -1175,6 +1175,75 @@ void main() {
matchesGoldenFile
(
'dialog_test.cupertino.default.png'
),
);
});
testWidgets
(
'showCupertinoDialog - custom barrierLabel'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
Center
(
child:
CupertinoButton
(
child:
const
Text
(
'X'
),
onPressed:
()
{
showCupertinoDialog
<
void
>(
context:
context
,
barrierLabel:
'Custom label'
,
builder:
(
BuildContext
context
)
{
return
const
CupertinoAlertDialog
(
title:
Text
(
'Title'
),
content:
Text
(
'Content'
),
actions:
<
Widget
>[
CupertinoDialogAction
(
child:
Text
(
'Yes'
)),
CupertinoDialogAction
(
child:
Text
(
'No'
)),
],
);
},
);
},
),
);
},
),
),
);
expect
(
semantics
,
isNot
(
includesNodeWith
(
label:
'Custom label'
,
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
namesRoute
],
)));
});
testWidgets
(
'CupertinoDialogRoute is state restorable'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
restorationScopeId:
'app'
,
home:
_RestorableDialogTestWidget
(),
),
);
expect
(
find
.
byType
(
CupertinoAlertDialog
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
CupertinoAlertDialog
),
findsOneWidget
);
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
await
tester
.
restartAndRestore
();
expect
(
find
.
byType
(
CupertinoAlertDialog
),
findsOneWidget
);
// Tap on the barrier.
await
tester
.
tapAt
(
const
Offset
(
10.0
,
10.0
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
CupertinoAlertDialog
),
findsNothing
);
await
tester
.
restoreFrom
(
restorationData
);
expect
(
find
.
byType
(
CupertinoAlertDialog
),
findsOneWidget
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
}
RenderBox
findActionButtonRenderBoxByTitle
(
WidgetTester
tester
,
String
title
)
{
...
...
@@ -1234,3 +1303,37 @@ Widget createAppWithCenteredButton(Widget child) {
)
);
}
class
_RestorableDialogTestWidget
extends
StatelessWidget
{
static
Route
<
Object
?>
_dialogBuilder
(
BuildContext
context
,
Object
?
arguments
)
{
return
CupertinoDialogRoute
<
void
>(
context:
context
,
builder:
(
BuildContext
context
)
{
return
const
CupertinoAlertDialog
(
title:
Text
(
'Title'
),
content:
Text
(
'Content'
),
actions:
<
Widget
>[
CupertinoDialogAction
(
child:
Text
(
'Yes'
)),
CupertinoDialogAction
(
child:
Text
(
'No'
)),
],
);
},
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
CupertinoPageScaffold
(
navigationBar:
const
CupertinoNavigationBar
(
middle:
Text
(
'Home'
),
),
child:
Center
(
child:
CupertinoButton
(
onPressed:
()
{
Navigator
.
of
(
context
).
restorablePush
(
_dialogBuilder
);
},
child:
const
Text
(
'X'
),
)),
);
}
}
packages/flutter/test/cupertino/route_test.dart
View file @
5801f0e5
...
...
@@ -1734,7 +1734,7 @@ class DialogObserver extends NavigatorObserver {
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>?
previousRoute
)
{
if
(
route
.
toString
().
contains
(
'_DialogRoute'
)
)
{
if
(
route
is
CupertinoDialogRoute
)
{
dialogCount
++;
}
super
.
didPush
(
route
,
previousRoute
);
...
...
packages/flutter/test/material/about_test.dart
View file @
5801f0e5
...
...
@@ -744,7 +744,7 @@ class AboutDialogObserver extends NavigatorObserver {
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>?
previousRoute
)
{
if
(
route
.
toString
().
contains
(
'_DialogRoute'
)
)
{
if
(
route
is
DialogRoute
)
{
dialogCount
++;
}
super
.
didPush
(
route
,
previousRoute
);
...
...
packages/flutter/test/material/date_picker_test.dart
View file @
5801f0e5
...
...
@@ -1095,7 +1095,7 @@ class _DatePickerObserver extends NavigatorObserver {
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>?
previousRoute
)
{
if
(
route
.
toString
().
contains
(
'_DialogRoute'
)
)
{
if
(
route
is
DialogRoute
)
{
datePickerCount
++;
}
super
.
didPush
(
route
,
previousRoute
);
...
...
packages/flutter/test/material/dialog_test.dart
View file @
5801f0e5
...
...
@@ -217,29 +217,6 @@ void main() {
expect
(
materialWidget
.
shape
,
customBorder
);
});
testWidgets
(
'showDialog builder must be defined'
,
(
WidgetTester
tester
)
async
{
late
BuildContext
currentBuildContext
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
currentBuildContext
=
context
;
return
Container
();
}
),
),
),
),
);
expect
(
()
=>
showDialog
<
void
>(
context:
currentBuildContext
),
throwsAssertionError
,
);
});
testWidgets
(
'Simple dialog control test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
MaterialApp
(
...
...
@@ -1856,6 +1833,98 @@ void main() {
await
tester
.
pumpAndSettle
();
expect
(
currentRouteSetting
.
name
,
'/'
);
});
testWidgets
(
'showDialog - custom barrierLabel'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
platform:
TargetPlatform
.
iOS
),
home:
Material
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
Center
(
child:
ElevatedButton
(
child:
const
Text
(
'X'
),
onPressed:
()
{
showDialog
<
void
>(
context:
context
,
barrierLabel:
'Custom label'
,
builder:
(
BuildContext
context
)
{
return
const
AlertDialog
(
title:
Text
(
'Title'
),
content:
Text
(
'Y'
),
actions:
<
Widget
>[],
);
},
);
},
),
);
},
),
),
),
);
expect
(
semantics
,
isNot
(
includesNodeWith
(
label:
'Custom label'
,
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
namesRoute
],
)));
});
testWidgets
(
'DialogRoute is state restorable'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
restorationScopeId:
'app'
,
home:
_RestorableDialogTestWidget
(),
),
);
expect
(
find
.
byType
(
AlertDialog
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
AlertDialog
),
findsOneWidget
);
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
await
tester
.
restartAndRestore
();
expect
(
find
.
byType
(
AlertDialog
),
findsOneWidget
);
// Tap on the barrier.
await
tester
.
tapAt
(
const
Offset
(
10.0
,
10.0
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
AlertDialog
),
findsNothing
);
await
tester
.
restoreFrom
(
restorationData
);
expect
(
find
.
byType
(
AlertDialog
),
findsOneWidget
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
}
class
_RestorableDialogTestWidget
extends
StatelessWidget
{
static
Route
<
Object
?>
_materialDialogBuilder
(
BuildContext
context
,
Object
?
arguments
)
{
return
DialogRoute
<
void
>(
context:
context
,
builder:
(
BuildContext
context
)
=>
const
AlertDialog
(
title:
Text
(
'Material Alert!'
)),
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
body:
Center
(
child:
OutlinedButton
(
onPressed:
()
{
Navigator
.
of
(
context
).
restorablePush
(
_materialDialogBuilder
);
},
child:
const
Text
(
'X'
),
),
),
);
}
}
class
DialogObserver
extends
NavigatorObserver
{
...
...
@@ -1863,7 +1932,7 @@ class DialogObserver extends NavigatorObserver {
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>?
previousRoute
)
{
if
(
route
.
toString
().
contains
(
'_DialogRoute'
)
)
{
if
(
route
is
DialogRoute
)
{
dialogCount
++;
}
super
.
didPush
(
route
,
previousRoute
);
...
...
packages/flutter/test/material/time_picker_test.dart
View file @
5801f0e5
...
...
@@ -958,7 +958,7 @@ class PickerObserver extends NavigatorObserver {
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>?
previousRoute
)
{
if
(
route
.
toString
().
contains
(
'_DialogRoute'
)
)
{
if
(
route
is
DialogRoute
)
{
pickerCount
++;
}
super
.
didPush
(
route
,
previousRoute
);
...
...
packages/flutter/test/widgets/routes_test.dart
View file @
5801f0e5
...
...
@@ -1748,6 +1748,36 @@ void main() {
expect
(
parentRoute
,
isNotNull
);
expect
(
parentRoute
,
isA
<
MaterialPageRoute
<
void
>>());
});
testWidgets
(
'RawDialogRoute is state restorable'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
restorationScopeId:
'app'
,
home:
_RestorableDialogTestWidget
(),
),
);
expect
(
find
.
byType
(
AlertDialog
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'X'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
AlertDialog
),
findsOneWidget
);
final
TestRestorationData
restorationData
=
await
tester
.
getRestorationData
();
await
tester
.
restartAndRestore
();
expect
(
find
.
byType
(
AlertDialog
),
findsOneWidget
);
// Tap on the barrier.
await
tester
.
tapAt
(
const
Offset
(
10.0
,
10.0
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
AlertDialog
),
findsNothing
);
await
tester
.
restoreFrom
(
restorationData
);
expect
(
find
.
byType
(
AlertDialog
),
findsOneWidget
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
}
double
_getOpacity
(
GlobalKey
key
,
WidgetTester
tester
)
{
...
...
@@ -1823,8 +1853,8 @@ class DialogObserver extends NavigatorObserver {
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>?
previousRoute
)
{
if
(
route
.
toString
().
contains
(
'_DialogRoute'
)
)
{
dialogRoutes
.
add
(
route
as
ModalRoute
<
dynamic
>
);
if
(
route
is
RawDialogRoute
)
{
dialogRoutes
.
add
(
route
);
dialogCount
++;
}
super
.
didPush
(
route
,
previousRoute
);
...
...
@@ -1832,7 +1862,7 @@ class DialogObserver extends NavigatorObserver {
@override
void
didPop
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>?
previousRoute
)
{
if
(
route
.
toString
().
contains
(
'_DialogRoute'
)
)
{
if
(
route
is
RawDialogRoute
)
{
dialogRoutes
.
removeLast
();
dialogCount
--;
}
...
...
@@ -1951,3 +1981,31 @@ Widget buildNavigator({
),
);
}
class
_RestorableDialogTestWidget
extends
StatelessWidget
{
static
Route
<
Object
?>
_dialogBuilder
(
BuildContext
context
,
Object
?
arguments
)
{
return
RawDialogRoute
<
void
>(
pageBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
,
)
{
return
const
AlertDialog
(
title:
Text
(
'Alert!'
));
},
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
body:
Center
(
child:
OutlinedButton
(
onPressed:
()
{
Navigator
.
of
(
context
).
restorablePush
(
_dialogBuilder
);
},
child:
const
Text
(
'X'
),
),
),
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment