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
5734821f
Commit
5734821f
authored
Nov 19, 2015
by
Ian Hickson
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #479 from Hixie/yak3-snackbar-PENDING
Snackbar Refactor
parents
67b2ee3a
954713ab
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
393 additions
and
180 deletions
+393
-180
feed.dart
examples/fitness/lib/feed.dart
+7
-10
measurement.dart
examples/fitness/lib/measurement.dart
+3
-8
stock_home.dart
examples/stocks/lib/stock_home.dart
+27
-24
constants.dart
packages/flutter/lib/src/material/constants.dart
+0
-1
scaffold.dart
packages/flutter/lib/src/material/scaffold.dart
+86
-26
snack_bar.dart
packages/flutter/lib/src/material/snack_bar.dart
+54
-55
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+25
-18
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+73
-2
snack_bar_test.dart
packages/unit/test/widget/snack_bar_test.dart
+118
-36
No files found.
examples/fitness/lib/feed.dart
View file @
5734821f
...
...
@@ -54,7 +54,6 @@ class FeedFragment extends StatefulComponent {
}
class
FeedFragmentState
extends
State
<
FeedFragment
>
{
final
GlobalKey
<
PlaceholderState
>
_snackBarPlaceholderKey
=
new
GlobalKey
<
PlaceholderState
>();
FitnessMode
_fitnessMode
=
FitnessMode
.
feed
;
void
_handleFitnessModeChange
(
FitnessMode
value
)
{
...
...
@@ -115,15 +114,14 @@ class FeedFragmentState extends State<FeedFragment> {
void
_handleItemDismissed
(
FitnessItem
item
)
{
config
.
onItemDeleted
(
item
);
showSnackBar
(
context:
context
,
placeholderKey:
_snackBarPlaceholderKey
,
Scaffold
.
of
(
context
).
showSnackBar
(
new
SnackBar
(
content:
new
Text
(
"Item deleted."
),
actions:
<
SnackBarAction
>[
new
SnackBarAction
(
label:
"UNDO"
,
onPressed:
()
{
config
.
onItemCreated
(
item
);
Navigator
.
of
(
context
).
pop
();
})]
);
actions:
<
SnackBarAction
>[
new
SnackBarAction
(
label:
"UNDO"
,
onPressed:
()
{
config
.
onItemCreated
(
item
);
}),
]
));
}
Widget
buildChart
()
{
...
...
@@ -212,7 +210,6 @@ class FeedFragmentState extends State<FeedFragment> {
return
new
Scaffold
(
toolBar:
buildToolBar
(),
body:
buildBody
(),
snackBar:
new
Placeholder
(
key:
_snackBarPlaceholderKey
),
floatingActionButton:
buildFloatingActionButton
()
);
}
...
...
examples/fitness/lib/measurement.dart
View file @
5734821f
...
...
@@ -112,8 +112,6 @@ class MeasurementFragment extends StatefulComponent {
}
class
MeasurementFragmentState
extends
State
<
MeasurementFragment
>
{
final
GlobalKey
<
PlaceholderState
>
_snackBarPlaceholderKey
=
new
GlobalKey
<
PlaceholderState
>();
String
_weight
=
""
;
DateTime
_when
=
new
DateTime
.
now
();
...
...
@@ -123,11 +121,9 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
parsedWeight
=
double
.
parse
(
_weight
);
}
on
FormatException
catch
(
e
)
{
print
(
"Exception
$e
"
);
showSnackBar
(
context:
context
,
placeholderKey:
_snackBarPlaceholderKey
,
Scaffold
.
of
(
context
).
showSnackBar
(
new
SnackBar
(
content:
new
Text
(
'Save failed'
)
);
)
)
;
}
config
.
onCreated
(
new
Measurement
(
when:
_when
,
weight:
parsedWeight
));
Navigator
.
of
(
context
).
pop
();
...
...
@@ -198,8 +194,7 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
Widget
build
(
BuildContext
context
)
{
return
new
Scaffold
(
toolBar:
buildToolBar
(),
body:
buildBody
(
context
),
snackBar:
new
Placeholder
(
key:
_snackBarPlaceholderKey
)
body:
buildBody
(
context
)
);
}
}
examples/stocks/lib/stock_home.dart
View file @
5734821f
...
...
@@ -19,7 +19,7 @@ class StockHome extends StatefulComponent {
class
StockHomeState
extends
State
<
StockHome
>
{
final
GlobalKey
<
PlaceholderState
>
_snackBarPlaceholderKey
=
new
GlobalKey
<
PlaceholderState
>
();
final
GlobalKey
scaffoldKey
=
new
GlobalKey
();
final
GlobalKey
<
PlaceholderState
>
_bottomSheetPlaceholderKey
=
new
GlobalKey
<
PlaceholderState
>();
bool
_isSearching
=
false
;
String
_searchQuery
;
...
...
@@ -179,19 +179,23 @@ class StockHomeState extends State<StockHome> {
return
stocks
.
where
((
Stock
stock
)
=>
stock
.
symbol
.
contains
(
regexp
));
}
void
_buyStock
(
Stock
stock
,
Key
arrowKey
)
{
setState
(()
{
stock
.
percentChange
=
100.0
*
(
1.0
/
stock
.
lastSale
);
stock
.
lastSale
+=
1.0
;
});
scaffoldKey
.
currentState
.
showSnackBar
(
new
SnackBar
(
content:
new
Text
(
"Purchased
${stock.symbol}
for
${stock.lastSale}
"
),
actions:
<
SnackBarAction
>[
new
SnackBarAction
(
label:
"BUY MORE"
,
onPressed:
()
{
_buyStock
(
stock
,
arrowKey
);
})
]
));
}
Widget
buildStockList
(
BuildContext
context
,
Iterable
<
Stock
>
stocks
)
{
return
new
StockList
(
stocks:
stocks
.
toList
(),
onAction:
(
Stock
stock
,
Key
arrowKey
)
{
setState
(()
{
stock
.
percentChange
=
100.0
*
(
1.0
/
stock
.
lastSale
);
stock
.
lastSale
+=
1.0
;
});
showModalBottomSheet
(
context:
context
,
child:
new
StockSymbolBottomSheet
(
stock:
stock
)
);
},
onAction:
_buyStock
,
onOpen:
(
Stock
stock
,
Key
arrowKey
)
{
Set
<
Key
>
mostValuableKeys
=
new
Set
<
Key
>();
mostValuableKeys
.
add
(
arrowKey
);
...
...
@@ -229,6 +233,7 @@ class StockHomeState extends State<StockHome> {
}
static
GlobalKey
searchFieldKey
=
new
GlobalKey
();
static
GlobalKey
companyNameKey
=
new
GlobalKey
();
// TODO(abarth): Should we factor this into a SearchBar in the framework?
Widget
buildSearchBar
()
{
...
...
@@ -247,18 +252,16 @@ class StockHomeState extends State<StockHome> {
);
}
void
_handleUndo
()
{
Navigator
.
of
(
context
).
pop
();
}
void
_handleStockPurchased
()
{
showSnackBar
(
void
_handleCreateCompany
()
{
showModalBottomSheet
(
// TODO(ianh): Fill this out.
context:
context
,
placeholderKey:
_snackBarPlaceholderKey
,
content:
new
Text
(
"Stock purchased!"
),
actions:
<
SnackBarAction
>[
new
SnackBarAction
(
label:
"UNDO"
,
onPressed:
_handleUndo
)
]
child:
new
Column
([
new
Input
(
key:
companyNameKey
,
placeholder:
'Company Name'
),
])
);
}
...
...
@@ -266,15 +269,15 @@ class StockHomeState extends State<StockHome> {
return
new
FloatingActionButton
(
child:
new
Icon
(
icon:
'content/add'
),
backgroundColor:
Colors
.
redAccent
[
200
],
onPressed:
_handle
StockPurchased
onPressed:
_handle
CreateCompany
);
}
Widget
build
(
BuildContext
context
)
{
return
new
Scaffold
(
key:
scaffoldKey
,
toolBar:
_isSearching
?
buildSearchBar
()
:
buildToolBar
(),
body:
buildTabNavigator
(),
snackBar:
new
Placeholder
(
key:
_snackBarPlaceholderKey
),
bottomSheet:
new
Placeholder
(
key:
_bottomSheetPlaceholderKey
),
floatingActionButton:
buildFloatingActionButton
()
);
...
...
packages/flutter/lib/src/material/constants.dart
View file @
5734821f
...
...
@@ -15,7 +15,6 @@ const double kStatusBarHeight = 50.0;
// Tablet/Desktop: 64dp
const
double
kToolBarHeight
=
56.0
;
const
double
kExtendedToolBarHeight
=
128.0
;
const
double
kSnackBarHeight
=
52.0
;
// https://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing
const
double
kListTitleHeight
=
72.0
;
...
...
packages/flutter/lib/src/material/scaffold.dart
View file @
5734821f
...
...
@@ -2,15 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:collection'
;
import
'dart:math'
as
math
;
import
'dart:ui'
as
ui
;
import
'package:flutter/animation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'constants.dart'
;
import
'material.dart'
;
import
'tool_bar.dart'
;
import
'snack_bar.dart'
;
const
double
_kFloatingActionButtonMargin
=
16.0
;
// TODO(hmuller): should be device dependent
...
...
@@ -77,46 +80,103 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
final
_ScaffoldLayout
_scaffoldLayout
=
new
_ScaffoldLayout
();
void
_addIfNonNull
(
List
<
LayoutId
>
children
,
Widget
child
,
Object
childId
)
{
if
(
child
!=
null
)
children
.
add
(
new
LayoutId
(
child:
child
,
id:
childId
));
}
class
Scaffold
extends
StatelessComponent
{
class
Scaffold
extends
StatefulComponent
{
Scaffold
({
Key
key
,
this
.
body
,
this
.
toolBar
,
this
.
snackBar
,
this
.
floatingActionButton
,
this
.
bottomSheet
this
.
body
,
this
.
bottomSheet
,
this
.
floatingActionButton
})
:
super
(
key:
key
);
final
Widget
body
;
final
ToolBar
toolBar
;
final
Widget
snackBar
;
final
Widget
body
;
final
Widget
bottomSheet
;
// this is for non-modal bottom sheets
final
Widget
floatingActionButton
;
final
Widget
bottomSheet
;
static
ScaffoldState
of
(
BuildContext
context
)
=>
context
.
ancestorStateOfType
(
ScaffoldState
);
ScaffoldState
createState
()
=>
new
ScaffoldState
();
}
class
ScaffoldState
extends
State
<
Scaffold
>
{
Queue
<
SnackBar
>
_snackBars
=
new
Queue
<
SnackBar
>();
Performance
_snackBarPerformance
;
Timer
_snackBarTimer
;
void
showSnackBar
(
SnackBar
snackbar
)
{
_snackBarPerformance
??=
SnackBar
.
createPerformance
()
..
addStatusListener
(
_handleSnackBarStatusChange
);
setState
(()
{
_snackBars
.
addLast
(
snackbar
.
withPerformance
(
_snackBarPerformance
));
});
}
void
_handleSnackBarStatusChange
(
PerformanceStatus
status
)
{
switch
(
status
)
{
case
PerformanceStatus
.
dismissed
:
assert
(
_snackBars
.
isNotEmpty
);
setState
(()
{
_snackBars
.
removeFirst
();
});
break
;
case
PerformanceStatus
.
completed
:
setState
(()
{
assert
(
_snackBarTimer
==
null
);
// build will create a new timer if necessary to dismiss the snack bar
});
break
;
case
PerformanceStatus
.
forward
:
case
PerformanceStatus
.
reverse
:
break
;
}
}
void
_hideSnackBar
()
{
_snackBarPerformance
.
reverse
();
_snackBarTimer
=
null
;
}
void
dispose
()
{
_snackBarPerformance
?.
stop
();
_snackBarPerformance
=
null
;
_snackBarTimer
?.
cancel
();
_snackBarTimer
=
null
;
super
.
dispose
();
}
void
_addIfNonNull
(
List
<
LayoutId
>
children
,
Widget
child
,
Object
childId
)
{
if
(
child
!=
null
)
children
.
add
(
new
LayoutId
(
child:
child
,
id:
childId
));
}
Widget
build
(
BuildContext
context
)
{
final
Widget
paddedToolBar
=
toolBar
?.
withPadding
(
new
EdgeDims
.
only
(
top:
ui
.
window
.
padding
.
top
));
final
Widget
materialBody
=
body
!=
null
?
new
Material
(
child:
body
)
:
null
;
Widget
constrainedSnackBar
;
if
(
snackBar
!=
null
)
{
// TODO(jackson): On tablet/desktop, minWidth = 288, maxWidth = 568
constrainedSnackBar
=
new
ConstrainedBox
(
constraints:
const
BoxConstraints
(
maxHeight:
kSnackBarHeight
),
child:
snackBar
);
final
Widget
paddedToolBar
=
config
.
toolBar
?.
withPadding
(
new
EdgeDims
.
only
(
top:
ui
.
window
.
padding
.
top
));
final
Widget
materialBody
=
config
.
body
!=
null
?
new
Material
(
child:
config
.
body
)
:
null
;
if
(
_snackBars
.
length
>
0
)
{
if
(
_snackBarPerformance
.
isDismissed
)
_snackBarPerformance
.
forward
();
ModalRoute
route
=
ModalRoute
.
of
(
context
);
if
(
route
==
null
||
route
.
isCurrent
)
{
if
(
_snackBarPerformance
.
isCompleted
&&
_snackBarTimer
==
null
)
_snackBarTimer
=
new
Timer
(
_snackBars
.
first
.
duration
,
_hideSnackBar
);
}
else
{
_snackBarTimer
?.
cancel
();
_snackBarTimer
=
null
;
}
}
final
List
<
LayoutId
>
children
=
new
List
<
LayoutId
>();
_addIfNonNull
(
children
,
materialBody
,
_Child
.
body
);
_addIfNonNull
(
children
,
paddedToolBar
,
_Child
.
toolBar
);
_addIfNonNull
(
children
,
bottomSheet
,
_Child
.
bottomSheet
);
_addIfNonNull
(
children
,
constrainedSnackBar
,
_Child
.
snackBar
);
_addIfNonNull
(
children
,
floatingActionButton
,
_Child
.
floatingActionButton
);
_addIfNonNull
(
children
,
config
.
bottomSheet
,
_Child
.
bottomSheet
);
if
(
_snackBars
.
isNotEmpty
)
_addIfNonNull
(
children
,
_snackBars
.
first
,
_Child
.
snackBar
);
_addIfNonNull
(
children
,
config
.
floatingActionButton
,
_Child
.
floatingActionButton
);
return
new
CustomMultiChildLayout
(
children
,
delegate:
_scaffoldLayout
);
}
}
packages/flutter/lib/src/material/snack_bar.dart
View file @
5734821f
...
...
@@ -2,20 +2,30 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'package:flutter/animation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'constants.dart'
;
import
'material.dart'
;
import
'theme.dart'
;
import
'typography.dart'
;
// https://www.google.com/design/spec/components/snackbars-toasts.html#snackbars-toasts-specs
const
double
_kSideMargins
=
24.0
;
const
double
_kVerticalPadding
=
14.0
;
const
double
_kSingleLineVerticalPadding
=
14.0
;
const
double
_kMultiLineVerticalPadding
=
24.0
;
const
Color
_kSnackBackground
=
const
Color
(
0xFF323232
);
// TODO(ianh): We should check if the given text and actions are going to fit on
// one line or not, and if they are, use the single-line layout, and if not, use
// the multiline layout. See link above.
// TODO(ianh): Implement the Tablet version of snackbar if we're "on a tablet".
const
Duration
kSnackBarTransitionDuration
=
const
Duration
(
milliseconds:
250
);
const
Duration
kSnackBarShortDisplayDuration
=
const
Duration
(
milliseconds:
1500
);
const
Duration
kSnackBarMediumDisplayDuration
=
const
Duration
(
milliseconds:
2750
);
const
Curve
snackBarFadeCurve
=
const
Interval
(
0.72
,
1.0
,
curve:
Curves
.
fastOutSlowIn
);
class
SnackBarAction
extends
StatelessComponent
{
SnackBarAction
({
Key
key
,
this
.
label
,
this
.
onPressed
})
:
super
(
key:
key
)
{
assert
(
label
!=
null
);
...
...
@@ -29,32 +39,35 @@ class SnackBarAction extends StatelessComponent {
onTap:
onPressed
,
child:
new
Container
(
margin:
const
EdgeDims
.
only
(
left:
_kSideMargins
),
padding:
const
EdgeDims
.
symmetric
(
vertical:
_kVerticalPadding
),
padding:
const
EdgeDims
.
symmetric
(
vertical:
_k
SingleLine
VerticalPadding
),
child:
new
Text
(
label
)
)
);
}
}
class
_
SnackBar
extends
StatelessComponent
{
_
SnackBar
({
class
SnackBar
extends
StatelessComponent
{
SnackBar
({
Key
key
,
this
.
content
,
this
.
actions
,
this
.
route
this
.
duration
:
kSnackBarShortDisplayDuration
,
this
.
performance
})
:
super
(
key:
key
)
{
assert
(
content
!=
null
);
}
final
Widget
content
;
final
List
<
SnackBarAction
>
actions
;
final
_SnackBarRoute
route
;
final
Duration
duration
;
final
PerformanceView
performance
;
Widget
build
(
BuildContext
context
)
{
assert
(
performance
!=
null
);
List
<
Widget
>
children
=
<
Widget
>[
new
Flexible
(
child:
new
Container
(
margin:
const
EdgeDims
.
symmetric
(
vertical:
_kVerticalPadding
),
margin:
const
EdgeDims
.
symmetric
(
vertical:
_k
SingleLine
VerticalPadding
),
child:
new
DefaultTextStyle
(
style:
Typography
.
white
.
subhead
,
child:
content
...
...
@@ -64,25 +77,21 @@ class _SnackBar extends StatelessComponent {
];
if
(
actions
!=
null
)
children
.
addAll
(
actions
);
return
new
SquashTransition
(
performance:
route
.
performance
,
height:
new
AnimatedValue
<
double
>(
0.0
,
end:
kSnackBarHeight
,
curve:
Curves
.
easeIn
,
reverseCurve:
Curves
.
easeOut
),
child:
new
ClipRect
(
child:
new
OverflowBox
(
minHeight:
kSnackBarHeight
,
maxHeight:
kSnackBarHeight
,
child:
new
Material
(
elevation:
6
,
color:
_kSnackBackground
,
child:
new
Container
(
margin:
const
EdgeDims
.
symmetric
(
horizontal:
_kSideMargins
),
child:
new
DefaultTextStyle
(
style:
new
TextStyle
(
color:
Theme
.
of
(
context
).
accentColor
),
return
new
ClipRect
(
child:
new
AlignTransition
(
performance:
performance
,
alignment:
new
AnimatedValue
<
FractionalOffset
>(
const
FractionalOffset
(
0.0
,
0.0
)),
heightFactor:
new
AnimatedValue
<
double
>(
0.0
,
end:
1.0
,
curve:
Curves
.
fastOutSlowIn
),
child:
new
Material
(
elevation:
6
,
color:
_kSnackBackground
,
child:
new
Container
(
margin:
const
EdgeDims
.
symmetric
(
horizontal:
_kSideMargins
),
child:
new
DefaultTextStyle
(
style:
new
TextStyle
(
color:
Theme
.
of
(
context
).
accentColor
),
child:
new
FadeTransition
(
performance:
performance
,
opacity:
new
AnimatedValue
<
double
>(
0.0
,
end:
1.0
,
curve:
snackBarFadeCurve
),
child:
new
Row
(
children
)
)
)
...
...
@@ -91,33 +100,23 @@ class _SnackBar extends StatelessComponent {
)
);
}
}
class
_SnackBarRoute
extends
TransitionRoute
{
_SnackBarRoute
({
Completer
completer
})
:
super
(
completer:
completer
);
bool
get
opaque
=>
false
;
Duration
get
transitionDuration
=>
const
Duration
(
milliseconds:
200
);
}
Future
showSnackBar
(
{
BuildContext
context
,
GlobalKey
<
PlaceholderState
>
placeholderKey
,
Widget
content
,
List
<
SnackBarAction
>
actions
})
{
final
Completer
completer
=
new
Completer
();
_SnackBarRoute
route
=
new
_SnackBarRoute
(
completer:
completer
);
_SnackBar
snackBar
=
new
_SnackBar
(
route:
route
,
content:
content
,
actions:
actions
);
// API for Scaffold.addSnackBar():
// TODO(hansmuller): https://github.com/flutter/flutter/issues/374
assert
(
placeholderKey
.
currentState
.
child
==
null
);
static
Performance
createPerformance
()
{
return
new
Performance
(
duration:
kSnackBarTransitionDuration
,
debugLabel:
'SnackBar'
);
}
placeholderKey
.
currentState
.
child
=
snackBar
;
Navigator
.
of
(
context
).
pushEphemeral
(
route
);
return
completer
.
future
.
then
((
_
)
{
// If our overlay has been obscured by an opaque OverlayEntry currentState
// will have been cleared already.
if
(
placeholderKey
.
currentState
!=
null
)
placeholderKey
.
currentState
.
child
=
null
;
});
SnackBar
withPerformance
(
Performance
newPerformance
)
{
return
new
SnackBar
(
key:
key
,
content:
content
,
actions:
actions
,
duration:
duration
,
performance:
newPerformance
);
}
}
packages/flutter/lib/src/widgets/navigator.dart
View file @
5734821f
...
...
@@ -109,13 +109,15 @@ class NavigatorState extends State<Navigator> {
}
void
push
(
Route
route
,
{
Set
<
Key
>
mostValuableKeys
})
{
_popAllEphemeralRoutes
();
int
index
=
_modal
.
length
-
1
;
while
(
index
>=
0
&&
_modal
[
index
].
willPushNext
(
route
))
index
-=
1
;
route
.
didPush
(
overlay
,
_currentOverlay
);
config
.
observer
?.
didPushModal
(
route
,
index
>=
0
?
_modal
[
index
]
:
null
);
_modal
.
add
(
route
);
setState
(()
{
_popAllEphemeralRoutes
();
int
index
=
_modal
.
length
-
1
;
while
(
index
>=
0
&&
_modal
[
index
].
willPushNext
(
route
))
index
-=
1
;
route
.
didPush
(
overlay
,
_currentOverlay
);
config
.
observer
?.
didPushModal
(
route
,
index
>=
0
?
_modal
[
index
]
:
null
);
_modal
.
add
(
route
);
});
}
void
pushEphemeral
(
Route
route
)
{
...
...
@@ -132,17 +134,22 @@ class NavigatorState extends State<Navigator> {
}
void
pop
([
dynamic
result
])
{
if
(
_ephemeral
.
isNotEmpty
)
{
_ephemeral
.
removeLast
().
didPop
(
result
);
}
else
{
assert
(
_modal
.
length
>
1
);
Route
route
=
_modal
.
removeLast
();
route
.
didPop
(
result
);
int
index
=
_modal
.
length
-
1
;
while
(
index
>=
0
&&
_modal
[
index
].
didPopNext
(
route
))
index
-=
1
;
config
.
observer
?.
didPopModal
(
route
,
index
>=
0
?
_modal
[
index
]
:
null
);
}
setState
(()
{
// We use setState to guarantee that we'll rebuild, since the routes can't
// do that for themselves, even if they have changed their own state (e.g.
// ModalScope.isCurrent).
if
(
_ephemeral
.
isNotEmpty
)
{
_ephemeral
.
removeLast
().
didPop
(
result
);
}
else
{
assert
(
_modal
.
length
>
1
);
Route
route
=
_modal
.
removeLast
();
route
.
didPop
(
result
);
int
index
=
_modal
.
length
-
1
;
while
(
index
>=
0
&&
_modal
[
index
].
didPopNext
(
route
))
index
-=
1
;
config
.
observer
?.
didPopModal
(
route
,
index
>=
0
?
_modal
[
index
]
:
null
);
}
});
}
Widget
build
(
BuildContext
context
)
{
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
5734821f
...
...
@@ -31,7 +31,7 @@ class StateRoute extends Route {
bool
didPopNext
(
Route
nextRoute
)
=>
true
;
}
class
OverlayRoute
extends
Route
{
abstract
class
OverlayRoute
extends
Route
{
List
<
WidgetBuilder
>
get
builders
=>
const
<
WidgetBuilder
>[];
List
<
OverlayEntry
>
get
overlayEntries
=>
_overlayEntries
;
...
...
@@ -108,24 +108,56 @@ abstract class TransitionRoute extends OverlayRoute {
String
toString
()
=>
'
$runtimeType
(performance:
$_performance
)'
;
}
class
_ModalScopeStatus
extends
InheritedWidget
{
_ModalScopeStatus
({
Key
key
,
this
.
current
,
this
.
route
,
Widget
child
})
:
super
(
key:
key
,
child:
child
)
{
assert
(
current
!=
null
);
assert
(
route
!=
null
);
assert
(
child
!=
null
);
}
final
bool
current
;
final
Route
route
;
bool
updateShouldNotify
(
_ModalScopeStatus
old
)
{
return
current
!=
old
.
current
||
route
!=
old
.
route
;
}
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'
${current ? "active" : "inactive"}
'
);
}
}
class
_ModalScope
extends
StatusTransitionComponent
{
_ModalScope
({
Key
key
,
this
.
subtreeKey
,
this
.
storageBucket
,
PerformanceView
performance
,
this
.
current
,
this
.
route
})
:
super
(
key:
key
,
performance:
performance
);
final
GlobalKey
subtreeKey
;
final
PageStorageBucket
storageBucket
;
final
bool
current
;
final
ModalRoute
route
;
Widget
build
(
BuildContext
context
)
{
Widget
contents
=
new
PageStorage
(
key:
subtreeKey
,
bucket:
storageBucket
,
child:
route
.
buildPage
(
context
)
child:
new
_ModalScopeStatus
(
current:
current
,
route:
route
,
child:
route
.
buildPage
(
context
)
)
);
if
(
route
.
offstage
)
{
contents
=
new
OffStage
(
child:
contents
);
...
...
@@ -165,8 +197,18 @@ abstract class ModalRoute extends TransitionRoute {
this
.
settings
:
const
NamedRouteSettings
()
})
:
super
(
completer:
completer
);
// The API for general users of this class
final
NamedRouteSettings
settings
;
static
ModalRoute
of
(
BuildContext
context
)
{
_ModalScopeStatus
widget
=
context
.
inheritFromWidgetOfType
(
_ModalScopeStatus
);
return
widget
?.
route
;
}
bool
get
isCurrent
=>
_isCurrent
;
bool
_isCurrent
=
false
;
// The API for subclasses to override - used by _ModalScope
...
...
@@ -204,6 +246,34 @@ abstract class ModalRoute extends TransitionRoute {
// Internals
void
didPush
(
OverlayState
overlay
,
OverlayEntry
insertionPoint
)
{
assert
(!
_isCurrent
);
_isCurrent
=
true
;
super
.
didPush
(
overlay
,
insertionPoint
);
}
void
didPop
(
dynamic
result
)
{
assert
(
_isCurrent
);
_isCurrent
=
false
;
super
.
didPop
(
result
);
}
bool
willPushNext
(
Route
nextRoute
)
{
if
(
nextRoute
is
ModalRoute
)
{
assert
(
_isCurrent
);
_isCurrent
=
false
;
}
return
false
;
}
bool
didPopNext
(
Route
nextRoute
)
{
if
(
nextRoute
is
ModalRoute
)
{
assert
(!
_isCurrent
);
_isCurrent
=
true
;
}
return
false
;
}
final
GlobalKey
<
StatusTransitionState
>
_scopeKey
=
new
GlobalKey
<
StatusTransitionState
>();
final
GlobalKey
_subtreeKey
=
new
GlobalKey
();
final
PageStorageBucket
_storageBucket
=
new
PageStorageBucket
();
...
...
@@ -222,6 +292,7 @@ abstract class ModalRoute extends TransitionRoute {
subtreeKey:
_subtreeKey
,
storageBucket:
_storageBucket
,
performance:
performance
,
current:
isCurrent
,
route:
this
);
}
...
...
packages/unit/test/widget/snack_bar_test.dart
View file @
5734821f
...
...
@@ -7,60 +7,142 @@ import 'package:test/test.dart';
import
'widget_tester.dart'
;
class
Builder
extends
StatelessComponent
{
Builder
({
this
.
builder
});
final
WidgetBuilder
builder
;
Widget
build
(
BuildContext
context
)
=>
builder
(
context
);
}
void
main
(
)
{
test
(
'SnackBar control test'
,
()
{
testWidgets
((
WidgetTester
tester
)
{
String
helloSnackBar
=
'Hello SnackBar'
;
GlobalKey
<
PlaceholderState
>
placeholderKey
=
new
GlobalKey
<
PlaceholderState
>();
Key
tapTarget
=
new
Key
(
'tap-target'
);
BuildContext
context
;
bool
showSnackBarThenCalled
=
false
;
tester
.
pumpWidget
(
new
MaterialApp
(
routes:
<
String
,
RouteBuilder
>{
'/'
:
(
RouteArguments
args
)
{
context
=
args
.
context
;
return
new
GestureDetector
(
onTap:
()
{
showSnackBar
(
context:
args
.
context
,
placeholderKey:
placeholderKey
,
content:
new
Text
(
helloSnackBar
)
).
then
((
_
)
{
showSnackBarThenCalled
=
true
;
});
},
child:
new
Container
(
decoration:
const
BoxDecoration
(
backgroundColor:
const
Color
(
0xFF00FF00
)
),
child:
new
Center
(
key:
tapTarget
,
child:
new
Placeholder
(
key:
placeholderKey
)
)
return
new
Scaffold
(
body:
new
Builder
(
builder:
(
BuildContext
context
)
{
return
new
GestureDetector
(
onTap:
()
{
Scaffold
.
of
(
context
).
showSnackBar
(
new
SnackBar
(
content:
new
Text
(
helloSnackBar
),
duration:
new
Duration
(
seconds:
2
)
));
},
behavior:
HitTestBehavior
.
opaque
,
child:
new
Container
(
height:
100.0
,
width:
100.0
,
key:
tapTarget
)
);
}
)
);
}
}
));
// TODO(hansmuller): find a way to avoid calling pump over and over.
// https://github.com/flutter/flutter/issues/348
expect
(
tester
.
findText
(
helloSnackBar
),
isNull
);
tester
.
tap
(
tester
.
findElementByKey
(
tapTarget
));
expect
(
tester
.
findText
(
helloSnackBar
),
isNull
);
tester
.
pump
(
new
Duration
(
seconds:
1
));
tester
.
pump
(
new
Duration
(
seconds:
1
));
tester
.
pump
();
// schedule animation
expect
(
tester
.
findText
(
helloSnackBar
),
isNotNull
);
Navigator
.
of
(
context
).
pop
();
tester
.
pump
();
// begin animation
expect
(
tester
.
findText
(
helloSnackBar
),
isNotNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 0.75s // animation last frame; two second timer starts here
expect
(
tester
.
findText
(
helloSnackBar
),
isNotNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 1.50s
expect
(
tester
.
findText
(
helloSnackBar
),
isNotNull
);
tester
.
pump
(
new
Duration
(
seconds:
1
));
tester
.
pump
(
new
Duration
(
seconds:
1
));
tester
.
pump
(
new
Duration
(
seconds:
1
));
expect
(
showSnackBarThenCalled
,
isTrue
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 2.25s
expect
(
tester
.
findText
(
helloSnackBar
),
isNotNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
tester
.
pump
();
// begin animation
expect
(
tester
.
findText
(
helloSnackBar
),
isNotNull
);
// frame 0 of dismiss animation
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 3.75s // last frame of animation, snackbar removed from build
expect
(
tester
.
findText
(
helloSnackBar
),
isNull
);
expect
(
placeholderKey
.
currentState
.
child
,
isNull
);
});
});
test
(
'SnackBar twice test'
,
()
{
testWidgets
((
WidgetTester
tester
)
{
int
snackBarCount
=
0
;
Key
tapTarget
=
new
Key
(
'tap-target'
);
tester
.
pumpWidget
(
new
MaterialApp
(
routes:
<
String
,
RouteBuilder
>{
'/'
:
(
RouteArguments
args
)
{
return
new
Scaffold
(
body:
new
Builder
(
builder:
(
BuildContext
context
)
{
return
new
GestureDetector
(
onTap:
()
{
snackBarCount
+=
1
;
Scaffold
.
of
(
context
).
showSnackBar
(
new
SnackBar
(
content:
new
Text
(
"bar
$snackBarCount
"
),
duration:
new
Duration
(
seconds:
2
)
));
},
behavior:
HitTestBehavior
.
opaque
,
child:
new
Container
(
height:
100.0
,
width:
100.0
,
key:
tapTarget
)
);
}
)
);
}
}
));
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
tester
.
tap
(
tester
.
findElementByKey
(
tapTarget
));
// queue bar1
tester
.
tap
(
tester
.
findElementByKey
(
tapTarget
));
// queue bar2
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
tester
.
pump
();
// schedule animation for bar1
expect
(
tester
.
findText
(
'bar1'
),
isNotNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
tester
.
pump
();
// begin animation
expect
(
tester
.
findText
(
'bar1'
),
isNotNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 0.75s // animation last frame; two second timer starts here
expect
(
tester
.
findText
(
'bar1'
),
isNotNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 1.50s
expect
(
tester
.
findText
(
'bar1'
),
isNotNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 2.25s
expect
(
tester
.
findText
(
'bar1'
),
isNotNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
tester
.
pump
();
// begin animation
expect
(
tester
.
findText
(
'bar1'
),
isNotNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 3.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNotNull
);
tester
.
pump
();
// begin animation
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNotNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 4.50s // animation last frame; two second timer starts here
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNotNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 5.25s
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNotNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 6.00s
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNotNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 6.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
tester
.
pump
();
// begin animation
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNotNull
);
tester
.
pump
(
new
Duration
(
milliseconds:
750
));
// 7.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect
(
tester
.
findText
(
'bar1'
),
isNull
);
expect
(
tester
.
findText
(
'bar2'
),
isNull
);
});
});
}
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