mirror of
https://github.com/flutter/flutter
synced 2024-09-19 16:21:58 +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;
|
library material;
|
||||||
|
|
||||||
export 'src/material/about.dart';
|
export 'src/material/about.dart';
|
||||||
|
export 'src/material/action_buttons.dart';
|
||||||
export 'src/material/action_chip.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/adaptive_text_selection_toolbar.dart';
|
||||||
export 'src/material/animated_icons.dart';
|
export 'src/material/animated_icons.dart';
|
||||||
export 'src/material/app.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/app_bar_theme.dart';
|
||||||
export 'src/material/arc.dart';
|
export 'src/material/arc.dart';
|
||||||
export 'src/material/autocomplete.dart';
|
export 'src/material/autocomplete.dart';
|
||||||
export 'src/material/back_button.dart';
|
|
||||||
export 'src/material/badge.dart';
|
export 'src/material/badge.dart';
|
||||||
export 'src/material/badge_theme.dart';
|
export 'src/material/badge_theme.dart';
|
||||||
export 'src/material/banner.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/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'action_buttons.dart';
|
||||||
import 'app_bar_theme.dart';
|
import 'app_bar_theme.dart';
|
||||||
import 'back_button.dart';
|
|
||||||
import 'button_style.dart';
|
import 'button_style.dart';
|
||||||
import 'color_scheme.dart';
|
import 'color_scheme.dart';
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
|
@ -21,7 +21,6 @@ import 'icon_button.dart';
|
||||||
import 'icon_button_theme.dart';
|
import 'icon_button_theme.dart';
|
||||||
import 'icons.dart';
|
import 'icons.dart';
|
||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
import 'material_localizations.dart';
|
|
||||||
import 'material_state.dart';
|
import 'material_state.dart';
|
||||||
import 'scaffold.dart';
|
import 'scaffold.dart';
|
||||||
import 'tabs.dart';
|
import 'tabs.dart';
|
||||||
|
@ -755,14 +754,6 @@ class _AppBarState extends State<AppBar> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDrawerButton() {
|
|
||||||
Scaffold.of(context).openDrawer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDrawerButtonEnd() {
|
|
||||||
Scaffold.of(context).openEndDrawer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleScrollNotification(ScrollNotification notification) {
|
void _handleScrollNotification(ScrollNotification notification) {
|
||||||
if (notification is ScrollUpdateNotification && widget.notificationPredicate(notification)) {
|
if (notification is ScrollUpdateNotification && widget.notificationPredicate(notification)) {
|
||||||
final bool oldScrolledUnder = _scrolledUnder;
|
final bool oldScrolledUnder = _scrolledUnder;
|
||||||
|
@ -894,11 +885,8 @@ class _AppBarState extends State<AppBar> {
|
||||||
Widget? leading = widget.leading;
|
Widget? leading = widget.leading;
|
||||||
if (leading == null && widget.automaticallyImplyLeading) {
|
if (leading == null && widget.automaticallyImplyLeading) {
|
||||||
if (hasDrawer) {
|
if (hasDrawer) {
|
||||||
leading = IconButton(
|
leading = DrawerButton(
|
||||||
icon: const Icon(Icons.menu),
|
style: IconButton.styleFrom(iconSize: overallIconTheme.size ?? 24),
|
||||||
iconSize: overallIconTheme.size ?? 24,
|
|
||||||
onPressed: _handleDrawerButton,
|
|
||||||
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
|
|
||||||
);
|
);
|
||||||
// TODO(chunhtai): remove (!hasEndDrawer && canPop) once internal tests
|
// TODO(chunhtai): remove (!hasEndDrawer && canPop) once internal tests
|
||||||
// are migrated.
|
// are migrated.
|
||||||
|
@ -1009,11 +997,8 @@ class _AppBarState extends State<AppBar> {
|
||||||
children: widget.actions!,
|
children: widget.actions!,
|
||||||
);
|
);
|
||||||
} else if (hasEndDrawer) {
|
} else if (hasEndDrawer) {
|
||||||
actions = IconButton(
|
actions = EndDrawerButton(
|
||||||
icon: const Icon(Icons.menu),
|
style: IconButton.styleFrom(iconSize: overallIconTheme.size ?? 24),
|
||||||
iconSize: overallIconTheme.size ?? 24,
|
|
||||||
onPressed: _handleDrawerButtonEnd,
|
|
||||||
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,197 +2,4 @@
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
export 'action_buttons.dart' show BackButton, BackButtonIcon, CloseButton, CloseButtonIcon;
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ import 'dart:ui' show Color, lerpDouble;
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'action_buttons.dart';
|
||||||
|
import 'action_icons_theme.dart';
|
||||||
import 'app_bar_theme.dart';
|
import 'app_bar_theme.dart';
|
||||||
import 'badge_theme.dart';
|
import 'badge_theme.dart';
|
||||||
import 'banner_theme.dart';
|
import 'banner_theme.dart';
|
||||||
|
@ -337,6 +339,7 @@ class ThemeData with Diagnosticable {
|
||||||
TextTheme? textTheme,
|
TextTheme? textTheme,
|
||||||
Typography? typography,
|
Typography? typography,
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
ActionIconThemeData? actionIconTheme,
|
||||||
AppBarTheme? appBarTheme,
|
AppBarTheme? appBarTheme,
|
||||||
BadgeThemeData? badgeTheme,
|
BadgeThemeData? badgeTheme,
|
||||||
MaterialBannerThemeData? bannerTheme,
|
MaterialBannerThemeData? bannerTheme,
|
||||||
|
@ -649,6 +652,7 @@ class ThemeData with Diagnosticable {
|
||||||
typography: typography,
|
typography: typography,
|
||||||
primaryIconTheme: primaryIconTheme,
|
primaryIconTheme: primaryIconTheme,
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
actionIconTheme: actionIconTheme,
|
||||||
appBarTheme: appBarTheme,
|
appBarTheme: appBarTheme,
|
||||||
badgeTheme: badgeTheme,
|
badgeTheme: badgeTheme,
|
||||||
bannerTheme: bannerTheme,
|
bannerTheme: bannerTheme,
|
||||||
|
@ -759,6 +763,7 @@ class ThemeData with Diagnosticable {
|
||||||
required this.textTheme,
|
required this.textTheme,
|
||||||
required this.typography,
|
required this.typography,
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
required this.actionIconTheme,
|
||||||
required this.appBarTheme,
|
required this.appBarTheme,
|
||||||
required this.badgeTheme,
|
required this.badgeTheme,
|
||||||
required this.bannerTheme,
|
required this.bannerTheme,
|
||||||
|
@ -1356,6 +1361,10 @@ class ThemeData with Diagnosticable {
|
||||||
|
|
||||||
// COMPONENT THEMES
|
// 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
|
/// A theme for customizing the color, elevation, brightness, iconTheme and
|
||||||
/// textTheme of [AppBar]s.
|
/// textTheme of [AppBar]s.
|
||||||
final AppBarTheme appBarTheme;
|
final AppBarTheme appBarTheme;
|
||||||
|
@ -1654,6 +1663,7 @@ class ThemeData with Diagnosticable {
|
||||||
TextTheme? textTheme,
|
TextTheme? textTheme,
|
||||||
Typography? typography,
|
Typography? typography,
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
ActionIconThemeData? actionIconTheme,
|
||||||
AppBarTheme? appBarTheme,
|
AppBarTheme? appBarTheme,
|
||||||
BadgeThemeData? badgeTheme,
|
BadgeThemeData? badgeTheme,
|
||||||
MaterialBannerThemeData? bannerTheme,
|
MaterialBannerThemeData? bannerTheme,
|
||||||
|
@ -1787,6 +1797,7 @@ class ThemeData with Diagnosticable {
|
||||||
textTheme: textTheme ?? this.textTheme,
|
textTheme: textTheme ?? this.textTheme,
|
||||||
typography: typography ?? this.typography,
|
typography: typography ?? this.typography,
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
actionIconTheme: actionIconTheme ?? this.actionIconTheme,
|
||||||
appBarTheme: appBarTheme ?? this.appBarTheme,
|
appBarTheme: appBarTheme ?? this.appBarTheme,
|
||||||
badgeTheme: badgeTheme ?? this.badgeTheme,
|
badgeTheme: badgeTheme ?? this.badgeTheme,
|
||||||
bannerTheme: bannerTheme ?? this.bannerTheme,
|
bannerTheme: bannerTheme ?? this.bannerTheme,
|
||||||
|
@ -1980,6 +1991,7 @@ class ThemeData with Diagnosticable {
|
||||||
textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t),
|
textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t),
|
||||||
typography: Typography.lerp(a.typography, b.typography, t),
|
typography: Typography.lerp(a.typography, b.typography, t),
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
actionIconTheme: ActionIconThemeData.lerp(a.actionIconTheme, b.actionIconTheme, t),
|
||||||
appBarTheme: AppBarTheme.lerp(a.appBarTheme, b.appBarTheme, t),
|
appBarTheme: AppBarTheme.lerp(a.appBarTheme, b.appBarTheme, t),
|
||||||
badgeTheme: BadgeThemeData.lerp(a.badgeTheme, b.badgeTheme, t),
|
badgeTheme: BadgeThemeData.lerp(a.badgeTheme, b.badgeTheme, t),
|
||||||
bannerTheme: MaterialBannerThemeData.lerp(a.bannerTheme, b.bannerTheme, t),
|
bannerTheme: MaterialBannerThemeData.lerp(a.bannerTheme, b.bannerTheme, t),
|
||||||
|
@ -2085,6 +2097,7 @@ class ThemeData with Diagnosticable {
|
||||||
other.textTheme == textTheme &&
|
other.textTheme == textTheme &&
|
||||||
other.typography == typography &&
|
other.typography == typography &&
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
other.actionIconTheme == actionIconTheme &&
|
||||||
other.appBarTheme == appBarTheme &&
|
other.appBarTheme == appBarTheme &&
|
||||||
other.badgeTheme == badgeTheme &&
|
other.badgeTheme == badgeTheme &&
|
||||||
other.bannerTheme == bannerTheme &&
|
other.bannerTheme == bannerTheme &&
|
||||||
|
@ -2187,6 +2200,7 @@ class ThemeData with Diagnosticable {
|
||||||
textTheme,
|
textTheme,
|
||||||
typography,
|
typography,
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
actionIconTheme,
|
||||||
appBarTheme,
|
appBarTheme,
|
||||||
badgeTheme,
|
badgeTheme,
|
||||||
bannerTheme,
|
bannerTheme,
|
||||||
|
@ -2291,6 +2305,7 @@ class ThemeData with Diagnosticable {
|
||||||
properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme, level: DiagnosticLevel.debug));
|
properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme, level: DiagnosticLevel.debug));
|
||||||
properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography, level: DiagnosticLevel.debug));
|
properties.add(DiagnosticsProperty<Typography>('typography', typography, defaultValue: defaultData.typography, level: DiagnosticLevel.debug));
|
||||||
// COMPONENT THEMES
|
// 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<AppBarTheme>('appBarTheme', appBarTheme, defaultValue: defaultData.appBarTheme, level: DiagnosticLevel.debug));
|
||||||
properties.add(DiagnosticsProperty<BadgeThemeData>('badgeTheme', badgeTheme, defaultValue: defaultData.badgeTheme, 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));
|
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 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 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)));
|
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(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(macOSIcon.icon == iOSIcon.icon, isTrue);
|
||||||
expect(windowsIcon.icon == androidIcon.icon, isTrue);
|
expect(windowsIcon.icon == androidIcon.icon, isTrue);
|
||||||
});
|
});
|
||||||
|
@ -120,7 +120,7 @@ void main() {
|
||||||
const MaterialApp(
|
const MaterialApp(
|
||||||
home: Material(
|
home: Material(
|
||||||
child: BackButton(
|
child: BackButton(
|
||||||
color: Colors.blue,
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -130,7 +130,51 @@ void main() {
|
||||||
of: find.byType(BackButton),
|
of: find.byType(BackButton),
|
||||||
matching: find.byType(RichText),
|
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 {
|
testWidgets('BackButton semantics', (WidgetTester tester) async {
|
||||||
|
@ -239,6 +283,50 @@ void main() {
|
||||||
expect(iconText.text.style!.color, Colors.red);
|
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 {
|
testWidgets('CloseButton onPressed overrides default pop behavior', (WidgetTester tester) async {
|
||||||
bool customCallbackWasCalled = false;
|
bool customCallbackWasCalled = false;
|
||||||
await tester.pumpWidget(
|
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 }));
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia }));
|
||||||
|
|
||||||
testWidgets('Back arrow uses correct default', (WidgetTester tester) async {
|
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 }));
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -757,6 +757,7 @@ void main() {
|
||||||
textTheme: ThemeData.dark().textTheme,
|
textTheme: ThemeData.dark().textTheme,
|
||||||
typography: Typography.material2018(),
|
typography: Typography.material2018(),
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
actionIconTheme: const ActionIconThemeData(),
|
||||||
appBarTheme: const AppBarTheme(backgroundColor: Colors.black),
|
appBarTheme: const AppBarTheme(backgroundColor: Colors.black),
|
||||||
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
|
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
|
||||||
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.black),
|
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.black),
|
||||||
|
@ -874,6 +875,7 @@ void main() {
|
||||||
typography: Typography.material2018(platform: TargetPlatform.iOS),
|
typography: Typography.material2018(platform: TargetPlatform.iOS),
|
||||||
|
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
actionIconTheme: const ActionIconThemeData(),
|
||||||
appBarTheme: const AppBarTheme(backgroundColor: Colors.white),
|
appBarTheme: const AppBarTheme(backgroundColor: Colors.white),
|
||||||
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
|
badgeTheme: const BadgeThemeData(backgroundColor: Colors.black),
|
||||||
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.white),
|
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.white),
|
||||||
|
@ -977,6 +979,7 @@ void main() {
|
||||||
typography: otherTheme.typography,
|
typography: otherTheme.typography,
|
||||||
|
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
actionIconTheme: otherTheme.actionIconTheme,
|
||||||
appBarTheme: otherTheme.appBarTheme,
|
appBarTheme: otherTheme.appBarTheme,
|
||||||
badgeTheme: otherTheme.badgeTheme,
|
badgeTheme: otherTheme.badgeTheme,
|
||||||
bannerTheme: otherTheme.bannerTheme,
|
bannerTheme: otherTheme.bannerTheme,
|
||||||
|
@ -1077,6 +1080,7 @@ void main() {
|
||||||
expect(themeDataCopy.typography, equals(otherTheme.typography));
|
expect(themeDataCopy.typography, equals(otherTheme.typography));
|
||||||
|
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
expect(themeDataCopy.actionIconTheme, equals(otherTheme.actionIconTheme));
|
||||||
expect(themeDataCopy.appBarTheme, equals(otherTheme.appBarTheme));
|
expect(themeDataCopy.appBarTheme, equals(otherTheme.appBarTheme));
|
||||||
expect(themeDataCopy.badgeTheme, equals(otherTheme.badgeTheme));
|
expect(themeDataCopy.badgeTheme, equals(otherTheme.badgeTheme));
|
||||||
expect(themeDataCopy.bannerTheme, equals(otherTheme.bannerTheme));
|
expect(themeDataCopy.bannerTheme, equals(otherTheme.bannerTheme));
|
||||||
|
@ -1211,6 +1215,7 @@ void main() {
|
||||||
'iconTheme',
|
'iconTheme',
|
||||||
'primaryIconTheme',
|
'primaryIconTheme',
|
||||||
// COMPONENT THEMES
|
// COMPONENT THEMES
|
||||||
|
'actionIconTheme',
|
||||||
'appBarTheme',
|
'appBarTheme',
|
||||||
'badgeTheme',
|
'badgeTheme',
|
||||||
'bannerTheme',
|
'bannerTheme',
|
||||||
|
|
Loading…
Reference in a new issue