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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user