diff --git a/packages/stream_core_flutter/CHANGELOG.md b/packages/stream_core_flutter/CHANGELOG.md index 23d6946e..5c59059e 100644 --- a/packages/stream_core_flutter/CHANGELOG.md +++ b/packages/stream_core_flutter/CHANGELOG.md @@ -4,6 +4,9 @@ - Fixed `StreamTextInput` ignoring typed text after the widgets around it, such as `leading` and `trailing`, changed while it had focus. +- Fixed `StreamSnackbarHost` and `StreamSnackbarPopup` throwing in debug when + they mount with a snackbar already queued on their messenger, for example + after the widget holding them is rebuilt. ## 0.5.3 diff --git a/packages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar_messenger.dart b/packages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar_messenger.dart index ebb58300..2d7a2590 100644 --- a/packages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar_messenger.dart +++ b/packages/stream_core_flutter/lib/src/components/snackbar/stream_snackbar_messenger.dart @@ -734,6 +734,12 @@ class _SnackbarStageState extends State<_SnackbarStage> with SingleTickerProvide void _onControllerChanged() => _syncWithController(); + // No dependency on MediaQuery: the first read can come from initState. + bool get _accessibleNavigation { + final mediaQuery = context.getInheritedWidgetOfExactType(); + return mediaQuery?.data.accessibleNavigation ?? false; + } + void _syncWithController() { final next = widget.messenger._queue.isEmpty ? null : widget.messenger._queue.first; @@ -756,7 +762,7 @@ class _SnackbarStageState extends State<_SnackbarStage> with SingleTickerProvide _animation.value = 0; setState(() {}); if (next != null) { - if (MediaQuery.accessibleNavigationOf(context)) { + if (_accessibleNavigation) { _animation.value = 1; } else { _animation.forward(from: 0); @@ -786,7 +792,7 @@ class _SnackbarStageState extends State<_SnackbarStage> with SingleTickerProvide final messenger = widget.messenger; _exiting = true; _timer?.cancel(); - if (MediaQuery.accessibleNavigationOf(context)) { + if (_accessibleNavigation) { _animation.value = 0; } else { await _animation.reverse(); diff --git a/packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart b/packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart index 083d50dd..fc9addc5 100644 --- a/packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart +++ b/packages/stream_core_flutter/test/components/snackbar/stream_snackbar_test.dart @@ -848,6 +848,29 @@ void main() { expect(find.text('Auto-managed'), findsOneWidget); }); + + testWidgets('shows a snackbar queued before it mounts', (tester) async { + final messenger = StreamSnackbarMessenger(); + addTearDown(messenger.dispose); + + messenger.show(StreamSnackbar(message: const Text('Queued'))); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(extensions: [StreamTheme.light()]), + home: Center( + child: StreamSnackbarPopup.withState( + messenger: messenger, + child: const SizedBox(width: 200, height: 60), + ), + ), + ), + ); + await tester.pump(const Duration(milliseconds: 300)); + + expect(tester.takeException(), isNull); + expect(find.text('Queued'), findsOneWidget); + }); }); group('accessibleNavigation', () { @@ -893,6 +916,22 @@ void main() { expect(fadeOf(tester).opacity.value, equals(1.0)); }); + testWidgets('on — a snackbar queued before the host mounts skips animation', (tester) async { + tester.platformDispatcher.accessibilityFeaturesTestValue = const FakeAccessibilityFeatures( + accessibleNavigation: true, + ); + addTearDown(tester.platformDispatcher.clearAccessibilityFeaturesTestValue); + + final messenger = StreamSnackbarMessenger(); + addTearDown(messenger.dispose); + + messenger.show(StreamSnackbar(message: const Text('Queued'))); + await tester.pumpWidget(hostScaffold(messenger)); + + expect(tester.takeException(), isNull); + expect(fadeOf(tester).opacity.value, equals(1.0)); + }); + testWidgets('on — exit skips animation and resolves controller after one frame', (tester) async { tester.platformDispatcher.accessibilityFeaturesTestValue = const FakeAccessibilityFeatures( accessibleNavigation: true,