blob: d4ca927d11f2f067aba04f0cba2e8796dac40f18 [file] [log] [blame]
// 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;
}
}