Remove deprecated Scaffold SnackBar API (#98549)
This commit is contained in:
parent
1cf5c74009
commit
135bb5d4f2
@ -2017,195 +2017,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
|
|||||||
|
|
||||||
// Used for both the snackbar and material banner APIs
|
// Used for both the snackbar and material banner APIs
|
||||||
ScaffoldMessengerState? _scaffoldMessenger;
|
ScaffoldMessengerState? _scaffoldMessenger;
|
||||||
bool? _accessibleNavigation;
|
|
||||||
|
|
||||||
// SNACKBAR API
|
// SNACKBAR API
|
||||||
final Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> _snackBars = Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>();
|
|
||||||
AnimationController? _snackBarController;
|
|
||||||
Timer? _snackBarTimer;
|
|
||||||
|
|
||||||
/// [ScaffoldMessengerState.showSnackBar] shows a [SnackBar] at the bottom of
|
|
||||||
/// the scaffold. This method should not be used, and will be deprecated in
|
|
||||||
/// the near future..
|
|
||||||
///
|
|
||||||
/// A scaffold can show at most one snack bar at a time. If this function is
|
|
||||||
/// called while another snack bar is already visible, the given snack bar
|
|
||||||
/// will be added to a queue and displayed after the earlier snack bars have
|
|
||||||
/// closed.
|
|
||||||
///
|
|
||||||
/// To control how long a [SnackBar] remains visible, use [SnackBar.duration].
|
|
||||||
///
|
|
||||||
/// To remove the [SnackBar] with an exit animation, use
|
|
||||||
/// [ScaffoldMessengerState.hideCurrentSnackBar] or call
|
|
||||||
/// [ScaffoldFeatureController.close] on the returned [ScaffoldFeatureController].
|
|
||||||
/// To remove a [SnackBar] suddenly (without an animation), use
|
|
||||||
/// [ScaffoldMessengerState.removeCurrentSnackBar].
|
|
||||||
///
|
|
||||||
/// See [ScaffoldMessenger.of] for information about how to obtain the
|
|
||||||
/// [ScaffoldMessengerState].
|
|
||||||
///
|
|
||||||
/// {@tool dartpad}
|
|
||||||
/// Here is an example of showing a [SnackBar] when the user presses a button.
|
|
||||||
///
|
|
||||||
/// ** See code in examples/api/lib/material/scaffold/scaffold_state.show_snack_bar.0.dart **
|
|
||||||
/// {@end-tool}
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
|
|
||||||
@Deprecated(
|
|
||||||
'Use ScaffoldMessenger.showSnackBar. '
|
|
||||||
'This feature was deprecated after v1.23.0-14.0.pre.',
|
|
||||||
)
|
|
||||||
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackbar) {
|
|
||||||
_snackBarController ??= SnackBar.createAnimationController(vsync: this)
|
|
||||||
..addStatusListener(_handleSnackBarStatusChange);
|
|
||||||
if (_snackBars.isEmpty) {
|
|
||||||
assert(_snackBarController!.isDismissed);
|
|
||||||
_snackBarController!.forward();
|
|
||||||
}
|
|
||||||
late ScaffoldFeatureController<SnackBar, SnackBarClosedReason> controller;
|
|
||||||
controller = ScaffoldFeatureController<SnackBar, SnackBarClosedReason>._(
|
|
||||||
// We provide a fallback key so that if back-to-back snackbars happen to
|
|
||||||
// match in structure, material ink splashes and highlights don't survive
|
|
||||||
// from one to the next.
|
|
||||||
snackbar.withAnimation(_snackBarController!, fallbackKey: UniqueKey()),
|
|
||||||
Completer<SnackBarClosedReason>(),
|
|
||||||
() {
|
|
||||||
assert(_snackBars.first == controller);
|
|
||||||
hideCurrentSnackBar();
|
|
||||||
},
|
|
||||||
null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
_snackBars.addLast(controller);
|
|
||||||
});
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleSnackBarStatusChange(AnimationStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case AnimationStatus.dismissed:
|
|
||||||
assert(_snackBars.isNotEmpty);
|
|
||||||
setState(() {
|
|
||||||
_snackBars.removeFirst();
|
|
||||||
});
|
|
||||||
if (_snackBars.isNotEmpty)
|
|
||||||
_snackBarController!.forward();
|
|
||||||
break;
|
|
||||||
case AnimationStatus.completed:
|
|
||||||
setState(() {
|
|
||||||
assert(_snackBarTimer == null);
|
|
||||||
// build will create a new timer if necessary to dismiss the snack bar
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case AnimationStatus.forward:
|
|
||||||
case AnimationStatus.reverse:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [ScaffoldMessengerState.removeCurrentSnackBar] removes the current
|
|
||||||
/// [SnackBar] (if any) immediately. This method should not be used, and will
|
|
||||||
/// be deprecated in the near future.
|
|
||||||
///
|
|
||||||
/// The removed snack bar does not run its normal exit animation. If there are
|
|
||||||
/// any queued snack bars, they begin their entrance animation immediately.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
|
|
||||||
@Deprecated(
|
|
||||||
'Use ScaffoldMessenger.removeCurrentSnackBar. '
|
|
||||||
'This feature was deprecated after v1.23.0-14.0.pre.',
|
|
||||||
)
|
|
||||||
void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
|
|
||||||
assert(reason != null);
|
|
||||||
|
|
||||||
// SnackBars and SnackBarActions can call to hide and remove themselves, but
|
|
||||||
// they are not aware of who presented them, the Scaffold or the
|
|
||||||
// ScaffoldMessenger. As such, when the SnackBar classes call upon Scaffold
|
|
||||||
// to remove (the current default), we should re-direct to the
|
|
||||||
// ScaffoldMessenger here if that is where the SnackBar originated from.
|
|
||||||
if (_messengerSnackBar != null) {
|
|
||||||
// ScaffoldMessenger is presenting SnackBars.
|
|
||||||
assert(debugCheckHasScaffoldMessenger(context));
|
|
||||||
assert(
|
|
||||||
_scaffoldMessenger != null,
|
|
||||||
'A SnackBar was shown by the ScaffoldMessenger, but has been called upon '
|
|
||||||
'to be removed from a Scaffold that is not registered with a '
|
|
||||||
'ScaffoldMessenger, this can happen if a Scaffold has been rebuilt '
|
|
||||||
'without an ancestor ScaffoldMessenger.',
|
|
||||||
);
|
|
||||||
_scaffoldMessenger!.removeCurrentSnackBar(reason: reason);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_snackBars.isEmpty)
|
|
||||||
return;
|
|
||||||
final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
|
|
||||||
if (!completer.isCompleted)
|
|
||||||
completer.complete(reason);
|
|
||||||
_snackBarTimer?.cancel();
|
|
||||||
_snackBarTimer = null;
|
|
||||||
_snackBarController!.value = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [ScaffoldMessengerState.hideCurrentSnackBar] removes the current
|
|
||||||
/// [SnackBar] by running its normal exit animation. This method should not be
|
|
||||||
/// used, and will be deprecated in the near future.
|
|
||||||
///
|
|
||||||
/// The closed completer is called after the animation is complete.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
|
|
||||||
@Deprecated(
|
|
||||||
'Use ScaffoldMessenger.hideCurrentSnackBar. '
|
|
||||||
'This feature was deprecated after v1.23.0-14.0.pre.',
|
|
||||||
)
|
|
||||||
void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
|
|
||||||
assert(reason != null);
|
|
||||||
|
|
||||||
// SnackBars and SnackBarActions can call to hide and remove themselves, but
|
|
||||||
// they are not aware of who presented them, the Scaffold or the
|
|
||||||
// ScaffoldMessenger. As such, when the SnackBar classes call upon Scaffold
|
|
||||||
// to remove (the current default), we should re-direct to the
|
|
||||||
// ScaffoldMessenger here if that is where the SnackBar originated from.
|
|
||||||
if (_messengerSnackBar != null) {
|
|
||||||
// ScaffoldMessenger is presenting SnackBars.
|
|
||||||
assert(debugCheckHasScaffoldMessenger(context));
|
|
||||||
assert(
|
|
||||||
_scaffoldMessenger != null,
|
|
||||||
'A SnackBar was shown by the ScaffoldMessenger, but has been called upon '
|
|
||||||
'to be removed from a Scaffold that is not registered with a '
|
|
||||||
'ScaffoldMessenger, this can happen if a Scaffold has been rebuilt '
|
|
||||||
'without an ancestor ScaffoldMessenger.',
|
|
||||||
);
|
|
||||||
_scaffoldMessenger!.hideCurrentSnackBar(reason: reason);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_snackBars.isEmpty || _snackBarController!.status == AnimationStatus.dismissed)
|
|
||||||
return;
|
|
||||||
final MediaQueryData mediaQuery = MediaQuery.of(context);
|
|
||||||
final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
|
|
||||||
if (mediaQuery.accessibleNavigation) {
|
|
||||||
_snackBarController!.value = 0.0;
|
|
||||||
completer.complete(reason);
|
|
||||||
} else {
|
|
||||||
_snackBarController!.reverse().then<void>((void value) {
|
|
||||||
assert(mounted);
|
|
||||||
if (!completer.isCompleted)
|
|
||||||
completer.complete(reason);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_snackBarTimer?.cancel();
|
|
||||||
_snackBarTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The _messengerSnackBar represents the current SnackBar being managed by
|
|
||||||
// the ScaffoldMessenger, instead of the Scaffold.
|
|
||||||
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? _messengerSnackBar;
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? _messengerSnackBar;
|
||||||
|
|
||||||
// This is used to update the _messengerSnackBar by the ScaffoldMessenger.
|
// This is used to update the _messengerSnackBar by the ScaffoldMessenger.
|
||||||
@ -2672,31 +2485,12 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
|
|||||||
_scaffoldMessenger = currentScaffoldMessenger;
|
_scaffoldMessenger = currentScaffoldMessenger;
|
||||||
_scaffoldMessenger?._register(this);
|
_scaffoldMessenger?._register(this);
|
||||||
|
|
||||||
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
|
|
||||||
final MediaQueryData mediaQuery = MediaQuery.of(context);
|
|
||||||
// If we transition from accessible navigation to non-accessible navigation
|
|
||||||
// and there is a SnackBar that would have timed out that has already
|
|
||||||
// completed its timer, dismiss that SnackBar. If the timer hasn't finished
|
|
||||||
// yet, let it timeout as normal.
|
|
||||||
if ((_accessibleNavigation ?? false)
|
|
||||||
&& !mediaQuery.accessibleNavigation
|
|
||||||
&& _snackBarTimer != null
|
|
||||||
&& !_snackBarTimer!.isActive) {
|
|
||||||
hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
|
|
||||||
}
|
|
||||||
_accessibleNavigation = mediaQuery.accessibleNavigation;
|
|
||||||
|
|
||||||
_maybeBuildPersistentBottomSheet();
|
_maybeBuildPersistentBottomSheet();
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
|
|
||||||
_snackBarController?.dispose();
|
|
||||||
_snackBarTimer?.cancel();
|
|
||||||
_snackBarTimer = null;
|
|
||||||
|
|
||||||
_geometryNotifier.dispose();
|
_geometryNotifier.dispose();
|
||||||
_floatingActionButtonMoveController.dispose();
|
_floatingActionButtonMoveController.dispose();
|
||||||
_floatingActionButtonVisibilityController.dispose();
|
_floatingActionButtonVisibilityController.dispose();
|
||||||
@ -2817,31 +2611,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
|
|||||||
final ThemeData themeData = Theme.of(context);
|
final ThemeData themeData = Theme.of(context);
|
||||||
final TextDirection textDirection = Directionality.of(context);
|
final TextDirection textDirection = Directionality.of(context);
|
||||||
|
|
||||||
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
|
|
||||||
_accessibleNavigation = mediaQuery.accessibleNavigation;
|
|
||||||
if (_snackBars.isNotEmpty) {
|
|
||||||
final ModalRoute<dynamic>? route = ModalRoute.of(context);
|
|
||||||
if (route == null || route.isCurrent) {
|
|
||||||
if (_snackBarController!.isCompleted && _snackBarTimer == null) {
|
|
||||||
final SnackBar snackBar = _snackBars.first._widget;
|
|
||||||
_snackBarTimer = Timer(snackBar.duration, () {
|
|
||||||
assert(
|
|
||||||
_snackBarController!.status == AnimationStatus.forward ||
|
|
||||||
_snackBarController!.status == AnimationStatus.completed,
|
|
||||||
);
|
|
||||||
// Look up MediaQuery again in case the setting changed.
|
|
||||||
final MediaQueryData mediaQuery = MediaQuery.of(context);
|
|
||||||
if (mediaQuery.accessibleNavigation && snackBar.action != null)
|
|
||||||
return;
|
|
||||||
hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_snackBarTimer?.cancel();
|
|
||||||
_snackBarTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<LayoutId> children = <LayoutId>[];
|
final List<LayoutId> children = <LayoutId>[];
|
||||||
_addIfNonNull(
|
_addIfNonNull(
|
||||||
children,
|
children,
|
||||||
@ -2895,15 +2664,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
|
|||||||
|
|
||||||
bool isSnackBarFloating = false;
|
bool isSnackBarFloating = false;
|
||||||
double? snackBarWidth;
|
double? snackBarWidth;
|
||||||
// We should only be using one API for SnackBars. Currently, we can use the
|
|
||||||
// Scaffold, which creates a SnackBar queue (_snackBars), or the
|
|
||||||
// ScaffoldMessenger, which sends a SnackBar to descendant Scaffolds.
|
|
||||||
// (_messengerSnackBar).
|
|
||||||
assert(
|
|
||||||
_snackBars.isEmpty || _messengerSnackBar == null,
|
|
||||||
'Only one API should be used to manage SnackBars. The ScaffoldMessenger is '
|
|
||||||
'the preferred API instead of the Scaffold methods.',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) {
|
if (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) {
|
||||||
final Widget stack = Stack(
|
final Widget stack = Stack(
|
||||||
@ -2944,27 +2704,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SnackBar set by Scaffold
|
|
||||||
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
|
|
||||||
if (_snackBars.isNotEmpty) {
|
|
||||||
final SnackBarBehavior snackBarBehavior = _snackBars.first._widget.behavior
|
|
||||||
?? themeData.snackBarTheme.behavior
|
|
||||||
?? SnackBarBehavior.fixed;
|
|
||||||
isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
|
|
||||||
snackBarWidth = _snackBars.first._widget.width;
|
|
||||||
|
|
||||||
_addIfNonNull(
|
|
||||||
children,
|
|
||||||
_snackBars.first._widget,
|
|
||||||
_ScaffoldSlot.snackBar,
|
|
||||||
removeLeftPadding: false,
|
|
||||||
removeTopPadding: true,
|
|
||||||
removeRightPadding: false,
|
|
||||||
removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null,
|
|
||||||
maintainBottomViewPadding: !_resizeToAvoidBottomInset,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool extendBodyBehindMaterialBanner = false;
|
bool extendBodyBehindMaterialBanner = false;
|
||||||
// MaterialBanner set by ScaffoldMessenger
|
// MaterialBanner set by ScaffoldMessenger
|
||||||
if (_messengerMaterialBanner != null) {
|
if (_messengerMaterialBanner != null) {
|
||||||
|
@ -122,7 +122,7 @@ class _SnackBarActionState extends State<SnackBarAction> {
|
|||||||
_haveTriggeredAction = true;
|
_haveTriggeredAction = true;
|
||||||
});
|
});
|
||||||
widget.onPressed();
|
widget.onPressed();
|
||||||
Scaffold.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action);
|
ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -583,14 +583,14 @@ class _SnackBarState extends State<SnackBar> {
|
|||||||
container: true,
|
container: true,
|
||||||
liveRegion: true,
|
liveRegion: true,
|
||||||
onDismiss: () {
|
onDismiss: () {
|
||||||
Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
|
ScaffoldMessenger.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
|
||||||
},
|
},
|
||||||
child: Dismissible(
|
child: Dismissible(
|
||||||
key: const Key('dismissible'),
|
key: const Key('dismissible'),
|
||||||
direction: widget.dismissDirection,
|
direction: widget.dismissDirection,
|
||||||
resizeDuration: null,
|
resizeDuration: null,
|
||||||
onDismissed: (DismissDirection direction) {
|
onDismissed: (DismissDirection direction) {
|
||||||
Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
|
ScaffoldMessenger.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
|
||||||
},
|
},
|
||||||
child: snackBar,
|
child: snackBar,
|
||||||
),
|
),
|
||||||
|
@ -263,8 +263,10 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
// The Scaffold should assert we still have an ancestor ScaffoldMessenger in
|
// Tap SnackBarAction to dismiss.
|
||||||
// order to dismiss the SnackBar from the ScaffoldMessenger.
|
// The SnackBarAction should assert we still have an ancestor
|
||||||
|
// ScaffoldMessenger in order to dismiss the SnackBar from the
|
||||||
|
// Scaffold.
|
||||||
await tester.tap(find.text('Test'));
|
await tester.tap(find.text('Test'));
|
||||||
FlutterError.onError = oldHandler;
|
FlutterError.onError = oldHandler;
|
||||||
|
|
||||||
@ -286,11 +288,67 @@ void main() {
|
|||||||
expect(error.toStringDeep(), equalsIgnoringHashCodes(
|
expect(error.toStringDeep(), equalsIgnoringHashCodes(
|
||||||
'FlutterError\n'
|
'FlutterError\n'
|
||||||
' No ScaffoldMessenger widget found.\n'
|
' No ScaffoldMessenger widget found.\n'
|
||||||
' Scaffold widgets require a ScaffoldMessenger widget ancestor.\n'
|
' SnackBarAction widgets require a ScaffoldMessenger widget\n'
|
||||||
|
' ancestor.\n'
|
||||||
' The specific widget that could not find a ScaffoldMessenger\n'
|
' The specific widget that could not find a ScaffoldMessenger\n'
|
||||||
' ancestor was:\n'
|
' ancestor was:\n'
|
||||||
' Scaffold-[LabeledGlobalKey<ScaffoldState>#00829]\n'
|
' SnackBarAction\n'
|
||||||
' The ancestors of this widget were:\n'
|
' The ancestors of this widget were:\n'
|
||||||
|
' TextButtonTheme\n'
|
||||||
|
' Padding\n'
|
||||||
|
' Row\n'
|
||||||
|
' Padding\n'
|
||||||
|
' MediaQuery\n'
|
||||||
|
' Padding\n'
|
||||||
|
' SafeArea\n'
|
||||||
|
' FadeTransition\n'
|
||||||
|
' IconTheme\n'
|
||||||
|
' IconTheme\n'
|
||||||
|
' _InheritedCupertinoTheme\n'
|
||||||
|
' CupertinoTheme\n'
|
||||||
|
' _InheritedTheme\n'
|
||||||
|
' Theme\n'
|
||||||
|
' DefaultTextStyle\n'
|
||||||
|
' AnimatedDefaultTextStyle\n'
|
||||||
|
' _InkFeatures-[GlobalKey#00000 ink renderer]\n'
|
||||||
|
' NotificationListener<LayoutChangedNotification>\n'
|
||||||
|
' PhysicalModel\n'
|
||||||
|
' AnimatedPhysicalModel\n'
|
||||||
|
' Material\n'
|
||||||
|
' FractionalTranslation\n'
|
||||||
|
' SlideTransition\n'
|
||||||
|
' Listener\n'
|
||||||
|
' _GestureSemantics\n'
|
||||||
|
' RawGestureDetector\n'
|
||||||
|
' GestureDetector\n'
|
||||||
|
" Dismissible-[<'dismissible'>]\n"
|
||||||
|
' Semantics\n'
|
||||||
|
' Align\n'
|
||||||
|
' AnimatedBuilder\n'
|
||||||
|
' ClipRect\n'
|
||||||
|
' KeyedSubtree-[GlobalKey#00000]\n'
|
||||||
|
' _EffectiveTickerMode\n'
|
||||||
|
' TickerMode\n'
|
||||||
|
' Offstage\n'
|
||||||
|
' SizedBox\n'
|
||||||
|
' Hero\n'
|
||||||
|
' SnackBar-[#00000]\n'
|
||||||
|
' MediaQuery\n'
|
||||||
|
' LayoutId-[<_ScaffoldSlot.snackBar>]\n'
|
||||||
|
' CustomMultiChildLayout\n'
|
||||||
|
' AnimatedBuilder\n'
|
||||||
|
' DefaultTextStyle\n'
|
||||||
|
' AnimatedDefaultTextStyle\n'
|
||||||
|
' _InkFeatures-[GlobalKey#00000 ink renderer]\n'
|
||||||
|
' NotificationListener<LayoutChangedNotification>\n'
|
||||||
|
' PhysicalModel\n'
|
||||||
|
' AnimatedPhysicalModel\n'
|
||||||
|
' Material\n'
|
||||||
|
' _ScrollMetricsNotificationObserverScope\n'
|
||||||
|
' NotificationListener<ScrollMetricsNotification>\n'
|
||||||
|
' ScrollMetricsNotificationObserver\n'
|
||||||
|
' _ScaffoldScope\n'
|
||||||
|
' Scaffold-[LabeledGlobalKey<ScaffoldState>#00000]\n'
|
||||||
' MediaQuery\n'
|
' MediaQuery\n'
|
||||||
' Directionality\n'
|
' Directionality\n'
|
||||||
' [root]\n'
|
' [root]\n'
|
||||||
|
@ -8,58 +8,12 @@
|
|||||||
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart' show FlutterExceptionHandler;
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('SnackBar control test', (WidgetTester tester) async {
|
testWidgets('SnackBar control test', (WidgetTester tester) async {
|
||||||
const String helloSnackBar = 'Hello SnackBar';
|
|
||||||
const Key tapTarget = Key('tap-target');
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
key: tapTarget,
|
|
||||||
onTap: () {
|
|
||||||
Scaffold.of(context).showSnackBar(const SnackBar(
|
|
||||||
content: Text(helloSnackBar),
|
|
||||||
duration: Duration(seconds: 2),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: const SizedBox(
|
|
||||||
height: 100.0,
|
|
||||||
width: 100.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
expect(find.text(helloSnackBar), findsNothing);
|
|
||||||
await tester.tap(find.byKey(tapTarget));
|
|
||||||
expect(find.text(helloSnackBar), findsNothing);
|
|
||||||
await tester.pump(); // schedule animation
|
|
||||||
expect(find.text(helloSnackBar), findsOneWidget);
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text(helloSnackBar), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
|
|
||||||
expect(find.text(helloSnackBar), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
|
|
||||||
expect(find.text(helloSnackBar), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
|
|
||||||
expect(find.text(helloSnackBar), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text(helloSnackBar), findsOneWidget); // frame 0 of dismiss animation
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build
|
|
||||||
expect(find.text(helloSnackBar), findsNothing);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('SnackBar control test - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
const String helloSnackBar = 'Hello SnackBar';
|
const String helloSnackBar = 'Hello SnackBar';
|
||||||
const Key tapTarget = Key('tap-target');
|
const Key tapTarget = Key('tap-target');
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
@ -105,81 +59,6 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('SnackBar twice test', (WidgetTester tester) async {
|
testWidgets('SnackBar twice test', (WidgetTester tester) async {
|
||||||
int snackBarCount = 0;
|
|
||||||
const Key tapTarget = Key('tap-target');
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
key: tapTarget,
|
|
||||||
onTap: () {
|
|
||||||
snackBarCount += 1;
|
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text('bar$snackBarCount'),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: const SizedBox(
|
|
||||||
height: 100.0,
|
|
||||||
width: 100.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.tap(find.byKey(tapTarget)); // queue bar1
|
|
||||||
await tester.tap(find.byKey(tapTarget)); // queue bar2
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(); // schedule animation for bar1
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 4.50s // animation last frame; two second timer starts here
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 5.25s
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 6.00s
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 6.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 7.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('SnackBar twice test - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
int snackBarCount = 0;
|
int snackBarCount = 0;
|
||||||
const Key tapTarget = Key('tap-target');
|
const Key tapTarget = Key('tap-target');
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
@ -255,92 +134,6 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('SnackBar cancel test', (WidgetTester tester) async {
|
testWidgets('SnackBar cancel test', (WidgetTester tester) async {
|
||||||
int snackBarCount = 0;
|
|
||||||
const Key tapTarget = Key('tap-target');
|
|
||||||
late int time;
|
|
||||||
late ScaffoldFeatureController<SnackBar, SnackBarClosedReason> lastController;
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
key: tapTarget,
|
|
||||||
onTap: () {
|
|
||||||
snackBarCount += 1;
|
|
||||||
lastController = Scaffold.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text('bar$snackBarCount'),
|
|
||||||
duration: Duration(seconds: time),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: const SizedBox(
|
|
||||||
height: 100.0,
|
|
||||||
width: 100.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
time = 1000;
|
|
||||||
await tester.tap(find.byKey(tapTarget)); // queue bar1
|
|
||||||
final ScaffoldFeatureController<SnackBar, SnackBarClosedReason> firstController = lastController;
|
|
||||||
time = 2;
|
|
||||||
await tester.tap(find.byKey(tapTarget)); // queue bar2
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(); // schedule animation for bar1
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 10000)); // 12.25s
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
|
|
||||||
firstController.close(); // snackbar is manually dismissed
|
|
||||||
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 13.00s // reverse animation is scheduled
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text('bar1'), findsOneWidget);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 13.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 14.50s // animation last frame; two second timer starts here
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 15.25s
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 16.00s
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 16.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsOneWidget);
|
|
||||||
await tester.pump(const Duration(milliseconds: 750)); // 17.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
|
|
||||||
expect(find.text('bar1'), findsNothing);
|
|
||||||
expect(find.text('bar2'), findsNothing);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('SnackBar cancel test - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
int snackBarCount = 0;
|
int snackBarCount = 0;
|
||||||
const Key tapTarget = Key('tap-target');
|
const Key tapTarget = Key('tap-target');
|
||||||
late int time;
|
late int time;
|
||||||
@ -432,48 +225,6 @@ void main() {
|
|||||||
late double width;
|
late double width;
|
||||||
int snackBarCount = 0;
|
int snackBarCount = 0;
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
width = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
key: tapTarget,
|
|
||||||
onTap: () {
|
|
||||||
snackBarCount += 1;
|
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text('bar$snackBarCount'),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
dismissDirection: dismissDirection,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: const SizedBox(
|
|
||||||
height: 100.0,
|
|
||||||
width: 100.0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
|
|
||||||
await _testSnackBarDismiss(
|
|
||||||
tester: tester,
|
|
||||||
tapTarget: tapTarget,
|
|
||||||
scaffoldWidth: width,
|
|
||||||
onDismissDirectionChange: (DismissDirection dir) => dismissDirection = dir,
|
|
||||||
onDragGestureChange: () => snackBarCount = 0,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('SnackBar dismiss test - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
const Key tapTarget = Key('tap-target');
|
|
||||||
late DismissDirection dismissDirection;
|
|
||||||
late double width;
|
|
||||||
int snackBarCount = 0;
|
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: Builder(
|
body: Builder(
|
||||||
@ -511,45 +262,6 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('SnackBar cannot be tapped twice', (WidgetTester tester) async {
|
testWidgets('SnackBar cannot be tapped twice', (WidgetTester tester) async {
|
||||||
int tapCount = 0;
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
|
||||||
content: const Text('I am a snack bar.'),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'ACTION',
|
|
||||||
onPressed: () {
|
|
||||||
++tapCount;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
child: const Text('X'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
await tester.tap(find.text('X'));
|
|
||||||
await tester.pump(); // start animation
|
|
||||||
await tester.pump(const Duration(milliseconds: 750));
|
|
||||||
|
|
||||||
expect(tapCount, equals(0));
|
|
||||||
await tester.tap(find.text('ACTION'));
|
|
||||||
expect(tapCount, equals(1));
|
|
||||||
await tester.tap(find.text('ACTION'));
|
|
||||||
expect(tapCount, equals(1));
|
|
||||||
await tester.pump();
|
|
||||||
await tester.tap(find.text('ACTION'));
|
|
||||||
expect(tapCount, equals(1));
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('SnackBar cannot be tapped twice - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
int tapCount = 0;
|
int tapCount = 0;
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
@ -598,7 +310,7 @@ void main() {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text('I am a snack bar.'),
|
content: const Text('I am a snack bar.'),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
@ -640,7 +352,7 @@ void main() {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text('I am a snack bar.'),
|
content: const Text('I am a snack bar.'),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
@ -679,7 +391,7 @@ void main() {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text('I am a snack bar.'),
|
content: const Text('I am a snack bar.'),
|
||||||
duration: const Duration(seconds: 2),
|
duration: const Duration(seconds: 2),
|
||||||
@ -809,7 +521,7 @@ void main() {
|
|||||||
themeBeforeSnackBar = Theme.of(context);
|
themeBeforeSnackBar = Theme.of(context);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Builder(
|
content: Builder(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
@ -853,7 +565,7 @@ void main() {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('I am a snack bar.'),
|
content: Text('I am a snack bar.'),
|
||||||
margin: EdgeInsets.all(padding),
|
margin: EdgeInsets.all(padding),
|
||||||
@ -885,53 +597,6 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('SnackbarBehavior.floating is positioned within safe area', (WidgetTester tester) async {
|
testWidgets('SnackbarBehavior.floating is positioned within safe area', (WidgetTester tester) async {
|
||||||
const double viewPadding = 50.0;
|
|
||||||
const double floatingSnackBarDefaultBottomMargin = 10.0;
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
home: MediaQuery(
|
|
||||||
data: const MediaQueryData(
|
|
||||||
// Simulate non-safe area.
|
|
||||||
viewPadding: EdgeInsets.only(bottom: viewPadding),
|
|
||||||
),
|
|
||||||
child: Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Scaffold.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('I am a snack bar.'),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('X'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.tap(find.text('X'));
|
|
||||||
await tester.pump(); // Start animation
|
|
||||||
await tester.pump(const Duration(milliseconds: 750));
|
|
||||||
|
|
||||||
final Finder materialFinder = find.descendant(
|
|
||||||
of: find.byType(SnackBar),
|
|
||||||
matching: find.byType(Material),
|
|
||||||
);
|
|
||||||
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
|
|
||||||
expect(
|
|
||||||
snackBarBottomLeft.dy,
|
|
||||||
// Device height is 600.
|
|
||||||
600 - viewPadding - floatingSnackBarDefaultBottomMargin,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('SnackbarBehavior.floating is positioned within safe area - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
const double viewPadding = 50.0;
|
const double viewPadding = 50.0;
|
||||||
const double floatingSnackBarDefaultBottomMargin = 10.0;
|
const double floatingSnackBarDefaultBottomMargin = 10.0;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -1452,47 +1117,6 @@ void main() {
|
|||||||
testWidgets('accessible navigation behavior with action', (WidgetTester tester) async {
|
testWidgets('accessible navigation behavior with action', (WidgetTester tester) async {
|
||||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
|
||||||
home: MediaQuery(
|
|
||||||
data: const MediaQueryData(accessibleNavigation: true),
|
|
||||||
child: Scaffold(
|
|
||||||
key: scaffoldKey,
|
|
||||||
body: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
|
||||||
content: const Text('snack'),
|
|
||||||
duration: const Duration(seconds: 1),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'ACTION',
|
|
||||||
onPressed: () { },
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
child: const Text('X'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
await tester.tap(find.text('X'));
|
|
||||||
await tester.pump();
|
|
||||||
// Find action immediately
|
|
||||||
expect(find.text('ACTION'), findsOneWidget);
|
|
||||||
// Snackbar doesn't close
|
|
||||||
await tester.pump(const Duration(seconds: 10));
|
|
||||||
expect(find.text('ACTION'), findsOneWidget);
|
|
||||||
await tester.tap(find.text('ACTION'));
|
|
||||||
await tester.pump();
|
|
||||||
// Snackbar closes immediately
|
|
||||||
expect(find.text('ACTION'), findsNothing);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('accessible navigation behavior with action - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
home: MediaQuery(
|
home: MediaQuery(
|
||||||
data: const MediaQueryData(accessibleNavigation: true),
|
data: const MediaQueryData(accessibleNavigation: true),
|
||||||
@ -1537,47 +1161,6 @@ void main() {
|
|||||||
final SemanticsHandle handle = tester.ensureSemantics();
|
final SemanticsHandle handle = tester.ensureSemantics();
|
||||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
|
||||||
home: MediaQuery(
|
|
||||||
data: const MediaQueryData(accessibleNavigation: true),
|
|
||||||
child: Scaffold(
|
|
||||||
key: scaffoldKey,
|
|
||||||
body: Builder(builder: (BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
|
||||||
content: const Text('snack'),
|
|
||||||
duration: const Duration(seconds: 1),
|
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'ACTION',
|
|
||||||
onPressed: () { },
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
child: const Text('X'),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
await tester.tap(find.text('X'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(tester.getSemantics(find.text('snack')), matchesSemantics(
|
|
||||||
isLiveRegion: true,
|
|
||||||
hasDismissAction: true,
|
|
||||||
hasScrollDownAction: true,
|
|
||||||
hasScrollUpAction: true,
|
|
||||||
label: 'snack',
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
));
|
|
||||||
handle.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('contributes dismiss semantics - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
final SemanticsHandle handle = tester.ensureSemantics();
|
|
||||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
home: MediaQuery(
|
home: MediaQuery(
|
||||||
data: const MediaQueryData(accessibleNavigation: true),
|
data: const MediaQueryData(accessibleNavigation: true),
|
||||||
@ -1666,54 +1249,6 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('SnackBar handles updates to accessibleNavigation', (WidgetTester tester) async {
|
testWidgets('SnackBar handles updates to accessibleNavigation', (WidgetTester tester) async {
|
||||||
Future<void> boilerplate({ required bool accessibleNavigation }) {
|
|
||||||
return tester.pumpWidget(MaterialApp(
|
|
||||||
home: MediaQuery(
|
|
||||||
data: MediaQueryData(accessibleNavigation: accessibleNavigation),
|
|
||||||
child: Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
|
||||||
content: const Text('test'),
|
|
||||||
action: SnackBarAction(label: 'foo', onPressed: () { }),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
child: const Text('X'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
await boilerplate(accessibleNavigation: false);
|
|
||||||
expect(find.text('test'), findsNothing);
|
|
||||||
await tester.tap(find.text('X'));
|
|
||||||
await tester.pump(); // schedule animation
|
|
||||||
expect(find.text('test'), findsOneWidget);
|
|
||||||
await tester.pump(); // begin animation
|
|
||||||
await tester.pump(const Duration(milliseconds: 4750)); // 4.75s
|
|
||||||
expect(find.text('test'), findsOneWidget);
|
|
||||||
|
|
||||||
// Enabled accessible navigation
|
|
||||||
await boilerplate(accessibleNavigation: true);
|
|
||||||
|
|
||||||
await tester.pump(const Duration(milliseconds: 4000)); // 8.75s
|
|
||||||
await tester.pump();
|
|
||||||
expect(find.text('test'), findsOneWidget);
|
|
||||||
|
|
||||||
// disable accessible navigation
|
|
||||||
await boilerplate(accessibleNavigation: false);
|
|
||||||
await tester.pumpAndSettle(const Duration(milliseconds: 5750));
|
|
||||||
|
|
||||||
expect(find.text('test'), findsNothing);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('SnackBar handles updates to accessibleNavigation - ScaffoldMessenger', (WidgetTester tester) async {
|
|
||||||
Future<void> boilerplate({ required bool accessibleNavigation }) {
|
Future<void> boilerplate({ required bool accessibleNavigation }) {
|
||||||
return tester.pumpWidget(MaterialApp(
|
return tester.pumpWidget(MaterialApp(
|
||||||
home: MediaQuery(
|
home: MediaQuery(
|
||||||
@ -2127,7 +1662,7 @@ void main() {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
content: const Text('Some content'),
|
content: const Text('Some content'),
|
||||||
behavior: SnackBarBehavior.fixed,
|
behavior: SnackBarBehavior.fixed,
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
@ -2164,7 +1699,7 @@ void main() {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
content: const Text('Some content'),
|
content: const Text('Some content'),
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
@ -2260,39 +1795,6 @@ void main() {
|
|||||||
expect(find.text(secondHeader), findsOneWidget);
|
expect(find.text(secondHeader), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('SnackBars cannot be used by the Scaffold and ScaffoldMessenger at the same time', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(const MaterialApp(
|
|
||||||
home: Scaffold(),
|
|
||||||
));
|
|
||||||
|
|
||||||
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
|
|
||||||
scaffoldMessengerState.showSnackBar(SnackBar(
|
|
||||||
content: const Text('ScaffoldMessenger'),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
));
|
|
||||||
final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
|
|
||||||
scaffoldState.showSnackBar(SnackBar(
|
|
||||||
content: const Text('Scaffold'),
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
));
|
|
||||||
|
|
||||||
final List<dynamic> exceptions = <dynamic>[];
|
|
||||||
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
|
|
||||||
FlutterError.onError = (FlutterErrorDetails details) {
|
|
||||||
exceptions.add(details.exception);
|
|
||||||
};
|
|
||||||
await tester.pump();
|
|
||||||
FlutterError.onError = oldHandler;
|
|
||||||
|
|
||||||
expect(exceptions.length, 1);
|
|
||||||
final AssertionError error = exceptions.first as AssertionError;
|
|
||||||
expect(error.message, contains('Only one API should be used to manage SnackBars.'));
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('SnackBars should be shown above the bottomSheet', (WidgetTester tester) async {
|
testWidgets('SnackBars should be shown above the bottomSheet', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(const MaterialApp(
|
await tester.pumpWidget(const MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user