1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-10 14:58:34 +00:00

New M3 Demo App for Master Channel (#1378)

* Added components

* Added test

* Added README

* Run flutter format

* Added all the updated components and tests

* Added README file

* Update README.md

* Add textfields to M3 demo app

* move textfield to exprerimental/

* Update component_screen_test.dart

* Fixed comment: changed sdk version

* Changed all double quotes to single

* Added trailing comma, changed all private fields to public except private state classes

* Added enum for screen selection

* Removed comment for material_3_demo

* Added experimental m3 demo app to master CI pipeline

* fixed failing tests

* Modified the analysis_options.yaml

* Fixed warning

* Ran flutter format command

Co-authored-by: Qun Cheng <quncheng@google.com>
Co-authored-by: hangyu <108393416+hangyujin@users.noreply.github.com>
This commit is contained in:
Qun Cheng
2022-08-18 05:38:13 -07:00
committed by GitHub
parent 0cb2bf64d7
commit bd00db0ecc
124 changed files with 5898 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
// Copyright 2021 The Flutter team. 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';
const Widget divider = SizedBox(height: 10);
// If screen content width is greater or equal to this value, the light and dark
// color schemes will be displayed in a column. Otherwise, they will
// be displayed in a row.
const double narrowScreenWidthThreshold = 400;
class ColorPalettesScreen extends StatelessWidget {
const ColorPalettesScreen({super.key});
@override
Widget build(BuildContext context) {
Color selectedColor = Theme.of(context).primaryColor;
ThemeData lightTheme = ThemeData(
colorSchemeSeed: selectedColor,
brightness: Brightness.light,
);
ThemeData darkTheme = ThemeData(
colorSchemeSeed: selectedColor,
brightness: Brightness.dark,
);
Widget schemeLabel(String brightness) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Text(
brightness,
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
}
Widget schemeView(ThemeData theme) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: ColorSchemeView(
colorScheme: theme.colorScheme,
),
);
}
return Expanded(
child: LayoutBuilder(builder: (context, constraints) {
if (constraints.maxWidth < narrowScreenWidthThreshold) {
return SingleChildScrollView(
child: Column(
children: [
divider,
schemeLabel('Light Theme'),
schemeView(lightTheme),
divider,
divider,
schemeLabel('Dark Theme'),
schemeView(darkTheme),
],
),
);
} else {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(top: 5),
child: Row(
children: [
Expanded(
child: Column(
children: [
schemeLabel('Light Theme'),
schemeView(lightTheme),
],
),
),
Expanded(
child: Column(
children: [
schemeLabel('Dark Theme'),
schemeView(darkTheme),
],
),
),
],
),
),
);
}
}),
);
}
}
class ColorSchemeView extends StatelessWidget {
const ColorSchemeView({super.key, required this.colorScheme});
final ColorScheme colorScheme;
@override
Widget build(BuildContext context) {
return Column(
children: [
ColorGroup(children: [
ColorChip(
label: 'primary',
color: colorScheme.primary,
onColor: colorScheme.onPrimary,
),
ColorChip(
label: 'onPrimary',
color: colorScheme.onPrimary,
onColor: colorScheme.primary),
ColorChip(
label: 'primaryContainer',
color: colorScheme.primaryContainer,
onColor: colorScheme.onPrimaryContainer,
),
ColorChip(
label: 'onPrimaryContainer',
color: colorScheme.onPrimaryContainer,
onColor: colorScheme.primaryContainer,
),
]),
divider,
ColorGroup(children: [
ColorChip(
label: 'secondary',
color: colorScheme.secondary,
onColor: colorScheme.onSecondary,
),
ColorChip(
label: 'onSecondary',
color: colorScheme.onSecondary,
onColor: colorScheme.secondary,
),
ColorChip(
label: 'secondaryContainer',
color: colorScheme.secondaryContainer,
onColor: colorScheme.onSecondaryContainer,
),
ColorChip(
label: 'onSecondaryContainer',
color: colorScheme.onSecondaryContainer,
onColor: colorScheme.secondaryContainer),
]),
divider,
ColorGroup(
children: [
ColorChip(
label: 'tertiary',
color: colorScheme.tertiary,
onColor: colorScheme.onTertiary),
ColorChip(
label: 'onTertiary',
color: colorScheme.onTertiary,
onColor: colorScheme.tertiary),
ColorChip(
label: 'tertiaryContainer',
color: colorScheme.tertiaryContainer,
onColor: colorScheme.onTertiaryContainer),
ColorChip(
label: 'onTertiaryContainer',
color: colorScheme.onTertiaryContainer,
onColor: colorScheme.tertiaryContainer),
],
),
divider,
ColorGroup(
children: [
ColorChip(
label: 'error',
color: colorScheme.error,
onColor: colorScheme.onError),
ColorChip(
label: 'onError',
color: colorScheme.onError,
onColor: colorScheme.error),
ColorChip(
label: 'errorContainer',
color: colorScheme.errorContainer,
onColor: colorScheme.onErrorContainer),
ColorChip(
label: 'onErrorContainer',
color: colorScheme.onErrorContainer,
onColor: colorScheme.errorContainer),
],
),
divider,
ColorGroup(
children: [
ColorChip(
label: 'background',
color: colorScheme.background,
onColor: colorScheme.onBackground),
ColorChip(
label: 'onBackground',
color: colorScheme.onBackground,
onColor: colorScheme.background),
],
),
divider,
ColorGroup(
children: [
ColorChip(
label: 'surface',
color: colorScheme.surface,
onColor: colorScheme.onSurface),
ColorChip(
label: 'onSurface',
color: colorScheme.onSurface,
onColor: colorScheme.surface),
ColorChip(
label: 'surfaceVariant',
color: colorScheme.surfaceVariant,
onColor: colorScheme.onSurfaceVariant),
ColorChip(
label: 'onSurfaceVariant',
color: colorScheme.onSurfaceVariant,
onColor: colorScheme.surfaceVariant),
],
),
divider,
ColorGroup(
children: [
ColorChip(label: 'outline', color: colorScheme.outline),
ColorChip(label: 'shadow', color: colorScheme.shadow),
ColorChip(
label: 'inverseSurface',
color: colorScheme.inverseSurface,
onColor: colorScheme.onInverseSurface),
ColorChip(
label: 'onInverseSurface',
color: colorScheme.onInverseSurface,
onColor: colorScheme.inverseSurface),
ColorChip(
label: 'inversePrimary',
color: colorScheme.inversePrimary,
onColor: colorScheme.primary),
],
),
],
);
}
}
class ColorGroup extends StatelessWidget {
const ColorGroup({super.key, required this.children});
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
children: children,
),
);
}
}
class ColorChip extends StatelessWidget {
const ColorChip({
super.key,
required this.color,
required this.label,
this.onColor,
});
final Color color;
final Color? onColor;
final String label;
static Color contrastColor(Color color) {
final brightness = ThemeData.estimateBrightnessForColor(color);
switch (brightness) {
case Brightness.dark:
return Colors.white;
case Brightness.light:
return Colors.black;
}
}
@override
Widget build(BuildContext context) {
final Color labelColor = onColor ?? contrastColor(color);
return Container(
color: color,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(child: Text(label, style: TextStyle(color: labelColor))),
],
),
),
);
}
}

View File

@@ -0,0 +1,947 @@
// Copyright 2021 The Flutter team. 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';
class ComponentScreen extends StatelessWidget {
const ComponentScreen({super.key, required this.showNavBottomBar});
final bool showNavBottomBar;
@override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Align(
alignment: Alignment.topCenter,
child: SizedBox(
width: maxWidthConstraint,
child: ListView(
shrinkWrap: true,
children: [
colDivider,
colDivider,
const Buttons(),
colDivider,
colDivider,
const IconToggleButtons(),
colDivider,
const FloatingActionButtons(),
colDivider,
const Chips(),
colDivider,
const Cards(),
colDivider,
const TextFields(),
colDivider,
const Dialogs(),
colDivider,
showNavBottomBar
? const NavigationBars(
selectedIndex: 0,
isExampleBar: true,
)
: Container(),
],
),
),
),
),
);
}
}
const rowDivider = SizedBox(width: 10);
const colDivider = SizedBox(height: 10);
const double cardWidth = 115;
const double maxWidthConstraint = 400;
void Function()? handlePressed(
BuildContext context, bool isDisabled, String buttonName) {
return isDisabled
? null
: () {
final snackBar = SnackBar(
content: Text(
'Yay! $buttonName is clicked!',
style: TextStyle(color: Theme.of(context).colorScheme.surface),
),
action: SnackBarAction(
textColor: Theme.of(context).colorScheme.surface,
label: 'Close',
onPressed: () {},
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
};
}
class Buttons extends StatefulWidget {
const Buttons({super.key});
@override
State<Buttons> createState() => _ButtonsState();
}
class _ButtonsState extends State<Buttons> {
@override
Widget build(BuildContext context) {
return Wrap(
alignment: WrapAlignment.spaceEvenly,
children: const <Widget>[
ButtonsWithoutIcon(isDisabled: false),
rowDivider,
ButtonsWithIcon(),
rowDivider,
ButtonsWithoutIcon(isDisabled: true),
],
);
}
}
class ButtonsWithoutIcon extends StatelessWidget {
final bool isDisabled;
const ButtonsWithoutIcon({super.key, required this.isDisabled});
@override
Widget build(BuildContext context) {
return IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton(
onPressed: handlePressed(context, isDisabled, 'ElevatedButton'),
child: const Text('Elevated'),
),
colDivider,
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
onPressed: handlePressed(context, isDisabled, 'FilledButton'),
child: const Text('Filled'),
),
colDivider,
ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.onSecondaryContainer,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
onPressed: handlePressed(context, isDisabled, 'FilledTonalButton'),
child: const Text('Filled Tonal'),
),
colDivider,
OutlinedButton(
onPressed: handlePressed(context, isDisabled, 'OutlinedButton'),
child: const Text('Outlined'),
),
colDivider,
TextButton(
onPressed: handlePressed(context, isDisabled, 'TextButton'),
child: const Text('Text')),
],
),
);
}
}
class ButtonsWithIcon extends StatelessWidget {
const ButtonsWithIcon({super.key});
@override
Widget build(BuildContext context) {
return IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton.icon(
onPressed:
handlePressed(context, false, 'ElevatedButton with Icon'),
icon: const Icon(Icons.add),
label: const Text('Icon'),
),
colDivider,
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
onPressed: handlePressed(context, false, 'FilledButton with Icon'),
label: const Text('Icon'),
icon: const Icon(Icons.add),
),
colDivider,
ElevatedButton.icon(
style: ElevatedButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.onSecondaryContainer,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
onPressed:
handlePressed(context, false, 'FilledTonalButton with Icon'),
label: const Text('Icon'),
icon: const Icon(Icons.add),
),
colDivider,
OutlinedButton.icon(
onPressed:
handlePressed(context, false, 'OutlinedButton with Icon'),
icon: const Icon(Icons.add),
label: const Text('Icon'),
),
colDivider,
TextButton.icon(
onPressed: handlePressed(context, false, 'TextButton with Icon'),
icon: const Icon(Icons.add),
label: const Text('Icon'),
)
],
),
);
}
}
class FloatingActionButtons extends StatelessWidget {
const FloatingActionButtons({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
FloatingActionButton.small(
onPressed: () {},
child: const Icon(Icons.add),
),
rowDivider,
FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
rowDivider,
FloatingActionButton.extended(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('Create'),
),
rowDivider,
FloatingActionButton.large(
onPressed: () {},
child: const Icon(Icons.add),
),
],
),
);
}
}
class Cards extends StatelessWidget {
const Cards({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: [
SizedBox(
width: cardWidth,
child: Tooltip(
margin: const EdgeInsets.only(top: 20),
message: 'Elevated Card',
child: Card(
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
children: const [
Align(
alignment: Alignment.topRight,
child: Icon(Icons.more_vert),
),
SizedBox(height: 35),
Align(
alignment: Alignment.bottomLeft,
child: Text('Elevated'),
)
],
),
),
),
),
),
SizedBox(
width: cardWidth,
child: Tooltip(
margin: const EdgeInsets.only(top: 20),
message: 'Filled Card',
child: Card(
color: Theme.of(context).colorScheme.surfaceVariant,
elevation: 0,
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
children: const [
Align(
alignment: Alignment.topRight,
child: Icon(Icons.more_vert),
),
SizedBox(height: 35),
Align(
alignment: Alignment.bottomLeft,
child: Text('Filled'),
)
],
),
),
),
),
),
SizedBox(
width: cardWidth,
child: Tooltip(
margin: const EdgeInsets.only(top: 20),
message: 'Outlined Card',
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.outline,
),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
children: const [
Align(
alignment: Alignment.topRight,
child: Icon(Icons.more_vert),
),
SizedBox(height: 35),
Align(
alignment: Alignment.bottomLeft,
child: Text('Outlined'),
)
],
),
),
),
),
),
],
),
);
}
}
class TextFields extends StatelessWidget {
const TextFields({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(10),
child: TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: Icon(Icons.clear),
labelText: 'Filled',
hintText: 'hint text',
helperText: 'supporting text',
filled: true,
),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
SizedBox(
width: 170,
child: TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: Icon(Icons.clear),
labelText: 'Filled',
hintText: 'hint text',
helperText: 'supporting text',
filled: true,
errorText: 'error text',
),
),
),
SizedBox(
width: 170,
child: TextField(
enabled: false,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: Icon(Icons.clear),
labelText: 'Disabled',
hintText: 'hint text',
helperText: 'supporting text',
filled: true,
),
),
),
],
),
),
const Padding(
padding: EdgeInsets.all(10),
child: TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: Icon(Icons.clear),
labelText: 'Outlined',
hintText: 'hint text',
helperText: 'supporting text',
border: OutlineInputBorder(),
),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
SizedBox(
width: 170,
child: TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: Icon(Icons.clear),
labelText: 'Outlined',
hintText: 'hint text',
helperText: 'supporting text',
errorText: 'error text',
border: OutlineInputBorder(),
filled: true,
),
),
),
SizedBox(
width: 170,
child: TextField(
enabled: false,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
suffixIcon: Icon(Icons.clear),
labelText: 'Disabled',
hintText: 'hint text',
helperText: 'supporting text',
border: OutlineInputBorder(),
filled: true,
),
),
),
])),
],
);
}
}
class Dialogs extends StatefulWidget {
const Dialogs({super.key});
@override
State<Dialogs> createState() => _DialogsState();
}
class _DialogsState extends State<Dialogs> {
void openDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Basic Dialog Title'),
content: const Text(
'A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made.'),
actions: <Widget>[
TextButton(
child: const Text('Dismiss'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text('Action'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextButton(
child: const Text(
'Open Dialog',
style: TextStyle(fontWeight: FontWeight.bold),
),
onPressed: () => openDialog(context),
),
);
}
}
const List<NavigationDestination> appBarDestinations = [
NavigationDestination(
tooltip: 'Updated component list',
icon: Icon(Icons.widgets_outlined),
label: 'Components',
selectedIcon: Icon(Icons.widgets),
),
NavigationDestination(
tooltip: 'Color palettes in light and dark modes',
icon: Icon(Icons.format_paint_outlined),
label: 'Color',
selectedIcon: Icon(Icons.format_paint),
),
NavigationDestination(
tooltip: 'Different text styles for the default TextTheme',
icon: Icon(Icons.text_snippet_outlined),
label: 'Typography',
selectedIcon: Icon(Icons.text_snippet),
),
NavigationDestination(
tooltip:
'Different ways of elevation with a new supported feature "surfaceTintColor"',
icon: Icon(Icons.invert_colors_on_outlined),
label: 'Elevation',
selectedIcon: Icon(Icons.opacity),
)
];
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();
const List<Widget> exampleBarDestinations = [
NavigationDestination(
tooltip: '',
icon: Icon(Icons.explore_outlined),
label: 'Explore',
selectedIcon: Icon(Icons.explore),
),
NavigationDestination(
tooltip: '',
icon: Icon(Icons.pets_outlined),
label: 'Pets',
selectedIcon: Icon(Icons.pets),
),
NavigationDestination(
tooltip: '',
icon: Icon(Icons.account_box_outlined),
label: 'Account',
selectedIcon: Icon(Icons.account_box),
)
];
class NavigationBars extends StatefulWidget {
final void Function(int)? onSelectItem;
final int selectedIndex;
final bool isExampleBar;
const NavigationBars({
super.key,
this.onSelectItem,
required this.selectedIndex,
required this.isExampleBar,
});
@override
State<NavigationBars> createState() => _NavigationBarsState();
}
class _NavigationBarsState extends State<NavigationBars> {
int selectedIndex = 0;
@override
void initState() {
super.initState();
selectedIndex = widget.selectedIndex;
}
@override
Widget build(BuildContext context) {
return NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: (index) {
setState(() {
selectedIndex = index;
});
if (!widget.isExampleBar) widget.onSelectItem!(index);
},
destinations:
widget.isExampleBar ? exampleBarDestinations : appBarDestinations,
);
}
}
class NavigationRailSection extends StatefulWidget {
final void Function(int) onSelectItem;
final int selectedIndex;
const NavigationRailSection(
{super.key, required this.onSelectItem, required this.selectedIndex});
@override
State<NavigationRailSection> createState() => _NavigationRailSectionState();
}
class _NavigationRailSectionState extends State<NavigationRailSection> {
int selectedIndex = 0;
@override
void initState() {
super.initState();
selectedIndex = widget.selectedIndex;
}
@override
Widget build(BuildContext context) {
return NavigationRail(
minWidth: 50,
destinations: navRailDestinations,
selectedIndex: selectedIndex,
useIndicator: true,
onDestinationSelected: (index) {
setState(() {
selectedIndex = index;
});
widget.onSelectItem(index);
},
);
}
}
class IconToggleButtons extends StatefulWidget {
const IconToggleButtons({super.key});
@override
State<IconToggleButtons> createState() => _IconToggleButtonsState();
}
class _IconToggleButtonsState extends State<IconToggleButtons> {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
// Standard IconButton
children: const <Widget>[
IconToggleButton(isEnabled: true),
colDivider,
IconToggleButton(isEnabled: false),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
// Filled IconButton
IconToggleButton(
isEnabled: true,
getDefaultStyle: enabledFilledButtonStyle,
),
colDivider,
IconToggleButton(
isEnabled: false,
getDefaultStyle: disabledFilledButtonStyle,
),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
// Filled Tonal IconButton
IconToggleButton(
isEnabled: true,
getDefaultStyle: enabledFilledTonalButtonStyle,
),
colDivider,
IconToggleButton(
isEnabled: false,
getDefaultStyle: disabledFilledTonalButtonStyle,
),
],
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
// Outlined IconButton
IconToggleButton(
isEnabled: true,
getDefaultStyle: enabledOutlinedButtonStyle,
),
colDivider,
IconToggleButton(
isEnabled: false,
getDefaultStyle: disabledOutlinedButtonStyle,
),
],
),
],
),
);
}
}
class IconToggleButton extends StatefulWidget {
const IconToggleButton({
required this.isEnabled,
this.getDefaultStyle,
super.key,
});
final bool isEnabled;
final ButtonStyle? Function(bool, ColorScheme)? getDefaultStyle;
@override
State<IconToggleButton> createState() => _IconToggleButtonState();
}
class _IconToggleButtonState extends State<IconToggleButton> {
bool selected = false;
@override
Widget build(BuildContext context) {
final ColorScheme colors = Theme.of(context).colorScheme;
final VoidCallback? onPressed = widget.isEnabled
? () {
setState(() {
selected = !selected;
});
}
: null;
ButtonStyle? style = widget.getDefaultStyle?.call(selected, colors);
return IconButton(
visualDensity: VisualDensity.standard,
isSelected: selected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: onPressed,
style: style,
);
}
}
ButtonStyle enabledFilledButtonStyle(bool selected, ColorScheme colors) {
return IconButton.styleFrom(
foregroundColor: selected ? colors.onPrimary : colors.primary,
backgroundColor: selected ? colors.primary : colors.surfaceVariant,
disabledForegroundColor: colors.onSurface.withOpacity(0.38),
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
hoverColor: selected
? colors.onPrimary.withOpacity(0.08)
: colors.primary.withOpacity(0.08),
focusColor: selected
? colors.onPrimary.withOpacity(0.12)
: colors.primary.withOpacity(0.12),
highlightColor: selected
? colors.onPrimary.withOpacity(0.12)
: colors.primary.withOpacity(0.12),
);
}
ButtonStyle disabledFilledButtonStyle(bool selected, ColorScheme colors) {
return IconButton.styleFrom(
disabledForegroundColor: colors.onSurface.withOpacity(0.38),
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
);
}
ButtonStyle enabledFilledTonalButtonStyle(bool selected, ColorScheme colors) {
return IconButton.styleFrom(
foregroundColor:
selected ? colors.onSecondaryContainer : colors.onSurfaceVariant,
backgroundColor:
selected ? colors.secondaryContainer : colors.surfaceVariant,
hoverColor: selected
? colors.onSecondaryContainer.withOpacity(0.08)
: colors.onSurfaceVariant.withOpacity(0.08),
focusColor: selected
? colors.onSecondaryContainer.withOpacity(0.12)
: colors.onSurfaceVariant.withOpacity(0.12),
highlightColor: selected
? colors.onSecondaryContainer.withOpacity(0.12)
: colors.onSurfaceVariant.withOpacity(0.12),
);
}
ButtonStyle disabledFilledTonalButtonStyle(bool selected, ColorScheme colors) {
return IconButton.styleFrom(
disabledForegroundColor: colors.onSurface.withOpacity(0.38),
disabledBackgroundColor: colors.onSurface.withOpacity(0.12),
);
}
ButtonStyle enabledOutlinedButtonStyle(bool selected, ColorScheme colors) {
return IconButton.styleFrom(
backgroundColor: selected ? colors.inverseSurface : null,
hoverColor: selected
? colors.onInverseSurface.withOpacity(0.08)
: colors.onSurfaceVariant.withOpacity(0.08),
focusColor: selected
? colors.onInverseSurface.withOpacity(0.12)
: colors.onSurfaceVariant.withOpacity(0.12),
highlightColor: selected
? colors.onInverseSurface.withOpacity(0.12)
: colors.onSurface.withOpacity(0.12),
side: BorderSide(color: colors.outline),
).copyWith(
foregroundColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.selected)) {
return colors.onInverseSurface;
}
if (states.contains(MaterialState.pressed)) {
return colors.onSurface;
}
return null;
}),
);
}
ButtonStyle disabledOutlinedButtonStyle(bool selected, ColorScheme colors) {
return IconButton.styleFrom(
disabledForegroundColor: colors.onSurface.withOpacity(0.38),
disabledBackgroundColor:
selected ? colors.onSurface.withOpacity(0.12) : null,
side: selected ? null : BorderSide(color: colors.outline.withOpacity(0.12)),
);
}
class Chips extends StatelessWidget {
const Chips({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Wrap(
alignment: WrapAlignment.spaceBetween,
runSpacing: 5,
children: <Widget>[
ActionChip(
label: const Text('Assist'),
avatar: const Icon(Icons.chat_outlined),
onPressed: () {}),
ActionChip(
label: const Text('Set alarm'),
avatar: const Icon(Icons.alarm_add_outlined),
onPressed: () {}),
const ActionChip(
label: Text('No Action'),
avatar: Icon(Icons.indeterminate_check_box_outlined),
),
],
),
colDivider,
Wrap(
alignment: WrapAlignment.spaceBetween,
runSpacing: 5,
children: <Widget>[
FilterChip(
label: const Text('Filter'),
onSelected: (isSelected) {},
),
FilterChip(
label: const Text('OK'),
selected: true,
onSelected: (isSelected) {},
),
const FilterChip(
label: Text('Disabled'),
selected: true,
onSelected: null,
),
const FilterChip(
label: Text('Disabled'),
onSelected: null,
)
],
),
colDivider,
Wrap(
alignment: WrapAlignment.spaceBetween,
runSpacing: 5,
children: <Widget>[
InputChip(
label: const Text('Input'),
onDeleted: () {},
),
InputChip(
label: const Text('Egg'),
onDeleted: () {},
),
InputChip(
label: const Text('Lettuce'),
showCheckmark: false,
selected: true,
onDeleted: () {},
),
const InputChip(
label: Text('No'),
isEnabled: false,
),
],
),
colDivider,
Wrap(
alignment: WrapAlignment.spaceBetween,
runSpacing: 5,
children: <Widget>[
ActionChip(
label: const Text('Suggestion'),
onPressed: () {},
),
ActionChip(
label: const Text('I agree'),
onPressed: () {},
),
ActionChip(
label: const Text('LGTM'),
onPressed: () {},
),
const ActionChip(label: Text('Nope')),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,177 @@
// Copyright 2021 The Flutter team. 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';
class ElevationScreen extends StatelessWidget {
const ElevationScreen({super.key});
@override
Widget build(BuildContext context) {
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,
),
),
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,
),
),
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,
),
),
ElevationGrid(shadowColor: shadowColor),
],
),
);
}
}
const double narrowScreenWidthThreshold = 450;
class ElevationGrid extends StatelessWidget {
const ElevationGrid({super.key, this.shadowColor, this.surfaceTintColor});
final Color? shadowColor;
final Color? surfaceTintColor;
List<ElevationCard> elevationCards(
Color? shadowColor, Color? surfaceTintColor) {
return elevations
.map(
(elevationInfo) => ElevationCard(
info: elevationInfo,
shadowColor: shadowColor,
surfaceTint: surfaceTintColor,
),
)
.toList();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: LayoutBuilder(builder: (context, constraints) {
if (constraints.maxWidth < narrowScreenWidthThreshold) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 3,
children: elevationCards(shadowColor, surfaceTintColor),
);
} else {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 6,
children: elevationCards(shadowColor, surfaceTintColor),
);
}
}),
);
}
}
class ElevationCard extends StatefulWidget {
const ElevationCard(
{super.key, required this.info, this.shadowColor, this.surfaceTint});
final ElevationInfo info;
final Color? shadowColor;
final Color? surfaceTint;
@override
State<ElevationCard> createState() => _ElevationCardState();
}
class _ElevationCardState extends State<ElevationCard> {
late double _elevation;
@override
void initState() {
super.initState();
_elevation = widget.info.elevation;
}
@override
Widget build(BuildContext context) {
const BorderRadius borderRadius = BorderRadius.all(Radius.circular(4.0));
final bool showOpacity = _elevation == widget.info.elevation;
final Color color = Theme.of(context).colorScheme.surface;
return Padding(
padding: const EdgeInsets.all(8.0),
child: Material(
borderRadius: borderRadius,
elevation: _elevation,
color: color,
shadowColor: widget.shadowColor,
surfaceTintColor: widget.surfaceTint,
type: MaterialType.card,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Level ${widget.info.level}',
style: Theme.of(context).textTheme.labelMedium,
),
Text(
'${widget.info.level.toInt()} dp',
style: Theme.of(context).textTheme.labelMedium,
),
if (showOpacity)
Expanded(
child: Align(
alignment: Alignment.bottomRight,
child: Text(
'${widget.info.overlayPercent}%',
style: Theme.of(context).textTheme.bodySmall,
),
),
),
],
),
),
),
);
}
}
class ElevationInfo {
const ElevationInfo(this.level, this.elevation, this.overlayPercent);
final int level;
final double elevation;
final int overlayPercent;
}
const List<ElevationInfo> elevations = <ElevationInfo>[
ElevationInfo(0, 0.0, 0),
ElevationInfo(1, 1.0, 5),
ElevationInfo(2, 3.0, 8),
ElevationInfo(3, 6.0, 11),
ElevationInfo(4, 8.0, 12),
ElevationInfo(5, 12.0, 14),
];

View File

@@ -0,0 +1,215 @@
// Copyright 2021 The Flutter team. 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 'color_palettes_screen.dart';
import 'component_screen.dart';
import 'elevation_screen.dart';
import 'typography_screen.dart';
void main() {
runApp(const Material3Demo());
}
class Material3Demo extends StatefulWidget {
const Material3Demo({super.key});
@override
State<Material3Demo> createState() => _Material3DemoState();
}
// NavigationRail shows if the screen width is greater or equal to
// 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',
];
enum ScreenSelected {
component(0),
color(1),
typography(2),
elevation(3);
const ScreenSelected(this.value);
final int value;
}
class _Material3DemoState extends State<Material3Demo> {
bool useMaterial3 = true;
bool useLightMode = true;
int colorSelected = 0;
int screenIndex = ScreenSelected.component.value;
late ThemeData themeData;
@override
initState() {
super.initState();
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
}
ThemeData updateThemes(int colorIndex, bool useMaterial3, bool useLightMode) {
return ThemeData(
colorSchemeSeed: colorOptions[colorSelected],
useMaterial3: useMaterial3,
brightness: useLightMode ? Brightness.light : Brightness.dark);
}
void handleScreenChanged(int screenSelected) {
setState(() {
screenIndex = screenSelected;
});
}
void handleBrightnessChange() {
setState(() {
useLightMode = !useLightMode;
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
});
}
void handleMaterialVersionChange() {
setState(() {
useMaterial3 = !useMaterial3;
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
});
}
void handleColorSelect(int value) {
setState(() {
colorSelected = value;
themeData = updateThemes(colorSelected, useMaterial3, useLightMode);
});
}
Widget createScreenFor(
ScreenSelected screenSelected, bool showNavBarExample) {
switch (screenSelected) {
case ScreenSelected.component:
return ComponentScreen(showNavBottomBar: showNavBarExample);
case ScreenSelected.color:
return const ColorPalettesScreen();
case ScreenSelected.typography:
return const TypographyScreen();
case ScreenSelected.elevation:
return const ElevationScreen();
default:
return ComponentScreen(showNavBottomBar: showNavBarExample);
}
}
PreferredSizeWidget createAppBar() {
return AppBar(
title: useMaterial3 ? const Text('Material 3') : const Text('Material 2'),
actions: [
IconButton(
icon: useLightMode
? const Icon(Icons.wb_sunny_outlined)
: const Icon(Icons.wb_sunny),
onPressed: handleBrightnessChange,
tooltip: 'Toggle brightness',
),
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]),
),
],
),
);
});
},
onSelected: handleColorSelect,
),
],
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
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),
],
),
),
);
}
}),
);
}
}

View File

@@ -0,0 +1,63 @@
// Copyright 2021 The Flutter team. 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';
class TypographyScreen extends StatelessWidget {
const TypographyScreen({super.key});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context)
.textTheme
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
return Expanded(
child: ListView(
children: <Widget>[
const SizedBox(height: 7),
TextStyleExample(
name: 'Display Large', style: textTheme.displayLarge!),
TextStyleExample(
name: 'Display Medium', style: textTheme.displayMedium!),
TextStyleExample(
name: 'Display Small', style: textTheme.displaySmall!),
TextStyleExample(
name: 'Headline Large', style: textTheme.headlineLarge!),
TextStyleExample(
name: 'Headline Medium', style: textTheme.headlineMedium!),
TextStyleExample(
name: 'Headline Small', style: textTheme.headlineSmall!),
TextStyleExample(name: 'Title Large', style: textTheme.titleLarge!),
TextStyleExample(name: 'Title Medium', style: textTheme.titleMedium!),
TextStyleExample(name: 'Title Small', style: textTheme.titleSmall!),
TextStyleExample(name: 'Label Large', style: textTheme.labelLarge!),
TextStyleExample(name: 'Label Medium', style: textTheme.labelMedium!),
TextStyleExample(name: 'Label Small', style: textTheme.labelSmall!),
TextStyleExample(name: 'Body Large', style: textTheme.bodyLarge!),
TextStyleExample(name: 'Body Medium', style: textTheme.bodyMedium!),
TextStyleExample(name: 'Body Small', style: textTheme.bodySmall!),
],
),
);
}
}
class TextStyleExample extends StatelessWidget {
const TextStyleExample({
super.key,
required this.name,
required this.style,
});
final String name;
final TextStyle style;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(name, style: style),
);
}
}