mirror of https://github.com/flutter/samples.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2549 lines
72 KiB
2549 lines
72 KiB
// 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 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
const rowDivider = SizedBox(width: 20);
|
|
const colDivider = SizedBox(height: 10);
|
|
const tinySpacing = 3.0;
|
|
const smallSpacing = 10.0;
|
|
const double cardWidth = 115;
|
|
const double widthConstraint = 450;
|
|
|
|
class FirstComponentList extends StatelessWidget {
|
|
const FirstComponentList({
|
|
super.key,
|
|
required this.showNavBottomBar,
|
|
required this.scaffoldKey,
|
|
required this.showSecondList,
|
|
});
|
|
|
|
final bool showNavBottomBar;
|
|
final GlobalKey<ScaffoldState> scaffoldKey;
|
|
final bool showSecondList;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
List<Widget> children = [
|
|
const Actions(),
|
|
colDivider,
|
|
const Communication(),
|
|
colDivider,
|
|
const Containment(),
|
|
if (!showSecondList) ...[
|
|
colDivider,
|
|
Navigation(scaffoldKey: scaffoldKey),
|
|
colDivider,
|
|
const Selection(),
|
|
colDivider,
|
|
const TextInputs()
|
|
],
|
|
];
|
|
List<double?> heights = List.filled(children.length, null);
|
|
|
|
// Fully traverse this list before moving on.
|
|
return FocusTraversalGroup(
|
|
child: CustomScrollView(
|
|
slivers: [
|
|
SliverPadding(
|
|
padding: showSecondList
|
|
? const EdgeInsetsDirectional.only(end: smallSpacing)
|
|
: EdgeInsets.zero,
|
|
sliver: SliverList(
|
|
delegate: BuildSlivers(
|
|
heights: heights,
|
|
builder: (context, index) {
|
|
return _CacheHeight(
|
|
heights: heights,
|
|
index: index,
|
|
child: children[index],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SecondComponentList extends StatelessWidget {
|
|
const SecondComponentList({
|
|
super.key,
|
|
required this.scaffoldKey,
|
|
});
|
|
|
|
final GlobalKey<ScaffoldState> scaffoldKey;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
List<Widget> children = [
|
|
Navigation(scaffoldKey: scaffoldKey),
|
|
colDivider,
|
|
const Selection(),
|
|
colDivider,
|
|
const TextInputs(),
|
|
];
|
|
List<double?> heights = List.filled(children.length, null);
|
|
|
|
// Fully traverse this list before moving on.
|
|
return FocusTraversalGroup(
|
|
child: CustomScrollView(
|
|
slivers: [
|
|
SliverPadding(
|
|
padding: const EdgeInsetsDirectional.only(end: smallSpacing),
|
|
sliver: SliverList(
|
|
delegate: BuildSlivers(
|
|
heights: heights,
|
|
builder: (context, index) {
|
|
return _CacheHeight(
|
|
heights: heights,
|
|
index: index,
|
|
child: children[index],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// If the content of a CustomScrollView does not change, then it's
|
|
// safe to cache the heights of each item as they are laid out. The
|
|
// sum of the cached heights are returned by an override of
|
|
// `SliverChildDelegate.estimateMaxScrollOffset`. The default version
|
|
// of this method bases its estimate on the average height of the
|
|
// visible items. The override ensures that the scrollbar thumb's
|
|
// size, which depends on the max scroll offset, will shrink smoothly
|
|
// as the contents of the list are exposed for the first time, and
|
|
// then remain fixed.
|
|
class _CacheHeight extends SingleChildRenderObjectWidget {
|
|
const _CacheHeight({
|
|
super.child,
|
|
required this.heights,
|
|
required this.index,
|
|
});
|
|
|
|
final List<double?> heights;
|
|
final int index;
|
|
|
|
@override
|
|
RenderObject createRenderObject(BuildContext context) {
|
|
return _RenderCacheHeight(
|
|
heights: heights,
|
|
index: index,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(
|
|
BuildContext context, _RenderCacheHeight renderObject) {
|
|
renderObject
|
|
..heights = heights
|
|
..index = index;
|
|
}
|
|
}
|
|
|
|
class _RenderCacheHeight extends RenderProxyBox {
|
|
_RenderCacheHeight({
|
|
required List<double?> heights,
|
|
required int index,
|
|
}) : _heights = heights,
|
|
_index = index,
|
|
super();
|
|
|
|
List<double?> _heights;
|
|
List<double?> get heights => _heights;
|
|
set heights(List<double?> value) {
|
|
if (value == _heights) {
|
|
return;
|
|
}
|
|
_heights = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
int _index;
|
|
int get index => _index;
|
|
set index(int value) {
|
|
if (value == index) {
|
|
return;
|
|
}
|
|
_index = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
@override
|
|
void performLayout() {
|
|
super.performLayout();
|
|
heights[index] = size.height;
|
|
}
|
|
}
|
|
|
|
// The heights information is used to override the `estimateMaxScrollOffset` and
|
|
// provide a more accurate estimation for the max scroll offset.
|
|
class BuildSlivers extends SliverChildBuilderDelegate {
|
|
BuildSlivers({
|
|
required NullableIndexedWidgetBuilder builder,
|
|
required this.heights,
|
|
}) : super(builder, childCount: heights.length);
|
|
|
|
final List<double?> heights;
|
|
|
|
@override
|
|
double? estimateMaxScrollOffset(int firstIndex, int lastIndex,
|
|
double leadingScrollOffset, double trailingScrollOffset) {
|
|
return heights.reduce((sum, height) => (sum ?? 0) + (height ?? 0))!;
|
|
}
|
|
}
|
|
|
|
class Actions extends StatelessWidget {
|
|
const Actions({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentGroupDecoration(label: 'Actions', children: <Widget>[
|
|
Buttons(),
|
|
FloatingActionButtons(),
|
|
IconToggleButtons(),
|
|
SegmentedButtons(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
class Communication extends StatelessWidget {
|
|
const Communication({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentGroupDecoration(label: 'Communication', children: [
|
|
NavigationBars(
|
|
selectedIndex: 1,
|
|
isExampleBar: true,
|
|
isBadgeExample: true,
|
|
),
|
|
ProgressIndicators(),
|
|
SnackBarSection(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
class Containment extends StatelessWidget {
|
|
const Containment({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentGroupDecoration(label: 'Containment', children: [
|
|
BottomSheetSection(),
|
|
Cards(),
|
|
Dialogs(),
|
|
Dividers(),
|
|
// TODO: Add Lists, https://github.com/flutter/flutter/issues/114006
|
|
// TODO: Add Side sheets, https://github.com/flutter/flutter/issues/119328
|
|
]);
|
|
}
|
|
}
|
|
|
|
class Navigation extends StatelessWidget {
|
|
const Navigation({super.key, required this.scaffoldKey});
|
|
|
|
final GlobalKey<ScaffoldState> scaffoldKey;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentGroupDecoration(label: 'Navigation', children: [
|
|
const BottomAppBars(),
|
|
const NavigationBars(
|
|
selectedIndex: 0,
|
|
isExampleBar: true,
|
|
),
|
|
NavigationDrawers(scaffoldKey: scaffoldKey),
|
|
const NavigationRails(),
|
|
const Tabs(),
|
|
const SearchAnchors(),
|
|
const TopAppBars(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
class Selection extends StatelessWidget {
|
|
const Selection({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentGroupDecoration(label: 'Selection', children: [
|
|
Checkboxes(),
|
|
Chips(),
|
|
DatePickers(),
|
|
Menus(),
|
|
Radios(),
|
|
Sliders(),
|
|
Switches(),
|
|
TimePickers(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
class TextInputs extends StatelessWidget {
|
|
const TextInputs({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentGroupDecoration(
|
|
label: 'Text inputs',
|
|
children: [TextFields()],
|
|
);
|
|
}
|
|
}
|
|
|
|
class Buttons extends StatefulWidget {
|
|
const Buttons({super.key});
|
|
|
|
@override
|
|
State<Buttons> createState() => _ButtonsState();
|
|
}
|
|
|
|
class _ButtonsState extends State<Buttons> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentDecoration(
|
|
label: 'Common buttons',
|
|
tooltipMessage:
|
|
'Use ElevatedButton, FilledButton, FilledButton.tonal, OutlinedButton, or TextButton',
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: <Widget>[
|
|
ButtonsWithoutIcon(isDisabled: false),
|
|
ButtonsWithIcon(),
|
|
ButtonsWithoutIcon(isDisabled: true),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ButtonsWithoutIcon extends StatelessWidget {
|
|
final bool isDisabled;
|
|
|
|
const ButtonsWithoutIcon({super.key, required this.isDisabled});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
|
child: IntrinsicWidth(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: <Widget>[
|
|
ElevatedButton(
|
|
onPressed: isDisabled ? null : () {},
|
|
child: const Text('Elevated'),
|
|
),
|
|
colDivider,
|
|
FilledButton(
|
|
onPressed: isDisabled ? null : () {},
|
|
child: const Text('Filled'),
|
|
),
|
|
colDivider,
|
|
FilledButton.tonal(
|
|
onPressed: isDisabled ? null : () {},
|
|
child: const Text('Filled tonal'),
|
|
),
|
|
colDivider,
|
|
OutlinedButton(
|
|
onPressed: isDisabled ? null : () {},
|
|
child: const Text('Outlined'),
|
|
),
|
|
colDivider,
|
|
TextButton(
|
|
onPressed: isDisabled ? null : () {},
|
|
child: const Text('Text'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ButtonsWithIcon extends StatelessWidget {
|
|
const ButtonsWithIcon({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
|
child: IntrinsicWidth(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: <Widget>[
|
|
ElevatedButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('Icon'),
|
|
),
|
|
colDivider,
|
|
FilledButton.icon(
|
|
onPressed: () {},
|
|
label: const Text('Icon'),
|
|
icon: const Icon(Icons.add),
|
|
),
|
|
colDivider,
|
|
FilledButton.tonalIcon(
|
|
onPressed: () {},
|
|
label: const Text('Icon'),
|
|
icon: const Icon(Icons.add),
|
|
),
|
|
colDivider,
|
|
OutlinedButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('Icon'),
|
|
),
|
|
colDivider,
|
|
TextButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('Icon'),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class FloatingActionButtons extends StatelessWidget {
|
|
const FloatingActionButtons({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Floating action buttons',
|
|
tooltipMessage:
|
|
'Use FloatingActionButton or FloatingActionButton.extended',
|
|
child: Wrap(
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
runSpacing: smallSpacing,
|
|
spacing: smallSpacing,
|
|
children: [
|
|
FloatingActionButton.small(
|
|
onPressed: () {},
|
|
tooltip: 'Small',
|
|
child: const Icon(Icons.add),
|
|
),
|
|
FloatingActionButton.extended(
|
|
onPressed: () {},
|
|
tooltip: 'Extended',
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('Create'),
|
|
),
|
|
FloatingActionButton(
|
|
onPressed: () {},
|
|
tooltip: 'Standard',
|
|
child: const Icon(Icons.add),
|
|
),
|
|
FloatingActionButton.large(
|
|
onPressed: () {},
|
|
tooltip: 'Large',
|
|
child: const Icon(Icons.add),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class Cards extends StatelessWidget {
|
|
const Cards({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Cards',
|
|
tooltipMessage: 'Use Card',
|
|
child: Wrap(
|
|
alignment: WrapAlignment.spaceEvenly,
|
|
children: [
|
|
SizedBox(
|
|
width: cardWidth,
|
|
child: Card(
|
|
child: Container(
|
|
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
|
|
child: Column(
|
|
children: [
|
|
Align(
|
|
alignment: Alignment.topRight,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.more_vert),
|
|
onPressed: () {},
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Align(
|
|
alignment: Alignment.bottomLeft,
|
|
child: Text('Elevated'),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(
|
|
width: cardWidth,
|
|
child: Card(
|
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
|
elevation: 0,
|
|
child: Container(
|
|
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
|
|
child: Column(
|
|
children: [
|
|
Align(
|
|
alignment: Alignment.topRight,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.more_vert),
|
|
onPressed: () {},
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Align(
|
|
alignment: Alignment.bottomLeft,
|
|
child: Text('Filled'),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(
|
|
width: cardWidth,
|
|
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.fromLTRB(10, 5, 5, 10),
|
|
child: Column(
|
|
children: [
|
|
Align(
|
|
alignment: Alignment.topRight,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.more_vert),
|
|
onPressed: () {},
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Align(
|
|
alignment: Alignment.bottomLeft,
|
|
child: Text('Outlined'),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ClearButton extends StatelessWidget {
|
|
const _ClearButton({required this.controller});
|
|
|
|
final TextEditingController controller;
|
|
|
|
@override
|
|
Widget build(BuildContext context) => IconButton(
|
|
icon: const Icon(Icons.clear),
|
|
onPressed: () => controller.clear(),
|
|
);
|
|
}
|
|
|
|
class TextFields extends StatefulWidget {
|
|
const TextFields({super.key});
|
|
|
|
@override
|
|
State<TextFields> createState() => _TextFieldsState();
|
|
}
|
|
|
|
class _TextFieldsState extends State<TextFields> {
|
|
final TextEditingController _controllerFilled = TextEditingController();
|
|
final TextEditingController _controllerOutlined = TextEditingController();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Text fields',
|
|
tooltipMessage: 'Use TextField with different InputDecoration',
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(smallSpacing),
|
|
child: TextField(
|
|
controller: _controllerFilled,
|
|
decoration: InputDecoration(
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: _ClearButton(controller: _controllerFilled),
|
|
labelText: 'Filled',
|
|
hintText: 'hint text',
|
|
helperText: 'supporting text',
|
|
filled: true,
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(smallSpacing),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Flexible(
|
|
child: SizedBox(
|
|
width: 200,
|
|
child: TextField(
|
|
maxLength: 10,
|
|
maxLengthEnforcement: MaxLengthEnforcement.none,
|
|
controller: _controllerFilled,
|
|
decoration: InputDecoration(
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: _ClearButton(controller: _controllerFilled),
|
|
labelText: 'Filled',
|
|
hintText: 'hint text',
|
|
helperText: 'supporting text',
|
|
filled: true,
|
|
errorText: 'error text',
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: smallSpacing),
|
|
Flexible(
|
|
child: SizedBox(
|
|
width: 200,
|
|
child: TextField(
|
|
controller: _controllerFilled,
|
|
enabled: false,
|
|
decoration: InputDecoration(
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: _ClearButton(controller: _controllerFilled),
|
|
labelText: 'Disabled',
|
|
hintText: 'hint text',
|
|
helperText: 'supporting text',
|
|
filled: true,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(smallSpacing),
|
|
child: TextField(
|
|
controller: _controllerOutlined,
|
|
decoration: InputDecoration(
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon: _ClearButton(controller: _controllerOutlined),
|
|
labelText: 'Outlined',
|
|
hintText: 'hint text',
|
|
helperText: 'supporting text',
|
|
border: const OutlineInputBorder(),
|
|
),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(smallSpacing),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Flexible(
|
|
child: SizedBox(
|
|
width: 200,
|
|
child: TextField(
|
|
controller: _controllerOutlined,
|
|
decoration: InputDecoration(
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon:
|
|
_ClearButton(controller: _controllerOutlined),
|
|
labelText: 'Outlined',
|
|
hintText: 'hint text',
|
|
helperText: 'supporting text',
|
|
errorText: 'error text',
|
|
border: const OutlineInputBorder(),
|
|
filled: true,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: smallSpacing),
|
|
Flexible(
|
|
child: SizedBox(
|
|
width: 200,
|
|
child: TextField(
|
|
controller: _controllerOutlined,
|
|
enabled: false,
|
|
decoration: InputDecoration(
|
|
prefixIcon: const Icon(Icons.search),
|
|
suffixIcon:
|
|
_ClearButton(controller: _controllerOutlined),
|
|
labelText: 'Disabled',
|
|
hintText: 'hint text',
|
|
helperText: 'supporting text',
|
|
border: const 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('What is a dialog?'),
|
|
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('Okay'),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
FilledButton(
|
|
child: const Text('Dismiss'),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void openFullscreenDialog(BuildContext context) {
|
|
showDialog<void>(
|
|
context: context,
|
|
builder: (context) => Dialog.fullscreen(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Full-screen dialog'),
|
|
centerTitle: false,
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.close),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
child: const Text('Close'),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Dialog',
|
|
tooltipMessage:
|
|
'Use showDialog with Dialog.fullscreen, AlertDialog, or SimpleDialog',
|
|
child: Wrap(
|
|
alignment: WrapAlignment.spaceBetween,
|
|
children: [
|
|
TextButton(
|
|
child: const Text(
|
|
'Show dialog',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
onPressed: () => openDialog(context),
|
|
),
|
|
TextButton(
|
|
child: const Text(
|
|
'Show full-screen dialog',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
onPressed: () => openFullscreenDialog(context),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class Dividers extends StatelessWidget {
|
|
const Dividers({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentDecoration(
|
|
label: 'Dividers',
|
|
tooltipMessage: 'Use Divider or VerticalDivider',
|
|
child: Column(
|
|
children: <Widget>[
|
|
Divider(key: Key('divider')),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class Switches extends StatelessWidget {
|
|
const Switches({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentDecoration(
|
|
label: 'Switches',
|
|
tooltipMessage: 'Use SwitchListTile or Switch',
|
|
child: Column(
|
|
children: <Widget>[
|
|
SwitchRow(isEnabled: true),
|
|
SwitchRow(isEnabled: false),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SwitchRow extends StatefulWidget {
|
|
const SwitchRow({super.key, required this.isEnabled});
|
|
|
|
final bool isEnabled;
|
|
|
|
@override
|
|
State<SwitchRow> createState() => _SwitchRowState();
|
|
}
|
|
|
|
class _SwitchRowState extends State<SwitchRow> {
|
|
bool value0 = false;
|
|
bool value1 = true;
|
|
|
|
final MaterialStateProperty<Icon?> thumbIcon =
|
|
MaterialStateProperty.resolveWith<Icon?>((states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return const Icon(Icons.check);
|
|
}
|
|
return const Icon(Icons.close);
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: <Widget>[
|
|
// TODO: use SwitchListTile when thumbIcon is available https://github.com/flutter/flutter/issues/118616
|
|
Switch(
|
|
value: value0,
|
|
onChanged: widget.isEnabled
|
|
? (value) {
|
|
setState(() {
|
|
value0 = value;
|
|
});
|
|
}
|
|
: null,
|
|
),
|
|
Switch(
|
|
thumbIcon: thumbIcon,
|
|
value: value1,
|
|
onChanged: widget.isEnabled
|
|
? (value) {
|
|
setState(() {
|
|
value1 = value;
|
|
});
|
|
}
|
|
: null,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class Checkboxes extends StatefulWidget {
|
|
const Checkboxes({super.key});
|
|
|
|
@override
|
|
State<Checkboxes> createState() => _CheckboxesState();
|
|
}
|
|
|
|
class _CheckboxesState extends State<Checkboxes> {
|
|
bool? isChecked0 = true;
|
|
bool? isChecked1;
|
|
bool? isChecked2 = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Checkboxes',
|
|
tooltipMessage: 'Use CheckboxListTile or Checkbox',
|
|
child: Column(
|
|
children: <Widget>[
|
|
CheckboxListTile(
|
|
tristate: true,
|
|
value: isChecked0,
|
|
title: const Text('Option 1'),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
isChecked0 = value;
|
|
});
|
|
},
|
|
),
|
|
CheckboxListTile(
|
|
tristate: true,
|
|
value: isChecked1,
|
|
title: const Text('Option 2'),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
isChecked1 = value;
|
|
});
|
|
},
|
|
),
|
|
CheckboxListTile(
|
|
tristate: true,
|
|
value: isChecked2,
|
|
title: const Text('Option 3'),
|
|
// TODO: showcase error state https://github.com/flutter/flutter/issues/118616
|
|
onChanged: (value) {
|
|
setState(() {
|
|
isChecked2 = value;
|
|
});
|
|
},
|
|
),
|
|
const CheckboxListTile(
|
|
tristate: true,
|
|
title: Text('Option 4'),
|
|
value: true,
|
|
onChanged: null,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
enum Value { first, second }
|
|
|
|
class Radios extends StatefulWidget {
|
|
const Radios({super.key});
|
|
|
|
@override
|
|
State<Radios> createState() => _RadiosState();
|
|
}
|
|
|
|
enum Options { option1, option2, option3 }
|
|
|
|
class _RadiosState extends State<Radios> {
|
|
Options? _selectedOption = Options.option1;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Radio buttons',
|
|
tooltipMessage: 'Use RadioListTile<T> or Radio<T>',
|
|
child: Column(
|
|
children: <Widget>[
|
|
RadioListTile<Options>(
|
|
title: const Text('Option 1'),
|
|
value: Options.option1,
|
|
groupValue: _selectedOption,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_selectedOption = value;
|
|
});
|
|
},
|
|
),
|
|
RadioListTile<Options>(
|
|
title: const Text('Option 2'),
|
|
value: Options.option2,
|
|
groupValue: _selectedOption,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_selectedOption = value;
|
|
});
|
|
},
|
|
),
|
|
RadioListTile<Options>(
|
|
title: const Text('Option 3'),
|
|
value: Options.option3,
|
|
groupValue: _selectedOption,
|
|
onChanged: null,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ProgressIndicators extends StatefulWidget {
|
|
const ProgressIndicators({super.key});
|
|
|
|
@override
|
|
State<ProgressIndicators> createState() => _ProgressIndicatorsState();
|
|
}
|
|
|
|
class _ProgressIndicatorsState extends State<ProgressIndicators> {
|
|
bool playProgressIndicator = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final double? progressValue = playProgressIndicator ? null : 0.7;
|
|
|
|
return ComponentDecoration(
|
|
label: 'Progress indicators',
|
|
tooltipMessage:
|
|
'Use CircularProgressIndicator or LinearProgressIndicator',
|
|
child: Column(
|
|
children: <Widget>[
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
isSelected: playProgressIndicator,
|
|
selectedIcon: const Icon(Icons.pause),
|
|
icon: const Icon(Icons.play_arrow),
|
|
onPressed: () {
|
|
setState(() {
|
|
playProgressIndicator = !playProgressIndicator;
|
|
});
|
|
},
|
|
),
|
|
Expanded(
|
|
child: Row(
|
|
children: <Widget>[
|
|
rowDivider,
|
|
CircularProgressIndicator(
|
|
value: progressValue,
|
|
),
|
|
rowDivider,
|
|
Expanded(
|
|
child: LinearProgressIndicator(
|
|
value: progressValue,
|
|
),
|
|
),
|
|
rowDivider,
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
const List<NavigationDestination> appBarDestinations = [
|
|
NavigationDestination(
|
|
tooltip: '',
|
|
icon: Icon(Icons.widgets_outlined),
|
|
label: 'Components',
|
|
selectedIcon: Icon(Icons.widgets),
|
|
),
|
|
NavigationDestination(
|
|
tooltip: '',
|
|
icon: Icon(Icons.format_paint_outlined),
|
|
label: 'Color',
|
|
selectedIcon: Icon(Icons.format_paint),
|
|
),
|
|
NavigationDestination(
|
|
tooltip: '',
|
|
icon: Icon(Icons.text_snippet_outlined),
|
|
label: 'Typography',
|
|
selectedIcon: Icon(Icons.text_snippet),
|
|
),
|
|
NavigationDestination(
|
|
tooltip: '',
|
|
icon: Icon(Icons.invert_colors_on_outlined),
|
|
label: 'Elevation',
|
|
selectedIcon: Icon(Icons.opacity),
|
|
)
|
|
];
|
|
|
|
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),
|
|
)
|
|
];
|
|
|
|
List<Widget> barWithBadgeDestinations = [
|
|
NavigationDestination(
|
|
tooltip: '',
|
|
icon: Badge.count(count: 1000, child: const Icon(Icons.mail_outlined)),
|
|
label: 'Mail',
|
|
selectedIcon: Badge.count(count: 1000, child: const Icon(Icons.mail)),
|
|
),
|
|
const NavigationDestination(
|
|
tooltip: '',
|
|
icon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble_outline)),
|
|
label: 'Chat',
|
|
selectedIcon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble)),
|
|
),
|
|
const NavigationDestination(
|
|
tooltip: '',
|
|
icon: Badge(child: Icon(Icons.group_outlined)),
|
|
label: 'Rooms',
|
|
selectedIcon: Badge(child: Icon(Icons.group_rounded)),
|
|
),
|
|
NavigationDestination(
|
|
tooltip: '',
|
|
icon: Badge.count(count: 3, child: const Icon(Icons.videocam_outlined)),
|
|
label: 'Meet',
|
|
selectedIcon: Badge.count(count: 3, child: const Icon(Icons.videocam)),
|
|
)
|
|
];
|
|
|
|
class NavigationBars extends StatefulWidget {
|
|
const NavigationBars({
|
|
super.key,
|
|
this.onSelectItem,
|
|
required this.selectedIndex,
|
|
required this.isExampleBar,
|
|
this.isBadgeExample = false,
|
|
});
|
|
|
|
final void Function(int)? onSelectItem;
|
|
final int selectedIndex;
|
|
final bool isExampleBar;
|
|
final bool isBadgeExample;
|
|
|
|
@override
|
|
State<NavigationBars> createState() => _NavigationBarsState();
|
|
}
|
|
|
|
class _NavigationBarsState extends State<NavigationBars> {
|
|
late int selectedIndex;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
selectedIndex = widget.selectedIndex;
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant NavigationBars oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (widget.selectedIndex != oldWidget.selectedIndex) {
|
|
selectedIndex = widget.selectedIndex;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// App NavigationBar should get first focus.
|
|
Widget navigationBar = Focus(
|
|
autofocus: !(widget.isExampleBar || widget.isBadgeExample),
|
|
child: NavigationBar(
|
|
selectedIndex: selectedIndex,
|
|
onDestinationSelected: (index) {
|
|
setState(() {
|
|
selectedIndex = index;
|
|
});
|
|
if (!widget.isExampleBar) widget.onSelectItem!(index);
|
|
},
|
|
destinations: widget.isExampleBar && widget.isBadgeExample
|
|
? barWithBadgeDestinations
|
|
: widget.isExampleBar
|
|
? exampleBarDestinations
|
|
: appBarDestinations,
|
|
),
|
|
);
|
|
|
|
if (widget.isExampleBar && widget.isBadgeExample) {
|
|
navigationBar = ComponentDecoration(
|
|
label: 'Badges',
|
|
tooltipMessage: 'Use Badge or Badge.count',
|
|
child: navigationBar);
|
|
} else if (widget.isExampleBar) {
|
|
navigationBar = ComponentDecoration(
|
|
label: 'Navigation bar',
|
|
tooltipMessage: 'Use NavigationBar',
|
|
child: navigationBar);
|
|
}
|
|
|
|
return navigationBar;
|
|
}
|
|
}
|
|
|
|
class IconToggleButtons extends StatefulWidget {
|
|
const IconToggleButtons({super.key});
|
|
|
|
@override
|
|
State<IconToggleButtons> createState() => _IconToggleButtonsState();
|
|
}
|
|
|
|
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, IconButton.filled, IconButton.filledTonal, and IconButton.outlined',
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: <Widget>[
|
|
Column(
|
|
// Standard IconButton
|
|
children: <Widget>[
|
|
IconButton(
|
|
isSelected: standardSelected,
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
onPressed: () {
|
|
setState(() {
|
|
standardSelected = !standardSelected;
|
|
});
|
|
},
|
|
),
|
|
colDivider,
|
|
IconButton(
|
|
isSelected: standardSelected,
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
onPressed: null,
|
|
),
|
|
],
|
|
),
|
|
Column(
|
|
children: <Widget>[
|
|
// Filled IconButton
|
|
IconButton.filled(
|
|
isSelected: filledSelected,
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
onPressed: () {
|
|
setState(() {
|
|
filledSelected = !filledSelected;
|
|
});
|
|
},
|
|
),
|
|
colDivider,
|
|
IconButton.filled(
|
|
isSelected: filledSelected,
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
onPressed: null,
|
|
),
|
|
],
|
|
),
|
|
Column(
|
|
children: <Widget>[
|
|
// Filled Tonal IconButton
|
|
IconButton.filledTonal(
|
|
isSelected: tonalSelected,
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
onPressed: () {
|
|
setState(() {
|
|
tonalSelected = !tonalSelected;
|
|
});
|
|
},
|
|
),
|
|
colDivider,
|
|
IconButton.filledTonal(
|
|
isSelected: tonalSelected,
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
onPressed: null,
|
|
),
|
|
],
|
|
),
|
|
Column(
|
|
children: <Widget>[
|
|
// Outlined IconButton
|
|
IconButton.outlined(
|
|
isSelected: outlinedSelected,
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
onPressed: () {
|
|
setState(() {
|
|
outlinedSelected = !outlinedSelected;
|
|
});
|
|
},
|
|
),
|
|
colDivider,
|
|
IconButton.outlined(
|
|
isSelected: outlinedSelected,
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
onPressed: null,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class Chips extends StatefulWidget {
|
|
const Chips({super.key});
|
|
|
|
@override
|
|
State<Chips> createState() => _ChipsState();
|
|
}
|
|
|
|
class _ChipsState extends State<Chips> {
|
|
bool isFiltered = true;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Chips',
|
|
tooltipMessage:
|
|
'Use ActionChip, FilterChip, or InputChip. \nActionChip can also be used for suggestion chip',
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[
|
|
Wrap(
|
|
spacing: smallSpacing,
|
|
runSpacing: smallSpacing,
|
|
children: <Widget>[
|
|
ActionChip(
|
|
label: const Text('Assist'),
|
|
avatar: const Icon(Icons.event),
|
|
onPressed: () {},
|
|
),
|
|
FilterChip(
|
|
label: const Text('Filter'),
|
|
selected: isFiltered,
|
|
onSelected: (selected) {
|
|
setState(() => isFiltered = selected);
|
|
},
|
|
),
|
|
InputChip(
|
|
label: const Text('Input'),
|
|
onPressed: () {},
|
|
onDeleted: () {},
|
|
),
|
|
ActionChip(
|
|
label: const Text('Suggestion'),
|
|
onPressed: () {},
|
|
),
|
|
],
|
|
),
|
|
colDivider,
|
|
Wrap(
|
|
spacing: smallSpacing,
|
|
runSpacing: smallSpacing,
|
|
children: <Widget>[
|
|
const ActionChip(
|
|
label: Text('Assist'),
|
|
avatar: Icon(Icons.event),
|
|
),
|
|
FilterChip(
|
|
label: const Text('Filter'),
|
|
selected: isFiltered,
|
|
onSelected: null,
|
|
),
|
|
InputChip(
|
|
label: const Text('Input'),
|
|
onDeleted: () {},
|
|
isEnabled: false,
|
|
),
|
|
const ActionChip(
|
|
label: Text('Suggestion'),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
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(),
|
|
builder: (context, child) {
|
|
return MediaQuery(
|
|
data: MediaQuery.of(context).copyWith(
|
|
alwaysUse24HourFormat: true,
|
|
),
|
|
child: child!,
|
|
);
|
|
},
|
|
);
|
|
setState(() {
|
|
selectedTime = time;
|
|
if (selectedTime != null) {
|
|
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 const ComponentDecoration(
|
|
label: 'Segmented buttons',
|
|
tooltipMessage: 'Use SegmentedButton<T>',
|
|
child: Column(
|
|
children: <Widget>[
|
|
SingleChoice(),
|
|
colDivider,
|
|
MultipleChoice(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
enum Calendar { day, week, month, year }
|
|
|
|
class SingleChoice extends StatefulWidget {
|
|
const SingleChoice({super.key});
|
|
|
|
@override
|
|
State<SingleChoice> createState() => _SingleChoiceState();
|
|
}
|
|
|
|
class _SingleChoiceState extends State<SingleChoice> {
|
|
Calendar calendarView = Calendar.day;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SegmentedButton<Calendar>(
|
|
segments: const <ButtonSegment<Calendar>>[
|
|
ButtonSegment<Calendar>(
|
|
value: Calendar.day,
|
|
label: Text('Day'),
|
|
icon: Icon(Icons.calendar_view_day)),
|
|
ButtonSegment<Calendar>(
|
|
value: Calendar.week,
|
|
label: Text('Week'),
|
|
icon: Icon(Icons.calendar_view_week)),
|
|
ButtonSegment<Calendar>(
|
|
value: Calendar.month,
|
|
label: Text('Month'),
|
|
icon: Icon(Icons.calendar_view_month)),
|
|
ButtonSegment<Calendar>(
|
|
value: Calendar.year,
|
|
label: Text('Year'),
|
|
icon: Icon(Icons.calendar_today)),
|
|
],
|
|
selected: <Calendar>{calendarView},
|
|
onSelectionChanged: (newSelection) {
|
|
setState(() {
|
|
// By default there is only a single segment that can be
|
|
// selected at one time, so its value is always the first
|
|
// item in the selected set.
|
|
calendarView = newSelection.first;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
enum Sizes { extraSmall, small, medium, large, extraLarge }
|
|
|
|
class MultipleChoice extends StatefulWidget {
|
|
const MultipleChoice({super.key});
|
|
|
|
@override
|
|
State<MultipleChoice> createState() => _MultipleChoiceState();
|
|
}
|
|
|
|
class _MultipleChoiceState extends State<MultipleChoice> {
|
|
Set<Sizes> selection = <Sizes>{Sizes.large, Sizes.extraLarge};
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SegmentedButton<Sizes>(
|
|
segments: const <ButtonSegment<Sizes>>[
|
|
ButtonSegment<Sizes>(value: Sizes.extraSmall, label: Text('XS')),
|
|
ButtonSegment<Sizes>(value: Sizes.small, label: Text('S')),
|
|
ButtonSegment<Sizes>(value: Sizes.medium, label: Text('M')),
|
|
ButtonSegment<Sizes>(
|
|
value: Sizes.large,
|
|
label: Text('L'),
|
|
),
|
|
ButtonSegment<Sizes>(value: Sizes.extraLarge, label: Text('XL')),
|
|
],
|
|
selected: selection,
|
|
onSelectionChanged: (newSelection) {
|
|
setState(() {
|
|
selection = newSelection;
|
|
});
|
|
},
|
|
multiSelectionEnabled: true,
|
|
);
|
|
}
|
|
}
|
|
|
|
class SnackBarSection extends StatelessWidget {
|
|
const SnackBarSection({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Snackbar',
|
|
tooltipMessage:
|
|
'Use ScaffoldMessenger.of(context).showSnackBar with SnackBar',
|
|
child: TextButton(
|
|
onPressed: () {
|
|
final snackBar = SnackBar(
|
|
behavior: SnackBarBehavior.floating,
|
|
width: 400.0,
|
|
content: const Text('This is a snackbar'),
|
|
action: SnackBarAction(
|
|
label: 'Close',
|
|
onPressed: () {},
|
|
),
|
|
);
|
|
|
|
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
},
|
|
child: const Text(
|
|
'Show snackbar',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class BottomSheetSection extends StatefulWidget {
|
|
const BottomSheetSection({super.key});
|
|
|
|
@override
|
|
State<BottomSheetSection> createState() => _BottomSheetSectionState();
|
|
}
|
|
|
|
class _BottomSheetSectionState extends State<BottomSheetSection> {
|
|
bool isNonModalBottomSheetOpen = false;
|
|
PersistentBottomSheetController<void>? _nonModalBottomSheetController;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
List<Widget> buttonList = <Widget>[
|
|
IconButton(onPressed: () {}, icon: const Icon(Icons.share_outlined)),
|
|
IconButton(onPressed: () {}, icon: const Icon(Icons.add)),
|
|
IconButton(onPressed: () {}, icon: const Icon(Icons.delete_outline)),
|
|
IconButton(onPressed: () {}, icon: const Icon(Icons.archive_outlined)),
|
|
IconButton(onPressed: () {}, icon: const Icon(Icons.settings_outlined)),
|
|
IconButton(onPressed: () {}, icon: const Icon(Icons.favorite_border)),
|
|
];
|
|
List<Text> labelList = const <Text>[
|
|
Text('Share'),
|
|
Text('Add to'),
|
|
Text('Trash'),
|
|
Text('Archive'),
|
|
Text('Settings'),
|
|
Text('Favorite')
|
|
];
|
|
|
|
buttonList = List.generate(
|
|
buttonList.length,
|
|
(index) => Padding(
|
|
padding: const EdgeInsets.fromLTRB(20.0, 30.0, 20.0, 20.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: [
|
|
buttonList[index],
|
|
labelList[index],
|
|
],
|
|
),
|
|
));
|
|
|
|
return ComponentDecoration(
|
|
label: 'Bottom sheet',
|
|
tooltipMessage: 'Use showModalBottomSheet<T> or showBottomSheet<T>',
|
|
child: Wrap(
|
|
alignment: WrapAlignment.spaceEvenly,
|
|
children: [
|
|
TextButton(
|
|
child: const Text(
|
|
'Show modal bottom sheet',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
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),
|
|
builder: (context) {
|
|
return SizedBox(
|
|
height: 150,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
|
child: ListView(
|
|
shrinkWrap: true,
|
|
scrollDirection: Axis.horizontal,
|
|
children: buttonList,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
TextButton(
|
|
child: Text(
|
|
isNonModalBottomSheetOpen
|
|
? 'Hide bottom sheet'
|
|
: 'Show bottom sheet',
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
onPressed: () {
|
|
if (isNonModalBottomSheetOpen) {
|
|
_nonModalBottomSheetController?.close();
|
|
setState(() {
|
|
isNonModalBottomSheetOpen = false;
|
|
});
|
|
return;
|
|
} else {
|
|
setState(() {
|
|
isNonModalBottomSheetOpen = true;
|
|
});
|
|
}
|
|
|
|
_nonModalBottomSheetController = showBottomSheet<void>(
|
|
elevation: 8.0,
|
|
context: context,
|
|
// TODO: Remove when this is in the framework https://github.com/flutter/flutter/issues/118619
|
|
constraints: const BoxConstraints(maxWidth: 640),
|
|
builder: (context) {
|
|
return SizedBox(
|
|
height: 150,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
|
child: ListView(
|
|
shrinkWrap: true,
|
|
scrollDirection: Axis.horizontal,
|
|
children: buttonList,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class BottomAppBars extends StatelessWidget {
|
|
const BottomAppBars({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Bottom app bar',
|
|
tooltipMessage: 'Use BottomAppBar',
|
|
child: Column(
|
|
children: [
|
|
SizedBox(
|
|
height: 80,
|
|
child: Scaffold(
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () {},
|
|
elevation: 0.0,
|
|
child: const Icon(Icons.add),
|
|
),
|
|
floatingActionButtonLocation:
|
|
FloatingActionButtonLocation.endContained,
|
|
bottomNavigationBar: BottomAppBar(
|
|
child: Row(
|
|
children: <Widget>[
|
|
const IconButtonAnchorExample(),
|
|
IconButton(
|
|
tooltip: 'Search',
|
|
icon: const Icon(Icons.search),
|
|
onPressed: () {},
|
|
),
|
|
IconButton(
|
|
tooltip: 'Favorite',
|
|
icon: const Icon(Icons.favorite),
|
|
onPressed: () {},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class IconButtonAnchorExample extends StatelessWidget {
|
|
const IconButtonAnchorExample({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MenuAnchor(
|
|
builder: (context, controller, child) {
|
|
return IconButton(
|
|
onPressed: () {
|
|
if (controller.isOpen) {
|
|
controller.close();
|
|
} else {
|
|
controller.open();
|
|
}
|
|
},
|
|
icon: const Icon(Icons.more_vert),
|
|
);
|
|
},
|
|
menuChildren: [
|
|
MenuItemButton(
|
|
child: const Text('Menu 1'),
|
|
onPressed: () {},
|
|
),
|
|
MenuItemButton(
|
|
child: const Text('Menu 2'),
|
|
onPressed: () {},
|
|
),
|
|
SubmenuButton(
|
|
menuChildren: <Widget>[
|
|
MenuItemButton(
|
|
onPressed: () {},
|
|
child: const Text('Menu 3.1'),
|
|
),
|
|
MenuItemButton(
|
|
onPressed: () {},
|
|
child: const Text('Menu 3.2'),
|
|
),
|
|
MenuItemButton(
|
|
onPressed: () {},
|
|
child: const Text('Menu 3.3'),
|
|
),
|
|
],
|
|
child: const Text('Menu 3'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class ButtonAnchorExample extends StatelessWidget {
|
|
const ButtonAnchorExample({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MenuAnchor(
|
|
builder: (context, controller, child) {
|
|
return FilledButton.tonal(
|
|
onPressed: () {
|
|
if (controller.isOpen) {
|
|
controller.close();
|
|
} else {
|
|
controller.open();
|
|
}
|
|
},
|
|
child: const Text('Show menu'),
|
|
);
|
|
},
|
|
menuChildren: [
|
|
MenuItemButton(
|
|
leadingIcon: const Icon(Icons.people_alt_outlined),
|
|
child: const Text('Item 1'),
|
|
onPressed: () {},
|
|
),
|
|
MenuItemButton(
|
|
leadingIcon: const Icon(Icons.remove_red_eye_outlined),
|
|
child: const Text('Item 2'),
|
|
onPressed: () {},
|
|
),
|
|
MenuItemButton(
|
|
leadingIcon: const Icon(Icons.refresh),
|
|
onPressed: () {},
|
|
child: const Text('Item 3'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class NavigationDrawers extends StatelessWidget {
|
|
const NavigationDrawers({super.key, required this.scaffoldKey});
|
|
final GlobalKey<ScaffoldState> scaffoldKey;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Navigation drawer',
|
|
tooltipMessage:
|
|
'Use NavigationDrawer. For modal navigation drawers, see Scaffold.endDrawer',
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 520, child: NavigationDrawerSection()),
|
|
colDivider,
|
|
colDivider,
|
|
TextButton(
|
|
child: const Text('Show modal navigation drawer',
|
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
|
onPressed: () {
|
|
scaffoldKey.currentState!.openEndDrawer();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class NavigationDrawerSection extends StatefulWidget {
|
|
const NavigationDrawerSection({super.key});
|
|
|
|
@override
|
|
State<NavigationDrawerSection> createState() =>
|
|
_NavigationDrawerSectionState();
|
|
}
|
|
|
|
class _NavigationDrawerSectionState extends State<NavigationDrawerSection> {
|
|
int navDrawerIndex = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return NavigationDrawer(
|
|
onDestinationSelected: (selectedIndex) {
|
|
setState(() {
|
|
navDrawerIndex = selectedIndex;
|
|
});
|
|
},
|
|
selectedIndex: navDrawerIndex,
|
|
children: <Widget>[
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(28, 16, 16, 10),
|
|
child: Text(
|
|
'Mail',
|
|
style: Theme.of(context).textTheme.titleSmall,
|
|
),
|
|
),
|
|
...destinations.map((destination) {
|
|
return NavigationDrawerDestination(
|
|
label: Text(destination.label),
|
|
icon: destination.icon,
|
|
selectedIcon: destination.selectedIcon,
|
|
);
|
|
}),
|
|
const Divider(indent: 28, endIndent: 28),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(28, 16, 16, 10),
|
|
child: Text(
|
|
'Labels',
|
|
style: Theme.of(context).textTheme.titleSmall,
|
|
),
|
|
),
|
|
...labelDestinations.map((destination) {
|
|
return NavigationDrawerDestination(
|
|
label: Text(destination.label),
|
|
icon: destination.icon,
|
|
selectedIcon: destination.selectedIcon,
|
|
);
|
|
}),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class ExampleDestination {
|
|
const ExampleDestination(this.label, this.icon, this.selectedIcon);
|
|
|
|
final String label;
|
|
final Widget icon;
|
|
final Widget selectedIcon;
|
|
}
|
|
|
|
const List<ExampleDestination> destinations = <ExampleDestination>[
|
|
ExampleDestination('Inbox', Icon(Icons.inbox_outlined), Icon(Icons.inbox)),
|
|
ExampleDestination('Outbox', Icon(Icons.send_outlined), Icon(Icons.send)),
|
|
ExampleDestination(
|
|
'Favorites', Icon(Icons.favorite_outline), Icon(Icons.favorite)),
|
|
ExampleDestination('Trash', Icon(Icons.delete_outline), Icon(Icons.delete)),
|
|
];
|
|
|
|
const List<ExampleDestination> labelDestinations = <ExampleDestination>[
|
|
ExampleDestination(
|
|
'Family', Icon(Icons.bookmark_border), Icon(Icons.bookmark)),
|
|
ExampleDestination(
|
|
'School', Icon(Icons.bookmark_border), Icon(Icons.bookmark)),
|
|
ExampleDestination('Work', Icon(Icons.bookmark_border), Icon(Icons.bookmark)),
|
|
];
|
|
|
|
class NavigationRails extends StatelessWidget {
|
|
const NavigationRails({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const ComponentDecoration(
|
|
label: 'Navigation rail',
|
|
tooltipMessage: 'Use NavigationRail',
|
|
child: IntrinsicWidth(
|
|
child: SizedBox(height: 420, child: NavigationRailSection())),
|
|
);
|
|
}
|
|
}
|
|
|
|
class NavigationRailSection extends StatefulWidget {
|
|
const NavigationRailSection({super.key});
|
|
|
|
@override
|
|
State<NavigationRailSection> createState() => _NavigationRailSectionState();
|
|
}
|
|
|
|
class _NavigationRailSectionState extends State<NavigationRailSection> {
|
|
int navRailIndex = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return NavigationRail(
|
|
onDestinationSelected: (selectedIndex) {
|
|
setState(() {
|
|
navRailIndex = selectedIndex;
|
|
});
|
|
},
|
|
elevation: 4,
|
|
leading: FloatingActionButton(
|
|
child: const Icon(Icons.create), onPressed: () {}),
|
|
groupAlignment: 0.0,
|
|
selectedIndex: navRailIndex,
|
|
labelType: NavigationRailLabelType.selected,
|
|
destinations: <NavigationRailDestination>[
|
|
...destinations.map((destination) {
|
|
return NavigationRailDestination(
|
|
label: Text(destination.label),
|
|
icon: destination.icon,
|
|
selectedIcon: destination.selectedIcon,
|
|
);
|
|
}),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class Tabs extends StatefulWidget {
|
|
const Tabs({super.key});
|
|
|
|
@override
|
|
State<Tabs> createState() => _TabsState();
|
|
}
|
|
|
|
class _TabsState extends State<Tabs> with TickerProviderStateMixin {
|
|
late TabController _tabController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 3, vsync: this);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Tabs',
|
|
tooltipMessage: 'Use TabBar',
|
|
child: SizedBox(
|
|
height: 80,
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
bottom: TabBar(
|
|
controller: _tabController,
|
|
tabs: const <Widget>[
|
|
Tab(
|
|
icon: Icon(Icons.videocam_outlined),
|
|
text: 'Video',
|
|
iconMargin: EdgeInsets.only(bottom: 0.0),
|
|
),
|
|
Tab(
|
|
icon: Icon(Icons.photo_outlined),
|
|
text: 'Photos',
|
|
iconMargin: EdgeInsets.only(bottom: 0.0),
|
|
),
|
|
Tab(
|
|
icon: Icon(Icons.audiotrack_sharp),
|
|
text: 'Audio',
|
|
iconMargin: EdgeInsets.only(bottom: 0.0),
|
|
),
|
|
],
|
|
),
|
|
// TODO: Showcase secondary tab bar https://github.com/flutter/flutter/issues/111962
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class TopAppBars extends StatelessWidget {
|
|
const TopAppBars({super.key});
|
|
|
|
static final actions = [
|
|
IconButton(icon: const Icon(Icons.attach_file), onPressed: () {}),
|
|
IconButton(icon: const Icon(Icons.event), onPressed: () {}),
|
|
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
|
|
];
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Top app bars',
|
|
tooltipMessage:
|
|
'Use AppBar, SliverAppBar, SliverAppBar.medium, or SliverAppBar.large',
|
|
child: Column(
|
|
children: [
|
|
AppBar(
|
|
title: const Text('Center-aligned'),
|
|
leading: const BackButton(),
|
|
actions: [
|
|
IconButton(
|
|
iconSize: 32,
|
|
icon: const Icon(Icons.account_circle_outlined),
|
|
onPressed: () {},
|
|
),
|
|
],
|
|
centerTitle: true,
|
|
),
|
|
colDivider,
|
|
AppBar(
|
|
title: const Text('Small'),
|
|
leading: const BackButton(),
|
|
actions: actions,
|
|
centerTitle: false,
|
|
),
|
|
colDivider,
|
|
SizedBox(
|
|
height: 100,
|
|
child: CustomScrollView(
|
|
slivers: [
|
|
SliverAppBar.medium(
|
|
title: const Text('Medium'),
|
|
leading: const BackButton(),
|
|
actions: actions,
|
|
),
|
|
const SliverFillRemaining(),
|
|
],
|
|
),
|
|
),
|
|
colDivider,
|
|
SizedBox(
|
|
height: 130,
|
|
child: CustomScrollView(
|
|
slivers: [
|
|
SliverAppBar.large(
|
|
title: const Text('Large'),
|
|
leading: const BackButton(),
|
|
actions: actions,
|
|
),
|
|
const SliverFillRemaining(),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class Menus extends StatefulWidget {
|
|
const Menus({super.key});
|
|
|
|
@override
|
|
State<Menus> createState() => _MenusState();
|
|
}
|
|
|
|
class _MenusState extends State<Menus> {
|
|
final TextEditingController colorController = TextEditingController();
|
|
final TextEditingController iconController = TextEditingController();
|
|
IconLabel? selectedIcon = IconLabel.smile;
|
|
ColorLabel? selectedColor;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final List<DropdownMenuEntry<ColorLabel>> colorEntries =
|
|
<DropdownMenuEntry<ColorLabel>>[];
|
|
for (final ColorLabel color in ColorLabel.values) {
|
|
colorEntries.add(DropdownMenuEntry<ColorLabel>(
|
|
value: color, label: color.label, enabled: color.label != 'Grey'));
|
|
}
|
|
|
|
final List<DropdownMenuEntry<IconLabel>> iconEntries =
|
|
<DropdownMenuEntry<IconLabel>>[];
|
|
for (final IconLabel icon in IconLabel.values) {
|
|
iconEntries
|
|
.add(DropdownMenuEntry<IconLabel>(value: icon, label: icon.label));
|
|
}
|
|
|
|
return ComponentDecoration(
|
|
label: 'Menus',
|
|
tooltipMessage: 'Use MenuAnchor or DropdownMenu<T>',
|
|
child: Column(
|
|
children: [
|
|
const Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
ButtonAnchorExample(),
|
|
rowDivider,
|
|
IconButtonAnchorExample(),
|
|
],
|
|
),
|
|
colDivider,
|
|
Wrap(
|
|
alignment: WrapAlignment.spaceAround,
|
|
runAlignment: WrapAlignment.center,
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
spacing: smallSpacing,
|
|
runSpacing: smallSpacing,
|
|
children: [
|
|
DropdownMenu<ColorLabel>(
|
|
controller: colorController,
|
|
label: const Text('Color'),
|
|
enableFilter: true,
|
|
dropdownMenuEntries: colorEntries,
|
|
inputDecorationTheme: const InputDecorationTheme(filled: true),
|
|
onSelected: (color) {
|
|
setState(() {
|
|
selectedColor = color;
|
|
});
|
|
},
|
|
),
|
|
DropdownMenu<IconLabel>(
|
|
initialSelection: IconLabel.smile,
|
|
controller: iconController,
|
|
leadingIcon: const Icon(Icons.search),
|
|
label: const Text('Icon'),
|
|
dropdownMenuEntries: iconEntries,
|
|
onSelected: (icon) {
|
|
setState(() {
|
|
selectedIcon = icon;
|
|
});
|
|
},
|
|
),
|
|
Icon(
|
|
selectedIcon?.icon,
|
|
color: selectedColor?.color ?? Colors.grey.withOpacity(0.5),
|
|
)
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
enum ColorLabel {
|
|
blue('Blue', Colors.blue),
|
|
pink('Pink', Colors.pink),
|
|
green('Green', Colors.green),
|
|
yellow('Yellow', Colors.yellow),
|
|
grey('Grey', Colors.grey);
|
|
|
|
const ColorLabel(this.label, this.color);
|
|
final String label;
|
|
final Color color;
|
|
}
|
|
|
|
enum IconLabel {
|
|
smile('Smile', Icons.sentiment_satisfied_outlined),
|
|
cloud(
|
|
'Cloud',
|
|
Icons.cloud_outlined,
|
|
),
|
|
brush('Brush', Icons.brush_outlined),
|
|
heart('Heart', Icons.favorite);
|
|
|
|
const IconLabel(this.label, this.icon);
|
|
final String label;
|
|
final IconData icon;
|
|
}
|
|
|
|
class Sliders extends StatefulWidget {
|
|
const Sliders({super.key});
|
|
|
|
@override
|
|
State<Sliders> createState() => _SlidersState();
|
|
}
|
|
|
|
class _SlidersState extends State<Sliders> {
|
|
double sliderValue0 = 30.0;
|
|
double sliderValue1 = 20.0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ComponentDecoration(
|
|
label: 'Sliders',
|
|
tooltipMessage: 'Use Slider or RangeSlider',
|
|
child: Column(
|
|
children: <Widget>[
|
|
Slider(
|
|
max: 100,
|
|
value: sliderValue0,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
sliderValue0 = value;
|
|
});
|
|
},
|
|
),
|
|
const SizedBox(height: 20),
|
|
Slider(
|
|
max: 100,
|
|
divisions: 5,
|
|
value: sliderValue1,
|
|
label: sliderValue1.round().toString(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
sliderValue1 = value;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
));
|
|
}
|
|
}
|
|
|
|
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,
|
|
required this.label,
|
|
required this.child,
|
|
this.tooltipMessage = '',
|
|
});
|
|
|
|
final String label;
|
|
final Widget child;
|
|
final String? tooltipMessage;
|
|
|
|
@override
|
|
State<ComponentDecoration> createState() => _ComponentDecorationState();
|
|
}
|
|
|
|
class _ComponentDecorationState extends State<ComponentDecoration> {
|
|
final focusNode = FocusNode();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return RepaintBoundary(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: smallSpacing),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(widget.label,
|
|
style: Theme.of(context).textTheme.titleSmall),
|
|
Tooltip(
|
|
message: widget.tooltipMessage,
|
|
child: const Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
|
child: Icon(Icons.info_outline, size: 16)),
|
|
),
|
|
],
|
|
),
|
|
ConstrainedBox(
|
|
constraints:
|
|
const BoxConstraints.tightFor(width: widthConstraint),
|
|
// Tapping within the a component card should request focus
|
|
// for that component's children.
|
|
child: Focus(
|
|
focusNode: focusNode,
|
|
canRequestFocus: true,
|
|
child: GestureDetector(
|
|
onTapDown: (_) {
|
|
focusNode.requestFocus();
|
|
},
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Card(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
side: BorderSide(
|
|
color: Theme.of(context).colorScheme.outlineVariant,
|
|
),
|
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 5.0, vertical: 20.0),
|
|
child: Center(
|
|
child: widget.child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ComponentGroupDecoration extends StatelessWidget {
|
|
const ComponentGroupDecoration(
|
|
{super.key, required this.label, required this.children});
|
|
|
|
final String label;
|
|
final List<Widget> children;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Fully traverse this component group before moving on
|
|
return FocusTraversalGroup(
|
|
child: Card(
|
|
margin: EdgeInsets.zero,
|
|
elevation: 0,
|
|
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
|
child: Center(
|
|
child: Column(
|
|
children: [
|
|
Text(label, style: Theme.of(context).textTheme.titleLarge),
|
|
colDivider,
|
|
...children
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|