Unverified Commit bd77118e authored by MH Johnson's avatar MH Johnson Committed by GitHub

[Material 3] Add optional indicator to Navigation Rail. (#92930)

parent 7ef52267
...@@ -300,7 +300,7 @@ class NavigationDestination extends StatelessWidget { ...@@ -300,7 +300,7 @@ class NavigationDestination extends StatelessWidget {
return Stack( return Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: <Widget>[ children: <Widget>[
_NavigationIndicator( NavigationIndicator(
animation: animation, animation: animation,
color: navigationBarTheme.indicatorColor, color: navigationBarTheme.indicatorColor,
), ),
...@@ -507,20 +507,24 @@ class _NavigationDestinationInfo extends InheritedWidget { ...@@ -507,20 +507,24 @@ class _NavigationDestinationInfo extends InheritedWidget {
} }
} }
/// Selection Indicator for the Material 3 Navigation Bar component. /// Selection Indicator for the Material 3 [NavigationBar] and [NavigationRail]
/// components.
/// ///
/// When [animation] is 0, the indicator is not present. As [animation] grows /// When [animation] is 0, the indicator is not present. As [animation] grows
/// from 0 to 1, the indicator scales in on the x axis. /// from 0 to 1, the indicator scales in on the x axis.
/// ///
/// Useful in a [Stack] widget behind the icons in the Material 3 Navigation Bar /// Used in a [Stack] widget behind the icons in the Material 3 Navigation Bar
/// to illuminate the selected destination. /// to illuminate the selected destination.
class _NavigationIndicator extends StatelessWidget { class NavigationIndicator extends StatelessWidget {
/// Builds an indicator, usually used in a stack behind the icon of a /// Builds an indicator, usually used in a stack behind the icon of a
/// navigation bar destination. /// navigation bar destination.
const _NavigationIndicator({ const NavigationIndicator({
Key? key, Key? key,
required this.animation, required this.animation,
this.color, this.color,
this.width = 64,
this.height = 32,
this.borderRadius = const BorderRadius.all(Radius.circular(16)),
}) : super(key: key); }) : super(key: key);
/// Determines the scale of the indicator. /// Determines the scale of the indicator.
...@@ -534,6 +538,21 @@ class _NavigationIndicator extends StatelessWidget { ...@@ -534,6 +538,21 @@ class _NavigationIndicator extends StatelessWidget {
/// If null, defaults to [ColorScheme.secondary]. /// If null, defaults to [ColorScheme.secondary].
final Color? color; final Color? color;
/// The width of the container that holds in the indicator.
///
/// Defaults to `64`.
final double width;
/// The height of the container that holds in the indicator.
///
/// Defaults to `32`.
final double height;
/// The radius of the container that holds in the indicator.
///
/// Defaults to `BorderRadius.circular(16)`.
final BorderRadius borderRadius;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme; final ColorScheme colorScheme = Theme.of(context).colorScheme;
...@@ -573,10 +592,10 @@ class _NavigationIndicator extends StatelessWidget { ...@@ -573,10 +592,10 @@ class _NavigationIndicator extends StatelessWidget {
return FadeTransition( return FadeTransition(
opacity: fadeAnimation, opacity: fadeAnimation,
child: Container( child: Container(
width: 64.0, width: width,
height: 32.0, height: height,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16.0)), borderRadius: borderRadius,
color: color ?? colorScheme.secondary.withOpacity(.24), color: color ?? colorScheme.secondary.withOpacity(.24),
), ),
), ),
......
...@@ -10,6 +10,7 @@ import 'color_scheme.dart'; ...@@ -10,6 +10,7 @@ import 'color_scheme.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
import 'material_localizations.dart'; import 'material_localizations.dart';
import 'navigation_bar.dart';
import 'navigation_rail_theme.dart'; import 'navigation_rail_theme.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -94,6 +95,8 @@ class NavigationRail extends StatefulWidget { ...@@ -94,6 +95,8 @@ class NavigationRail extends StatefulWidget {
this.selectedIconTheme, this.selectedIconTheme,
this.minWidth, this.minWidth,
this.minExtendedWidth, this.minExtendedWidth,
this.useIndicator,
this.indicatorColor,
}) : assert(destinations != null && destinations.length >= 2), }) : assert(destinations != null && destinations.length >= 2),
assert(selectedIndex != null), assert(selectedIndex != null),
assert(0 <= selectedIndex && selectedIndex < destinations.length), assert(0 <= selectedIndex && selectedIndex < destinations.length),
...@@ -279,6 +282,21 @@ class NavigationRail extends StatefulWidget { ...@@ -279,6 +282,21 @@ class NavigationRail extends StatefulWidget {
/// The default value is 256. /// The default value is 256.
final double? minExtendedWidth; final double? minExtendedWidth;
/// If `true`, adds a rounded [NavigationIndicator] behind the selected
/// destination's icon.
///
/// The indicator's shape will be circular if [labelType] is
/// [NavigationRailLabelType.none], or a [StadiumBorder] if [labelType] is
/// [NavigationRailLabelType.all] or [NavigationRailLabelType.selected].
///
/// If `null`, defaults to [NavigationRailThemeData.useIndicator]. If that is
/// `null`, defaults to [ThemeData.useMaterial3].
final bool? useIndicator;
/// Overrides the default value of [NavigationRail]'s selection indicator color,
/// when [useIndicator] is true.
final Color? indicatorColor;
/// Returns the animation that controls the [NavigationRail.extended] state. /// Returns the animation that controls the [NavigationRail.extended] state.
/// ///
/// This can be used to synchronize animations in the [leading] or [trailing] /// This can be used to synchronize animations in the [leading] or [trailing]
...@@ -413,6 +431,8 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat ...@@ -413,6 +431,8 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat
iconTheme: widget.selectedIndex == i ? selectedIconTheme : unselectedIconTheme, iconTheme: widget.selectedIndex == i ? selectedIconTheme : unselectedIconTheme,
labelTextStyle: widget.selectedIndex == i ? selectedLabelTextStyle : unselectedLabelTextStyle, labelTextStyle: widget.selectedIndex == i ? selectedLabelTextStyle : unselectedLabelTextStyle,
padding: widget.destinations[i].padding, padding: widget.destinations[i].padding,
useIndicator: widget.useIndicator ?? navigationRailTheme.useIndicator ?? theme.useMaterial3,
indicatorColor: widget.indicatorColor ?? navigationRailTheme.indicatorColor,
onTap: () { onTap: () {
if (widget.onDestinationSelected != null) if (widget.onDestinationSelected != null)
widget.onDestinationSelected!(i); widget.onDestinationSelected!(i);
...@@ -498,6 +518,8 @@ class _RailDestination extends StatelessWidget { ...@@ -498,6 +518,8 @@ class _RailDestination extends StatelessWidget {
required this.onTap, required this.onTap,
required this.indexLabel, required this.indexLabel,
this.padding, this.padding,
required this.useIndicator,
this.indicatorColor,
}) : assert(minWidth != null), }) : assert(minWidth != null),
assert(minExtendedWidth != null), assert(minExtendedWidth != null),
assert(icon != null), assert(icon != null),
...@@ -529,11 +551,18 @@ class _RailDestination extends StatelessWidget { ...@@ -529,11 +551,18 @@ class _RailDestination extends StatelessWidget {
final VoidCallback onTap; final VoidCallback onTap;
final String indexLabel; final String indexLabel;
final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? padding;
final bool useIndicator;
final Color? indicatorColor;
final Animation<double> _positionAnimation; final Animation<double> _positionAnimation;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(
useIndicator || indicatorColor == null,
'[NavigationRail.indicatorColor] does not have an effect when [NavigationRail.useIndicator] is false',
);
final Widget themedIcon = IconTheme( final Widget themedIcon = IconTheme(
data: iconTheme, data: iconTheme,
child: icon, child: icon,
...@@ -542,13 +571,19 @@ class _RailDestination extends StatelessWidget { ...@@ -542,13 +571,19 @@ class _RailDestination extends StatelessWidget {
style: labelTextStyle, style: labelTextStyle,
child: label, child: label,
); );
final Widget content; final Widget content;
switch (labelType) { switch (labelType) {
case NavigationRailLabelType.none: case NavigationRailLabelType.none:
final Widget iconPart = SizedBox( final Widget iconPart = SizedBox(
width: minWidth, width: minWidth,
height: minWidth, height: minWidth,
child: Align( child: _AddIndicator(
addIndicator: useIndicator,
indicatorColor: indicatorColor,
isCircular: true,
indicatorAnimation: destinationAnimation,
child: themedIcon, child: themedIcon,
), ),
); );
...@@ -606,6 +641,7 @@ class _RailDestination extends StatelessWidget { ...@@ -606,6 +641,7 @@ class _RailDestination extends StatelessWidget {
final double verticalPadding = lerpDouble(_verticalDestinationPaddingNoLabel, _verticalDestinationPaddingWithLabel, appearingAnimationValue)!; final double verticalPadding = lerpDouble(_verticalDestinationPaddingNoLabel, _verticalDestinationPaddingWithLabel, appearingAnimationValue)!;
final Interval interval = selected ? const Interval(0.25, 0.75) : const Interval(0.75, 1.0); final Interval interval = selected ? const Interval(0.25, 0.75) : const Interval(0.75, 1.0);
final Animation<double> labelFadeAnimation = destinationAnimation.drive(CurveTween(curve: interval)); final Animation<double> labelFadeAnimation = destinationAnimation.drive(CurveTween(curve: interval));
content = Container( content = Container(
constraints: BoxConstraints( constraints: BoxConstraints(
minWidth: minWidth, minWidth: minWidth,
...@@ -618,7 +654,13 @@ class _RailDestination extends StatelessWidget { ...@@ -618,7 +654,13 @@ class _RailDestination extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
SizedBox(height: verticalPadding), SizedBox(height: verticalPadding),
themedIcon, _AddIndicator(
addIndicator: useIndicator,
indicatorColor: indicatorColor,
isCircular: false,
indicatorAnimation: destinationAnimation,
child: themedIcon,
),
Align( Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
heightFactor: appearingAnimationValue, heightFactor: appearingAnimationValue,
...@@ -645,7 +687,13 @@ class _RailDestination extends StatelessWidget { ...@@ -645,7 +687,13 @@ class _RailDestination extends StatelessWidget {
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
const SizedBox(height: _verticalDestinationPaddingWithLabel), const SizedBox(height: _verticalDestinationPaddingWithLabel),
themedIcon, _AddIndicator(
addIndicator: useIndicator,
indicatorColor: indicatorColor,
isCircular: false,
indicatorAnimation: destinationAnimation,
child: themedIcon,
),
styledLabel, styledLabel,
const SizedBox(height: _verticalDestinationPaddingWithLabel), const SizedBox(height: _verticalDestinationPaddingWithLabel),
], ],
...@@ -682,6 +730,62 @@ class _RailDestination extends StatelessWidget { ...@@ -682,6 +730,62 @@ class _RailDestination extends StatelessWidget {
} }
} }
/// When [addIndicator] is `true`, puts [child] center aligned in a [Stack] with
/// a [NavigationIndicator] behind it, otherwise returns [child].
///
/// When [isCircular] is true, the indicator will be a circle, otherwise the
/// indicator will be a stadium shape.
class _AddIndicator extends StatelessWidget {
const _AddIndicator({
Key? key,
required this.addIndicator,
required this.isCircular,
required this.indicatorColor,
required this.indicatorAnimation,
required this.child,
}) : super(key: key);
final bool addIndicator;
final bool isCircular;
final Color? indicatorColor;
final Animation<double> indicatorAnimation;
final Widget child;
@override
Widget build(BuildContext context) {
if (!addIndicator) {
return child;
}
late final Widget indicator;
if (isCircular) {
const double circularIndicatorDiameter = 56;
indicator = NavigationIndicator(
animation: indicatorAnimation,
height: circularIndicatorDiameter,
width: circularIndicatorDiameter,
borderRadius: BorderRadius.circular(circularIndicatorDiameter / 2),
color: indicatorColor,
);
} else {
indicator = NavigationIndicator(
animation: indicatorAnimation,
width: 56,
borderRadius: BorderRadius.circular(16),
color: indicatorColor,
);
}
return Stack(
alignment: Alignment.center,
children: <Widget>[
indicator,
child,
],
);
}
}
/// Defines the behavior of the labels of a [NavigationRail]. /// Defines the behavior of the labels of a [NavigationRail].
/// ///
/// See also: /// See also:
......
...@@ -44,6 +44,8 @@ class NavigationRailThemeData with Diagnosticable { ...@@ -44,6 +44,8 @@ class NavigationRailThemeData with Diagnosticable {
this.selectedIconTheme, this.selectedIconTheme,
this.groupAlignment, this.groupAlignment,
this.labelType, this.labelType,
this.useIndicator,
this.indicatorColor,
}); });
/// Color to be used for the [NavigationRail]'s background. /// Color to be used for the [NavigationRail]'s background.
...@@ -76,6 +78,14 @@ class NavigationRailThemeData with Diagnosticable { ...@@ -76,6 +78,14 @@ class NavigationRailThemeData with Diagnosticable {
/// [NavigationRail]. /// [NavigationRail].
final NavigationRailLabelType? labelType; final NavigationRailLabelType? labelType;
/// Whether or not the selected [NavigationRailDestination] should include a
/// [NavigationIndicator].
final bool? useIndicator;
/// Overrides the default value of [NavigationRail]'s selection indicator color,
/// when [useIndicator] is true.
final Color? indicatorColor;
/// Creates a copy of this object with the given fields replaced with the /// Creates a copy of this object with the given fields replaced with the
/// new values. /// new values.
NavigationRailThemeData copyWith({ NavigationRailThemeData copyWith({
...@@ -87,6 +97,8 @@ class NavigationRailThemeData with Diagnosticable { ...@@ -87,6 +97,8 @@ class NavigationRailThemeData with Diagnosticable {
IconThemeData? selectedIconTheme, IconThemeData? selectedIconTheme,
double? groupAlignment, double? groupAlignment,
NavigationRailLabelType? labelType, NavigationRailLabelType? labelType,
bool? useIndicator,
Color? indicatorColor,
}) { }) {
return NavigationRailThemeData( return NavigationRailThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor, backgroundColor: backgroundColor ?? this.backgroundColor,
...@@ -97,6 +109,8 @@ class NavigationRailThemeData with Diagnosticable { ...@@ -97,6 +109,8 @@ class NavigationRailThemeData with Diagnosticable {
selectedIconTheme: selectedIconTheme ?? this.selectedIconTheme, selectedIconTheme: selectedIconTheme ?? this.selectedIconTheme,
groupAlignment: groupAlignment ?? this.groupAlignment, groupAlignment: groupAlignment ?? this.groupAlignment,
labelType: labelType ?? this.labelType, labelType: labelType ?? this.labelType,
useIndicator: useIndicator ?? this.useIndicator,
indicatorColor: indicatorColor ?? this.indicatorColor,
); );
} }
...@@ -118,6 +132,8 @@ class NavigationRailThemeData with Diagnosticable { ...@@ -118,6 +132,8 @@ class NavigationRailThemeData with Diagnosticable {
selectedIconTheme: IconThemeData.lerp(a?.selectedIconTheme, b?.selectedIconTheme, t), selectedIconTheme: IconThemeData.lerp(a?.selectedIconTheme, b?.selectedIconTheme, t),
groupAlignment: lerpDouble(a?.groupAlignment, b?.groupAlignment, t), groupAlignment: lerpDouble(a?.groupAlignment, b?.groupAlignment, t),
labelType: t < 0.5 ? a?.labelType : b?.labelType, labelType: t < 0.5 ? a?.labelType : b?.labelType,
useIndicator: t < 0.5 ? a?.useIndicator : b?.useIndicator,
indicatorColor: Color.lerp(a?.indicatorColor, b?.indicatorColor, t),
); );
} }
...@@ -132,6 +148,8 @@ class NavigationRailThemeData with Diagnosticable { ...@@ -132,6 +148,8 @@ class NavigationRailThemeData with Diagnosticable {
selectedIconTheme, selectedIconTheme,
groupAlignment, groupAlignment,
labelType, labelType,
useIndicator,
indicatorColor,
); );
} }
...@@ -149,7 +167,9 @@ class NavigationRailThemeData with Diagnosticable { ...@@ -149,7 +167,9 @@ class NavigationRailThemeData with Diagnosticable {
&& other.unselectedIconTheme == unselectedIconTheme && other.unselectedIconTheme == unselectedIconTheme
&& other.selectedIconTheme == selectedIconTheme && other.selectedIconTheme == selectedIconTheme
&& other.groupAlignment == groupAlignment && other.groupAlignment == groupAlignment
&& other.labelType == labelType; && other.labelType == labelType
&& other.useIndicator == useIndicator
&& other.indicatorColor == indicatorColor;
} }
@override @override
...@@ -165,6 +185,8 @@ class NavigationRailThemeData with Diagnosticable { ...@@ -165,6 +185,8 @@ class NavigationRailThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<IconThemeData>('selectedIconTheme', selectedIconTheme, defaultValue: defaultData.selectedIconTheme)); properties.add(DiagnosticsProperty<IconThemeData>('selectedIconTheme', selectedIconTheme, defaultValue: defaultData.selectedIconTheme));
properties.add(DoubleProperty('groupAlignment', groupAlignment, defaultValue: defaultData.groupAlignment)); properties.add(DoubleProperty('groupAlignment', groupAlignment, defaultValue: defaultData.groupAlignment));
properties.add(DiagnosticsProperty<NavigationRailLabelType>('labelType', labelType, defaultValue: defaultData.labelType)); properties.add(DiagnosticsProperty<NavigationRailLabelType>('labelType', labelType, defaultValue: defaultData.labelType));
properties.add(DiagnosticsProperty<bool>('useIndicator', useIndicator, defaultValue: defaultData.useIndicator));
properties.add(ColorProperty('indicatorColor', indicatorColor, defaultValue: defaultData.indicatorColor));
} }
} }
......
...@@ -2160,6 +2160,195 @@ void main() { ...@@ -2160,6 +2160,195 @@ void main() {
expect(secondItem.padding, secondItemPadding); expect(secondItem.padding, secondItemPadding);
expect(thirdItem.padding, thirdItemPadding); expect(thirdItem.padding, thirdItemPadding);
}); });
testWidgets('NavigationRailDestination adds indicator by default when ThemeData.useMaterial3 is true', (WidgetTester tester) async {
await _pumpNavigationRail(
tester,
theme: ThemeData(useMaterial3: true),
navigationRail: NavigationRail(
labelType: NavigationRailLabelType.selected,
selectedIndex: 0,
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('Abc'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.bookmark),
label: Text('Def'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Ghi'),
),
],
),
);
expect(find.byType(NavigationIndicator), findsWidgets);
});
testWidgets('NavigationRailDestination adds indicator when useIndicator is true', (WidgetTester tester) async {
await _pumpNavigationRail(
tester,
navigationRail: NavigationRail(
useIndicator: true,
labelType: NavigationRailLabelType.selected,
selectedIndex: 0,
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('Abc'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.bookmark),
label: Text('Def'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Ghi'),
),
],
),
);
expect(find.byType(NavigationIndicator), findsWidgets);
});
testWidgets('NavigationRailDestination does not add indicator when useIndicator is false', (WidgetTester tester) async {
await _pumpNavigationRail(
tester,
navigationRail: NavigationRail(
useIndicator: false,
labelType: NavigationRailLabelType.selected,
selectedIndex: 0,
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('Abc'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.bookmark),
label: Text('Def'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Ghi'),
),
],
),
);
expect(find.byType(NavigationIndicator), findsNothing);
});
testWidgets('NavigationRailDestination adds circular indicator when no labels are present', (WidgetTester tester) async {
await _pumpNavigationRail(
tester,
navigationRail: NavigationRail(
useIndicator: true,
labelType: NavigationRailLabelType.none,
selectedIndex: 0,
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('Abc'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.bookmark),
label: Text('Def'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Ghi'),
),
],
),
);
final NavigationIndicator indicator = tester.widget<NavigationIndicator>(find.byType(NavigationIndicator).first);
expect(indicator.width, 56);
expect(indicator.height, 56);
});
testWidgets('NavigationRailDestination adds circular indicator when selected labels are present', (WidgetTester tester) async {
await _pumpNavigationRail(
tester,
navigationRail: NavigationRail(
useIndicator: true,
labelType: NavigationRailLabelType.selected,
selectedIndex: 0,
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('Abc'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.bookmark),
label: Text('Def'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Ghi'),
),
],
),
);
final NavigationIndicator indicator = tester.widget<NavigationIndicator>(find.byType(NavigationIndicator).first);
expect(indicator.width, 56);
expect(indicator.height, 32);
});
testWidgets('NavigationRailDestination adds circular indicator when all labels are present', (WidgetTester tester) async {
await _pumpNavigationRail(
tester,
navigationRail: NavigationRail(
useIndicator: true,
labelType: NavigationRailLabelType.all,
selectedIndex: 0,
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('Abc'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.bookmark),
label: Text('Def'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Ghi'),
),
],
),
);
final NavigationIndicator indicator = tester.widget<NavigationIndicator>(find.byType(NavigationIndicator).first);
expect(indicator.width, 56);
expect(indicator.height, 32);
});
} }
TestSemantics _expectedSemantics() { TestSemantics _expectedSemantics() {
...@@ -2243,9 +2432,11 @@ Future<void> _pumpNavigationRail( ...@@ -2243,9 +2432,11 @@ Future<void> _pumpNavigationRail(
WidgetTester tester, { WidgetTester tester, {
double textScaleFactor = 1.0, double textScaleFactor = 1.0,
required NavigationRail navigationRail, required NavigationRail navigationRail,
ThemeData? theme,
}) async { }) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: theme,
home: Builder( home: Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
return MediaQuery( return MediaQuery(
......
...@@ -36,6 +36,7 @@ void main() { ...@@ -36,6 +36,7 @@ void main() {
expect(_unselectedLabelStyle(tester).fontSize, 14.0); expect(_unselectedLabelStyle(tester).fontSize, 14.0);
expect(_destinationsAlign(tester).alignment, Alignment.topCenter); expect(_destinationsAlign(tester).alignment, Alignment.topCenter);
expect(_labelType(tester), NavigationRailLabelType.none); expect(_labelType(tester), NavigationRailLabelType.none);
expect(find.byType(NavigationIndicator), findsNothing);
}); });
testWidgets('NavigationRailThemeData values are used when no NavigationRail properties are specified', (WidgetTester tester) async { testWidgets('NavigationRailThemeData values are used when no NavigationRail properties are specified', (WidgetTester tester) async {
...@@ -51,6 +52,8 @@ void main() { ...@@ -51,6 +52,8 @@ void main() {
const double unselectedLabelFontSize = 11.0; const double unselectedLabelFontSize = 11.0;
const double groupAlignment = 0.0; const double groupAlignment = 0.0;
const NavigationRailLabelType labelType = NavigationRailLabelType.all; const NavigationRailLabelType labelType = NavigationRailLabelType.all;
const bool useIndicator = true;
const Color indicatorColor = Color(0x00000004);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -73,6 +76,8 @@ void main() { ...@@ -73,6 +76,8 @@ void main() {
unselectedLabelTextStyle: TextStyle(fontSize: unselectedLabelFontSize), unselectedLabelTextStyle: TextStyle(fontSize: unselectedLabelFontSize),
groupAlignment: groupAlignment, groupAlignment: groupAlignment,
labelType: labelType, labelType: labelType,
useIndicator: useIndicator,
indicatorColor: indicatorColor,
), ),
child: NavigationRail( child: NavigationRail(
selectedIndex: 0, selectedIndex: 0,
...@@ -95,6 +100,8 @@ void main() { ...@@ -95,6 +100,8 @@ void main() {
expect(_unselectedLabelStyle(tester).fontSize, unselectedLabelFontSize); expect(_unselectedLabelStyle(tester).fontSize, unselectedLabelFontSize);
expect(_destinationsAlign(tester).alignment, Alignment.center); expect(_destinationsAlign(tester).alignment, Alignment.center);
expect(_labelType(tester), labelType); expect(_labelType(tester), labelType);
expect(find.byType(NavigationIndicator), findsWidgets);
expect(_indicatorDecoration(tester)?.color, indicatorColor);
}); });
testWidgets('NavigationRail values take priority over NavigationRailThemeData values when both properties are specified', (WidgetTester tester) async { testWidgets('NavigationRail values take priority over NavigationRailThemeData values when both properties are specified', (WidgetTester tester) async {
...@@ -110,6 +117,8 @@ void main() { ...@@ -110,6 +117,8 @@ void main() {
const double unselectedLabelFontSize = 11.0; const double unselectedLabelFontSize = 11.0;
const double groupAlignment = 0.0; const double groupAlignment = 0.0;
const NavigationRailLabelType labelType = NavigationRailLabelType.all; const NavigationRailLabelType labelType = NavigationRailLabelType.all;
const bool useIndicator = true;
const Color indicatorColor = Color(0x00000004);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -132,6 +141,8 @@ void main() { ...@@ -132,6 +141,8 @@ void main() {
unselectedLabelTextStyle: TextStyle(fontSize: 7.0), unselectedLabelTextStyle: TextStyle(fontSize: 7.0),
groupAlignment: 1.0, groupAlignment: 1.0,
labelType: NavigationRailLabelType.selected, labelType: NavigationRailLabelType.selected,
useIndicator: false,
indicatorColor: Color(0x00000096),
), ),
child: NavigationRail( child: NavigationRail(
selectedIndex: 0, selectedIndex: 0,
...@@ -152,6 +163,8 @@ void main() { ...@@ -152,6 +163,8 @@ void main() {
unselectedLabelTextStyle: const TextStyle(fontSize: unselectedLabelFontSize), unselectedLabelTextStyle: const TextStyle(fontSize: unselectedLabelFontSize),
groupAlignment: groupAlignment, groupAlignment: groupAlignment,
labelType: labelType, labelType: labelType,
useIndicator: useIndicator,
indicatorColor: indicatorColor,
), ),
), ),
), ),
...@@ -170,6 +183,8 @@ void main() { ...@@ -170,6 +183,8 @@ void main() {
expect(_unselectedLabelStyle(tester).fontSize, unselectedLabelFontSize); expect(_unselectedLabelStyle(tester).fontSize, unselectedLabelFontSize);
expect(_destinationsAlign(tester).alignment, Alignment.center); expect(_destinationsAlign(tester).alignment, Alignment.center);
expect(_labelType(tester), labelType); expect(_labelType(tester), labelType);
expect(find.byType(NavigationIndicator), findsWidgets);
expect(_indicatorDecoration(tester)?.color, indicatorColor);
}); });
testWidgets('Default debugFillProperties', (WidgetTester tester) async { testWidgets('Default debugFillProperties', (WidgetTester tester) async {
...@@ -195,6 +210,8 @@ void main() { ...@@ -195,6 +210,8 @@ void main() {
unselectedLabelTextStyle: TextStyle(fontSize: 7.0), unselectedLabelTextStyle: TextStyle(fontSize: 7.0),
groupAlignment: 1.0, groupAlignment: 1.0,
labelType: NavigationRailLabelType.selected, labelType: NavigationRailLabelType.selected,
useIndicator: true,
indicatorColor: Color(0x00000096),
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties final List<String> description = builder.properties
...@@ -215,7 +232,8 @@ void main() { ...@@ -215,7 +232,8 @@ void main() {
expect(description[6], 'groupAlignment: 1.0'); expect(description[6], 'groupAlignment: 1.0');
expect(description[7], 'labelType: NavigationRailLabelType.selected'); expect(description[7], 'labelType: NavigationRailLabelType.selected');
expect(description[8], 'useIndicator: true');
expect(description[9], 'indicatorColor: Color(0x00000096)');
}); });
} }
...@@ -244,6 +262,16 @@ Material _railMaterial(WidgetTester tester) { ...@@ -244,6 +262,16 @@ Material _railMaterial(WidgetTester tester) {
); );
} }
BoxDecoration? _indicatorDecoration(WidgetTester tester) {
return tester.firstWidget<Container>(
find.descendant(
of: find.byType(NavigationIndicator),
matching: find.byType(Container),
),
).decoration as BoxDecoration?;
}
IconThemeData _selectedIconTheme(WidgetTester tester) { IconThemeData _selectedIconTheme(WidgetTester tester) {
return _iconTheme(tester, Icons.favorite); return _iconTheme(tester, Icons.favorite);
} }
......
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