Unverified Commit 321ffa2e authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Move `ListTileTheme` and its tests into separate classes and add...

Move `ListTileTheme` and its tests into separate classes and  add `visualDensity` to the `ListTileTheme` (#100622)
parent ba53b559
...@@ -94,6 +94,7 @@ export 'src/material/input_border.dart'; ...@@ -94,6 +94,7 @@ export 'src/material/input_border.dart';
export 'src/material/input_date_picker_form_field.dart'; export 'src/material/input_date_picker_form_field.dart';
export 'src/material/input_decorator.dart'; export 'src/material/input_decorator.dart';
export 'src/material/list_tile.dart'; export 'src/material/list_tile.dart';
export 'src/material/list_tile_theme.dart';
export 'src/material/material.dart'; export 'src/material/material.dart';
export 'src/material/material_button.dart'; export 'src/material/material_button.dart';
export 'src/material/material_localizations.dart'; export 'src/material/material_localizations.dart';
......
...@@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart'; ...@@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart';
import 'checkbox.dart'; import 'checkbox.dart';
import 'list_tile.dart'; import 'list_tile.dart';
import 'list_tile_theme.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
......
...@@ -9,6 +9,7 @@ import 'colors.dart'; ...@@ -9,6 +9,7 @@ import 'colors.dart';
import 'debug.dart'; import 'debug.dart';
import 'drawer_theme.dart'; import 'drawer_theme.dart';
import 'list_tile.dart'; import 'list_tile.dart';
import 'list_tile_theme.dart';
import 'material.dart'; import 'material.dart';
import 'material_localizations.dart'; import 'material_localizations.dart';
import 'theme.dart'; import 'theme.dart';
......
...@@ -9,6 +9,7 @@ import 'colors.dart'; ...@@ -9,6 +9,7 @@ import 'colors.dart';
import 'expansion_tile_theme.dart'; import 'expansion_tile_theme.dart';
import 'icons.dart'; import 'icons.dart';
import 'list_tile.dart'; import 'list_tile.dart';
import 'list_tile_theme.dart';
import 'theme.dart'; import 'theme.dart';
const Duration _kExpand = Duration(milliseconds: 200); const Duration _kExpand = Duration(milliseconds: 200);
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -15,6 +14,7 @@ import 'debug.dart'; ...@@ -15,6 +14,7 @@ import 'debug.dart';
import 'divider.dart'; import 'divider.dart';
import 'ink_decoration.dart'; import 'ink_decoration.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'list_tile_theme.dart';
import 'material_state.dart'; import 'material_state.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
...@@ -57,480 +57,6 @@ enum ListTileControlAffinity { ...@@ -57,480 +57,6 @@ enum ListTileControlAffinity {
platform, platform,
} }
/// Used with [ListTileTheme] to define default property values for
/// descendant [ListTile] widgets, as well as classes that build
/// [ListTile]s, like [CheckboxListTile], [RadioListTile], and
/// [SwitchListTile].
///
/// Descendant widgets obtain the current [ListTileThemeData] object
/// using `ListTileTheme.of(context)`. Instances of
/// [ListTileThemeData] can be customized with
/// [ListTileThemeData.copyWith].
///
/// A [ListTileThemeData] is often specified as part of the
/// overall [Theme] with [ThemeData.listTileTheme].
///
/// All [ListTileThemeData] properties are `null` by default.
/// When a theme property is null, the [ListTile] will provide its own
/// default based on the overall [Theme]'s textTheme and
/// colorScheme. See the individual [ListTile] properties for details.
///
/// The [Drawer] widget specifies a list tile theme for its children that
/// defines [style] to be [ListTileStyle.drawer].
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class ListTileThemeData with Diagnosticable {
/// Creates a [ListTileThemeData].
const ListTileThemeData ({
this.dense,
this.shape,
this.style,
this.selectedColor,
this.iconColor,
this.textColor,
this.contentPadding,
this.tileColor,
this.selectedTileColor,
this.horizontalTitleGap,
this.minVerticalPadding,
this.minLeadingWidth,
this.enableFeedback,
this.mouseCursor,
});
/// Overrides the default value of [ListTile.dense].
final bool? dense;
/// Overrides the default value of [ListTile.shape].
final ShapeBorder? shape;
/// Overrides the default value of [ListTile.style].
final ListTileStyle? style;
/// Overrides the default value of [ListTile.selectedColor].
final Color? selectedColor;
/// Overrides the default value of [ListTile.iconColor].
final Color? iconColor;
/// Overrides the default value of [ListTile.textColor].
final Color? textColor;
/// Overrides the default value of [ListTile.contentPadding].
final EdgeInsetsGeometry? contentPadding;
/// Overrides the default value of [ListTile.tileColor].
final Color? tileColor;
/// Overrides the default value of [ListTile.selectedTileColor].
final Color? selectedTileColor;
/// Overrides the default value of [ListTile.horizontalTitleGap].
final double? horizontalTitleGap;
/// Overrides the default value of [ListTile.minVerticalPadding].
final double? minVerticalPadding;
/// Overrides the default value of [ListTile.minLeadingWidth].
final double? minLeadingWidth;
/// Overrides the default value of [ListTile.enableFeedback].
final bool? enableFeedback;
/// If specified, overrides the default value of [ListTile.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
ListTileThemeData copyWith({
bool? dense,
ShapeBorder? shape,
ListTileStyle? style,
Color? selectedColor,
Color? iconColor,
Color? textColor,
EdgeInsetsGeometry? contentPadding,
Color? tileColor,
Color? selectedTileColor,
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
bool? enableFeedback,
MaterialStateProperty<MouseCursor?>? mouseCursor,
}) {
return ListTileThemeData(
dense: dense ?? this.dense,
shape: shape ?? this.shape,
style: style ?? this.style,
selectedColor: selectedColor ?? this.selectedColor,
iconColor: iconColor ?? this.iconColor,
textColor: textColor ?? this.textColor,
contentPadding: contentPadding ?? this.contentPadding,
tileColor: tileColor ?? this.tileColor,
selectedTileColor: selectedTileColor ?? this.selectedTileColor,
horizontalTitleGap: horizontalTitleGap ?? this.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? this.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? this.minLeadingWidth,
enableFeedback: enableFeedback ?? this.enableFeedback,
mouseCursor: mouseCursor ?? this.mouseCursor,
);
}
/// Linearly interpolate between ListTileThemeData objects.
static ListTileThemeData? lerp(ListTileThemeData? a, ListTileThemeData? b, double t) {
assert (t != null);
if (a == null && b == null)
return null;
return ListTileThemeData(
dense: t < 0.5 ? a?.dense : b?.dense,
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
style: t < 0.5 ? a?.style : b?.style,
selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
textColor: Color.lerp(a?.textColor, b?.textColor, t),
contentPadding: EdgeInsetsGeometry.lerp(a?.contentPadding, b?.contentPadding, t),
tileColor: Color.lerp(a?.tileColor, b?.tileColor, t),
selectedTileColor: Color.lerp(a?.selectedTileColor, b?.selectedTileColor, t),
horizontalTitleGap: lerpDouble(a?.horizontalTitleGap, b?.horizontalTitleGap, t),
minVerticalPadding: lerpDouble(a?.minVerticalPadding, b?.minVerticalPadding, t),
minLeadingWidth: lerpDouble(a?.minLeadingWidth, b?.minLeadingWidth, t),
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
);
}
@override
int get hashCode => Object.hash(
dense,
shape,
style,
selectedColor,
iconColor,
textColor,
contentPadding,
tileColor,
selectedTileColor,
horizontalTitleGap,
minVerticalPadding,
minLeadingWidth,
enableFeedback,
mouseCursor,
);
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is ListTileThemeData
&& other.dense == dense
&& other.shape == shape
&& other.style == style
&& other.selectedColor == selectedColor
&& other.iconColor == iconColor
&& other.textColor == textColor
&& other.contentPadding == contentPadding
&& other.tileColor == tileColor
&& other.selectedTileColor == selectedTileColor
&& other.horizontalTitleGap == horizontalTitleGap
&& other.minVerticalPadding == minVerticalPadding
&& other.minLeadingWidth == minLeadingWidth
&& other.enableFeedback == enableFeedback
&& other.mouseCursor == mouseCursor;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('dense', dense, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(EnumProperty<ListTileStyle>('style', style, defaultValue: null));
properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
properties.add(ColorProperty('textColor', textColor, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: null));
properties.add(ColorProperty('tileColor', tileColor, defaultValue: null));
properties.add(ColorProperty('selectedTileColor', selectedTileColor, defaultValue: null));
properties.add(DoubleProperty('horizontalTitleGap', horizontalTitleGap, defaultValue: null));
properties.add(DoubleProperty('minVerticalPadding', minVerticalPadding, defaultValue: null));
properties.add(DoubleProperty('minLeadingWidth', minLeadingWidth, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
}
}
/// An inherited widget that defines color and style parameters for [ListTile]s
/// in this widget's subtree.
///
/// Values specified here are used for [ListTile] properties that are not given
/// an explicit non-null value.
///
/// The [Drawer] widget specifies a tile theme for its children which sets
/// [style] to [ListTileStyle.drawer].
class ListTileTheme extends InheritedTheme {
/// Creates a list tile theme that defines the color and style parameters for
/// descendant [ListTile]s.
///
/// Only the [data] parameter should be used. The other parameters are
/// redundant (are now obsolete) and will be deprecated in a future update.
const ListTileTheme({
Key? key,
ListTileThemeData? data,
bool? dense,
ShapeBorder? shape,
ListTileStyle? style,
Color? selectedColor,
Color? iconColor,
Color? textColor,
EdgeInsetsGeometry? contentPadding,
Color? tileColor,
Color? selectedTileColor,
bool? enableFeedback,
MaterialStateProperty<MouseCursor?>? mouseCursor,
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
required Widget child,
}) : assert(
data == null ||
(shape ??
selectedColor ??
iconColor ??
textColor ??
contentPadding ??
tileColor ??
selectedTileColor ??
enableFeedback ??
mouseCursor ??
horizontalTitleGap ??
minVerticalPadding ??
minLeadingWidth) == null),
_data = data,
_dense = dense,
_shape = shape,
_style = style,
_selectedColor = selectedColor,
_iconColor = iconColor,
_textColor = textColor,
_contentPadding = contentPadding,
_tileColor = tileColor,
_selectedTileColor = selectedTileColor,
_enableFeedback = enableFeedback,
_mouseCursor = mouseCursor,
_horizontalTitleGap = horizontalTitleGap,
_minVerticalPadding = minVerticalPadding,
_minLeadingWidth = minLeadingWidth,
super(key: key, child: child);
final ListTileThemeData? _data;
final bool? _dense;
final ShapeBorder? _shape;
final ListTileStyle? _style;
final Color? _selectedColor;
final Color? _iconColor;
final Color? _textColor;
final EdgeInsetsGeometry? _contentPadding;
final Color? _tileColor;
final Color? _selectedTileColor;
final double? _horizontalTitleGap;
final double? _minVerticalPadding;
final double? _minLeadingWidth;
final bool? _enableFeedback;
final MaterialStateProperty<MouseCursor?>? _mouseCursor;
/// The configuration of this theme.
ListTileThemeData get data {
return _data ?? ListTileThemeData(
dense: _dense,
shape: _shape,
style: _style,
selectedColor: _selectedColor,
iconColor: _iconColor,
textColor: _textColor,
contentPadding: _contentPadding,
tileColor: _tileColor,
selectedTileColor: _selectedTileColor,
enableFeedback: _enableFeedback,
mouseCursor: _mouseCursor,
horizontalTitleGap: _horizontalTitleGap,
minVerticalPadding: _minVerticalPadding,
minLeadingWidth: _minLeadingWidth,
);
}
/// Overrides the default value of [ListTile.dense].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.dense] property instead.
bool? get dense => _data != null ? _data!.dense : _dense;
/// Overrides the default value of [ListTile.shape].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.shape] property instead.
ShapeBorder? get shape => _data != null ? _data!.shape : _shape;
/// Overrides the default value of [ListTile.style].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.style] property instead.
ListTileStyle? get style => _data != null ? _data!.style : _style;
/// Overrides the default value of [ListTile.selectedColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.selectedColor] property instead.
Color? get selectedColor => _data != null ? _data!.selectedColor : _selectedColor;
/// Overrides the default value of [ListTile.iconColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.iconColor] property instead.
Color? get iconColor => _data != null ? _data!.iconColor : _iconColor;
/// Overrides the default value of [ListTile.textColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.textColor] property instead.
Color? get textColor => _data != null ? _data!.textColor : _textColor;
/// Overrides the default value of [ListTile.contentPadding].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.contentPadding] property instead.
EdgeInsetsGeometry? get contentPadding => _data != null ? _data!.contentPadding : _contentPadding;
/// Overrides the default value of [ListTile.tileColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.tileColor] property instead.
Color? get tileColor => _data != null ? _data!.tileColor : _tileColor;
/// Overrides the default value of [ListTile.selectedTileColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.selectedTileColor] property instead.
Color? get selectedTileColor => _data != null ? _data!.selectedTileColor : _selectedTileColor;
/// Overrides the default value of [ListTile.horizontalTitleGap].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.horizontalTitleGap] property instead.
double? get horizontalTitleGap => _data != null ? _data!.horizontalTitleGap : _horizontalTitleGap;
/// Overrides the default value of [ListTile.minVerticalPadding].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.minVerticalPadding] property instead.
double? get minVerticalPadding => _data != null ? _data!.minVerticalPadding : _minVerticalPadding;
/// Overrides the default value of [ListTile.minLeadingWidth].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.minLeadingWidth] property instead.
double? get minLeadingWidth => _data != null ? _data!.minLeadingWidth : _minLeadingWidth;
/// Overrides the default value of [ListTile.enableFeedback].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.enableFeedback] property instead.
bool? get enableFeedback => _data != null ? _data!.enableFeedback : _enableFeedback;
/// The [data] property of the closest instance of this class that
/// encloses the given context.
///
/// If there is no enclosing [ListTileTheme] widget, then
/// [ThemeData.listTileTheme] is used (see [Theme.of]).
///
/// Typical usage is as follows:
///
/// ```dart
/// ListTileThemeData theme = ListTileTheme.of(context);
/// ```
static ListTileThemeData of(BuildContext context) {
final ListTileTheme? result = context.dependOnInheritedWidgetOfExactType<ListTileTheme>();
return result?.data ?? Theme.of(context).listTileTheme;
}
/// Creates a list tile theme that controls the color and style parameters for
/// [ListTile]s, and merges in the current list tile theme, if any.
///
/// The [child] argument must not be null.
static Widget merge({
Key? key,
bool? dense,
ShapeBorder? shape,
ListTileStyle? style,
Color? selectedColor,
Color? iconColor,
Color? textColor,
EdgeInsetsGeometry? contentPadding,
Color? tileColor,
Color? selectedTileColor,
bool? enableFeedback,
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
required Widget child,
}) {
assert(child != null);
return Builder(
builder: (BuildContext context) {
final ListTileThemeData parent = ListTileTheme.of(context);
return ListTileTheme(
key: key,
data: ListTileThemeData(
dense: dense ?? parent.dense,
shape: shape ?? parent.shape,
style: style ?? parent.style,
selectedColor: selectedColor ?? parent.selectedColor,
iconColor: iconColor ?? parent.iconColor,
textColor: textColor ?? parent.textColor,
contentPadding: contentPadding ?? parent.contentPadding,
tileColor: tileColor ?? parent.tileColor,
selectedTileColor: selectedTileColor ?? parent.selectedTileColor,
enableFeedback: enableFeedback ?? parent.enableFeedback,
horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth,
),
child: child,
);
},
);
}
@override
Widget wrap(BuildContext context, Widget child) {
return ListTileTheme(
data: ListTileThemeData(
dense: dense,
shape: shape,
style: style,
selectedColor: selectedColor,
iconColor: iconColor,
textColor: textColor,
contentPadding: contentPadding,
tileColor: tileColor,
selectedTileColor: selectedTileColor,
enableFeedback: enableFeedback,
horizontalTitleGap: horizontalTitleGap,
minVerticalPadding: minVerticalPadding,
minLeadingWidth: minLeadingWidth,
),
child: child,
);
}
@override
bool updateShouldNotify(ListTileTheme oldWidget) => data != oldWidget.data;
}
/// A single fixed-height row that typically contains some text as well as /// A single fixed-height row that typically contains some text as well as
/// a leading or trailing icon. /// a leading or trailing icon.
/// ///
...@@ -1262,7 +788,7 @@ class ListTile extends StatelessWidget { ...@@ -1262,7 +788,7 @@ class ListTile extends StatelessWidget {
subtitle: subtitleText, subtitle: subtitleText,
trailing: trailingIcon, trailing: trailingIcon,
isDense: _isDenseLayout(theme, tileTheme), isDense: _isDenseLayout(theme, tileTheme),
visualDensity: visualDensity ?? theme.visualDensity, visualDensity: visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity,
isThreeLine: isThreeLine, isThreeLine: isThreeLine,
textDirection: textDirection, textDirection: textDirection,
titleBaselineType: titleStyle.textBaseline!, titleBaselineType: titleStyle.textBaseline!,
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'list_tile.dart';
import 'material_state.dart';
import 'theme.dart';
import 'theme_data.dart';
/// Used with [ListTileTheme] to define default property values for
/// descendant [ListTile] widgets, as well as classes that build
/// [ListTile]s, like [CheckboxListTile], [RadioListTile], and
/// [SwitchListTile].
///
/// Descendant widgets obtain the current [ListTileThemeData] object
/// using `ListTileTheme.of(context)`. Instances of
/// [ListTileThemeData] can be customized with
/// [ListTileThemeData.copyWith].
///
/// A [ListTileThemeData] is often specified as part of the
/// overall [Theme] with [ThemeData.listTileTheme].
///
/// All [ListTileThemeData] properties are `null` by default.
/// When a theme property is null, the [ListTile] will provide its own
/// default based on the overall [Theme]'s textTheme and
/// colorScheme. See the individual [ListTile] properties for details.
///
/// The [Drawer] widget specifies a list tile theme for its children that
/// defines [style] to be [ListTileStyle.drawer].
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class ListTileThemeData with Diagnosticable {
/// Creates a [ListTileThemeData].
const ListTileThemeData ({
this.dense,
this.shape,
this.style,
this.selectedColor,
this.iconColor,
this.textColor,
this.contentPadding,
this.tileColor,
this.selectedTileColor,
this.horizontalTitleGap,
this.minVerticalPadding,
this.minLeadingWidth,
this.enableFeedback,
this.mouseCursor,
this.visualDensity,
});
/// Overrides the default value of [ListTile.dense].
final bool? dense;
/// Overrides the default value of [ListTile.shape].
final ShapeBorder? shape;
/// Overrides the default value of [ListTile.style].
final ListTileStyle? style;
/// Overrides the default value of [ListTile.selectedColor].
final Color? selectedColor;
/// Overrides the default value of [ListTile.iconColor].
final Color? iconColor;
/// Overrides the default value of [ListTile.textColor].
final Color? textColor;
/// Overrides the default value of [ListTile.contentPadding].
final EdgeInsetsGeometry? contentPadding;
/// Overrides the default value of [ListTile.tileColor].
final Color? tileColor;
/// Overrides the default value of [ListTile.selectedTileColor].
final Color? selectedTileColor;
/// Overrides the default value of [ListTile.horizontalTitleGap].
final double? horizontalTitleGap;
/// Overrides the default value of [ListTile.minVerticalPadding].
final double? minVerticalPadding;
/// Overrides the default value of [ListTile.minLeadingWidth].
final double? minLeadingWidth;
/// Overrides the default value of [ListTile.enableFeedback].
final bool? enableFeedback;
/// If specified, overrides the default value of [ListTile.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// If specified, overrides the default value of [ListTile.visualDensity].
final VisualDensity? visualDensity;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
ListTileThemeData copyWith({
bool? dense,
ShapeBorder? shape,
ListTileStyle? style,
Color? selectedColor,
Color? iconColor,
Color? textColor,
EdgeInsetsGeometry? contentPadding,
Color? tileColor,
Color? selectedTileColor,
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
bool? enableFeedback,
MaterialStateProperty<MouseCursor?>? mouseCursor,
bool? isThreeLine,
VisualDensity? visualDensity,
}) {
return ListTileThemeData(
dense: dense ?? this.dense,
shape: shape ?? this.shape,
style: style ?? this.style,
selectedColor: selectedColor ?? this.selectedColor,
iconColor: iconColor ?? this.iconColor,
textColor: textColor ?? this.textColor,
contentPadding: contentPadding ?? this.contentPadding,
tileColor: tileColor ?? this.tileColor,
selectedTileColor: selectedTileColor ?? this.selectedTileColor,
horizontalTitleGap: horizontalTitleGap ?? this.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? this.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? this.minLeadingWidth,
enableFeedback: enableFeedback ?? this.enableFeedback,
mouseCursor: mouseCursor ?? this.mouseCursor,
visualDensity: visualDensity ?? this.visualDensity,
);
}
/// Linearly interpolate between ListTileThemeData objects.
static ListTileThemeData? lerp(ListTileThemeData? a, ListTileThemeData? b, double t) {
assert (t != null);
if (a == null && b == null)
return null;
return ListTileThemeData(
dense: t < 0.5 ? a?.dense : b?.dense,
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
style: t < 0.5 ? a?.style : b?.style,
selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
textColor: Color.lerp(a?.textColor, b?.textColor, t),
contentPadding: EdgeInsetsGeometry.lerp(a?.contentPadding, b?.contentPadding, t),
tileColor: Color.lerp(a?.tileColor, b?.tileColor, t),
selectedTileColor: Color.lerp(a?.selectedTileColor, b?.selectedTileColor, t),
horizontalTitleGap: lerpDouble(a?.horizontalTitleGap, b?.horizontalTitleGap, t),
minVerticalPadding: lerpDouble(a?.minVerticalPadding, b?.minVerticalPadding, t),
minLeadingWidth: lerpDouble(a?.minLeadingWidth, b?.minLeadingWidth, t),
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
);
}
@override
int get hashCode => Object.hash(
dense,
shape,
style,
selectedColor,
iconColor,
textColor,
contentPadding,
tileColor,
selectedTileColor,
horizontalTitleGap,
minVerticalPadding,
minLeadingWidth,
enableFeedback,
mouseCursor,
visualDensity,
);
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is ListTileThemeData
&& other.dense == dense
&& other.shape == shape
&& other.style == style
&& other.selectedColor == selectedColor
&& other.iconColor == iconColor
&& other.textColor == textColor
&& other.contentPadding == contentPadding
&& other.tileColor == tileColor
&& other.selectedTileColor == selectedTileColor
&& other.horizontalTitleGap == horizontalTitleGap
&& other.minVerticalPadding == minVerticalPadding
&& other.minLeadingWidth == minLeadingWidth
&& other.enableFeedback == enableFeedback
&& other.mouseCursor == mouseCursor
&& other.visualDensity == visualDensity;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('dense', dense, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(EnumProperty<ListTileStyle>('style', style, defaultValue: null));
properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
properties.add(ColorProperty('textColor', textColor, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: null));
properties.add(ColorProperty('tileColor', tileColor, defaultValue: null));
properties.add(ColorProperty('selectedTileColor', selectedTileColor, defaultValue: null));
properties.add(DoubleProperty('horizontalTitleGap', horizontalTitleGap, defaultValue: null));
properties.add(DoubleProperty('minVerticalPadding', minVerticalPadding, defaultValue: null));
properties.add(DoubleProperty('minLeadingWidth', minLeadingWidth, defaultValue: null));
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));
}
}
/// An inherited widget that defines color and style parameters for [ListTile]s
/// in this widget's subtree.
///
/// Values specified here are used for [ListTile] properties that are not given
/// an explicit non-null value.
///
/// The [Drawer] widget specifies a tile theme for its children which sets
/// [style] to [ListTileStyle.drawer].
class ListTileTheme extends InheritedTheme {
/// Creates a list tile theme that defines the color and style parameters for
/// descendant [ListTile]s.
///
/// Only the [data] parameter should be used. The other parameters are
/// redundant (are now obsolete) and will be deprecated in a future update.
const ListTileTheme({
Key? key,
ListTileThemeData? data,
bool? dense,
ShapeBorder? shape,
ListTileStyle? style,
Color? selectedColor,
Color? iconColor,
Color? textColor,
EdgeInsetsGeometry? contentPadding,
Color? tileColor,
Color? selectedTileColor,
bool? enableFeedback,
MaterialStateProperty<MouseCursor?>? mouseCursor,
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
required Widget child,
}) : assert(
data == null ||
(shape ??
selectedColor ??
iconColor ??
textColor ??
contentPadding ??
tileColor ??
selectedTileColor ??
enableFeedback ??
mouseCursor ??
horizontalTitleGap ??
minVerticalPadding ??
minLeadingWidth) == null),
_data = data,
_dense = dense,
_shape = shape,
_style = style,
_selectedColor = selectedColor,
_iconColor = iconColor,
_textColor = textColor,
_contentPadding = contentPadding,
_tileColor = tileColor,
_selectedTileColor = selectedTileColor,
_enableFeedback = enableFeedback,
_mouseCursor = mouseCursor,
_horizontalTitleGap = horizontalTitleGap,
_minVerticalPadding = minVerticalPadding,
_minLeadingWidth = minLeadingWidth,
super(key: key, child: child);
final ListTileThemeData? _data;
final bool? _dense;
final ShapeBorder? _shape;
final ListTileStyle? _style;
final Color? _selectedColor;
final Color? _iconColor;
final Color? _textColor;
final EdgeInsetsGeometry? _contentPadding;
final Color? _tileColor;
final Color? _selectedTileColor;
final double? _horizontalTitleGap;
final double? _minVerticalPadding;
final double? _minLeadingWidth;
final bool? _enableFeedback;
final MaterialStateProperty<MouseCursor?>? _mouseCursor;
/// The configuration of this theme.
ListTileThemeData get data {
return _data ?? ListTileThemeData(
dense: _dense,
shape: _shape,
style: _style,
selectedColor: _selectedColor,
iconColor: _iconColor,
textColor: _textColor,
contentPadding: _contentPadding,
tileColor: _tileColor,
selectedTileColor: _selectedTileColor,
enableFeedback: _enableFeedback,
mouseCursor: _mouseCursor,
horizontalTitleGap: _horizontalTitleGap,
minVerticalPadding: _minVerticalPadding,
minLeadingWidth: _minLeadingWidth,
);
}
/// Overrides the default value of [ListTile.dense].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.dense] property instead.
bool? get dense => _data != null ? _data!.dense : _dense;
/// Overrides the default value of [ListTile.shape].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.shape] property instead.
ShapeBorder? get shape => _data != null ? _data!.shape : _shape;
/// Overrides the default value of [ListTile.style].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.style] property instead.
ListTileStyle? get style => _data != null ? _data!.style : _style;
/// Overrides the default value of [ListTile.selectedColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.selectedColor] property instead.
Color? get selectedColor => _data != null ? _data!.selectedColor : _selectedColor;
/// Overrides the default value of [ListTile.iconColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.iconColor] property instead.
Color? get iconColor => _data != null ? _data!.iconColor : _iconColor;
/// Overrides the default value of [ListTile.textColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.textColor] property instead.
Color? get textColor => _data != null ? _data!.textColor : _textColor;
/// Overrides the default value of [ListTile.contentPadding].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.contentPadding] property instead.
EdgeInsetsGeometry? get contentPadding => _data != null ? _data!.contentPadding : _contentPadding;
/// Overrides the default value of [ListTile.tileColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.tileColor] property instead.
Color? get tileColor => _data != null ? _data!.tileColor : _tileColor;
/// Overrides the default value of [ListTile.selectedTileColor].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.selectedTileColor] property instead.
Color? get selectedTileColor => _data != null ? _data!.selectedTileColor : _selectedTileColor;
/// Overrides the default value of [ListTile.horizontalTitleGap].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.horizontalTitleGap] property instead.
double? get horizontalTitleGap => _data != null ? _data!.horizontalTitleGap : _horizontalTitleGap;
/// Overrides the default value of [ListTile.minVerticalPadding].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.minVerticalPadding] property instead.
double? get minVerticalPadding => _data != null ? _data!.minVerticalPadding : _minVerticalPadding;
/// Overrides the default value of [ListTile.minLeadingWidth].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.minLeadingWidth] property instead.
double? get minLeadingWidth => _data != null ? _data!.minLeadingWidth : _minLeadingWidth;
/// Overrides the default value of [ListTile.enableFeedback].
///
/// This property is obsolete: please use the [data]
/// [ListTileThemeData.enableFeedback] property instead.
bool? get enableFeedback => _data != null ? _data!.enableFeedback : _enableFeedback;
/// The [data] property of the closest instance of this class that
/// encloses the given context.
///
/// If there is no enclosing [ListTileTheme] widget, then
/// [ThemeData.listTileTheme] is used (see [Theme.of]).
///
/// Typical usage is as follows:
///
/// ```dart
/// ListTileThemeData theme = ListTileTheme.of(context);
/// ```
static ListTileThemeData of(BuildContext context) {
final ListTileTheme? result = context.dependOnInheritedWidgetOfExactType<ListTileTheme>();
return result?.data ?? Theme.of(context).listTileTheme;
}
/// Creates a list tile theme that controls the color and style parameters for
/// [ListTile]s, and merges in the current list tile theme, if any.
///
/// The [child] argument must not be null.
static Widget merge({
Key? key,
bool? dense,
ShapeBorder? shape,
ListTileStyle? style,
Color? selectedColor,
Color? iconColor,
Color? textColor,
EdgeInsetsGeometry? contentPadding,
Color? tileColor,
Color? selectedTileColor,
bool? enableFeedback,
double? horizontalTitleGap,
double? minVerticalPadding,
double? minLeadingWidth,
required Widget child,
}) {
assert(child != null);
return Builder(
builder: (BuildContext context) {
final ListTileThemeData parent = ListTileTheme.of(context);
return ListTileTheme(
key: key,
data: ListTileThemeData(
dense: dense ?? parent.dense,
shape: shape ?? parent.shape,
style: style ?? parent.style,
selectedColor: selectedColor ?? parent.selectedColor,
iconColor: iconColor ?? parent.iconColor,
textColor: textColor ?? parent.textColor,
contentPadding: contentPadding ?? parent.contentPadding,
tileColor: tileColor ?? parent.tileColor,
selectedTileColor: selectedTileColor ?? parent.selectedTileColor,
enableFeedback: enableFeedback ?? parent.enableFeedback,
horizontalTitleGap: horizontalTitleGap ?? parent.horizontalTitleGap,
minVerticalPadding: minVerticalPadding ?? parent.minVerticalPadding,
minLeadingWidth: minLeadingWidth ?? parent.minLeadingWidth,
),
child: child,
);
},
);
}
@override
Widget wrap(BuildContext context, Widget child) {
return ListTileTheme(
data: ListTileThemeData(
dense: dense,
shape: shape,
style: style,
selectedColor: selectedColor,
iconColor: iconColor,
textColor: textColor,
contentPadding: contentPadding,
tileColor: tileColor,
selectedTileColor: selectedTileColor,
enableFeedback: enableFeedback,
horizontalTitleGap: horizontalTitleGap,
minVerticalPadding: minVerticalPadding,
minLeadingWidth: minLeadingWidth,
),
child: child,
);
}
@override
bool updateShouldNotify(ListTileTheme oldWidget) => data != oldWidget.data;
}
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'list_tile.dart'; import 'list_tile.dart';
import 'list_tile_theme.dart';
import 'radio.dart'; import 'radio.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'list_tile.dart'; import 'list_tile.dart';
import 'list_tile_theme.dart';
import 'switch.dart'; import 'switch.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
......
...@@ -31,6 +31,7 @@ import 'ink_splash.dart'; ...@@ -31,6 +31,7 @@ import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory; import 'ink_well.dart' show InteractiveInkFeatureFactory;
import 'input_decorator.dart'; import 'input_decorator.dart';
import 'list_tile.dart'; import 'list_tile.dart';
import 'list_tile_theme.dart';
import 'navigation_bar_theme.dart'; import 'navigation_bar_theme.dart';
import 'navigation_rail_theme.dart'; import 'navigation_rail_theme.dart';
import 'outlined_button_theme.dart'; import 'outlined_button_theme.dart';
......
...@@ -51,133 +51,6 @@ class TestTextState extends State<TestText> { ...@@ -51,133 +51,6 @@ class TestTextState extends State<TestText> {
} }
void main() { void main() {
test('ListTileThemeData copyWith, ==, hashCode basics', () {
expect(const ListTileThemeData(), const ListTileThemeData().copyWith());
expect(const ListTileThemeData().hashCode, const ListTileThemeData().copyWith().hashCode);
});
test('ListTileThemeData defaults', () {
const ListTileThemeData themeData = ListTileThemeData();
expect(themeData.dense, null);
expect(themeData.shape, null);
expect(themeData.style, null);
expect(themeData.selectedColor, null);
expect(themeData.iconColor, null);
expect(themeData.textColor, null);
expect(themeData.contentPadding, null);
expect(themeData.tileColor, null);
expect(themeData.selectedTileColor, null);
expect(themeData.horizontalTitleGap, null);
expect(themeData.minVerticalPadding, null);
expect(themeData.minLeadingWidth, null);
expect(themeData.enableFeedback, null);
expect(themeData.mouseCursor, null);
});
testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ListTileThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('ListTileThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ListTileThemeData(
dense: true,
shape: StadiumBorder(),
style: ListTileStyle.drawer,
selectedColor: Color(0x00000001),
iconColor: Color(0x00000002),
textColor: Color(0x00000003),
contentPadding: EdgeInsets.all(100),
tileColor: Color(0x00000004),
selectedTileColor: Color(0x00000005),
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[
'dense: true',
'shape: StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none))',
'style: drawer',
'selectedColor: Color(0x00000001)',
'iconColor: Color(0x00000002)',
'textColor: Color(0x00000003)',
'contentPadding: EdgeInsets.all(100.0)',
'tileColor: Color(0x00000004)',
'selectedTileColor: Color(0x00000005)',
'horizontalTitleGap: 200.0',
'minVerticalPadding: 300.0',
'minLeadingWidth: 400.0',
'enableFeedback: true',
'mouseCursor: MaterialStateMouseCursor(clickable)',
]);
});
testWidgets('ListTileTheme backwards compatibility constructor', (WidgetTester tester) async {
late ListTileThemeData theme;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
dense: true,
shape: const StadiumBorder(),
style: ListTileStyle.drawer,
selectedColor: const Color(0x00000001),
iconColor: const Color(0x00000002),
textColor: const Color(0x00000003),
contentPadding: const EdgeInsets.all(100),
tileColor: const Color(0x00000004),
selectedTileColor: const Color(0x00000005),
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
child: Center(
child: Builder(
builder: (BuildContext context) {
theme = ListTileTheme.of(context);
return const Placeholder();
},
),
),
),
),
),
);
expect(theme.dense, true);
expect(theme.shape, const StadiumBorder());
expect(theme.style, ListTileStyle.drawer);
expect(theme.selectedColor, const Color(0x00000001));
expect(theme.iconColor, const Color(0x00000002));
expect(theme.textColor, const Color(0x00000003));
expect(theme.contentPadding, const EdgeInsets.all(100));
expect(theme.tileColor, const Color(0x00000004));
expect(theme.selectedTileColor, const Color(0x00000005));
expect(theme.horizontalTitleGap, 200);
expect(theme.minVerticalPadding, 300);
expect(theme.minLeadingWidth, 400);
expect(theme.enableFeedback, true);
expect(theme.mouseCursor, MaterialStateMouseCursor.clickable);
});
testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async { testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async {
// See https://material.io/go/design-lists // See https://material.io/go/design-lists
...@@ -403,126 +276,6 @@ void main() { ...@@ -403,126 +276,6 @@ void main() {
expect(callCount, 1); expect(callCount, 1);
}); });
testWidgets('ListTileTheme', (WidgetTester tester) async {
final Key titleKey = UniqueKey();
final Key subtitleKey = UniqueKey();
final Key leadingKey = UniqueKey();
final Key trailingKey = UniqueKey();
late ThemeData theme;
Widget buildFrame({
bool enabled = true,
bool dense = false,
bool selected = false,
ShapeBorder? shape,
Color? selectedColor,
Color? iconColor,
Color? textColor,
}) {
return MaterialApp(
home: Material(
child: Center(
child: ListTileTheme(
data: ListTileThemeData(
dense: dense,
shape: shape,
selectedColor: selectedColor,
iconColor: iconColor,
textColor: textColor,
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return SystemMouseCursors.forbidden;
}
return SystemMouseCursors.click;
}),
),
child: Builder(
builder: (BuildContext context) {
theme = Theme.of(context);
return ListTile(
enabled: enabled,
selected: selected,
leading: TestIcon(key: leadingKey),
trailing: TestIcon(key: trailingKey),
title: TestText('title', key: titleKey),
subtitle: TestText('subtitle', key: subtitleKey),
);
},
),
),
),
),
);
}
const Color green = Color(0xFF00FF00);
const Color red = Color(0xFFFF0000);
const ShapeBorder roundedShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
);
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;
Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color!;
ShapeBorder inkWellBorder() => tester.widget<InkWell>(find.descendant(of: find.byType(ListTile), matching: find.byType(InkWell))).customBorder!;
// A selected ListTile's leading, trailing, and text get the primary color by default
await tester.pumpWidget(buildFrame(selected: true));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.primaryColor);
expect(iconColor(trailingKey), theme.primaryColor);
expect(textColor(titleKey), theme.primaryColor);
expect(textColor(subtitleKey), theme.primaryColor);
// A selected ListTile's leading, trailing, and text get the ListTileTheme's selectedColor
await tester.pumpWidget(buildFrame(selected: true, selectedColor: green));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), green);
expect(iconColor(trailingKey), green);
expect(textColor(titleKey), green);
expect(textColor(subtitleKey), green);
// An unselected ListTile's leading and trailing get the ListTileTheme's iconColor
// An unselected ListTile's title texts get the ListTileTheme's textColor
await tester.pumpWidget(buildFrame(iconColor: red, textColor: green));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), red);
expect(iconColor(trailingKey), red);
expect(textColor(titleKey), green);
expect(textColor(subtitleKey), green);
// If the item is disabled it's rendered with the theme's disabled color.
await tester.pumpWidget(buildFrame(enabled: false));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.disabledColor);
expect(iconColor(trailingKey), theme.disabledColor);
expect(textColor(titleKey), theme.disabledColor);
expect(textColor(subtitleKey), theme.disabledColor);
// If the item is disabled it's rendered with the theme's disabled color.
// Even if it's selected.
await tester.pumpWidget(buildFrame(enabled: false, selected: true));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.disabledColor);
expect(iconColor(trailingKey), theme.disabledColor);
expect(textColor(titleKey), theme.disabledColor);
expect(textColor(subtitleKey), theme.disabledColor);
// A selected ListTile's InkWell gets the ListTileTheme's shape
await tester.pumpWidget(buildFrame(selected: true, shape: roundedShape));
expect(inkWellBorder(), roundedShape);
// Cursor updates when hovering disabled ListTile
await tester.pumpWidget(buildFrame(enabled: false));
final Offset listTile = tester.getCenter(find.byKey(titleKey));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(listTile);
await tester.pumpAndSettle();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
});
testWidgets('ListTile semantics', (WidgetTester tester) async { testWidgets('ListTile semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
...@@ -686,7 +439,7 @@ void main() { ...@@ -686,7 +439,7 @@ void main() {
expect(right('L'), 790.0); // 800 - contentPadding.start expect(right('L'), 790.0); // 800 - contentPadding.start
}); });
testWidgets('ListTileTheme wide leading Widget', (WidgetTester tester) async { testWidgets('ListTile wide leading Widget', (WidgetTester tester) async {
const Key leadingKey = ValueKey<String>('L'); const Key leadingKey = ValueKey<String>('L');
Widget buildFrame(double leadingWidth, TextDirection textDirection) { Widget buildFrame(double leadingWidth, TextDirection textDirection) {
...@@ -1870,88 +1623,6 @@ void main() { ...@@ -1870,88 +1623,6 @@ void main() {
expect(find.byType(Material), paints..path(color: defaultColor)); expect(find.byType(Material), paints..path(color: defaultColor));
}); });
testWidgets("ListTile respects ListTileTheme's tileColor & selectedTileColor", (WidgetTester tester) async {
late ListTileThemeData theme;
bool isSelected = false;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
data: ListTileThemeData(
tileColor: Colors.green.shade500,
selectedTileColor: Colors.red.shade500,
),
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
theme = ListTileTheme.of(context);
return ListTile(
selected: isSelected,
onTap: () {
setState(()=> isSelected = !isSelected);
},
title: const Text('Title'),
);
},
),
),
),
),
),
);
expect(find.byType(Material), paints..path(color: theme.tileColor));
// Tap on tile to change isSelected.
await tester.tap(find.byType(ListTile));
await tester.pumpAndSettle();
expect(find.byType(Material), paints..path(color: theme.selectedTileColor));
});
testWidgets("ListTileTheme's tileColor & selectedTileColor are overridden by ListTile properties", (WidgetTester tester) async {
bool isSelected = false;
final Color tileColor = Colors.green.shade500;
final Color selectedTileColor = Colors.red.shade500;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
data: const ListTileThemeData(
selectedTileColor: Colors.green,
tileColor: Colors.red,
),
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return ListTile(
tileColor: tileColor,
selectedTileColor: selectedTileColor,
selected: isSelected,
onTap: () {
setState(()=> isSelected = !isSelected);
},
title: const Text('Title'),
);
},
),
),
),
),
),
);
expect(find.byType(Material), paints..path(color: tileColor));
// Tap on tile to change isSelected.
await tester.tap(find.byType(ListTile));
await tester.pumpAndSettle();
expect(find.byType(Material), paints..path(color: selectedTileColor));
});
testWidgets('ListTile layout at zero size', (WidgetTester tester) async { testWidgets('ListTile layout at zero size', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/66636 // Regression test for https://github.com/flutter/flutter/issues/66636
const Key key = Key('key'); const Key key = Key('key');
...@@ -2408,66 +2079,6 @@ void main() { ...@@ -2408,66 +2079,6 @@ void main() {
expect(textColor(trailingKey), theme.disabledColor); expect(textColor(trailingKey), theme.disabledColor);
}); });
testWidgets('ListTileTheme colors are applied to leading and trailing text widgets', (WidgetTester tester) async {
final Key leadingKey = UniqueKey();
final Key trailingKey = UniqueKey();
const Color selectedColor = Colors.orange;
const Color defaultColor = Colors.black;
late ThemeData theme;
Widget buildFrame({
bool enabled = true,
bool selected = false,
}) {
return MaterialApp(
home: Material(
child: Center(
child: ListTileTheme(
data: const ListTileThemeData(
selectedColor: selectedColor,
textColor: defaultColor,
),
child: Builder(
builder: (BuildContext context) {
theme = Theme.of(context);
return ListTile(
enabled: enabled,
selected: selected,
leading: TestText('leading', key: leadingKey),
title: const TestText('title'),
trailing: TestText('trailing', key: trailingKey),
);
},
),
),
),
),
);
}
Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color!;
await tester.pumpWidget(buildFrame());
// Enabled color should use ListTileTheme.textColor.
expect(textColor(leadingKey), defaultColor);
expect(textColor(trailingKey), defaultColor);
await tester.pumpWidget(buildFrame(selected: true));
// Wait for text color to animate.
await tester.pumpAndSettle();
// Selected color should use ListTileTheme.selectedColor.
expect(textColor(leadingKey), selectedColor);
expect(textColor(trailingKey), selectedColor);
await tester.pumpWidget(buildFrame(enabled: false));
// Wait for text color to animate.
await tester.pumpAndSettle();
// Disabled color should be ThemeData.disabledColor.
expect(textColor(leadingKey), theme.disabledColor);
expect(textColor(trailingKey), theme.disabledColor);
});
testWidgets('selected, enabled ListTile default icon color, light and dark themes', (WidgetTester tester) async { testWidgets('selected, enabled ListTile default icon color, light and dark themes', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/pull/77004 // Regression test for https://github.com/flutter/flutter/pull/77004
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
class TestIcon extends StatefulWidget {
const TestIcon({ Key? key }) : super(key: key);
@override
TestIconState createState() => TestIconState();
}
class TestIconState extends State<TestIcon> {
late IconThemeData iconTheme;
@override
Widget build(BuildContext context) {
iconTheme = IconTheme.of(context);
return const Icon(Icons.add);
}
}
class TestText extends StatefulWidget {
const TestText(this.text, { Key? key }) : super(key: key);
final String text;
@override
TestTextState createState() => TestTextState();
}
class TestTextState extends State<TestText> {
late TextStyle textStyle;
@override
Widget build(BuildContext context) {
textStyle = DefaultTextStyle.of(context).style;
return Text(widget.text);
}
}
void main() {
test('ListTileThemeData copyWith, ==, hashCode basics', () {
expect(const ListTileThemeData(), const ListTileThemeData().copyWith());
expect(const ListTileThemeData().hashCode, const ListTileThemeData().copyWith().hashCode);
});
test('ListTileThemeData defaults', () {
const ListTileThemeData themeData = ListTileThemeData();
expect(themeData.dense, null);
expect(themeData.shape, null);
expect(themeData.style, null);
expect(themeData.selectedColor, null);
expect(themeData.iconColor, null);
expect(themeData.textColor, null);
expect(themeData.contentPadding, null);
expect(themeData.tileColor, null);
expect(themeData.selectedTileColor, null);
expect(themeData.horizontalTitleGap, null);
expect(themeData.minVerticalPadding, null);
expect(themeData.minLeadingWidth, null);
expect(themeData.enableFeedback, null);
expect(themeData.mouseCursor, null);
expect(themeData.visualDensity, null);
});
testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ListTileThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('ListTileThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ListTileThemeData(
dense: true,
shape: StadiumBorder(),
style: ListTileStyle.drawer,
selectedColor: Color(0x00000001),
iconColor: Color(0x00000002),
textColor: Color(0x00000003),
contentPadding: EdgeInsets.all(100),
tileColor: Color(0x00000004),
selectedTileColor: Color(0x00000005),
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
visualDensity: VisualDensity.comfortable,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description[0], 'dense: true');
expect(description[1], 'shape: StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none))');
expect(description[2], 'style: drawer');
expect(description[3], 'selectedColor: Color(0x00000001)');
expect(description[4], 'iconColor: Color(0x00000002)');
expect(description[5], 'textColor: Color(0x00000003)');
expect(description[6], 'contentPadding: EdgeInsets.all(100.0)');
expect(description[7], 'tileColor: Color(0x00000004)');
expect(description[8], 'selectedTileColor: Color(0x00000005)');
expect(description[9], 'horizontalTitleGap: 200.0');
expect(description[10], 'minVerticalPadding: 300.0');
expect(description[11], 'minLeadingWidth: 400.0');
expect(description[12], 'enableFeedback: true');
expect(description[13], 'mouseCursor: MaterialStateMouseCursor(clickable)');
expect(
description[14],
equalsIgnoringHashCodes('visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0)'),
);
});
testWidgets('ListTileTheme backwards compatibility constructor', (WidgetTester tester) async {
late ListTileThemeData theme;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
dense: true,
shape: const StadiumBorder(),
style: ListTileStyle.drawer,
selectedColor: const Color(0x00000001),
iconColor: const Color(0x00000002),
textColor: const Color(0x00000003),
contentPadding: const EdgeInsets.all(100),
tileColor: const Color(0x00000004),
selectedTileColor: const Color(0x00000005),
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
child: Center(
child: Builder(
builder: (BuildContext context) {
theme = ListTileTheme.of(context);
return const Placeholder();
},
),
),
),
),
),
);
expect(theme.dense, true);
expect(theme.shape, const StadiumBorder());
expect(theme.style, ListTileStyle.drawer);
expect(theme.selectedColor, const Color(0x00000001));
expect(theme.iconColor, const Color(0x00000002));
expect(theme.textColor, const Color(0x00000003));
expect(theme.contentPadding, const EdgeInsets.all(100));
expect(theme.tileColor, const Color(0x00000004));
expect(theme.selectedTileColor, const Color(0x00000005));
expect(theme.horizontalTitleGap, 200);
expect(theme.minVerticalPadding, 300);
expect(theme.minLeadingWidth, 400);
expect(theme.enableFeedback, true);
expect(theme.mouseCursor, MaterialStateMouseCursor.clickable);
});
testWidgets('ListTileTheme', (WidgetTester tester) async {
final Key listTileKey = UniqueKey();
final Key titleKey = UniqueKey();
final Key subtitleKey = UniqueKey();
final Key leadingKey = UniqueKey();
final Key trailingKey = UniqueKey();
late ThemeData theme;
Widget buildFrame({
bool enabled = true,
bool dense = false,
bool selected = false,
ShapeBorder? shape,
Color? selectedColor,
Color? iconColor,
Color? textColor,
}) {
return MaterialApp(
home: Material(
child: Center(
child: ListTileTheme(
data: ListTileThemeData(
dense: dense,
shape: shape,
selectedColor: selectedColor,
iconColor: iconColor,
textColor: textColor,
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return SystemMouseCursors.forbidden;
}
return SystemMouseCursors.click;
}),
visualDensity: VisualDensity.compact,
),
child: Builder(
builder: (BuildContext context) {
theme = Theme.of(context);
return ListTile(
key: listTileKey,
enabled: enabled,
selected: selected,
leading: TestIcon(key: leadingKey),
trailing: TestIcon(key: trailingKey),
title: TestText('title', key: titleKey),
subtitle: TestText('subtitle', key: subtitleKey),
);
},
),
),
),
),
);
}
const Color green = Color(0xFF00FF00);
const Color red = Color(0xFFFF0000);
const ShapeBorder roundedShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
);
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;
Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color!;
ShapeBorder inkWellBorder() => tester.widget<InkWell>(find.descendant(of: find.byType(ListTile), matching: find.byType(InkWell))).customBorder!;
// A selected ListTile's leading, trailing, and text get the primary color by default
await tester.pumpWidget(buildFrame(selected: true));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.primaryColor);
expect(iconColor(trailingKey), theme.primaryColor);
expect(textColor(titleKey), theme.primaryColor);
expect(textColor(subtitleKey), theme.primaryColor);
// A selected ListTile's leading, trailing, and text get the ListTileTheme's selectedColor
await tester.pumpWidget(buildFrame(selected: true, selectedColor: green));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), green);
expect(iconColor(trailingKey), green);
expect(textColor(titleKey), green);
expect(textColor(subtitleKey), green);
// An unselected ListTile's leading and trailing get the ListTileTheme's iconColor
// An unselected ListTile's title texts get the ListTileTheme's textColor
await tester.pumpWidget(buildFrame(iconColor: red, textColor: green));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), red);
expect(iconColor(trailingKey), red);
expect(textColor(titleKey), green);
expect(textColor(subtitleKey), green);
// If the item is disabled it's rendered with the theme's disabled color.
await tester.pumpWidget(buildFrame(enabled: false));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.disabledColor);
expect(iconColor(trailingKey), theme.disabledColor);
expect(textColor(titleKey), theme.disabledColor);
expect(textColor(subtitleKey), theme.disabledColor);
// If the item is disabled it's rendered with the theme's disabled color.
// Even if it's selected.
await tester.pumpWidget(buildFrame(enabled: false, selected: true));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.disabledColor);
expect(iconColor(trailingKey), theme.disabledColor);
expect(textColor(titleKey), theme.disabledColor);
expect(textColor(subtitleKey), theme.disabledColor);
// A selected ListTile's InkWell gets the ListTileTheme's shape
await tester.pumpWidget(buildFrame(selected: true, shape: roundedShape));
expect(inkWellBorder(), roundedShape);
// Cursor updates when hovering disabled ListTile
await tester.pumpWidget(buildFrame(enabled: false));
final Offset listTile = tester.getCenter(find.byKey(titleKey));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(listTile);
await tester.pumpAndSettle();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
// VisualDensity is respected
final RenderBox box = tester.renderObject(find.byKey(listTileKey));
expect(box.size, equals(const Size(800, 64.0)));
});
testWidgets('ListTileTheme colors are applied to leading and trailing text widgets', (WidgetTester tester) async {
final Key leadingKey = UniqueKey();
final Key trailingKey = UniqueKey();
const Color selectedColor = Colors.orange;
const Color defaultColor = Colors.black;
late ThemeData theme;
Widget buildFrame({
bool enabled = true,
bool selected = false,
}) {
return MaterialApp(
home: Material(
child: Center(
child: ListTileTheme(
data: const ListTileThemeData(
selectedColor: selectedColor,
textColor: defaultColor,
),
child: Builder(
builder: (BuildContext context) {
theme = Theme.of(context);
return ListTile(
enabled: enabled,
selected: selected,
leading: TestText('leading', key: leadingKey),
title: const TestText('title'),
trailing: TestText('trailing', key: trailingKey),
);
},
),
),
),
),
);
}
Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color!;
await tester.pumpWidget(buildFrame());
// Enabled color should use ListTileTheme.textColor.
expect(textColor(leadingKey), defaultColor);
expect(textColor(trailingKey), defaultColor);
await tester.pumpWidget(buildFrame(selected: true));
// Wait for text color to animate.
await tester.pumpAndSettle();
// Selected color should use ListTileTheme.selectedColor.
expect(textColor(leadingKey), selectedColor);
expect(textColor(trailingKey), selectedColor);
await tester.pumpWidget(buildFrame(enabled: false));
// Wait for text color to animate.
await tester.pumpAndSettle();
// Disabled color should be ThemeData.disabledColor.
expect(textColor(leadingKey), theme.disabledColor);
expect(textColor(trailingKey), theme.disabledColor);
});
testWidgets("ListTile respects ListTileTheme's tileColor & selectedTileColor", (WidgetTester tester) async {
late ListTileThemeData theme;
bool isSelected = false;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
data: ListTileThemeData(
tileColor: Colors.green.shade500,
selectedTileColor: Colors.red.shade500,
),
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
theme = ListTileTheme.of(context);
return ListTile(
selected: isSelected,
onTap: () {
setState(()=> isSelected = !isSelected);
},
title: const Text('Title'),
);
},
),
),
),
),
),
);
expect(find.byType(Material), paints..path(color: theme.tileColor));
// Tap on tile to change isSelected.
await tester.tap(find.byType(ListTile));
await tester.pumpAndSettle();
expect(find.byType(Material), paints..path(color: theme.selectedTileColor));
});
testWidgets("ListTileTheme's tileColor & selectedTileColor are overridden by ListTile properties", (WidgetTester tester) async {
bool isSelected = false;
final Color tileColor = Colors.green.shade500;
final Color selectedTileColor = Colors.red.shade500;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
data: const ListTileThemeData(
selectedTileColor: Colors.green,
tileColor: Colors.red,
),
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return ListTile(
tileColor: tileColor,
selectedTileColor: selectedTileColor,
selected: isSelected,
onTap: () {
setState(()=> isSelected = !isSelected);
},
title: const Text('Title'),
);
},
),
),
),
),
),
);
expect(find.byType(Material), paints..path(color: tileColor));
// Tap on tile to change isSelected.
await tester.tap(find.byType(ListTile));
await tester.pumpAndSettle();
expect(find.byType(Material), paints..path(color: selectedTileColor));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment