Unverified Commit 9fdb1671 authored by Matheus Kirchesch's avatar Matheus Kirchesch Committed by GitHub

Added option to disable [NavigationDestination]s ([NavigationBar] destination widget) (#132361)

This PR adds a new option in the NavigationDestination api (the destination widget for the NavigationBar) allowing it to be disabled.

As the issue states this PR is the NavigationBar's version of these two PRs (https://github.com/flutter/flutter/pull/132349 and https://github.com/flutter/flutter/pull/127113)

* https://github.com/flutter/flutter/issues/132359
parent f76c150c
...@@ -295,6 +295,7 @@ class NavigationDestination extends StatelessWidget { ...@@ -295,6 +295,7 @@ class NavigationDestination extends StatelessWidget {
this.selectedIcon, this.selectedIcon,
required this.label, required this.label,
this.tooltip, this.tooltip,
this.enabled = true,
}); });
/// The [Widget] (usually an [Icon]) that's displayed for this /// The [Widget] (usually an [Icon]) that's displayed for this
...@@ -333,11 +334,17 @@ class NavigationDestination extends StatelessWidget { ...@@ -333,11 +334,17 @@ class NavigationDestination extends StatelessWidget {
/// Defaults to null, in which case the [label] text will be used. /// Defaults to null, in which case the [label] text will be used.
final String? tooltip; final String? tooltip;
/// Indicates that this destination is selectable.
///
/// Defaults to true.
final bool enabled;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context); final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
const Set<MaterialState> selectedState = <MaterialState>{MaterialState.selected}; const Set<MaterialState> selectedState = <MaterialState>{MaterialState.selected};
const Set<MaterialState> unselectedState = <MaterialState>{}; const Set<MaterialState> unselectedState = <MaterialState>{};
const Set<MaterialState> disabledState = <MaterialState>{MaterialState.disabled};
final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context); final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
final NavigationBarThemeData defaults = _defaultsFor(context); final NavigationBarThemeData defaults = _defaultsFor(context);
...@@ -346,15 +353,24 @@ class NavigationDestination extends StatelessWidget { ...@@ -346,15 +353,24 @@ class NavigationDestination extends StatelessWidget {
return _NavigationDestinationBuilder( return _NavigationDestinationBuilder(
label: label, label: label,
tooltip: tooltip, tooltip: tooltip,
enabled: enabled,
buildIcon: (BuildContext context) { buildIcon: (BuildContext context) {
final IconThemeData selectedIconTheme =
navigationBarTheme.iconTheme?.resolve(selectedState)
?? defaults.iconTheme!.resolve(selectedState)!;
final IconThemeData unselectedIconTheme =
navigationBarTheme.iconTheme?.resolve(unselectedState)
?? defaults.iconTheme!.resolve(unselectedState)!;
final IconThemeData disabledIconTheme =
navigationBarTheme.iconTheme?.resolve(disabledState)
?? defaults.iconTheme!.resolve(disabledState)!;
final Widget selectedIconWidget = IconTheme.merge( final Widget selectedIconWidget = IconTheme.merge(
data: navigationBarTheme.iconTheme?.resolve(selectedState) data: enabled ? selectedIconTheme : disabledIconTheme,
?? defaults.iconTheme!.resolve(selectedState)!,
child: selectedIcon ?? icon, child: selectedIcon ?? icon,
); );
final Widget unselectedIconWidget = IconTheme.merge( final Widget unselectedIconWidget = IconTheme.merge(
data: navigationBarTheme.iconTheme?.resolve(unselectedState) data: enabled ? unselectedIconTheme : disabledIconTheme,
?? defaults.iconTheme!.resolve(unselectedState)!,
child: icon, child: icon,
); );
...@@ -382,7 +398,15 @@ class NavigationDestination extends StatelessWidget { ...@@ -382,7 +398,15 @@ class NavigationDestination extends StatelessWidget {
?? defaults.labelTextStyle!.resolve(selectedState); ?? defaults.labelTextStyle!.resolve(selectedState);
final TextStyle? effectiveUnselectedLabelTextStyle = navigationBarTheme.labelTextStyle?.resolve(unselectedState) final TextStyle? effectiveUnselectedLabelTextStyle = navigationBarTheme.labelTextStyle?.resolve(unselectedState)
?? defaults.labelTextStyle!.resolve(unselectedState); ?? defaults.labelTextStyle!.resolve(unselectedState);
final TextStyle? textStyle = _isForwardOrCompleted(animation) ? effectiveSelectedLabelTextStyle : effectiveUnselectedLabelTextStyle; final TextStyle? effectiveDisabledLabelTextStyle = navigationBarTheme.labelTextStyle?.resolve(disabledState)
?? defaults.labelTextStyle!.resolve(disabledState);
final TextStyle? textStyle = enabled
? _isForwardOrCompleted(animation)
? effectiveSelectedLabelTextStyle
: effectiveUnselectedLabelTextStyle
: effectiveDisabledLabelTextStyle;
return Padding( return Padding(
padding: const EdgeInsets.only(top: 4), padding: const EdgeInsets.only(top: 4),
child: MediaQuery.withClampedTextScaling( child: MediaQuery.withClampedTextScaling(
...@@ -416,6 +440,7 @@ class _NavigationDestinationBuilder extends StatefulWidget { ...@@ -416,6 +440,7 @@ class _NavigationDestinationBuilder extends StatefulWidget {
required this.buildLabel, required this.buildLabel,
required this.label, required this.label,
this.tooltip, this.tooltip,
this.enabled = true,
}); });
/// Builds the icon for a destination in a [NavigationBar]. /// Builds the icon for a destination in a [NavigationBar].
...@@ -454,6 +479,11 @@ class _NavigationDestinationBuilder extends StatefulWidget { ...@@ -454,6 +479,11 @@ class _NavigationDestinationBuilder extends StatefulWidget {
/// Defaults to null, in which case the [label] text will be used. /// Defaults to null, in which case the [label] text will be used.
final String? tooltip; final String? tooltip;
/// Indicates that this destination is selectable.
///
/// Defaults to true.
final bool enabled;
@override @override
State<_NavigationDestinationBuilder> createState() => _NavigationDestinationBuilderState(); State<_NavigationDestinationBuilder> createState() => _NavigationDestinationBuilderState();
} }
...@@ -474,7 +504,7 @@ class _NavigationDestinationBuilderState extends State<_NavigationDestinationBui ...@@ -474,7 +504,7 @@ class _NavigationDestinationBuilderState extends State<_NavigationDestinationBui
iconKey: iconKey, iconKey: iconKey,
labelBehavior: info.labelBehavior, labelBehavior: info.labelBehavior,
customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape, customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
onTap: info.onTap, onTap: widget.enabled ? info.onTap : null,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
...@@ -1319,7 +1349,9 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData { ...@@ -1319,7 +1349,9 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) { return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
return IconThemeData( return IconThemeData(
size: 24.0, size: 24.0,
color: states.contains(MaterialState.selected) color: states.contains(MaterialState.disabled)
? _colors.onSurfaceVariant.withOpacity(0.38)
: states.contains(MaterialState.selected)
? _colors.onSecondaryContainer ? _colors.onSecondaryContainer
: _colors.onSurfaceVariant, : _colors.onSurfaceVariant,
); );
...@@ -1332,7 +1364,9 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData { ...@@ -1332,7 +1364,9 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
@override MaterialStateProperty<TextStyle?>? get labelTextStyle { @override MaterialStateProperty<TextStyle?>? get labelTextStyle {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) { return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
final TextStyle style = _textTheme.labelMedium!; final TextStyle style = _textTheme.labelMedium!;
return style.apply(color: states.contains(MaterialState.selected) return style.apply(color: states.contains(MaterialState.disabled)
? _colors.onSurfaceVariant.withOpacity(0.38)
: states.contains(MaterialState.selected)
? _colors.onSurface ? _colors.onSurface
: _colors.onSurfaceVariant : _colors.onSurfaceVariant
); );
......
...@@ -977,6 +977,43 @@ void main() { ...@@ -977,6 +977,43 @@ void main() {
expect(_getIndicatorDecoration(tester)?.shape, shape); expect(_getIndicatorDecoration(tester)?.shape, shape);
}); });
testWidgetsWithLeakTracking('Destinations respect their disabled state', (WidgetTester tester) async {
int selectedIndex = 0;
await tester.pumpWidget(
_buildWidget(
NavigationBar(
destinations: const <Widget>[
NavigationDestination(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
NavigationDestination(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
NavigationDestination(
icon: Icon(Icons.bookmark),
label: 'Bookmark',
enabled: false,
),
],
onDestinationSelected: (int i) => selectedIndex = i,
selectedIndex: selectedIndex,
),
)
);
await tester.tap(find.text('AC'));
expect(selectedIndex, 0);
await tester.tap(find.text('Alarm'));
expect(selectedIndex, 1);
await tester.tap(find.text('Bookmark'));
expect(selectedIndex, 1);
});
group('Material 2', () { group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2 // These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests // support is deprecated and the APIs are removed, these tests
......
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