Unverified Commit 397fd25b authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Add ability to customize `NavigationBar` indicator overlay and fix indicator...

Add ability to customize `NavigationBar` indicator overlay and fix indicator shape for the overlay (#138901)

fixes [Provide ability to override `NavigationBar` indicator ink response overlay](https://github.com/flutter/flutter/issues/138850)
fixes [`NavigationBar.indicatorShape` is ignored, `NavigationBarThemeData.indicatorShape` is applied to the indicator inkwell](https://github.com/flutter/flutter/issues/138900)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        bottomNavigationBar: NavigationBarExample(),
      ),
    );
  }
}

class NavigationBarExample extends StatefulWidget {
  const NavigationBarExample({super.key});

  @override
  State<NavigationBarExample> createState() => _NavigationBarExampleState();
}

class _NavigationBarExampleState extends State<NavigationBarExample> {
  int index = 0;

  @override
  Widget build(BuildContext context) {
    return NavigationBar(
      elevation: 0,
      overlayColor: const MaterialStatePropertyAll<Color>(Colors.transparent),
      // indicatorShape: RoundedRectangleBorder(
      //   borderRadius: BorderRadius.circular(4.0),
      // ),
      indicatorColor: Colors.transparent,
      selectedIndex: index,
      onDestinationSelected: (int index) {
        setState(() {
          this.index = index;
        });
      },
      destinations: const <Widget>[
        NavigationDestination(
          selectedIcon: Icon(Icons.home_filled),
          icon: Icon(Icons.home_outlined),
          label: 'Home',
        ),
        NavigationDestination(
          selectedIcon: Icon(Icons.favorite),
          icon: Icon(Icons.favorite_outline),
          label: 'Favorites',
        ),
      ],
    );
  }
}

