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
02c21447
Commit
02c21447
authored
Aug 07, 2017
by
Ian Hickson
Committed by
GitHub
Aug 07, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CupertinoPageRoute cleanup (#11473)
parent
35496d00
Changes
9
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
508 additions
and
293 deletions
+508
-293
page.dart
packages/flutter/lib/src/cupertino/page.dart
+265
-88
page.dart
packages/flutter/lib/src/material/page.dart
+16
-24
scaffold.dart
packages/flutter/lib/src/material/scaffold.dart
+0
-55
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+23
-1
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+27
-83
pages.dart
packages/flutter/lib/src/widgets/pages.dart
+2
-2
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+87
-34
page_test.dart
packages/flutter/test/material/page_test.dart
+67
-0
page_transitions_test.dart
packages/flutter/test/widgets/page_transitions_test.dart
+21
-6
No files found.
packages/flutter/lib/src/cupertino/page.dart
View file @
02c21447
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/material/page.dart
View file @
02c21447
...
...
@@ -72,19 +72,28 @@ class MaterialPageRoute<T> extends PageRoute<T> {
/// Builds the primary contents of the route.
final
WidgetBuilder
builder
;
@override
final
bool
maintainState
;
/// A delegate PageRoute to which iOS themed page operations are delegated to.
/// It's lazily created on first use.
CupertinoPageRoute
<
T
>
_internalCupertinoPageRoute
;
CupertinoPageRoute
<
T
>
get
_cupertinoPageRoute
{
assert
(
_useCupertinoTransitions
);
_internalCupertinoPageRoute
??=
new
CupertinoPageRoute
<
T
>(
builder:
builder
,
// Not used.
fullscreenDialog:
fullscreenDialog
,
hostRoute:
this
,
);
return
_internalCupertinoPageRoute
;
}
CupertinoPageRoute
<
T
>
_internalCupertinoPageRoute
;
@override
final
bool
maintainState
;
/// Whether we should currently be using Cupertino transitions. This is true
/// if the theme says we're on iOS, or if we're in an active gesture.
bool
get
_useCupertinoTransitions
{
return
_internalCupertinoPageRoute
?.
popGestureInProgress
==
true
||
Theme
.
of
(
navigator
.
context
).
platform
==
TargetPlatform
.
iOS
;
}
@override
Duration
get
transitionDuration
=>
const
Duration
(
milliseconds:
300
);
...
...
@@ -93,8 +102,8 @@ class MaterialPageRoute<T> extends PageRoute<T> {
Color
get
barrierColor
=>
null
;
@override
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
next
Route
)
{
return
nextRoute
is
MaterialPageRoute
<
dynamic
>
||
nextRoute
is
CupertinoPageRoute
<
dynamic
>
;
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
previous
Route
)
{
return
(
previousRoute
is
MaterialPageRoute
||
previousRoute
is
CupertinoPageRoute
)
;
}
@override
...
...
@@ -110,23 +119,6 @@ class MaterialPageRoute<T> extends PageRoute<T> {
super
.
dispose
();
}
/// Support for dismissing this route with a horizontal swipe is enabled
/// for [TargetPlatform.iOS]. If attempts to dismiss this route might be
/// vetoed because a [WillPopCallback] was defined for the route then the
/// platform-specific back gesture is disabled.
///
/// See also:
///
/// * [CupertinoPageRoute] that backs the gesture for iOS.
/// * [hasScopedWillPopCallback], which is true if a `willPop` callback
/// is defined for this route.
@override
NavigationGestureController
startPopGesture
()
{
return
Theme
.
of
(
navigator
.
context
).
platform
==
TargetPlatform
.
iOS
?
_cupertinoPageRoute
.
startPopGestureForRoute
(
this
)
:
null
;
}
@override
Widget
buildPage
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
final
Widget
result
=
builder
(
context
);
...
...
@@ -144,12 +136,12 @@ class MaterialPageRoute<T> extends PageRoute<T> {
@override
Widget
buildTransitions
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
,
Widget
child
)
{
if
(
Theme
.
of
(
context
).
platform
==
TargetPlatform
.
iOS
)
{
if
(
_useCupertinoTransitions
)
{
return
_cupertinoPageRoute
.
buildTransitions
(
context
,
animation
,
secondaryAnimation
,
child
);
}
else
{
return
new
_MountainViewPageTransition
(
routeAnimation:
animation
,
child:
child
child:
child
,
);
}
}
...
...
packages/flutter/lib/src/material/scaffold.dart
View file @
02c21447
...
...
@@ -23,8 +23,6 @@ const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be de
const
Duration
_kFloatingActionButtonSegue
=
const
Duration
(
milliseconds:
200
);
final
Tween
<
double
>
_kFloatingActionButtonTurnTween
=
new
Tween
<
double
>(
begin:
-
0.125
,
end:
0.0
);
const
double
_kBackGestureWidth
=
20.0
;
enum
_ScaffoldSlot
{
body
,
appBar
,
...
...
@@ -717,40 +715,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
}
}
final
GlobalKey
_backGestureKey
=
new
GlobalKey
();
NavigationGestureController
_backGestureController
;
bool
_shouldHandleBackGesture
()
{
assert
(
mounted
);
return
Theme
.
of
(
context
).
platform
==
TargetPlatform
.
iOS
&&
Navigator
.
canPop
(
context
);
}
void
_handleDragStart
(
DragStartDetails
details
)
{
assert
(
mounted
);
_backGestureController
=
Navigator
.
of
(
context
).
startPopGesture
();
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
assert
(
mounted
);
_backGestureController
?.
dragUpdate
(
details
.
primaryDelta
/
context
.
size
.
width
);
}
void
_handleDragEnd
(
DragEndDetails
details
)
{
assert
(
mounted
);
final
bool
willPop
=
_backGestureController
?.
dragEnd
(
details
.
velocity
.
pixelsPerSecond
.
dx
/
context
.
size
.
width
)
??
false
;
if
(
willPop
)
_currentBottomSheet
?.
close
();
_backGestureController
=
null
;
}
void
_handleDragCancel
()
{
assert
(
mounted
);
final
bool
willPop
=
_backGestureController
?.
dragEnd
(
0.0
)
??
false
;
if
(
willPop
)
_currentBottomSheet
?.
close
();
_backGestureController
=
null
;
}
// INTERNALS
...
...
@@ -887,25 +851,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
child:
widget
.
drawer
,
)
));
}
else
if
(
_shouldHandleBackGesture
())
{
assert
(!
hasDrawer
);
// Add a gesture for navigating back.
children
.
add
(
new
LayoutId
(
id:
_ScaffoldSlot
.
drawer
,
child:
new
Align
(
alignment:
FractionalOffset
.
centerLeft
,
child:
new
GestureDetector
(
key:
_backGestureKey
,
onHorizontalDragStart:
_handleDragStart
,
onHorizontalDragUpdate:
_handleDragUpdate
,
onHorizontalDragEnd:
_handleDragEnd
,
onHorizontalDragCancel:
_handleDragCancel
,
behavior:
HitTestBehavior
.
translucent
,
excludeFromSemantics:
true
,
child:
new
Container
(
width:
_kBackGestureWidth
)
)
)
));
}
return
new
_ScaffoldScope
(
...
...
packages/flutter/lib/src/widgets/framework.dart
View file @
02c21447
...
...
@@ -3113,6 +3113,8 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
'resulting object.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
'The associated render sliver was:
\n
'
'
${renderObject.toStringShallow("\n ")}
'
);
}
if
(
renderObject
is
!
RenderBox
)
{
...
...
@@ -3124,10 +3126,12 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
'and extracting its size manually.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
'The associated render object was:
\n
'
'
${renderObject.toStringShallow("\n ")}
'
);
}
final
RenderBox
box
=
renderObject
;
if
(!
box
.
hasSize
||
box
.
debugNeedsLayout
)
{
if
(!
box
.
hasSize
)
{
throw
new
FlutterError
(
'Cannot get size from a render object that has not been through layout.
\n
'
'The size of this render object has not yet been determined because '
...
...
@@ -3137,6 +3141,24 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
'the size and position of the render objects during layout.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
'The render object from which the size was to be obtained was:
\n
'
'
${box.toStringShallow("\n ")}
'
);
}
if
(
box
.
debugNeedsLayout
)
{
throw
new
FlutterError
(
'Cannot get size from a render object that has been marked dirty for layout.
\n
'
'The size of this render object is ambiguous because this render object has '
'been modified since it was last laid out, which typically means that the size '
'getter was called too early in the pipeline (e.g., during the build phase) '
'before the framework has determined the size and position of the render '
'objects during layout.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
'The render object from which the size was to be obtained was:
\n
'
'
${box.toStringShallow("\n ")}
\n
'
'Consider using debugPrintMarkNeedsLayoutStacks to determine why the render '
'object in question is dirty, if you did not expect this.'
);
}
return
true
;
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
02c21447
...
...
@@ -178,29 +178,9 @@ abstract class Route<T> {
@mustCallSuper
@protected
void
dispose
()
{
assert
(()
{
if
(
navigator
==
null
)
{
throw
new
FlutterError
(
'
$runtimeType
.dipose() called more than once.
\n
'
'A given route cannot be disposed more than once.'
);
}
return
true
;
});
_navigator
=
null
;
}
/// If the route's transition can be popped via a user gesture (e.g. the iOS
/// back gesture), this should return a controller object that can be used to
/// control the transition animation's progress. Otherwise, it should return
/// null.
///
/// If attempts to dismiss this route might be vetoed, for example because
/// a [WillPopCallback] was defined for the route, then it may make sense
/// to disable the pop gesture. For example, the iOS back gesture is disabled
/// when [ModalRoute.hasScopedWillPopCallback] is true.
NavigationGestureController
startPopGesture
()
=>
null
;
/// Whether this route is the top-most route on the navigator.
///
/// If this is true, then [isActive] is also true.
...
...
@@ -288,54 +268,16 @@ class NavigatorObserver {
/// The [Navigator] removed `route`.
void
didRemove
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
}
/// The [Navigator]
is being controll
ed by a user gesture.
/// The [Navigator]
's routes are being mov
ed by a user gesture.
///
/// Used for the iOS back gesture.
/// For example, this is called when an iOS back gesture starts, and is used
/// to disabled hero animations during such interactions.
void
didStartUserGesture
()
{
}
/// User gesture is no longer controlling the [Navigator].
void
didStopUserGesture
()
{
}
}
/// Interface describing an object returned by the [Route.startPopGesture]
/// method, allowing the route's transition animations to be controlled by a
/// drag or other user gesture.
abstract
class
NavigationGestureController
{
/// Configures the NavigationGestureController and tells the given [Navigator] that
/// a gesture has started.
NavigationGestureController
(
this
.
_navigator
)
:
assert
(
_navigator
!=
null
)
{
// Disable Hero transitions until the gesture is complete.
_navigator
.
didStartUserGesture
();
}
/// The navigator that this object is controlling.
@protected
NavigatorState
get
navigator
=>
_navigator
;
NavigatorState
_navigator
;
/// Release the resources used by this object. The object is no longer usable
/// after this method is called.
///
/// Must be called when the gesture is done.
///
/// Calling this method notifies the navigator that the gesture has completed.
@mustCallSuper
void
dispose
()
{
_navigator
.
didStopUserGesture
();
_navigator
=
null
;
}
/// The drag gesture has changed by [fractionalDelta]. The total range of the
/// drag should be 0.0 to 1.0.
void
dragUpdate
(
double
fractionalDelta
);
/// The drag gesture has ended with a horizontal motion of
/// [fractionalVelocity] as a fraction of screen width per second.
///
/// Returns true if the gesture will complete (i.e. a back gesture will
/// result in a pop).
bool
dragEnd
(
double
fractionalVelocity
);
/// Paired with an earlier call to [didStartUserGesture].
void
didStopUserGesture
()
{
}
}
/// Signature for the [Navigator.popUntil] predicate argument.
...
...
@@ -1326,34 +1268,36 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
return
_history
.
length
>
1
||
_history
[
0
].
willHandlePopInternally
;
}
/// Starts a gesture that results in popping the navigator.
NavigationGestureController
startPopGesture
()
{
if
(
canPop
())
return
_history
.
last
.
startPopGesture
();
return
null
;
}
/// Whether a gesture controlled by a [NavigationGestureController] is currently in progress.
bool
get
userGestureInProgress
=>
_userGestureInProgress
;
// TODO(mpcomplete): remove this bool when we fix
// https://github.com/flutter/flutter/issues/5577
bool
_userGestureInProgress
=
false
;
/// Whether a route is currently being manipulated by the user, e.g.
/// as during an iOS back gesture.
bool
get
userGestureInProgress
=>
_userGesturesInProgress
>
0
;
int
_userGesturesInProgress
=
0
;
/// The navigator is being controlled by a user gesture.
///
/// Used for the iOS back gesture.
/// For example, called when the user beings an iOS back gesture.
///
/// When the gesture finishes, call [didStopUserGesture].
void
didStartUserGesture
()
{
_userGestureInProgress
=
true
;
_userGesturesInProgress
+=
1
;
if
(
_userGesturesInProgress
==
1
)
{
for
(
NavigatorObserver
observer
in
widget
.
observers
)
observer
.
didStartUserGesture
();
}
}
/// A user gesture is no longer controlling the navigator.
/// A user gesture completed.
///
/// Notifies the navigator that a gesture regarding which the navigator was
/// previously notified with [didStartUserGesture] has completed.
void
didStopUserGesture
()
{
_userGestureInProgress
=
false
;
assert
(
_userGesturesInProgress
>
0
);
_userGesturesInProgress
-=
1
;
if
(
_userGesturesInProgress
==
0
)
{
for
(
NavigatorObserver
observer
in
widget
.
observers
)
observer
.
didStopUserGesture
();
}
}
final
Set
<
int
>
_activePointers
=
new
Set
<
int
>();
...
...
packages/flutter/lib/src/widgets/pages.dart
View file @
02c21447
...
...
@@ -32,10 +32,10 @@ abstract class PageRoute<T> extends ModalRoute<T> {
bool
get
barrierDismissible
=>
false
;
@override
bool
canTransitionTo
(
TransitionRoute
<
dynamic
>
nextRoute
)
=>
nextRoute
is
PageRoute
<
dynamic
>
;
bool
canTransitionTo
(
TransitionRoute
<
dynamic
>
nextRoute
)
=>
nextRoute
is
PageRoute
;
@override
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
nextRoute
)
=>
nextRoute
is
PageRoute
<
dynamic
>
;
bool
canTransitionFrom
(
TransitionRoute
<
dynamic
>
previousRoute
)
=>
previousRoute
is
PageRoute
;
@override
AnimationController
createAnimationController
()
{
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
02c21447
This diff is collapsed.
Click to expand it.
packages/flutter/test/material/page_test.dart
View file @
02c21447
...
...
@@ -272,6 +272,73 @@ void main() {
expect
(
tester
.
getTopLeft
(
find
.
text
(
'Page 2'
)),
const
Offset
(
100.0
,
0.0
));
});
testWidgets
(
'back gesture while OS changes'
,
(
WidgetTester
tester
)
async
{
final
Map
<
String
,
WidgetBuilder
>
routes
=
<
String
,
WidgetBuilder
>{
'/'
:
(
BuildContext
context
)
=>
new
Material
(
child:
new
FlatButton
(
child:
new
Text
(
'PUSH'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pushNamed
(
'/b'
);
},
),
),
'/b'
:
(
BuildContext
context
)
=>
new
Container
(
child:
new
Text
(
'HELLO'
)),
};
await
tester
.
pumpWidget
(
new
MaterialApp
(
theme:
new
ThemeData
(
platform:
TargetPlatform
.
iOS
),
routes:
routes
,
),
);
await
tester
.
tap
(
find
.
text
(
'PUSH'
));
expect
(
await
tester
.
pumpAndSettle
(
const
Duration
(
minutes:
1
)),
2
);
expect
(
find
.
text
(
'PUSH'
),
findsNothing
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
final
Offset
helloPosition1
=
tester
.
getCenter
(
find
.
text
(
'HELLO'
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
2.5
,
300.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
await
gesture
.
moveBy
(
const
Offset
(
100.0
,
0.0
));
expect
(
find
.
text
(
'PUSH'
),
findsNothing
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
expect
(
find
.
text
(
'PUSH'
),
findsOneWidget
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
final
Offset
helloPosition2
=
tester
.
getCenter
(
find
.
text
(
'HELLO'
));
expect
(
helloPosition1
.
dx
,
lessThan
(
helloPosition2
.
dx
));
expect
(
helloPosition1
.
dy
,
helloPosition2
.
dy
);
expect
(
Theme
.
of
(
tester
.
element
(
find
.
text
(
'HELLO'
))).
platform
,
TargetPlatform
.
iOS
);
await
tester
.
pumpWidget
(
new
MaterialApp
(
theme:
new
ThemeData
(
platform:
TargetPlatform
.
android
),
routes:
routes
,
),
);
// Now we have to let the theme animation run through.
// This takes three frames (including the first one above):
// 1. Start the Theme animation. It's at t=0 so everything else is identical.
// 2. Start any animations that are informed by the Theme, for example, the
// DefaultTextStyle, on the first frame that the theme is not at t=0. In
// this case, it's at t=1.0 of the theme animation, so this is also the
// frame in which the theme animation ends.
// 3. End all the other animations.
expect
(
await
tester
.
pumpAndSettle
(
const
Duration
(
minutes:
1
)),
2
);
expect
(
Theme
.
of
(
tester
.
element
(
find
.
text
(
'HELLO'
))).
platform
,
TargetPlatform
.
android
);
final
Offset
helloPosition3
=
tester
.
getCenter
(
find
.
text
(
'HELLO'
));
expect
(
helloPosition3
,
helloPosition2
);
expect
(
find
.
text
(
'PUSH'
),
findsOneWidget
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
await
gesture
.
moveBy
(
const
Offset
(
100.0
,
0.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
expect
(
find
.
text
(
'PUSH'
),
findsOneWidget
);
expect
(
find
.
text
(
'HELLO'
),
findsOneWidget
);
final
Offset
helloPosition4
=
tester
.
getCenter
(
find
.
text
(
'HELLO'
));
expect
(
helloPosition3
.
dx
,
lessThan
(
helloPosition4
.
dx
));
expect
(
helloPosition3
.
dy
,
helloPosition4
.
dy
);
await
gesture
.
moveBy
(
const
Offset
(
500.0
,
0.0
));
await
gesture
.
up
();
expect
(
await
tester
.
pumpAndSettle
(
const
Duration
(
minutes:
1
)),
2
);
expect
(
find
.
text
(
'PUSH'
),
findsOneWidget
);
expect
(
find
.
text
(
'HELLO'
),
findsNothing
);
});
testWidgets
(
'test no back gesture on iOS fullscreen dialogs'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
MaterialApp
(
...
...
packages/flutter/test/widgets/page_transitions_test.dart
View file @
02c21447
...
...
@@ -268,24 +268,39 @@ void main() {
expect
(
find
.
text
(
'Home'
),
findsNothing
);
expect
(
find
.
text
(
'Sheet'
),
isOnstage
);
// Drag from left edge to invoke the gesture. We should go back.
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
100.0
));
await
gesture
.
moveBy
(
const
Offset
(
500.0
,
0.0
));
await
gesture
.
up
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
Navigator
.
pushNamed
(
containerKey1
.
currentContext
,
'/sheet'
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Home'
),
findsNothing
);
expect
(
find
.
text
(
'Sheet'
),
isOnstage
);
// Show the bottom sheet.
final
PersistentBottomSheetTestState
sheet
=
containerKey2
.
currentState
;
sheet
.
showBottomSheet
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// Drag from left edge to invoke the gesture.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
100.0
));
// Drag from left edge to invoke the gesture.
Nothing should happen.
gesture
=
await
tester
.
startGesture
(
const
Offset
(
5.0
,
100.0
));
await
gesture
.
moveBy
(
const
Offset
(
500.0
,
0.0
));
await
gesture
.
up
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
find
.
text
(
'Home'
),
isOnstage
);
expect
(
find
.
text
(
'Sheet'
),
findsNothing
);
expect
(
find
.
text
(
'Home'
),
findsNothing
);
expect
(
find
.
text
(
'Sheet'
),
isOnstage
);
// Sheet
called setState and didn't crash
.
expect
(
sheet
.
setStateCalled
,
is
Tru
e
);
// Sheet
did not call setState (since the gesture did nothing)
.
expect
(
sheet
.
setStateCalled
,
is
Fals
e
);
});
testWidgets
(
'Test completed future'
,
(
WidgetTester
tester
)
async
{
...
...
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