Skip to content

Commit 9fdb167

Browse files
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 (flutter/flutter#132349 and flutter/flutter#127113) * flutter/flutter#132359
1 parent f76c150 commit 9fdb167

File tree

2 files changed

+83
-12
lines changed

2 files changed

+83
-12
lines changed

packages/flutter/lib/src/material/navigation_bar.dart

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ class NavigationDestination extends StatelessWidget {
295295
this.selectedIcon,
296296
required this.label,
297297
this.tooltip,
298+
this.enabled = true,
298299
});
299300

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

337+
/// Indicates that this destination is selectable.
338+
///
339+
/// Defaults to true.
340+
final bool enabled;
341+
336342
@override
337343
Widget build(BuildContext context) {
338344
final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
339345
const Set<MaterialState> selectedState = <MaterialState>{MaterialState.selected};
340346
const Set<MaterialState> unselectedState = <MaterialState>{};
347+
const Set<MaterialState> disabledState = <MaterialState>{MaterialState.disabled};
341348

342349
final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
343350
final NavigationBarThemeData defaults = _defaultsFor(context);
@@ -346,15 +353,24 @@ class NavigationDestination extends StatelessWidget {
346353
return _NavigationDestinationBuilder(
347354
label: label,
348355
tooltip: tooltip,
356+
enabled: enabled,
349357
buildIcon: (BuildContext context) {
358+
final IconThemeData selectedIconTheme =
359+
navigationBarTheme.iconTheme?.resolve(selectedState)
360+
?? defaults.iconTheme!.resolve(selectedState)!;
361+
final IconThemeData unselectedIconTheme =
362+
navigationBarTheme.iconTheme?.resolve(unselectedState)
363+
?? defaults.iconTheme!.resolve(unselectedState)!;
364+
final IconThemeData disabledIconTheme =
365+
navigationBarTheme.iconTheme?.resolve(disabledState)
366+
?? defaults.iconTheme!.resolve(disabledState)!;
367+
350368
final Widget selectedIconWidget = IconTheme.merge(
351-
data: navigationBarTheme.iconTheme?.resolve(selectedState)
352-
?? defaults.iconTheme!.resolve(selectedState)!,
369+
data: enabled ? selectedIconTheme : disabledIconTheme,
353370
child: selectedIcon ?? icon,
354371
);
355372
final Widget unselectedIconWidget = IconTheme.merge(
356-
data: navigationBarTheme.iconTheme?.resolve(unselectedState)
357-
?? defaults.iconTheme!.resolve(unselectedState)!,
373+
data: enabled ? unselectedIconTheme : disabledIconTheme,
358374
child: icon,
359375
);
360376

@@ -382,7 +398,15 @@ class NavigationDestination extends StatelessWidget {
382398
?? defaults.labelTextStyle!.resolve(selectedState);
383399
final TextStyle? effectiveUnselectedLabelTextStyle = navigationBarTheme.labelTextStyle?.resolve(unselectedState)
384400
?? defaults.labelTextStyle!.resolve(unselectedState);
385-
final TextStyle? textStyle = _isForwardOrCompleted(animation) ? effectiveSelectedLabelTextStyle : effectiveUnselectedLabelTextStyle;
401+
final TextStyle? effectiveDisabledLabelTextStyle = navigationBarTheme.labelTextStyle?.resolve(disabledState)
402+
?? defaults.labelTextStyle!.resolve(disabledState);
403+
404+
final TextStyle? textStyle = enabled
405+
? _isForwardOrCompleted(animation)
406+
? effectiveSelectedLabelTextStyle
407+
: effectiveUnselectedLabelTextStyle
408+
: effectiveDisabledLabelTextStyle;
409+
386410
return Padding(
387411
padding: const EdgeInsets.only(top: 4),
388412
child: MediaQuery.withClampedTextScaling(
@@ -416,6 +440,7 @@ class _NavigationDestinationBuilder extends StatefulWidget {
416440
required this.buildLabel,
417441
required this.label,
418442
this.tooltip,
443+
this.enabled = true,
419444
});
420445

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

482+
/// Indicates that this destination is selectable.
483+
///
484+
/// Defaults to true.
485+
final bool enabled;
486+
457487
@override
458488
State<_NavigationDestinationBuilder> createState() => _NavigationDestinationBuilderState();
459489
}
@@ -474,7 +504,7 @@ class _NavigationDestinationBuilderState extends State<_NavigationDestinationBui
474504
iconKey: iconKey,
475505
labelBehavior: info.labelBehavior,
476506
customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
477-
onTap: info.onTap,
507+
onTap: widget.enabled ? info.onTap : null,
478508
child: Row(
479509
children: <Widget>[
480510
Expanded(
@@ -1319,9 +1349,11 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
13191349
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
13201350
return IconThemeData(
13211351
size: 24.0,
1322-
color: states.contains(MaterialState.selected)
1323-
? _colors.onSecondaryContainer
1324-
: _colors.onSurfaceVariant,
1352+
color: states.contains(MaterialState.disabled)
1353+
? _colors.onSurfaceVariant.withOpacity(0.38)
1354+
: states.contains(MaterialState.selected)
1355+
? _colors.onSecondaryContainer
1356+
: _colors.onSurfaceVariant,
13251357
);
13261358
});
13271359
}
@@ -1332,9 +1364,11 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
13321364
@override MaterialStateProperty<TextStyle?>? get labelTextStyle {
13331365
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
13341366
final TextStyle style = _textTheme.labelMedium!;
1335-
return style.apply(color: states.contains(MaterialState.selected)
1336-
? _colors.onSurface
1337-
: _colors.onSurfaceVariant
1367+
return style.apply(color: states.contains(MaterialState.disabled)
1368+
? _colors.onSurfaceVariant.withOpacity(0.38)
1369+
: states.contains(MaterialState.selected)
1370+
? _colors.onSurface
1371+
: _colors.onSurfaceVariant
13381372
);
13391373
});
13401374
}

packages/flutter/test/material/navigation_bar_test.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,43 @@ void main() {
977977
expect(_getIndicatorDecoration(tester)?.shape, shape);
978978
});
979979

980+
testWidgetsWithLeakTracking('Destinations respect their disabled state', (WidgetTester tester) async {
981+
int selectedIndex = 0;
982+
983+
await tester.pumpWidget(
984+
_buildWidget(
985+
NavigationBar(
986+
destinations: const <Widget>[
987+
NavigationDestination(
988+
icon: Icon(Icons.ac_unit),
989+
label: 'AC',
990+
),
991+
NavigationDestination(
992+
icon: Icon(Icons.access_alarm),
993+
label: 'Alarm',
994+
),
995+
NavigationDestination(
996+
icon: Icon(Icons.bookmark),
997+
label: 'Bookmark',
998+
enabled: false,
999+
),
1000+
],
1001+
onDestinationSelected: (int i) => selectedIndex = i,
1002+
selectedIndex: selectedIndex,
1003+
),
1004+
)
1005+
);
1006+
1007+
await tester.tap(find.text('AC'));
1008+
expect(selectedIndex, 0);
1009+
1010+
await tester.tap(find.text('Alarm'));
1011+
expect(selectedIndex, 1);
1012+
1013+
await tester.tap(find.text('Bookmark'));
1014+
expect(selectedIndex, 1);
1015+
});
1016+
9801017
group('Material 2', () {
9811018
// These tests are only relevant for Material 2. Once Material 2
9821019
// support is deprecated and the APIs are removed, these tests

0 commit comments

Comments
 (0)