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

[Text Scaling][Material] Update BottomNavigationBar to show tooltips on long press. (#59127)

parent 6f0cfc95
......@@ -58,15 +58,15 @@ class CupertinoNavigationDemo extends StatelessWidget {
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
title: Text('Home'),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.conversation_bubble),
title: Text('Support'),
label: 'Support',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.profile_circled),
title: Text('Profile'),
label: 'Profile',
),
],
),
......
......@@ -19,7 +19,7 @@ class NavigationIconView {
item = BottomNavigationBarItem(
icon: icon,
activeIcon: activeIcon,
title: Text(title),
label: title,
backgroundColor: color,
),
controller = AnimationController(
......
......@@ -258,6 +258,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
child: Center(child: active ? item.activeIcon : item.icon),
),
if (item.title != null) item.title,
if (item.label != null) Text(item.label),
];
}
......
......@@ -19,6 +19,7 @@ import 'material.dart';
import 'material_localizations.dart';
import 'text_theme.dart';
import 'theme.dart';
import 'tooltip.dart';
/// Defines the layout and behavior of a [BottomNavigationBar].
///
......@@ -112,15 +113,15 @@ enum BottomNavigationBarType {
/// items: const <BottomNavigationBarItem>[
/// BottomNavigationBarItem(
/// icon: Icon(Icons.home),
/// title: Text('Home'),
/// label: 'Home',
/// ),
/// BottomNavigationBarItem(
/// icon: Icon(Icons.business),
/// title: Text('Business'),
/// label: 'Business',
/// ),
/// BottomNavigationBarItem(
/// icon: Icon(Icons.school),
/// title: Text('School'),
/// label: 'School',
/// ),
/// ],
/// currentIndex: _selectedIndex,
......@@ -194,8 +195,9 @@ class BottomNavigationBar extends StatefulWidget {
}) : assert(items != null),
assert(items.length >= 2),
assert(
items.every((BottomNavigationBarItem item) => item.title != null) == true,
'Every item must have a non-null title',
items.every((BottomNavigationBarItem item) => item.title != null) ||
items.every((BottomNavigationBarItem item) => item.label != null),
'Every item must have a non-null title or label',
),
assert(0 <= currentIndex && currentIndex < items.length),
assert(elevation == null || elevation >= 0.0),
......@@ -458,52 +460,65 @@ class _BottomNavigationTile extends StatelessWidget {
break;
}
return Expanded(
flex: size,
child: Semantics(
container: true,
selected: selected,
child: Stack(
Widget result = InkResponse(
onTap: onTap,
mouseCursor: mouseCursor,
child: Padding(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
InkResponse(
onTap: onTap,
mouseCursor: mouseCursor,
child: Padding(
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_TileIcon(
colorTween: colorTween,
animation: animation,
iconSize: iconSize,
selected: selected,
item: item,
selectedIconTheme: selectedIconTheme ?? bottomTheme.selectedIconTheme,
unselectedIconTheme: unselectedIconTheme ?? bottomTheme.unselectedIconTheme,
),
_Label(
colorTween: colorTween,
animation: animation,
item: item,
selectedLabelStyle: selectedLabelStyle ?? bottomTheme.selectedLabelStyle,
unselectedLabelStyle: unselectedLabelStyle ?? bottomTheme.unselectedLabelStyle,
showSelectedLabels: showSelectedLabels ?? bottomTheme.showUnselectedLabels,
showUnselectedLabels: showUnselectedLabels ?? bottomTheme.showUnselectedLabels,
),
],
),
),
_TileIcon(
colorTween: colorTween,
animation: animation,
iconSize: iconSize,
selected: selected,
item: item,
selectedIconTheme: selectedIconTheme ?? bottomTheme.selectedIconTheme,
unselectedIconTheme: unselectedIconTheme ?? bottomTheme.unselectedIconTheme,
),
Semantics(
label: indexLabel,
_Label(
colorTween: colorTween,
animation: animation,
item: item,
selectedLabelStyle: selectedLabelStyle ?? bottomTheme.selectedLabelStyle,
unselectedLabelStyle: unselectedLabelStyle ?? bottomTheme.unselectedLabelStyle,
showSelectedLabels: showSelectedLabels ?? bottomTheme.showUnselectedLabels,
showUnselectedLabels: showUnselectedLabels ?? bottomTheme.showUnselectedLabels,
),
],
),
),
);
if (item.label != null) {
result = Tooltip(
message: item.label,
preferBelow: false,
verticalOffset: selectedIconSize + selectedFontSize,
child: result,
);
}
result = Semantics(
selected: selected,
container: true,
child: Stack(
children: <Widget>[
result,
Semantics(
label: indexLabel,
),
],
),
);
return Expanded(
flex: size,
child: result,
);
}
}
......@@ -611,7 +626,7 @@ class _Label extends StatelessWidget {
),
),
alignment: Alignment.bottomCenter,
child: item.title,
child: item.title ?? Text(item.label),
),
);
......@@ -638,11 +653,25 @@ class _Label extends StatelessWidget {
);
}
return Align(
text = Align(
alignment: Alignment.bottomCenter,
heightFactor: 1.0,
child: Container(child: text),
);
if (item.label != null) {
// Do not grow text in bottom navigation bar when we can show a tooltip
// instead.
final MediaQueryData mediaQueryData = MediaQuery.of(context);
text = MediaQuery(
data: mediaQueryData.copyWith(
textScaleFactor: math.min(1.0, mediaQueryData.textScaleFactor),
),
child: text,
);
}
return text;
}
}
......@@ -893,6 +922,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
assert(debugCheckHasDirectionality(context));
assert(debugCheckHasMaterialLocalizations(context));
assert(debugCheckHasMediaQuery(context));
assert(Overlay.of(context, debugRequiredFor: widget) != null);
final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context);
......
......@@ -26,10 +26,16 @@ class BottomNavigationBarItem {
/// The argument [icon] should not be null and the argument [title] should not be null when used in a Material Design's [BottomNavigationBar].
const BottomNavigationBarItem({
@required this.icon,
@Deprecated(
'Use "label" instead, as it allows for an improved text-scaling experience. '
'This feature was deprecated after v1.19.0.'
)
this.title,
this.label,
Widget activeIcon,
this.backgroundColor,
}) : activeIcon = activeIcon ?? icon,
assert(label == null || title == null),
assert(icon != null);
/// The icon of the item.
......@@ -63,8 +69,21 @@ class BottomNavigationBarItem {
final Widget activeIcon;
/// The title of the item. If the title is not provided only the icon will be shown when not used in a Material Design [BottomNavigationBar].
///
/// This field is deprecated, use [label] instead.
@Deprecated(
'Use "label" instead, as it allows for an improved text-scaling experience. '
'This feature was deprecated after v1.19.0.'
)
final Widget title;
/// The text label for this [BottomNavigationBarItem].
///
/// This will be used to create a [Text] widget to put in the bottom navigation bar,
/// and in Material Design [BottomNavigationBar]s, this will be used to display
/// a tooltip on long press of an item in the [BottomNavigationBar].
final String label;
/// The color of the background radial animation for material [BottomNavigationBar].
///
/// If the navigation bar's type is [BottomNavigationBarType.shifting], then
......
......@@ -195,11 +195,11 @@ class PageStorageBucket {
/// items: <BottomNavigationBarItem>[
/// BottomNavigationBarItem(
/// icon: Icon(Icons.home),
/// title: Text('page 1'),
/// label: 'page 1',
/// ),
/// BottomNavigationBarItem(
/// icon: Icon(Icons.settings),
/// title: Text('page2'),
/// label: 'page2',
/// ),
/// ],
/// ),
......
......@@ -78,6 +78,29 @@ void main() {
expect(actualActive.text.style.color, const Color(0xFF123456));
});
testWidgets('BottomNavigationBar.label will create a text widget', (WidgetTester tester) async {
await pumpWidgetWithBoilerplate(tester, MediaQuery(
data: const MediaQueryData(),
child: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: ImageIcon(TestImageProvider(24, 24)),
label: 'Tab 1',
),
BottomNavigationBarItem(
icon: ImageIcon(TestImageProvider(24, 24)),
label: 'Tab 2',
),
],
currentIndex: 1,
),
));
expect(find.text('Tab 1'), findsOneWidget);
expect(find.text('Tab 2'), findsOneWidget);
});
testWidgets('Active and inactive colors dark mode', (WidgetTester tester) async {
const CupertinoDynamicColor dynamicActiveColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFF000000),
......
......@@ -952,7 +952,6 @@ void main() {
expect(builderIconSize, 12.0);
});
testWidgets('BottomNavigationBar responds to textScaleFactor', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
......@@ -1026,6 +1025,133 @@ void main() {
expect(box.size.height, equals(66.0));
});
testWidgets('BottomNavigationBar does not grow with textScaleFactor when labels are provided', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'A',
icon: Icon(Icons.ac_unit),
),
BottomNavigationBarItem(
label: 'B',
icon: Icon(Icons.battery_alert),
),
],
),
),
),
);
final RenderBox defaultBox = tester.renderObject(find.byType(BottomNavigationBar));
expect(defaultBox.size.height, equals(kBottomNavigationBarHeight));
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'A',
icon: Icon(Icons.ac_unit),
),
BottomNavigationBarItem(
label: 'B',
icon: Icon(Icons.battery_alert),
),
],
),
),
),
);
final RenderBox shiftingBox = tester.renderObject(find.byType(BottomNavigationBar));
expect(shiftingBox.size.height, equals(kBottomNavigationBarHeight));
await tester.pumpWidget(
MaterialApp(
home: MediaQuery(
data: const MediaQueryData(textScaleFactor: 2.0),
child: Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'A',
icon: Icon(Icons.ac_unit),
),
BottomNavigationBarItem(
label: 'B',
icon: Icon(Icons.battery_alert),
),
],
),
),
),
),
);
final RenderBox box = tester.renderObject(find.byType(BottomNavigationBar));
expect(box.size.height, equals(kBottomNavigationBarHeight));
});
testWidgets('BottomNavigationBar shows tool tips with text scaling on long press when labels are provided', (WidgetTester tester) async {
const String label = 'Foo';
Widget buildApp({ double textScaleFactor }) {
return MediaQuery(
data: MediaQueryData(textScaleFactor: textScaleFactor),
child: Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
child: Directionality(
textDirection: TextDirection.ltr,
child: Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: label,
icon: Icon(Icons.ac_unit),
),
BottomNavigationBarItem(
label: 'B',
icon: Icon(Icons.battery_alert),
),
],
),
);
}
);
},
),
),
),
);
}
await tester.pumpWidget(buildApp(textScaleFactor: 1.0));
expect(find.text(label), findsOneWidget);
await tester.longPress(find.text(label));
expect(find.text(label), findsNWidgets(2));
expect(tester.getSize(find.text(label).last), equals(const Size(42.0, 14.0)));
await tester.pumpAndSettle(const Duration(seconds: 2));
await tester.pumpWidget(buildApp(textScaleFactor: 4.0));
expect(find.text(label), findsOneWidget);
await tester.longPress(find.text(label));
expect(tester.getSize(find.text(label).last), equals(const Size(168.0, 56.0)));
});
testWidgets('BottomNavigationBar limits width of tiles with long titles', (WidgetTester tester) async {
final Text longTextA = Text(''.padLeft(100, 'A'));
final Text longTextB = Text(''.padLeft(100, 'B'));
......@@ -1639,7 +1765,7 @@ void main() {
await gesture.addPointer(location: tester.getCenter(find.text('AC')));
addTearDown(gesture.removePointer);
await tester.pump();
await tester.pumpAndSettle();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
......@@ -1666,19 +1792,21 @@ void main() {
Widget boilerplate({ Widget bottomNavigationBar, @required TextDirection textDirection }) {
assert(textDirection != null);
return Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
child: Directionality(
textDirection: textDirection,
child: MediaQuery(
data: const MediaQueryData(),
child: Material(
child: Scaffold(
bottomNavigationBar: bottomNavigationBar,
return MaterialApp(
home: Localizations(
locale: const Locale('en', 'US'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
child: Directionality(
textDirection: textDirection,
child: MediaQuery(
data: const MediaQueryData(),
child: Material(
child: Scaffold(
bottomNavigationBar: bottomNavigationBar,
),
),
),
),
......
......@@ -1117,17 +1117,25 @@ void main() {
Scaffold(
resizeToAvoidBottomInset: false,
body: const Placeholder(),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text('test'),
),
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text('test'),
),
],
bottomNavigationBar: Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
builder: (BuildContext context) {
return BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text('test'),
),
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text('test'),
),
],
);
},
);
},
),
),
);
......
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