Skip to content

Commit

Permalink
Added option to disable [NavigationDestination]s ([NavigationBar] des…
Browse files Browse the repository at this point in the history
…tination 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 (flutter/flutter#132349 and flutter/flutter#127113)

* flutter/flutter#132359
  • Loading branch information
matheus-kirchesch authored Sep 28, 2023
1 parent f76c150 commit 9fdb167
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 12 deletions.
58 changes: 46 additions & 12 deletions packages/flutter/lib/src/material/navigation_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ class NavigationDestination extends StatelessWidget {
this.selectedIcon,
required this.label,
this.tooltip,
this.enabled = true,
});

/// The [Widget] (usually an [Icon]) that's displayed for this
Expand Down Expand Up @@ -333,11 +334,17 @@ class NavigationDestination extends StatelessWidget {
/// Defaults to null, in which case the [label] text will be used.
final String? tooltip;

/// Indicates that this destination is selectable.
///
/// Defaults to true.
final bool enabled;

@override
Widget build(BuildContext context) {
final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
const Set<MaterialState> selectedState = <MaterialState>{MaterialState.selected};
const Set<MaterialState> unselectedState = <MaterialState>{};
const Set<MaterialState> disabledState = <MaterialState>{MaterialState.disabled};

final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
final NavigationBarThemeData defaults = _defaultsFor(context);
Expand All @@ -346,15 +353,24 @@ class NavigationDestination extends StatelessWidget {
return _NavigationDestinationBuilder(
label: label,
tooltip: tooltip,
enabled: enabled,
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(
data: navigationBarTheme.iconTheme?.resolve(selectedState)
?? defaults.iconTheme!.resolve(selectedState)!,
data: enabled ? selectedIconTheme : disabledIconTheme,
child: selectedIcon ?? icon,
);
final Widget unselectedIconWidget = IconTheme.merge(
data: navigationBarTheme.iconTheme?.resolve(unselectedState)
?? defaults.iconTheme!.resolve(unselectedState)!,
data: enabled ? unselectedIconTheme : disabledIconTheme,
child: icon,
);

Expand Down Expand Up @@ -382,7 +398,15 @@ class NavigationDestination extends StatelessWidget {
?? defaults.labelTextStyle!.resolve(selectedState);
final TextStyle? effectiveUnselectedLabelTextStyle = navigationBarTheme.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(
padding: const EdgeInsets.only(top: 4),
child: MediaQuery.withClampedTextScaling(
Expand Down Expand Up @@ -416,6 +440,7 @@ class _NavigationDestinationBuilder extends StatefulWidget {
required this.buildLabel,
required this.label,
this.tooltip,
this.enabled = true,
});

/// Builds the icon for a destination in a [NavigationBar].
Expand Down Expand Up @@ -454,6 +479,11 @@ class _NavigationDestinationBuilder extends StatefulWidget {
/// Defaults to null, in which case the [label] text will be used.
final String? tooltip;

/// Indicates that this destination is selectable.
///
/// Defaults to true.
final bool enabled;

@override
State<_NavigationDestinationBuilder> createState() => _NavigationDestinationBuilderState();
}
Expand All @@ -474,7 +504,7 @@ class _NavigationDestinationBuilderState extends State<_NavigationDestinationBui
iconKey: iconKey,
labelBehavior: info.labelBehavior,
customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
onTap: info.onTap,
onTap: widget.enabled ? info.onTap : null,
child: Row(
children: <Widget>[
Expanded(
Expand Down Expand Up @@ -1319,9 +1349,11 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
return IconThemeData(
size: 24.0,
color: states.contains(MaterialState.selected)
? _colors.onSecondaryContainer
: _colors.onSurfaceVariant,
color: states.contains(MaterialState.disabled)
? _colors.onSurfaceVariant.withOpacity(0.38)
: states.contains(MaterialState.selected)
? _colors.onSecondaryContainer
: _colors.onSurfaceVariant,
);
});
}
Expand All @@ -1332,9 +1364,11 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
final TextStyle style = _textTheme.labelMedium!;
return style.apply(color: states.contains(MaterialState.selected)
? _colors.onSurface
: _colors.onSurfaceVariant
return style.apply(color: states.contains(MaterialState.disabled)
? _colors.onSurfaceVariant.withOpacity(0.38)
: states.contains(MaterialState.selected)
? _colors.onSurface
: _colors.onSurfaceVariant
);
});
}
Expand Down
37 changes: 37 additions & 0 deletions packages/flutter/test/material/navigation_bar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,43 @@ void main() {
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', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
Expand Down

0 comments on commit 9fdb167

Please sign in to comment.