upgrade to 9a721c456d4a6418c60f2512cbb54ee420b9c69b
diff --git a/framework/lib/src/cupertino/bottom_tab_bar.dart b/framework/lib/src/cupertino/bottom_tab_bar.dart
index 286cd02..d970962 100644
--- a/framework/lib/src/cupertino/bottom_tab_bar.dart
+++ b/framework/lib/src/cupertino/bottom_tab_bar.dart
@@ -15,7 +15,7 @@
const double _kTabBarHeight = 50.0;
const Color _kDefaultTabBarBorderColor = CupertinoDynamicColor.withBrightness(
- color: Color(0x4C000000),
+ color: Color(0x4D000000),
darkColor: Color(0x29000000),
);
const Color _kDefaultTabBarInactiveColor = CupertinoColors.inactiveGray;
diff --git a/framework/lib/src/cupertino/context_menu.dart b/framework/lib/src/cupertino/context_menu.dart
index 15788df..1fca184 100644
--- a/framework/lib/src/cupertino/context_menu.dart
+++ b/framework/lib/src/cupertino/context_menu.dart
@@ -8,6 +8,7 @@
import 'package:flute/foundation.dart';
import 'package:flute/gestures.dart' show kMinFlingVelocity;
import 'package:flute/scheduler.dart';
+import 'package:flute/services.dart' show HapticFeedback;
import 'package:flute/widgets.dart';
import 'colors.dart';
@@ -137,6 +138,7 @@
super.key,
required this.actions,
required Widget this.child,
+ this.enableHapticFeedback = false,
@Deprecated(
'Use CupertinoContextMenu.builder instead. '
'This feature was deprecated after v3.4.0-34.1.pre.',
@@ -157,6 +159,7 @@
super.key,
required this.actions,
required this.builder,
+ this.enableHapticFeedback = false,
}) : assert(actions.isNotEmpty),
child = null,
previewBuilder = null;
@@ -388,6 +391,13 @@
/// This parameter cannot be null or empty.
final List<Widget> actions;
+ /// If true, clicking on the [CupertinoContextMenuAction]s will
+ /// produce haptic feedback.
+ ///
+ /// Uses [HapticFeedback.heavyImpact] when activated.
+ /// Defaults to false.
+ final bool enableHapticFeedback;
+
/// A function that returns an alternative widget to show when the
/// [CupertinoContextMenu] is open.
///
@@ -481,6 +491,15 @@
_openController.addStatusListener(_onDecoyAnimationStatusChange);
}
+ void _listenerCallback() {
+ if (_openController.status != AnimationStatus.reverse &&
+ _openController.value >= _midpoint &&
+ widget.enableHapticFeedback) {
+ HapticFeedback.heavyImpact();
+ _openController.removeListener(_listenerCallback);
+ }
+ }
+
// Determine the _ContextMenuLocation based on the location of the original
// child in the screen.
//
@@ -583,24 +602,28 @@
}
void _onTap() {
+ _openController.removeListener(_listenerCallback);
if (_openController.isAnimating && _openController.value < _midpoint) {
_openController.reverse();
}
}
void _onTapCancel() {
+ _openController.removeListener(_listenerCallback);
if (_openController.isAnimating && _openController.value < _midpoint) {
_openController.reverse();
}
}
void _onTapUp(TapUpDetails details) {
+ _openController.removeListener(_listenerCallback);
if (_openController.isAnimating && _openController.value < _midpoint) {
_openController.reverse();
}
}
void _onTapDown(TapDownDetails details) {
+ _openController.addListener(_listenerCallback);
setState(() {
_childHidden = true;
});
@@ -1370,27 +1393,24 @@
);
return SafeArea(
- child: Padding(
- padding: const EdgeInsets.all(_kPadding),
- child: Align(
- alignment: Alignment.topLeft,
- child: GestureDetector(
- onPanEnd: _onPanEnd,
- onPanStart: _onPanStart,
- onPanUpdate: _onPanUpdate,
- child: AnimatedBuilder(
- animation: _moveController,
- builder: _buildAnimation,
- child: widget.orientation == Orientation.portrait
- ? Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: children,
- )
- : Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: children,
- ),
- ),
+ child: Align(
+ alignment: Alignment.topLeft,
+ child: GestureDetector(
+ onPanEnd: _onPanEnd,
+ onPanStart: _onPanStart,
+ onPanUpdate: _onPanUpdate,
+ child: AnimatedBuilder(
+ animation: _moveController,
+ builder: _buildAnimation,
+ child: widget.orientation == Orientation.portrait
+ ? Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: children,
+ )
+ : Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: children,
+ ),
),
),
),
@@ -1414,12 +1434,13 @@
final _ContextMenuLocation _contextMenuLocation;
final Orientation _orientation;
+ static const double _kMenuWidth = 250.0;
+
// Get the children, whose order depends on orientation and
// contextMenuLocation.
List<Widget> getChildren(BuildContext context) {
- final Widget menu = Flexible(
- fit: FlexFit.tight,
- flex: 2,
+ final Widget menu = SizedBox(
+ width: _kMenuWidth,
child: IntrinsicHeight(
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(13.0)),
@@ -1432,9 +1453,12 @@
decoration: BoxDecoration(
border: Border(
top: BorderSide(
- color: CupertinoDynamicColor.resolve(_borderColor, context),
- width: 0.5,
- )
+ color: CupertinoDynamicColor.resolve(
+ _borderColor,
+ context,
+ ),
+ width: 0.4,
+ ),
),
),
position: DecorationPosition.foreground,
diff --git a/framework/lib/src/cupertino/context_menu_action.dart b/framework/lib/src/cupertino/context_menu_action.dart
index 0f011ea..b01d277 100644
--- a/framework/lib/src/cupertino/context_menu_action.dart
+++ b/framework/lib/src/cupertino/context_menu_action.dart
@@ -48,18 +48,18 @@
class _CupertinoContextMenuActionState extends State<CupertinoContextMenuAction> {
static const Color _kBackgroundColor = CupertinoDynamicColor.withBrightness(
- color: Color(0xFFEEEEEE),
+ color: Color(0xFFF1F1F1),
darkColor: Color(0xFF212122),
);
static const Color _kBackgroundColorPressed = CupertinoDynamicColor.withBrightness(
color: Color(0xFFDDDDDD),
darkColor: Color(0xFF3F3F40),
);
- static const double _kButtonHeight = 56.0;
+ static const double _kButtonHeight = 43;
static const TextStyle _kActionSheetActionStyle = TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
- fontSize: 20.0,
+ fontSize: 16.0,
fontWeight: FontWeight.w400,
color: CupertinoColors.black,
textBaseline: TextBaseline.alphabetic,
@@ -125,9 +125,11 @@
? CupertinoDynamicColor.resolve(_kBackgroundColorPressed, context)
: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
),
- padding: const EdgeInsets.symmetric(
- vertical: 16.0,
- horizontal: 10.0,
+ padding: const EdgeInsets.only(
+ top: 8,
+ bottom: 8,
+ left: 15.5,
+ right: 17.5,
),
child: DefaultTextStyle(
style: _textStyle,
@@ -141,6 +143,7 @@
Icon(
widget.trailingIcon,
color: _textStyle.color,
+ size: 21.0,
),
],
),
diff --git a/framework/lib/src/foundation/binding.dart b/framework/lib/src/foundation/binding.dart
index ac6d4ac..97e30cb 100644
--- a/framework/lib/src/foundation/binding.dart
+++ b/framework/lib/src/foundation/binding.dart
@@ -138,7 +138,7 @@
/// First calls [initInstances] to have bindings initialize their
/// instance pointers and other state, then calls
/// [initServiceExtensions] to have bindings initialize their
- /// observatory service extensions, if any.
+ /// VM service extensions, if any.
BindingBase() {
developer.Timeline.startSync('Framework initialization');
assert(() {
diff --git a/framework/lib/src/gestures/converter.dart b/framework/lib/src/gestures/converter.dart
index 6aa7741..db86594 100644
--- a/framework/lib/src/gestures/converter.dart
+++ b/framework/lib/src/gestures/converter.dart
@@ -278,6 +278,8 @@
scale: datum.scale,
);
case ui.PointerSignalKind.unknown:
+ default: // ignore: no_default_cases, to allow adding a new [PointerSignalKind] - PointerStylusAuxiliaryAction
+ // TODO(louisehsu): remove after landing engine PR https://github.com/flutter/engine/pull/39637
// This branch should already have 'unknown' filtered out, but
// we don't want to return anything or miss if someone adds a new
// enumeration to PointerSignalKind.
diff --git a/framework/lib/src/gestures/recognizer.dart b/framework/lib/src/gestures/recognizer.dart
index 0a30fbf..d55b2b3 100644
--- a/framework/lib/src/gestures/recognizer.dart
+++ b/framework/lib/src/gestures/recognizer.dart
@@ -78,10 +78,9 @@
/// {@endtemplate}
GestureRecognizer({
this.debugOwner,
- Set<PointerDeviceKind>? supportedDevices,
+ this.supportedDevices,
AllowedButtonsFilter? allowedButtonsFilter,
- }) : _supportedDevices = supportedDevices,
- _allowedButtonsFilter = allowedButtonsFilter ?? _defaultButtonAcceptBehavior;
+ }) : _allowedButtonsFilter = allowedButtonsFilter ?? _defaultButtonAcceptBehavior;
/// The recognizer's owner.
///
@@ -97,7 +96,7 @@
/// `supportedDevices` in the constructor, or the currently deprecated `kind`.
/// These cannot both be set. If both are null, events from all device kinds will be
/// tracked and recognized.
- final Set<PointerDeviceKind>? _supportedDevices;
+ Set<PointerDeviceKind>? supportedDevices;
/// {@template flutter.gestures.multidrag._allowedButtonsFilter}
/// Called when interaction starts. This limits the dragging behavior
@@ -209,8 +208,8 @@
/// Checks whether or not a pointer is allowed to be tracked by this recognizer.
@protected
bool isPointerAllowed(PointerDownEvent event) {
- return (_supportedDevices == null ||
- _supportedDevices!.contains(event.kind)) &&
+ return (supportedDevices == null ||
+ supportedDevices!.contains(event.kind)) &&
_allowedButtonsFilter(event.buttons);
}
@@ -223,7 +222,7 @@
/// Checks whether or not a pointer pan/zoom is allowed to be tracked by this recognizer.
@protected
bool isPointerPanZoomAllowed(PointerPanZoomStartEvent event) {
- return _supportedDevices == null || _supportedDevices!.contains(event.kind);
+ return supportedDevices == null || supportedDevices!.contains(event.kind);
}
/// For a given pointer ID, returns the device kind associated with it.
diff --git a/framework/lib/src/material/about.dart b/framework/lib/src/material/about.dart
index dea95b4..0b26bd2 100644
--- a/framework/lib/src/material/about.dart
+++ b/framework/lib/src/material/about.dart
@@ -889,7 +889,11 @@
// A Scrollbar is built-in below.
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: Scrollbar(
- child: ListView(padding: padding, children: listWidgets),
+ child: ListView(
+ primary: true,
+ padding: padding,
+ children: listWidgets,
+ ),
),
),
),
diff --git a/framework/lib/src/material/animated_icons/animated_icons_data.dart b/framework/lib/src/material/animated_icons/animated_icons_data.dart
index 6efca7d..9cd3595 100644
--- a/framework/lib/src/material/animated_icons/animated_icons_data.dart
+++ b/framework/lib/src/material/animated_icons/animated_icons_data.dart
@@ -25,6 +25,9 @@
///
/// * [Icons], for the list of available static Material Icons.
abstract class AnimatedIcons {
+ // This class is not meant to be instantiated or extended; this constructor
+ // prevents instantiation and extension.
+ AnimatedIcons._();
/// The Material Design add to event icon animation.
///
diff --git a/framework/lib/src/material/app_bar.dart b/framework/lib/src/material/app_bar.dart
index 884b05f..638ed8a 100644
--- a/framework/lib/src/material/app_bar.dart
+++ b/framework/lib/src/material/app_bar.dart
@@ -172,10 +172,7 @@
class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// Creates a Material Design app bar.
///
- /// The arguments [primary], [toolbarOpacity], [bottomOpacity],
- /// [backwardsCompatibility], and [automaticallyImplyLeading] must
- /// not be null. Additionally, if [elevation] is specified, it must
- /// be non-negative.
+ /// If [elevation] is specified, it must be non-negative.
///
/// Typically used in the [Scaffold.appBar] property.
AppBar({
@@ -194,11 +191,6 @@
this.shape,
this.backgroundColor,
this.foregroundColor,
- @Deprecated(
- 'This property is no longer used, please use systemOverlayStyle instead. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- this.brightness,
this.iconTheme,
this.actionsIconTheme,
this.primary = true,
@@ -209,11 +201,6 @@
this.bottomOpacity = 1.0,
this.toolbarHeight,
this.leadingWidth,
- @Deprecated(
- 'This property is obsolete and is false by default. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- this.backwardsCompatibility,
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
@@ -499,7 +486,7 @@
/// If null, then the [AppBarTheme.backgroundColor] is used. If that value is also
/// null, then [AppBar] uses the overall theme's [ColorScheme.primary] if the
/// overall theme's brightness is [Brightness.light], and [ColorScheme.surface]
- /// if the overall theme's [brightness] is [Brightness.dark].
+ /// if the overall theme's brightness is [Brightness.dark].
///
/// If this color is a [MaterialStateColor] it will be resolved against
/// [MaterialState.scrolledUnder] when the content of the app's
@@ -525,7 +512,7 @@
/// value is also null, then [AppBar] uses the overall theme's
/// [ColorScheme.onPrimary] if the overall theme's brightness is
/// [Brightness.light], and [ColorScheme.onSurface] if the overall
- /// theme's [brightness] is [Brightness.dark].
+ /// theme's brightness is [Brightness.dark].
///
/// This color is used to configure [DefaultTextStyle] that contains
/// the toolbar's children, and the default [IconTheme] widgets that
@@ -543,38 +530,6 @@
/// is light or dark.
final Color? foregroundColor;
- /// {@template flutter.material.appbar.brightness}
- /// This property is deprecated, please use [systemOverlayStyle] instead.
- ///
- /// Determines the brightness of the [SystemUiOverlayStyle]: for
- /// [Brightness.dark], [SystemUiOverlayStyle.light] is used and for
- /// [Brightness.light], [SystemUiOverlayStyle.dark] is used.
- ///
- /// If this value is null then [AppBarTheme.brightness] is used
- /// and if that's null then overall theme's brightness is used.
- ///
- /// The AppBar is built within a `AnnotatedRegion<SystemUiOverlayStyle>`
- /// which causes [SystemChrome.setSystemUIOverlayStyle] to be called
- /// automatically. Apps should not enclose the AppBar with
- /// their own [AnnotatedRegion].
- /// {@endtemplate}
- ///
- /// See also:
- ///
- /// * [Theme.of], which returns the current overall Material theme as
- /// a [ThemeData].
- /// * [ThemeData.colorScheme], the thirteen colors that most Material widget
- /// default colors are based on.
- /// * [ColorScheme.brightness], which indicates if the overall [Theme]
- /// is light or dark.
- /// * [backwardsCompatibility], which forces AppBar to use this
- /// obsolete property.
- @Deprecated(
- 'This property is no longer used, please use systemOverlayStyle instead. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- final Brightness? brightness;
-
/// {@template flutter.material.appbar.iconTheme}
/// The color, opacity, and size to use for toolbar icons.
///
@@ -688,28 +643,6 @@
/// {@endtemplate}
final double? leadingWidth;
- /// {@template flutter.material.appbar.backwardsCompatibility}
- /// This property is deprecated and is false by default.
- ///
- /// If true, preserves the original defaults for the [backgroundColor],
- /// [iconTheme], [actionsIconTheme] properties, and the original use of
- /// the [brightness] property.
- ///
- /// If this property is null, then [AppBarTheme.backwardsCompatibility] of
- /// [ThemeData.appBarTheme] is used. If that is also null, the default
- /// value is false.
- ///
- /// This is a temporary property and it has been deprecated. App
- /// developers are encouraged to opt into the new features by
- /// leaving it default (false) and using the [foregroundColor] and
- /// [systemOverlayStyle] properties as needed.
- /// {@endtemplate}
- @Deprecated(
- 'This property is obsolete and is false by default. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- final bool? backwardsCompatibility;
-
/// {@template flutter.material.appbar.toolbarTextStyle}
/// The default text style for the AppBar's [leading], and
/// [actions] widgets, but not its [title].
@@ -748,8 +681,6 @@
/// {@template flutter.material.appbar.systemOverlayStyle}
/// Specifies the style to use for the system overlays that overlap the AppBar.
///
- /// This property is only used if [backwardsCompatibility] is false (the default).
- ///
/// If this property is null, then [AppBarTheme.systemOverlayStyle] of
/// [ThemeData.appBarTheme] is used. If that is also null, an appropriate
/// [SystemUiOverlayStyle] is calculated based on the [backgroundColor].
@@ -900,18 +831,13 @@
final bool useCloseButton = parentRoute is PageRoute<dynamic> && parentRoute.fullscreenDialog;
final double toolbarHeight = widget.toolbarHeight ?? appBarTheme.toolbarHeight ?? kToolbarHeight;
- final bool backwardsCompatibility = widget.backwardsCompatibility ?? appBarTheme.backwardsCompatibility ?? false;
- final Color backgroundColor = backwardsCompatibility
- ? widget.backgroundColor
- ?? appBarTheme.backgroundColor
- ?? theme.primaryColor
- : _resolveColor(
- states,
- widget.backgroundColor,
- appBarTheme.backgroundColor,
- defaults.backgroundColor!,
- );
+ final Color backgroundColor = _resolveColor(
+ states,
+ widget.backgroundColor,
+ appBarTheme.backgroundColor,
+ defaults.backgroundColor!,
+ );
final Color foregroundColor = widget.foregroundColor
?? appBarTheme.foregroundColor
@@ -928,13 +854,9 @@
?? elevation
: elevation;
- IconThemeData overallIconTheme = backwardsCompatibility
- ? widget.iconTheme
- ?? appBarTheme.iconTheme
- ?? theme.primaryIconTheme
- : widget.iconTheme
- ?? appBarTheme.iconTheme
- ?? defaults.iconTheme!.copyWith(color: foregroundColor);
+ IconThemeData overallIconTheme = widget.iconTheme
+ ?? appBarTheme.iconTheme
+ ?? defaults.iconTheme!.copyWith(color: foregroundColor);
final Color? actionForegroundColor = widget.foregroundColor
?? appBarTheme.foregroundColor;
@@ -945,21 +867,13 @@
?? defaults.actionsIconTheme?.copyWith(color: actionForegroundColor)
?? overallIconTheme;
- TextStyle? toolbarTextStyle = backwardsCompatibility
- ? widget.toolbarTextStyle
- ?? appBarTheme.toolbarTextStyle
- ?? theme.primaryTextTheme.bodyMedium
- : widget.toolbarTextStyle
- ?? appBarTheme.toolbarTextStyle
- ?? defaults.toolbarTextStyle?.copyWith(color: foregroundColor);
+ TextStyle? toolbarTextStyle = widget.toolbarTextStyle
+ ?? appBarTheme.toolbarTextStyle
+ ?? defaults.toolbarTextStyle?.copyWith(color: foregroundColor);
- TextStyle? titleTextStyle = backwardsCompatibility
- ? widget.titleTextStyle
- ?? appBarTheme.titleTextStyle
- ?? theme.primaryTextTheme.titleLarge
- : widget.titleTextStyle
- ?? appBarTheme.titleTextStyle
- ?? defaults.titleTextStyle?.copyWith(color: foregroundColor);
+ TextStyle? titleTextStyle = widget.titleTextStyle
+ ?? appBarTheme.titleTextStyle
+ ?? defaults.titleTextStyle?.copyWith(color: foregroundColor);
if (widget.toolbarOpacity != 1.0) {
final double opacity = const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.toolbarOpacity);
@@ -1211,21 +1125,15 @@
);
}
- final SystemUiOverlayStyle overlayStyle = backwardsCompatibility
- ? _systemOverlayStyleForBrightness(
- widget.brightness
- ?? appBarTheme.brightness
- ?? ThemeData.estimateBrightnessForColor(backgroundColor),
- )
- : widget.systemOverlayStyle
- ?? appBarTheme.systemOverlayStyle
- ?? defaults.systemOverlayStyle
- ?? _systemOverlayStyleForBrightness(
- ThemeData.estimateBrightnessForColor(backgroundColor),
- // Make the status bar transparent for M3 so the elevation overlay
- // color is picked up by the statusbar.
- theme.useMaterial3 ? const Color(0x00000000) : null,
- );
+ final SystemUiOverlayStyle overlayStyle = widget.systemOverlayStyle
+ ?? appBarTheme.systemOverlayStyle
+ ?? defaults.systemOverlayStyle
+ ?? _systemOverlayStyleForBrightness(
+ ThemeData.estimateBrightnessForColor(backgroundColor),
+ // Make the status bar transparent for M3 so the elevation overlay
+ // color is picked up by the statusbar.
+ theme.useMaterial3 ? const Color(0x00000000) : null,
+ );
return Semantics(
container: true,
@@ -1269,7 +1177,6 @@
required this.forceElevated,
required this.backgroundColor,
required this.foregroundColor,
- required this.brightness,
required this.iconTheme,
required this.actionsIconTheme,
required this.primary,
@@ -1288,7 +1195,6 @@
required this.shape,
required this.toolbarHeight,
required this.leadingWidth,
- required this.backwardsCompatibility,
required this.toolbarTextStyle,
required this.titleTextStyle,
required this.systemOverlayStyle,
@@ -1309,7 +1215,6 @@
final bool forceElevated;
final Color? backgroundColor;
final Color? foregroundColor;
- final Brightness? brightness;
final IconThemeData? iconTheme;
final IconThemeData? actionsIconTheme;
final bool primary;
@@ -1324,7 +1229,6 @@
final ShapeBorder? shape;
final double? toolbarHeight;
final double? leadingWidth;
- final bool? backwardsCompatibility;
final TextStyle? toolbarTextStyle;
final TextStyle? titleTextStyle;
final SystemUiOverlayStyle? systemOverlayStyle;
@@ -1385,7 +1289,6 @@
surfaceTintColor: surfaceTintColor,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
- brightness: brightness,
iconTheme: iconTheme,
actionsIconTheme: actionsIconTheme,
primary: primary,
@@ -1397,7 +1300,6 @@
bottomOpacity: pinned ? 1.0 : clampDouble(visibleMainHeight / _bottomHeight, 0.0, 1.0),
toolbarHeight: toolbarHeight,
leadingWidth: leadingWidth,
- backwardsCompatibility: backwardsCompatibility,
toolbarTextStyle: toolbarTextStyle,
titleTextStyle: titleTextStyle,
systemOverlayStyle: systemOverlayStyle,
@@ -1420,7 +1322,6 @@
|| shadowColor != oldDelegate.shadowColor
|| backgroundColor != oldDelegate.backgroundColor
|| foregroundColor != oldDelegate.foregroundColor
- || brightness != oldDelegate.brightness
|| iconTheme != oldDelegate.iconTheme
|| actionsIconTheme != oldDelegate.actionsIconTheme
|| primary != oldDelegate.primary
@@ -1437,7 +1338,6 @@
|| forceElevated != oldDelegate.forceElevated
|| toolbarHeight != oldDelegate.toolbarHeight
|| leadingWidth != oldDelegate.leadingWidth
- || backwardsCompatibility != oldDelegate.backwardsCompatibility
|| toolbarTextStyle != oldDelegate.toolbarTextStyle
|| titleTextStyle != oldDelegate.titleTextStyle
|| systemOverlayStyle != oldDelegate.systemOverlayStyle
@@ -1555,11 +1455,6 @@
this.forceElevated = false,
this.backgroundColor,
this.foregroundColor,
- @Deprecated(
- 'This property is no longer used, please use systemOverlayStyle instead. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- this.brightness,
this.iconTheme,
this.actionsIconTheme,
this.primary = true,
@@ -1577,11 +1472,6 @@
this.shape,
this.toolbarHeight = kToolbarHeight,
this.leadingWidth,
- @Deprecated(
- 'This property is obsolete and is false by default. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- this.backwardsCompatibility,
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
@@ -1865,15 +1755,6 @@
/// This property is used to configure an [AppBar].
final Color? foregroundColor;
- /// {@macro flutter.material.appbar.brightness}
- ///
- /// This property is used to configure an [AppBar].
- @Deprecated(
- 'This property is no longer used, please use systemOverlayStyle instead. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- final Brightness? brightness;
-
/// {@macro flutter.material.appbar.iconTheme}
///
/// This property is used to configure an [AppBar].
@@ -2035,15 +1916,6 @@
/// This property is used to configure an [AppBar].
final double? leadingWidth;
- /// {@macro flutter.material.appbar.backwardsCompatibility}
- ///
- /// This property is used to configure an [AppBar].
- @Deprecated(
- 'This property is obsolete and is false by default. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- final bool? backwardsCompatibility;
-
/// {@macro flutter.material.appbar.toolbarTextStyle}
///
/// This property is used to configure an [AppBar].
@@ -2149,7 +2021,6 @@
forceElevated: widget.forceElevated,
backgroundColor: widget.backgroundColor,
foregroundColor: widget.foregroundColor,
- brightness: widget.brightness,
iconTheme: widget.iconTheme,
actionsIconTheme: widget.actionsIconTheme,
primary: widget.primary,
@@ -2167,7 +2038,6 @@
showOnScreenConfiguration: _showOnScreenConfiguration,
toolbarHeight: widget.toolbarHeight,
leadingWidth: widget.leadingWidth,
- backwardsCompatibility: widget.backwardsCompatibility,
toolbarTextStyle: widget.toolbarTextStyle,
titleTextStyle: widget.titleTextStyle,
systemOverlayStyle: widget.systemOverlayStyle,
diff --git a/framework/lib/src/material/app_bar_theme.dart b/framework/lib/src/material/app_bar_theme.dart
index 0229bcd..dae83c0 100644
--- a/framework/lib/src/material/app_bar_theme.dart
+++ b/framework/lib/src/material/app_bar_theme.dart
@@ -27,11 +27,6 @@
class AppBarTheme with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.appBarTheme].
const AppBarTheme({
- @Deprecated(
- 'This property is no longer used, please use systemOverlayStyle instead. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- this.brightness,
Color? color,
Color? backgroundColor,
this.foregroundColor,
@@ -48,53 +43,12 @@
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
- @Deprecated(
- 'This property is obsolete and is false by default. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- this.backwardsCompatibility,
}) : assert(
color == null || backgroundColor == null,
'The color and backgroundColor parameters mean the same thing. Only specify one.',
),
backgroundColor = backgroundColor ?? color;
- /// This property is deprecated, please use [systemOverlayStyle] instead.
- ///
- /// Overrides the default value of the obsolete [AppBar.brightness]
- /// property which implicitly defines [AppBar.systemOverlayStyle] in
- /// all descendant [AppBar] widgets.
- ///
- /// See also:
- ///
- /// * [systemOverlayStyle], which overrides the default value of
- /// [AppBar.systemOverlayStyle] in all descendant [AppBar] widgets.
- /// * [AppBar.backwardsCompatibility], which forces [AppBar] to depend
- /// on this obsolete property.
- @Deprecated(
- 'This property is no longer used, please use systemOverlayStyle instead. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- final Brightness? brightness;
-
- /// This property is deprecated, please use [backgroundColor] instead.
- ///
- /// Obsolete property that overrides the default value of
- /// [AppBar.backgroundColor] in all descendant [AppBar] widgets.
- ///
- /// See also:
- ///
- /// * [backgroundColor], which serves this same purpose
- /// as this property, but has a name that's consistent with
- /// [AppBar.backgroundColor].
- /// * [AppBar.backwardsCompatibility], which forces [AppBar] to depend
- /// on this obsolete property.
- @Deprecated(
- 'This property is no longer used, please use backgroundColor instead. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- Color? get color => backgroundColor;
-
/// Overrides the default value of [AppBar.backgroundColor] in all
/// descendant [AppBar] widgets.
///
@@ -177,9 +131,6 @@
/// Overrides the default value of the obsolete [AppBar.toolbarTextStyle]
/// property in all descendant [AppBar] widgets.
///
- /// If this property is specified, then [backwardsCompatibility]
- /// should be false (the default).
- ///
/// See also:
///
/// * [titleTextStyle], which overrides the default of [AppBar.titleTextStyle]
@@ -189,9 +140,6 @@
/// Overrides the default value of [AppBar.titleTextStyle]
/// property in all descendant [AppBar] widgets.
///
- /// If this property is specified, then [backwardsCompatibility]
- /// should be false (the default).
- ///
/// See also:
///
/// * [toolbarTextStyle], which overrides the default of [AppBar.toolbarTextStyle]
@@ -202,23 +150,10 @@
/// property in all descendant [AppBar] widgets.
final SystemUiOverlayStyle? systemOverlayStyle;
- /// Overrides the default value of [AppBar.backwardsCompatibility]
- /// property in all descendant [AppBar] widgets.
- @Deprecated(
- 'This property is obsolete and is false by default. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- final bool? backwardsCompatibility;
-
/// Creates a copy of this object with the given fields replaced with the
/// new values.
AppBarTheme copyWith({
IconThemeData? actionsIconTheme,
- @Deprecated(
- 'This property is no longer used, please use systemOverlayStyle instead. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- Brightness? brightness,
Color? color,
Color? backgroundColor,
Color? foregroundColor,
@@ -234,18 +169,12 @@
TextStyle? toolbarTextStyle,
TextStyle? titleTextStyle,
SystemUiOverlayStyle? systemOverlayStyle,
- @Deprecated(
- 'This property is obsolete and is false by default. '
- 'This feature was deprecated after v2.4.0-0.0.pre.',
- )
- bool? backwardsCompatibility,
}) {
assert(
color == null || backgroundColor == null,
'The color and backgroundColor parameters mean the same thing. Only specify one.',
);
return AppBarTheme(
- brightness: brightness ?? this.brightness,
backgroundColor: backgroundColor ?? color ?? this.backgroundColor,
foregroundColor: foregroundColor ?? this.foregroundColor,
elevation: elevation ?? this.elevation,
@@ -261,7 +190,6 @@
toolbarTextStyle: toolbarTextStyle ?? this.toolbarTextStyle,
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
systemOverlayStyle: systemOverlayStyle ?? this.systemOverlayStyle,
- backwardsCompatibility: backwardsCompatibility ?? this.backwardsCompatibility,
);
}
@@ -277,7 +205,6 @@
/// {@macro dart.ui.shadow.lerp}
static AppBarTheme lerp(AppBarTheme? a, AppBarTheme? b, double t) {
return AppBarTheme(
- brightness: t < 0.5 ? a?.brightness : b?.brightness,
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
foregroundColor: Color.lerp(a?.foregroundColor, b?.foregroundColor, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t),
@@ -293,13 +220,11 @@
toolbarTextStyle: TextStyle.lerp(a?.toolbarTextStyle, b?.toolbarTextStyle, t),
titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
systemOverlayStyle: t < 0.5 ? a?.systemOverlayStyle : b?.systemOverlayStyle,
- backwardsCompatibility: t < 0.5 ? a?.backwardsCompatibility : b?.backwardsCompatibility,
);
}
@override
int get hashCode => Object.hash(
- brightness,
backgroundColor,
foregroundColor,
elevation,
@@ -315,7 +240,6 @@
toolbarTextStyle,
titleTextStyle,
systemOverlayStyle,
- backwardsCompatibility,
);
@override
@@ -327,7 +251,6 @@
return false;
}
return other is AppBarTheme
- && other.brightness == brightness
&& other.backgroundColor == backgroundColor
&& other.foregroundColor == foregroundColor
&& other.elevation == elevation
@@ -342,14 +265,12 @@
&& other.toolbarHeight == toolbarHeight
&& other.toolbarTextStyle == toolbarTextStyle
&& other.titleTextStyle == titleTextStyle
- && other.systemOverlayStyle == systemOverlayStyle
- && other.backwardsCompatibility == backwardsCompatibility;
+ && other.systemOverlayStyle == systemOverlayStyle;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
- properties.add(DiagnosticsProperty<Brightness>('brightness', brightness, defaultValue: null));
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
properties.add(ColorProperty('foregroundColor', foregroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<double>('elevation', elevation, defaultValue: null));
@@ -364,6 +285,5 @@
properties.add(DiagnosticsProperty<double>('toolbarHeight', toolbarHeight, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('toolbarTextStyle', toolbarTextStyle, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
- properties.add(DiagnosticsProperty<bool>('backwardsCompatibility', backwardsCompatibility, defaultValue: null));
}
}
diff --git a/framework/lib/src/material/badge.dart b/framework/lib/src/material/badge.dart
index 2d8d65d..837a91f 100644
--- a/framework/lib/src/material/badge.dart
+++ b/framework/lib/src/material/badge.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flute/rendering.dart';
import 'package:flute/widgets.dart';
import 'badge_theme.dart';
@@ -35,6 +36,7 @@
this.textStyle,
this.padding,
this.alignment,
+ this.offset,
this.label,
this.isLabelVisible = true,
this.child,
@@ -54,6 +56,7 @@
this.textStyle,
this.padding,
this.alignment,
+ this.offset,
required int count,
this.isLabelVisible = true,
this.child,
@@ -106,13 +109,29 @@
/// left and right if the theme's value is null.
final EdgeInsetsGeometry? padding;
- /// The location of the [label] relative to the [child].
+ /// Combined with [offset] to determine the location of the [label]
+ /// relative to the [child].
+ ///
+ /// The alignment positions the label in the same way a child of an
+ /// [Align] widget is positioned, except that, the alignment is
+ /// resolved as if the label was a [largeSize] square and [offset]
+ /// is added to the result.
///
/// This value is only used if [label] is non-null.
///
- /// Defaults to the [BadgeTheme]'s alignment, or `start = 12`
- /// and `top = -4` if the theme's value is null.
- final AlignmentDirectional? alignment;
+ /// Defaults to the [BadgeTheme]'s alignment, or
+ /// [AlignmentDirectional.topEnd] if the theme's value is null.
+ final AlignmentGeometry? alignment;
+
+ /// Combined with [alignment] to determine the location of the [label]
+ /// relative to the [child].
+ ///
+ /// This value is only used if [label] is non-null.
+ ///
+ /// Defaults to the [BadgeTheme]'s offset, or
+ /// if the theme's value is null then `Offset(4, -4)` for
+ /// [TextDirection.ltr] or `Offset(-4, -4)` for [TextDirection.rtl].
+ final Offset? offset;
/// The badge's content, typically a [Text] widget that contains 1 to 4
/// characters.
@@ -168,24 +187,99 @@
return badge;
}
- final AlignmentDirectional effectiveAlignment = alignment ?? badgeTheme.alignment ?? defaults.alignment!;
+ final AlignmentGeometry effectiveAlignment = alignment ?? badgeTheme.alignment ?? defaults.alignment!;
+ final TextDirection textDirection = Directionality.of(context);
+ final Offset defaultOffset = textDirection == TextDirection.ltr ? const Offset(4, -4) : const Offset(-4, -4);
+ final Offset effectiveOffset = offset ?? badgeTheme.offset ?? defaultOffset;
+
return
Stack(
clipBehavior: Clip.none,
children: <Widget>[
child!,
- Positioned.directional(
- textDirection: Directionality.of(context),
- start: label == null ? null : effectiveAlignment.start,
- end: label == null ? 0 : null,
- top: label == null ? 0 : effectiveAlignment.y,
- child: badge,
+ Positioned.fill(
+ child: _Badge(
+ alignment: effectiveAlignment,
+ offset: label == null ? Offset.zero : effectiveOffset,
+ textDirection: textDirection,
+ child: badge,
+ ),
),
],
);
}
}
+class _Badge extends SingleChildRenderObjectWidget {
+ const _Badge({
+ required this.alignment,
+ required this.offset,
+ required this.textDirection,
+ super.child, // the badge
+ });
+
+ final AlignmentGeometry alignment;
+ final Offset offset;
+ final TextDirection textDirection;
+
+ @override
+ _RenderBadge createRenderObject(BuildContext context) {
+ return _RenderBadge(
+ alignment: alignment,
+ offset: offset,
+ textDirection: Directionality.maybeOf(context),
+ );
+ }
+
+ @override
+ void updateRenderObject(BuildContext context, _RenderBadge renderObject) {
+ renderObject
+ ..alignment = alignment
+ ..offset = offset
+ ..textDirection = Directionality.maybeOf(context);
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
+ properties.add(DiagnosticsProperty<Offset>('offset', offset));
+ }
+}
+
+class _RenderBadge extends RenderAligningShiftedBox {
+ _RenderBadge({
+ super.textDirection,
+ super.alignment,
+ required Offset offset,
+ }) : _offset = offset;
+
+ Offset get offset => _offset;
+ Offset _offset;
+ set offset(Offset value) {
+ if (_offset == value) {
+ return;
+ }
+ _offset = value;
+ markNeedsLayout();
+ }
+
+ @override
+ void performLayout() {
+ final BoxConstraints constraints = this.constraints;
+ assert(constraints.hasBoundedWidth);
+ assert(constraints.hasBoundedHeight);
+ size = constraints.biggest;
+
+ child!.layout(const BoxConstraints(), parentUsesSize: true);
+ final double badgeSize = child!.size.height;
+ final Alignment resolvedAlignment = alignment.resolve(textDirection);
+ final BoxParentData childParentData = child!.parentData! as BoxParentData;
+ childParentData.offset = offset + resolvedAlignment.alongOffset(Offset(size.width - badgeSize, size.height - badgeSize));
+ }
+}
+
+
// BEGIN GENERATED TOKEN PROPERTIES - Badge
// Do not edit by hand. The code between the "BEGIN GENERATED" and
@@ -200,7 +294,7 @@
smallSize: 6.0,
largeSize: 16.0,
padding: const EdgeInsets.symmetric(horizontal: 4),
- alignment: const AlignmentDirectional(12, -4),
+ alignment: AlignmentDirectional.topEnd,
);
final BuildContext context;
diff --git a/framework/lib/src/material/badge_theme.dart b/framework/lib/src/material/badge_theme.dart
index 1b0298a..1095100 100644
--- a/framework/lib/src/material/badge_theme.dart
+++ b/framework/lib/src/material/badge_theme.dart
@@ -41,6 +41,7 @@
this.textStyle,
this.padding,
this.alignment,
+ this.offset,
});
/// Overrides the default value for [Badge.backgroundColor].
@@ -62,7 +63,10 @@
final EdgeInsetsGeometry? padding;
/// Overrides the default value for [Badge.alignment].
- final AlignmentDirectional? alignment;
+ final AlignmentGeometry? alignment;
+
+ /// Overrides the default value for [Badge.offset].
+ final Offset? offset;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
@@ -73,7 +77,8 @@
double? largeSize,
TextStyle? textStyle,
EdgeInsetsGeometry? padding,
- AlignmentDirectional? alignment,
+ AlignmentGeometry? alignment,
+ Offset? offset,
}) {
return BadgeThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
@@ -83,6 +88,7 @@
textStyle: textStyle ?? this.textStyle,
padding: padding ?? this.padding,
alignment: alignment ?? this.alignment,
+ offset: offset ?? this.offset,
);
}
@@ -95,7 +101,8 @@
largeSize: lerpDouble(a?.largeSize, b?.largeSize, t),
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
- alignment: AlignmentDirectional.lerp(a?.alignment, b?.alignment, t),
+ alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
+ offset: Offset.lerp(a?.offset, b?.offset, t),
);
}
@@ -108,6 +115,7 @@
textStyle,
padding,
alignment,
+ offset,
);
@override
@@ -125,7 +133,8 @@
&& other.largeSize == largeSize
&& other.textStyle == textStyle
&& other.padding == padding
- && other.alignment == alignment;
+ && other.alignment == alignment
+ && other.offset == offset;
}
@override
@@ -137,7 +146,8 @@
properties.add(DoubleProperty('largeSize', largeSize, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
- properties.add(DiagnosticsProperty<AlignmentDirectional>('alignment', alignment, defaultValue: null));
+ properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
+ properties.add(DiagnosticsProperty<Offset>('offset', offset, defaultValue: null));
}
}
diff --git a/framework/lib/src/material/checkbox.dart b/framework/lib/src/material/checkbox.dart
index d4e8346..39b8846 100644
--- a/framework/lib/src/material/checkbox.dart
+++ b/framework/lib/src/material/checkbox.dart
@@ -254,10 +254,12 @@
/// [ThemeData.focusColor] is used.
final Color? focusColor;
+ /// {@template flutter.material.checkbox.hoverColor}
/// The color for the checkbox's [Material] when a pointer is hovering over it.
///
/// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
/// state, it will be used instead.
+ /// {@endtemplate}
///
/// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
/// hovered state. If that is also null, then the value of
@@ -332,10 +334,12 @@
/// will be width 2.
final BorderSide? side;
+ /// {@template flutter.material.checkbox.isError}
/// True if this checkbox wants to show an error state.
///
/// The checkbox will have different default container color and check color when
/// this is true. This is only used when [ThemeData.useMaterial3] is set to true.
+ /// {@endtemplate}
///
/// Must not be null. Defaults to false.
final bool isError;
diff --git a/framework/lib/src/material/checkbox_list_tile.dart b/framework/lib/src/material/checkbox_list_tile.dart
index 97f25c7..a7f046a 100644
--- a/framework/lib/src/material/checkbox_list_tile.dart
+++ b/framework/lib/src/material/checkbox_list_tile.dart
@@ -163,8 +163,20 @@
super.key,
required this.value,
required this.onChanged,
+ this.mouseCursor,
this.activeColor,
+ this.fillColor,
this.checkColor,
+ this.hoverColor,
+ this.overlayColor,
+ this.splashRadius,
+ this.materialTapTargetSize,
+ this.visualDensity,
+ this.focusNode,
+ this.autofocus = false,
+ this.shape,
+ this.side,
+ this.isError = false,
this.enabled,
this.tileColor,
this.title,
@@ -174,15 +186,10 @@
this.secondary,
this.selected = false,
this.controlAffinity = ListTileControlAffinity.platform,
- this.autofocus = false,
this.contentPadding,
this.tristate = false,
- this.shape,
this.checkboxShape,
this.selectedTileColor,
- this.side,
- this.visualDensity,
- this.focusNode,
this.onFocusChange,
this.enableFeedback,
}) : assert(tristate || value != null),
@@ -219,16 +226,98 @@
/// {@end-tool}
final ValueChanged<bool?>? onChanged;
+ /// The cursor for a mouse pointer when it enters or is hovering over the
+ /// widget.
+ ///
+ /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+ /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+ ///
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.disabled].
+ ///
+ /// If null, then the value of [CheckboxThemeData.mouseCursor] is used. If
+ /// that is also null, then [MaterialStateMouseCursor.clickable] is used.
+ final MouseCursor? mouseCursor;
+
/// The color to use when this checkbox is checked.
///
/// Defaults to [ColorScheme.secondary] of the current [Theme].
final Color? activeColor;
+ /// The color that fills the checkbox.
+ ///
+ /// Resolves in the following states:
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.disabled].
+ ///
+ /// If null, then the value of [activeColor] is used in the selected
+ /// state. If that is also null, the value of [CheckboxThemeData.fillColor]
+ /// is used. If that is also null, then the default value is used.
+ final MaterialStateProperty<Color?>? fillColor;
+
/// The color to use for the check icon when this checkbox is checked.
///
/// Defaults to Color(0xFFFFFFFF).
final Color? checkColor;
+ /// {@macro flutter.material.checkbox.hoverColor}
+ final Color? hoverColor;
+
+ /// The color for the checkbox's [Material].
+ ///
+ /// Resolves in the following states:
+ /// * [MaterialState.pressed].
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ ///
+ /// If null, then the value of [activeColor] with alpha [kRadialReactionAlpha]
+ /// and [hoverColor] is used in the pressed and hovered state. If that is also null,
+ /// the value of [CheckboxThemeData.overlayColor] is used. If that is also null,
+ /// then the the default value is used in the pressed and hovered state.
+ final MaterialStateProperty<Color?>? overlayColor;
+
+ /// {@macro flutter.material.checkbox.splashRadius}
+ ///
+ /// If null, then the value of [CheckboxThemeData.splashRadius] is used. If
+ /// that is also null, then [kRadialReactionRadius] is used.
+ final double? splashRadius;
+
+ /// {@macro flutter.material.checkbox.materialTapTargetSize}
+ ///
+ /// Defaults to [MaterialTapTargetSize.shrinkWrap].
+ final MaterialTapTargetSize? materialTapTargetSize;
+
+ /// Defines how compact the list tile's layout will be.
+ ///
+ /// {@macro flutter.material.themedata.visualDensity}
+ final VisualDensity? visualDensity;
+
+
+ /// {@macro flutter.widgets.Focus.focusNode}
+ final FocusNode? focusNode;
+
+ /// {@macro flutter.widgets.Focus.autofocus}
+ final bool autofocus;
+
+ /// {@macro flutter.material.ListTile.shape}
+ final ShapeBorder? shape;
+
+ /// {@macro flutter.material.checkbox.side}
+ ///
+ /// The given value is passed directly to [Checkbox.side].
+ ///
+ /// If this property is null, then [CheckboxThemeData.side] of
+ /// [ThemeData.checkboxTheme] is used. If that is also null, then the side
+ /// will be width 2.
+ final BorderSide? side;
+
+ /// {@macro flutter.material.checkbox.isError}
+ ///
+ /// Defaults to false.
+ final bool isError;
+
/// {@macro flutter.material.ListTile.tileColor}
final Color? tileColor;
@@ -270,9 +359,6 @@
/// Where to place the control relative to the text.
final ListTileControlAffinity controlAffinity;
- /// {@macro flutter.widgets.Focus.autofocus}
- final bool autofocus;
-
/// Defines insets surrounding the tile's contents.
///
/// This value will surround the [Checkbox], [title], [subtitle], and [secondary]
@@ -293,9 +379,6 @@
/// If tristate is false (the default), [value] must not be null.
final bool tristate;
- /// {@macro flutter.material.ListTile.shape}
- final ShapeBorder? shape;
-
/// {@macro flutter.material.checkbox.shape}
///
/// If this property is null then [CheckboxThemeData.shape] of [ThemeData.checkboxTheme]
@@ -306,23 +389,6 @@
/// If non-null, defines the background color when [CheckboxListTile.selected] is true.
final Color? selectedTileColor;
- /// {@macro flutter.material.checkbox.side}
- ///
- /// The given value is passed directly to [Checkbox.side].
- ///
- /// If this property is null, then [CheckboxThemeData.side] of
- /// [ThemeData.checkboxTheme] is used. If that is also null, then the side
- /// will be width 2.
- final BorderSide? side;
-
- /// Defines how compact the list tile's layout will be.
- ///
- /// {@macro flutter.material.themedata.visualDensity}
- final VisualDensity? visualDensity;
-
- /// {@macro flutter.widgets.Focus.focusNode}
- final FocusNode? focusNode;
-
/// {@macro flutter.material.inkwell.onFocusChange}
final ValueChanged<bool>? onFocusChange;
@@ -359,14 +425,20 @@
Widget build(BuildContext context) {
final Widget control = Checkbox(
value: value,
- onChanged: enabled ?? true ? onChanged : null ,
+ onChanged: enabled ?? true ? onChanged : null,
+ mouseCursor: mouseCursor,
activeColor: activeColor,
+ fillColor: fillColor,
checkColor: checkColor,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ hoverColor: hoverColor,
+ overlayColor: overlayColor,
+ splashRadius: splashRadius,
+ materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
autofocus: autofocus,
tristate: tristate,
shape: checkboxShape,
side: side,
+ isError: isError,
);
Widget? leading, trailing;
switch (controlAffinity) {
diff --git a/framework/lib/src/material/expansion_tile.dart b/framework/lib/src/material/expansion_tile.dart
index 855b9ce..0ec82f8 100644
--- a/framework/lib/src/material/expansion_tile.dart
+++ b/framework/lib/src/material/expansion_tile.dart
@@ -34,8 +34,7 @@
/// to the [leading] and [trailing] properties of [ExpansionTile].
///
/// {@tool dartpad}
-/// This example demonstrates how the [ExpansionTile] icon's location and appearance
-/// can be customized.
+/// This example demonstrates different configurations of ExpansionTile.
///
/// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.0.dart **
/// {@end-tool}
@@ -217,7 +216,7 @@
/// Used to override to the [ListTileThemeData.iconColor].
///
/// If this property is null then [ExpansionTileThemeData.iconColor] is used. If that
- /// is also null then the value of [ColorScheme.primary] is used.
+ /// is also null then the value of [ListTileThemeData.iconColor] is used.
///
/// See also:
///
@@ -228,15 +227,6 @@
/// The icon color of tile's expansion arrow icon when the sublist is collapsed.
///
/// Used to override to the [ListTileThemeData.iconColor].
- ///
- /// If this property is null then [ExpansionTileThemeData.collapsedIconColor] is used. If that
- /// is also null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurface] is used. Otherwise,
- /// defaults to [ThemeData.unselectedWidgetColor] color.
- ///
- /// See also:
- ///
- /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
- /// [ExpansionTileThemeData].
final Color? collapsedIconColor;
@@ -245,8 +235,7 @@
/// Used to override to the [ListTileThemeData.textColor].
///
/// If this property is null then [ExpansionTileThemeData.textColor] is used. If that
- /// is also null then and [ThemeData.useMaterial3] is true, color of the [TextTheme.bodyLarge]
- /// will be used for the [title] and [subtitle]. Otherwise, defaults to [ColorScheme.primary] color.
+ /// is also null then the value of [ListTileThemeData.textColor] is used.
///
/// See also:
///
@@ -258,10 +247,8 @@
///
/// Used to override to the [ListTileThemeData.textColor].
///
- /// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used.
- /// If that is also null and [ThemeData.useMaterial3] is true, color of the
- /// [TextTheme.bodyLarge] will be used for the [title] and [subtitle]. Otherwise,
- /// defaults to color of the [TextTheme.titleMedium].
+ /// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used. If that
+ /// is also null then the value of [ListTileThemeData.textColor] is used.
///
/// See also:
///
@@ -454,9 +441,7 @@
void didChangeDependencies() {
final ThemeData theme = Theme.of(context);
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
- final ExpansionTileThemeData defaults = theme.useMaterial3
- ? _ExpansionTileDefaultsM3(context)
- : _ExpansionTileDefaultsM2(context);
+ final ColorScheme colorScheme = theme.colorScheme;
_borderTween
..begin = widget.collapsedShape
?? expansionTileTheme.collapsedShape
@@ -473,13 +458,13 @@
_headerColorTween
..begin = widget.collapsedTextColor
?? expansionTileTheme.collapsedTextColor
- ?? defaults.collapsedTextColor
- ..end = widget.textColor ?? expansionTileTheme.textColor ?? defaults.textColor;
+ ?? theme.textTheme.titleMedium!.color
+ ..end = widget.textColor ?? expansionTileTheme.textColor ?? colorScheme.primary;
_iconColorTween
..begin = widget.collapsedIconColor
?? expansionTileTheme.collapsedIconColor
- ?? defaults.collapsedIconColor
- ..end = widget.iconColor ?? expansionTileTheme.iconColor ?? defaults.iconColor;
+ ?? theme.unselectedWidgetColor
+ ..end = widget.iconColor ?? expansionTileTheme.iconColor ?? colorScheme.primary;
_backgroundColorTween
..begin = widget.collapsedBackgroundColor ?? expansionTileTheme.collapsedBackgroundColor
..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor;
@@ -513,54 +498,3 @@
);
}
}
-
-class _ExpansionTileDefaultsM2 extends ExpansionTileThemeData {
- _ExpansionTileDefaultsM2(this.context);
-
- final BuildContext context;
- late final ThemeData _theme = Theme.of(context);
- late final ColorScheme _colorScheme = _theme.colorScheme;
-
- @override
- Color? get textColor => _colorScheme.primary;
-
- @override
- Color? get iconColor => _colorScheme.primary;
-
- @override
- Color? get collapsedTextColor => _theme.textTheme.titleMedium!.color;
-
- @override
- Color? get collapsedIconColor => _theme.unselectedWidgetColor;
-}
-
-// BEGIN GENERATED TOKEN PROPERTIES - ExpansionTile
-
-// Do not edit by hand. The code between the "BEGIN GENERATED" and
-// "END GENERATED" comments are generated from data in the Material
-// Design token database by the script:
-// dev/tools/gen_defaults/bin/gen_defaults.dart.
-
-// Token database version: v0_152
-
-class _ExpansionTileDefaultsM3 extends ExpansionTileThemeData {
- _ExpansionTileDefaultsM3(this.context);
-
- final BuildContext context;
- late final ThemeData _theme = Theme.of(context);
- late final ColorScheme _colors = _theme.colorScheme;
-
- @override
- Color? get textColor => Theme.of(context).textTheme.bodyLarge!.color;
-
- @override
- Color? get iconColor => _colors.primary;
-
- @override
- Color? get collapsedTextColor => Theme.of(context).textTheme.bodyLarge!.color;
-
- @override
- Color? get collapsedIconColor => _colors.onSurface;
-}
-
-// END GENERATED TOKEN PROPERTIES - ExpansionTile
diff --git a/framework/lib/src/material/input_chip.dart b/framework/lib/src/material/input_chip.dart
index ac5c40d..0e64b35 100644
--- a/framework/lib/src/material/input_chip.dart
+++ b/framework/lib/src/material/input_chip.dart
@@ -194,7 +194,7 @@
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ChipThemeData? defaults = Theme.of(context).useMaterial3
- ? _InputChipDefaultsM3(context, isEnabled)
+ ? _InputChipDefaultsM3(context, isEnabled, selected)
: null;
final Widget? resolvedDeleteIcon = deleteIcon
?? (Theme.of(context).useMaterial3 ? const Icon(Icons.clear, size: 18) : null);
@@ -247,7 +247,7 @@
// Token database version: v0_158
class _InputChipDefaultsM3 extends ChipThemeData {
- const _InputChipDefaultsM3(this.context, this.isEnabled)
+ const _InputChipDefaultsM3(this.context, this.isEnabled, this.isSelected)
: super(
elevation: 0.0,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
@@ -256,6 +256,7 @@
final BuildContext context;
final bool isEnabled;
+ final bool isSelected;
@override
TextStyle? get labelStyle => Theme.of(context).textTheme.labelLarge;
@@ -270,7 +271,9 @@
Color? get surfaceTintColor => Colors.transparent;
@override
- Color? get selectedColor => Theme.of(context).colorScheme.secondaryContainer;
+ Color? get selectedColor => isEnabled
+ ? Theme.of(context).colorScheme.secondaryContainer
+ : Theme.of(context).colorScheme.onSurface.withOpacity(0.12);
@override
Color? get checkmarkColor => null;
@@ -282,9 +285,11 @@
Color? get deleteIconColor => Theme.of(context).colorScheme.onSecondaryContainer;
@override
- BorderSide? get side => isEnabled
- ? BorderSide(color: Theme.of(context).colorScheme.outline)
- : BorderSide(color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12));
+ BorderSide? get side => !isSelected
+ ? isEnabled
+ ? BorderSide(color: Theme.of(context).colorScheme.outline)
+ : BorderSide(color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12))
+ : const BorderSide(color: Colors.transparent);
@override
IconThemeData? get iconTheme => IconThemeData(
diff --git a/framework/lib/src/material/list_tile.dart b/framework/lib/src/material/list_tile.dart
index c8af8a0..7fa0ea3 100644
--- a/framework/lib/src/material/list_tile.dart
+++ b/framework/lib/src/material/list_tile.dart
@@ -63,6 +63,49 @@
platform,
}
+/// Defines how [ListTile.leading] and [ListTile.trailing] are
+/// vertically aligned relative to the [ListTile]'s titles
+/// ([ListTile.title] and [ListTile.subtitle]).
+///
+/// See also:
+///
+/// * [ListTile.titleAlignment], to configure the title alignment for an
+/// individual [ListTile].
+/// * [ListTileThemeData.titleAlignment], to configure the title alignment
+/// for all of the [ListTile]s under a [ListTileTheme].
+/// * [ThemeData.listTileTheme], to configure the [ListTileTheme]
+/// for an entire app.
+enum ListTileTitleAlignment {
+ /// The top of the [ListTile.leading] and [ListTile.trailing] widgets are
+ /// placed [ListTile.minVerticalPadding] below the top of the [ListTile.title]
+ /// if [ListTile.isThreeLine] is true, otherwise they're centered relative
+ /// to the [ListTile.title] and [ListTile.subtitle] widgets.
+ ///
+ /// This is the default when [ThemeData.useMaterial3] is true.
+ threeLine,
+
+ /// The tops of the [ListTile.leading] and [ListTile.trailing] widgets are
+ /// placed 16 units below the top of the [ListTile.title]
+ /// if the titles' overall height is greater than 72, otherwise they're
+ /// centered relative to the [ListTile.title] and [ListTile.subtitle] widgets.
+ ///
+ /// This is the default when [ThemeData.useMaterial3] is false.
+ titleHeight,
+
+ /// The tops of the [ListTile.leading] and [ListTile.trailing] widgets are
+ /// placed [ListTile.minVerticalPadding] below the top of the [ListTile.title].
+ top,
+
+ /// The [ListTile.leading] and [ListTile.trailing] widgets are
+ /// centered relative to the [ListTile]'s titles.
+ center,
+
+ /// The bottoms of the [ListTile.leading] and [ListTile.trailing] widgets are
+ /// placed [ListTile.minVerticalPadding] above the bottom of the [ListTile]'s
+ /// titles.
+ bottom,
+}
+
/// A single fixed-height row that typically contains some text as well as
/// a leading or trailing icon.
///
@@ -156,6 +199,14 @@
/// ** See code in examples/api/lib/material/list_tile/list_tile.3.dart **
/// {@end-tool}
///
+/// {@tool dartpad}
+/// This sample shows [ListTile.titleAlignment] can be used to configure the
+/// [leading] and [trailing] widgets alignment relative to the [title] and
+/// [subtitle] widgets.
+///
+/// ** See code in examples/api/lib/material/list_tile/list_tile.4.dart **
+/// {@end-tool}
+///
/// {@tool snippet}
/// To use a [ListTile] within a [Row], it needs to be wrapped in an
/// [Expanded] widget. [ListTile] requires fixed width constraints,
@@ -317,6 +368,7 @@
this.horizontalTitleGap,
this.minVerticalPadding,
this.minLeadingWidth,
+ this.titleAlignment,
}) : assert(!isThreeLine || subtitle != null);
/// A widget to display before the title.
@@ -422,7 +474,7 @@
/// Defines the default color for [leading] and [trailing] icons.
///
/// If this property is null and [selected] is false then [ListTileThemeData.iconColor]
- /// is used. If that is also null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurface]
+ /// is used. If that is also null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurfaceVariant]
/// is used, otherwise if [ThemeData.brightness] is [Brightness.light], [Colors.black54] is used,
/// and if [ThemeData.brightness] is [Brightness.dark], the value is null.
///
@@ -616,6 +668,20 @@
/// that is also null, then a default value of 40 is used.
final double? minLeadingWidth;
+ /// Defines how [ListTile.leading] and [ListTile.trailing] are
+ /// vertically aligned relative to the [ListTile]'s titles
+ /// ([ListTile.title] and [ListTile.subtitle]).
+ ///
+ /// If this property is null then [ListTileThemeData.titleAlignment]
+ /// is used. If that is also null then [ListTileTitleAlignment.threeLine]
+ /// is used.
+ ///
+ /// See also:
+ ///
+ /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s
+ /// [ListTileThemeData].
+ final ListTileTitleAlignment? titleAlignment;
+
/// Add a one pixel border in between each tile. If color isn't specified the
/// [ThemeData.dividerColor] of the context's [Theme] is used.
///
@@ -761,6 +827,7 @@
final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection)
?? tileTheme.contentPadding?.resolve(textDirection)
?? defaults.contentPadding!.resolve(textDirection);
+
// Show basic cursor when ListTile isn't enabled or gesture callbacks are null.
final Set<MaterialState> mouseStates = <MaterialState>{
if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
@@ -769,6 +836,10 @@
?? tileTheme.mouseCursor?.resolve(mouseStates)
?? MaterialStateMouseCursor.clickable.resolve(mouseStates);
+ final ListTileTitleAlignment effectiveTitleAlignment = titleAlignment
+ ?? tileTheme.titleAlignment
+ ?? (theme.useMaterial3 ? ListTileTitleAlignment.threeLine : ListTileTitleAlignment.titleHeight);
+
return InkWell(
customBorder: shape ?? tileTheme.shape,
onTap: enabled ? onTap : null,
@@ -812,7 +883,7 @@
horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? defaults.minVerticalPadding!,
minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? defaults.minLeadingWidth!,
- material3: theme.useMaterial3,
+ titleAlignment: effectiveTitleAlignment,
),
),
),
@@ -856,6 +927,7 @@
properties.add(DoubleProperty('horizontalTitleGap', horizontalTitleGap, defaultValue: null));
properties.add(DoubleProperty('minVerticalPadding', minVerticalPadding, defaultValue: null));
properties.add(DoubleProperty('minLeadingWidth', minLeadingWidth, defaultValue: null));
+ properties.add(DiagnosticsProperty<ListTileTitleAlignment>('titleAlignment', titleAlignment, defaultValue: null));
}
}
@@ -910,7 +982,7 @@
required this.minVerticalPadding,
required this.minLeadingWidth,
this.subtitleBaselineType,
- required this.material3,
+ required this.titleAlignment,
});
final Widget? leading;
@@ -926,7 +998,7 @@
final double horizontalTitleGap;
final double minVerticalPadding;
final double minLeadingWidth;
- final bool material3;
+ final ListTileTitleAlignment titleAlignment;
@override
Iterable<_ListTileSlot> get slots => _ListTileSlot.values;
@@ -957,7 +1029,7 @@
horizontalTitleGap: horizontalTitleGap,
minVerticalPadding: minVerticalPadding,
minLeadingWidth: minLeadingWidth,
- material3: material3,
+ titleAlignment: titleAlignment,
);
}
@@ -973,7 +1045,7 @@
..horizontalTitleGap = horizontalTitleGap
..minLeadingWidth = minLeadingWidth
..minVerticalPadding = minVerticalPadding
- ..material3 = material3;
+ ..titleAlignment = titleAlignment;
}
}
@@ -988,7 +1060,7 @@
required double horizontalTitleGap,
required double minVerticalPadding,
required double minLeadingWidth,
- required bool material3,
+ required ListTileTitleAlignment titleAlignment,
}) : _isDense = isDense,
_visualDensity = visualDensity,
_isThreeLine = isThreeLine,
@@ -998,7 +1070,7 @@
_horizontalTitleGap = horizontalTitleGap,
_minVerticalPadding = minVerticalPadding,
_minLeadingWidth = minLeadingWidth,
- _material3 = material3;
+ _titleAlignment = titleAlignment;
RenderBox? get leading => childForSlot(_ListTileSlot.leading);
RenderBox? get title => childForSlot(_ListTileSlot.title);
@@ -1114,13 +1186,13 @@
markNeedsLayout();
}
- bool get material3 => _material3;
- bool _material3;
- set material3(bool value) {
- if (_material3 == value) {
+ ListTileTitleAlignment get titleAlignment => _titleAlignment;
+ ListTileTitleAlignment _titleAlignment;
+ set titleAlignment(ListTileTitleAlignment value) {
+ if (_titleAlignment == value) {
return;
}
- _material3 = value;
+ _titleAlignment = value;
markNeedsLayout();
}
@@ -1314,30 +1386,51 @@
final double leadingY;
final double trailingY;
- if (material3) {
- if (isThreeLine) {
+
+ switch (titleAlignment) {
+ case ListTileTitleAlignment.threeLine: {
+ if (isThreeLine) {
+ leadingY = _minVerticalPadding;
+ trailingY = _minVerticalPadding;
+ } else {
+ leadingY = (tileHeight - leadingSize.height) / 2.0;
+ trailingY = (tileHeight - trailingSize.height) / 2.0;
+ }
+ break;
+ }
+ case ListTileTitleAlignment.titleHeight: {
+ // This attempts to implement the redlines for the vertical position of the
+ // leading and trailing icons on the spec page:
+ // https://m2.material.io/components/lists#specs
+ // The interpretation for these redlines is as follows:
+ // - For large tiles (> 72dp), both leading and trailing controls should be
+ // a fixed distance from top. As per guidelines this is set to 16dp.
+ // - For smaller tiles, trailing should always be centered. Leading can be
+ // centered or closer to the top. It should never be further than 16dp
+ // to the top.
+ if (tileHeight > 72.0) {
+ leadingY = 16.0;
+ trailingY = 16.0;
+ } else {
+ leadingY = math.min((tileHeight - leadingSize.height) / 2.0, 16.0);
+ trailingY = (tileHeight - trailingSize.height) / 2.0;
+ }
+ break;
+ }
+ case ListTileTitleAlignment.top: {
leadingY = _minVerticalPadding;
trailingY = _minVerticalPadding;
- } else {
+ break;
+ }
+ case ListTileTitleAlignment.center: {
leadingY = (tileHeight - leadingSize.height) / 2.0;
trailingY = (tileHeight - trailingSize.height) / 2.0;
+ break;
}
- } else {
- // This attempts to implement the redlines for the vertical position of the
- // leading and trailing icons on the spec page:
- // https://material.io/design/components/lists.html#specs
- // The interpretation for these redlines is as follows:
- // - For large tiles (> 72dp), both leading and trailing controls should be
- // a fixed distance from top. As per guidelines this is set to 16dp.
- // - For smaller tiles, trailing should always be centered. Leading can be
- // centered or closer to the top. It should never be further than 16dp
- // to the top.
- if (tileHeight > 72.0) {
- leadingY = 16.0;
- trailingY = 16.0;
- } else {
- leadingY = math.min((tileHeight - leadingSize.height) / 2.0, 16.0);
- trailingY = (tileHeight - trailingSize.height) / 2.0;
+ case ListTileTitleAlignment.bottom: {
+ leadingY = tileHeight - leadingSize.height - _minVerticalPadding;
+ trailingY = tileHeight - trailingSize.height - _minVerticalPadding;
+ break;
}
}
@@ -1500,7 +1593,7 @@
Color? get selectedColor => _colors.primary;
@override
- Color? get iconColor => _colors.onSurface;
+ Color? get iconColor => _colors.onSurfaceVariant;
}
// END GENERATED TOKEN PROPERTIES - LisTile
diff --git a/framework/lib/src/material/list_tile_theme.dart b/framework/lib/src/material/list_tile_theme.dart
index 9f3aee6..9be598a 100644
--- a/framework/lib/src/material/list_tile_theme.dart
+++ b/framework/lib/src/material/list_tile_theme.dart
@@ -63,6 +63,7 @@
this.enableFeedback,
this.mouseCursor,
this.visualDensity,
+ this.titleAlignment,
});
/// Overrides the default value of [ListTile.dense].
@@ -119,6 +120,9 @@
/// If specified, overrides the default value of [ListTile.visualDensity].
final VisualDensity? visualDensity;
+ /// If specified, overrides the default value of [ListTile.titleAlignment].
+ final ListTileTitleAlignment? titleAlignment;
+
/// Creates a copy of this object with the given fields replaced with the
/// new values.
ListTileThemeData copyWith({
@@ -141,6 +145,7 @@
MaterialStateProperty<MouseCursor?>? mouseCursor,
bool? isThreeLine,
VisualDensity? visualDensity,
+ ListTileTitleAlignment? titleAlignment,
}) {
return ListTileThemeData(
dense: dense ?? this.dense,
@@ -161,6 +166,7 @@
enableFeedback: enableFeedback ?? this.enableFeedback,
mouseCursor: mouseCursor ?? this.mouseCursor,
visualDensity: visualDensity ?? this.visualDensity,
+ titleAlignment: titleAlignment ?? this.titleAlignment,
);
}
@@ -188,6 +194,7 @@
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
+ titleAlignment: t < 0.5 ? a?.titleAlignment : b?.titleAlignment,
);
}
@@ -211,6 +218,7 @@
enableFeedback,
mouseCursor,
visualDensity,
+ titleAlignment,
);
@override
@@ -239,7 +247,8 @@
&& other.minLeadingWidth == minLeadingWidth
&& other.enableFeedback == enableFeedback
&& other.mouseCursor == mouseCursor
- && other.visualDensity == visualDensity;
+ && other.visualDensity == visualDensity
+ && other.titleAlignment == titleAlignment;
}
@override
@@ -263,6 +272,7 @@
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
+ properties.add(DiagnosticsProperty<ListTileTitleAlignment>('titleAlignment', titleAlignment, defaultValue: null));
}
}
@@ -477,6 +487,7 @@
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
+ ListTileTitleAlignment? titleAlignment,
required Widget child,
}) {
return Builder(
@@ -498,6 +509,7 @@
horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth,
+ titleAlignment: titleAlignment ?? parent.titleAlignment,
),
child: child,
);
diff --git a/framework/lib/src/material/material_state.dart b/framework/lib/src/material/material_state.dart
index 16357a0..de9ab14 100644
--- a/framework/lib/src/material/material_state.dart
+++ b/framework/lib/src/material/material_state.dart
@@ -632,7 +632,6 @@
///
/// {@macro flutter.material.MaterialStateProperty.implementations}
abstract class MaterialStateProperty<T> {
-
/// Returns a value of type `T` that depends on [states].
///
/// Widgets like [TextButton] and [ElevatedButton] apply this method to their
diff --git a/framework/lib/src/material/navigation_rail_theme.dart b/framework/lib/src/material/navigation_rail_theme.dart
index 72780de..2253054 100644
--- a/framework/lib/src/material/navigation_rail_theme.dart
+++ b/framework/lib/src/material/navigation_rail_theme.dart
@@ -151,8 +151,10 @@
elevation: lerpDouble(a?.elevation, b?.elevation, t),
unselectedLabelTextStyle: TextStyle.lerp(a?.unselectedLabelTextStyle, b?.unselectedLabelTextStyle, t),
selectedLabelTextStyle: TextStyle.lerp(a?.selectedLabelTextStyle, b?.selectedLabelTextStyle, t),
- unselectedIconTheme: IconThemeData.lerp(a?.unselectedIconTheme, b?.unselectedIconTheme, t),
- selectedIconTheme: IconThemeData.lerp(a?.selectedIconTheme, b?.selectedIconTheme, t),
+ unselectedIconTheme: a?.unselectedIconTheme == null && b?.unselectedIconTheme == null
+ ? null : IconThemeData.lerp(a?.unselectedIconTheme, b?.unselectedIconTheme, t),
+ selectedIconTheme: a?.selectedIconTheme == null && b?.selectedIconTheme == null
+ ? null : IconThemeData.lerp(a?.selectedIconTheme, b?.selectedIconTheme, t),
groupAlignment: lerpDouble(a?.groupAlignment, b?.groupAlignment, t),
labelType: t < 0.5 ? a?.labelType : b?.labelType,
useIndicator: t < 0.5 ? a?.useIndicator : b?.useIndicator,
diff --git a/framework/lib/src/material/radio.dart b/framework/lib/src/material/radio.dart
index 8fc68d5..014bb72 100644
--- a/framework/lib/src/material/radio.dart
+++ b/framework/lib/src/material/radio.dart
@@ -264,10 +264,12 @@
/// [ThemeData.focusColor] is used.
final Color? focusColor;
+ /// {@template flutter.material.radio.hoverColor}
/// The color for the radio's [Material] when a pointer is hovering over it.
///
/// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
/// state, it will be used instead.
+ /// {@endtemplate}
///
/// If null, then the value of [RadioThemeData.overlayColor] is used in the
/// hovered state. If that is also null, then the value of
@@ -275,7 +277,7 @@
final Color? hoverColor;
/// {@template flutter.material.radio.overlayColor}
- /// The color for the checkbox's [Material].
+ /// The color for the radio's [Material].
///
/// Resolves in the following states:
/// * [MaterialState.pressed].
diff --git a/framework/lib/src/material/radio_list_tile.dart b/framework/lib/src/material/radio_list_tile.dart
index 754220c..bc5534d 100644
--- a/framework/lib/src/material/radio_list_tile.dart
+++ b/framework/lib/src/material/radio_list_tile.dart
@@ -162,8 +162,14 @@
required this.value,
required this.groupValue,
required this.onChanged,
+ this.mouseCursor,
this.toggleable = false,
this.activeColor,
+ this.fillColor,
+ this.hoverColor,
+ this.overlayColor,
+ this.splashRadius,
+ this.materialTapTargetSize,
this.title,
this.subtitle,
this.isThreeLine = false,
@@ -220,6 +226,20 @@
/// ```
final ValueChanged<T?>? onChanged;
+ /// The cursor for a mouse pointer when it enters or is hovering over the
+ /// widget.
+ ///
+ /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+ /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+ ///
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.disabled].
+ ///
+ /// If null, then the value of [RadioThemeData.mouseCursor] is used.
+ /// If that is also null, then [MaterialStateMouseCursor.clickable] is used.
+ final MouseCursor? mouseCursor;
+
/// Set to true if this radio list tile is allowed to be returned to an
/// indeterminate state by selecting it again when selected.
///
@@ -250,6 +270,45 @@
/// Defaults to [ColorScheme.secondary] of the current [Theme].
final Color? activeColor;
+ /// The color that fills the radio button.
+ ///
+ /// Resolves in the following states:
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.disabled].
+ ///
+ /// If null, then the value of [activeColor] is used in the selected state. If
+ /// that is also null, then the value of [RadioThemeData.fillColor] is used.
+ /// If that is also null, then the default value is used.
+ final MaterialStateProperty<Color?>? fillColor;
+
+ /// {@macro flutter.material.radio.materialTapTargetSize}
+ ///
+ /// Defaults to [MaterialTapTargetSize.shrinkWrap].
+ final MaterialTapTargetSize? materialTapTargetSize;
+
+ /// {@macro flutter.material.radio.hoverColor}
+ final Color? hoverColor;
+
+ /// The color for the radio's [Material].
+ ///
+ /// Resolves in the following states:
+ /// * [MaterialState.pressed].
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ ///
+ /// If null, then the value of [activeColor] with alpha [kRadialReactionAlpha]
+ /// and [hoverColor] is used in the pressed and hovered state. If that is also
+ /// null, the value of [SwitchThemeData.overlayColor] is used. If that is
+ /// also null, then the default value is used in the pressed and hovered state.
+ final MaterialStateProperty<Color?>? overlayColor;
+
+ /// {@macro flutter.material.radio.splashRadius}
+ ///
+ /// If null, then the value of [RadioThemeData.splashRadius] is used. If that
+ /// is also null, then [kRadialReactionRadius] is used.
+ final double? splashRadius;
+
/// The primary content of the list tile.
///
/// Typically a [Text] widget.
@@ -341,8 +400,13 @@
onChanged: onChanged,
toggleable: toggleable,
activeColor: activeColor,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
autofocus: autofocus,
+ fillColor: fillColor,
+ mouseCursor: mouseCursor,
+ hoverColor: hoverColor,
+ overlayColor: overlayColor,
+ splashRadius: splashRadius,
);
Widget? leading, trailing;
switch (controlAffinity) {
diff --git a/framework/lib/src/material/search.dart b/framework/lib/src/material/search.dart
index e5f656b..f26d895 100644
--- a/framework/lib/src/material/search.dart
+++ b/framework/lib/src/material/search.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flute/services.dart';
import 'package:flute/widgets.dart';
import 'app_bar.dart';
@@ -231,7 +232,9 @@
final ColorScheme colorScheme = theme.colorScheme;
return theme.copyWith(
appBarTheme: AppBarTheme(
- brightness: colorScheme.brightness,
+ systemOverlayStyle: colorScheme.brightness == Brightness.dark
+ ? SystemUiOverlayStyle.light
+ : SystemUiOverlayStyle.dark,
backgroundColor: colorScheme.brightness == Brightness.dark ? Colors.grey[900] : Colors.white,
iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
titleTextStyle: theme.textTheme.titleLarge,
diff --git a/framework/lib/src/material/slider.dart b/framework/lib/src/material/slider.dart
index d64d736..c903541 100644
--- a/framework/lib/src/material/slider.dart
+++ b/framework/lib/src/material/slider.dart
@@ -1215,7 +1215,7 @@
return;
}
_sliderTheme = value;
- markNeedsPaint();
+ _updateLabelPainter();
}
double get textScaleFactor => _textScaleFactor;
diff --git a/framework/lib/src/material/snack_bar.dart b/framework/lib/src/material/snack_bar.dart
index 5d45c47..67cf2bd 100644
--- a/framework/lib/src/material/snack_bar.dart
+++ b/framework/lib/src/material/snack_bar.dart
@@ -140,15 +140,20 @@
final SnackBarThemeData snackBarTheme = Theme.of(context).snackBarTheme;
MaterialStateColor resolveForegroundColor() {
- if (widget.textColor is MaterialStateColor) {
- return widget.textColor! as MaterialStateColor;
+ if (widget.textColor != null) {
+ if (widget.textColor is MaterialStateColor) {
+ return widget.textColor! as MaterialStateColor;
+ }
+ } else if (snackBarTheme.actionTextColor != null) {
+ if (snackBarTheme.actionTextColor is MaterialStateColor) {
+ return snackBarTheme.actionTextColor! as MaterialStateColor;
+ }
+ } else if (defaults.actionTextColor != null) {
+ if (defaults.actionTextColor is MaterialStateColor) {
+ return defaults.actionTextColor! as MaterialStateColor;
+ }
}
- if (snackBarTheme.actionTextColor is MaterialStateColor) {
- return snackBarTheme.actionTextColor! as MaterialStateColor;
- }
- if (defaults.actionTextColor is MaterialStateColor) {
- return defaults.actionTextColor! as MaterialStateColor;
- }
+
return MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return widget.disabledTextColor ??
@@ -234,6 +239,7 @@
this.shape,
this.behavior,
this.action,
+ this.actionOverflowThreshold,
this.showCloseIcon,
this.closeIconColor,
this.duration = _snackBarDisplayDuration,
@@ -242,10 +248,11 @@
this.dismissDirection = DismissDirection.down,
this.clipBehavior = Clip.hardEdge,
}) : assert(elevation == null || elevation >= 0.0),
- assert(
- width == null || margin == null,
+ assert(width == null || margin == null,
'Width and margin can not be used together',
- );
+ ),
+ assert(actionOverflowThreshold == null || (actionOverflowThreshold >= 0 && actionOverflowThreshold <= 1),
+ 'Action overflow threshold must be between 0 and 1 inclusive');
/// The primary content of the snack bar.
///
@@ -353,6 +360,18 @@
/// The action should not be "dismiss" or "cancel".
final SnackBarAction? action;
+ /// (optional) The percentage threshold for action widget's width before it overflows
+ /// to a new line.
+ ///
+ /// Must be between 0 and 1. If the width of the snackbar's [content] is greater
+ /// than this percentage of the width of the snackbar less the width of its [action],
+ /// then the [action] will appear below the [content].
+ ///
+ /// At a value of 0, the action will not overflow to a new line.
+ ///
+ /// Defaults to 0.25.
+ final double? actionOverflowThreshold;
+
/// (optional) Whether to include a "close" icon widget.
///
/// Tapping the icon will close the snack bar.
@@ -426,6 +445,7 @@
shape: shape,
behavior: behavior,
action: action,
+ actionOverflowThreshold: actionOverflowThreshold,
showCloseIcon: showCloseIcon,
closeIconColor: closeIconColor,
duration: duration,
@@ -596,10 +616,11 @@
final EdgeInsets margin = widget.margin?.resolve(TextDirection.ltr) ?? snackBarTheme.insetPadding ?? defaults.insetPadding!;
final double snackBarWidth = widget.width ?? MediaQuery.sizeOf(context).width - (margin.left + margin.right);
- // Action and Icon will overflow to a new line if their width is greater
- // than one quarter of the total Snack Bar width.
- final bool actionLineOverflow =
- actionAndIconWidth / snackBarWidth > 0.25;
+ final double actionOverflowThreshold = widget.actionOverflowThreshold
+ ?? snackBarTheme.actionOverflowThreshold
+ ?? defaults.actionOverflowThreshold!;
+
+ final bool willOverflowAction = actionAndIconWidth / snackBarWidth > actionOverflowThreshold;
final List<Widget> maybeActionAndIcon = <Widget>[
if (widget.action != null)
@@ -640,18 +661,17 @@
),
),
),
- if(!actionLineOverflow) ...maybeActionAndIcon,
- if(actionLineOverflow) SizedBox(width: snackBarWidth*0.4),
+ if (!willOverflowAction) ...maybeActionAndIcon,
+ if (willOverflowAction) SizedBox(width: snackBarWidth * 0.4),
],
),
- if(actionLineOverflow) Padding(
- padding: const EdgeInsets.only(bottom: _singleLineVerticalPadding),
- child: Row(mainAxisAlignment: MainAxisAlignment.end,
- children: maybeActionAndIcon),
- ),
- ],
-
- ),
+ if (willOverflowAction)
+ Padding(
+ padding: const EdgeInsets.only(bottom: _singleLineVerticalPadding),
+ child: Row(mainAxisAlignment: MainAxisAlignment.end, children: maybeActionAndIcon),
+ ),
+ ],
+ ),
);
if (!isFloatingSnackBar) {
@@ -815,6 +835,9 @@
@override
Color get closeIconColor => _colors.onSurface;
+
+ @override
+ double get actionOverflowThreshold => 0.25;
}
// BEGIN GENERATED TOKEN PROPERTIES - Snackbar
@@ -879,6 +902,12 @@
@override
bool get showCloseIcon => false;
+
+ @override
+ Color? get closeIconColor => _colors.onInverseSurface;
+
+ @override
+ double get actionOverflowThreshold => 0.25;
}
// END GENERATED TOKEN PROPERTIES - Snackbar
diff --git a/framework/lib/src/material/snack_bar_theme.dart b/framework/lib/src/material/snack_bar_theme.dart
index a4b4c88..820e35a 100644
--- a/framework/lib/src/material/snack_bar_theme.dart
+++ b/framework/lib/src/material/snack_bar_theme.dart
@@ -64,11 +64,13 @@
this.insetPadding,
this.showCloseIcon,
this.closeIconColor,
+ this.actionOverflowThreshold,
}) : assert(elevation == null || elevation >= 0.0),
- assert(
- width == null ||
- (identical(behavior, SnackBarBehavior.floating)),
- 'Width can only be set if behaviour is SnackBarBehavior.floating');
+ assert(width == null || identical(behavior, SnackBarBehavior.floating),
+ 'Width can only be set if behaviour is SnackBarBehavior.floating'),
+ assert(actionOverflowThreshold == null || (actionOverflowThreshold >= 0 && actionOverflowThreshold <= 1),
+ 'Action overflow threshold must be between 0 and 1 inclusive');
+
/// Overrides the default value for [SnackBar.backgroundColor].
///
/// If null, [SnackBar] defaults to dark grey: `Color(0xFF323232)`.
@@ -133,6 +135,11 @@
/// This value is only used if [showCloseIcon] is true.
final Color? closeIconColor;
+ /// Overrides the default value for [SnackBar.actionOverflowThreshold].
+ ///
+ /// Must be a value between 0 and 1, if present.
+ final double? actionOverflowThreshold;
+
/// Creates a copy of this object with the given fields replaced with the
/// new values.
SnackBarThemeData copyWith({
@@ -147,6 +154,7 @@
EdgeInsets? insetPadding,
bool? showCloseIcon,
Color? closeIconColor,
+ double? actionOverflowThreshold,
}) {
return SnackBarThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
@@ -160,6 +168,7 @@
insetPadding: insetPadding ?? this.insetPadding,
showCloseIcon: showCloseIcon ?? this.showCloseIcon,
closeIconColor: closeIconColor ?? this.closeIconColor,
+ actionOverflowThreshold: actionOverflowThreshold ?? this.actionOverflowThreshold,
);
}
@@ -180,6 +189,7 @@
width: lerpDouble(a?.width, b?.width, t),
insetPadding: EdgeInsets.lerp(a?.insetPadding, b?.insetPadding, t),
closeIconColor: Color.lerp(a?.closeIconColor, b?.closeIconColor, t),
+ actionOverflowThreshold: lerpDouble(a?.actionOverflowThreshold, b?.actionOverflowThreshold, t),
);
}
@@ -196,6 +206,7 @@
insetPadding,
showCloseIcon,
closeIconColor,
+ actionOverflowThreshold,
);
@override
@@ -217,7 +228,8 @@
&& other.width == width
&& other.insetPadding == insetPadding
&& other.showCloseIcon == showCloseIcon
- && other.closeIconColor == closeIconColor;
+ && other.closeIconColor == closeIconColor
+ && other.actionOverflowThreshold == actionOverflowThreshold;
}
@override
@@ -234,5 +246,6 @@
properties.add(DiagnosticsProperty<EdgeInsets>('insetPadding', insetPadding, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('showCloseIcon', showCloseIcon, defaultValue: null));
properties.add(ColorProperty('closeIconColor', closeIconColor, defaultValue: null));
+ properties.add(DoubleProperty('actionOverflowThreshold', actionOverflowThreshold, defaultValue: null));
}
}
diff --git a/framework/lib/src/material/switch.dart b/framework/lib/src/material/switch.dart
index 0170814..1e05612 100644
--- a/framework/lib/src/material/switch.dart
+++ b/framework/lib/src/material/switch.dart
@@ -105,6 +105,7 @@
this.onInactiveThumbImageError,
this.thumbColor,
this.trackColor,
+ this.trackOutlineColor,
this.thumbIcon,
this.materialTapTargetSize,
this.dragStartBehavior = DragStartBehavior.start,
@@ -151,6 +152,7 @@
this.materialTapTargetSize,
this.thumbColor,
this.trackColor,
+ this.trackOutlineColor,
this.thumbIcon,
this.dragStartBehavior = DragStartBehavior.start,
this.mouseCursor,
@@ -195,7 +197,9 @@
/// ```
final ValueChanged<bool>? onChanged;
+ /// {@template flutter.material.switch.activeColor}
/// The color to use when this switch is on.
+ /// {@endtemplate}
///
/// Defaults to [ColorScheme.secondary].
///
@@ -203,7 +207,9 @@
/// state, it will be used instead of this color.
final Color? activeColor;
+ /// {@template flutter.material.switch.activeTrackColor}
/// The color to use on the track when this switch is on.
+ /// {@endtemplate}
///
/// Defaults to [ColorScheme.secondary] with the opacity set at 50%.
///
@@ -213,7 +219,9 @@
/// state, it will be used instead of this color.
final Color? activeTrackColor;
+ /// {@template flutter.material.switch.inactiveThumbColor}
/// The color to use on the thumb when this switch is off.
+ /// {@endtemplate}
///
/// Defaults to the colors described in the Material design specification.
///
@@ -223,7 +231,9 @@
/// used instead of this color.
final Color? inactiveThumbColor;
+ /// {@template flutter.material.switch.inactiveTrackColor}
/// The color to use on the track when this switch is off.
+ /// {@endtemplate}
///
/// Defaults to the colors described in the Material design specification.
///
@@ -233,22 +243,30 @@
/// used instead of this color.
final Color? inactiveTrackColor;
+ /// {@template flutter.material.switch.activeThumbImage}
/// An image to use on the thumb of this switch when the switch is on.
+ /// {@endtemplate}
///
/// Ignored if this switch is created with [Switch.adaptive].
final ImageProvider? activeThumbImage;
+ /// {@template flutter.material.switch.onActiveThumbImageError}
/// An optional error callback for errors emitted when loading
/// [activeThumbImage].
+ /// {@endtemplate}
final ImageErrorListener? onActiveThumbImageError;
+ /// {@template flutter.material.switch.inactiveThumbImage}
/// An image to use on the thumb of this switch when the switch is off.
+ /// {@endtemplate}
///
/// Ignored if this switch is created with [Switch.adaptive].
final ImageProvider? inactiveThumbImage;
+ /// {@template flutter.material.switch.onInactiveThumbImageError}
/// An optional error callback for errors emitted when loading
/// [inactiveThumbImage].
+ /// {@endtemplate}
final ImageErrorListener? onInactiveThumbImageError;
/// {@template flutter.material.switch.thumbColor}
@@ -333,6 +351,40 @@
/// | Disabled | `Colors.black12` | `Colors.white10` |
final MaterialStateProperty<Color?>? trackColor;
+ /// {@template flutter.material.switch.trackOutlineColor}
+ /// The outline color of this [Switch]'s track.
+ ///
+ /// Resolved in the following states:
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.focused].
+ /// * [MaterialState.disabled].
+ ///
+ /// {@tool snippet}
+ /// This example resolves the [trackOutlineColor] based on the current
+ /// [MaterialState] of the [Switch], providing a different [Color] when it is
+ /// [MaterialState.disabled].
+ ///
+ /// ```dart
+ /// Switch(
+ /// value: true,
+ /// onChanged: (_) => true,
+ /// trackOutlineColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
+ /// if (states.contains(MaterialState.disabled)) {
+ /// return Colors.orange.withOpacity(.48);
+ /// }
+ /// return null; // Use the default color.
+ /// }),
+ /// )
+ /// ```
+ /// {@end-tool}
+ /// {@endtemplate}
+ ///
+ /// In Material 3, the outline color defaults to transparent in the selected
+ /// state and [ColorScheme.outline] in the unselected state. In Material 2,
+ /// the [Switch] track has no outline by default.
+ final MaterialStateProperty<Color?>? trackOutlineColor;
+
/// {@template flutter.material.switch.thumbIcon}
/// The icon to use on the thumb of this switch
///
@@ -519,6 +571,7 @@
onInactiveThumbImageError: onInactiveThumbImageError,
thumbColor: thumbColor,
trackColor: trackColor,
+ trackOutlineColor: trackOutlineColor,
thumbIcon: thumbIcon,
materialTapTargetSize: materialTapTargetSize,
dragStartBehavior: dragStartBehavior,
@@ -578,6 +631,7 @@
this.onInactiveThumbImageError,
this.thumbColor,
this.trackColor,
+ this.trackOutlineColor,
this.thumbIcon,
this.materialTapTargetSize,
this.dragStartBehavior = DragStartBehavior.start,
@@ -604,6 +658,7 @@
final ImageErrorListener? onInactiveThumbImageError;
final MaterialStateProperty<Color?>? thumbColor;
final MaterialStateProperty<Color?>? trackColor;
+ final MaterialStateProperty<Color?>? trackOutlineColor;
final MaterialStateProperty<Icon?>? thumbIcon;
final MaterialTapTargetSize? materialTapTargetSize;
final DragStartBehavior dragStartBehavior;
@@ -765,11 +820,17 @@
?? switchTheme.trackColor?.resolve(activeStates)
?? _widgetThumbColor.resolve(activeStates)?.withAlpha(0x80)
?? defaults.trackColor!.resolve(activeStates)!;
+ final Color effectiveActiveTrackOutlineColor = widget.trackOutlineColor?.resolve(activeStates)
+ ?? switchTheme.trackOutlineColor?.resolve(activeStates)
+ ?? Colors.transparent;
+
final Color effectiveInactiveTrackColor = widget.trackColor?.resolve(inactiveStates)
?? _widgetTrackColor.resolve(inactiveStates)
?? switchTheme.trackColor?.resolve(inactiveStates)
?? defaults.trackColor!.resolve(inactiveStates)!;
- final Color? effectiveInactiveTrackOutlineColor = switchConfig.trackOutlineColor?.resolve(inactiveStates);
+ final Color? effectiveInactiveTrackOutlineColor = widget.trackOutlineColor?.resolve(inactiveStates)
+ ?? switchTheme.trackOutlineColor?.resolve(inactiveStates)
+ ?? defaults.trackOutlineColor?.resolve(inactiveStates);
final Icon? effectiveActiveIcon = widget.thumbIcon?.resolve(activeStates)
?? switchTheme.thumbIcon?.resolve(activeStates);
@@ -858,6 +919,7 @@
..inactiveThumbImage = widget.inactiveThumbImage
..onInactiveThumbImageError = widget.onInactiveThumbImageError
..activeTrackColor = effectiveActiveTrackColor
+ ..activeTrackOutlineColor = effectiveActiveTrackOutlineColor
..inactiveTrackColor = effectiveInactiveTrackColor
..inactiveTrackOutlineColor = effectiveInactiveTrackOutlineColor
..configuration = createLocalImageConfiguration(context)
@@ -1089,6 +1151,16 @@
notifyListeners();
}
+ Color? get activeTrackOutlineColor => _activeTrackOutlineColor;
+ Color? _activeTrackOutlineColor;
+ set activeTrackOutlineColor(Color? value) {
+ if (value == _activeTrackOutlineColor) {
+ return;
+ }
+ _activeTrackOutlineColor = value;
+ notifyListeners();
+ }
+
Color? get inactiveTrackOutlineColor => _inactiveTrackOutlineColor;
Color? _inactiveTrackOutlineColor;
set inactiveTrackOutlineColor(Color? value) {
@@ -1297,7 +1369,7 @@
final double colorValue = CurvedAnimation(parent: positionController, curve: Curves.easeOut, reverseCurve: Curves.easeIn).value;
final Color trackColor = Color.lerp(inactiveTrackColor, activeTrackColor, colorValue)!;
final Color? trackOutlineColor = inactiveTrackOutlineColor == null ? null
- : Color.lerp(inactiveTrackOutlineColor, Colors.transparent, colorValue);
+ : Color.lerp(inactiveTrackOutlineColor, activeTrackOutlineColor, colorValue);
Color lerpedThumbColor;
if (!reaction.isDismissed) {
lerpedThumbColor = Color.lerp(inactivePressedColor, activePressedColor, colorValue)!;
@@ -1493,7 +1565,6 @@
double get pressedThumbRadius;
double get thumbRadiusWithIcon;
List<BoxShadow>? get thumbShadow;
- MaterialStateProperty<Color?>? get trackOutlineColor;
MaterialStateProperty<Color> get iconColor;
double? get thumbOffset;
Size get transitionalThumbSize;
@@ -1535,9 +1606,6 @@
double get trackHeight => 14.0;
@override
- MaterialStateProperty<Color?>? get trackOutlineColor => null;
-
- @override
double get trackWidth => 33.0;
@override
@@ -1591,6 +1659,9 @@
}
@override
+ MaterialStateProperty<Color?>? get trackOutlineColor => null;
+
+ @override
MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize;
@override
@@ -1701,6 +1772,19 @@
}
@override
+ MaterialStateProperty<Color?> get trackOutlineColor {
+ return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return Colors.transparent;
+ }
+ if (states.contains(MaterialState.disabled)) {
+ return _colors.onSurface.withOpacity(0.12);
+ }
+ return _colors.outline;
+ });
+ }
+
+ @override
MaterialStateProperty<Color?> get overlayColor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
@@ -1803,19 +1887,6 @@
double get trackHeight => 32.0;
@override
- MaterialStateProperty<Color?> get trackOutlineColor {
- return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
- if (states.contains(MaterialState.selected)) {
- return null;
- }
- if (states.contains(MaterialState.disabled)) {
- return _colors.onSurface.withOpacity(0.12);
- }
- return _colors.outline;
- });
- }
-
- @override
double get trackWidth => 52.0;
// The thumb size at the middle of the track. Hand coded default based on the animation specs.
diff --git a/framework/lib/src/material/switch_list_tile.dart b/framework/lib/src/material/switch_list_tile.dart
index e017c2d..f060e9b 100644
--- a/framework/lib/src/material/switch_list_tile.dart
+++ b/framework/lib/src/material/switch_list_tile.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flute/gestures.dart';
import 'package:flute/widgets.dart';
import 'list_tile.dart';
@@ -163,13 +164,27 @@
super.key,
required this.value,
required this.onChanged,
- this.tileColor,
this.activeColor,
this.activeTrackColor,
this.inactiveThumbColor,
this.inactiveTrackColor,
this.activeThumbImage,
+ this.onActiveThumbImageError,
this.inactiveThumbImage,
+ this.onInactiveThumbImageError,
+ this.thumbColor,
+ this.trackColor,
+ this.trackOutlineColor,
+ this.thumbIcon,
+ this.materialTapTargetSize,
+ this.dragStartBehavior = DragStartBehavior.start,
+ this.mouseCursor,
+ this.overlayColor,
+ this.splashRadius,
+ this.focusNode,
+ this.onFocusChange,
+ this.autofocus = false,
+ this.tileColor,
this.title,
this.subtitle,
this.isThreeLine = false,
@@ -177,16 +192,16 @@
this.contentPadding,
this.secondary,
this.selected = false,
- this.autofocus = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.shape,
this.selectedTileColor,
this.visualDensity,
- this.focusNode,
- this.onFocusChange,
this.enableFeedback,
this.hoverColor,
}) : _switchListTileType = _SwitchListTileType.material,
+ applyCupertinoTheme = false,
+ assert(activeThumbImage != null || onActiveThumbImageError == null),
+ assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
assert(!isThreeLine || subtitle != null);
/// Creates a Material [ListTile] with an adaptive [Switch], following
@@ -205,13 +220,28 @@
super.key,
required this.value,
required this.onChanged,
- this.tileColor,
this.activeColor,
this.activeTrackColor,
this.inactiveThumbColor,
this.inactiveTrackColor,
this.activeThumbImage,
+ this.onActiveThumbImageError,
this.inactiveThumbImage,
+ this.onInactiveThumbImageError,
+ this.thumbColor,
+ this.trackColor,
+ this.trackOutlineColor,
+ this.thumbIcon,
+ this.materialTapTargetSize,
+ this.dragStartBehavior = DragStartBehavior.start,
+ this.mouseCursor,
+ this.overlayColor,
+ this.splashRadius,
+ this.focusNode,
+ this.onFocusChange,
+ this.autofocus = false,
+ this.applyCupertinoTheme,
+ this.tileColor,
this.title,
this.subtitle,
this.isThreeLine = false,
@@ -219,17 +249,16 @@
this.contentPadding,
this.secondary,
this.selected = false,
- this.autofocus = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.shape,
this.selectedTileColor,
this.visualDensity,
- this.focusNode,
- this.onFocusChange,
this.enableFeedback,
this.hoverColor,
}) : _switchListTileType = _SwitchListTileType.adaptive,
- assert(!isThreeLine || subtitle != null);
+ assert(!isThreeLine || subtitle != null),
+ assert(activeThumbImage != null || onActiveThumbImageError == null),
+ assert(inactiveThumbImage != null || onInactiveThumbImageError == null);
/// Whether this switch is checked.
///
@@ -263,43 +292,146 @@
/// {@end-tool}
final ValueChanged<bool>? onChanged;
- /// The color to use when this switch is on.
+ /// {@macro flutter.material.switch.activeColor}
///
/// Defaults to [ColorScheme.secondary] of the current [Theme].
final Color? activeColor;
- /// The color to use on the track when this switch is on.
+ /// {@macro flutter.material.switch.activeTrackColor}
///
/// Defaults to [ThemeData.toggleableActiveColor] with the opacity set at 50%.
///
/// Ignored if created with [SwitchListTile.adaptive].
final Color? activeTrackColor;
- /// The color to use on the thumb when this switch is off.
+ /// {@macro flutter.material.switch.inactiveThumbColor}
///
/// Defaults to the colors described in the Material design specification.
///
/// Ignored if created with [SwitchListTile.adaptive].
final Color? inactiveThumbColor;
- /// The color to use on the track when this switch is off.
+ /// {@macro flutter.material.switch.inactiveTrackColor}
///
/// Defaults to the colors described in the Material design specification.
///
/// Ignored if created with [SwitchListTile.adaptive].
final Color? inactiveTrackColor;
- /// {@macro flutter.material.ListTile.tileColor}
- final Color? tileColor;
-
- /// An image to use on the thumb of this switch when the switch is on.
+ /// {@macro flutter.material.switch.activeThumbImage}
final ImageProvider? activeThumbImage;
- /// An image to use on the thumb of this switch when the switch is off.
+ /// {@macro flutter.material.switch.onActiveThumbImageError}
+ final ImageErrorListener? onActiveThumbImageError;
+
+ /// {@macro flutter.material.switch.inactiveThumbImage}
///
/// Ignored if created with [SwitchListTile.adaptive].
final ImageProvider? inactiveThumbImage;
+ /// {@macro flutter.material.switch.onInactiveThumbImageError}
+ final ImageErrorListener? onInactiveThumbImageError;
+
+ /// The color of this switch's thumb.
+ ///
+ /// Resolved in the following states:
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.disabled].
+ ///
+ /// If null, then the value of [activeColor] is used in the selected state
+ /// and [inactiveThumbColor] in the default state. If that is also null, then
+ /// the value of [SwitchThemeData.thumbColor] is used. If that is also null,
+ /// The default value is used.
+ final MaterialStateProperty<Color?>? thumbColor;
+
+ /// The color of this switch's track.
+ ///
+ /// Resolved in the following states:
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.disabled].
+ ///
+ /// If null, then the value of [activeTrackColor] is used in the selected
+ /// state and [inactiveTrackColor] in the default state. If that is also null,
+ /// then the value of [SwitchThemeData.trackColor] is used. If that is also
+ /// null, then the default value is used.
+ final MaterialStateProperty<Color?>? trackColor;
+
+ /// {@macro flutter.material.switch.trackOutlineColor}
+ ///
+ /// The [ListTile] will be focused when this [SwitchListTile] requests focus,
+ /// so the focused outline color of the switch will be ignored.
+ ///
+ /// In Material 3, the outline color defaults to transparent in the selected
+ /// state and [ColorScheme.outline] in the unselected state. In Material 2,
+ /// the [Switch] track has no outline.
+ final MaterialStateProperty<Color?>? trackOutlineColor;
+
+ /// The icon to use on the thumb of this switch
+ ///
+ /// Resolved in the following states:
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.disabled].
+ ///
+ /// If null, then the value of [SwitchThemeData.thumbIcon] is used. If this is
+ /// also null, then the [Switch] does not have any icons on the thumb.
+ final MaterialStateProperty<Icon?>? thumbIcon;
+
+ /// {@macro flutter.material.switch.materialTapTargetSize}
+ ///
+ /// defaults to [MaterialTapTargetSize.shrinkWrap].
+ final MaterialTapTargetSize? materialTapTargetSize;
+
+ /// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
+ final DragStartBehavior dragStartBehavior;
+
+ /// The cursor for a mouse pointer when it enters or is hovering over the
+ /// widget.
+ ///
+ /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
+ /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
+ ///
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ /// * [MaterialState.disabled].
+ ///
+ /// If null, then the value of [SwitchThemeData.mouseCursor] is used. If that
+ /// is also null, then [MaterialStateMouseCursor.clickable] is used.
+ final MouseCursor? mouseCursor;
+
+ /// The color for the switch's [Material].
+ ///
+ /// Resolves in the following states:
+ /// * [MaterialState.pressed].
+ /// * [MaterialState.selected].
+ /// * [MaterialState.hovered].
+ ///
+ /// If null, then the value of [activeColor] with alpha [kRadialReactionAlpha]
+ /// and [hoverColor] is used in the pressed and hovered state. If that is also
+ /// null, the value of [SwitchThemeData.overlayColor] is used. If that is
+ /// also null, then the default value is used in the pressed and hovered state.
+ final MaterialStateProperty<Color?>? overlayColor;
+
+ /// {@macro flutter.material.switch.splashRadius}
+ ///
+ /// If null, then the value of [SwitchThemeData.splashRadius] is used. If that
+ /// is also null, then [kRadialReactionRadius] is used.
+ final double? splashRadius;
+
+ /// {@macro flutter.widgets.Focus.focusNode}
+ final FocusNode? focusNode;
+
+ /// {@macro flutter.material.inkwell.onFocusChange}
+ final ValueChanged<bool>? onFocusChange;
+
+ /// {@macro flutter.widgets.Focus.autofocus}
+ final bool autofocus;
+
+ /// {@macro flutter.material.ListTile.tileColor}
+ final Color? tileColor;
+
/// The primary content of the list tile.
///
/// Typically a [Text] widget.
@@ -344,9 +476,6 @@
/// Normally, this property is left to its default value, false.
final bool selected;
- /// {@macro flutter.widgets.Focus.autofocus}
- final bool autofocus;
-
/// If adaptive, creates the switch with [Switch.adaptive].
final _SwitchListTileType _switchListTileType;
@@ -366,12 +495,6 @@
/// {@macro flutter.material.themedata.visualDensity}
final VisualDensity? visualDensity;
- /// {@macro flutter.widgets.Focus.focusNode}
- final FocusNode? focusNode;
-
- /// {@macro flutter.material.inkwell.onFocusChange}
- final ValueChanged<bool>? onFocusChange;
-
/// {@macro flutter.material.ListTile.enableFeedback}
///
/// See also:
@@ -382,6 +505,9 @@
/// The color for the tile's [Material] when a pointer is hovering over it.
final Color? hoverColor;
+ /// {@macro flutter.cupertino.CupertinoSwitch.applyTheme}
+ final bool? applyCupertinoTheme;
+
@override
Widget build(BuildContext context) {
final Widget control;
@@ -393,12 +519,23 @@
activeColor: activeColor,
activeThumbImage: activeThumbImage,
inactiveThumbImage: inactiveThumbImage,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
inactiveThumbColor: inactiveThumbColor,
autofocus: autofocus,
onFocusChange: onFocusChange,
+ onActiveThumbImageError: onActiveThumbImageError,
+ onInactiveThumbImageError: onInactiveThumbImageError,
+ thumbColor: thumbColor,
+ trackColor: trackColor,
+ trackOutlineColor: trackOutlineColor,
+ thumbIcon: thumbIcon,
+ applyCupertinoTheme: applyCupertinoTheme,
+ dragStartBehavior: dragStartBehavior,
+ mouseCursor: mouseCursor,
+ splashRadius: splashRadius,
+ overlayColor: overlayColor,
);
break;
@@ -409,12 +546,22 @@
activeColor: activeColor,
activeThumbImage: activeThumbImage,
inactiveThumbImage: inactiveThumbImage,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ materialTapTargetSize: materialTapTargetSize ?? MaterialTapTargetSize.shrinkWrap,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
inactiveThumbColor: inactiveThumbColor,
autofocus: autofocus,
onFocusChange: onFocusChange,
+ onActiveThumbImageError: onActiveThumbImageError,
+ onInactiveThumbImageError: onInactiveThumbImageError,
+ thumbColor: thumbColor,
+ trackColor: trackColor,
+ trackOutlineColor: trackOutlineColor,
+ thumbIcon: thumbIcon,
+ dragStartBehavior: dragStartBehavior,
+ mouseCursor: mouseCursor,
+ splashRadius: splashRadius,
+ overlayColor: overlayColor,
);
}
diff --git a/framework/lib/src/material/switch_theme.dart b/framework/lib/src/material/switch_theme.dart
index 3c6139b..4d204fe 100644
--- a/framework/lib/src/material/switch_theme.dart
+++ b/framework/lib/src/material/switch_theme.dart
@@ -39,6 +39,7 @@
const SwitchThemeData({
this.thumbColor,
this.trackColor,
+ this.trackOutlineColor,
this.materialTapTargetSize,
this.mouseCursor,
this.overlayColor,
@@ -56,6 +57,11 @@
/// If specified, overrides the default value of [Switch.trackColor].
final MaterialStateProperty<Color?>? trackColor;
+ /// {@macro flutter.material.switch.trackOutlineColor}
+ ///
+ /// If specified, overrides the default value of [Switch.trackOutlineColor].
+ final MaterialStateProperty<Color?>? trackOutlineColor;
+
/// {@macro flutter.material.switch.materialTapTargetSize}
///
/// If specified, overrides the default value of
@@ -87,6 +93,7 @@
SwitchThemeData copyWith({
MaterialStateProperty<Color?>? thumbColor,
MaterialStateProperty<Color?>? trackColor,
+ MaterialStateProperty<Color?>? trackOutlineColor,
MaterialTapTargetSize? materialTapTargetSize,
MaterialStateProperty<MouseCursor?>? mouseCursor,
MaterialStateProperty<Color?>? overlayColor,
@@ -96,6 +103,7 @@
return SwitchThemeData(
thumbColor: thumbColor ?? this.thumbColor,
trackColor: trackColor ?? this.trackColor,
+ trackOutlineColor: trackOutlineColor ?? this.trackOutlineColor,
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
mouseCursor: mouseCursor ?? this.mouseCursor,
overlayColor: overlayColor ?? this.overlayColor,
@@ -111,6 +119,7 @@
return SwitchThemeData(
thumbColor: MaterialStateProperty.lerp<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
trackColor: MaterialStateProperty.lerp<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
+ trackOutlineColor: MaterialStateProperty.lerp<Color?>(a?.trackOutlineColor, b?.trackOutlineColor, t, Color.lerp),
materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
@@ -123,6 +132,7 @@
int get hashCode => Object.hash(
thumbColor,
trackColor,
+ trackOutlineColor,
materialTapTargetSize,
mouseCursor,
overlayColor,
@@ -141,6 +151,7 @@
return other is SwitchThemeData
&& other.thumbColor == thumbColor
&& other.trackColor == trackColor
+ && other.trackOutlineColor == trackOutlineColor
&& other.materialTapTargetSize == materialTapTargetSize
&& other.mouseCursor == mouseCursor
&& other.overlayColor == overlayColor
@@ -153,6 +164,7 @@
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('thumbColor', thumbColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackColor', trackColor, defaultValue: null));
+ properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackOutlineColor', trackOutlineColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
diff --git a/framework/lib/src/material/tab_bar_theme.dart b/framework/lib/src/material/tab_bar_theme.dart
index 5e9f249..3c011d1 100644
--- a/framework/lib/src/material/tab_bar_theme.dart
+++ b/framework/lib/src/material/tab_bar_theme.dart
@@ -55,6 +55,12 @@
final Color? dividerColor;
/// Overrides the default value for [TabBar.labelColor].
+ ///
+ /// If [labelColor] is a [MaterialStateColor], then the effective color will
+ /// depend on the [MaterialState.selected] state, i.e. if the [Tab] is
+ /// selected or not. In case of unselected state, this [MaterialStateColor]'s
+ /// resolved color will be used even if [TabBar.unselectedLabelColor] or
+ /// [unselectedLabelColor] is non-null.
final Color? labelColor;
/// Overrides the default value for [TabBar.labelPadding].
diff --git a/framework/lib/src/material/tabs.dart b/framework/lib/src/material/tabs.dart
index 732009f..ebac40f 100644
--- a/framework/lib/src/material/tabs.dart
+++ b/framework/lib/src/material/tabs.dart
@@ -166,7 +166,7 @@
class _TabStyle extends AnimatedWidget {
const _TabStyle({
required Animation<double> animation,
- required this.selected,
+ required this.isSelected,
required this.labelColor,
required this.unselectedLabelColor,
required this.labelStyle,
@@ -176,11 +176,47 @@
final TextStyle? labelStyle;
final TextStyle? unselectedLabelStyle;
- final bool selected;
+ final bool isSelected;
final Color? labelColor;
final Color? unselectedLabelColor;
final Widget child;
+ MaterialStateColor _resolveWithLabelColor(BuildContext context) {
+ final ThemeData themeData = Theme.of(context);
+ final TabBarTheme tabBarTheme = TabBarTheme.of(context);
+ final TabBarTheme defaults = themeData.useMaterial3 ? _TabsDefaultsM3(context) : _TabsDefaultsM2(context);
+ final Animation<double> animation = listenable as Animation<double>;
+
+ // labelStyle.color (and tabBarTheme.labelStyle.color) is not considered
+ // as it'll be a breaking change without a possible migration plan. for
+ // details: https://github.com/flutter/flutter/pull/109541#issuecomment-1294241417
+ Color selectedColor = labelColor
+ ?? tabBarTheme.labelColor
+ ?? defaults.labelColor!;
+
+ final Color unselectedColor;
+
+ if (selectedColor is MaterialStateColor) {
+ unselectedColor = selectedColor.resolve(const <MaterialState>{});
+ selectedColor = selectedColor.resolve(const <MaterialState>{MaterialState.selected});
+ } else {
+ // unselectedLabelColor and tabBarTheme.unselectedLabelColor are ignored
+ // when labelColor is a MaterialStateColor.
+ unselectedColor = unselectedLabelColor
+ ?? tabBarTheme.unselectedLabelColor
+ ?? (themeData.useMaterial3
+ ? defaults.unselectedLabelColor!
+ : selectedColor.withAlpha(0xB2)); // 70% alpha
+ }
+
+ return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.selected)) {
+ return Color.lerp(selectedColor, unselectedColor, animation.value)!;
+ }
+ return Color.lerp(unselectedColor, selectedColor, animation.value)!;
+ });
+ }
+
@override
Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context);
@@ -188,6 +224,10 @@
final TabBarTheme defaults = themeData.useMaterial3 ? _TabsDefaultsM3(context) : _TabsDefaultsM2(context);
final Animation<double> animation = listenable as Animation<double>;
+ final Set<MaterialState> states = isSelected
+ ? const <MaterialState>{MaterialState.selected}
+ : const <MaterialState>{};
+
// To enable TextStyle.lerp(style1, style2, value), both styles must have
// the same value of inherit. Force that to be inherit=true here.
final TextStyle defaultStyle = (labelStyle
@@ -199,21 +239,10 @@
?? labelStyle
?? defaults.unselectedLabelStyle!
).copyWith(inherit: true);
- final TextStyle textStyle = selected
+ final TextStyle textStyle = isSelected
? TextStyle.lerp(defaultStyle, defaultUnselectedStyle, animation.value)!
: TextStyle.lerp(defaultUnselectedStyle, defaultStyle, animation.value)!;
-
- final Color selectedColor = labelColor
- ?? tabBarTheme.labelColor
- ?? defaults.labelColor!;
- final Color unselectedColor = unselectedLabelColor
- ?? tabBarTheme.unselectedLabelColor
- ?? (themeData.useMaterial3
- ? defaults.unselectedLabelColor!
- : selectedColor.withAlpha(0xB2)); // 70% alpha
- final Color color = selected
- ? Color.lerp(selectedColor, unselectedColor, animation.value)!
- : Color.lerp(unselectedColor, selectedColor, animation.value)!;
+ final Color color = _resolveWithLabelColor(context).resolve(states);
return DefaultTextStyle(
style: textStyle.copyWith(color: color),
@@ -738,7 +767,8 @@
///
/// If [automaticIndicatorColorAdjustment] is true,
/// then the [indicatorColor] will be automatically adjusted to [Colors.white]
- /// when the [indicatorColor] is same as [Material.color] of the [Material] parent widget.
+ /// when the [indicatorColor] is same as [Material.color] of the [Material]
+ /// parent widget.
final bool automaticIndicatorColorAdjustment;
/// Defines how the selected tab indicator's size is computed.
@@ -762,23 +792,50 @@
/// The color of selected tab labels.
///
- /// If [ThemeData.useMaterial3] is false, unselected tab labels are rendered with
- /// the same color with 70% opacity unless [unselectedLabelColor] is non-null.
- ///
- /// If this property is null and [ThemeData.useMaterial3] is true, [ColorScheme.primary]
- /// will be used, otherwise the color of the [ThemeData.primaryTextTheme]'s
+ /// If null, then [TabBarTheme.labelColor] is used. If that is also null and
+ /// [ThemeData.useMaterial3] is true, [ColorScheme.primary] will be used,
+ /// otherwise the color of the [ThemeData.primaryTextTheme]'s
/// [TextTheme.bodyLarge] text color is used.
+ ///
+ /// If [labelColor] (or, if null, [TabBarTheme.labelColor]) is a
+ /// [MaterialStateColor], then the effective tab color will depend on the
+ /// [MaterialState.selected] state, i.e. if the [Tab] is selected or not,
+ /// ignoring [unselectedLabelColor] even if it's non-null.
+ ///
+ /// Note: [labelStyle]'s color and [TabBarTheme.labelStyle]'s color do not
+ /// affect the effective [labelColor].
+ ///
+ /// See also:
+ ///
+ /// * [unselectedLabelColor], for color of unselected tab labels.
final Color? labelColor;
/// The color of unselected tab labels.
///
- /// If this property is null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurfaceVariant]
- /// will be used, otherwise unselected tab labels are rendered with the
- /// [labelColor] with 70% opacity.
+ /// If [labelColor] (or, if null, [TabBarTheme.labelColor]) is a
+ /// [MaterialStateColor], then the unselected tabs are rendered with
+ /// that [MaterialStateColor]'s resolved color for unselected state, even if
+ /// [unselectedLabelColor] is non-null.
+ ///
+ /// If null, then [TabBarTheme.unselectedLabelColor] is used. If that is also
+ /// null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurfaceVariant]
+ /// will be used, otherwise unselected tab labels are rendered with
+ /// [labelColor] at 70% opacity.
+ ///
+ /// Note: [unselectedLabelStyle]'s color and
+ /// [TabBarTheme.unselectedLabelStyle]'s color are ignored in
+ /// [unselectedLabelColor]'s precedence calculation.
+ ///
+ /// See also:
+ ///
+ /// * [labelColor], for color of selected tab labels.
final Color? unselectedLabelColor;
/// The text style of the selected tab labels.
///
+ /// This does not influence color of the tab labels even if [TextStyle.color]
+ /// is non-null. Refer [labelColor] to color selected tab labels instead.
+ ///
/// If [unselectedLabelStyle] is null, then this text style will be used for
/// both selected and unselected label styles.
///
@@ -787,6 +844,18 @@
/// [TextTheme.bodyLarge] definition is used.
final TextStyle? labelStyle;
+ /// The text style of the unselected tab labels.
+ ///
+ /// This does not influence color of the tab labels even if [TextStyle.color]
+ /// is non-null. Refer [unselectedLabelColor] to color unselected tab labels
+ /// instead.
+ ///
+ /// If this property is null and [ThemeData.useMaterial3] is true,
+ /// [TextTheme.titleSmall] will be used, otherwise then the [labelStyle] value
+ /// is used. If [labelStyle] is null, the text style of the
+ /// [ThemeData.primaryTextTheme]'s [TextTheme.bodyLarge] definition is used.
+ final TextStyle? unselectedLabelStyle;
+
/// The padding added to each of the tab labels.
///
/// If there are few tabs with both icon and text and few
@@ -796,14 +865,6 @@
/// If this property is null, then kTabLabelPadding is used.
final EdgeInsetsGeometry? labelPadding;
- /// The text style of the unselected tab labels.
- ///
- /// If this property is null and [ThemeData.useMaterial3] is true, [TextTheme.titleSmall]
- /// will be used, otherwise then the [labelStyle] value is used. If [labelStyle]
- /// is null, the text style of the [ThemeData.primaryTextTheme]'s
- /// [TextTheme.bodyLarge] definition is used.
- final TextStyle? unselectedLabelStyle;
-
/// Defines the ink response focus, hover, and splash colors.
///
/// If non-null, it is resolved against one of [MaterialState.focused],
@@ -1081,7 +1142,7 @@
_updateTabController();
_initIndicatorPainter();
// Adjust scroll position.
- if (_scrollController != null) {
+ if (_scrollController != null && _scrollController!.hasClients) {
final ScrollPosition position = _scrollController!.position;
if (position is _TabBarScrollPosition) {
position.markNeedsPixelsCorrection();
@@ -1209,10 +1270,10 @@
widget.onTap?.call(index);
}
- Widget _buildStyledTab(Widget child, bool selected, Animation<double> animation) {
+ Widget _buildStyledTab(Widget child, bool isSelected, Animation<double> animation) {
return _TabStyle(
animation: animation,
- selected: selected,
+ isSelected: isSelected,
labelColor: widget.labelColor,
unselectedLabelColor: widget.unselectedLabelColor,
labelStyle: widget.labelStyle,
@@ -1368,7 +1429,7 @@
painter: _indicatorPainter,
child: _TabStyle(
animation: kAlwaysDismissedAnimation,
- selected: false,
+ isSelected: false,
labelColor: widget.labelColor,
unselectedLabelColor: widget.unselectedLabelColor,
labelStyle: widget.labelStyle,
diff --git a/framework/lib/src/material/theme_data.dart b/framework/lib/src/material/theme_data.dart
index cdaf4c0..1f85ff4 100644
--- a/framework/lib/src/material/theme_data.dart
+++ b/framework/lib/src/material/theme_data.dart
@@ -389,13 +389,6 @@
)
Color? accentColor,
@Deprecated(
- 'No longer used by the framework, please remove any reference to it. '
- 'For more information, consult the migration guide at '
- 'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
- 'This feature was deprecated after v2.3.0-0.1.pre.',
- )
- Brightness? accentColorBrightness,
- @Deprecated(
'This "fix" is now enabled by default. '
'This feature was deprecated after v2.5.0-1.0.pre.',
)
@@ -486,7 +479,6 @@
primaryColorBrightness = ThemeData.estimateBrightnessForColor(primarySurfaceColor);
canvasColor ??= colorScheme.background;
accentColor ??= colorScheme.secondary;
- accentColorBrightness ??= ThemeData.estimateBrightnessForColor(colorScheme.secondary);
scaffoldBackgroundColor ??= colorScheme.background;
bottomAppBarColor ??= colorScheme.surface;
cardColor ??= colorScheme.surface;
@@ -506,7 +498,6 @@
final bool primaryIsDark = estimatedPrimaryColorBrightness == Brightness.dark;
toggleableActiveColor ??= isDark ? Colors.tealAccent[200]! : (accentColor ?? primarySwatch[600]!);
accentColor ??= isDark ? Colors.tealAccent[200]! : primarySwatch[500]!;
- accentColorBrightness ??= estimateBrightnessForColor(accentColor);
focusColor ??= isDark ? Colors.white.withOpacity(0.12) : Colors.black.withOpacity(0.12);
hoverColor ??= isDark ? Colors.white.withOpacity(0.04) : Colors.black.withOpacity(0.04);
shadowColor ??= Colors.black;
@@ -712,7 +703,6 @@
tooltipTheme: tooltipTheme,
// DEPRECATED (newest deprecations at the bottom)
accentColor: accentColor,
- accentColorBrightness: accentColorBrightness,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
primaryColorBrightness: primaryColorBrightness,
androidOverscrollIndicator: androidOverscrollIndicator,
@@ -831,13 +821,6 @@
)
Color? accentColor,
@Deprecated(
- 'No longer used by the framework, please remove any reference to it. '
- 'For more information, consult the migration guide at '
- 'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
- 'This feature was deprecated after v2.3.0-0.1.pre.',
- )
- Brightness? accentColorBrightness,
- @Deprecated(
'This "fix" is now enabled by default. '
'This feature was deprecated after v2.5.0-1.0.pre.',
)
@@ -883,7 +866,6 @@
}) : // DEPRECATED (newest deprecations at the bottom)
// should not be `required`, use getter pattern to avoid breakages.
_accentColor = accentColor,
- _accentColorBrightness = accentColorBrightness,
_fixTextFieldOutlineLabel = fixTextFieldOutlineLabel,
_primaryColorBrightness = primaryColorBrightness,
_toggleableActiveColor = toggleableActiveColor,
@@ -894,7 +876,6 @@
assert(toggleableActiveColor != null),
// DEPRECATED (newest deprecations at the bottom)
assert(accentColor != null),
- assert(accentColorBrightness != null),
assert(fixTextFieldOutlineLabel != null),
assert(primaryColorBrightness != null),
assert(errorColor != null),
@@ -946,7 +927,6 @@
primaryColorBrightness: ThemeData.estimateBrightnessForColor(primarySurfaceColor),
canvasColor: colorScheme.background,
accentColor: colorScheme.secondary,
- accentColorBrightness: ThemeData.estimateBrightnessForColor(colorScheme.secondary),
scaffoldBackgroundColor: colorScheme.background,
bottomAppBarColor: colorScheme.surface,
cardColor: colorScheme.surface,
@@ -1572,23 +1552,6 @@
Color get accentColor => _accentColor!;
final Color? _accentColor;
- /// Obsolete property that was originally used to determine the color
- /// of text and icons placed on top of the accent color (e.g. the
- /// icons on a floating action button).
- ///
- /// The material library no longer uses this property. The
- /// [floatingActionButtonTheme] can be used to configure
- /// the appearance of [FloatingActionButton]s. The brightness
- /// of any color can be found with [ThemeData.estimateBrightnessForColor].
- @Deprecated(
- 'No longer used by the framework, please remove any reference to it. '
- 'For more information, consult the migration guide at '
- 'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
- 'This feature was deprecated after v2.3.0-0.1.pre.',
- )
- Brightness get accentColorBrightness => _accentColorBrightness!;
- final Brightness? _accentColorBrightness;
-
/// An obsolete flag to allow apps to opt-out of a
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// coordinate of the floating label in a [TextField] [OutlineInputBorder].
@@ -1781,13 +1744,6 @@
)
Color? accentColor,
@Deprecated(
- 'No longer used by the framework, please remove any reference to it. '
- 'For more information, consult the migration guide at '
- 'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
- 'This feature was deprecated after v2.3.0-0.1.pre.',
- )
- Brightness? accentColorBrightness,
- @Deprecated(
'This "fix" is now enabled by default. '
'This feature was deprecated after v2.5.0-1.0.pre.',
)
@@ -1921,7 +1877,6 @@
tooltipTheme: tooltipTheme ?? this.tooltipTheme,
// DEPRECATED (newest deprecations at the bottom)
accentColor: accentColor ?? _accentColor,
- accentColorBrightness: accentColorBrightness ?? _accentColorBrightness,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? _fixTextFieldOutlineLabel,
primaryColorBrightness: primaryColorBrightness ?? _primaryColorBrightness,
androidOverscrollIndicator: androidOverscrollIndicator ?? this.androidOverscrollIndicator,
@@ -2113,7 +2068,6 @@
tooltipTheme: TooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t)!,
// DEPRECATED (newest deprecations at the bottom)
accentColor: Color.lerp(a.accentColor, b.accentColor, t),
- accentColorBrightness: t < 0.5 ? a.accentColorBrightness : b.accentColorBrightness,
fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel,
primaryColorBrightness: t < 0.5 ? a.primaryColorBrightness : b.primaryColorBrightness,
androidOverscrollIndicator:t < 0.5 ? a.androidOverscrollIndicator : b.androidOverscrollIndicator,
@@ -2220,7 +2174,6 @@
other.tooltipTheme == tooltipTheme &&
// DEPRECATED (newest deprecations at the bottom)
other.accentColor == accentColor &&
- other.accentColorBrightness == accentColorBrightness &&
other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel &&
other.primaryColorBrightness == primaryColorBrightness &&
other.androidOverscrollIndicator == androidOverscrollIndicator &&
@@ -2324,7 +2277,6 @@
tooltipTheme,
// DEPRECATED (newest deprecations at the bottom)
accentColor,
- accentColorBrightness,
fixTextFieldOutlineLabel,
primaryColorBrightness,
androidOverscrollIndicator,
@@ -2430,7 +2382,6 @@
properties.add(DiagnosticsProperty<TooltipThemeData>('tooltipTheme', tooltipTheme, level: DiagnosticLevel.debug));
// DEPRECATED (newest deprecations at the bottom)
properties.add(ColorProperty('accentColor', accentColor, defaultValue: defaultData.accentColor, level: DiagnosticLevel.debug));
- properties.add(EnumProperty<Brightness>('accentColorBrightness', accentColorBrightness, defaultValue: defaultData.accentColorBrightness, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<bool>('fixTextFieldOutlineLabel', fixTextFieldOutlineLabel, level: DiagnosticLevel.debug));
properties.add(EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness, defaultValue: defaultData.primaryColorBrightness, level: DiagnosticLevel.debug));
properties.add(EnumProperty<AndroidOverscrollIndicator>('androidOverscrollIndicator', androidOverscrollIndicator, defaultValue: null, level: DiagnosticLevel.debug));
diff --git a/framework/lib/src/painting/shader_warm_up.dart b/framework/lib/src/painting/shader_warm_up.dart
index 8da3379..d7d7df5 100644
--- a/framework/lib/src/painting/shader_warm_up.dart
+++ b/framework/lib/src/painting/shader_warm_up.dart
@@ -69,10 +69,10 @@
///
/// To decide which draw operations to be added to your custom warm up
/// process, consider capturing an skp using `flutter screenshot
- /// --observatory-uri=<uri> --type=skia` and analyzing it with
+ /// --vm-service-uri=<uri> --type=skia` and analyzing it with
/// <https://debugger.skia.org/>. Alternatively, one may run the app with
/// `flutter run --trace-skia` and then examine the raster thread in the
- /// observatory timeline to see which Skia draw operations are commonly used,
+ /// Flutter DevTools timeline to see which Skia draw operations are commonly used,
/// and which shader compilations are causing jank.
@protected
Future<void> warmUpOnCanvas(ui.Canvas canvas);
diff --git a/framework/lib/src/rendering/proxy_box.dart b/framework/lib/src/rendering/proxy_box.dart
index 8f54447..f38f77f 100644
--- a/framework/lib/src/rendering/proxy_box.dart
+++ b/framework/lib/src/rendering/proxy_box.dart
@@ -53,8 +53,10 @@
///
/// Use this mixin in situations where the proxying behavior
/// of [RenderProxyBox] is desired but inheriting from [RenderProxyBox] is
-/// impractical (e.g. because you want to mix in other classes as well).
-// TODO(ianh): Remove this class once https://github.com/dart-lang/sdk/issues/31543 is fixed
+/// impractical (e.g. because you want to inherit from a different class).
+///
+/// If a class already inherits from [RenderProxyBox] and also uses this mixin,
+/// you can safely delete the use of the mixin.
@optionalTypeArgs
mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChildMixin<T> {
@override
@@ -1095,7 +1097,7 @@
///
/// This is a variant of [RenderOpacity] that uses an [Animation<double>] rather
/// than a [double] to control the opacity.
-class RenderAnimatedOpacity extends RenderProxyBox with RenderProxyBoxMixin, RenderAnimatedOpacityMixin<RenderBox> {
+class RenderAnimatedOpacity extends RenderProxyBox with RenderAnimatedOpacityMixin<RenderBox> {
/// Creates a partially transparent render object.
///
/// The [opacity] argument must not be null.
diff --git a/framework/lib/src/services/platform_views.dart b/framework/lib/src/services/platform_views.dart
index 6229559..7825904 100644
--- a/framework/lib/src/services/platform_views.dart
+++ b/framework/lib/src/services/platform_views.dart
@@ -863,7 +863,7 @@
Future<void> setLayoutDirection(TextDirection layoutDirection) async {
assert(
_state != _AndroidViewState.disposed,
- 'trying to set a layout direction for a disposed UIView. View id: $viewId',
+ 'trying to set a layout direction for a disposed Android view. View id: $viewId',
);
if (layoutDirection == _layoutDirection) {
@@ -938,12 +938,13 @@
/// disposed.
@override
Future<void> dispose() async {
- if (_state == _AndroidViewState.creating || _state == _AndroidViewState.created) {
+ final _AndroidViewState state = _state;
+ _state = _AndroidViewState.disposed;
+ _platformViewCreatedCallbacks.clear();
+ PlatformViewsService._instance._focusCallbacks.remove(viewId);
+ if (state == _AndroidViewState.creating || state == _AndroidViewState.created) {
await _sendDisposeMessage();
}
- _platformViewCreatedCallbacks.clear();
- _state = _AndroidViewState.disposed;
- PlatformViewsService._instance._focusCallbacks.remove(viewId);
}
}
diff --git a/framework/lib/src/services/system_chrome.dart b/framework/lib/src/services/system_chrome.dart
index e37f78b..6b0ed70 100644
--- a/framework/lib/src/services/system_chrome.dart
+++ b/framework/lib/src/services/system_chrome.dart
@@ -72,7 +72,7 @@
/// Specifies a system overlay at a particular location.
///
-/// Used by [SystemChrome.setEnabledSystemUIOverlays].
+/// Used by [SystemChrome.setEnabledSystemUIMode].
enum SystemUiOverlay {
/// The status bar provided by the embedder on the top of the application
/// surface, if any.
@@ -400,36 +400,6 @@
);
}
- /// Specifies the set of system overlays to have visible when the application
- /// is running.
- ///
- /// The `overlays` argument is a list of [SystemUiOverlay] enum values
- /// denoting the overlays to show.
- ///
- /// If a particular overlay is unsupported on the platform, enabling or
- /// disabling that overlay will be ignored.
- ///
- /// The settings here can be overridden by the platform when System UI becomes
- /// necessary for functionality.
- ///
- /// For example, on Android, when the keyboard becomes visible, it will enable the
- /// navigation bar and status bar system UI overlays. When the keyboard is closed,
- /// Android will not restore the previous UI visibility settings, and the UI
- /// visibility cannot be changed until 1 second after the keyboard is closed to
- /// prevent malware locking users from navigation buttons.
- ///
- /// To regain "fullscreen" after text entry, the UI overlays should be set again
- /// after a delay of 1 second. This can be achieved through [restoreSystemUIOverlays]
- /// or calling this again. Otherwise, the original UI overlay settings will be
- /// automatically restored only when the application loses and regains focus.
- @Deprecated(
- 'Migrate to setEnabledSystemUIMode. '
- 'This feature was deprecated after v2.3.0-17.0.pre.'
- )
- static Future<void> setEnabledSystemUIOverlays(List<SystemUiOverlay> overlays) async {
- await setEnabledSystemUIMode(SystemUiMode.manual, overlays: overlays);
- }
-
/// Specifies the [SystemUiMode] to have visible when the application
/// is running.
///
@@ -508,7 +478,7 @@
}
/// Restores the system overlays to the last settings provided via
- /// [setEnabledSystemUIOverlays]. May be used when the platform force enables/disables
+ /// [setEnabledSystemUIMode]. May be used when the platform force enables/disables
/// UI elements.
///
/// For example, when the Android keyboard disables hidden status and navigation bars,
@@ -534,10 +504,10 @@
/// system UI styles. For instance, to change the system UI style on a new
/// page, consider calling when pushing/popping a new [PageRoute].
///
- /// However, the [AppBar] widget automatically sets the system overlay style
- /// based on its [AppBar.brightness], so configure that instead of calling
- /// this method directly. Likewise, do the same for [CupertinoNavigationBar]
- /// via [CupertinoNavigationBar.backgroundColor].
+ /// The [AppBar] widget automatically sets the system overlay style based on
+ /// its [AppBar.systemOverlayStyle], so configure that instead of calling this
+ /// method directly. Likewise, do the same for [CupertinoNavigationBar] via
+ /// [CupertinoNavigationBar.backgroundColor].
///
/// If a particular style is not supported on the platform, selecting it will
/// have no effect.
diff --git a/framework/lib/src/services/text_boundary.dart b/framework/lib/src/services/text_boundary.dart
index efbea24..4e2414e 100644
--- a/framework/lib/src/services/text_boundary.dart
+++ b/framework/lib/src/services/text_boundary.dart
@@ -150,17 +150,16 @@
return 0;
}
- final List<int> codeUnits = _text.codeUnits;
int index = position;
- if (index > 1 && codeUnits[index] == 0xA && codeUnits[index - 1] == 0xD) {
+ if (index > 1 && _text.codeUnitAt(index) == 0x0A && _text.codeUnitAt(index - 1) == 0x0D) {
index -= 2;
- } else if (TextLayoutMetrics.isLineTerminator(codeUnits[index])) {
+ } else if (TextLayoutMetrics.isLineTerminator(_text.codeUnitAt(index))) {
index -= 1;
}
while (index > 0) {
- if (TextLayoutMetrics.isLineTerminator(codeUnits[index])) {
+ if (TextLayoutMetrics.isLineTerminator(_text.codeUnitAt(index))) {
return index + 1;
}
index -= 1;
@@ -183,19 +182,18 @@
return 0;
}
- final List<int> codeUnits = _text.codeUnits;
int index = position;
- while (!TextLayoutMetrics.isLineTerminator(codeUnits[index])) {
+ while (!TextLayoutMetrics.isLineTerminator(_text.codeUnitAt(index))) {
index += 1;
- if (index == codeUnits.length) {
+ if (index == _text.length) {
return index;
}
}
- return index < codeUnits.length - 1
- && codeUnits[index] == 0xD
- && codeUnits[index + 1] == 0xA
+ return index < _text.length - 1
+ && _text.codeUnitAt(index) == 0x0D
+ && _text.codeUnitAt(index + 1) == 0x0A
? index + 2
: index + 1;
}
diff --git a/framework/lib/src/services/text_layout_metrics.dart b/framework/lib/src/services/text_layout_metrics.dart
index 6ae774a..ab23137 100644
--- a/framework/lib/src/services/text_layout_metrics.dart
+++ b/framework/lib/src/services/text_layout_metrics.dart
@@ -60,10 +60,10 @@
/// (https://www.unicode.org/standard/reports/tr13/tr13-5.html).
static bool isLineTerminator(int codeUnit) {
switch (codeUnit) {
- case 0xA: // line feed
- case 0xB: // vertical feed
- case 0xC: // form feed
- case 0xD: // carriage return
+ case 0x0A: // line feed
+ case 0x0B: // vertical feed
+ case 0x0C: // form feed
+ case 0x0D: // carriage return
case 0x85: // new line
case 0x2028: // line separator
case 0x2029: // paragraph separator
diff --git a/framework/lib/src/widgets/app.dart b/framework/lib/src/widgets/app.dart
index 1791729..a5f6449 100644
--- a/framework/lib/src/widgets/app.dart
+++ b/framework/lib/src/widgets/app.dart
@@ -1174,7 +1174,7 @@
/// If true, forces the performance overlay to be visible in all instances.
///
- /// Used by the `showPerformanceOverlay` observatory extension.
+ /// Used by the `showPerformanceOverlay` VM service extension.
static bool showPerformanceOverlayOverride = false;
/// If true, forces the widget inspector to be visible.
@@ -1184,12 +1184,12 @@
/// The inspector allows you to select a location on your device or emulator
/// and view what widgets and render objects associated with it. An outline of
/// the selected widget and some summary information is shown on device and
- /// more detailed information is shown in the IDE or Observatory.
+ /// more detailed information is shown in the IDE or DevTools.
static bool debugShowWidgetInspectorOverride = false;
/// If false, prevents the debug banner from being visible.
///
- /// Used by the `debugAllowBanner` observatory extension.
+ /// Used by the `debugAllowBanner` VM service extension.
///
/// This is how `flutter run` turns off the banner when you take a screen shot
/// with "s".
@@ -1623,6 +1623,7 @@
debugLabel: 'Navigator Scope',
autofocus: true,
child: Navigator(
+ clipBehavior: Clip.none,
restorationScopeId: 'nav',
key: _navigator,
initialRoute: _initialRouteName,
diff --git a/framework/lib/src/widgets/debug.dart b/framework/lib/src/widgets/debug.dart
index 6ac04f9..d401f2d 100644
--- a/framework/lib/src/widgets/debug.dart
+++ b/framework/lib/src/widgets/debug.dart
@@ -33,8 +33,8 @@
/// Combined with [debugPrintScheduleBuildForStacks], this lets you watch a
/// widget's dirty/clean lifecycle.
///
-/// To get similar information but showing it on the timeline available from the
-/// Observatory rather than getting it in the console (where it can be
+/// To get similar information but showing it on the timeline available from
+/// Flutter DevTools rather than getting it in the console (where it can be
/// overwhelming), consider [debugProfileBuildsEnabled].
///
/// See also:
diff --git a/framework/lib/src/widgets/default_text_editing_shortcuts.dart b/framework/lib/src/widgets/default_text_editing_shortcuts.dart
index ae75678..edee66f 100644
--- a/framework/lib/src/widgets/default_text_editing_shortcuts.dart
+++ b/framework/lib/src/widgets/default_text_editing_shortcuts.dart
@@ -207,6 +207,9 @@
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): const ExtendSelectionToNextWordBoundaryIntent(forward: false, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): const ExtendSelectionToNextWordBoundaryIntent(forward: true, collapseSelection: false),
+ const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, control: true): const ExtendSelectionToNextParagraphBoundaryIntent(forward: false, collapseSelection: false),
+ const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, control: true): const ExtendSelectionToNextParagraphBoundaryIntent(forward: true, collapseSelection: false),
+
// Page Up / Down: Move selection by page.
const SingleActivator(LogicalKeyboardKey.pageUp): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: false, collapseSelection: true),
const SingleActivator(LogicalKeyboardKey.pageDown): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: true, collapseSelection: true),
diff --git a/framework/lib/src/widgets/editable_text.dart b/framework/lib/src/widgets/editable_text.dart
index 95353e9..02093ae 100644
--- a/framework/lib/src/widgets/editable_text.dart
+++ b/framework/lib/src/widgets/editable_text.dart
@@ -3463,11 +3463,9 @@
);
} else {
_scrollController.jumpTo(targetOffset.offset);
- if (_value.selection.isCollapsed) {
- renderEditable.showOnScreen(
- rect: caretPadding.inflateRect(rectToReveal),
- );
- }
+ renderEditable.showOnScreen(
+ rect: caretPadding.inflateRect(rectToReveal),
+ );
}
});
}
@@ -4424,6 +4422,7 @@
ExtendSelectionByCharacterIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionByCharacterIntent>(this, _characterBoundary, _moveBeyondTextBoundary, ignoreNonCollapsedSelection: false)),
ExtendSelectionByPageIntent: _makeOverridable(CallbackAction<ExtendSelectionByPageIntent>(onInvoke: _extendSelectionByPage)),
ExtendSelectionToNextWordBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToNextWordBoundaryIntent>(this, _nextWordBoundary, _moveBeyondTextBoundary, ignoreNonCollapsedSelection: true)),
+ ExtendSelectionToNextParagraphBoundaryIntent : _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToNextParagraphBoundaryIntent>(this, _paragraphBoundary, _moveBeyondTextBoundary, ignoreNonCollapsedSelection: true)),
ExtendSelectionToLineBreakIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToLineBreakIntent>(this, _linebreak, _moveToTextBoundary, ignoreNonCollapsedSelection: true)),
ExtendSelectionVerticallyToAdjacentLineIntent: _makeOverridable(_verticalSelectionUpdateAction),
ExtendSelectionVerticallyToAdjacentPageIntent: _makeOverridable(_verticalSelectionUpdateAction),
diff --git a/framework/lib/src/widgets/gesture_detector.dart b/framework/lib/src/widgets/gesture_detector.dart
index 42adfac..21c413b 100644
--- a/framework/lib/src/widgets/gesture_detector.dart
+++ b/framework/lib/src/widgets/gesture_detector.dart
@@ -1050,7 +1050,8 @@
..onTertiaryTapDown = onTertiaryTapDown
..onTertiaryTapUp = onTertiaryTapUp
..onTertiaryTapCancel = onTertiaryTapCancel
- ..gestureSettings = gestureSettings;
+ ..gestureSettings = gestureSettings
+ ..supportedDevices = supportedDevices;
},
);
}
@@ -1065,7 +1066,8 @@
..onDoubleTapDown = onDoubleTapDown
..onDoubleTap = onDoubleTap
..onDoubleTapCancel = onDoubleTapCancel
- ..gestureSettings = gestureSettings;
+ ..gestureSettings = gestureSettings
+ ..supportedDevices = supportedDevices;
},
);
}
@@ -1116,7 +1118,8 @@
..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate
..onTertiaryLongPressUp = onTertiaryLongPressUp
..onTertiaryLongPressEnd = onTertiaryLongPressEnd
- ..gestureSettings = gestureSettings;
+ ..gestureSettings = gestureSettings
+ ..supportedDevices = supportedDevices;
},
);
}
@@ -1136,7 +1139,8 @@
..onEnd = onVerticalDragEnd
..onCancel = onVerticalDragCancel
..dragStartBehavior = dragStartBehavior
- ..gestureSettings = gestureSettings;
+ ..gestureSettings = gestureSettings
+ ..supportedDevices = supportedDevices;
},
);
}
@@ -1156,7 +1160,8 @@
..onEnd = onHorizontalDragEnd
..onCancel = onHorizontalDragCancel
..dragStartBehavior = dragStartBehavior
- ..gestureSettings = gestureSettings;
+ ..gestureSettings = gestureSettings
+ ..supportedDevices = supportedDevices;
},
);
}
@@ -1176,7 +1181,8 @@
..onEnd = onPanEnd
..onCancel = onPanCancel
..dragStartBehavior = dragStartBehavior
- ..gestureSettings = gestureSettings;
+ ..gestureSettings = gestureSettings
+ ..supportedDevices = supportedDevices;
},
);
}
@@ -1192,7 +1198,8 @@
..dragStartBehavior = dragStartBehavior
..gestureSettings = gestureSettings
..trackpadScrollCausesScale = trackpadScrollCausesScale
- ..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor;
+ ..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor
+ ..supportedDevices = supportedDevices;
},
);
}
@@ -1209,7 +1216,8 @@
..onPeak = onForcePressPeak
..onUpdate = onForcePressUpdate
..onEnd = onForcePressEnd
- ..gestureSettings = gestureSettings;
+ ..gestureSettings = gestureSettings
+ ..supportedDevices = supportedDevices;
},
);
}
diff --git a/framework/lib/src/widgets/overlay.dart b/framework/lib/src/widgets/overlay.dart
index d3cd0de..f1629c9 100644
--- a/framework/lib/src/widgets/overlay.dart
+++ b/framework/lib/src/widgets/overlay.dart
@@ -702,8 +702,6 @@
addAll(children);
}
- bool _hasVisualOverflow = false;
-
@override
void setupParentData(RenderBox child) {
if (child.parentData is! StackParentData) {
@@ -827,8 +825,6 @@
@override
void performLayout() {
- _hasVisualOverflow = false;
-
if (_onstageChildCount == 0) {
return;
}
@@ -847,7 +843,7 @@
child.layout(nonPositionedConstraints, parentUsesSize: true);
childParentData.offset = _resolvedAlignment!.alongOffset(size - child.size as Offset);
} else {
- _hasVisualOverflow = RenderStack.layoutPositionedChild(child, childParentData, size, _resolvedAlignment!) || _hasVisualOverflow;
+ RenderStack.layoutPositionedChild(child, childParentData, size, _resolvedAlignment!);
}
assert(child.parentData == childParentData);
@@ -889,7 +885,7 @@
@override
void paint(PaintingContext context, Offset offset) {
- if (_hasVisualOverflow && clipBehavior != Clip.none) {
+ if (clipBehavior != Clip.none) {
_clipRectLayer.layer = context.pushClipRect(
needsCompositing,
offset,
@@ -930,7 +926,7 @@
case Clip.hardEdge:
case Clip.antiAlias:
case Clip.antiAliasWithSaveLayer:
- return _hasVisualOverflow ? Offset.zero & size : null;
+ return Offset.zero & size;
}
}
diff --git a/framework/lib/src/widgets/overscroll_indicator.dart b/framework/lib/src/widgets/overscroll_indicator.dart
index eae45a3..272d089 100644
--- a/framework/lib/src/widgets/overscroll_indicator.dart
+++ b/framework/lib/src/widgets/overscroll_indicator.dart
@@ -601,6 +601,15 @@
}
}
+enum _StretchDirection {
+ /// The [trailing] direction indicates that the content will be stretched toward
+ /// the trailing edge.
+ trailing,
+ /// The [leading] direction indicates that the content will be stretched toward
+ /// the leading edge.
+ leading,
+}
+
/// A Material Design visual indication that a scroll view has overscrolled.
///
/// A [StretchingOverscrollIndicator] listens for [ScrollNotification]s in order
@@ -689,6 +698,9 @@
late final _StretchController _stretchController = _StretchController(vsync: this);
ScrollNotification? _lastNotification;
OverscrollNotification? _lastOverscrollNotification;
+
+ double _totalOverscroll = 0.0;
+
bool _accepted = true;
bool _handleScrollNotification(ScrollNotification notification) {
@@ -706,9 +718,11 @@
assert(notification.metrics.axis == widget.axis);
if (_accepted) {
+ _totalOverscroll += notification.overscroll;
+
if (notification.velocity != 0.0) {
assert(notification.dragDetails == null);
- _stretchController.absorbImpact(notification.velocity.abs());
+ _stretchController.absorbImpact(notification.velocity.abs(), _totalOverscroll);
} else {
assert(notification.overscroll != 0.0);
if (notification.dragDetails != null) {
@@ -716,38 +730,40 @@
// which is the furthest distance a single pointer could pull on the
// screen. This is because more than one pointer will multiply the
// amount of overscroll - https://github.com/flutter/flutter/issues/11884
+
final double viewportDimension = notification.metrics.viewportDimension;
- final double distanceForPull =
- (notification.overscroll.abs() / viewportDimension) + _stretchController.pullDistance;
+ final double distanceForPull = _totalOverscroll.abs() / viewportDimension;
final double clampedOverscroll = clampDouble(distanceForPull, 0, 1.0);
- _stretchController.pull(clampedOverscroll);
+ _stretchController.pull(clampedOverscroll, _totalOverscroll);
}
}
}
} else if (notification is ScrollEndNotification || notification is ScrollUpdateNotification) {
+ // Since the overscrolling ended, we reset the total overscroll amount.
+ _totalOverscroll = 0;
_stretchController.scrollEnd();
}
_lastNotification = notification;
return false;
}
- AlignmentGeometry _getAlignmentForAxisDirection(double overscroll) {
+ AlignmentGeometry _getAlignmentForAxisDirection(_StretchDirection stretchDirection) {
// Accounts for reversed scrollables by checking the AxisDirection
switch (widget.axisDirection) {
case AxisDirection.up:
- return overscroll > 0
+ return stretchDirection == _StretchDirection.trailing
? AlignmentDirectional.topCenter
: AlignmentDirectional.bottomCenter;
case AxisDirection.right:
- return overscroll > 0
+ return stretchDirection == _StretchDirection.trailing
? Alignment.centerRight
: Alignment.centerLeft;
case AxisDirection.down:
- return overscroll > 0
+ return stretchDirection == _StretchDirection.trailing
? AlignmentDirectional.bottomCenter
: AlignmentDirectional.topCenter;
case AxisDirection.left:
- return overscroll > 0
+ return stretchDirection == _StretchDirection.trailing
? Alignment.centerLeft
: Alignment.centerRight;
}
@@ -784,7 +800,7 @@
}
final AlignmentGeometry alignment = _getAlignmentForAxisDirection(
- _lastOverscrollNotification?.overscroll ?? 0.0
+ _stretchController.stretchDirection,
);
final double viewportDimension = _lastOverscrollNotification?.metrics.viewportDimension ?? mainAxisSize;
@@ -836,6 +852,9 @@
double get pullDistance => _pullDistance;
double _pullDistance = 0.0;
+ _StretchDirection get stretchDirection => _stretchDirection;
+ _StretchDirection _stretchDirection = _StretchDirection.trailing;
+
// Constants from Android.
static const double _exponentialScalar = math.e / 0.33;
static const double _stretchIntensity = 0.016;
@@ -847,7 +866,7 @@
/// Handle a fling to the edge of the viewport at a particular velocity.
///
/// The velocity must be positive.
- void absorbImpact(double velocity) {
+ void absorbImpact(double velocity, double totalOverscroll) {
assert(velocity >= 0.0);
velocity = clampDouble(velocity, 1, 10000);
_stretchSizeTween.begin = _stretchSize.value;
@@ -855,6 +874,7 @@
_stretchController.duration = Duration(milliseconds: (velocity * 0.02).round());
_stretchController.forward(from: 0.0);
_state = _StretchState.absorb;
+ _stretchDirection = totalOverscroll > 0 ? _StretchDirection.trailing : _StretchDirection.leading;
}
/// Handle a user-driven overscroll.
@@ -862,8 +882,19 @@
/// The `normalizedOverscroll` argument should be the absolute value of the
/// scroll distance in logical pixels, divided by the extent of the viewport
/// in the main axis.
- void pull(double normalizedOverscroll) {
+ void pull(double normalizedOverscroll, double totalOverscroll) {
assert(normalizedOverscroll >= 0.0);
+
+ final _StretchDirection newStretchDirection = totalOverscroll > 0 ? _StretchDirection.trailing : _StretchDirection.leading;
+ if (_stretchDirection != newStretchDirection && _state == _StretchState.recede) {
+ // When the stretch direction changes while we are in the recede state, we need to ignore the change.
+ // If we don't, the stretch will instantly jump to the new direction with the recede animation still playing, which causes
+ // a unwanted visual abnormality (https://github.com/flutter/flutter/pull/116548#issuecomment-1414872567).
+ // By ignoring the directional change until the recede state is finished, we can avoid this.
+ return;
+ }
+
+ _stretchDirection = newStretchDirection;
_pullDistance = normalizedOverscroll;
_stretchSizeTween.begin = _stretchSize.value;
final double linearIntensity =_stretchIntensity * _pullDistance;
diff --git a/framework/lib/src/widgets/platform_view.dart b/framework/lib/src/widgets/platform_view.dart
index a18f7cc..2513191 100644
--- a/framework/lib/src/widgets/platform_view.dart
+++ b/framework/lib/src/widgets/platform_view.dart
@@ -485,7 +485,7 @@
_layoutDirection = newLayoutDirection;
if (widget.viewType != oldWidget.viewType) {
- _controller.dispose();
+ _controller.disposePostFrame();
_createNewAndroidView();
return;
}
@@ -890,7 +890,7 @@
super.didUpdateWidget(oldWidget);
if (widget.viewType != oldWidget.viewType) {
- _controller?.dispose();
+ _controller?.disposePostFrame();
// The _surface has to be recreated as its controller is disposed.
// Setting _surface to null will trigger its creation in build().
_surface = null;
@@ -911,9 +911,11 @@
}
void _onPlatformViewCreated(int id) {
- setState(() {
- _platformViewCreated = true;
- });
+ if (mounted) {
+ setState(() {
+ _platformViewCreated = true;
+ });
+ }
}
void _handleFrameworkFocusChanged(bool isFocused) {
@@ -1209,3 +1211,13 @@
renderObject.onLayout = onLayout;
}
}
+
+extension on PlatformViewController {
+ /// Disposes the controller in a post-frame callback, to allow other widgets to
+ /// remove their listeners before the controller is disposed.
+ void disposePostFrame() {
+ SchedulerBinding.instance.addPostFrameCallback((_) {
+ dispose();
+ });
+ }
+}
diff --git a/framework/lib/src/widgets/scrollable.dart b/framework/lib/src/widgets/scrollable.dart
index 9a3ae7c..57ff081 100644
--- a/framework/lib/src/widgets/scrollable.dart
+++ b/framework/lib/src/widgets/scrollable.dart
@@ -630,7 +630,8 @@
..maxFlingVelocity = _physics?.maxFlingVelocity
..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
..dragStartBehavior = widget.dragStartBehavior
- ..gestureSettings = _mediaQueryGestureSettings;
+ ..gestureSettings = _mediaQueryGestureSettings
+ ..supportedDevices = _configuration.dragDevices;
},
),
};
@@ -651,7 +652,8 @@
..maxFlingVelocity = _physics?.maxFlingVelocity
..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
..dragStartBehavior = widget.dragStartBehavior
- ..gestureSettings = _mediaQueryGestureSettings;
+ ..gestureSettings = _mediaQueryGestureSettings
+ ..supportedDevices = _configuration.dragDevices;
},
),
};
diff --git a/framework/lib/src/widgets/text_editing_intents.dart b/framework/lib/src/widgets/text_editing_intents.dart
index 80b2588..2d7143a 100644
--- a/framework/lib/src/widgets/text_editing_intents.dart
+++ b/framework/lib/src/widgets/text_editing_intents.dart
@@ -223,10 +223,23 @@
/// Extends, or moves the current selection from the current
/// [TextSelection.extent] position to the previous or the next paragraph
+/// boundary.
+class ExtendSelectionToNextParagraphBoundaryIntent extends DirectionalCaretMovementIntent {
+ /// Creates an [ExtendSelectionToNextParagraphBoundaryIntent].
+ const ExtendSelectionToNextParagraphBoundaryIntent({
+ required bool forward,
+ required bool collapseSelection,
+ }) : super(forward, collapseSelection);
+}
+
+/// Extends, or moves the current selection from the current
+/// [TextSelection.extent] position to the previous or the next paragraph
/// boundary depending on the [forward] parameter.
///
-/// This [Intent] collapses the selection when the order of [TextSelection.base]
-/// and [TextSelection.extent] would reverse.
+/// This [Intent] typically has the same effect as an
+/// [ExtendSelectionToNextParagraphBoundaryIntent], except it collapses the selection
+/// when the order of [TextSelection.base] and [TextSelection.extent] would
+/// reverse.
///
/// This is typically only used on MacOS.
class ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent extends DirectionalCaretMovementIntent {
diff --git a/framework/lib/src/widgets/widget_inspector.dart b/framework/lib/src/widgets/widget_inspector.dart
index ee4922e..f8d557f 100644
--- a/framework/lib/src/widgets/widget_inspector.dart
+++ b/framework/lib/src/widgets/widget_inspector.dart
@@ -23,6 +23,7 @@
import 'package:flute/foundation.dart';
import 'package:flute/rendering.dart';
import 'package:flute/scheduler.dart';
+import 'package:meta/meta_meta.dart';
import 'app.dart';
import 'basic.dart';
@@ -702,7 +703,7 @@
/// operation making it easier to avoid memory leaks.
///
/// All methods in this class are appropriate to invoke from debugging tools
-/// using the Observatory service protocol to evaluate Dart expressions of the
+/// using the VM service protocol to evaluate Dart expressions of the
/// form `WidgetInspectorService.instance.methodName(arg1, arg2, ...)`. If you
/// make changes to any instance method of this class you need to verify that
/// the [Flutter IntelliJ Plugin](https://github.com/flutter/flutter-intellij/blob/master/README.md)
@@ -711,7 +712,7 @@
/// All methods returning String values return JSON.
mixin WidgetInspectorService {
/// Ring of cached JSON values to prevent JSON from being garbage
- /// collected before it can be requested over the Observatory protocol.
+ /// collected before it can be requested over the VM service protocol.
final List<String?> _serializeRing = List<String?>.filled(20, null);
int _serializeRingIndex = 0;
@@ -738,7 +739,7 @@
/// when the inspection target changes on device.
InspectorSelectionChangedCallback? selectionChangedCallback;
- /// The Observatory protocol does not keep alive object references so this
+ /// The VM service protocol does not keep alive object references so this
/// class needs to manually manage groups of objects that should be kept
/// alive.
final Map<String, Set<_InspectorReferenceData>> _groups = <String, Set<_InspectorReferenceData>>{};
@@ -1689,7 +1690,7 @@
/// Wrapper around `json.encode` that uses a ring of cached values to prevent
/// the Dart garbage collector from collecting objects between when
- /// the value is returned over the Observatory protocol and when the
+ /// the value is returned over the VM service protocol and when the
/// separate observatory protocol command has to be used to retrieve its full
/// contents.
//
@@ -2455,7 +2456,10 @@
return;
}
final _HasCreationLocation creationLocationSource = widget;
- final _Location location = creationLocationSource._location;
+ final _Location? location = creationLocationSource._location;
+ if (location == null) {
+ return;
+ }
final int id = _toLocationId(location);
_LocationCount entry;
@@ -3279,7 +3283,7 @@
/// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree}
// ignore: unused_element
abstract class _HasCreationLocation {
- _Location get _location;
+ _Location? get _location;
}
/// A tuple with file, line, and column number, for displaying human-readable
@@ -3681,3 +3685,68 @@
);
}
}
+
+@Target(<TargetKind>{TargetKind.method})
+class _WidgetFactory {
+ const _WidgetFactory();
+}
+
+/// Annotation which marks a function as a widget factory for the purpose of
+/// widget creation tracking.
+///
+/// When widget creation tracking is enabled, the framework tracks the source
+/// code location of the constructor call for each widget instance. This
+/// information is used by the DevTools to provide an improved developer
+/// experience. For example, it allows the Flutter inspector to present the
+/// widget tree in a manner similar to how the UI was defined in your source
+/// code.
+///
+/// [Widget] constructors are automatically instrumented to track the source
+/// code location of constructor calls. However, there are cases where
+/// a function acts as a sort of a constructor for a widget and a call to such
+/// a function should be considered as the creation location for the returned
+/// widget instance.
+///
+/// Annotating a function with this annotation marks the function as a widget
+/// factory. The framework will then instrument that function in the same way
+/// as it does for [Widget] constructors.
+///
+/// Note that the function **must not** have optional positional parameters for
+/// tracking to work correctly.
+///
+/// Currently this annotation is only supported on extension methods.
+///
+/// {@tool snippet}
+///
+/// This example shows how to use the [widgetFactory] annotation to mark an
+/// extension method as a widget factory:
+///
+/// ```dart
+/// extension PaddingModifier on Widget {
+/// @widgetFactory
+/// Widget padding(EdgeInsetsGeometry padding) {
+/// return Padding(padding: padding, child: this);
+/// }
+/// }
+/// ```
+///
+/// When using the above extension method, the framework will track the
+/// creation location of the [Padding] widget instance as the source code
+/// location where the `padding` extension method was called:
+///
+/// ```dart
+/// // continuing from previous example...
+/// const Text('Hello World!')
+/// .padding(const EdgeInsets.all(8));
+/// ```
+///
+/// {@end-tool}
+///
+/// See also:
+///
+/// * the documentation for [Track widget creation](https://docs.flutter.dev/development/tools/devtools/inspector#track-widget-creation).
+// The below ignore is needed because the static type of the annotation is used
+// by the CFE kernel transformer that implements the instrumentation to
+// recognize the annotation.
+// ignore: library_private_types_in_public_api
+const _WidgetFactory widgetFactory = _WidgetFactory();