1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-08 13:58:47 +00:00

Update for Flutter 3.10 beta (#1746)

## Pre-launch Checklist

- [x] I read the [Flutter Style Guide] _recently_, and have followed its
advice.
- [x] I signed the [CLA].
- [x] I read the [Contributors Guide].
- [x] I updated/added relevant documentation (doc comments with `///`).
- [ ] All existing and new tests are passing.

---------

Co-authored-by: David Iglesias <ditman@gmail.com>
Co-authored-by: Mark Thompson <2554588+MarkTechson@users.noreply.github.com>
Co-authored-by: John Ryan <ryjohn@google.com>
This commit is contained in:
Brett Morgan
2023-05-11 06:16:31 +10:00
committed by GitHub
parent 474756ce04
commit 36e7a6ab04
188 changed files with 1779 additions and 1968 deletions

View File

@@ -140,8 +140,8 @@ class Navigation extends StatelessWidget {
),
NavigationDrawers(scaffoldKey: scaffoldKey),
const NavigationRails(),
// TODO: Add Search https://github.com/flutter/flutter/issues/117483
const Tabs(),
const SearchAnchors(),
const TopAppBars(),
]);
}
@@ -155,12 +155,12 @@ class Selection extends StatelessWidget {
return const ComponentGroupDecoration(label: 'Selection', children: [
Checkboxes(),
Chips(),
// TODO: Add Date pickers https://github.com/flutter/flutter/issues/101481
DatePickers(),
Menus(),
Radios(),
Sliders(),
Switches(),
// TODO: Add Time pickers https://github.com/flutter/flutter/issues/101480
TimePickers(),
]);
}
}
@@ -187,7 +187,7 @@ class Buttons extends StatefulWidget {
class _ButtonsState extends State<Buttons> {
@override
Widget build(BuildContext context) {
return ComponentDecoration(
return const ComponentDecoration(
label: 'Common buttons',
tooltipMessage:
'Use ElevatedButton, FilledButton, FilledButton.tonal, OutlinedButton, or TextButton',
@@ -195,7 +195,7 @@ class _ButtonsState extends State<Buttons> {
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: const <Widget>[
children: <Widget>[
ButtonsWithoutIcon(isDisabled: false),
ButtonsWithIcon(),
ButtonsWithoutIcon(isDisabled: true),
@@ -681,11 +681,11 @@ class Dividers extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ComponentDecoration(
return const ComponentDecoration(
label: 'Dividers',
tooltipMessage: 'Use Divider or VerticalDivider',
child: Column(
children: const <Widget>[
children: <Widget>[
Divider(key: Key('divider')),
],
),
@@ -698,11 +698,11 @@ class Switches extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ComponentDecoration(
return const ComponentDecoration(
label: 'Switches',
tooltipMessage: 'Use SwitchListTile or Switch',
child: Column(
children: const <Widget>[
children: <Widget>[
SwitchRow(isEnabled: true),
SwitchRow(isEnabled: false),
],
@@ -1091,73 +1091,105 @@ class IconToggleButtons extends StatefulWidget {
}
class _IconToggleButtonsState extends State<IconToggleButtons> {
bool standardSelected = false;
bool filledSelected = false;
bool tonalSelected = false;
bool outlinedSelected = false;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Icon buttons',
tooltipMessage: 'Use IconButton',
tooltipMessage:
'Use IconButton, IconButton.filled, IconButton.filledTonal, and IconButton.outlined',
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
// Standard IconButton
children: const <Widget>[
IconToggleButton(
isEnabled: true,
tooltip: 'Standard',
children: <Widget>[
IconButton(
isSelected: standardSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: () {
setState(() {
standardSelected = !standardSelected;
});
},
),
colDivider,
IconToggleButton(
isEnabled: false,
tooltip: 'Standard (disabled)',
IconButton(
isSelected: standardSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: null,
),
],
),
Column(
children: const <Widget>[
children: <Widget>[
// Filled IconButton
IconToggleButton(
isEnabled: true,
tooltip: 'Filled',
getDefaultStyle: enabledFilledButtonStyle,
IconButton.filled(
isSelected: filledSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: () {
setState(() {
filledSelected = !filledSelected;
});
},
),
colDivider,
IconToggleButton(
isEnabled: false,
tooltip: 'Filled (disabled)',
getDefaultStyle: disabledFilledButtonStyle,
IconButton.filled(
isSelected: filledSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: null,
),
],
),
Column(
children: const <Widget>[
children: <Widget>[
// Filled Tonal IconButton
IconToggleButton(
isEnabled: true,
tooltip: 'Filled tonal',
getDefaultStyle: enabledFilledTonalButtonStyle,
IconButton.filledTonal(
isSelected: tonalSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: () {
setState(() {
tonalSelected = !tonalSelected;
});
},
),
colDivider,
IconToggleButton(
isEnabled: false,
tooltip: 'Filled tonal (disabled)',
getDefaultStyle: disabledFilledTonalButtonStyle,
IconButton.filledTonal(
isSelected: tonalSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: null,
),
],
),
Column(
children: const <Widget>[
children: <Widget>[
// Outlined IconButton
IconToggleButton(
isEnabled: true,
tooltip: 'Outlined',
getDefaultStyle: enabledOutlinedButtonStyle,
IconButton.outlined(
isSelected: outlinedSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: () {
setState(() {
outlinedSelected = !outlinedSelected;
});
},
),
colDivider,
IconToggleButton(
isEnabled: false,
tooltip: 'Outlined (disabled)',
getDefaultStyle: disabledOutlinedButtonStyle,
IconButton.outlined(
isSelected: outlinedSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: null,
),
],
),
@@ -1167,134 +1199,6 @@ class _IconToggleButtonsState extends State<IconToggleButtons> {
}
}
class IconToggleButton extends StatefulWidget {
const IconToggleButton({
required this.isEnabled,
required this.tooltip,
this.getDefaultStyle,
super.key,
});
final bool isEnabled;
final String tooltip;
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,
tooltip: widget.tooltip,
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 StatefulWidget {
const Chips({super.key});
@@ -1371,16 +1275,97 @@ class _ChipsState extends State<Chips> {
}
}
class DatePickers extends StatefulWidget {
const DatePickers({super.key});
@override
State<DatePickers> createState() => _DatePickersState();
}
class _DatePickersState extends State<DatePickers> {
DateTime? selectedDate;
final DateTime _firstDate = DateTime(DateTime.now().year - 2);
final DateTime _lastDate = DateTime(DateTime.now().year + 1);
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Date picker',
tooltipMessage: 'Use showDatePicker',
child: TextButton(
onPressed: () async {
DateTime? date = await showDatePicker(
context: context,
initialDate: selectedDate ?? DateTime.now(),
firstDate: _firstDate,
lastDate: _lastDate,
);
setState(() {
selectedDate = date;
if (selectedDate != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'Selected Date: ${selectedDate!.day}/${selectedDate!.month}/${selectedDate!.year}'),
));
}
});
},
child: const Text(
'Show date picker',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
);
}
}
class TimePickers extends StatefulWidget {
const TimePickers({super.key});
@override
State<TimePickers> createState() => _TimePickersState();
}
class _TimePickersState extends State<TimePickers> {
TimeOfDay? selectedTime;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Time picker',
tooltipMessage: 'Use showTimePicker',
child: TextButton(
onPressed: () async {
final TimeOfDay? time = await showTimePicker(
context: context,
initialTime: selectedTime ?? TimeOfDay.now(),
);
setState(() {
selectedTime = time;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Selected time: ${selectedTime!.format(context)}'),
));
});
},
child: const Text(
'Show time picker',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
);
}
}
class SegmentedButtons extends StatelessWidget {
const SegmentedButtons({super.key});
@override
Widget build(BuildContext context) {
return ComponentDecoration(
return const ComponentDecoration(
label: 'Segmented buttons',
tooltipMessage: 'Use SegmentedButton<T>',
child: Column(
children: const <Widget>[
children: <Widget>[
SingleChoice(),
colDivider,
MultipleChoice(),
@@ -1561,6 +1546,7 @@ class _BottomSheetSectionState extends State<BottomSheetSection> {
),
onPressed: () {
showModalBottomSheet<void>(
showDragHandle: true,
context: context,
// TODO: Remove when this is in the framework https://github.com/flutter/flutter/issues/118619
constraints: const BoxConstraints(maxWidth: 640),
@@ -2075,9 +2061,9 @@ class _MenusState extends State<Menus> {
tooltipMessage: 'Use MenuAnchor or DropdownMenu<T>',
child: Column(
children: [
Row(
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
children: <Widget>[
ButtonAnchorExample(),
rowDivider,
IconButtonAnchorExample(),
@@ -2197,6 +2183,101 @@ class _SlidersState extends State<Sliders> {
}
}
class SearchAnchors extends StatefulWidget {
const SearchAnchors({super.key});
@override
State<SearchAnchors> createState() => _SearchAnchorsState();
}
class _SearchAnchorsState extends State<SearchAnchors> {
String? selectedColor;
List<ColorItem> searchHistory = <ColorItem>[];
Iterable<Widget> getHistoryList(SearchController controller) {
return searchHistory.map((color) => ListTile(
leading: const Icon(Icons.history),
title: Text(color.label),
trailing: IconButton(
icon: const Icon(Icons.call_missed),
onPressed: () {
controller.text = color.label;
controller.selection =
TextSelection.collapsed(offset: controller.text.length);
}),
onTap: () {
controller.closeView(color.label);
handleSelection(color);
},
));
}
Iterable<Widget> getSuggestions(SearchController controller) {
final String input = controller.value.text;
return ColorItem.values
.where((color) => color.label.contains(input))
.map((filteredColor) => ListTile(
leading: CircleAvatar(backgroundColor: filteredColor.color),
title: Text(filteredColor.label),
trailing: IconButton(
icon: const Icon(Icons.call_missed),
onPressed: () {
controller.text = filteredColor.label;
controller.selection =
TextSelection.collapsed(offset: controller.text.length);
}),
onTap: () {
controller.closeView(filteredColor.label);
handleSelection(filteredColor);
},
));
}
void handleSelection(ColorItem color) {
setState(() {
selectedColor = color.label;
if (searchHistory.length >= 5) {
searchHistory.removeLast();
}
searchHistory.insert(0, color);
});
}
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Search',
tooltipMessage: 'Use SearchAnchor or SearchAnchor.bar',
child: Column(
children: <Widget>[
SearchAnchor.bar(
barHintText: 'Search colors',
suggestionsBuilder: (context, controller) {
if (controller.text.isEmpty) {
if (searchHistory.isNotEmpty) {
return getHistoryList(controller);
}
return <Widget>[
const Center(
child: Text('No search history.',
style: TextStyle(color: Colors.grey)),
)
];
}
return getSuggestions(controller);
},
),
const SizedBox(height: 20),
if (selectedColor == null)
const Text('Select a color')
else
Text('Last selected color is $selectedColor')
],
),
);
}
}
class ComponentDecoration extends StatefulWidget {
const ComponentDecoration({
super.key,
@@ -2306,3 +2387,26 @@ class ComponentGroupDecoration extends StatelessWidget {
);
}
}
enum ColorItem {
red('red', Colors.red),
orange('orange', Colors.orange),
yellow('yellow', Colors.yellow),
green('green', Colors.green),
blue('blue', Colors.blue),
indigo('indigo', Colors.indigo),
violet('violet', Color(0xFF8F00FF)),
purple('purple', Colors.purple),
pink('pink', Colors.pink),
silver('silver', Color(0xFF808080)),
gold('gold', Color(0xFFFFD700)),
beige('beige', Color(0xFFF5F5DC)),
brown('brown', Colors.brown),
grey('grey', Colors.grey),
black('black', Colors.black),
white('white', Colors.white);
const ColorItem(this.label, this.color);
final String label;
final Color color;
}

View File

@@ -13,6 +13,13 @@ const double largeWidthBreakpoint = 1500;
const double transitionLength = 500;
// Whether the user has chosen a theme color via a direct [ColorSeed] selection,
// or an image [ColorImageProvider].
enum ColorSelectionMethod {
colorSeed,
image,
}
enum ColorSeed {
baseColor('M3 Baseline', Color(0xff6750a4)),
indigo('Indigo', Colors.indigo),
@@ -29,6 +36,25 @@ enum ColorSeed {
final Color color;
}
enum ColorImageProvider {
leaves('Leaves',
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png'),
peonies('Peonies',
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_2.png'),
bubbles('Bubbles',
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_3.png'),
seaweed('Seaweed',
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_4.png'),
seagrapes('Sea Grapes',
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_5.png'),
petals('Petals',
'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_6.png');
const ColorImageProvider(this.label, this.url);
final String label;
final String url;
}
enum ScreenSelected {
component(0),
color(1),

View File

@@ -19,14 +19,21 @@ class Home extends StatefulWidget {
required this.handleBrightnessChange,
required this.handleMaterialVersionChange,
required this.handleColorSelect,
required this.handleImageSelect,
required this.colorSelectionMethod,
required this.imageSelected,
});
final bool useLightMode;
final bool useMaterial3;
final ColorSeed colorSelected;
final ColorImageProvider imageSelected;
final ColorSelectionMethod colorSelectionMethod;
final void Function(bool useLightMode) handleBrightnessChange;
final void Function() handleMaterialVersionChange;
final void Function(int value) handleColorSelect;
final void Function(int value) handleImageSelect;
@override
State<Home> createState() => _HomeState();
@@ -146,66 +153,18 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
_ColorSeedButton(
handleColorSelect: widget.handleColorSelect,
colorSelected: widget.colorSelected,
colorSelectionMethod: widget.colorSelectionMethod,
),
_ColorImageButton(
handleImageSelect: widget.handleImageSelect,
imageSelected: widget.imageSelected,
colorSelectionMethod: widget.colorSelectionMethod,
)
]
: [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: widget.useLightMode,
onChanged: (value) {
widget.handleBrightnessChange(value);
})
],
),
Row(
children: [
widget.useMaterial3
? const Text('Material 3')
: const Text('Material 2'),
Expanded(child: Container()),
Switch(
value: widget.useMaterial3,
onChanged: (_) {
widget.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.radio_button_unchecked),
color: ColorSeed.values[i].color,
isSelected: widget.colorSelected.color ==
ColorSeed.values[i].color,
selectedIcon: const Icon(Icons.circle),
onPressed: () {
widget.handleColorSelect(i);
},
)),
),
),
],
),
);
Widget _trailingActions() => Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@@ -225,6 +184,14 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
child: _ColorSeedButton(
handleColorSelect: widget.handleColorSelect,
colorSelected: widget.colorSelected,
colorSelectionMethod: widget.colorSelectionMethod,
),
),
Flexible(
child: _ColorImageButton(
handleImageSelect: widget.handleImageSelect,
imageSelected: widget.imageSelected,
colorSelectionMethod: widget.colorSelectionMethod,
),
),
],
@@ -256,7 +223,18 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
child: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: showLargeSizeLayout
? _expandedTrailingActions()
? _ExpandedTrailingActions(
useLightMode: widget.useLightMode,
handleBrightnessChange: widget.handleBrightnessChange,
useMaterial3: widget.useMaterial3,
handleMaterialVersionChange:
widget.handleMaterialVersionChange,
handleImageSelect: widget.handleImageSelect,
handleColorSelect: widget.handleColorSelect,
colorSelectionMethod: widget.colorSelectionMethod,
imageSelected: widget.imageSelected,
colorSelected: widget.colorSelected,
)
: _trailingActions(),
),
),
@@ -331,10 +309,12 @@ class _ColorSeedButton extends StatelessWidget {
const _ColorSeedButton({
required this.handleColorSelect,
required this.colorSelected,
required this.colorSelectionMethod,
});
final void Function(int) handleColorSelect;
final ColorSeed colorSelected;
final ColorSelectionMethod colorSelectionMethod;
@override
Widget build(BuildContext context) {
@@ -351,13 +331,15 @@ class _ColorSeedButton extends StatelessWidget {
return PopupMenuItem(
value: index,
enabled: currentColor != colorSelected,
enabled: currentColor != colorSelected ||
colorSelectionMethod != ColorSelectionMethod.colorSeed,
child: Wrap(
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: Icon(
currentColor == colorSelected
currentColor == colorSelected &&
colorSelectionMethod != ColorSelectionMethod.image
? Icons.color_lens
: Icons.color_lens_outlined,
color: currentColor.color,
@@ -377,6 +359,234 @@ class _ColorSeedButton extends StatelessWidget {
}
}
class _ColorImageButton extends StatelessWidget {
const _ColorImageButton({
required this.handleImageSelect,
required this.imageSelected,
required this.colorSelectionMethod,
});
final void Function(int) handleImageSelect;
final ColorImageProvider imageSelected;
final ColorSelectionMethod colorSelectionMethod;
@override
Widget build(BuildContext context) {
return PopupMenuButton(
icon: Icon(
Icons.image_outlined,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
tooltip: 'Select a color extraction image',
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
itemBuilder: (context) {
return List.generate(ColorImageProvider.values.length, (index) {
ColorImageProvider currentImageProvider =
ColorImageProvider.values[index];
return PopupMenuItem(
value: index,
enabled: currentImageProvider != imageSelected ||
colorSelectionMethod != ColorSelectionMethod.image,
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 48),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image(
image: NetworkImage(
ColorImageProvider.values[index].url),
),
),
),
),
),
Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(currentImageProvider.label),
),
],
),
);
});
},
onSelected: handleImageSelect,
);
}
}
class _ExpandedTrailingActions extends StatelessWidget {
const _ExpandedTrailingActions({
required this.useLightMode,
required this.handleBrightnessChange,
required this.useMaterial3,
required this.handleMaterialVersionChange,
required this.handleColorSelect,
required this.handleImageSelect,
required this.imageSelected,
required this.colorSelected,
required this.colorSelectionMethod,
});
final void Function(bool) handleBrightnessChange;
final void Function() handleMaterialVersionChange;
final void Function(int) handleImageSelect;
final void Function(int) handleColorSelect;
final bool useLightMode;
final bool useMaterial3;
final ColorImageProvider imageSelected;
final ColorSeed colorSelected;
final ColorSelectionMethod colorSelectionMethod;
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final trailingActionsBody = 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: (value) {
handleBrightnessChange(value);
})
],
),
Row(
children: [
useMaterial3
? const Text('Material 3')
: const Text('Material 2'),
Expanded(child: Container()),
Switch(
value: useMaterial3,
onChanged: (_) {
handleMaterialVersionChange();
})
],
),
const Divider(),
_ExpandedColorSeedAction(
handleColorSelect: handleColorSelect,
colorSelected: colorSelected,
colorSelectionMethod: colorSelectionMethod,
),
const Divider(),
_ExpandedImageColorAction(
handleImageSelect: handleImageSelect,
imageSelected: imageSelected,
colorSelectionMethod: colorSelectionMethod,
),
],
),
);
return screenHeight > 740
? trailingActionsBody
: SingleChildScrollView(child: trailingActionsBody);
}
}
class _ExpandedColorSeedAction extends StatelessWidget {
const _ExpandedColorSeedAction({
required this.handleColorSelect,
required this.colorSelected,
required this.colorSelectionMethod,
});
final void Function(int) handleColorSelect;
final ColorSeed colorSelected;
final ColorSelectionMethod colorSelectionMethod;
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200.0),
child: GridView.count(
crossAxisCount: 3,
children: List.generate(
ColorSeed.values.length,
(i) => IconButton(
icon: const Icon(Icons.radio_button_unchecked),
color: ColorSeed.values[i].color,
isSelected: colorSelected.color == ColorSeed.values[i].color &&
colorSelectionMethod == ColorSelectionMethod.colorSeed,
selectedIcon: const Icon(Icons.circle),
onPressed: () {
handleColorSelect(i);
},
),
),
),
);
}
}
class _ExpandedImageColorAction extends StatelessWidget {
const _ExpandedImageColorAction({
required this.handleImageSelect,
required this.imageSelected,
required this.colorSelectionMethod,
});
final void Function(int) handleImageSelect;
final ColorImageProvider imageSelected;
final ColorSelectionMethod colorSelectionMethod;
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 150.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: GridView.count(
crossAxisCount: 3,
children: List.generate(
ColorImageProvider.values.length,
(i) => InkWell(
borderRadius: BorderRadius.circular(4.0),
onTap: () => handleImageSelect(i),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Material(
borderRadius: BorderRadius.circular(4.0),
elevation: imageSelected == ColorImageProvider.values[i] &&
colorSelectionMethod == ColorSelectionMethod.image
? 3
: 0,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(4.0),
child: Image(
image: NetworkImage(ColorImageProvider.values[i].url),
),
),
),
),
),
),
),
),
),
);
}
}
class NavigationTransition extends StatefulWidget {
const NavigationTransition(
{super.key,

View File

@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'constants.dart';
import 'home.dart';
@@ -25,11 +24,14 @@ class _AppState extends State<App> {
bool useMaterial3 = true;
ThemeMode themeMode = ThemeMode.system;
ColorSeed colorSelected = ColorSeed.baseColor;
ColorImageProvider imageSelected = ColorImageProvider.leaves;
ColorScheme? imageColorScheme = const ColorScheme.light();
ColorSelectionMethod colorSelectionMethod = ColorSelectionMethod.colorSeed;
bool get useLightMode {
switch (themeMode) {
case ThemeMode.system:
return SchedulerBinding.instance.window.platformBrightness ==
return View.of(context).platformDispatcher.platformBrightness ==
Brightness.light;
case ThemeMode.light:
return true;
@@ -52,10 +54,23 @@ class _AppState extends State<App> {
void handleColorSelect(int value) {
setState(() {
colorSelectionMethod = ColorSelectionMethod.colorSeed;
colorSelected = ColorSeed.values[value];
});
}
void handleImageSelect(int value) {
final String url = ColorImageProvider.values[value].url;
ColorScheme.fromImageProvider(provider: NetworkImage(url))
.then((newScheme) {
setState(() {
colorSelectionMethod = ColorSelectionMethod.image;
imageSelected = ColorImageProvider.values[value];
imageColorScheme = newScheme;
});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
@@ -63,12 +78,19 @@ class _AppState extends State<App> {
title: 'Material 3',
themeMode: themeMode,
theme: ThemeData(
colorSchemeSeed: colorSelected.color,
colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed
? colorSelected.color
: null,
colorScheme: colorSelectionMethod == ColorSelectionMethod.image
? imageColorScheme
: null,
useMaterial3: useMaterial3,
brightness: Brightness.light,
),
darkTheme: ThemeData(
colorSchemeSeed: colorSelected.color,
colorSchemeSeed: colorSelectionMethod == ColorSelectionMethod.colorSeed
? colorSelected.color
: imageColorScheme!.primary,
useMaterial3: useMaterial3,
brightness: Brightness.dark,
),
@@ -76,9 +98,12 @@ class _AppState extends State<App> {
useLightMode: useLightMode,
useMaterial3: useMaterial3,
colorSelected: colorSelected,
imageSelected: imageSelected,
handleBrightnessChange: handleBrightnessChange,
handleMaterialVersionChange: handleMaterialVersionChange,
handleColorSelect: handleColorSelect,
handleImageSelect: handleImageSelect,
colorSelectionMethod: colorSelectionMethod,
),
);
}