Unverified Commit e8eac0d0 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Update `ExpansionTile` to support Material 3 & add an example (#119712)

parent da36bd6f
...@@ -31,6 +31,7 @@ import 'package:gen_defaults/date_picker_template.dart'; ...@@ -31,6 +31,7 @@ import 'package:gen_defaults/date_picker_template.dart';
import 'package:gen_defaults/dialog_template.dart'; import 'package:gen_defaults/dialog_template.dart';
import 'package:gen_defaults/divider_template.dart'; import 'package:gen_defaults/divider_template.dart';
import 'package:gen_defaults/drawer_template.dart'; import 'package:gen_defaults/drawer_template.dart';
import 'package:gen_defaults/expansion_tile_template.dart';
import 'package:gen_defaults/fab_template.dart'; import 'package:gen_defaults/fab_template.dart';
import 'package:gen_defaults/filter_chip_template.dart'; import 'package:gen_defaults/filter_chip_template.dart';
import 'package:gen_defaults/icon_button_template.dart'; import 'package:gen_defaults/icon_button_template.dart';
...@@ -153,6 +154,7 @@ Future<void> main(List<String> args) async { ...@@ -153,6 +154,7 @@ Future<void> main(List<String> args) async {
DialogTemplate('Dialog', '$materialLib/dialog.dart', tokens).updateFile(); DialogTemplate('Dialog', '$materialLib/dialog.dart', tokens).updateFile();
DividerTemplate('Divider', '$materialLib/divider.dart', tokens).updateFile(); DividerTemplate('Divider', '$materialLib/divider.dart', tokens).updateFile();
DrawerTemplate('Drawer', '$materialLib/drawer.dart', tokens).updateFile(); DrawerTemplate('Drawer', '$materialLib/drawer.dart', tokens).updateFile();
ExpansionTileTemplate('ExpansionTile', '$materialLib/expansion_tile.dart', tokens).updateFile();
FABTemplate('FAB', '$materialLib/floating_action_button.dart', tokens).updateFile(); FABTemplate('FAB', '$materialLib/floating_action_button.dart', tokens).updateFile();
FilterChipTemplate('ChoiceChip', '$materialLib/choice_chip.dart', tokens).updateFile(); FilterChipTemplate('ChoiceChip', '$materialLib/choice_chip.dart', tokens).updateFile();
FilterChipTemplate('FilterChip', '$materialLib/filter_chip.dart', tokens).updateFile(); FilterChipTemplate('FilterChip', '$materialLib/filter_chip.dart', tokens).updateFile();
......
// 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 'template.dart';
class ExpansionTileTemplate extends TokenTemplate {
const ExpansionTileTemplate(super.blockName, super.fileName, super.tokens, {
super.colorSchemePrefix = '_colors.',
});
@override
String generate() => '''
class _${blockName}DefaultsM3 extends ExpansionTileThemeData {
_${blockName}DefaultsM3(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override
Color? get textColor => ${textStyle("md.comp.list.list-item.label-text")}!.color;
@override
Color? get iconColor => ${componentColor('md.comp.list.list-item.selected.trailing-icon')};
@override
Color? get collapsedTextColor => ${textStyle("md.comp.list.list-item.label-text")}!.color;
@override
Color? get collapsedIconColor => ${componentColor('md.comp.list.list-item.unselected.trailing-icon')};
}
''';
}
...@@ -6,33 +6,31 @@ ...@@ -6,33 +6,31 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
void main() => runApp(const MyApp()); void main() => runApp(const ExpansionTileApp());
class MyApp extends StatelessWidget { class ExpansionTileApp extends StatelessWidget {
const MyApp({super.key}); const ExpansionTileApp({super.key});
static const String _title = 'Flutter Code Sample';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: _title, theme: ThemeData(useMaterial3: true),
home: Scaffold( home: Scaffold(
appBar: AppBar(title: const Text(_title)), appBar: AppBar(title: const Text('ExpansionTile Sample')),
body: const MyStatefulWidget(), body: const ExpansionTileExample(),
), ),
); );
} }
} }
class MyStatefulWidget extends StatefulWidget { class ExpansionTileExample extends StatefulWidget {
const MyStatefulWidget({super.key}); const ExpansionTileExample({super.key});
@override @override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState(); State<ExpansionTileExample> createState() => _ExpansionTileExampleState();
} }
class _MyStatefulWidgetState extends State<MyStatefulWidget> { class _ExpansionTileExampleState extends State<ExpansionTileExample> {
bool _customTileExpanded = false; bool _customTileExpanded = false;
@override @override
...@@ -58,7 +56,9 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> { ...@@ -58,7 +56,9 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
ListTile(title: Text('This is tile number 2')), ListTile(title: Text('This is tile number 2')),
], ],
onExpansionChanged: (bool expanded) { onExpansionChanged: (bool expanded) {
setState(() => _customTileExpanded = expanded); setState(() {
_customTileExpanded = expanded;
});
}, },
), ),
const ExpansionTile( const ExpansionTile(
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/expansion_tile/expansion_tile.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('When expansion tiles are expanded tile numbers are revealed', (WidgetTester tester) async {
const int totalTiles = 3;
await tester.pumpWidget(
const example.ExpansionTileApp(),
);
expect(find.byType(ExpansionTile), findsNWidgets(totalTiles));
const String tileOne = 'This is tile number 1';
expect(find.text(tileOne), findsNothing);
await tester.tap(find.text('ExpansionTile 1'));
await tester.pumpAndSettle();
expect(find.text(tileOne), findsOneWidget);
const String tileTwo = 'This is tile number 2';
expect(find.text(tileTwo), findsNothing);
await tester.tap(find.text('ExpansionTile 2'));
await tester.pumpAndSettle();
expect(find.text(tileTwo), findsOneWidget);
const String tileThree = 'This is tile number 3';
expect(find.text(tileThree), findsNothing);
await tester.tap(find.text('ExpansionTile 3'));
await tester.pumpAndSettle();
expect(find.text(tileThree), findsOneWidget);
});
}
...@@ -34,7 +34,8 @@ const Duration _kExpand = Duration(milliseconds: 200); ...@@ -34,7 +34,8 @@ const Duration _kExpand = Duration(milliseconds: 200);
/// to the [leading] and [trailing] properties of [ExpansionTile]. /// to the [leading] and [trailing] properties of [ExpansionTile].
/// ///
/// {@tool dartpad} /// {@tool dartpad}
/// This example demonstrates different configurations of ExpansionTile. /// This example demonstrates how the [ExpansionTile] icon's location and appearance
/// can be customized.
/// ///
/// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.0.dart ** /// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.0.dart **
/// {@end-tool} /// {@end-tool}
...@@ -216,7 +217,7 @@ class ExpansionTile extends StatefulWidget { ...@@ -216,7 +217,7 @@ class ExpansionTile extends StatefulWidget {
/// Used to override to the [ListTileThemeData.iconColor]. /// Used to override to the [ListTileThemeData.iconColor].
/// ///
/// If this property is null then [ExpansionTileThemeData.iconColor] is used. If that /// If this property is null then [ExpansionTileThemeData.iconColor] is used. If that
/// is also null then the value of [ListTileThemeData.iconColor] is used. /// is also null then the value of [ColorScheme.primary] is used.
/// ///
/// See also: /// See also:
/// ///
...@@ -227,6 +228,15 @@ class ExpansionTile extends StatefulWidget { ...@@ -227,6 +228,15 @@ class ExpansionTile extends StatefulWidget {
/// The icon color of tile's expansion arrow icon when the sublist is collapsed. /// The icon color of tile's expansion arrow icon when the sublist is collapsed.
/// ///
/// Used to override to the [ListTileThemeData.iconColor]. /// 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; final Color? collapsedIconColor;
...@@ -235,7 +245,8 @@ class ExpansionTile extends StatefulWidget { ...@@ -235,7 +245,8 @@ class ExpansionTile extends StatefulWidget {
/// Used to override to the [ListTileThemeData.textColor]. /// Used to override to the [ListTileThemeData.textColor].
/// ///
/// If this property is null then [ExpansionTileThemeData.textColor] is used. If that /// If this property is null then [ExpansionTileThemeData.textColor] is used. If that
/// is also null then the value of [ListTileThemeData.textColor] is used. /// 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.
/// ///
/// See also: /// See also:
/// ///
...@@ -247,8 +258,10 @@ class ExpansionTile extends StatefulWidget { ...@@ -247,8 +258,10 @@ class ExpansionTile extends StatefulWidget {
/// ///
/// Used to override to the [ListTileThemeData.textColor]. /// Used to override to the [ListTileThemeData.textColor].
/// ///
/// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used. If that /// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used.
/// is also null then the value of [ListTileThemeData.textColor] 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].
/// ///
/// See also: /// See also:
/// ///
...@@ -441,7 +454,9 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -441,7 +454,9 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
void didChangeDependencies() { void didChangeDependencies() {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context); final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
final ColorScheme colorScheme = theme.colorScheme; final ExpansionTileThemeData defaults = theme.useMaterial3
? _ExpansionTileDefaultsM3(context)
: _ExpansionTileDefaultsM2(context);
_borderTween _borderTween
..begin = widget.collapsedShape ..begin = widget.collapsedShape
?? expansionTileTheme.collapsedShape ?? expansionTileTheme.collapsedShape
...@@ -458,13 +473,13 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -458,13 +473,13 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
_headerColorTween _headerColorTween
..begin = widget.collapsedTextColor ..begin = widget.collapsedTextColor
?? expansionTileTheme.collapsedTextColor ?? expansionTileTheme.collapsedTextColor
?? theme.textTheme.titleMedium!.color ?? defaults.collapsedTextColor
..end = widget.textColor ?? expansionTileTheme.textColor ?? colorScheme.primary; ..end = widget.textColor ?? expansionTileTheme.textColor ?? defaults.textColor;
_iconColorTween _iconColorTween
..begin = widget.collapsedIconColor ..begin = widget.collapsedIconColor
?? expansionTileTheme.collapsedIconColor ?? expansionTileTheme.collapsedIconColor
?? theme.unselectedWidgetColor ?? defaults.collapsedIconColor
..end = widget.iconColor ?? expansionTileTheme.iconColor ?? colorScheme.primary; ..end = widget.iconColor ?? expansionTileTheme.iconColor ?? defaults.iconColor;
_backgroundColorTween _backgroundColorTween
..begin = widget.collapsedBackgroundColor ?? expansionTileTheme.collapsedBackgroundColor ..begin = widget.collapsedBackgroundColor ?? expansionTileTheme.collapsedBackgroundColor
..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor; ..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor;
...@@ -498,3 +513,54 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -498,3 +513,54 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
); );
} }
} }
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
...@@ -522,6 +522,35 @@ void main() { ...@@ -522,6 +522,35 @@ void main() {
expect(shapeDecoration.color, backgroundColor); expect(shapeDecoration.color, backgroundColor);
}); });
testWidgets('ExpansionTile default iconColor, textColor', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
await tester.pumpWidget(MaterialApp(
theme: theme,
home: const Material(
child: ExpansionTile(
title: TestText('title'),
trailing: TestIcon(),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
));
Color getIconColor() => tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
Color getTextColor() => tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
expect(getIconColor(), theme.colorScheme.onSurface);
expect(getTextColor(), theme.textTheme.bodyLarge!.color);
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
expect(getIconColor(), theme.colorScheme.primary);
expect(getTextColor(), theme.textTheme.bodyLarge!.color);
});
testWidgets('ExpansionTile iconColor, textColor', (WidgetTester tester) async { testWidgets('ExpansionTile iconColor, textColor', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/pull/78281 // Regression test for https://github.com/flutter/flutter/pull/78281
...@@ -666,4 +695,38 @@ void main() { ...@@ -666,4 +695,38 @@ void main() {
expect(listTile.leading.runtimeType, Icon); expect(listTile.leading.runtimeType, Icon);
expect(listTile.trailing, isNull); expect(listTile.trailing, isNull);
}); });
group('Material 2', () {
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
// is turned on by default, these tests can be removed.
testWidgets('ExpansionTile default iconColor, textColor', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: false);
await tester.pumpWidget(MaterialApp(
theme: theme,
home: const Material(
child: ExpansionTile(
title: TestText('title'),
trailing: TestIcon(),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
));
Color getIconColor() => tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
Color getTextColor() => tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
expect(getIconColor(), theme.unselectedWidgetColor);
expect(getTextColor(), theme.textTheme.titleMedium!.color);
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
expect(getIconColor(), theme.colorScheme.primary);
expect(getTextColor(), theme.colorScheme.primary);
});
});
} }
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