Update Material 3 Demo App (#1530)

pull/1543/head
Qun Cheng 2 years ago committed by GitHub
parent 019441374e
commit 405ebafe04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

File diff suppressed because it is too large Load Diff

@ -12,35 +12,45 @@ class ElevationScreen extends StatelessWidget {
Color shadowColor = Theme.of(context).colorScheme.shadow;
Color surfaceTint = Theme.of(context).colorScheme.primary;
return Expanded(
child: ListView(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20, 16.0, 0),
child: Text(
'Surface Tint only',
style: Theme.of(context).textTheme.titleLarge,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 20, 16.0, 0),
child: Text(
'Surface Tint Color Only',
style: Theme.of(context).textTheme.titleLarge,
),
),
),
ElevationGrid(surfaceTintColor: surfaceTint),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0),
child: Text(
'Surface Tint and Shadow',
style: Theme.of(context).textTheme.titleLarge,
),
SliverList(
delegate: SliverChildListDelegate(<Widget>[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0),
child: Text(
'Surface Tint Color and Shadow Color',
style: Theme.of(context).textTheme.titleLarge,
),
),
]),
),
ElevationGrid(
shadowColor: shadowColor,
surfaceTintColor: surfaceTint,
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0),
child: Text(
'Shadow only',
style: Theme.of(context).textTheme.titleLarge,
),
SliverList(
delegate: SliverChildListDelegate(<Widget>[
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0),
child: Text(
'Shadow Color Only',
style: Theme.of(context).textTheme.titleLarge,
),
),
]),
),
ElevationGrid(shadowColor: shadowColor),
],
@ -72,18 +82,16 @@ class ElevationGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
return SliverPadding(
padding: const EdgeInsets.all(8),
child: LayoutBuilder(builder: (context, constraints) {
if (constraints.maxWidth < narrowScreenWidthThreshold) {
return GridView.count(
shrinkWrap: true,
sliver: SliverLayoutBuilder(builder: (context, constraints) {
if (constraints.crossAxisExtent < narrowScreenWidthThreshold) {
return SliverGrid.count(
crossAxisCount: 3,
children: elevationCards(shadowColor, surfaceTintColor),
);
} else {
return GridView.count(
shrinkWrap: true,
return SliverGrid.count(
crossAxisCount: 6,
children: elevationCards(shadowColor, surfaceTintColor),
);

@ -9,7 +9,8 @@ import 'elevation_screen.dart';
import 'typography_screen.dart';
void main() {
runApp(const Material3Demo());
runApp(const MaterialApp(
debugShowCheckedModeBanner: false, home: Material3Demo()));
}
class Material3Demo extends StatefulWidget {
@ -23,25 +24,23 @@ class Material3Demo extends StatefulWidget {
// screenWidthThreshold; otherwise, NavigationBar is used for navigation.
const double narrowScreenWidthThreshold = 450;
const Color m3BaseColor = Color(0xff6750a4);
const List<Color> colorOptions = [
m3BaseColor,
Colors.blue,
Colors.teal,
Colors.green,
Colors.yellow,
Colors.orange,
Colors.pink
];
const List<String> colorText = <String>[
'M3 Baseline',
'Blue',
'Teal',
'Green',
'Yellow',
'Orange',
'Pink',
];
const double transitionLength = 500;
enum ColorSeed {
baseColor('M3 Baseline', Color(0xff6750a4)),
indigo('Indigo', Colors.indigo),
blue('Blue', Colors.blue),
teal('Teal', Colors.teal),
green('Green', Colors.green),
yellow('Yellow', Colors.yellow),
orange('Orange', Colors.orange),
deepOrange('Deep Orange', Colors.deepOrange),
pink('Pink', Colors.pink);
const ColorSeed(this.label, this.color);
final String label;
final Color color;
}
enum ScreenSelected {
component(0),
@ -53,10 +52,17 @@ enum ScreenSelected {
final int value;
}
class _Material3DemoState extends State<Material3Demo> {
class _Material3DemoState extends State<Material3Demo>
with SingleTickerProviderStateMixin {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
late final AnimationController controller;
late final CurvedAnimation railAnimation;
bool controllerInitialized = false;
bool showMediumSizeLayout = false;
bool showLargeSizeLayout = false;
bool useMaterial3 = true;
bool useLightMode = true;
int colorSelected = 0;
ColorSeed colorSelected = ColorSeed.baseColor;
int screenIndex = ScreenSelected.component.value;
late ThemeData themeData;
@ -64,12 +70,61 @@ class _Material3DemoState extends State<Material3Demo> {
@override
initState() {
super.initState();
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
themeData = updateThemes(colorSelected.color, useMaterial3, useLightMode);
controller = AnimationController(
duration: Duration(milliseconds: transitionLength.toInt() * 2),
value: 0,
vsync: this,
);
railAnimation = CurvedAnimation(
parent: controller,
curve: const Interval(0.5, 1.0),
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
ThemeData updateThemes(int colorIndex, bool useMaterial3, bool useLightMode) {
@override
void didChangeDependencies() {
super.didChangeDependencies();
final double width = MediaQuery.of(context).size.width;
final AnimationStatus status = controller.status;
if (width > 1000) {
if (width > 1500) {
showMediumSizeLayout = false;
showLargeSizeLayout = true;
} else {
showMediumSizeLayout = true;
showLargeSizeLayout = false;
}
if (status != AnimationStatus.forward &&
status != AnimationStatus.completed) {
controller.forward();
}
} else {
showMediumSizeLayout = false;
showLargeSizeLayout = false;
if (status != AnimationStatus.reverse &&
status != AnimationStatus.dismissed) {
controller.reverse();
}
}
if (!controllerInitialized) {
controllerInitialized = true;
controller.value = width > 1000 ? 1 : 0;
}
}
ThemeData updateThemes(
Color colorSelected, bool useMaterial3, bool useLightMode) {
return ThemeData(
colorSchemeSeed: colorOptions[colorSelected],
colorSchemeSeed: colorSelected,
useMaterial3: useMaterial3,
brightness: useLightMode ? Brightness.light : Brightness.dark);
}
@ -83,21 +138,21 @@ class _Material3DemoState extends State<Material3Demo> {
void handleBrightnessChange() {
setState(() {
useLightMode = !useLightMode;
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
themeData = updateThemes(colorSelected.color, useMaterial3, useLightMode);
});
}
void handleMaterialVersionChange() {
setState(() {
useMaterial3 = !useMaterial3;
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
themeData = updateThemes(colorSelected.color, useMaterial3, useLightMode);
});
}
void handleColorSelect(int value) {
setState(() {
colorSelected = value;
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
colorSelected = ColorSeed.values[value];
themeData = updateThemes(colorSelected.color, useMaterial3, useLightMode);
});
}
@ -105,7 +160,15 @@ class _Material3DemoState extends State<Material3Demo> {
ScreenSelected screenSelected, bool showNavBarExample) {
switch (screenSelected) {
case ScreenSelected.component:
return ComponentScreen(showNavBottomBar: showNavBarExample);
return Expanded(
child: OneTwoTransition(
animation: railAnimation,
one: FirstComponentList(
showNavBottomBar: showNavBarExample,
scaffoldKey: scaffoldKey,
showSecondList:
showMediumSizeLayout || showLargeSizeLayout),
two: const SecondComponentList()));
case ScreenSelected.color:
return const ColorPalettesScreen();
case ScreenSelected.typography:
@ -113,62 +176,140 @@ class _Material3DemoState extends State<Material3Demo> {
case ScreenSelected.elevation:
return const ElevationScreen();
default:
return ComponentScreen(showNavBottomBar: showNavBarExample);
return FirstComponentList(
showNavBottomBar: showNavBarExample,
scaffoldKey: scaffoldKey,
showSecondList: showMediumSizeLayout || showLargeSizeLayout);
}
}
PreferredSizeWidget createAppBar() {
return AppBar(
title: useMaterial3 ? const Text('Material 3') : const Text('Material 2'),
actions: [
IconButton(
Widget brightnessButton({bool showTooltipBelow = true}) => Tooltip(
preferBelow: showTooltipBelow,
message: 'Toggle brightness',
child: IconButton(
icon: useLightMode
? const Icon(Icons.wb_sunny_outlined)
: const Icon(Icons.wb_sunny),
onPressed: handleBrightnessChange,
tooltip: 'Toggle brightness',
),
IconButton(
);
Widget material3Button({bool showTooltipBelow = true}) => Tooltip(
preferBelow: showTooltipBelow,
message: 'Switch to Material ${useMaterial3 ? 2 : 3}',
child: IconButton(
icon: useMaterial3
? const Icon(Icons.filter_3)
: const Icon(Icons.filter_2),
onPressed: handleMaterialVersionChange,
tooltip: 'Switch to Material ${useMaterial3 ? 2 : 3}',
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
itemBuilder: (context) {
return List.generate(colorOptions.length, (index) {
return PopupMenuItem(
value: index,
child: Wrap(
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: Icon(
index == colorSelected
? Icons.color_lens
: Icons.color_lens_outlined,
color: colorOptions[index],
),
),
Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(colorText[index]),
);
Widget colorSeedButton(Icon icon) => PopupMenuButton(
icon: icon,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
itemBuilder: (context) {
return List.generate(ColorSeed.values.length, (index) {
ColorSeed currentColor = ColorSeed.values[index];
return PopupMenuItem(
value: index,
child: Wrap(
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: Icon(
currentColor == colorSelected
? Icons.color_lens
: Icons.color_lens_outlined,
color: currentColor.color,
),
],
),
);
});
},
onSelected: handleColorSelect,
),
],
),
Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(currentColor.label),
),
],
),
);
});
},
onSelected: handleColorSelect,
);
PreferredSizeWidget createAppBar() {
return AppBar(
title: useMaterial3 ? const Text('Material 3') : const Text('Material 2'),
actions: !showMediumSizeLayout && !showLargeSizeLayout
? [
brightnessButton(),
material3Button(),
colorSeedButton(const Icon(Icons.more_vert)),
]
: [Container()],
);
}
Widget _expandedTrailingActions() => Container(
constraints: const BoxConstraints.tightFor(width: 250),
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Text('Brightness'),
Expanded(child: Container()),
Switch(
value: useLightMode,
onChanged: (_) {
handleBrightnessChange();
})
],
),
Row(
children: [
useMaterial3
? const Text('Material 3')
: const Text('Material 2'),
Expanded(child: Container()),
Switch(
value: useMaterial3,
onChanged: (_) {
handleMaterialVersionChange();
})
],
),
const Divider(),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200.0),
child: GridView.count(
crossAxisCount: 3,
children: List.generate(
ColorSeed.values.length,
(i) => IconButton(
icon: const Icon(Icons.circle),
color: ColorSeed.values[i].color,
onPressed: () {
handleColorSelect(i);
},
)),
),
),
],
),
);
Widget _trailingActions() => Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Flexible(child: brightnessButton(showTooltipBelow: false)),
Flexible(child: material3Button(showTooltipBelow: false)),
Flexible(child: colorSeedButton(const Icon(Icons.more_horiz))),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp(
@ -176,40 +317,337 @@ class _Material3DemoState extends State<Material3Demo> {
title: 'Material 3',
themeMode: useLightMode ? ThemeMode.light : ThemeMode.dark,
theme: themeData,
home: LayoutBuilder(builder: (context, constraints) {
if (constraints.maxWidth < narrowScreenWidthThreshold) {
return Scaffold(
appBar: createAppBar(),
body: Row(children: <Widget>[
createScreenFor(ScreenSelected.values[screenIndex], false),
]),
bottomNavigationBar: NavigationBars(
onSelectItem: handleScreenChanged,
selectedIndex: screenIndex,
isExampleBar: false,
),
);
} else {
return Scaffold(
appBar: createAppBar(),
body: SafeArea(
bottom: false,
top: false,
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: NavigationRailSection(
onSelectItem: handleScreenChanged,
selectedIndex: screenIndex)),
const VerticalDivider(thickness: 1, width: 1),
createScreenFor(ScreenSelected.values[screenIndex], true),
],
home: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return NavigationTransition(
scaffoldKey: scaffoldKey,
animationController: controller,
railAnimation: railAnimation,
appBar: createAppBar(),
body: createScreenFor(
ScreenSelected.values[screenIndex], controller.value == 1),
navigationRail: NavigationRail(
extended: showLargeSizeLayout,
destinations: navRailDestinations,
selectedIndex: screenIndex,
onDestinationSelected: (index) {
setState(() {
screenIndex = index;
handleScreenChanged(screenIndex);
});
},
trailing: Expanded(
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: showLargeSizeLayout
? _expandedTrailingActions()
: _trailingActions(),
),
),
),
navigationBar: NavigationBars(
onSelectItem: (index) {
setState(() {
screenIndex = index;
handleScreenChanged(screenIndex);
});
},
selectedIndex: screenIndex,
isExampleBar: false,
),
);
}),
);
}
}
class NavigationTransition extends StatefulWidget {
const NavigationTransition(
{super.key,
required this.scaffoldKey,
required this.animationController,
required this.railAnimation,
required this.navigationRail,
required this.navigationBar,
required this.appBar,
required this.body});
final GlobalKey<ScaffoldState> scaffoldKey;
final AnimationController animationController;
final CurvedAnimation railAnimation;
final Widget navigationRail;
final Widget navigationBar;
final PreferredSizeWidget appBar;
final Widget body;
@override
State<NavigationTransition> createState() => _NavigationTransitionState();
}
class _NavigationTransitionState extends State<NavigationTransition> {
late final AnimationController controller;
late final CurvedAnimation railAnimation;
late final ReverseAnimation barAnimation;
bool controllerInitialized = false;
bool showDivider = false;
@override
void initState() {
super.initState();
controller = widget.animationController;
railAnimation = widget.railAnimation;
barAnimation = ReverseAnimation(
CurvedAnimation(
parent: controller,
curve: const Interval(0.0, 0.5),
),
);
}
@override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
return Scaffold(
key: widget.scaffoldKey,
appBar: widget.appBar,
body: Row(
children: <Widget>[
RailTransition(
animation: railAnimation,
backgroundColor: colorScheme.surface,
child: widget.navigationRail,
),
railAnimation.isDismissed
? const SizedBox()
: const VerticalDivider(width: 1),
widget.body,
],
),
bottomNavigationBar: BarTransition(
animation: barAnimation,
backgroundColor: colorScheme.surface,
child: widget.navigationBar,
),
endDrawer: const NavigationDrawerSection(),
);
}
}
final List<NavigationRailDestination> navRailDestinations = appBarDestinations
.map(
(destination) => NavigationRailDestination(
icon: Tooltip(
message: destination.label,
child: destination.icon,
),
selectedIcon: Tooltip(
message: destination.label,
child: destination.selectedIcon,
),
label: Text(destination.label),
),
)
.toList();
class SizeAnimation extends CurvedAnimation {
SizeAnimation(Animation<double> parent)
: super(
parent: parent,
curve: const Interval(
0.2,
0.8,
curve: Curves.easeInOutCubicEmphasized,
),
reverseCurve: Interval(
0,
0.2,
curve: Curves.easeInOutCubicEmphasized.flipped,
),
);
}
class OffsetAnimation extends CurvedAnimation {
OffsetAnimation(Animation<double> parent)
: super(
parent: parent,
curve: const Interval(
0.4,
1.0,
curve: Curves.easeInOutCubicEmphasized,
),
reverseCurve: Interval(
0,
0.2,
curve: Curves.easeInOutCubicEmphasized.flipped,
),
);
}
class RailTransition extends StatefulWidget {
const RailTransition(
{super.key,
required this.animation,
required this.backgroundColor,
required this.child});
final Animation<double> animation;
final Widget child;
final Color backgroundColor;
@override
State<RailTransition> createState() => _RailTransition();
}
class _RailTransition extends State<RailTransition> {
late Animation<Offset> offsetAnimation;
late Animation<double> widthAnimation;
@override
void didChangeDependencies() {
super.didChangeDependencies();
// The animations are only rebuilt by this method when the text
// direction changes because this widget only depends on Directionality.
final bool ltr = Directionality.of(context) == TextDirection.ltr;
widthAnimation = Tween<double>(
begin: 0,
end: 1,
).animate(SizeAnimation(widget.animation));
offsetAnimation = Tween<Offset>(
begin: ltr ? const Offset(-1, 0) : const Offset(1, 0),
end: Offset.zero,
).animate(OffsetAnimation(widget.animation));
}
@override
Widget build(BuildContext context) {
return ClipRect(
child: DecoratedBox(
decoration: BoxDecoration(color: widget.backgroundColor),
child: Align(
alignment: Alignment.topLeft,
widthFactor: widthAnimation.value,
child: FractionalTranslation(
translation: offsetAnimation.value,
child: widget.child,
),
),
),
);
}
}
class BarTransition extends StatefulWidget {
const BarTransition(
{super.key,
required this.animation,
required this.backgroundColor,
required this.child});
final Animation<double> animation;
final Color backgroundColor;
final Widget child;
@override
State<BarTransition> createState() => _BarTransition();
}
class _BarTransition extends State<BarTransition> {
late final Animation<Offset> offsetAnimation;
late final Animation<double> heightAnimation;
@override
void initState() {
super.initState();
offsetAnimation = Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(OffsetAnimation(widget.animation));
heightAnimation = Tween<double>(
begin: 0,
end: 1,
).animate(SizeAnimation(widget.animation));
}
@override
Widget build(BuildContext context) {
return ClipRect(
child: DecoratedBox(
decoration: BoxDecoration(color: widget.backgroundColor),
child: Align(
alignment: Alignment.topLeft,
heightFactor: heightAnimation.value,
child: FractionalTranslation(
translation: offsetAnimation.value,
child: widget.child,
),
),
),
);
}
}
class OneTwoTransition extends StatefulWidget {
const OneTwoTransition({
super.key,
required this.animation,
required this.one,
required this.two,
});
final Animation<double> animation;
final Widget one;
final Widget two;
@override
State<OneTwoTransition> createState() => _OneTwoTransitionState();
}
class _OneTwoTransitionState extends State<OneTwoTransition> {
late final Animation<Offset> offsetAnimation;
late final Animation<double> widthAnimation;
@override
void initState() {
super.initState();
offsetAnimation = Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(OffsetAnimation(widget.animation));
widthAnimation = Tween<double>(
begin: 0,
end: 1000,
).animate(SizeAnimation(widget.animation));
}
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Flexible(
flex: 1000,
child: widget.one,
),
if (widthAnimation.value.toInt() > 0) ...[
Flexible(
flex: widthAnimation.value.toInt(),
child: FractionalTranslation(
translation: offsetAnimation.value,
child: widget.two,
),
);
}
}),
)
],
],
);
}
}

@ -345,7 +345,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@ -424,7 +424,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@ -471,7 +471,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;

@ -16,17 +16,25 @@ void main() {
'on NavigationBar', (tester) async {
widgetSetup(tester, 449);
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpWidget(const Material3Demo());
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
expect(find.text('Light Theme'), findsNothing);
expect(find.text('Dark Theme'), findsNothing);
expect(find.byType(NavigationBar), findsOneWidget);
Finder colorIconOnBar = find.byIcon(Icons.format_paint_outlined);
Finder colorIconOnBar = find.descendant(
of: find.byType(NavigationBar),
matching: find.widgetWithIcon(
NavigationDestination, Icons.format_paint_outlined));
expect(colorIconOnBar, findsOneWidget);
await tester.tap(colorIconOnBar);
await tester.pumpAndSettle(const Duration(microseconds: 500));
expect(colorIconOnBar, findsNothing);
expect(find.byIcon(Icons.format_paint), findsOneWidget);
Finder selectedColorIconOnBar = find.descendant(
of: find.byType(NavigationBar),
matching:
find.widgetWithIcon(NavigationDestination, Icons.format_paint));
expect(selectedColorIconOnBar, findsOneWidget);
expect(find.text('Light Theme'), findsOneWidget);
expect(find.text('Dark Theme'), findsOneWidget);
});
@ -34,18 +42,25 @@ void main() {
testWidgets(
'Color palettes screen shows correctly when color icon is clicked '
'on NavigationRail', (tester) async {
widgetSetup(tester, 450); // NavigationRail shows only when width is >= 450.
widgetSetup(
tester, 1200); // NavigationRail shows only when width is > 1000.
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpWidget(const Material3Demo());
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
await tester.pumpAndSettle();
expect(find.text('Light Theme'), findsNothing);
expect(find.text('Dark Theme'), findsNothing);
expect(find.byType(NavigationRail), findsOneWidget);
Finder colorIconOnRail = find.byIcon(Icons.format_paint_outlined);
Finder colorIconOnRail = find.descendant(
of: find.byType(NavigationRail),
matching: find.byIcon(Icons.format_paint_outlined));
expect(colorIconOnRail, findsOneWidget);
await tester.tap(colorIconOnRail);
await tester.pumpAndSettle(const Duration(microseconds: 500));
expect(colorIconOnRail, findsNothing);
expect(find.byIcon(Icons.format_paint), findsOneWidget);
Finder selectedColorIconOnRail = find.descendant(
of: find.byType(NavigationRail),
matching: find.byIcon(Icons.format_paint));
expect(selectedColorIconOnRail, findsOneWidget);
expect(find.text('Light Theme'), findsOneWidget);
expect(find.text('Dark Theme'), findsOneWidget);
});

@ -10,15 +10,15 @@ import 'package:material_3_demo/main.dart';
void main() {
testWidgets('Default main page shows all M3 components', (tester) async {
widgetSetup(tester, 800, windowHeight: 3500);
await tester.pumpWidget(const Material3Demo());
widgetSetup(tester, 800, windowHeight: 7000);
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
// Elements on the app bar
expect(find.text('Material 3'), findsOneWidget);
expect(find.widgetWithIcon(IconButton, Icons.wb_sunny_outlined),
findsOneWidget);
expect(find.widgetWithIcon(IconButton, Icons.filter_3), findsOneWidget);
expect(find.widgetWithIcon(IconButton, Icons.more_vert), findsOneWidget);
expect(
find.widgetWithIcon(AppBar, Icons.wb_sunny_outlined), findsOneWidget);
expect(find.widgetWithIcon(AppBar, Icons.filter_3), findsOneWidget);
expect(find.widgetWithIcon(AppBar, Icons.more_vert), findsOneWidget);
// Elements on the component screen
// Buttons
@ -27,13 +27,14 @@ void main() {
expect(find.widgetWithText(FilledButton, 'Filled Tonal'), findsNWidgets(2));
expect(find.widgetWithText(OutlinedButton, 'Outlined'), findsNWidgets(2));
expect(find.widgetWithText(TextButton, 'Text'), findsNWidgets(2));
expect(find.text('Icon'), findsNWidgets(5));
expect(find.widgetWithText(Buttons, 'Icon'), findsNWidgets(5));
// IconButtons
expect(find.byType(IconToggleButton), findsNWidgets(8));
// FABs
expect(find.byType(FloatingActionButton), findsNWidgets(4));
expect(find.byType(FloatingActionButton),
findsNWidgets(6)); // 2 more shows up in the bottom app bar.
expect(find.widgetWithText(FloatingActionButton, 'Create'), findsOneWidget);
// Chips
@ -43,9 +44,9 @@ void main() {
expect(find.byType(InputChip), findsNWidgets(4));
// Cards
expect(find.widgetWithText(Card, 'Filled'), findsOneWidget);
expect(find.widgetWithText(Card, 'Filled'), findsOneWidget);
expect(find.widgetWithText(Card, 'Outlined'), findsOneWidget);
expect(find.widgetWithText(Cards, 'Elevated'), findsOneWidget);
expect(find.widgetWithText(Cards, 'Filled'), findsOneWidget);
expect(find.widgetWithText(Cards, 'Outlined'), findsOneWidget);
// TextFields
expect(find.widgetWithText(TextField, 'Disabled'), findsNWidgets(2));
@ -76,68 +77,57 @@ void main() {
});
testWidgets(
'NavigationRail doesn\'t show when width value is small than 450 '
'NavigationRail doesn\'t show when width value is small than 1000 '
'(in Portrait mode or narrow screen)', (tester) async {
widgetSetup(tester, 449);
await tester.pumpWidget(const Material3Demo());
widgetSetup(tester, 999, windowHeight: 7000);
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
await tester.pumpAndSettle();
// When screen width is less than 450, NavigationBar will show. At the same
// time, the NavigationRail and the NavigationBar example will NOT show.
expect(find.byType(NavigationBars), findsOneWidget);
// When screen width is less than 1000, NavigationBar will show. At the same
// time, the NavigationBar example still show up in the navigation group.
expect(find.byType(NavigationBars),
findsNWidgets(3)); // The real navBar, badges example and navBar example
expect(find.widgetWithText(NavigationBar, 'Components'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Color'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Typography'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Elevation'), findsOneWidget);
expect(find.byType(NavigationRailSection), findsNothing);
expect(find.widgetWithText(NavigationBar, 'Explore'), findsNothing);
expect(find.widgetWithText(NavigationBar, 'Pets'), findsNothing);
expect(find.widgetWithText(NavigationBar, 'Account'), findsNothing);
expect(find.widgetWithText(NavigationBar, 'Explore'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Pets'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Account'), findsOneWidget);
});
testWidgets(
'NavigationRail shows when width value is greater than or equal '
'to 450 (in Landscape mode or wider screen)', (tester) async {
widgetSetup(tester, 450);
await tester.pumpWidget(const Material3Demo());
// When screen width is greater than or equal to 450, NavigationRail and
// NavigationBar example will show. At the same time, the NavigationBar
// will NOT show.
expect(find.byType(NavigationRailSection), findsOneWidget);
'to 1000 (in Landscape mode or wider screen)', (tester) async {
widgetSetup(tester, 1001, windowHeight: 3000);
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
await tester.pumpAndSettle();
// When screen width is greater than or equal to 1000, NavigationRail will show.
// At the same time, the NavigationBar will NOT show.
expect(find.byType(NavigationRail), findsOneWidget);
expect(find.byType(Tooltip, skipOffstage: false), findsWidgets);
expect(find.widgetWithText(NavigationRailSection, 'Components'),
findsOneWidget);
expect(find.widgetWithText(NavigationRailSection, 'Color'), findsOneWidget);
expect(find.widgetWithText(NavigationRailSection, 'Typography'),
findsOneWidget);
expect(find.widgetWithText(NavigationRailSection, 'Elevation'),
findsOneWidget);
final navbarExample = find.byType(NavigationBars);
await tester.scrollUntilVisible(
scrollable: find.byType(Scrollable).first,
navbarExample,
500.0,
);
expect(find.byType(NavigationBars), findsOneWidget);
expect(find.widgetWithText(NavigationRail, 'Components'), findsOneWidget);
expect(find.widgetWithText(NavigationRail, 'Color'), findsOneWidget);
expect(find.widgetWithText(NavigationRail, 'Typography'), findsOneWidget);
expect(find.widgetWithText(NavigationRail, 'Elevation'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Explore'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Pets'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Account'), findsOneWidget);
expect(find.widgetWithText(NavigationBar, 'Components'), findsNothing);
expect(find.widgetWithText(NavigationBar, 'Colors'), findsNothing);
expect(find.widgetWithText(NavigationBar, 'Typography'), findsNothing);
expect(find.widgetWithText(NavigationBar, 'Elevation'), findsNothing);
// the Navigation bar should be out of screen.
final RenderBox box =
tester.renderObject(find.widgetWithText(NavigationBar, 'Components'));
expect(box.localToGlobal(Offset.zero), const Offset(0.0, 3080.0));
});
testWidgets(
'Material version switches between Material3 and Material2 when'
'the version icon is clicked', (tester) async {
widgetSetup(tester, 450, windowHeight: 3000);
await tester.pumpWidget(const Material3Demo());
Finder m3Icon = find.widgetWithIcon(IconButton, Icons.filter_3);
Finder m2Icon = find.widgetWithIcon(IconButton, Icons.filter_2);
widgetSetup(tester, 450, windowHeight: 7000);
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
BuildContext defaultElevatedButton =
tester.firstElement(find.byType(ElevatedButton));
BuildContext defaultIconButton =
@ -157,8 +147,8 @@ void main() {
await tester.tap(dismiss);
await tester.pumpAndSettle(const Duration(microseconds: 500));
expect(m3Icon, findsOneWidget);
expect(m2Icon, findsNothing);
expect(find.widgetWithIcon(AppBar, Icons.filter_3), findsOneWidget);
expect(find.widgetWithIcon(AppBar, Icons.filter_2), findsNothing);
expect(find.text('Material 3'), findsOneWidget);
expect(Theme.of(defaultElevatedButton).useMaterial3, true);
expect(Theme.of(defaultIconButton).useMaterial3, true);
@ -166,7 +156,10 @@ void main() {
expect(Theme.of(defaultCard).useMaterial3, true);
expect(Theme.of(defaultChip).useMaterial3, true);
await tester.tap(m3Icon);
Finder appbarM3Icon = find.descendant(
of: find.byType(AppBar),
matching: find.widgetWithIcon(IconButton, Icons.filter_3));
await tester.tap(appbarM3Icon);
await tester.pumpAndSettle(const Duration(microseconds: 500));
BuildContext updatedElevatedButton =
tester.firstElement(find.byType(ElevatedButton));
@ -187,8 +180,8 @@ void main() {
await tester.tap(updatedDismiss);
await tester.pumpAndSettle(const Duration(microseconds: 500));
expect(m3Icon, findsNothing);
expect(m2Icon, findsOneWidget);
expect(find.widgetWithIcon(AppBar, Icons.filter_2), findsOneWidget);
expect(find.widgetWithIcon(AppBar, Icons.filter_3), findsNothing);
expect(find.text('Material 2'), findsOneWidget);
expect(Theme.of(updatedElevatedButton).useMaterial3, false);
expect(Theme.of(updatedIconButton).useMaterial3, false);
@ -200,17 +193,32 @@ void main() {
testWidgets(
'Other screens become Material2 mode after changing mode from '
'main screen', (tester) async {
await tester.pumpWidget(const Material3Demo());
await tester.tap(find.widgetWithIcon(IconButton, Icons.filter_3));
await tester.tap(find.byIcon(Icons.format_paint_outlined));
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
Finder appbarM3Icon = find.descendant(
of: find.byType(AppBar),
matching: find.widgetWithIcon(IconButton, Icons.filter_3));
await tester.tap(appbarM3Icon);
Finder secondScreenIcon = find.descendant(
of: find.byType(NavigationBar),
matching: find.widgetWithIcon(
NavigationDestination, Icons.format_paint_outlined));
await tester.tap(secondScreenIcon);
await tester.pumpAndSettle(const Duration(microseconds: 500));
BuildContext lightThemeText = tester.element(find.text('Light Theme'));
expect(Theme.of(lightThemeText).useMaterial3, false);
await tester.tap(find.byIcon(Icons.text_snippet_outlined));
Finder thirdScreenIcon = find.descendant(
of: find.byType(NavigationBar),
matching: find.widgetWithIcon(
NavigationDestination, Icons.text_snippet_outlined));
await tester.tap(thirdScreenIcon);
await tester.pumpAndSettle(const Duration(microseconds: 500));
BuildContext displayLargeText = tester.element(find.text('Display Large'));
expect(Theme.of(displayLargeText).useMaterial3, false);
await tester.tap(find.byIcon(Icons.invert_colors_on_outlined));
Finder fourthScreenIcon = find.descendant(
of: find.byType(NavigationBar),
matching: find.widgetWithIcon(
NavigationDestination, Icons.invert_colors_on_outlined));
await tester.tap(fourthScreenIcon);
await tester.pumpAndSettle(const Duration(microseconds: 500));
BuildContext material = tester.firstElement(find.byType(Material));
expect(Theme.of(material).useMaterial3, false);
@ -219,12 +227,17 @@ void main() {
testWidgets(
'Brightness mode switches between dark and light when'
'the brightness icon is clicked', (tester) async {
await tester.pumpWidget(const Material3Demo());
Finder lightIcon = find.widgetWithIcon(IconButton, Icons.wb_sunny_outlined);
Finder darkIcon = find.widgetWithIcon(IconButton, Icons.wb_sunny);
BuildContext appBar = tester.element(find.byType(AppBar));
BuildContext body = tester.element(find.byType(Scaffold));
BuildContext navigationRail = tester.element(find.byType(NavigationRail));
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
Finder lightIcon = find.descendant(
of: find.byType(AppBar),
matching: find.widgetWithIcon(IconButton, Icons.wb_sunny_outlined));
Finder darkIcon = find.descendant(
of: find.byType(AppBar),
matching: find.widgetWithIcon(IconButton, Icons.wb_sunny));
BuildContext appBar = tester.element(find.byType(AppBar).first);
BuildContext body = tester.firstElement(find.byType(Scaffold).first);
BuildContext navigationRail = tester.element(
find.widgetWithIcon(NavigationRail, Icons.format_paint_outlined));
expect(lightIcon, findsOneWidget);
expect(darkIcon, findsNothing);
expect(Theme.of(appBar).brightness, Brightness.light);
@ -233,8 +246,8 @@ void main() {
await tester.tap(lightIcon);
await tester.pumpAndSettle(const Duration(microseconds: 500));
BuildContext appBar2 = tester.element(find.byType(AppBar));
BuildContext body2 = tester.element(find.byType(Scaffold));
BuildContext appBar2 = tester.element(find.byType(AppBar).first);
BuildContext body2 = tester.element(find.byType(Scaffold).first);
BuildContext navigationRail2 = tester.element(find.byType(NavigationRail));
expect(lightIcon, findsNothing);
expect(darkIcon, findsOneWidget);
@ -245,10 +258,15 @@ void main() {
testWidgets('Color theme changes when a color is selected from menu',
(tester) async {
await tester.pumpWidget(const Material3Demo());
Finder menuIcon = find.widgetWithIcon(IconButton, Icons.more_vert);
BuildContext appBar = tester.element(find.byType(AppBar));
BuildContext body = tester.element(find.byType(Scaffold));
Color m3BaseColor = const Color(0xff6750a4);
await tester.pumpWidget(Container());
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
await tester.pump();
Finder menuIcon = find.descendant(
of: find.byType(AppBar),
matching: find.widgetWithIcon(IconButton, Icons.more_vert));
BuildContext appBar = tester.element(find.byType(AppBar).first);
BuildContext body = tester.element(find.byType(Scaffold).first);
BuildContext navigationRail = tester.element(find.byType(NavigationRail));
expect(Theme.of(appBar).primaryColor, m3BaseColor);
@ -256,11 +274,11 @@ void main() {
expect(Theme.of(navigationRail).primaryColor, m3BaseColor);
await tester.tap(menuIcon);
await tester.pumpAndSettle();
await tester.tap(find.text('Blue'));
await tester.tap(find.text('Blue').last);
await tester.pumpAndSettle();
BuildContext appBar2 = tester.element(find.byType(AppBar));
BuildContext body2 = tester.element(find.byType(Scaffold));
BuildContext appBar2 = tester.element(find.byType(AppBar).first);
BuildContext body2 = tester.element(find.byType(Scaffold).first);
BuildContext navigationRail2 = tester.element(find.byType(NavigationRail));
ThemeData expectedTheme = ThemeData(colorSchemeSeed: Colors.blue);
expect(Theme.of(appBar2).primaryColor, expectedTheme.primaryColor);

@ -16,43 +16,54 @@ void main() {
'selected on NavigationBar', (tester) async {
widgetSetup(tester, 449);
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpWidget(const Material3Demo());
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
expect(find.text('Surface Tint only'), findsNothing);
expect(find.text('Surface Tint Color Only'), findsNothing);
expect(find.byType(NavigationBar), findsOneWidget);
Finder tintIconOnBar = find.byIcon(Icons.invert_colors_on_outlined);
Finder tintIconOnBar = find.descendant(
of: find.byType(NavigationBar),
matching: find.widgetWithIcon(
NavigationDestination, Icons.invert_colors_on_outlined));
expect(tintIconOnBar, findsOneWidget);
await tester.tap(tintIconOnBar);
await tester.pumpAndSettle(const Duration(microseconds: 500));
expect(tintIconOnBar, findsNothing);
expect(find.byIcon(Icons.opacity), findsOneWidget);
expect(find.text('Surface Tint only'), findsOneWidget);
Finder selectedTintIconOnBar = find.descendant(
of: find.byType(NavigationBar),
matching: find.widgetWithIcon(NavigationDestination, Icons.opacity));
expect(selectedTintIconOnBar, findsOneWidget);
expect(find.text('Surface Tint Color Only'), findsOneWidget);
});
testWidgets(
'Surface Tones screen shows correctly when the corresponding icon is '
'selected on NavigationRail', (tester) async {
widgetSetup(tester, 450); // NavigationRail shows only when width is >= 450.
widgetSetup(
tester, 1200); // NavigationRail shows only when width is > 1000.
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpWidget(const Material3Demo());
expect(find.text('Surface Tint only'), findsNothing);
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
expect(find.text('Surface Tint Color Only'), findsNothing);
expect(find.byType(NavigationRail), findsOneWidget);
Finder tintIconOnRail = find.byIcon(Icons.invert_colors_on_outlined);
Finder tintIconOnRail = find.descendant(
of: find.byType(NavigationRail),
matching: find.byIcon(Icons.invert_colors_on_outlined));
expect(tintIconOnRail, findsOneWidget);
await tester.tap(tintIconOnRail);
await tester.pumpAndSettle(const Duration(microseconds: 500));
expect(tintIconOnRail, findsNothing);
expect(find.byIcon(Icons.opacity), findsOneWidget);
expect(find.text('Surface Tint only'), findsOneWidget);
Finder selectedTintIconOnRail = find.descendant(
of: find.byType(NavigationRail), matching: find.byIcon(Icons.opacity));
expect(selectedTintIconOnRail, findsOneWidget);
expect(find.text('Surface Tint Color Only'), findsOneWidget);
});
testWidgets('Surface Tones screen shows correct content', (tester) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(body: Row(children: const [ElevationScreen()])),
));
expect(find.text('Surface Tint only'), findsOneWidget);
expect(find.text('Surface Tint and Shadow'), findsOneWidget);
expect(find.text('Shadow only'), findsOneWidget);
expect(find.text('Surface Tint Color Only'), findsOneWidget);
expect(find.text('Surface Tint Color and Shadow Color'), findsOneWidget);
expect(find.text('Shadow Color Only'), findsOneWidget);
expect(find.byType(ElevationGrid), findsNWidgets(3));
expect(find.byType(ElevationCard), findsNWidgets(18));
});

@ -16,33 +16,44 @@ void main() {
'selected on NavigationBar', (tester) async {
widgetSetup(tester, 449);
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpWidget(const Material3Demo());
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
expect(find.text('Display Large'), findsNothing);
expect(find.byType(NavigationBar), findsOneWidget);
Finder textIconOnBar = find.byIcon(Icons.text_snippet_outlined);
Finder textIconOnBar = find.descendant(
of: find.byType(NavigationBar),
matching: find.byIcon(Icons.text_snippet_outlined));
expect(textIconOnBar, findsOneWidget);
await tester.tap(textIconOnBar);
await tester.pumpAndSettle(const Duration(microseconds: 500));
expect(textIconOnBar, findsNothing);
expect(find.byIcon(Icons.text_snippet), findsOneWidget);
Finder selectedTextIconOnBar = find.descendant(
of: find.byType(NavigationBar),
matching: find.byIcon(Icons.text_snippet));
expect(selectedTextIconOnBar, findsOneWidget);
expect(find.text('Display Large'), findsOneWidget);
});
testWidgets(
'Typography screen shows correctly when the corresponding icon is '
'selected on NavigationRail', (tester) async {
widgetSetup(tester, 450); // NavigationRail shows only when width is >= 450.
widgetSetup(
tester, 1200); // NavigationRail shows only when width is > 1000.
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
await tester.pumpWidget(const Material3Demo());
await tester.pumpWidget(const MaterialApp(home: Material3Demo()));
expect(find.text('Display Large'), findsNothing);
expect(find.byType(NavigationRail), findsOneWidget);
Finder textIconOnRail = find.byIcon(Icons.text_snippet_outlined);
Finder textIconOnRail = find.descendant(
of: find.byType(NavigationRail),
matching: find.byIcon(Icons.text_snippet_outlined));
expect(textIconOnRail, findsOneWidget);
await tester.tap(textIconOnRail);
await tester.pumpAndSettle(const Duration(microseconds: 500));
expect(textIconOnRail, findsNothing);
expect(find.byIcon(Icons.text_snippet), findsOneWidget);
Finder selectedTextIconOnRail = find.descendant(
of: find.byType(NavigationRail),
matching: find.byIcon(Icons.text_snippet));
expect(selectedTextIconOnRail, findsOneWidget);
expect(find.text('Display Large'), findsOneWidget);
});

Loading…
Cancel
Save