```

</details>

### Before

#### Cannot override `NavigationBar` Indicator ink well overlay

![Screenshot 2023-11-22 at 18 22 48](https://github.com/flutter/flutter/assets/48603081/06f54335-71ee-4882-afb0-53b614933c38)

#### Indicator shape is ignored for the indicator overlay

![Screenshot 2023-11-22 at 15 29 52](https://github.com/flutter/flutter/assets/48603081/913e0f77-48f4-4c6e-87f3-52c81b78f3d9)

### After

#### Can use `NavigationBar.overlayColor` or `NavigationBarThemeData.NavigationBar` to override default indicator overlay

`overlayColor: MaterialStatePropertyAll<Color>(Colors.red.withOpacity(0.33)),`

![Screenshot 2023-11-22 at 18 22 08](https://github.com/flutter/flutter/assets/48603081/28badae4-a7c7-4bf0-8bcc-278a1f84729d)

`overlayColor: MaterialStatePropertyAll<Color>(Colors.transparent),`

![Screenshot 2023-11-22 at 18 22 25](https://github.com/flutter/flutter/assets/48603081/674b48b1-f66a-4d91-9f10-ad307416ac32)

#### Indicator shape is respected for the indicator overlay

![Screenshot 2023-11-22 at 15 30 36](https://github.com/flutter/flutter/assets/48603081/ae9a3627-787e-45ac-9319-2ea8ea1e6ae6)
parent 7b80797d
...@@ -104,6 +104,7 @@ class NavigationBar extends StatelessWidget { ...@@ -104,6 +104,7 @@ class NavigationBar extends StatelessWidget {
this.indicatorShape, this.indicatorShape,
this.height, this.height,
this.labelBehavior, this.labelBehavior,
this.overlayColor,
}) : assert(destinations.length >= 2), }) : assert(destinations.length >= 2),
assert(0 <= selectedIndex && selectedIndex < destinations.length); assert(0 <= selectedIndex && selectedIndex < destinations.length);
...@@ -207,6 +208,10 @@ class NavigationBar extends StatelessWidget { ...@@ -207,6 +208,10 @@ class NavigationBar extends StatelessWidget {
/// [NavigationDestinationLabelBehavior.alwaysShow]. /// [NavigationDestinationLabelBehavior.alwaysShow].
final NavigationDestinationLabelBehavior? labelBehavior; final NavigationDestinationLabelBehavior? labelBehavior;
/// The highlight color that's typically used to indicate that
/// the [NavigationDestination] is focused, hovered, or pressed.
final MaterialStateProperty<Color?>? overlayColor;
VoidCallback _handleTap(int index) { VoidCallback _handleTap(int index) {
return onDestinationSelected != null return onDestinationSelected != null
? () => onDestinationSelected!(index) ? () => onDestinationSelected!(index)
...@@ -249,6 +254,7 @@ class NavigationBar extends StatelessWidget { ...@@ -249,6 +254,7 @@ class NavigationBar extends StatelessWidget {
labelBehavior: effectiveLabelBehavior, labelBehavior: effectiveLabelBehavior,
indicatorColor: indicatorColor, indicatorColor: indicatorColor,
indicatorShape: indicatorShape, indicatorShape: indicatorShape,
overlayColor: overlayColor,
onTap: _handleTap(i), onTap: _handleTap(i),
child: destinations[i], child: destinations[i],
); );
...@@ -509,7 +515,8 @@ class _NavigationDestinationBuilderState extends State<_NavigationDestinationBui ...@@ -509,7 +515,8 @@ class _NavigationDestinationBuilderState extends State<_NavigationDestinationBui
child: _IndicatorInkWell( child: _IndicatorInkWell(
iconKey: iconKey, iconKey: iconKey,
labelBehavior: info.labelBehavior, labelBehavior: info.labelBehavior,
customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape, customBorder: info.indicatorShape ?? navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
overlayColor: info.overlayColor ?? navigationBarTheme.overlayColor,
onTap: widget.enabled ? info.onTap : null, onTap: widget.enabled ? info.onTap : null,
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
...@@ -532,6 +539,7 @@ class _IndicatorInkWell extends InkResponse { ...@@ -532,6 +539,7 @@ class _IndicatorInkWell extends InkResponse {
const _IndicatorInkWell({ const _IndicatorInkWell({
required this.iconKey, required this.iconKey,
required this.labelBehavior, required this.labelBehavior,
super.overlayColor,
super.customBorder, super.customBorder,
super.onTap, super.onTap,
super.child, super.child,
...@@ -569,6 +577,7 @@ class _NavigationDestinationInfo extends InheritedWidget { ...@@ -569,6 +577,7 @@ class _NavigationDestinationInfo extends InheritedWidget {
required this.labelBehavior, required this.labelBehavior,
required this.indicatorColor, required this.indicatorColor,
required this.indicatorShape, required this.indicatorShape,
required this.overlayColor,
required this.onTap, required this.onTap,
required super.child, required super.child,
}); });
...@@ -635,6 +644,12 @@ class _NavigationDestinationInfo extends InheritedWidget { ...@@ -635,6 +644,12 @@ class _NavigationDestinationInfo extends InheritedWidget {
/// This is used by destinations to override the indicator shape. /// This is used by destinations to override the indicator shape.
final ShapeBorder? indicatorShape; final ShapeBorder? indicatorShape;
/// The highlight color that's typically used to indicate that
/// the [NavigationDestination] is focused, hovered, or pressed.
///
/// This is used by destinations to override the overlay color.
final MaterialStateProperty<Color?>? overlayColor;
/// The callback that should be called when this destination is tapped. /// The callback that should be called when this destination is tapped.
/// ///
/// This is computed by calling [NavigationBar.onDestinationSelected] /// This is computed by calling [NavigationBar.onDestinationSelected]
......
...@@ -52,6 +52,7 @@ class NavigationBarThemeData with Diagnosticable { ...@@ -52,6 +52,7 @@ class NavigationBarThemeData with Diagnosticable {
this.labelTextStyle, this.labelTextStyle,
this.iconTheme, this.iconTheme,
this.labelBehavior, this.labelBehavior,
this.overlayColor,
}); });
/// Overrides the default value of [NavigationBar.height]. /// Overrides the default value of [NavigationBar.height].
...@@ -91,6 +92,9 @@ class NavigationBarThemeData with Diagnosticable { ...@@ -91,6 +92,9 @@ class NavigationBarThemeData with Diagnosticable {
/// Overrides the default value of [NavigationBar.labelBehavior]. /// Overrides the default value of [NavigationBar.labelBehavior].
final NavigationDestinationLabelBehavior? labelBehavior; final NavigationDestinationLabelBehavior? labelBehavior;
/// Overrides the default value of [NavigationBar.overlayColor].
final MaterialStateProperty<Color?>? overlayColor;
/// Creates a copy of this object with the given fields replaced with the /// Creates a copy of this object with the given fields replaced with the
/// new values. /// new values.
NavigationBarThemeData copyWith({ NavigationBarThemeData copyWith({
...@@ -104,6 +108,7 @@ class NavigationBarThemeData with Diagnosticable { ...@@ -104,6 +108,7 @@ class NavigationBarThemeData with Diagnosticable {
MaterialStateProperty<TextStyle?>? labelTextStyle, MaterialStateProperty<TextStyle?>? labelTextStyle,
MaterialStateProperty<IconThemeData?>? iconTheme, MaterialStateProperty<IconThemeData?>? iconTheme,
NavigationDestinationLabelBehavior? labelBehavior, NavigationDestinationLabelBehavior? labelBehavior,
MaterialStateProperty<Color?>? overlayColor,
}) { }) {
return NavigationBarThemeData( return NavigationBarThemeData(
height: height ?? this.height, height: height ?? this.height,
...@@ -116,6 +121,7 @@ class NavigationBarThemeData with Diagnosticable { ...@@ -116,6 +121,7 @@ class NavigationBarThemeData with Diagnosticable {
labelTextStyle: labelTextStyle ?? this.labelTextStyle, labelTextStyle: labelTextStyle ?? this.labelTextStyle,
iconTheme: iconTheme ?? this.iconTheme, iconTheme: iconTheme ?? this.iconTheme,
labelBehavior: labelBehavior ?? this.labelBehavior, labelBehavior: labelBehavior ?? this.labelBehavior,
overlayColor: overlayColor ?? this.overlayColor,
); );
} }
...@@ -139,6 +145,7 @@ class NavigationBarThemeData with Diagnosticable { ...@@ -139,6 +145,7 @@ class NavigationBarThemeData with Diagnosticable {
labelTextStyle: MaterialStateProperty.lerp<TextStyle?>(a?.labelTextStyle, b?.labelTextStyle, t, TextStyle.lerp), labelTextStyle: MaterialStateProperty.lerp<TextStyle?>(a?.labelTextStyle, b?.labelTextStyle, t, TextStyle.lerp),
iconTheme: MaterialStateProperty.lerp<IconThemeData?>(a?.iconTheme, b?.iconTheme, t, IconThemeData.lerp), iconTheme: MaterialStateProperty.lerp<IconThemeData?>(a?.iconTheme, b?.iconTheme, t, IconThemeData.lerp),
labelBehavior: t < 0.5 ? a?.labelBehavior : b?.labelBehavior, labelBehavior: t < 0.5 ? a?.labelBehavior : b?.labelBehavior,
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
); );
} }
...@@ -154,6 +161,7 @@ class NavigationBarThemeData with Diagnosticable { ...@@ -154,6 +161,7 @@ class NavigationBarThemeData with Diagnosticable {
labelTextStyle, labelTextStyle,
iconTheme, iconTheme,
labelBehavior, labelBehavior,
overlayColor,
); );
@override @override
...@@ -165,16 +173,17 @@ class NavigationBarThemeData with Diagnosticable { ...@@ -165,16 +173,17 @@ class NavigationBarThemeData with Diagnosticable {
return false; return false;
} }
return other is NavigationBarThemeData return other is NavigationBarThemeData
&& other.height == height && other.height == height
&& other.backgroundColor == backgroundColor && other.backgroundColor == backgroundColor
&& other.elevation == elevation && other.elevation == elevation
&& other.shadowColor == shadowColor && other.shadowColor == shadowColor
&& other.surfaceTintColor == surfaceTintColor && other.surfaceTintColor == surfaceTintColor
&& other.indicatorColor == indicatorColor && other.indicatorColor == indicatorColor
&& other.indicatorShape == indicatorShape && other.indicatorShape == indicatorShape
&& other.labelTextStyle == labelTextStyle && other.labelTextStyle == labelTextStyle
&& other.iconTheme == iconTheme && other.iconTheme == iconTheme
&& other.labelBehavior == labelBehavior; && other.labelBehavior == labelBehavior
&& other.overlayColor == overlayColor;
} }
@override @override
...@@ -190,6 +199,7 @@ class NavigationBarThemeData with Diagnosticable { ...@@ -190,6 +199,7 @@ class NavigationBarThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('labelTextStyle', labelTextStyle, defaultValue: null)); properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('labelTextStyle', labelTextStyle, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<IconThemeData?>>('iconTheme', iconTheme, defaultValue: null)); properties.add(DiagnosticsProperty<MaterialStateProperty<IconThemeData?>>('iconTheme', iconTheme, defaultValue: null));
properties.add(DiagnosticsProperty<NavigationDestinationLabelBehavior>('labelBehavior', labelBehavior, defaultValue: null)); properties.add(DiagnosticsProperty<NavigationDestinationLabelBehavior>('labelBehavior', labelBehavior, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
} }
} }
......
...@@ -12,6 +12,7 @@ import 'dart:math'; ...@@ -12,6 +12,7 @@ import 'dart:math';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
...@@ -937,7 +938,7 @@ void main() { ...@@ -937,7 +938,7 @@ void main() {
}); });
testWidgetsWithLeakTracking('Material3 - Navigation destination updates indicator color and shape', (WidgetTester tester) async { testWidgetsWithLeakTracking('Material3 - Navigation destination updates indicator color and shape', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true); final ThemeData theme = ThemeData();
const Color color = Color(0xff0000ff); const Color color = Color(0xff0000ff);
const ShapeBorder shape = RoundedRectangleBorder(); const ShapeBorder shape = RoundedRectangleBorder();
...@@ -945,20 +946,22 @@ void main() { ...@@ -945,20 +946,22 @@ void main() {
return MaterialApp( return MaterialApp(
theme: theme, theme: theme,
home: Scaffold( home: Scaffold(
bottomNavigationBar: NavigationBar( bottomNavigationBar: RepaintBoundary(
indicatorColor: indicatorColor, child: NavigationBar(
indicatorShape: indicatorShape, indicatorColor: indicatorColor,
destinations: const <Widget>[ indicatorShape: indicatorShape,
NavigationDestination( destinations: const <Widget>[
icon: Icon(Icons.ac_unit), NavigationDestination(
label: 'AC', icon: Icon(Icons.ac_unit),
), label: 'AC',
NavigationDestination( ),
icon: Icon(Icons.access_alarm), NavigationDestination(
label: 'Alarm', icon: Icon(Icons.access_alarm),
), label: 'Alarm',
], ),
onDestinationSelected: (int i) { }, ],
onDestinationSelected: (int i) { },
),
), ),
), ),
); );
...@@ -970,11 +973,22 @@ void main() { ...@@ -970,11 +973,22 @@ void main() {
expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer); expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer);
expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder()); expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder());
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last));
await tester.pumpAndSettle();
// Test default indicator color and shape with ripple.
await expectLater(find.byType(NavigationBar), matchesGoldenFile('m3.navigation_bar.default.indicator.inkwell.shape.png'));
await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape)); await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape));
// Test custom indicator color and shape. // Test custom indicator color and shape.
expect(_getIndicatorDecoration(tester)?.color, color); expect(_getIndicatorDecoration(tester)?.color, color);
expect(_getIndicatorDecoration(tester)?.shape, shape); expect(_getIndicatorDecoration(tester)?.shape, shape);
// Test custom indicator color and shape with ripple.
await expectLater(find.byType(NavigationBar), matchesGoldenFile('m3.navigation_bar.custom.indicator.inkwell.shape.png'));
}); });
testWidgetsWithLeakTracking('Destinations respect their disabled state', (WidgetTester tester) async { testWidgetsWithLeakTracking('Destinations respect their disabled state', (WidgetTester tester) async {
...@@ -1014,6 +1028,86 @@ void main() { ...@@ -1014,6 +1028,86 @@ void main() {
expect(selectedIndex, 1); expect(selectedIndex, 1);
}); });
testWidgetsWithLeakTracking('NavigationBar respects overlayColor in active/pressed/hovered states', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoverColor = Color(0xff0000ff);
const Color focusColor = Color(0xff00ffff);
const Color pressedColor = Color(0xffff00ff);
final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return hoverColor;
}
if (states.contains(MaterialState.focused)) {
return focusColor;
}
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
return Colors.transparent;
});
await tester.pumpWidget(MaterialApp(
home: Scaffold(
bottomNavigationBar: RepaintBoundary(
child: NavigationBar(
overlayColor: overlayColor,
destinations: const <Widget>[
NavigationDestination(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
NavigationDestination(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
onDestinationSelected: (int i) { },
),
),
),
));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last));
await tester.pumpAndSettle();
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
// Test hovered state.
expect(
inkFeatures,
kIsWeb
? (paints..rrect()..rrect()..circle(color: hoverColor))
: (paints..circle(color: hoverColor)),
);
await gesture.down(tester.getCenter(find.byType(NavigationIndicator).last));
await tester.pumpAndSettle();
// Test pressed state.
expect(
inkFeatures,
kIsWeb
? (paints..circle()..circle()..circle(color: pressedColor))
: (paints..circle()..circle(color: pressedColor)),
);
await gesture.up();
await tester.pumpAndSettle();
// Press tab to focus the navigation bar.
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.pumpAndSettle();
// Test focused state.
expect(
inkFeatures,
kIsWeb ? (paints..circle()..circle(color: focusColor)) : (paints..circle()..circle(color: focusColor)),
);
});
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
......
...@@ -7,9 +7,11 @@ ...@@ -7,9 +7,11 @@
@Tags(<String>['reduced-test-set']) @Tags(<String>['reduced-test-set'])
library; library;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
...@@ -48,6 +50,7 @@ void main() { ...@@ -48,6 +50,7 @@ void main() {
labelTextStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(fontSize: 7.0)), labelTextStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(fontSize: 7.0)),
iconTheme: MaterialStatePropertyAll<IconThemeData>(IconThemeData(color: Color(0x00000097))), iconTheme: MaterialStatePropertyAll<IconThemeData>(IconThemeData(color: Color(0x00000097))),
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide, labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
overlayColor: MaterialStatePropertyAll<Color>(Color(0x00000096)),
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties final List<String> description = builder.properties
...@@ -61,12 +64,11 @@ void main() { ...@@ -61,12 +64,11 @@ void main() {
expect(description[3], 'indicatorColor: Color(0x00000098)'); expect(description[3], 'indicatorColor: Color(0x00000098)');
expect(description[4], 'indicatorShape: CircleBorder(BorderSide(width: 0.0, style: none))'); expect(description[4], 'indicatorShape: CircleBorder(BorderSide(width: 0.0, style: none))');
expect(description[5], 'labelTextStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 7.0))'); expect(description[5], 'labelTextStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 7.0))');
// Ignore instance address for IconThemeData. // Ignore instance address for IconThemeData.
expect(description[6].contains('iconTheme: MaterialStatePropertyAll(IconThemeData'), isTrue); expect(description[6].contains('iconTheme: MaterialStatePropertyAll(IconThemeData'), isTrue);
expect(description[6].contains('(color: Color(0x00000097))'), isTrue); expect(description[6].contains('(color: Color(0x00000097))'), isTrue);
expect(description[7], 'labelBehavior: NavigationDestinationLabelBehavior.alwaysHide'); expect(description[7], 'labelBehavior: NavigationDestinationLabelBehavior.alwaysHide');
expect(description[8], 'overlayColor: MaterialStatePropertyAll(Color(0x00000096))');
}); });
testWidgetsWithLeakTracking('NavigationBarThemeData values are used when no NavigationBar properties are specified', (WidgetTester tester) async { testWidgetsWithLeakTracking('NavigationBarThemeData values are used when no NavigationBar properties are specified', (WidgetTester tester) async {
...@@ -216,6 +218,86 @@ void main() { ...@@ -216,6 +218,86 @@ void main() {
await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_custom_label_style.png')); await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_custom_label_style.png'));
}); });
testWidgetsWithLeakTracking('NavigationBar respects NavigationBarTheme.overlayColor in active/pressed/hovered states', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoverColor = Color(0xff0000ff);
const Color focusColor = Color(0xff00ffff);
const Color pressedColor = Color(0xffff00ff);
final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return hoverColor;
}
if (states.contains(MaterialState.focused)) {
return focusColor;
}
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
return Colors.transparent;
});
await tester.pumpWidget(MaterialApp(
theme: ThemeData(navigationBarTheme: NavigationBarThemeData(overlayColor: overlayColor)),
home: Scaffold(
bottomNavigationBar: RepaintBoundary(
child: NavigationBar(
destinations: const <Widget>[
NavigationDestination(
icon: Icon(Icons.ac_unit),
label: 'AC',
),
NavigationDestination(
icon: Icon(Icons.access_alarm),
label: 'Alarm',
),
],
onDestinationSelected: (int i) { },
),
),
),
));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last));
await tester.pumpAndSettle();
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
// Test hovered state.
expect(
inkFeatures,
kIsWeb
? (paints..rrect()..rrect()..circle(color: hoverColor))
: (paints..circle(color: hoverColor)),
);
await gesture.down(tester.getCenter(find.byType(NavigationIndicator).last));
await tester.pumpAndSettle();
// Test pressed state.
expect(
inkFeatures,
kIsWeb
? (paints..circle()..circle()..circle(color: pressedColor))
: (paints..circle()..circle(color: pressedColor)),
);
await gesture.up();
await tester.pumpAndSettle();
// Press tab to focus the navigation bar.
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.pumpAndSettle();
// Test focused state.
expect(
inkFeatures,
kIsWeb ? (paints..circle()..circle(color: focusColor)) : (paints..circle()..circle(color: focusColor)),
);
});
} }
List<NavigationDestination> _destinations() { List<NavigationDestination> _destinations() {
......
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