mirror of
https://github.com/flutter/flutter
synced 2024-09-17 23:31:55 +00:00
Add ActionButtonIconsData for overriding action icons (#118229)
* Add ActionButtonIconsData for overriding action icons * Fix formatting issues * Add missing exports in material library and add copyWith method in ActionButtonIconsData * Move all action buttons, and icons to action_buttons.dart * Rename actionButtonIcons to actionIconTheme * Refactor buttons in action_buttons.dart to extend a private class for common implementation * Refactor icons in action_buttons * Fix docs in action_buttons_theme * Fix #107646 always use 'Icons.arrow_back' as a back_button icon in web * Update documentation for action buttons and add style parameter to every action button * Fix analyzer warnings * Add missing style argument in IconButton of _ActionButton * Add tests for action buttons, action icon theme, drawer buttons, and back buttons * Add example (+test) for action icon button's action icon theme in examples/api * Fix analysis errors * Add missing license header in action_icon_theme.0.dart * Fix deprecation notice in theme_data.dart * Update theme data tests for actionIconTheme * Remove iconSize parameter from ActionButtons and update docs * Fix failing tests * Update button color during backbutton tests to red * Fix analytics issues * Fix format
This commit is contained in:
parent
d816e72f07
commit
7d85a585da
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Flutter code sample for [ActionIconTheme].
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class _CustomEndDrawerIcon extends StatelessWidget {
|
||||
const _CustomEndDrawerIcon();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MaterialLocalizations localization = MaterialLocalizations.of(context);
|
||||
return Icon(
|
||||
Icons.more_horiz,
|
||||
semanticLabel: localization.openAppDrawerTooltip,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomDrawerIcon extends StatelessWidget {
|
||||
const _CustomDrawerIcon();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MaterialLocalizations localization = MaterialLocalizations.of(context);
|
||||
return Icon(
|
||||
Icons.segment,
|
||||
semanticLabel: localization.openAppDrawerTooltip,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
actionIconTheme: ActionIconThemeData(
|
||||
backButtonIconBuilder: (BuildContext context) {
|
||||
return const Icon(Icons.arrow_back_ios_new_rounded);
|
||||
},
|
||||
drawerButtonIconBuilder: (BuildContext context) {
|
||||
return const _CustomDrawerIcon();
|
||||
},
|
||||
endDrawerButtonIconBuilder: (BuildContext context) {
|
||||
return const _CustomEndDrawerIcon();
|
||||
},
|
||||
),
|
||||
),
|
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatelessWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
),
|
||||
drawer: const Drawer(),
|
||||
body: const Center(
|
||||
child: NextPageButton(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NextPageButton extends StatelessWidget {
|
||||
const NextPageButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<MySecondPage>(builder: (BuildContext context) {
|
||||
return const MySecondPage();
|
||||
}),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.arrow_forward),
|
||||
label: const Text('Next page'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MySecondPage extends StatelessWidget {
|
||||
const MySecondPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Second page'),
|
||||
),
|
||||
endDrawer: const Drawer(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_api_samples/material/action_buttons/action_icon_theme.0.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Action Icon Buttons', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: example.MyApp(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
expect(find.byType(DrawerButton), findsOneWidget);
|
||||
final Icon drawerButtonIcon = tester.widget(
|
||||
find.descendant(
|
||||
of: find.byType(DrawerButton),
|
||||
matching: find.byType(Icon),
|
||||
),
|
||||
);
|
||||
expect(drawerButtonIcon.icon, Icons.segment);
|
||||
|
||||
// open next page
|
||||
await tester.tap(find.byType(example.NextPageButton));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(EndDrawerButton), findsOneWidget);
|
||||
final Icon endDrawerButtonIcon = tester.widget(
|
||||
find.descendant(
|
||||
of: find.byType(EndDrawerButton),
|
||||
matching: find.byType(Icon),
|
||||
),
|
||||
);
|
||||
expect(endDrawerButtonIcon.icon, Icons.more_horiz);
|
||||
|
||||
expect(find.byType(BackButton), findsOneWidget);
|
||||
final Icon backButtonIcon = tester.widget(
|
||||
find.descendant(
|
||||
of: find.byType(BackButton),
|
||||
matching: find.byType(Icon),
|
||||
),
|
||||
);
|
||||
expect(backButtonIcon.icon, Icons.arrow_back_ios_new_rounded);
|
||||
});
|
||||
}
|
|
@ -21,7 +21,9 @@
|
|||
library material;
|
||||
|
||||
export 'src/material/about.dart';
|
||||
export 'src/material/action_buttons.dart';
|
||||
export 'src/material/action_chip.dart';
|
||||
export 'src/material/action_icons_theme.dart';
|
||||
export 'src/material/adaptive_text_selection_toolbar.dart';
|
||||
export 'src/material/animated_icons.dart';
|
||||
export 'src/material/app.dart';
|
||||
|
@ -29,7 +31,6 @@ export 'src/material/app_bar.dart';
|
|||
export 'src/material/app_bar_theme.dart';
|
||||
export 'src/material/arc.dart';
|
||||
export 'src/material/autocomplete.dart';
|
||||
export 'src/material/back_button.dart';
|
||||
export 'src/material/badge.dart';
|
||||
export 'src/material/badge_theme.dart';
|
||||
export 'src/material/banner.dart';
|
||||
|
|
418
packages/flutter/lib/src/material/action_buttons.dart
Normal file
418
packages/flutter/lib/src/material/action_buttons.dart
Normal file
|
@ -0,0 +1,418 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'action_icons_theme.dart';
|
||||
import 'button_style.dart';
|
||||
import 'debug.dart';
|
||||
import 'icon_button.dart';
|
||||
import 'icons.dart';
|
||||
import 'material_localizations.dart';
|
||||
import 'scaffold.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
abstract class _ActionButton extends StatelessWidget {
|
||||
/// Creates a Material Design icon button.
|
||||
const _ActionButton({
|
||||
super.key,
|
||||
this.color,
|
||||
required this.icon,
|
||||
required this.onPressed,
|
||||
this.style,
|
||||
});
|
||||
|
||||
/// The icon to display inside the button.
|
||||
final Widget icon;
|
||||
|
||||
/// The callback that is called when the button is tapped
|
||||
/// or otherwise activated.
|
||||
///
|
||||
/// If this is set to null, the button will do a default action
|
||||
/// when it is tapped or activated.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// The color to use for the icon.
|
||||
///
|
||||
/// Defaults to the [IconThemeData.color] specified in the ambient [IconTheme],
|
||||
/// which usually matches the ambient [Theme]'s [ThemeData.iconTheme].
|
||||
final Color? color;
|
||||
|
||||
/// Customizes this icon button's appearance.
|
||||
///
|
||||
/// The [style] is only used for Material 3 [IconButton]s. If [ThemeData.useMaterial3]
|
||||
/// is set to true, [style] is preferred for icon button customization, and any
|
||||
/// parameters defined in [style] will override the same parameters in [IconButton].
|
||||
///
|
||||
/// Null by default.
|
||||
final ButtonStyle? style;
|
||||
|
||||
/// This returns the appropriate tooltip text for this action button.
|
||||
String _getTooltip(BuildContext context);
|
||||
|
||||
/// This is the default function that is called when [onPressed] is set
|
||||
/// to null.
|
||||
void _onPressedCallback(BuildContext context);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
return IconButton(
|
||||
icon: icon,
|
||||
style: style,
|
||||
color: color,
|
||||
tooltip: _getTooltip(context),
|
||||
onPressed: () {
|
||||
if (onPressed != null) {
|
||||
onPressed!();
|
||||
} else {
|
||||
_onPressedCallback(context);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef _ActionIconBuilderCallback = WidgetBuilder? Function(ActionIconThemeData? actionIconTheme);
|
||||
typedef _ActionIconDataCallback = IconData Function(BuildContext context);
|
||||
typedef _AndroidSemanticsLabelCallback = String Function(MaterialLocalizations materialLocalization);
|
||||
|
||||
class _ActionIcon extends StatelessWidget {
|
||||
const _ActionIcon({
|
||||
required this.iconBuilderCallback,
|
||||
required this.getIcon,
|
||||
required this.getAndroidSemanticsLabel,
|
||||
});
|
||||
|
||||
final _ActionIconBuilderCallback iconBuilderCallback;
|
||||
final _ActionIconDataCallback getIcon;
|
||||
final _AndroidSemanticsLabelCallback getAndroidSemanticsLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ActionIconThemeData? actionIconTheme = ActionIconTheme.of(context);
|
||||
final WidgetBuilder? iconBuilder = iconBuilderCallback(actionIconTheme);
|
||||
if (iconBuilder != null) {
|
||||
return iconBuilder(context);
|
||||
}
|
||||
|
||||
final IconData data = getIcon(context);
|
||||
final String? semanticsLabel;
|
||||
// This can't use the platform from Theme because it is the Android OS that
|
||||
// expects the duplicated tooltip and label.
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
semanticsLabel = getAndroidSemanticsLabel(MaterialLocalizations.of(context));
|
||||
break;
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
semanticsLabel = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return Icon(data, semanticLabel: semanticsLabel);
|
||||
}
|
||||
}
|
||||
|
||||
/// A "back" icon that's appropriate for the current [TargetPlatform].
|
||||
///
|
||||
/// The current platform is determined by querying for the ambient [Theme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [BackButton], an [IconButton] with a [BackButtonIcon] that calls
|
||||
/// [Navigator.maybePop] to return to the previous route.
|
||||
/// * [IconButton], which is a more general widget for creating buttons
|
||||
/// with icons.
|
||||
/// * [Icon], a Material Design icon.
|
||||
/// * [ThemeData.platform], which specifies the current platform.
|
||||
class BackButtonIcon extends StatelessWidget {
|
||||
/// Creates an icon that shows the appropriate "back" image for
|
||||
/// the current platform (as obtained from the [Theme]).
|
||||
const BackButtonIcon({ super.key });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _ActionIcon(
|
||||
iconBuilderCallback: (ActionIconThemeData? actionIconTheme) {
|
||||
return actionIconTheme?.backButtonIconBuilder;
|
||||
},
|
||||
getIcon: (BuildContext context) {
|
||||
if (kIsWeb) {
|
||||
// Always use 'Icons.arrow_back' as a back_button icon in web.
|
||||
return Icons.arrow_back;
|
||||
}
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return Icons.arrow_back;
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
return Icons.arrow_back_ios;
|
||||
}
|
||||
},
|
||||
getAndroidSemanticsLabel: (MaterialLocalizations materialLocalization) {
|
||||
return materialLocalization.backButtonTooltip;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Material Design back icon button.
|
||||
///
|
||||
/// A [BackButton] is an [IconButton] with a "back" icon appropriate for the
|
||||
/// current [TargetPlatform]. When pressed, the back button calls
|
||||
/// [Navigator.maybePop] to return to the previous route unless a custom
|
||||
/// [onPressed] callback is provided.
|
||||
///
|
||||
/// The [onPressed] callback can, for instance, be used to pop the platform's navigation stack
|
||||
/// via [SystemNavigator] instead of Flutter's [Navigator] in add-to-app
|
||||
/// situations.
|
||||
///
|
||||
/// In Material Design 3, both [style]'s [ButtonStyle.iconColor] and [color] are
|
||||
/// used to override the default icon color of [BackButton]. If both exist, the [ButtonStyle.iconColor]
|
||||
/// will override [color] for states where [ButtonStyle.foregroundColor] resolves to non-null.
|
||||
///
|
||||
/// When deciding to display a [BackButton], consider using
|
||||
/// `ModalRoute.of(context)?.canPop` to check whether the current route can be
|
||||
/// popped. If that value is false (e.g., because the current route is the
|
||||
/// initial route), the [BackButton] will not have any effect when pressed,
|
||||
/// which could frustrate the user.
|
||||
///
|
||||
/// Requires one of its ancestors to be a [Material] widget.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar], which automatically uses a [BackButton] in its
|
||||
/// [AppBar.leading] slot when the [Scaffold] has no [Drawer] and the
|
||||
/// current [Route] is not the [Navigator]'s first route.
|
||||
/// * [BackButtonIcon], which is useful if you need to create a back button
|
||||
/// that responds differently to being pressed.
|
||||
/// * [IconButton], which is a more general widget for creating buttons with
|
||||
/// icons.
|
||||
/// * [CloseButton], an alternative which may be more appropriate for leaf
|
||||
/// node pages in the navigation tree.
|
||||
class BackButton extends _ActionButton {
|
||||
/// Creates an [IconButton] with the appropriate "back" icon for the current
|
||||
/// target platform.
|
||||
const BackButton({
|
||||
super.key,
|
||||
super.color,
|
||||
super.style,
|
||||
super.onPressed,
|
||||
}) : super(icon: const BackButtonIcon());
|
||||
|
||||
@override
|
||||
void _onPressedCallback(BuildContext context) => Navigator.maybePop(context);
|
||||
|
||||
@override
|
||||
String _getTooltip(BuildContext context) {
|
||||
return MaterialLocalizations.of(context).backButtonTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
/// A "close" icon that's appropriate for the current [TargetPlatform].
|
||||
///
|
||||
/// The current platform is determined by querying for the ambient [Theme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CloseButton], an [IconButton] with a [CloseButtonIcon] that calls
|
||||
/// [Navigator.maybePop] to return to the previous route.
|
||||
/// * [IconButton], which is a more general widget for creating buttons
|
||||
/// with icons.
|
||||
/// * [Icon], a Material Design icon.
|
||||
/// * [ThemeData.platform], which specifies the current platform.
|
||||
class CloseButtonIcon extends StatelessWidget {
|
||||
/// Creates an icon that shows the appropriate "close" image for
|
||||
/// the current platform (as obtained from the [Theme]).
|
||||
const CloseButtonIcon({ super.key });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _ActionIcon(
|
||||
iconBuilderCallback: (ActionIconThemeData? actionIconTheme) {
|
||||
return actionIconTheme?.closeButtonIconBuilder;
|
||||
},
|
||||
getIcon: (BuildContext context) => Icons.close,
|
||||
getAndroidSemanticsLabel: (MaterialLocalizations materialLocalization) {
|
||||
return materialLocalization.closeButtonTooltip;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Material Design close icon button.
|
||||
///
|
||||
/// A [CloseButton] is an [IconButton] with a "close" icon. When pressed, the
|
||||
/// close button calls [Navigator.maybePop] to return to the previous route.
|
||||
///
|
||||
/// The [onPressed] callback can, for instance, be used to pop the platform's navigation stack
|
||||
/// via [SystemNavigator] instead of Flutter's [Navigator] in add-to-app
|
||||
/// situations.
|
||||
///
|
||||
/// In Material Design 3, both [style]'s [ButtonStyle.iconColor] and [color] are
|
||||
/// used to override the default icon color of [CloseButton]. If both exist, the [ButtonStyle.iconColor]
|
||||
/// will override [color] for states where [ButtonStyle.foregroundColor] resolves to non-null.
|
||||
///
|
||||
/// Use a [CloseButton] instead of a [BackButton] on fullscreen dialogs or
|
||||
/// pages that may solicit additional actions to close.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar], which automatically uses a [CloseButton] in its
|
||||
/// [AppBar.leading] slot when appropriate.
|
||||
/// * [BackButton], which is more appropriate for middle nodes in the
|
||||
/// navigation tree or where pages can be popped instantaneously with
|
||||
/// no user data consequence.
|
||||
/// * [IconButton], to create other Material Design icon buttons.
|
||||
class CloseButton extends _ActionButton {
|
||||
/// Creates a Material Design close icon button.
|
||||
const CloseButton({ super.key, super.color, super.onPressed, super.style })
|
||||
: super(icon: const CloseButtonIcon());
|
||||
|
||||
@override
|
||||
void _onPressedCallback(BuildContext context) => Navigator.maybePop(context);
|
||||
|
||||
@override
|
||||
String _getTooltip(BuildContext context) {
|
||||
return MaterialLocalizations.of(context).closeButtonTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
/// A "drawer" icon that's appropriate for the current [TargetPlatform].
|
||||
///
|
||||
/// The current platform is determined by querying for the ambient [Theme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [DrawerButton], an [IconButton] with a [DrawerButtonIcon] that calls
|
||||
/// [ScaffoldState.openDrawer] to open the [Scaffold.drawer].
|
||||
/// * [EndDrawerButton], an [IconButton] with an [EndDrawerButtonIcon] that
|
||||
/// calls [ScaffoldState.openEndDrawer] to open the [Scaffold.endDrawer].
|
||||
/// * [IconButton], which is a more general widget for creating buttons
|
||||
/// with icons.
|
||||
/// * [Icon], a Material Design icon.
|
||||
/// * [ThemeData.platform], which specifies the current platform.
|
||||
class DrawerButtonIcon extends StatelessWidget {
|
||||
/// Creates an icon that shows the appropriate "close" image for
|
||||
/// the current platform (as obtained from the [Theme]).
|
||||
const DrawerButtonIcon({ super.key });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _ActionIcon(
|
||||
iconBuilderCallback: (ActionIconThemeData? actionIconTheme) {
|
||||
return actionIconTheme?.drawerButtonIconBuilder;
|
||||
},
|
||||
getIcon: (BuildContext context) => Icons.menu,
|
||||
getAndroidSemanticsLabel: (MaterialLocalizations materialLocalization) {
|
||||
return materialLocalization.openAppDrawerTooltip;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Material Design drawer icon button.
|
||||
///
|
||||
/// A [DrawerButton] is an [IconButton] with a "drawer" icon. When pressed, the
|
||||
/// close button calls [ScaffoldState.openDrawer] to the [Scaffold.drawer].
|
||||
///
|
||||
/// The default behaviour on press can be overriden with [onPressed].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [EndDrawerButton], an [IconButton] with an [EndDrawerButtonIcon] that
|
||||
/// calls [ScaffoldState.openEndDrawer] to open the [Scaffold.endDrawer].
|
||||
/// * [IconButton], which is a more general widget for creating buttons
|
||||
/// with icons.
|
||||
/// * [Icon], a Material Design icon.
|
||||
/// * [ThemeData.platform], which specifies the current platform.
|
||||
class DrawerButton extends _ActionButton {
|
||||
/// Creates a Material Design drawer icon button.
|
||||
const DrawerButton({
|
||||
super.key,
|
||||
super.style,
|
||||
super.onPressed,
|
||||
}) : super(icon: const DrawerButtonIcon());
|
||||
|
||||
@override
|
||||
void _onPressedCallback(BuildContext context) => Scaffold.of(context).openDrawer();
|
||||
|
||||
@override
|
||||
String _getTooltip(BuildContext context) {
|
||||
return MaterialLocalizations.of(context).openAppDrawerTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
/// A "end drawer" icon that's appropriate for the current [TargetPlatform].
|
||||
///
|
||||
/// The current platform is determined by querying for the ambient [Theme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [DrawerButton], an [IconButton] with a [DrawerButtonIcon] that calls
|
||||
/// [ScaffoldState.openDrawer] to open the [Scaffold.drawer].
|
||||
/// * [EndDrawerButton], an [IconButton] with an [EndDrawerButtonIcon] that
|
||||
/// calls [ScaffoldState.openEndDrawer] to open the [Scaffold.endDrawer]
|
||||
/// * [IconButton], which is a more general widget for creating buttons
|
||||
/// with icons.
|
||||
/// * [Icon], a Material Design icon.
|
||||
/// * [ThemeData.platform], which specifies the current platform.
|
||||
class EndDrawerButtonIcon extends StatelessWidget {
|
||||
/// Creates an icon that shows the appropriate "end drawer" image for
|
||||
/// the current platform (as obtained from the [Theme]).
|
||||
const EndDrawerButtonIcon({ super.key });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _ActionIcon(
|
||||
iconBuilderCallback: (ActionIconThemeData? actionIconTheme) {
|
||||
return actionIconTheme?.endDrawerButtonIconBuilder;
|
||||
},
|
||||
getIcon: (BuildContext context) => Icons.menu,
|
||||
getAndroidSemanticsLabel: (MaterialLocalizations materialLocalization) {
|
||||
return materialLocalization.openAppDrawerTooltip;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Material Design end drawer icon button.
|
||||
///
|
||||
/// A [EndDrawerButton] is an [IconButton] with a "drawer" icon. When pressed, the
|
||||
/// end drawer button calls [ScaffoldState.openEndDrawer] to open the [Scaffold.endDrawer].
|
||||
///
|
||||
/// The default behaviour on press can be overriden with [onPressed].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [DrawerButton], an [IconButton] with a [DrawerButtonIcon] that calls
|
||||
/// [ScaffoldState.openDrawer] to open a drawer.
|
||||
/// * [IconButton], which is a more general widget for creating buttons
|
||||
/// with icons.
|
||||
/// * [Icon], a Material Design icon.
|
||||
/// * [ThemeData.platform], which specifies the current platform.
|
||||
class EndDrawerButton extends _ActionButton {
|
||||
/// Creates a Material Design end drawer icon button.
|
||||
const EndDrawerButton({
|
||||
super.key,
|
||||
super.style,
|
||||
super.onPressed,
|
||||
}) : super(icon: const EndDrawerButtonIcon());
|
||||
|
||||
@override
|
||||
void _onPressedCallback(BuildContext context) => Scaffold.of(context).openEndDrawer();
|
||||
|
||||
@override
|
||||
String _getTooltip(BuildContext context) {
|
||||
return MaterialLocalizations.of(context).openAppDrawerTooltip;
|
||||
}
|
||||
}
|
153
packages/flutter/lib/src/material/action_icons_theme.dart
Normal file
153
packages/flutter/lib/src/material/action_icons_theme.dart
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'action_buttons.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
// Examples can assume:
|
||||
// late BuildContext context;
|
||||
|
||||
/// A [ActionIconThemeData] that overrides the default icons of
|
||||
/// [BackButton], [CloseButton], [DrawerButton], and [EndDrawerButton] with
|
||||
/// [ActionIconTheme.of] or the overall [Theme]'s [ThemeData.actionIconTheme].
|
||||
@immutable
|
||||
class ActionIconThemeData with Diagnosticable {
|
||||
/// Creates an [ActionIconThemeData].
|
||||
///
|
||||
/// The builders [backButtonIconBuilder], [closeButtonIconBuilder],
|
||||
/// [drawerButtonIconBuilder], [endDrawerButtonIconBuilder] may be null.
|
||||
const ActionIconThemeData({ this.backButtonIconBuilder, this.closeButtonIconBuilder, this.drawerButtonIconBuilder, this.endDrawerButtonIconBuilder });
|
||||
|
||||
/// Overrides [BackButtonIcon]'s icon.
|
||||
///
|
||||
/// If [backButtonIconBuilder] is null, then [BackButtonIcon]
|
||||
/// fallbacks to the platform's default back button icon.
|
||||
final WidgetBuilder? backButtonIconBuilder;
|
||||
|
||||
/// Overrides [CloseButtonIcon]'s icon.
|
||||
///
|
||||
/// If [closeButtonIconBuilder] is null, then [CloseButtonIcon]
|
||||
/// fallbacks to the platform's default close button icon.
|
||||
final WidgetBuilder? closeButtonIconBuilder;
|
||||
|
||||
/// Overrides [DrawerButtonIcon]'s icon.
|
||||
///
|
||||
/// If [drawerButtonIconBuilder] is null, then [DrawerButtonIcon]
|
||||
/// fallbacks to the platform's default drawer button icon.
|
||||
final WidgetBuilder? drawerButtonIconBuilder;
|
||||
|
||||
/// Overrides [EndDrawerButtonIcon]'s icon.
|
||||
///
|
||||
/// If [endDrawerButtonIconBuilder] is null, then [EndDrawerButtonIcon]
|
||||
/// fallbacks to the platform's default end drawer button icon.
|
||||
final WidgetBuilder? endDrawerButtonIconBuilder;
|
||||
|
||||
/// Creates a copy of this object but with the given fields replaced with the
|
||||
/// new values.
|
||||
ActionIconThemeData copyWith({
|
||||
WidgetBuilder? backButtonIconBuilder,
|
||||
WidgetBuilder? closeButtonIconBuilder,
|
||||
WidgetBuilder? drawerButtonIconBuilder,
|
||||
WidgetBuilder? endDrawerButtonIconBuilder,
|
||||
}) {
|
||||
return ActionIconThemeData(
|
||||
backButtonIconBuilder: backButtonIconBuilder ?? backButtonIconBuilder,
|
||||
closeButtonIconBuilder: closeButtonIconBuilder ?? closeButtonIconBuilder,
|
||||
drawerButtonIconBuilder: drawerButtonIconBuilder ?? drawerButtonIconBuilder,
|
||||
endDrawerButtonIconBuilder: endDrawerButtonIconBuilder ?? endDrawerButtonIconBuilder,
|
||||
);
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two action icon themes.
|
||||
static ActionIconThemeData? lerp(ActionIconThemeData? a, ActionIconThemeData? b, double t) {
|
||||
if (a == null && b == null) {
|
||||
return null;
|
||||
}
|
||||
return ActionIconThemeData(
|
||||
backButtonIconBuilder: t < 0.5 ? a?.backButtonIconBuilder : b?.backButtonIconBuilder,
|
||||
closeButtonIconBuilder: t < 0.5 ? a?.closeButtonIconBuilder : b?.closeButtonIconBuilder,
|
||||
drawerButtonIconBuilder: t < 0.5 ? a?.drawerButtonIconBuilder : b?.drawerButtonIconBuilder,
|
||||
endDrawerButtonIconBuilder: t < 0.5 ? a?.endDrawerButtonIconBuilder : b?.endDrawerButtonIconBuilder,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
final List<Object?> values = <Object?>[
|
||||
backButtonIconBuilder,
|
||||
closeButtonIconBuilder,
|
||||
drawerButtonIconBuilder,
|
||||
endDrawerButtonIconBuilder,
|
||||
];
|
||||
return Object.hashAll(values);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is ActionIconThemeData
|
||||
&& other.backButtonIconBuilder == backButtonIconBuilder
|
||||
&& other.closeButtonIconBuilder == closeButtonIconBuilder
|
||||
&& other.drawerButtonIconBuilder == drawerButtonIconBuilder
|
||||
&& other.endDrawerButtonIconBuilder == endDrawerButtonIconBuilder;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<WidgetBuilder>('backButtonIconBuilder', backButtonIconBuilder, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<WidgetBuilder>('closeButtonIconBuilder', closeButtonIconBuilder, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<WidgetBuilder>('drawerButtonIconBuilder', drawerButtonIconBuilder, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<WidgetBuilder>('endDrawerButtonIconBuilder', endDrawerButtonIconBuilder, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
/// An inherited widget that overrides the default icon of [BackButtonIcon],
|
||||
/// [CloseButtonIcon], [DrawerButtonIcon], and [EndDrawerButtonIcon] in this
|
||||
/// widget's subtree.
|
||||
class ActionIconTheme extends InheritedTheme {
|
||||
/// Creates a theme that overrides the default icon of [BackButtonIcon],
|
||||
/// [CloseButtonIcon], [DrawerButtonIcon], and [EndDrawerButtonIcon] in this
|
||||
/// widget's subtree.
|
||||
const ActionIconTheme({
|
||||
super.key,
|
||||
required this.data,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
/// Specifies the default icon overrides for descendant [BackButtonIcon],
|
||||
/// [CloseButtonIcon], [DrawerButtonIcon], and [EndDrawerButtonIcon] widgets.
|
||||
final ActionIconThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ActionIconTheme] widget, then
|
||||
/// [ThemeData.actionIconTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// ActionIconThemeData? theme = ActionIconTheme.of(context);
|
||||
/// ```
|
||||
static ActionIconThemeData? of(BuildContext context) {
|
||||
final ActionIconTheme? actionIconTheme = context.dependOnInheritedWidgetOfExactType<ActionIconTheme>();
|
||||
return actionIconTheme?.data ?? Theme.of(context).actionIconTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) {
|
||||
return ActionIconTheme(data: data, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ActionIconTheme oldWidget) => data != oldWidget.data;
|
||||
}
|
|
@ -9,8 +9,8 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'action_buttons.dart';
|
||||
import 'app_bar_theme.dart';
|
||||
import 'back_button.dart';
|
||||
import 'button_style.dart';
|
||||
import 'color_scheme.dart';
|
||||
import 'colors.dart';
|
||||
|
@ -21,7 +21,6 @@ import 'icon_button.dart';
|
|||
import 'icon_button_theme.dart';
|
||||
import 'icons.dart';
|
||||
import 'material.dart';
|
||||
import 'material_localizations.dart';
|
||||
import 'material_state.dart';
|
||||
import 'scaffold.dart';
|
||||
import 'tabs.dart';
|
||||
|
@ -755,14 +754,6 @@ class _AppBarState extends State<AppBar> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleDrawerButton() {
|
||||
Scaffold.of(context).openDrawer();
|
||||
}
|
||||
|
||||
void _handleDrawerButtonEnd() {
|
||||
Scaffold.of(context).openEndDrawer();
|
||||
}
|
||||
|
||||
void _handleScrollNotification(ScrollNotification notification) {
|
||||
if (notification is ScrollUpdateNotification && widget.notificationPredicate(notification)) {
|
||||
final bool oldScrolledUnder = _scrolledUnder;
|
||||
|
@ -894,11 +885,8 @@ class _AppBarState extends State<AppBar> {
|
|||
Widget? leading = widget.leading;
|
||||
if (leading == null && widget.automaticallyImplyLeading) {
|
||||
if (hasDrawer) {
|
||||
leading = IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
iconSize: overallIconTheme.size ?? 24,
|
||||
onPressed: _handleDrawerButton,
|
||||
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
|
||||
leading = DrawerButton(
|
||||
style: IconButton.styleFrom(iconSize: overallIconTheme.size ?? 24),
|
||||
);
|
||||
// TODO(chunhtai): remove (!hasEndDrawer && canPop) once internal tests
|
||||
// are migrated.
|
||||
|
@ -1009,11 +997,8 @@ class _AppBarState extends State<AppBar> {
|
|||
children: widget.actions!,
|
||||
);
|
||||
} else if (hasEndDrawer) {
|
||||
actions = IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
iconSize: overallIconTheme.size ?? 24,
|
||||
onPressed: _handleDrawerButtonEnd,
|
||||
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
|
||||
actions = EndDrawerButton(
|
||||
style: IconButton.styleFrom(iconSize: overallIconTheme.size ?? 24),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,197 +2,4 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'debug.dart';
|
||||
import 'icon_button.dart';
|
||||
import 'icons.dart';
|
||||
import 'material_localizations.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// A "back" icon that's appropriate for the current [TargetPlatform].
|
||||
///
|
||||
/// The current platform is determined by querying for the ambient [Theme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [BackButton], an [IconButton] with a [BackButtonIcon] that calls
|
||||
/// [Navigator.maybePop] to return to the previous route.
|
||||
/// * [IconButton], which is a more general widget for creating buttons
|
||||
/// with icons.
|
||||
/// * [Icon], a Material Design icon.
|
||||
/// * [ThemeData.platform], which specifies the current platform.
|
||||
class BackButtonIcon extends StatelessWidget {
|
||||
/// Creates an icon that shows the appropriate "back" image for
|
||||
/// the current platform (as obtained from the [Theme]).
|
||||
const BackButtonIcon({ super.key });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String? semanticsLabel;
|
||||
final IconData data;
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
data = Icons.arrow_back;
|
||||
break;
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
data = Icons.arrow_back_ios;
|
||||
break;
|
||||
}
|
||||
// This can't use the platform from Theme because it is the Android OS that
|
||||
// expects the duplicated tooltip and label.
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
semanticsLabel = MaterialLocalizations.of(context).backButtonTooltip;
|
||||
break;
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
semanticsLabel = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return Icon(data, semanticLabel: semanticsLabel);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Material Design back button.
|
||||
///
|
||||
/// A [BackButton] is an [IconButton] with a "back" icon appropriate for the
|
||||
/// current [TargetPlatform]. When pressed, the back button calls
|
||||
/// [Navigator.maybePop] to return to the previous route unless a custom
|
||||
/// [onPressed] callback is provided.
|
||||
///
|
||||
/// When deciding to display a [BackButton], consider using
|
||||
/// `ModalRoute.of(context)?.canPop` to check whether the current route can be
|
||||
/// popped. If that value is false (e.g., because the current route is the
|
||||
/// initial route), the [BackButton] will not have any effect when pressed,
|
||||
/// which could frustrate the user.
|
||||
///
|
||||
/// Requires one of its ancestors to be a [Material] widget.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar], which automatically uses a [BackButton] in its
|
||||
/// [AppBar.leading] slot when the [Scaffold] has no [Drawer] and the
|
||||
/// current [Route] is not the [Navigator]'s first route.
|
||||
/// * [BackButtonIcon], which is useful if you need to create a back button
|
||||
/// that responds differently to being pressed.
|
||||
/// * [IconButton], which is a more general widget for creating buttons with
|
||||
/// icons.
|
||||
/// * [CloseButton], an alternative which may be more appropriate for leaf
|
||||
/// node pages in the navigation tree.
|
||||
class BackButton extends StatelessWidget {
|
||||
/// Creates an [IconButton] with the appropriate "back" icon for the current
|
||||
/// target platform.
|
||||
const BackButton({ super.key, this.color, this.onPressed });
|
||||
|
||||
/// The color to use for the icon.
|
||||
///
|
||||
/// Defaults to the [IconThemeData.color] specified in the ambient [IconTheme],
|
||||
/// which usually matches the ambient [Theme]'s [ThemeData.iconTheme].
|
||||
final Color? color;
|
||||
|
||||
/// An override callback to perform instead of the default behavior which is
|
||||
/// to pop the [Navigator].
|
||||
///
|
||||
/// It can, for instance, be used to pop the platform's navigation stack
|
||||
/// via [SystemNavigator] instead of Flutter's [Navigator] in add-to-app
|
||||
/// situations.
|
||||
///
|
||||
/// Defaults to null.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
return IconButton(
|
||||
icon: const BackButtonIcon(),
|
||||
color: color,
|
||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
onPressed: () {
|
||||
if (onPressed != null) {
|
||||
onPressed!();
|
||||
} else {
|
||||
Navigator.maybePop(context);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A Material Design close button.
|
||||
///
|
||||
/// A [CloseButton] is an [IconButton] with a "close" icon. When pressed, the
|
||||
/// close button calls [Navigator.maybePop] to return to the previous route.
|
||||
///
|
||||
/// Use a [CloseButton] instead of a [BackButton] on fullscreen dialogs or
|
||||
/// pages that may solicit additional actions to close.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar], which automatically uses a [CloseButton] in its
|
||||
/// [AppBar.leading] slot when appropriate.
|
||||
/// * [BackButton], which is more appropriate for middle nodes in the
|
||||
/// navigation tree or where pages can be popped instantaneously with
|
||||
/// no user data consequence.
|
||||
/// * [IconButton], to create other Material Design icon buttons.
|
||||
class CloseButton extends StatelessWidget {
|
||||
/// Creates a Material Design close button.
|
||||
const CloseButton({ super.key, this.color, this.onPressed });
|
||||
|
||||
/// The color to use for the icon.
|
||||
///
|
||||
/// Defaults to the [IconThemeData.color] specified in the ambient [IconTheme],
|
||||
/// which usually matches the ambient [Theme]'s [ThemeData.iconTheme].
|
||||
final Color? color;
|
||||
|
||||
/// An override callback to perform instead of the default behavior which is
|
||||
/// to pop the [Navigator].
|
||||
///
|
||||
/// It can, for instance, be used to pop the platform's navigation stack
|
||||
/// via [SystemNavigator] instead of Flutter's [Navigator] in add-to-app
|
||||
/// situations.
|
||||
///
|
||||
/// Defaults to null.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
final String? semanticsLabel;
|
||||
// This can't use the platform from Theme because it is the Android OS that
|
||||
// expects the duplicated tooltip and label.
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
semanticsLabel = MaterialLocalizations.of(context).closeButtonTooltip;
|
||||
break;
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
semanticsLabel = null;
|
||||
break;
|
||||
}
|
||||
return IconButton(
|
||||
icon: Icon(Icons.close, semanticLabel: semanticsLabel),
|
||||
color: color,
|
||||
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
|
||||
onPressed: () {
|
||||
if (onPressed != null) {
|
||||
onPressed!();
|
||||
} else {
|
||||
Navigator.maybePop(context);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
export 'action_buttons.dart' show BackButton, BackButtonIcon, CloseButton, CloseButtonIcon;
|
||||
|
|
|
@ -7,6 +7,8 @@ import 'dart:ui' show Color, lerpDouble;
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'action_buttons.dart';
|
||||
import 'action_icons_theme.dart';
|
||||
import 'app_bar_theme.dart';
|
||||
import 'badge_theme.dart';
|
||||
import 'banner_theme.dart';
|
||||
|
@ -337,6 +339,7 @@ class ThemeData with Diagnosticable {
|
|||
TextTheme? textTheme,
|
||||
Typography? typography,
|
||||
// COMPONENT THEMES
|
||||
ActionIconThemeData? actionIconTheme,
|
||||
AppBarTheme? appBarTheme,
|
||||
BadgeThemeData? badgeTheme,
|
||||
MaterialBannerThemeData? bannerTheme,
|
||||
|
@ -649,6 +652,7 @@ class ThemeData with Diagnosticable {
|
|||
typography: typography,
|
||||
primaryIconTheme: primaryIconTheme,
|
||||
// COMPONENT THEMES
|
||||
actionIconTheme: actionIconTheme,
|
||||
appBarTheme: appBarTheme,
|
||||
badgeTheme: badgeTheme,
|
||||
bannerTheme: bannerTheme,
|
||||
|
@ -759,6 +763,7 @@ class ThemeData with Diagnosticable {
|
|||
required this.textTheme,
|
||||
required this.typography,
|
||||
// COMPONENT THEMES
|
||||
required this.actionIconTheme,
|
||||
required this.appBarTheme,
|
||||
required this.badgeTheme,
|
||||
required this.bannerTheme,
|
||||
|
@ -1356,6 +1361,10 @@ class ThemeData with Diagnosticable {
|
|||
|
||||
// COMPONENT THEMES
|
||||
|
||||
/// A theme for customizing icons of [BackButtonIcon], [CloseButtonIcon],
|
||||
/// [DrawerButtonIcon], or [EndDrawerButtonIcon].
|
||||
final ActionIconThemeData? actionIconTheme;
|
||||
|
||||
/// A theme for customizing the color, elevation, brightness, iconTheme and
|
||||
/// textTheme of [AppBar]s.
|
||||
final AppBarTheme appBarTheme;
|
||||
|
@ -1654,6 +1663,7 @@ class ThemeData with Diagnosticable {
|
|||
TextTheme? textTheme,
|
||||
Typography? typography,
|
||||
// COMPONENT THEMES
|
||||
ActionIconThemeData? actionIconTheme,
|
||||
AppBarTheme? appBarTheme,
|
||||
BadgeThemeData? badgeTheme,
|
||||
MaterialBannerThemeData? bannerTheme,
|
||||
|
@ -1787,6 +1797,7 @@ class ThemeData with Diagnosticable {
|
|||
textTheme: textTheme ?? this.textTheme,
|
||||
typography: typography ?? this.typography,
|
||||
// COMPONENT THEMES
|
||||
actionIconTheme: actionIconTheme ?? this.actionIconTheme,
|
||||
appBarTheme: appBarTheme ?? this.appBarTheme,
|
||||
badgeTheme: badgeTheme ?? this.badgeTheme,
|
||||
bannerTheme: bannerTheme ?? this.bannerTheme,
|
||||
|
@ -1980,6 +1991,7 @@ class ThemeData with Diagnosticable {
|
|||
textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t),
|
||||
typography: Typography.lerp(a.typography, b.typography, t),
|
||||
// COMPONENT THEMES
|
||||
actionIconTheme: ActionIconThemeData.lerp(a.actionIconTheme, b.actionIconTheme, t),
|
||||
appBarTheme: AppBarTheme.lerp(a.appBarTheme, b.appBarTheme, t),
|
||||
badgeTheme: BadgeThemeData.lerp(a.badgeTheme, b.badgeTheme, t),
|
||||
bannerTheme: MaterialBannerThemeData.lerp(a.bannerTheme, b.bannerTheme, t),
|
||||
|
@ -2085,6 +2097,7 @@ class ThemeData with Diagnosticable {
|
|||
other.textTheme == textTheme &&
|
||||
other.typography == typography &&
|
||||
// COMPONENT THEMES
|
||||
other.actionIconTheme == actionIconTheme &&
|
||||
other.appBarTheme == appBarTheme &&
|
||||
other.badgeTheme == badgeTheme &&
|
||||
other.bannerTheme == bannerTheme &&
|
||||
|
@ -2187,6 +2200,7 @@ class ThemeData with Diagnosticable {
|
|||
textTheme,
|
||||
typography,
|
||||
// COMPONENT THEMES
|
||||
actionIconTheme,
|
||||
appBarTheme,
|
||||
badgeTheme,
|
||||
bannerTheme,
|
||||
|
@ -2291,6 +2305,7 @@ class ThemeData with Diagnosticable {
|
|||
properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography, level: DiagnosticLevel.debug));
|
||||
// COMPONENT THEMES
|
||||
properties.add(DiagnosticsProperty<ActionIconThemeData>('actionIconTheme', actionIconTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<AppBarTheme>('appBarTheme', appBarTheme, defaultValue: defaultData.appBarTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<BadgeThemeData>('badgeTheme', badgeTheme, defaultValue: defaultData.badgeTheme, level: DiagnosticLevel.debug));
|
||||
properties.add(DiagnosticsProperty<MaterialBannerThemeData>('bannerTheme', bannerTheme, defaultValue: defaultData.bannerTheme, level: DiagnosticLevel.debug));
|
||||
|
|
183
packages/flutter/test/material/action_icons_theme_test.dart
Normal file
183
packages/flutter/test/material/action_icons_theme_test.dart
Normal file
|
@ -0,0 +1,183 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('ActionIconThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const ActionIconThemeData(), const ActionIconThemeData().copyWith());
|
||||
expect(const ActionIconThemeData().hashCode,
|
||||
const ActionIconThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
test('ActionIconThemeData defaults', () {
|
||||
const ActionIconThemeData themeData = ActionIconThemeData();
|
||||
expect(themeData.backButtonIconBuilder, null);
|
||||
expect(themeData.closeButtonIconBuilder, null);
|
||||
expect(themeData.drawerButtonIconBuilder, null);
|
||||
expect(themeData.endDrawerButtonIconBuilder, null);
|
||||
});
|
||||
|
||||
testWidgets('Default ActionIconThemeData debugFillProperties',
|
||||
(WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const ActionIconThemeData().debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[]);
|
||||
});
|
||||
|
||||
testWidgets('ActionIconThemeData implements debugFillProperties',
|
||||
(WidgetTester tester) async {
|
||||
Widget actionButtonIconBuilder(BuildContext context) {
|
||||
return const Icon(IconData(0));
|
||||
}
|
||||
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
ActionIconThemeData(
|
||||
backButtonIconBuilder: actionButtonIconBuilder,
|
||||
closeButtonIconBuilder: actionButtonIconBuilder,
|
||||
drawerButtonIconBuilder: actionButtonIconBuilder,
|
||||
endDrawerButtonIconBuilder: actionButtonIconBuilder,
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
final Matcher containsBuilderCallback = contains('Closure: (BuildContext) =>');
|
||||
expect(description, <dynamic>[
|
||||
allOf(startsWith('backButtonIconBuilder:'), containsBuilderCallback),
|
||||
allOf(startsWith('closeButtonIconBuilder:'), containsBuilderCallback),
|
||||
allOf(startsWith('drawerButtonIconBuilder:'), containsBuilderCallback),
|
||||
allOf(startsWith('endDrawerButtonIconBuilder:'), containsBuilderCallback),
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('Action buttons use ThemeData action icon theme', (WidgetTester tester) async {
|
||||
const Color green = Color(0xff00ff00);
|
||||
const IconData icon = IconData(0);
|
||||
|
||||
Widget buildSampleIcon(BuildContext context) {
|
||||
return const Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: green,
|
||||
);
|
||||
}
|
||||
|
||||
final ActionIconThemeData actionIconTheme = ActionIconThemeData(
|
||||
backButtonIconBuilder: buildSampleIcon,
|
||||
closeButtonIconBuilder: buildSampleIcon,
|
||||
drawerButtonIconBuilder: buildSampleIcon,
|
||||
endDrawerButtonIconBuilder: buildSampleIcon,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData.light(useMaterial3: true).copyWith(
|
||||
actionIconTheme: actionIconTheme,
|
||||
),
|
||||
home: const Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
BackButton(),
|
||||
CloseButton(),
|
||||
DrawerButton(),
|
||||
EndDrawerButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Icon backButtonIcon = tester.widget(find.descendant(of: find.byType(BackButton), matching: find.byType(Icon)));
|
||||
final Icon closeButtonIcon = tester.widget(find.descendant(of: find.byType(CloseButton), matching: find.byType(Icon)));
|
||||
final Icon drawerButtonIcon = tester.widget(find.descendant(of: find.byType(DrawerButton), matching: find.byType(Icon)));
|
||||
final Icon endDrawerButtonIcon = tester.widget(find.descendant(of: find.byType(EndDrawerButton), matching: find.byType(Icon)));
|
||||
|
||||
expect(backButtonIcon.icon == icon, isTrue);
|
||||
expect(closeButtonIcon.icon == icon, isTrue);
|
||||
expect(drawerButtonIcon.icon == icon, isTrue);
|
||||
expect(endDrawerButtonIcon.icon == icon, isTrue);
|
||||
|
||||
final RichText backButtonIconText = tester.widget(find.descendant(of: find.byType(BackButton), matching: find.byType(RichText)));
|
||||
final RichText closeButtonIconText = tester.widget(find.descendant(of: find.byType(CloseButton), matching: find.byType(RichText)));
|
||||
final RichText drawerButtonIconText = tester.widget(find.descendant(of: find.byType(DrawerButton), matching: find.byType(RichText)));
|
||||
final RichText endDrawerButtonIconText = tester.widget(find.descendant(of: find.byType(EndDrawerButton), matching: find.byType(RichText)));
|
||||
|
||||
expect(backButtonIconText.text.style!.color, green);
|
||||
expect(closeButtonIconText.text.style!.color, green);
|
||||
expect(drawerButtonIconText.text.style!.color, green);
|
||||
expect(endDrawerButtonIconText.text.style!.color, green);
|
||||
});
|
||||
|
||||
// This test is essentially the same as 'Action buttons use ThemeData action icon theme'. In
|
||||
// this case the theme is introduced with the ActionIconTheme widget instead of
|
||||
// ThemeData.actionIconTheme.
|
||||
testWidgets('Action buttons use ActionIconTheme', (WidgetTester tester) async {
|
||||
const Color green = Color(0xff00ff00);
|
||||
const IconData icon = IconData(0);
|
||||
|
||||
Widget buildSampleIcon(BuildContext context) {
|
||||
return const Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: green,
|
||||
);
|
||||
}
|
||||
|
||||
final ActionIconThemeData actionIconTheme = ActionIconThemeData(
|
||||
backButtonIconBuilder: buildSampleIcon,
|
||||
closeButtonIconBuilder: buildSampleIcon,
|
||||
drawerButtonIconBuilder: buildSampleIcon,
|
||||
endDrawerButtonIconBuilder: buildSampleIcon,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: ActionIconTheme(
|
||||
data: actionIconTheme,
|
||||
child: const Material(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
BackButton(),
|
||||
CloseButton(),
|
||||
DrawerButton(),
|
||||
EndDrawerButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Icon backButtonIcon = tester.widget(find.descendant(of: find.byType(BackButton), matching: find.byType(Icon)));
|
||||
final Icon closeButtonIcon = tester.widget(find.descendant(of: find.byType(CloseButton), matching: find.byType(Icon)));
|
||||
final Icon drawerButtonIcon = tester.widget(find.descendant(of: find.byType(DrawerButton), matching: find.byType(Icon)));
|
||||
final Icon endDrawerButtonIcon = tester.widget(find.descendant(of: find.byType(EndDrawerButton), matching: find.byType(Icon)));
|
||||
|
||||
expect(backButtonIcon.icon == icon, isTrue);
|
||||
expect(closeButtonIcon.icon == icon, isTrue);
|
||||
expect(drawerButtonIcon.icon == icon, isTrue);
|
||||
expect(endDrawerButtonIcon.icon == icon, isTrue);
|
||||
|
||||
final RichText backButtonIconText = tester.widget(find.descendant(of: find.byType(BackButton), matching: find.byType(RichText)));
|
||||
final RichText closeButtonIconText = tester.widget(find.descendant(of: find.byType(CloseButton), matching: find.byType(RichText)));
|
||||
final RichText drawerButtonIconText = tester.widget(find.descendant(of: find.byType(DrawerButton), matching: find.byType(RichText)));
|
||||
final RichText endDrawerButtonIconText = tester.widget(find.descendant(of: find.byType(EndDrawerButton), matching: find.byType(RichText)));
|
||||
|
||||
expect(backButtonIconText.text.style!.color, green);
|
||||
expect(closeButtonIconText.text.style!.color, green);
|
||||
expect(drawerButtonIconText.text.style!.color, green);
|
||||
expect(endDrawerButtonIconText.text.style!.color, green);
|
||||
});
|
||||
}
|
|
@ -108,9 +108,9 @@ void main() {
|
|||
final Icon linuxIcon = tester.widget(find.descendant(of: find.byKey(linuxKey), matching: find.byType(Icon)));
|
||||
final Icon macOSIcon = tester.widget(find.descendant(of: find.byKey(macOSKey), matching: find.byType(Icon)));
|
||||
final Icon windowsIcon = tester.widget(find.descendant(of: find.byKey(windowsKey), matching: find.byType(Icon)));
|
||||
expect(iOSIcon.icon == androidIcon.icon, isFalse);
|
||||
expect(iOSIcon.icon == androidIcon.icon, kIsWeb ? isTrue : isFalse);
|
||||
expect(linuxIcon.icon == androidIcon.icon, isTrue);
|
||||
expect(macOSIcon.icon == androidIcon.icon, isFalse);
|
||||
expect(macOSIcon.icon == androidIcon.icon, kIsWeb ? isTrue : isFalse);
|
||||
expect(macOSIcon.icon == iOSIcon.icon, isTrue);
|
||||
expect(windowsIcon.icon == androidIcon.icon, isTrue);
|
||||
});
|
||||
|
@ -120,7 +120,7 @@ void main() {
|
|||
const MaterialApp(
|
||||
home: Material(
|
||||
child: BackButton(
|
||||
color: Colors.blue,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -130,7 +130,51 @@ void main() {
|
|||
of: find.byType(BackButton),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(iconText.text.style!.color, Colors.blue);
|
||||
expect(iconText.text.style!.color, Colors.red);
|
||||
});
|
||||
|
||||
testWidgets('BackButton color with ButtonStyle', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true),
|
||||
home: const Material(
|
||||
child: BackButton(
|
||||
style: ButtonStyle(
|
||||
iconColor: MaterialStatePropertyAll<Color>(Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RichText iconText = tester.firstWidget(find.descendant(
|
||||
of: find.byType(BackButton),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(iconText.text.style!.color, Colors.red);
|
||||
});
|
||||
|
||||
testWidgets('BackButton.style.iconColor parameter overrides BackButton.color', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true),
|
||||
home: const Material(
|
||||
child: BackButton(
|
||||
color: Colors.green,
|
||||
style: ButtonStyle(
|
||||
iconColor: MaterialStatePropertyAll<Color>(Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RichText iconText = tester.firstWidget(find.descendant(
|
||||
of: find.byType(BackButton),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
|
||||
expect(iconText.text.style!.color, Colors.red);
|
||||
});
|
||||
|
||||
testWidgets('BackButton semantics', (WidgetTester tester) async {
|
||||
|
@ -239,6 +283,50 @@ void main() {
|
|||
expect(iconText.text.style!.color, Colors.red);
|
||||
});
|
||||
|
||||
testWidgets('CloseButton color with ButtonStyle', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true),
|
||||
home: const Material(
|
||||
child: CloseButton(
|
||||
style: ButtonStyle(
|
||||
iconColor: MaterialStatePropertyAll<Color>(Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RichText iconText = tester.firstWidget(find.descendant(
|
||||
of: find.byType(CloseButton),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(iconText.text.style!.color, Colors.red);
|
||||
});
|
||||
|
||||
testWidgets('CloseButton.style.iconColor parameter overrides CloseButton.color', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true),
|
||||
home: const Material(
|
||||
child: CloseButton(
|
||||
color: Colors.green,
|
||||
style: ButtonStyle(
|
||||
iconColor: MaterialStatePropertyAll<Color>(Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RichText iconText = tester.firstWidget(find.descendant(
|
||||
of: find.byType(CloseButton),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
|
||||
expect(iconText.text.style!.color, Colors.red);
|
||||
});
|
||||
|
||||
testWidgets('CloseButton onPressed overrides default pop behavior', (WidgetTester tester) async {
|
||||
bool customCallbackWasCalled = false;
|
||||
await tester.pumpWidget(
|
||||
|
|
279
packages/flutter/test/material/drawer_button_test.dart
Normal file
279
packages/flutter/test/material/drawer_button_test.dart
Normal file
|
@ -0,0 +1,279 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('DrawerButton control test', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: DrawerButton(),
|
||||
drawer: Drawer(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(Drawer), findsNothing);
|
||||
|
||||
await tester.tap(find.byType(DrawerButton));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(Drawer), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('DrawerButton onPressed overrides default end drawer open behaviour',
|
||||
(WidgetTester tester) async {
|
||||
bool customCallbackWasCalled = false;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: DrawerButton(
|
||||
onPressed: () => customCallbackWasCalled = true),
|
||||
),
|
||||
drawer: const Drawer(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(Drawer), findsNothing); // Start off with a closed drawer
|
||||
expect(customCallbackWasCalled,
|
||||
false); // customCallbackWasCalled should still be false.
|
||||
await tester.tap(find.byType(DrawerButton));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Drawer is still closed
|
||||
expect(find.byType(Drawer), findsNothing);
|
||||
// The custom callback is called, setting customCallbackWasCalled to true.
|
||||
expect(customCallbackWasCalled, true);
|
||||
});
|
||||
|
||||
testWidgets('DrawerButton icon', (WidgetTester tester) async {
|
||||
final Key androidKey = UniqueKey();
|
||||
final Key iOSKey = UniqueKey();
|
||||
final Key linuxKey = UniqueKey();
|
||||
final Key macOSKey = UniqueKey();
|
||||
final Key windowsKey = UniqueKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Column(
|
||||
children: <Widget>[
|
||||
Theme(
|
||||
data: ThemeData(platform: TargetPlatform.android),
|
||||
child: DrawerButtonIcon(key: androidKey),
|
||||
),
|
||||
Theme(
|
||||
data: ThemeData(platform: TargetPlatform.iOS),
|
||||
child: DrawerButtonIcon(key: iOSKey),
|
||||
),
|
||||
Theme(
|
||||
data: ThemeData(platform: TargetPlatform.linux),
|
||||
child: DrawerButtonIcon(key: linuxKey),
|
||||
),
|
||||
Theme(
|
||||
data: ThemeData(platform: TargetPlatform.macOS),
|
||||
child: DrawerButtonIcon(key: macOSKey),
|
||||
),
|
||||
Theme(
|
||||
data: ThemeData(platform: TargetPlatform.windows),
|
||||
child: DrawerButtonIcon(key: windowsKey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Icon androidIcon = tester.widget(find.descendant(
|
||||
of: find.byKey(androidKey), matching: find.byType(Icon)));
|
||||
final Icon iOSIcon = tester.widget(
|
||||
find.descendant(of: find.byKey(iOSKey), matching: find.byType(Icon)));
|
||||
final Icon linuxIcon = tester.widget(
|
||||
find.descendant(of: find.byKey(linuxKey), matching: find.byType(Icon)));
|
||||
final Icon macOSIcon = tester.widget(
|
||||
find.descendant(of: find.byKey(macOSKey), matching: find.byType(Icon)));
|
||||
final Icon windowsIcon = tester.widget(find.descendant(
|
||||
of: find.byKey(windowsKey), matching: find.byType(Icon)));
|
||||
|
||||
// All icons for drawer are the same
|
||||
expect(iOSIcon.icon == androidIcon.icon, isTrue);
|
||||
expect(linuxIcon.icon == androidIcon.icon, isTrue);
|
||||
expect(macOSIcon.icon == androidIcon.icon, isTrue);
|
||||
expect(windowsIcon.icon == androidIcon.icon, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('DrawerButton color', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true),
|
||||
home: const Material(
|
||||
child: DrawerButton(
|
||||
style: ButtonStyle(
|
||||
iconColor: MaterialStatePropertyAll<Color>(Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RichText iconText = tester.firstWidget(find.descendant(
|
||||
of: find.byType(DrawerButton),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(iconText.text.style!.color, Colors.red);
|
||||
});
|
||||
|
||||
testWidgets('DrawerButton semantics', (WidgetTester tester) async {
|
||||
final SemanticsHandle handle = tester.ensureSemantics();
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: DrawerButton(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final String? expectedLabel;
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
expectedLabel = 'Open navigation menu';
|
||||
break;
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.windows:
|
||||
expectedLabel = null;
|
||||
}
|
||||
expect(tester.getSemantics(find.byType(DrawerButton)), matchesSemantics(
|
||||
tooltip: 'Open navigation menu',
|
||||
label: expectedLabel,
|
||||
isButton: true,
|
||||
hasEnabledState: true,
|
||||
isEnabled: true,
|
||||
hasTapAction: true,
|
||||
isFocusable: true,
|
||||
));
|
||||
handle.dispose();
|
||||
}, variant: TargetPlatformVariant.all());
|
||||
|
||||
testWidgets('EndDrawerButton control test', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: EndDrawerButton(),
|
||||
endDrawer: Drawer(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(Drawer), findsNothing);
|
||||
|
||||
await tester.tap(find.byType(EndDrawerButton));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(Drawer), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('EndDrawerButton semantics', (WidgetTester tester) async {
|
||||
final SemanticsHandle handle = tester.ensureSemantics();
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Material(
|
||||
child: Center(
|
||||
child: EndDrawerButton(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
final String? expectedLabel;
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
expectedLabel = 'Open navigation menu';
|
||||
break;
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.windows:
|
||||
expectedLabel = null;
|
||||
}
|
||||
expect(tester.getSemantics(find.byType(EndDrawerButton)), matchesSemantics(
|
||||
tooltip: 'Open navigation menu',
|
||||
label: expectedLabel,
|
||||
isButton: true,
|
||||
hasEnabledState: true,
|
||||
isEnabled: true,
|
||||
hasTapAction: true,
|
||||
isFocusable: true,
|
||||
));
|
||||
handle.dispose();
|
||||
}, variant: TargetPlatformVariant.all());
|
||||
|
||||
testWidgets('EndDrawerButton color', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: true),
|
||||
home: const Material(
|
||||
child: EndDrawerButton(
|
||||
style: ButtonStyle(
|
||||
iconColor: MaterialStatePropertyAll<Color>(Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RichText iconText = tester.firstWidget(find.descendant(
|
||||
of: find.byType(EndDrawerButton),
|
||||
matching: find.byType(RichText),
|
||||
));
|
||||
expect(iconText.text.style!.color, Colors.red);
|
||||
});
|
||||
|
||||
testWidgets('EndDrawerButton onPressed overrides default end drawer open behaviour',
|
||||
(WidgetTester tester) async {
|
||||
bool customCallbackWasCalled = false;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: EndDrawerButton(onPressed: () => customCallbackWasCalled = true),
|
||||
),
|
||||
endDrawer: const Drawer(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(Drawer), findsNothing); // Start off with a closed drawer
|
||||
expect(customCallbackWasCalled,
|
||||
false); // customCallbackWasCalled should still be false.
|
||||
await tester.tap(find.byType(EndDrawerButton));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Drawer is still closed
|
||||
expect(find.byType(Drawer), findsNothing);
|
||||
// The custom callback is called, setting customCallbackWasCalled to true.
|
||||
expect(customCallbackWasCalled, true);
|
||||
});
|
||||
}
|
|
@ -769,7 +769,7 @@ void main() {
|
|||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia }));
|
||||
|
||||
testWidgets('Back arrow uses correct default', (WidgetTester tester) async {
|
||||
await expectBackIcon(tester, Icons.arrow_back_ios);
|
||||
await expectBackIcon(tester, kIsWeb ? Icons.arrow_back : Icons.arrow_back_ios);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
});
|
||||
|
||||
|
|
|
@ -757,6 +757,7 @@ void main() {
|
|||
textTheme: ThemeData.dark().textTheme,
|
||||
typography: Typography.material2018(),
|
||||
// COMPONENT THEMES
|
||||
actionIconTheme: const ActionIconThemeData(),
|
||||
appBarTheme: const AppBarTheme(backgroundColor: Colors.black),
|
||||
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
|
||||
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.black),
|
||||
|
@ -874,6 +875,7 @@ void main() {
|
|||
typography: Typography.material2018(platform: TargetPlatform.iOS),
|
||||
|
||||
// COMPONENT THEMES
|
||||
actionIconTheme: const ActionIconThemeData(),
|
||||
appBarTheme: const AppBarTheme(backgroundColor: Colors.white),
|
||||
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
|
||||
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.white),
|
||||
|
@ -977,6 +979,7 @@ void main() {
|
|||
typography: otherTheme.typography,
|
||||
|
||||
// COMPONENT THEMES
|
||||
actionIconTheme: otherTheme.actionIconTheme,
|
||||
appBarTheme: otherTheme.appBarTheme,
|
||||
badgeTheme: otherTheme.badgeTheme,
|
||||
bannerTheme: otherTheme.bannerTheme,
|
||||
|
@ -1077,6 +1080,7 @@ void main() {
|
|||
expect(themeDataCopy.typography, equals(otherTheme.typography));
|
||||
|
||||
// COMPONENT THEMES
|
||||
expect(themeDataCopy.actionIconTheme, equals(otherTheme.actionIconTheme));
|
||||
expect(themeDataCopy.appBarTheme, equals(otherTheme.appBarTheme));
|
||||
expect(themeDataCopy.badgeTheme, equals(otherTheme.badgeTheme));
|
||||
expect(themeDataCopy.bannerTheme, equals(otherTheme.bannerTheme));
|
||||
|
@ -1211,6 +1215,7 @@ void main() {
|
|||
'iconTheme',
|
||||
'primaryIconTheme',
|
||||
// COMPONENT THEMES
|
||||
'actionIconTheme',
|
||||
'appBarTheme',
|
||||
'badgeTheme',
|
||||
'bannerTheme',
|
||||
|
|
Loading…
Reference in a new issue