Unverified Commit e3f2b88d authored by Jesse's avatar Jesse Committed by GitHub

Make NavigationRail.selectedIndex nullable (#95336)

parent b7a64b76
...@@ -98,8 +98,7 @@ class NavigationRail extends StatefulWidget { ...@@ -98,8 +98,7 @@ class NavigationRail extends StatefulWidget {
this.useIndicator, this.useIndicator,
this.indicatorColor, this.indicatorColor,
}) : assert(destinations != null && destinations.length >= 2), }) : assert(destinations != null && destinations.length >= 2),
assert(selectedIndex != null), assert(selectedIndex == null || (0 <= selectedIndex && selectedIndex < destinations.length)),
assert(0 <= selectedIndex && selectedIndex < destinations.length),
assert(elevation == null || elevation > 0), assert(elevation == null || elevation > 0),
assert(minWidth == null || minWidth > 0), assert(minWidth == null || minWidth > 0),
assert(minExtendedWidth == null || minExtendedWidth > 0), assert(minExtendedWidth == null || minExtendedWidth > 0),
...@@ -160,8 +159,8 @@ class NavigationRail extends StatefulWidget { ...@@ -160,8 +159,8 @@ class NavigationRail extends StatefulWidget {
final List<NavigationRailDestination> destinations; final List<NavigationRailDestination> destinations;
/// The index into [destinations] for the current selected /// The index into [destinations] for the current selected
/// [NavigationRailDestination]. /// [NavigationRailDestination] or null if no destination is selected.
final int selectedIndex; final int? selectedIndex;
/// Called when one of the [destinations] is selected. /// Called when one of the [destinations] is selected.
/// ///
...@@ -357,8 +356,12 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat ...@@ -357,8 +356,12 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
} }
if (widget.selectedIndex != oldWidget.selectedIndex) { if (widget.selectedIndex != oldWidget.selectedIndex) {
_destinationControllers[oldWidget.selectedIndex].reverse(); if (oldWidget.selectedIndex != null) {
_destinationControllers[widget.selectedIndex].forward(); _destinationControllers[oldWidget.selectedIndex!].reverse();
}
if (widget.selectedIndex != null) {
_destinationControllers[widget.selectedIndex!].forward();
}
return; return;
} }
} }
...@@ -462,7 +465,9 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat ...@@ -462,7 +465,9 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
)..addListener(_rebuild); )..addListener(_rebuild);
}); });
_destinationAnimations = _destinationControllers.map((AnimationController controller) => controller.view).toList(); _destinationAnimations = _destinationControllers.map((AnimationController controller) => controller.view).toList();
_destinationControllers[widget.selectedIndex].value = 1.0; if (widget.selectedIndex != null) {
_destinationControllers[widget.selectedIndex!].value = 1.0;
}
_extendedController = AnimationController( _extendedController = AnimationController(
duration: kThemeAnimationDuration, duration: kThemeAnimationDuration,
vsync: this, vsync: this,
......
...@@ -55,6 +55,19 @@ void main() { ...@@ -55,6 +55,19 @@ void main() {
expect(actualUnselectedIconTheme.fontSize, equals(unselectedIconTheme.size)); expect(actualUnselectedIconTheme.fontSize, equals(unselectedIconTheme.size));
}); });
testWidgets('No selected destination when selectedIndex is null', (WidgetTester tester) async {
await _pumpNavigationRail(
tester,
navigationRail: NavigationRail(
selectedIndex: null,
destinations: _destinations(),
),
);
final Iterable<Semantics> semantics = tester.widgetList<Semantics>(find.byType(Semantics));
expect(semantics.where((Semantics s) => s.properties.selected ?? false), isEmpty);
});
testWidgets('backgroundColor can be changed', (WidgetTester tester) async { testWidgets('backgroundColor can be changed', (WidgetTester tester) async {
await _pumpNavigationRail( await _pumpNavigationRail(
tester, tester,
...@@ -2113,6 +2126,116 @@ void main() { ...@@ -2113,6 +2126,116 @@ void main() {
expect(_labelOpacity(tester, 'Ghi'), equals(1.0)); expect(_labelOpacity(tester, 'Ghi'), equals(1.0));
}); });
testWidgets('Changing destinations animate for selectedIndex=null', (WidgetTester tester) async {
int? selectedIndex = 0;
late StateSetter stateSetter;
await tester.pumpWidget(
MaterialApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
stateSetter = setState;
return Scaffold(
body: Row(
children: <Widget>[
NavigationRail(
destinations: _destinations(),
selectedIndex: selectedIndex,
labelType: NavigationRailLabelType.selected,
),
const Expanded(
child: Text('body'),
),
],
),
);
},
),
),
);
// Unset the selected index.
stateSetter(() {
selectedIndex = null;
});
// The first destination animates out.
expect(_labelOpacity(tester, 'Abc'), equals(1.0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 25));
expect(_labelOpacity(tester, 'Abc'), equals(0.5));
await tester.pumpAndSettle();
expect(_labelOpacity(tester, 'Abc'), equals(0.0));
// Set the selected index to the first destination.
stateSetter(() {
selectedIndex = 0;
});
// The first destination animates in.
expect(_labelOpacity(tester, 'Abc'), equals(0.0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(_labelOpacity(tester, 'Abc'), equals(0.5));
await tester.pumpAndSettle();
expect(_labelOpacity(tester, 'Abc'), equals(1.0));
});
testWidgets('Changing destinations animate when selectedIndex=null during transition', (WidgetTester tester) async {
int? selectedIndex = 0;
late StateSetter stateSetter;
await tester.pumpWidget(
MaterialApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
stateSetter = setState;
return Scaffold(
body: Row(
children: <Widget>[
NavigationRail(
destinations: _destinations(),
selectedIndex: selectedIndex,
labelType: NavigationRailLabelType.selected,
),
const Expanded(
child: Text('body'),
),
],
),
);
},
),
),
);
stateSetter(() {
selectedIndex = 1;
});
await tester.pump();
await tester.pump(const Duration(milliseconds: 175));
// Interrupt while animating from index 0 to 1.
stateSetter(() {
selectedIndex = null;
});
expect(_labelOpacity(tester, 'Abc'), equals(0));
expect(_labelOpacity(tester, 'Def'), equals(1));
await tester.pump();
// Create very close to 0, but non-zero, animation value.
await tester.pump(const Duration(milliseconds: 1));
// Ensure the opacity is animated back towards 0.
expect(_labelOpacity(tester, 'Def'), lessThan(0.5));
expect(_labelOpacity(tester, 'Def'), closeTo(0.5, 0.03));
await tester.pumpAndSettle();
expect(_labelOpacity(tester, 'Abc'), equals(0.0));
expect(_labelOpacity(tester, 'Def'), equals(0.0));
});
testWidgets('Semantics - labelType=[none]', (WidgetTester tester) async { testWidgets('Semantics - labelType=[none]', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment