Unverified Commit 888b141a authored by Zlati Pehlivanov's avatar Zlati Pehlivanov Committed by GitHub

fix: bottom navigation bar colors (#107924)

parent 29943e5f
......@@ -154,6 +154,11 @@ class BottomNavigationBar extends StatefulWidget {
/// [selectedIconTheme] and [unselectedIconTheme], and both
/// [IconThemeData.color] and [IconThemeData.size] must be set.
///
/// If [useLegacyColorScheme] is set to `false`
/// [selectedIconTheme] values will be used instead of [iconSize] and [selectedItemColor] for selected icons.
/// [unselectedIconTheme] values will be used instead of [iconSize] and [unselectedItemColor] for unselected icons.
///
///
/// If both [selectedLabelStyle].fontSize and [selectedFontSize] are set,
/// [selectedLabelStyle].fontSize will be used.
///
......@@ -193,6 +198,7 @@ class BottomNavigationBar extends StatefulWidget {
this.mouseCursor,
this.enableFeedback,
this.landscapeLayout,
this.useLegacyColorScheme = true,
}) : assert(items != null),
assert(items.length >= 2),
assert(
......@@ -381,6 +387,13 @@ class BottomNavigationBar extends StatefulWidget {
/// orientation.
final BottomNavigationBarLandscapeLayout? landscapeLayout;
/// This flag is controlling how [BottomNavigationBar] is going to use
/// the colors provided by the [selectedIconTheme], [unselectedIconTheme],
/// [selectedItemColor], [unselectedItemColor].
/// The default value is `true` as the new theming logic is a breaking change.
/// To opt-in the new theming logic set the flag to `false`
final bool useLegacyColorScheme;
@override
State<BottomNavigationBar> createState() => _BottomNavigationBarState();
}
......@@ -394,7 +407,8 @@ class _BottomNavigationTile extends StatelessWidget {
this.animation,
this.iconSize, {
this.onTap,
this.colorTween,
this.labelColorTween,
this.iconColorTween,
this.flex,
this.selected = false,
required this.selectedLabelStyle,
......@@ -420,7 +434,8 @@ class _BottomNavigationTile extends StatelessWidget {
final Animation<double> animation;
final double iconSize;
final VoidCallback? onTap;
final ColorTween? colorTween;
final ColorTween? labelColorTween;
final ColorTween? iconColorTween;
final double? flex;
final bool selected;
final IconThemeData? selectedIconTheme;
......@@ -523,7 +538,7 @@ class _BottomNavigationTile extends StatelessWidget {
child: _Tile(
layout: layout,
icon: _TileIcon(
colorTween: colorTween!,
colorTween: iconColorTween!,
animation: animation,
iconSize: iconSize,
selected: selected,
......@@ -532,7 +547,7 @@ class _BottomNavigationTile extends StatelessWidget {
unselectedIconTheme: unselectedIconTheme,
),
label: _Label(
colorTween: colorTween!,
colorTween: labelColorTween!,
animation: animation,
item: item,
selectedLabelStyle: selectedLabelStyle,
......@@ -910,6 +925,15 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
return textStyle.fontSize == null ? textStyle.copyWith(fontSize: fontSize) : textStyle;
}
// If [IconThemeData] is provided, it should be used.
// Otherwise, the [IconThemeData]'s color should be selectedItemColor
// or unselectedItemColor.
static IconThemeData _effectiveIconTheme(IconThemeData? iconTheme, Color? itemColor) {
// Prefer the iconTheme over itemColor if present.
return iconTheme ?? IconThemeData(color: itemColor);
}
List<Widget> _createTiles(BottomNavigationBarLandscapeLayout layout) {
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
assert(localizations != null);
......@@ -917,17 +941,6 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
final ThemeData themeData = Theme.of(context);
final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context);
final TextStyle effectiveSelectedLabelStyle =
_effectiveTextStyle(
widget.selectedLabelStyle ?? bottomTheme.selectedLabelStyle,
widget.selectedFontSize,
);
final TextStyle effectiveUnselectedLabelStyle =
_effectiveTextStyle(
widget.unselectedLabelStyle ?? bottomTheme.unselectedLabelStyle,
widget.unselectedFontSize,
);
final Color themeColor;
switch (themeData.brightness) {
case Brightness.light:
......@@ -938,6 +951,39 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
break;
}
final TextStyle effectiveSelectedLabelStyle =
_effectiveTextStyle(
widget.selectedLabelStyle
?? bottomTheme.selectedLabelStyle,
widget.selectedFontSize,
);
final TextStyle effectiveUnselectedLabelStyle =
_effectiveTextStyle(
widget.unselectedLabelStyle
?? bottomTheme.unselectedLabelStyle,
widget.unselectedFontSize,
);
final IconThemeData effectiveSelectedIconTheme =
_effectiveIconTheme(
widget.selectedIconTheme
?? bottomTheme.selectedIconTheme,
widget.selectedItemColor
?? bottomTheme.selectedItemColor
?? themeColor
);
final IconThemeData effectiveUnselectedIconTheme =
_effectiveIconTheme(
widget.unselectedIconTheme
?? bottomTheme.unselectedIconTheme,
widget.unselectedItemColor
?? bottomTheme.unselectedItemColor
?? themeData.unselectedWidgetColor
);
final ColorTween colorTween;
switch (_effectiveType) {
case BottomNavigationBarType.fixed:
......@@ -963,6 +1009,64 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
break;
}
final ColorTween labelColorTween;
switch (_effectiveType) {
case BottomNavigationBarType.fixed:
labelColorTween = ColorTween(
begin: effectiveUnselectedLabelStyle.color
?? widget.unselectedItemColor
?? bottomTheme.unselectedItemColor
?? themeData.unselectedWidgetColor,
end: effectiveSelectedLabelStyle.color
?? widget.selectedItemColor
?? bottomTheme.selectedItemColor
?? widget.fixedColor
?? themeColor,
);
break;
case BottomNavigationBarType.shifting:
labelColorTween = ColorTween(
begin: effectiveUnselectedLabelStyle.color
?? widget.unselectedItemColor
?? bottomTheme.unselectedItemColor
?? themeData.colorScheme.surface,
end: effectiveSelectedLabelStyle.color
?? widget.selectedItemColor
?? bottomTheme.selectedItemColor
?? themeColor,
);
break;
}
final ColorTween iconColorTween;
switch (_effectiveType) {
case BottomNavigationBarType.fixed:
iconColorTween = ColorTween(
begin: effectiveSelectedIconTheme.color
?? widget.unselectedItemColor
?? bottomTheme.unselectedItemColor
?? themeData.unselectedWidgetColor,
end: effectiveUnselectedIconTheme.color
?? widget.selectedItemColor
?? bottomTheme.selectedItemColor
?? widget.fixedColor
?? themeColor,
);
break;
case BottomNavigationBarType.shifting:
iconColorTween = ColorTween(
begin: effectiveUnselectedIconTheme.color
?? widget.unselectedItemColor
?? bottomTheme.unselectedItemColor
?? themeData.colorScheme.surface,
end: effectiveSelectedIconTheme.color
?? widget.selectedItemColor
?? bottomTheme.selectedItemColor
?? themeColor,
);
break;
}
final List<Widget> tiles = <Widget>[];
for (int i = 0; i < widget.items.length; i++) {
final Set<MaterialState> states = <MaterialState>{
......@@ -978,15 +1082,16 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
widget.items[i],
_animations[i],
widget.iconSize,
selectedIconTheme: widget.selectedIconTheme ?? bottomTheme.selectedIconTheme,
unselectedIconTheme: widget.unselectedIconTheme ?? bottomTheme.unselectedIconTheme,
selectedIconTheme: widget.useLegacyColorScheme ? widget.selectedIconTheme ?? bottomTheme.selectedIconTheme : effectiveSelectedIconTheme,
unselectedIconTheme: widget.useLegacyColorScheme ? widget.unselectedIconTheme ?? bottomTheme.unselectedIconTheme : effectiveUnselectedIconTheme,
selectedLabelStyle: effectiveSelectedLabelStyle,
unselectedLabelStyle: effectiveUnselectedLabelStyle,
enableFeedback: widget.enableFeedback ?? bottomTheme.enableFeedback ?? true,
onTap: () {
widget.onTap?.call(i);
},
colorTween: colorTween,
labelColorTween: widget.useLegacyColorScheme ? colorTween : labelColorTween,
iconColorTween: widget.useLegacyColorScheme ? colorTween : iconColorTween,
flex: _evaluateFlex(_animations[i]),
selected: i == widget.currentIndex,
showSelectedLabels: widget.showSelectedLabels ?? bottomTheme.showSelectedLabels ?? true,
......
......@@ -438,10 +438,10 @@ void main() {
});
testWidgets('Fixed BottomNavigationBar custom font size, color', (WidgetTester tester) async {
const Color primaryColor = Colors.black;
const Color unselectedWidgetColor = Colors.purple;
const Color selectedColor = Colors.blue;
const Color unselectedColor = Colors.yellow;
const Color primaryColor = Color(0xFF000000);
const Color unselectedWidgetColor = Color(0xFFD501FF);
const Color selectedColor = Color(0xFF0004FF);
const Color unselectedColor = Color(0xFFE5FF00);
const double selectedFontSize = 18.0;
const double unselectedFontSize = 14.0;
......@@ -473,6 +473,9 @@ void main() {
),
);
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style!.fontSize, selectedFontSize);
// Unselected label has a font size of 18 but is scaled down to be font size 14.
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style!.fontSize, selectedFontSize);
......@@ -482,6 +485,8 @@ void main() {
);
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style!.color, equals(selectedColor));
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style!.color, equals(unselectedColor));
expect(selectedIcon.color, equals(selectedColor));
expect(unselectedIcon.color, equals(unselectedColor));
// There should not be any [Opacity] or [FadeTransition] widgets
// since showUnselectedLabels and showSelectedLabels are true.
final Finder findOpacity = find.descendant(
......@@ -498,10 +503,10 @@ void main() {
testWidgets('Shifting BottomNavigationBar custom font size, color', (WidgetTester tester) async {
const Color primaryColor = Colors.black;
const Color unselectedWidgetColor = Colors.purple;
const Color selectedColor = Colors.blue;
const Color unselectedColor = Colors.yellow;
const Color primaryColor = Color(0xFF000000);
const Color unselectedWidgetColor = Color(0xFFD501FF);
const Color selectedColor = Color(0xFF0004FF);
const Color unselectedColor = Color(0xFFE5FF00);
const double selectedFontSize = 18.0;
const double unselectedFontSize = 14.0;
......@@ -533,9 +538,295 @@ void main() {
),
);
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style!.fontSize, selectedFontSize);
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style!.color, equals(selectedColor));
expect(_getOpacity(tester, 'Alarm'), equals(0.0));
expect(selectedIcon.color, equals(selectedColor));
expect(unselectedIcon.color, equals(unselectedColor));
});
testWidgets('label style color should override itemColor only for the label for BottomNavigationBarType.fixed', (WidgetTester tester) async {
const Color primaryColor = Color(0xFF000000);
const Color unselectedWidgetColor = Color(0xFFD501FF);
const Color selectedColor = Color(0xFF0004FF);
const Color unselectedColor = Color(0xFFE5FF00);
const Color selectedLabelColor = Color(0xFFFF9900);
const Color unselectedLabelColor = Color(0xFF92F74E);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: primaryColor,
unselectedWidgetColor: unselectedWidgetColor,
),
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedLabelStyle: const TextStyle(color: selectedLabelColor),
unselectedLabelStyle: const TextStyle(color: unselectedLabelColor),
selectedItemColor: selectedColor,
unselectedItemColor: unselectedColor,
useLegacyColorScheme: false,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
),
),
),
);
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(selectedIcon.color, equals(selectedColor));
expect(unselectedIcon.color, equals(unselectedColor));
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style!.color, equals(selectedLabelColor));
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style!.color, equals(unselectedLabelColor));
});
testWidgets('label style color should override itemColor only for the label for BottomNavigationBarType.shifting', (WidgetTester tester) async {
const Color primaryColor = Color(0xFF000000);
const Color unselectedWidgetColor = Color(0xFFD501FF);
const Color selectedColor = Color(0xFF0004FF);
const Color unselectedColor = Color(0xFFE5FF00);
const Color selectedLabelColor = Color(0xFFFF9900);
const Color unselectedLabelColor = Color(0xFF92F74E);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: primaryColor,
unselectedWidgetColor: unselectedWidgetColor,
),
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
selectedLabelStyle: const TextStyle(color: selectedLabelColor),
unselectedLabelStyle: const TextStyle(color: unselectedLabelColor),
selectedItemColor: selectedColor,
unselectedItemColor: unselectedColor,
useLegacyColorScheme: false,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
),
),
),
);
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(selectedIcon.color, equals(selectedColor));
expect(unselectedIcon.color, equals(unselectedColor));
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style!.color, equals(selectedLabelColor));
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style!.color, equals(unselectedLabelColor));
});
testWidgets('iconTheme color should override itemColor for BottomNavigationBarType.fixed', (WidgetTester tester) async {
const Color primaryColor = Color(0xFF000000);
const Color unselectedWidgetColor = Color(0xFFD501FF);
const Color selectedColor = Color(0xFF0004FF);
const Color unselectedColor = Color(0xFFE5FF00);
const Color selectedLabelColor = Color(0xFFFF9900);
const Color unselectedLabelColor = Color(0xFF92F74E);
const Color selectedIconThemeColor = Color(0xFF1E7723);
const Color unselectedIconThemeColor = Color(0xFF009688);
const IconThemeData selectedIconTheme = IconThemeData(size: 20, color: selectedIconThemeColor);
const IconThemeData unselectedIconTheme = IconThemeData(size: 18, color: unselectedIconThemeColor);
const TextStyle selectedTextStyle = TextStyle(fontSize: 18.0, color: selectedLabelColor);
const TextStyle unselectedTextStyle = TextStyle(fontSize: 18.0, color: unselectedLabelColor);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: primaryColor,
unselectedWidgetColor: unselectedWidgetColor,
),
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedLabelStyle: selectedTextStyle,
unselectedLabelStyle: unselectedTextStyle,
selectedIconTheme: selectedIconTheme,
unselectedIconTheme: unselectedIconTheme,
selectedItemColor: selectedColor,
unselectedItemColor: unselectedColor,
useLegacyColorScheme: false,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
),
),
),
);
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(selectedIcon.color, equals(selectedIconThemeColor));
expect(unselectedIcon.color, equals(unselectedIconThemeColor));
});
testWidgets('iconTheme color should override itemColor for BottomNavigationBarType.shifted', (WidgetTester tester) async {
const Color primaryColor = Color(0xFF000000);
const Color unselectedWidgetColor = Color(0xFFD501FF);
const Color selectedLabelColor = Color(0xFFFF9900);
const Color unselectedLabelColor = Color(0xFF92F74E);
const Color selectedIconThemeColor = Color(0xFF1E7723);
const Color unselectedIconThemeColor = Color(0xFF009688);
const IconThemeData selectedIconTheme = IconThemeData(size: 20, color: selectedIconThemeColor);
const IconThemeData unselectedIconTheme = IconThemeData(size: 18, color: unselectedIconThemeColor);
const TextStyle selectedTextStyle = TextStyle(fontSize: 18.0, color: selectedLabelColor);
const TextStyle unselectedTextStyle = TextStyle(fontSize: 18.0, color: unselectedLabelColor);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: primaryColor,
unselectedWidgetColor: unselectedWidgetColor,
),
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
selectedLabelStyle: selectedTextStyle,
unselectedLabelStyle: unselectedTextStyle,
selectedIconTheme: selectedIconTheme,
unselectedIconTheme: unselectedIconTheme,
useLegacyColorScheme: false,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
),
),
),
);
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(selectedIcon.color, equals(selectedIconThemeColor));
expect(unselectedIcon.color, equals(unselectedIconThemeColor));
});
testWidgets('iconTheme color should override itemColor color for BottomNavigationBarType.fixed', (WidgetTester tester) async {
const Color primaryColor = Color(0xFF000000);
const Color unselectedWidgetColor = Color(0xFFD501FF);
const Color selectedIconThemeColor = Color(0xFF1E7723);
const Color unselectedIconThemeColor = Color(0xFF009688);
const IconThemeData selectedIconTheme = IconThemeData(size: 20, color: selectedIconThemeColor);
const IconThemeData unselectedIconTheme = IconThemeData(size: 18, color: unselectedIconThemeColor);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: primaryColor,
unselectedWidgetColor: unselectedWidgetColor,
),
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedIconTheme: selectedIconTheme,
unselectedIconTheme: unselectedIconTheme,
useLegacyColorScheme: false,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
),
),
),
);
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(selectedIcon.color, equals(selectedIconThemeColor));
expect(unselectedIcon.color, equals(unselectedIconThemeColor));
});
testWidgets('iconTheme color should override itemColor for BottomNavigationBarType.shifted', (WidgetTester tester) async {
const Color primaryColor = Color(0xFF000000);
const Color unselectedWidgetColor = Color(0xFFD501FF);
const Color selectedColor = Color(0xFF0004FF);
const Color unselectedColor = Color(0xFFE5FF00);
const Color selectedIconThemeColor = Color(0xFF1E7723);
const Color unselectedIconThemeColor = Color(0xFF009688);
const IconThemeData selectedIconTheme = IconThemeData(size: 20, color: selectedIconThemeColor);
const IconThemeData unselectedIconTheme = IconThemeData(size: 18, color: unselectedIconThemeColor);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
primaryColor: primaryColor,
unselectedWidgetColor: unselectedWidgetColor,
),
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
selectedIconTheme: selectedIconTheme,
unselectedIconTheme: unselectedIconTheme,
selectedItemColor: selectedColor,
unselectedItemColor: unselectedColor,
useLegacyColorScheme: false,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
),
),
),
);
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(selectedIcon.color, equals(selectedIconThemeColor));
expect(unselectedIcon.color, equals(unselectedIconThemeColor));
});
testWidgets('Fixed BottomNavigationBar can hide unselected labels', (WidgetTester tester) async {
......
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