diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 28c9f2c..dd7651e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,7 +27,7 @@ on: paths-ignore: - "**.md" release: - branches: [none] + branches: [master] types: [published] jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a693e..eaf0505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. +## 3.2.0 + +**Aug 27, 2024** + + +**CHANGE** + +* The `FlexPaletteType.extended` tones got three new tones, tones 65, 75 and 84. It now has 30 tones. + +**NEW** + +* The `tones` configuration class `FlexTones` got a new modifier, `higherContrastFixed()`. It can be applied to any predefined or custom `FlexTones` to make a returned `FlexTones` instance where the tones for the fixed colors `fixed`, `onFixed`, `fixedDim` and `onFixedVariant` are set to **92, 6, 84 and 12** instead of their Material-3 specification tones **90, 10, 80 and 30**. This for an alternative set of fixed colors with more contrast. + + ## 3.1.2 **July 23, 2024** diff --git a/README.md b/README.md index bcf4947..4ead729 100644 --- a/README.md +++ b/README.md @@ -235,15 +235,15 @@ const FlexTones myDarkTones = FlexTones.dark( ## Extended Palette -The extended palette type is the new default, if you are using Flutter 3.22, or later you should only use it. The `common` tones option is provided for backwards compatibility with older Flutter versions and older FSS versions. +The extended palette type is the new default, it contains 30 tones. If you are using Flutter 3.22, or later you should only use it. The `common` tones option is provided for backwards compatibility with older Flutter versions and older FSS versions. By using `paletteType` with value `FlexPaletteType.extended`, you can create seed generated `ColorScheme`s that use and access new color tones that exists in the late 2022 revised `ColorScheme` for surface colors and even more colors for **fixed** and **fixedDim** main colors that arrived in the Material-3 design during later half of 2023. The `ColorScheme` colors that use these new tones are now also available in Flutter 3.22 or later. For more information and the latest updates, see [Material-3 color-roles](https://m3.material.io/styles/color/the-color-system/color-roles) specification. -The updated Material-3 color system adds tones `[4, 6, 12, 17, 22, 24]`, they are used for new dark mode surfaces in revised Material-3 dark surface colors. Likewise, the added tones `[87, 92, 94, 96, 98]` are for light mode surfaces in the updated Material-3 color system. By default `paletteType` of `FlexTones.extended` is now used to enable support for the tones in the updated specification and also adding three more custom tones `[2, 5, 97]`. The `paletteType` with value `FlexPaletteType.extended` is now default, it produces 27 tones `[0, 2, 4, 5, 6, 10, 12, 17, 20, 22, 24, 30, 40, 50, 60, 70, 80, 87, 90, 92, 94, 95, 96, 97, 98, 99, 100]`. +The updated Material-3 color system adds tones `[4, 6, 12, 17, 22, 24]`, they are used for new dark mode surfaces in revised Material-3 dark surface colors. Likewise, the added tones `[87, 92, 94, 96, 98]` are for light mode surfaces in the updated Material-3 color system. By default `paletteType` of `FlexTones.extended` is now used to enable support for the tones in the updated specification and also adding six more custom tones `[2, 5, 65, 75, 84, 97]`. The `paletteType` with value `FlexPaletteType.extended` is default. It produces the following 30 tones `[0, 2, 4, 5, 6, 10, 12, 17, 20, 22, 24, 30, 40, 50, 60, 65, 70, 75, 80, 84, 87, 90, 92, 94, 95, 96, 97, 98, 99, 100]`. -To use the older classic setup you can still use `FlexTones.common`. It produces the legacy M3 tones with its own two additions `[5]` and `[98]` resulting in 15 tones `[0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100]`. Flutter versions before 3.22 do not yet use these new tones in its standard `ColorScheme`. +To use older simpler tones setup you can still use `FlexTones.common`. It produces the legacy M3 tones with its own two additions `[5]` and `[98]` resulting in 15 tones `[0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100]`. Flutter versions before 3.22 do not yet use any of the extended tones in its standard `ColorScheme`. For backwards compatibility, when using type `FlexPaletteType.common` the chroma of high tones, meaning higher or equal to 90, are limited to maximum chroma of 40. This keeps the chromacity of tones 90 to 100 lower than or equal to 40. If the source seed color has higher chromacity than 40, there may be a sudden jump in chroma reduction at tone 90. This is the standard behavior for the original Material-3 tonal palette computation. The `FlexPaletteType.common` type is intended to be used when there is a need to follow the M3's original, now legacy, palette design. @@ -344,7 +344,6 @@ The surface on colors made black or white by `onSurfacesUseBW()` are: * `onSurface` * `onSurfaceVariant` * `onInverseSurface` -* `onBackground` (deprecated in Flutter 3.22) Here is a usage example, using both these modifiers. You can use them individually too, and you don't have to use them in both light and dark mode. @@ -379,23 +378,12 @@ final ColorScheme schemeDarkOnBW = SeedColorScheme.fromSeeds( #### FlexTones Modifier `surfacesUseBW()` -Another `FlexTones` modifier is `surfacesUseBW()`. This modifier will make the `surface` and `background` colors plain white in light mode and true black in dark mode. - -The surface colors made black or white by `surfacesUseBW()` are: - -* `surface` -* `background` (deprecated in Flutter 3.22) - -This modifier can be used for great effect in light mode, as you can remove the colored background surfaces from any of the `FlexTones` seeding strategies. Some designs may prefer plain white, for backgrounds in light mode, for a more platform-agnostic design. - -In dark mode `surfacesUseBW()` can be used create seeded color schemes with full black background and surface colors, but you may prefer to keep the primary seed color based slightly primary color tinted backgrounds in dark mode. - -This modifier will make the `surface` color and still also the deprecated `background` color plain white in light mode and full black in dark mode. +Another simple `FlexTones` modifier is `surfacesUseBW()`. This modifier will make the `surface` color plain white in light mode and full black in dark mode. ```dart // Make a Material 3 seeded light ColorScheme, but with always -// black and white contrasting onColors and ensure that background -// and surface colors are always white. +// black and white contrasting onColors and ensure that +// the surface color is always white. final ColorScheme schemeLightOnBW = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -410,14 +398,13 @@ final ColorScheme schemeLightOnBW = SeedColorScheme.fromSeeds( #### FlexTones Modifier `monochromeSurfaces()` -A new `FlexTones` modifier in FSS version 3.0.0 is `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make all the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes. Surface colors will then have no color tint from their own key color or from the primary seed key color. For those tired of tinted surface colors in Material-3, this is a useful helper. +A `FlexTones` modifier available in FSS version 3.0.0 and later. It can be applied to any predefined or custom `FlexTones` to make all the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes. Surface colors will then have no color tint from their own key color or from the primary seed key color. For those tired of tinted surface colors in Material-3, this is a useful helper. The surface colors made monochrome by `monochromeSurfaces()` are: * `surface`, `onSurface`, `surfaceContainer`, `onSurfaceVariant` * `surfaceContainerHighest`, `surfaceContainerHigh`, `surfaceContainerLow`, `surfaceContainerLowest` * `surfaceDim`,`surfaceBright`, `inverseSurface`, `onInverseSurface` * `outline`, `outlineVariant` -* `background`, `onBackground`, `surfaceVariant` (deprecated in Flutter 3.22) ```dart // Make a vivid Material 3 seeded light ColorScheme, where all surface colors @@ -431,6 +418,32 @@ final ColorScheme schemeLight = SeedColorScheme.fromSeeds( ); ``` +#### FlexTones Modifier `higherContrastFixed()` + +A `FlexTones` modifier available in FSS version 3.2.0 and later. +This modifier can be applied to any predefined or custom +`FlexTones` to make a returned instance where the tones for +the fixed colors `fixed`, `onFixed`, `fixedDim`, `onFixedVariant` are +set to 92, 6, 84 and 12, instead Material-3 designs specified 90, 10, 80 and 30. + +This gives us an alternative set of fixed colors with more contrast. + + +```dart +// Make an ultraContrast seeded light ColorScheme, where all fixed colors +// have higher contrast than the standard Material-3 design. +// +// It is here combined `FlexTones.ultraContrast` to make also the fixed and +// fixedDim colors and their on colors have higher contrast too. +final ColorScheme schemeLight = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + tones: FlexTones.ultraContrast(Brightness.light).higherContrastFixed(), +); +``` + ## Expressive On Container Colors By setting `useExpressiveOnContainerColors` to true in `SeedColorScheme.fromSeeds` you can opt in on using the new Material expressive on-colors specification for none surface on-container colors in light theme mode. diff --git a/example/lib/about/views/about.dart b/example/lib/about/views/about.dart index 9db70a0..70ba33c 100644 --- a/example/lib/about/views/about.dart +++ b/example/lib/about/views/about.dart @@ -54,7 +54,8 @@ void showAppAboutDialog(BuildContext context) { TextSpan( style: aboutTextStyle, text: 'The ${AppData.title(context)} application demonstrates ' - 'features of the ${AppData.packageName} custom key colors ' + 'features\n' + 'of the ${AppData.packageName} custom key colors ' 'ColorScheme generation package.\n\n' 'To learn more, check out the package on ', ), @@ -65,7 +66,7 @@ void showAppAboutDialog(BuildContext context) { ), TextSpan( style: aboutTextStyle, - text: '. It also includes the source ' + text: '.\nIt also includes the source ' 'code of this application.\n\n', ), TextSpan( diff --git a/example/lib/core/constants/app_data.dart b/example/lib/core/constants/app_data.dart index cd5bd97..ed5251c 100644 --- a/example/lib/core/constants/app_data.dart +++ b/example/lib/core/constants/app_data.dart @@ -24,14 +24,14 @@ sealed class AppData { // Version of the WEB build, usually same as package, but it also has a // build numbers. static const String versionMajor = '3'; - static const String versionMinor = '1'; - static const String versionPatch = '2'; + static const String versionMinor = '2'; + static const String versionPatch = '0'; static const String versionBuild = '01'; static const String version = '$versionMajor.$versionMinor.$versionPatch ' 'Build-$versionBuild'; static const String packageVersion = '$versionMajor.$versionMinor.$versionPatch'; - static const String flutterVersion = '3.22.3 (canvaskit)'; + static const String flutterVersion = '3.24.1 (canvaskit)'; static const String copyright = '© 2022-2024'; static const String author = 'Mike Rydstrom'; static const String license = 'BSD 3-Clause License'; diff --git a/example/lib/core/views/universal/list_tile_reveal.dart b/example/lib/core/views/universal/list_tile_reveal.dart index e9fbb28..44b9698 100644 --- a/example/lib/core/views/universal/list_tile_reveal.dart +++ b/example/lib/core/views/universal/list_tile_reveal.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; /// A custom [ListTile] that has a built-in animated custom leading action /// after the [leading] widget built in as a part of [title] that -/// reveals the [subtitle] when clicked. +/// reveals the [subtitleReveal] when clicked. /// /// This is useful when a more compact look is desired where more information /// is provided as an optional user based reveal action. The purpose is to make @@ -16,11 +16,12 @@ class ListTileReveal extends StatefulWidget { this.title, this.leading, this.subtitle, + this.subtitleReveal, this.trailing, this.contentPadding, this.onTap, this.dense, - this.subtitleDense, + this.revealDense, this.enabled = true, this.isOpen, this.duration = const Duration(milliseconds: 200), @@ -44,6 +45,11 @@ class ListTileReveal extends StatefulWidget { /// Typically a [Text] widget. final Widget? subtitle; + /// Additional content displayed below the subtitle in a reveal animation. + /// + /// Typically a [Text] widget. + final Widget? subtitleReveal; + /// A widget to display after the title. /// /// Typically an [Icon] widget. @@ -57,7 +63,8 @@ class ListTileReveal extends StatefulWidget { /// The [ListTileReveal]'s internal padding. /// - /// Insets a [ListTileReveal]'s contents: its [leading], [title], [subtitle], + /// Insets a [ListTileReveal]'s contents: its [leading], [title], + /// [subtitleReveal], /// and [trailing] widgets. /// /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. @@ -78,13 +85,10 @@ class ListTileReveal extends StatefulWidget { /// Dense list tiles default to a smaller height. final bool? dense; - /// Whether this list tile subtitle is dense. - /// - /// Dense list tiles default to a smaller height. The subtitle is also dense - /// if dense is true. + /// Whether the used reveal part of the ListTile is dense. /// - /// If not defined defaults to false. - final bool? subtitleDense; + /// If not defined, defaults to true. + final bool? revealDense; /// Set to true to open the info section of the ListTile, to false to close /// it. @@ -133,7 +137,7 @@ class _ListTileRevealState extends State { crossAxisAlignment: WrapCrossAlignment.center, children: [ if (widget.title != null) widget.title!, - if (widget.subtitle != null && widget.enabled) + if (widget.subtitleReveal != null && widget.enabled) IconButton( iconSize: 20, // ignore: avoid_bool_literals_in_conditional_expressions @@ -144,6 +148,7 @@ class _ListTileRevealState extends State { ), ], ), + subtitle: widget.subtitle, trailing: widget.trailing, onTap: widget.enabled ? widget.onTap : null, ), @@ -156,11 +161,10 @@ class _ListTileRevealState extends State { child: child, ); }, - child: (_isOpen && widget.subtitle != null && widget.enabled) + child: (_isOpen && widget.subtitleReveal != null && widget.enabled) ? ListTile( - dense: (widget.dense ?? false) || - (widget.subtitleDense ?? false), - subtitle: widget.subtitle, + dense: widget.revealDense ?? true, + subtitle: widget.subtitleReveal, onTap: widget.enabled ? _handleTap : null, ) : const SizedBox.shrink(), diff --git a/example/lib/core/views/universal/showcase_material.dart b/example/lib/core/views/universal/showcase_material.dart index 5b0f8f5..a706c7e 100644 --- a/example/lib/core/views/universal/showcase_material.dart +++ b/example/lib/core/views/universal/showcase_material.dart @@ -4,6 +4,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'switch_list_tile_reveal.dart'; + /// Used to show the current theme on Material widgets. /// /// Use this widget to review your theme's impact on [ThemeData] and see @@ -24,10 +26,16 @@ class ShowcaseMaterial extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); + const TextStyle headerStyle = TextStyle(fontSize: 16); return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ + // + // Buttons + // + const Text('Material Buttons', style: headerStyle), + const SizedBox(height: 8), const ElevatedButtonShowcase(), const SizedBox(height: 8), const FilledButtonShowcase(), @@ -37,102 +45,217 @@ class ShowcaseMaterial extends StatelessWidget { const OutlinedButtonShowcase(), const SizedBox(height: 8), const TextButtonShowcase(), + const SizedBox(height: 16), + // + // ToggleButtons and SegmentedButton + // + const Text('ToggleButtons', style: headerStyle), const SizedBox(height: 8), const ToggleButtonsShowcase(), + const SizedBox(height: 16), + const Text('SegmentedButton', style: headerStyle), + const SizedBox(height: 8), + const SegmentedButtonShowcase(showOutlinedButton: false), + const SizedBox(height: 16), + // + // FloatingActionButton and Chip + // + const Text('FloatingActionButton', style: headerStyle), const SizedBox(height: 8), - const SegmentedButtonShowcase(), - const Divider(), const FabShowcase(), const SizedBox(height: 16), - const ChipShowcase(), - const Divider(), - const SwitchShowcase(showCupertinoSwitches: false), - const CheckboxShowcase(), - const RadioShowcase(), + const Text('Chips', style: headerStyle), const SizedBox(height: 8), - const Divider(), + const ChipShowcase(showOptions: true), + const SizedBox(height: 16), + // + // Switch, CheckBox and Radio + // + const Text('Switch', style: headerStyle), const SizedBox(height: 8), - const TooltipShowcase(), + const SwitchShowcase(showCupertinoSwitches: true), + const SizedBox(height: 16), + const Text('Checkbox', style: headerStyle), + const SizedBox(height: 8), + const CheckboxShowcase(), const SizedBox(height: 16), - const IconButtonCircleAvatarDropdownShowcase(), + const Text('Radio', style: headerStyle), + const SizedBox(height: 8), + const RadioShowcase(), + const SizedBox(height: 16), + // + // Icon + // + const Text('Icon', style: headerStyle), + const SizedBox(height: 16), + const IconShowcase(), + const SizedBox(height: 16), + // + // IconButton + // + const Text('IconButton', style: headerStyle), const SizedBox(height: 16), const IconButtonShowcase(), const SizedBox(height: 16), - const ProgressIndicatorShowcase(), - const Divider(), - const SizedBox(height: 8), - const TextInputField(), + const IconButtonVariantsShowcase(), + const SizedBox(height: 16), + // + // CircleAvatar + // + const Text('CircleAvatar', style: headerStyle), + const SizedBox(height: 16), + const CircleAvatarShowcase(), + const SizedBox(height: 16), + // + // Tooltip + // + const Text('Tooltip', style: headerStyle), const SizedBox(height: 8), - const DropDownButtonFormField(), + const TooltipShowcase(), + const SizedBox(height: 16), + // + // ProgressIndicator + // + const Text('ProgressIndicator', style: headerStyle), const SizedBox(height: 8), - const PopupMenuButtonsShowcase(), + const ProgressIndicatorShowcase(), + const SizedBox(height: 16), + // + // Slider and RangeSlider + // + const Text('Slider and RangeSlider', style: headerStyle), const SizedBox(height: 8), + const SliderShowcase(), const Divider(), - const DropDownMenuShowcase(explainUsage: true), - const MenuBarShowcase(), - const MenuAnchorShowcase(), + const RangeSliderShowcase(), const SizedBox(height: 8), - const Divider(), - const SliderShowcase(), + // + // TextField + // + const Text('TextField', style: headerStyle), const SizedBox(height: 8), - const RangeSliderShowcase(), + const TextFieldShowcase(), + const SizedBox(height: 16), + // + // PopupMenuButton, DropdownButtonFormField, DropDownButton + // + const Text('PopupMenuButton', style: headerStyle), + const PopupMenuButtonsShowcase(explain: true), + const SizedBox(height: 16), + const Text('DropdownButtons', style: headerStyle), + const SizedBox(height: 8), + const DropDownButtonShowcase(explain: true), + const SizedBox(height: 8), + const DropdownButtonFormFieldShowcase(explain: true), + const SizedBox(height: 24), + // + // DropdownMenu, MenuBar, MenuAnchor + // + const Text('DropdownMenu, MenuAnchor and MenuBar', style: headerStyle), + const DropDownMenuShowcase(explain: true), + const MenuAnchorShowcase(explain: true), + const MenuBarShowcase(explain: true), + const SizedBox(height: 16), + // + // AppBars and TabBar + // + const Text('AppBar', style: headerStyle), const SizedBox(height: 8), - const Divider(), - const ListTileAllShowcase(), - const Divider(), - const ExpansionTileShowcase(), - const Divider(), - const ExpansionPanelListShowcase(), - const Divider(), const AppBarShowcase(), - const Divider(), - const SearchBarShowcase(), - const Divider(), - const BottomAppBarShowcase(), - const Divider(), - const TabBarForAppBarShowcase(), + const SizedBox(height: 16), + const Text('TabBar', style: headerStyle), const SizedBox(height: 8), - const Divider(), - const TabBarForBackgroundShowcase(), const SizedBox(height: 8), - const Divider(), - const BottomNavigationBarShowcase(), + const TabBarForAppBarShowcase(explain: true), const SizedBox(height: 8), - const NavigationBarShowcase(), + const TabBarForBackgroundShowcase(explain: true), + const SizedBox(height: 16), + // + // BottomAppBar and SearchBar + // + const Text('BottomAppBar and SearchBar', style: headerStyle), + const BottomAppBarShowcase(explain: true), + const SearchBarShowcase(explain: true), + const SizedBox(height: 16), + // + // BottomNavigationBar (M2), NavigationBar (M3) + // NavigationRail, NavigationDrawer + // + const Text('Navigation Components', style: headerStyle), + const BottomNavigationBarShowcase(explain: true), const SizedBox(height: 8), - const Divider(), - const NavigationRailShowcase(), + const NavigationBarShowcase(explain: true), const SizedBox(height: 8), - const NavigationDrawerShowcase(), - const DrawerShowcase(), + const NavigationRailShowcase(explain: true), const SizedBox(height: 8), - const Divider(), + const NavigationDrawerShowcase(explain: true), + const DrawerShowcase(explain: true), + const SizedBox(height: 16), + // + // AlertDialog, TimePickerDialog, DatePickerDialog + // + const Text('Dialogs', style: headerStyle), const AlertDialogShowcase(), const TimePickerDialogShowcase(), const DatePickerDialogShowcase(), const SizedBox(height: 8), - const Divider(), + // + // BottomSheet + // + const Text('BottomSheet', style: headerStyle), const SizedBox(height: 16), const BottomSheetShowcase(), const SizedBox(height: 16), const BottomSheetModalShowcase(), - const SizedBox(height: 32), + const SizedBox(height: 16), + // + // SnackBar and MaterialBanner + // + const Text('SnackBar and MaterialBanner', style: headerStyle), + const SizedBox(height: 8), const MaterialBannerSnackBarShowcase(), - const MaterialShowcase(), - const Divider(height: 32), - const CardShowcase(), + // + // Card + // + const Text('Card', style: headerStyle), const SizedBox(height: 8), + const CardShowcase(explain: true), + const SizedBox(height: 16), + // + // Material + // + const Text('Material', style: headerStyle), + const MaterialShowcase(explain: true), + const SizedBox(height: 16), + // + // LisTile, SwitchListTile, CheckboxListTile, RadioListTile + // and more exotic + // ExpansionTile, ExpansionPanelList + // + const Text('All List Tiles', style: headerStyle), + const ListTileShowcase(), + const Divider(height: 1), + const SwitchListTileShowcase(), + const Divider(height: 1), + const CheckboxListTileShowcase(), + const Divider(height: 1), + const RadioListTileShowcase(), + const Divider(), + const ExpansionTileShowcase(), + const Divider(), + const ExpansionPanelListShowcase(), + const SizedBox(height: 32), + // + // TextTheme and PrimaryTextTheme + // + const Text('Text', style: headerStyle), Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Text('Normal TextTheme', - style: theme.textTheme.titleMedium), - ), + Text('TextTheme', style: theme.textTheme.titleMedium), const TextThemeShowcase(), ], ), @@ -146,16 +269,14 @@ class ShowcaseMaterial extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Text('Primary TextTheme', - style: theme.primaryTextTheme.titleMedium), - ), + Text('PrimaryTextTheme', + style: theme.primaryTextTheme.titleMedium), const PrimaryTextThemeShowcase(), ], ), ), ), + const SizedBox(height: 16), ], ); } @@ -491,45 +612,69 @@ class _SegmentedButtonShowcaseState extends State { } } -class FabShowcase extends StatelessWidget { +class FabShowcase extends StatefulWidget { const FabShowcase({super.key}); + @override + State createState() => _FabShowcaseState(); +} + +class _FabShowcaseState extends State { + bool extended = true; + @override Widget build(BuildContext context) { return RepaintBoundary( child: Wrap( crossAxisAlignment: WrapCrossAlignment.center, - spacing: 8, - runSpacing: 8, + spacing: 16, + runSpacing: 16, children: [ - FloatingActionButton.small( - heroTag: 'FAB small', - onPressed: () {}, - child: const Icon(Icons.accessibility), - ), - FloatingActionButton.extended( - heroTag: 'FAB extended false', - isExtended: false, - onPressed: () {}, - icon: const Icon(Icons.accessibility), - label: const Text('Extended'), + Tooltip( + verticalOffset: 40, + waitDuration: const Duration(milliseconds: 500), + message: 'FloatingActionButton.small', + child: FloatingActionButton.small( + heroTag: 'FAB small', + onPressed: () {}, + child: const Icon(Icons.accessibility), + ), ), - FloatingActionButton.extended( - heroTag: 'FAB extended true', - isExtended: true, - onPressed: () {}, - icon: const Icon(Icons.accessibility), - label: const Text('Extended'), + Tooltip( + verticalOffset: 40, + waitDuration: const Duration(milliseconds: 500), + message: 'FloatingActionButton', + child: FloatingActionButton( + heroTag: 'FAB standard', + onPressed: () {}, + child: const Icon(Icons.accessibility), + ), ), - FloatingActionButton( - heroTag: 'FAB standard', - onPressed: () {}, - child: const Icon(Icons.accessibility), + Tooltip( + verticalOffset: 40, + waitDuration: const Duration(milliseconds: 500), + message: 'FloatingActionButton.extended(isExtended: $extended)', + child: FloatingActionButton.extended( + heroTag: 'FAB extendable', + isExtended: extended, + onPressed: () { + setState(() { + extended = !extended; + }); + }, + icon: const Icon(Icons.accessibility), + label: const Text('Extended'), + ), ), - FloatingActionButton.large( - heroTag: 'FAB large', - onPressed: () {}, - child: const Icon(Icons.accessibility), + Tooltip( + verticalOffset: 60, + waitDuration: const Duration(milliseconds: 500), + message: 'FloatingActionButton.large', + child: FloatingActionButton.large( + heroTag: 'FAB large', + onPressed: () {}, + child: const Icon(Icons.accessibility), + ), ), ], ), @@ -548,90 +693,172 @@ class SwitchShowcase extends StatefulWidget { class _SwitchShowcaseState extends State { bool isOn1 = true; + static const double _width = 75; + @override Widget build(BuildContext context) { final ColorScheme colorScheme = Theme.of(context).colorScheme; final bool isLight = Theme.of(context).brightness == Brightness.light; return RepaintBoundary( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 8, - runSpacing: 8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.showCupertinoSwitches) const Text('M3:'), - Switch( - value: isOn1, - onChanged: (bool value) { - setState(() { - isOn1 = value; - }); - }, - ), - Switch( - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.contains(WidgetState.selected)) { - return Icon(Icons.check, - color: - isLight ? colorScheme.primary : colorScheme.onPrimary); - } - // All other states will use the default thumbIcon. - return Icon(Icons.close, color: colorScheme.onPrimary); - }), - value: isOn1, - onChanged: (bool value) { - setState(() { - isOn1 = value; - }); - }, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Material')), + Switch( + value: isOn1, + onChanged: (bool value) { + setState(() { + isOn1 = value; + }); + }, + ), + Switch( + value: !isOn1, + onChanged: (bool value) { + setState(() { + isOn1 = !value; + }); + }, + ), + Switch( + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.selected)) { + return Icon(Icons.check, + color: isLight + ? colorScheme.primary + : colorScheme.onPrimary); + } + // All other states will use the default thumbIcon. + return Icon(Icons.close, color: colorScheme.onPrimary); + }), + value: isOn1, + onChanged: (bool value) { + setState(() { + isOn1 = value; + }); + }, + ), + ], ), - Switch( - value: isOn1, - onChanged: null, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Disabled')), + Switch( + value: isOn1, + onChanged: null, + ), + Switch( + value: !isOn1, + onChanged: null, + ), + Switch( + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.selected)) { + return Icon(Icons.check, + color: isLight + ? colorScheme.primary + : colorScheme.onPrimary); + } + // All other states will use the default thumbIcon. + return Icon(Icons.close, color: colorScheme.onPrimary); + }), + value: isOn1, + onChanged: null, + ), + ], ), - Switch( - value: !isOn1, - onChanged: (bool value) { - setState(() { - isOn1 = !value; - }); - }, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Adaptive')), + Switch.adaptive( + value: isOn1, + onChanged: (bool value) { + setState(() { + isOn1 = value; + }); + }, + ), + Switch.adaptive( + value: !isOn1, + onChanged: (bool value) { + setState(() { + isOn1 = !value; + }); + }, + ), + ], ), - Switch( - value: !isOn1, - onChanged: null, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Disabled')), + Switch.adaptive( + value: isOn1, + onChanged: null, + ), + Switch.adaptive( + value: !isOn1, + onChanged: null, + ), + ], ), - if (widget.showCupertinoSwitches) ...[ - const Text('iOS:'), - CupertinoSwitch( - activeColor: colorScheme.primary, - value: isOn1, - onChanged: (bool value) { - setState(() { - isOn1 = value; - }); - }, - ), - CupertinoSwitch( - activeColor: colorScheme.primary, - value: isOn1, - onChanged: null, - ), - CupertinoSwitch( - activeColor: colorScheme.primary, - value: !isOn1, - onChanged: (bool value) { - setState(() { - isOn1 = !value; - }); - }, + if (widget.showCupertinoSwitches) + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Cupertino')), + CupertinoSwitch( + value: isOn1, + onChanged: (bool value) { + setState(() { + isOn1 = value; + }); + }, + ), + CupertinoSwitch( + value: !isOn1, + onChanged: (bool value) { + setState(() { + isOn1 = !value; + }); + }, + ), + ], ), - CupertinoSwitch( - activeColor: colorScheme.primary, - value: !isOn1, - onChanged: null, + if (widget.showCupertinoSwitches) + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Disabled')), + CupertinoSwitch( + value: isOn1, + onChanged: null, + ), + CupertinoSwitch( + value: !isOn1, + onChanged: null, + ), + ], ), - ], ], ), ); @@ -646,50 +873,156 @@ class CheckboxShowcase extends StatefulWidget { } class _CheckboxShowcaseState extends State { - bool? isSelected1 = true; - bool? isSelected2; + bool isSelected1 = true; + bool? isSelectedTri1 = true; + bool? isSelectedTri2 = false; + bool? isSelectedTri3; + + static const double _width = 75; @override Widget build(BuildContext context) { return RepaintBoundary( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 8, - runSpacing: 8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Checkbox( - value: isSelected1, - onChanged: (bool? value) { - setState(() { - isSelected1 = value; - }); - }, - ), - Checkbox( - tristate: true, - value: isSelected2, - onChanged: (bool? value) { - setState(() { - isSelected2 = value; - }); - }, - ), - Checkbox( - value: false, - onChanged: (bool? value) {}, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Enabled')), + Checkbox( + value: isSelected1, + onChanged: (bool? value) { + setState(() { + isSelected1 = value ?? false; + }); + }, + ), + Checkbox( + value: !isSelected1, + onChanged: (bool? value) { + setState(() { + isSelected1 = !(value ?? false); + }); + }, + ), + ], ), - const Checkbox( - value: true, - onChanged: null, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Error')), + Checkbox( + isError: true, + value: isSelected1, + onChanged: (bool? value) { + setState(() { + isSelected1 = value ?? false; + }); + }, + ), + Checkbox( + isError: true, + value: !isSelected1, + onChanged: (bool? value) { + setState(() { + isSelected1 = !(value ?? false); + }); + }, + ), + ], ), - const Checkbox( - value: null, - tristate: true, - onChanged: null, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Tri-state')), + Checkbox( + tristate: true, + value: isSelectedTri1, + onChanged: (bool? value) { + setState(() { + isSelectedTri1 = value; + if (value == null) { + isSelectedTri2 = true; + isSelectedTri3 = false; + } else if (value) { + isSelectedTri2 = false; + isSelectedTri3 = null; + } else { + isSelectedTri2 = null; + isSelectedTri3 = true; + } + }); + }, + ), + Checkbox( + tristate: true, + value: isSelectedTri2, + onChanged: (bool? value) { + setState(() { + isSelectedTri2 = value; + if (value == null) { + isSelectedTri1 = false; + isSelectedTri3 = true; + } else if (value) { + isSelectedTri1 = null; + isSelectedTri3 = false; + } else { + isSelectedTri1 = true; + isSelectedTri3 = null; + } + }); + }, + ), + Checkbox( + tristate: true, + value: isSelectedTri3, + onChanged: (bool? value) { + setState(() { + isSelectedTri3 = value; + if (value == null) { + isSelectedTri1 = true; + isSelectedTri2 = false; + } else if (value) { + isSelectedTri1 = false; + isSelectedTri2 = null; + } else { + isSelectedTri1 = null; + isSelectedTri2 = true; + } + }); + }, + ), + ], ), - const Checkbox( - value: false, - onChanged: null, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Disabled')), + Checkbox( + tristate: true, + value: isSelectedTri1, + onChanged: null, + ), + Checkbox( + tristate: true, + value: isSelectedTri2, + onChanged: null, + ), + Checkbox( + tristate: true, + value: isSelectedTri3, + onChanged: null, + ), + ], ), ], ), @@ -706,42 +1039,57 @@ class RadioShowcase extends StatefulWidget { class _RadioShowcaseState extends State { bool? groupValue = true; + static const double _width = 75; @override Widget build(BuildContext context) { return RepaintBoundary( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 8, - runSpacing: 8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Radio( - value: true, - groupValue: groupValue, - onChanged: (bool? value) { - setState(() { - groupValue = value; - }); - }, - ), - Radio( - value: false, - groupValue: groupValue, - onChanged: (bool? value) { - setState(() { - groupValue = value; - }); - }, - ), - Radio( - value: true, - groupValue: groupValue, - onChanged: null, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Enabled')), + Radio( + value: true, + groupValue: groupValue, + onChanged: (bool? value) { + setState(() { + groupValue = value; + }); + }, + ), + Radio( + value: false, + groupValue: groupValue, + onChanged: (bool? value) { + setState(() { + groupValue = value; + }); + }, + ), + ], ), - Radio( - value: false, - groupValue: groupValue, - onChanged: null, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + const SizedBox(width: _width, child: Text('Disabled')), + Radio( + value: true, + groupValue: groupValue, + onChanged: null, + ), + Radio( + value: false, + groupValue: groupValue, + onChanged: null, + ), + ], ), ], ), @@ -922,8 +1270,8 @@ class _RangeSliderShowcaseState extends State { } class PopupMenuButtonsShowcase extends StatelessWidget { - const PopupMenuButtonsShowcase({super.key, this.explainUsage = true}); - final bool explainUsage; + const PopupMenuButtonsShowcase({super.key, this.explain = false}); + final bool explain; @override Widget build(BuildContext context) { @@ -937,7 +1285,7 @@ class PopupMenuButtonsShowcase extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (explainUsage) + if (explain) Padding( padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), child: Text( @@ -945,11 +1293,13 @@ class PopupMenuButtonsShowcase extends StatelessWidget { style: denseHeader, ), ), - if (explainUsage) + if (explain) Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 8), child: Text( - 'The classic Material popup menu.', + 'The PopupMenuButton is a Material-2 design commonly used in ' + 'Material apps. In M3 it has received a slightly updated style ' + 'with elevation tint.', style: denseBody, ), ), @@ -957,6 +1307,8 @@ class PopupMenuButtonsShowcase extends StatelessWidget { children: [ PopupMenuButtonShowcase(), SizedBox(width: 16), + CheckedPopupMenuButtonShowcase(), + SizedBox(width: 16), PopupMenuButtonTilesShowcase(), ], ), @@ -986,6 +1338,28 @@ class PopupMenuButtonShowcase extends StatelessWidget { } } +class CheckedPopupMenuButtonShowcase extends StatelessWidget { + const CheckedPopupMenuButtonShowcase({super.key}); + + @override + Widget build(BuildContext context) { + return RepaintBoundary( + child: PopupMenuButton( + onSelected: (_) {}, + position: PopupMenuPosition.under, + itemBuilder: (BuildContext context) => const >[ + CheckedPopupMenuItem(value: 1, child: Text('Option 1')), + CheckedPopupMenuItem(value: 2, child: Text('Option 2')), + CheckedPopupMenuItem(value: 3, child: Text('Option 3')), + CheckedPopupMenuItem(value: 4, child: Text('Option 4')), + CheckedPopupMenuItem(value: 5, child: Text('Option 5')), + ], + icon: const Icon(Icons.playlist_add_check), + ), + ); + } +} + class PopupMenuButtonTilesShowcase extends StatelessWidget { const PopupMenuButtonTilesShowcase({super.key}); @@ -993,6 +1367,7 @@ class PopupMenuButtonTilesShowcase extends StatelessWidget { Widget build(BuildContext context) { return RepaintBoundary( child: PopupMenuButton( + tooltip: 'Show menu using\nListTile items', onSelected: (_) {}, position: PopupMenuPosition.under, itemBuilder: (BuildContext context) => const >[ @@ -1021,90 +1396,160 @@ class PopupMenuButtonTilesShowcase extends StatelessWidget { } } -class _DropDownButton extends StatefulWidget { - const _DropDownButton(); +class DropDownButtonShowcase extends StatefulWidget { + const DropDownButtonShowcase({super.key, this.explain = false}); + final bool explain; @override - State<_DropDownButton> createState() => _DropDownButtonState(); + State createState() => _DropDownButtonShowcaseState(); } -class _DropDownButtonState extends State<_DropDownButton> { - String selectedItem = 'Dropdown button 1'; +class _DropDownButtonShowcaseState extends State { + String selectedItem = '1 DropdownButton'; @override Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final TextStyle denseHeader = theme.textTheme.titleMedium!.copyWith( + fontSize: 13, + ); + final TextStyle denseBody = theme.textTheme.bodyMedium! + .copyWith(fontSize: 12, color: theme.textTheme.bodySmall!.color); + return RepaintBoundary( - child: DropdownButton( - value: selectedItem, - onChanged: (String? value) { - setState(() { - selectedItem = value ?? 'Dropdown button 1'; - }); - }, - items: [ - 'Dropdown button 1', - 'Dropdown button 2', - 'Dropdown button 3', - 'Dropdown button 4', - 'Dropdown button 5' - ].map>((String value) { - return DropdownMenuItem( - value: value, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text(value), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.explain) + Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), + child: Text( + 'DropdownButton', + style: denseHeader, + ), ), - ); - }).toList(), + if (widget.explain) + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 8), + child: Text( + 'An older Material-2 widget, it cannot be themed. ' + 'Consider using M3 DropdownMenu instead.', + style: denseBody, + ), + ), + DropdownButton( + value: selectedItem, + onChanged: (String? value) { + setState(() { + selectedItem = value ?? '1 DropdownButton'; + }); + }, + items: [ + '1 DropdownButton', + '2 DropdownButton', + '3 DropdownButton', + '4 DropdownButton', + '5 DropdownButton', + '6 DropdownButton', + '7 DropdownButton', + '8 DropdownButton', + ].map>((String value) { + return DropdownMenuItem( + value: value, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text(value), + ), + ); + }).toList(), + ), + ], ), ); } } -class DropDownButtonFormField extends StatefulWidget { - const DropDownButtonFormField({super.key}); +class DropdownButtonFormFieldShowcase extends StatefulWidget { + const DropdownButtonFormFieldShowcase({super.key, this.explain = false}); + final bool explain; @override - State createState() => - _DropDownButtonFormFieldState(); + State createState() => + _DropdownButtonFormFieldShowcaseState(); } -class _DropDownButtonFormFieldState extends State { - String selectedItem = 'DropDown FormField - Option 1'; +class _DropdownButtonFormFieldShowcaseState + extends State { + String selectedItem = '1 DropdownButtonFormField'; @override Widget build(BuildContext context) { - return DropdownButtonFormField( - value: selectedItem, - onChanged: (String? value) { - setState(() { - selectedItem = value ?? 'DropDown FormField - Option 1'; - }); - }, - items: [ - 'DropDown FormField - Option 1', - 'DropDown FormField - Option 2', - 'DropDown FormField - Option 3', - 'DropDown FormField - Option 4', - 'DropDown FormField - Option 5', - ].map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), + final ThemeData theme = Theme.of(context); + final TextStyle denseHeader = theme.textTheme.titleMedium!.copyWith( + fontSize: 13, + ); + final TextStyle denseBody = theme.textTheme.bodyMedium! + .copyWith(fontSize: 12, color: theme.textTheme.bodySmall!.color); + + return RepaintBoundary( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.explain) + Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), + child: Text( + 'DropdownButtonFormField', + style: denseHeader, + ), + ), + if (widget.explain) + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 8), + child: Text( + 'An older M2 widget. Cannot theme its open style. ' + 'Closed style uses InputDecorator theme. Maybe consider ' + 'using M3 DropDownMenu instead.', + style: denseBody, + ), + ), + DropdownButtonFormField( + value: selectedItem, + onChanged: (String? value) { + setState(() { + selectedItem = value ?? '1 DropdownButtonFormField'; + }); + }, + items: [ + '1 DropdownButtonFormField', + '2 DropdownButtonFormField', + '3 DropdownButtonFormField', + '4 DropdownButtonFormField', + '5 DropdownButtonFormField', + '6 DropdownButtonFormField', + '7 DropdownButtonFormField', + '8 DropdownButtonFormField', + ].map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + ], + ), ); } } class DropDownMenuShowcase extends StatefulWidget { - const DropDownMenuShowcase({super.key, this.explainUsage = false}); - final bool explainUsage; + const DropDownMenuShowcase({super.key, this.explain = false}); + final bool explain; @override State createState() => _DropDownMenuShowcaseState(); } class _DropDownMenuShowcaseState extends State { - String selectedItem = 'one'; + IconData selectedItem = Icons.alarm; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); @@ -1118,7 +1563,7 @@ class _DropDownMenuShowcaseState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.explainUsage) + if (widget.explain) Padding( padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), child: Text( @@ -1126,48 +1571,74 @@ class _DropDownMenuShowcaseState extends State { style: denseHeader, ), ), - if (widget.explainUsage) + if (widget.explain) Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 8), child: Text( - 'The new M3 DropdownMenu shares building blocks with MenuBar ' + 'The M3 DropdownMenu shares building blocks with MenuBar ' 'and MenuAnchor, also uses InputDecorator for text entry.', style: denseBody, ), ), - DropdownMenu( + DropdownMenu( initialSelection: selectedItem, - onSelected: (String? value) { + requestFocusOnTap: true, + leadingIcon: Icon(selectedItem), + onSelected: (IconData? value) { setState(() { - selectedItem = value ?? 'one'; + selectedItem = value ?? Icons.alarm; }); + // Unfocus after select, see + // https://github.com/flutter/flutter/issues/138343 + FocusScope.of(context).unfocus(); }, - dropdownMenuEntries: const >[ - DropdownMenuEntry( + dropdownMenuEntries: const >[ + DropdownMenuEntry( + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.symmetric(horizontal: 12)), + ), label: 'Alarm settings', leadingIcon: Icon(Icons.alarm), - value: 'one', + value: Icons.alarm, ), - DropdownMenuEntry( + DropdownMenuEntry( + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.symmetric(horizontal: 12)), + ), label: 'Disabled settings', leadingIcon: Icon(Icons.settings), - value: 'two', enabled: false, + value: Icons.settings, ), - DropdownMenuEntry( + DropdownMenuEntry( + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.symmetric(horizontal: 12)), + ), label: 'Cabin overview', leadingIcon: Icon(Icons.cabin), - value: 'three', + value: Icons.cabin, ), - DropdownMenuEntry( - label: 'Surveillance view', - leadingIcon: Icon(Icons.camera_outdoor_rounded), - value: 'four', - ), - DropdownMenuEntry( + DropdownMenuEntry( + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.symmetric(horizontal: 12)), + ), + label: 'Surveillance view', + leadingIcon: Icon(Icons.camera_outdoor_rounded), + // value: 'four', + value: Icons.camera_outdoor_rounded), + DropdownMenuEntry( + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.symmetric(horizontal: 12)), + ), label: 'Water alert', leadingIcon: Icon(Icons.water_damage), - value: 'five', + // value: 'five', + value: Icons.water_damage, ), ], ), @@ -1182,41 +1653,102 @@ class TooltipShowcase extends StatelessWidget { @override Widget build(BuildContext context) { - return const RepaintBoundary( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 16, - runSpacing: 4, - children: [ - Tooltip( - message: 'Current tooltip theme', - child: Text('Text with tooltip'), + return const Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 16, + runSpacing: 4, + children: [ + Tooltip( + message: 'Current tooltip theme', + child: Text('Text with tooltip'), + ), + Tooltip( + message: 'Current tooltip theme.\nThis a two row tooltip.', + child: Text('Text with two row tooltip'), + ), + Tooltip( + message: 'Current tooltip theme.\nThis tooltip is too long.\n' + 'Try to keep them short.', + child: Text('Text with three row tooltip'), + ), + ], + ); + } +} + +class IconShowcase extends StatelessWidget { + const IconShowcase({super.key}); + + @override + Widget build(BuildContext context) { + return const Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 16, + runSpacing: 4, + children: [ + Tooltip( + message: 'Icon is Icons\nAddCircle', + child: Icon(Icons.add_circle), + ), + Tooltip( + message: 'Icon is Icons\nCameraAltOutlined', + child: Icon(Icons.camera_alt_outlined), + ), + Tooltip( + message: 'Icon is Icons\nFlutterDash', + child: Icon(Icons.flutter_dash), + ), + Tooltip( + message: 'Icon is Icons\nWarningAmber', + child: Icon(Icons.warning_amber), + ), + ], + ); + } +} + +class CircleAvatarShowcase extends StatelessWidget { + const CircleAvatarShowcase({super.key}); + + @override + Widget build(BuildContext context) { + return const Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 16, + runSpacing: 4, + children: [ + Tooltip( + message: 'This is a\nCircleAvatar', + child: CircleAvatar( + child: Text('CA'), ), - Tooltip( - message: 'Current tooltip theme.\nThis a two row tooltip.', - child: Text('Text with two row tooltip'), + ), + Tooltip( + message: 'CircleAvatar\nwith image', + child: CircleAvatar( + child: FlutterLogo(), ), - Tooltip( - message: 'Current tooltip theme.\nThis tooltip is too long.\n' - 'Try to keep them short.', - child: Text('Text with three row tooltip'), + ), + Tooltip( + message: 'CircleAvatar\nwith image\nradius 30', + child: CircleAvatar( + radius: 30, + child: FlutterLogo(size: 40), ), - ], - ), + ), + ], ); } } -class IconButtonCircleAvatarDropdownShowcase extends StatefulWidget { - const IconButtonCircleAvatarDropdownShowcase({super.key}); +class IconButtonShowcase extends StatefulWidget { + const IconButtonShowcase({super.key}); @override - State createState() => - _IconButtonCircleAvatarDropdownShowcaseState(); + State createState() => _IconButtonShowcaseState(); } -class _IconButtonCircleAvatarDropdownShowcaseState - extends State { +class _IconButtonShowcaseState extends State { bool isLockOpen = false; @override @@ -1227,14 +1759,6 @@ class _IconButtonCircleAvatarDropdownShowcaseState spacing: 16, runSpacing: 4, children: [ - const Tooltip( - message: 'This is\nan Icon', - child: Icon(Icons.add_circle), - ), - const Tooltip( - message: 'This is\nan Icon', - child: Icon(Icons.flutter_dash), - ), IconButton( icon: const Icon(Icons.accessibility), tooltip: 'This is an\nIconButton', @@ -1253,140 +1777,145 @@ class _IconButtonCircleAvatarDropdownShowcaseState }); }, ), - const Tooltip( - message: 'This is a\nCircleAvatar', - child: CircleAvatar( - child: Text('CA'), - ), - ), - const _DropDownButton(), ], ), ); } } -class IconButtonShowcase extends StatelessWidget { - const IconButtonShowcase({super.key}); +class IconButtonVariantsShowcase extends StatelessWidget { + const IconButtonVariantsShowcase({super.key}); @override Widget build(BuildContext context) { - return const RepaintBoundary( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 0, - runSpacing: 4, - children: [ - Column( - // crossAxisAlignment: CrossAxisAlignment.center, - // mainAxisAlignment: MainAxisAlignment.center, - // Standard IconButton - children: [ - SizedBox( - width: 65, - child: Text('Default', textAlign: TextAlign.center)), - SizedBox(height: 4), - _IconToggleButton( - isEnabled: true, - tooltip: 'Standard', - variant: _IconButtonVariant.standard, - toggleable: false, - ), - SizedBox(height: 8), - _IconToggleButton( - isEnabled: true, - tooltip: 'Standard toggleable', - variant: _IconButtonVariant.standard, - ), - SizedBox(height: 8), - _IconToggleButton( - isEnabled: false, - tooltip: 'Standard (disabled)', - variant: _IconButtonVariant.standard, - ), - ], - ), - Column( - children: [ - SizedBox( - width: 65, - child: Text('Filled', textAlign: TextAlign.center)), - SizedBox(height: 4), - // Filled IconButton - _IconToggleButton( - isEnabled: true, - tooltip: 'Filled', - variant: _IconButtonVariant.filled, - toggleable: false, - ), - SizedBox(height: 8), - _IconToggleButton( - isEnabled: true, - tooltip: 'Filled toggleable', - variant: _IconButtonVariant.filled, - ), - SizedBox(height: 8), - _IconToggleButton( - isEnabled: false, - tooltip: 'Filled (disabled)', - variant: _IconButtonVariant.filled, - ), - ], - ), - Column( - children: [ - SizedBox( - width: 65, child: Text('Tonal', textAlign: TextAlign.center)), - SizedBox(height: 4), - // Filled Tonal IconButton - _IconToggleButton( - isEnabled: true, - tooltip: 'Filled tonal', - variant: _IconButtonVariant.filledTonal, - toggleable: false, - ), - SizedBox(height: 8), - _IconToggleButton( - isEnabled: true, - tooltip: 'Filled tonal toggleable', - variant: _IconButtonVariant.filledTonal, - ), - SizedBox(height: 8), - _IconToggleButton( - isEnabled: false, - tooltip: 'Filled tonal (disabled)', - variant: _IconButtonVariant.filledTonal, - ), - ], - ), - Column( - children: [ - SizedBox( - width: 65, - child: Text('Outlined', textAlign: TextAlign.center)), - SizedBox(height: 4), - // Outlined IconButton - _IconToggleButton( - isEnabled: true, - tooltip: 'Outlined', - variant: _IconButtonVariant.outlined, - toggleable: false, - ), - SizedBox(height: 8), - _IconToggleButton( - isEnabled: true, - tooltip: 'Outlined toggleable', - variant: _IconButtonVariant.outlined, - ), - SizedBox(height: 8), - _IconToggleButton( - isEnabled: false, - tooltip: 'Outlined (disabled)', - variant: _IconButtonVariant.outlined, - ), - ], - ), - ], - ), + return const Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 0, + runSpacing: 4, + children: [ + Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(height: 18), + SizedBox(width: 75, height: 21, child: Text('Type')), + SizedBox(height: 16), + SizedBox(width: 75, height: 48, child: Text('Tappable')), + // SizedBox(height: 8), + SizedBox(width: 75, height: 48, child: Text('Toggleable')), + // SizedBox(height: 8), + SizedBox(width: 75, height: 48, child: Text('Disabled')), + ], + ), + Column( + // Standard IconButton + children: [ + SizedBox( + width: 65, child: Text('Default', textAlign: TextAlign.center)), + SizedBox(height: 4), + _IconToggleButton( + isEnabled: true, + tooltip: 'Standard', + variant: _IconButtonVariant.standard, + toggleable: false, + ), + SizedBox(height: 8), + _IconToggleButton( + isEnabled: true, + tooltip: 'Standard toggleable', + variant: _IconButtonVariant.standard, + ), + SizedBox(height: 8), + _IconToggleButton( + isEnabled: false, + tooltip: 'Standard (disabled)', + variant: _IconButtonVariant.standard, + toggleable: false, + ), + ], + ), + Column( + children: [ + SizedBox( + width: 65, child: Text('Filled', textAlign: TextAlign.center)), + SizedBox(height: 4), + // Filled IconButton + _IconToggleButton( + isEnabled: true, + tooltip: 'Filled', + variant: _IconButtonVariant.filled, + toggleable: false, + ), + SizedBox(height: 8), + _IconToggleButton( + isEnabled: true, + tooltip: 'Filled toggleable', + variant: _IconButtonVariant.filled, + ), + SizedBox(height: 8), + _IconToggleButton( + isEnabled: false, + tooltip: 'Filled (disabled)', + variant: _IconButtonVariant.filled, + toggleable: false, + ), + ], + ), + Column( + children: [ + SizedBox( + width: 65, child: Text('Tonal', textAlign: TextAlign.center)), + SizedBox(height: 4), + // Filled Tonal IconButton + _IconToggleButton( + isEnabled: true, + tooltip: 'Filled tonal', + variant: _IconButtonVariant.filledTonal, + toggleable: false, + ), + SizedBox(height: 8), + _IconToggleButton( + isEnabled: true, + tooltip: 'Filled tonal toggleable', + variant: _IconButtonVariant.filledTonal, + ), + SizedBox(height: 8), + _IconToggleButton( + isEnabled: false, + tooltip: 'Filled tonal (disabled)', + variant: _IconButtonVariant.filledTonal, + toggleable: false, + ), + ], + ), + Column( + children: [ + SizedBox( + width: 65, + child: Text('Outlined', textAlign: TextAlign.center)), + SizedBox(height: 4), + // Outlined IconButton + _IconToggleButton( + isEnabled: true, + tooltip: 'Outlined', + variant: _IconButtonVariant.outlined, + toggleable: false, + ), + SizedBox(height: 8), + _IconToggleButton( + isEnabled: true, + tooltip: 'Outlined toggleable', + variant: _IconButtonVariant.outlined, + ), + SizedBox(height: 8), + _IconToggleButton( + isEnabled: false, + tooltip: 'Outlined (disabled)', + variant: _IconButtonVariant.outlined, + toggleable: false, + ), + ], + ), + ], ); } } @@ -1411,7 +1940,7 @@ class _IconToggleButton extends StatefulWidget { } class _IconToggleButtonState extends State<_IconToggleButton> { - bool selected = false; + bool selected = true; @override Widget build(BuildContext context) { @@ -1423,12 +1952,18 @@ class _IconToggleButtonState extends State<_IconToggleButton> { } : null; + final String toggleState = widget.toggleable + ? selected + ? '\n(selected)' + : '\n(not selected)' + : ''; + switch (widget.variant) { case _IconButtonVariant.standard: { return IconButton( isSelected: selected & widget.toggleable, - tooltip: widget.tooltip, + tooltip: '${widget.tooltip}$toggleState', icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings), onPressed: onPressed, @@ -1438,7 +1973,7 @@ class _IconToggleButtonState extends State<_IconToggleButton> { { return IconButton.filled( isSelected: selected & widget.toggleable, - tooltip: widget.tooltip, + tooltip: '${widget.tooltip}$toggleState', icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings), onPressed: onPressed, @@ -1448,7 +1983,7 @@ class _IconToggleButtonState extends State<_IconToggleButton> { { return IconButton.filledTonal( isSelected: selected & widget.toggleable, - tooltip: widget.tooltip, + tooltip: '${widget.tooltip}$toggleState', icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings), onPressed: onPressed, @@ -1458,7 +1993,7 @@ class _IconToggleButtonState extends State<_IconToggleButton> { { return IconButton.outlined( isSelected: selected & widget.toggleable, - tooltip: widget.tooltip, + tooltip: '${widget.tooltip}$toggleState', icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings), onPressed: onPressed, @@ -1516,136 +2051,382 @@ class _ProgressIndicatorShowcaseState extends State { } } -class ChipShowcase extends StatelessWidget { - const ChipShowcase({super.key}); +class ChipShowcase extends StatefulWidget { + const ChipShowcase({super.key, this.showOptions = false}); + final bool showOptions; + + @override + State createState() => _ChipShowcaseState(); +} + +class _ChipShowcaseState extends State { + static const double _fontSize = 11; + static const double _textWidth = 95; + + bool filterSelected = true; + bool inputSelected = true; + bool choiceSelected = true; + bool showCheckmark = true; + bool showAvatar = false; @override Widget build(BuildContext context) { return RepaintBoundary( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: 8, - runSpacing: 8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Chip( - label: const Text('Chip'), - onDeleted: () {}, - ), - const Chip( - label: Text('Chip'), - avatar: FlutterLogo(), - ), - ActionChip( - label: const Text('ActionChip'), - avatar: const Icon(Icons.settings), - onPressed: () {}, - ), - const ActionChip( - label: Text('ActionChip'), - avatar: Icon(Icons.settings), - onPressed: null, + Wrap( + crossAxisAlignment: WrapCrossAlignment.start, + alignment: WrapAlignment.center, + spacing: 4, + runSpacing: 8, + children: [ + const SizedBox( + width: _textWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Chip', style: TextStyle(fontSize: _fontSize)), + Text('No M3 spec', style: TextStyle(fontSize: _fontSize)) + ], + ), + ), + Chip( + label: const Text('Chip'), + onDeleted: () {}, + ), + const Chip( + label: Text('Chip'), + avatar: FlutterLogo(), + ), + ], ), - FilterChip( - label: const Text('FilterChip'), - selected: true, - onSelected: (bool value) {}, - ), - const FilterChip( - label: Text('FilterChip'), - selected: true, - onSelected: null, - ), - FilterChip( - label: const Text('FilterChip'), - selected: false, - onSelected: (bool value) {}, - ), - const FilterChip( - label: Text('FilterChip'), - selected: false, - onSelected: null, - ), - ChoiceChip( - label: const Text('ChoiceChip'), - selected: true, - onSelected: (bool value) {}, - ), - const ChoiceChip( - label: Text('ChoiceChip'), - selected: true, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 8, + children: [ + const SizedBox( + width: _textWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('ActionChip', style: TextStyle(fontSize: _fontSize)), + Text('Assist (M3)', style: TextStyle(fontSize: _fontSize)), + ], + ), + ), + ActionChip( + label: const Text('ActionChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + onPressed: () {}, + ), + ActionChip( + label: const Text('ActionChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + onPressed: null, + ), + ], ), - ChoiceChip( - label: const Text('ChoiceChip'), - selected: false, - onSelected: (bool value) {}, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 8, + children: [ + const SizedBox( + width: _textWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('FilterChip', style: TextStyle(fontSize: _fontSize)), + Text('Filter (M3)', style: TextStyle(fontSize: _fontSize)), + ], + ), + ), + FilterChip( + label: const Text('FilterChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + selected: false, + onSelected: (bool value) {}, + ), + FilterChip( + label: const Text('FilterChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + selected: false, + onSelected: null, + ), + ], ), - const ChoiceChip( - label: Text('ChoiceChip'), - selected: false, - onSelected: null, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 8, + children: [ + const SizedBox( + width: _textWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Selected', style: TextStyle(fontSize: _fontSize)), + Text('Filter', style: TextStyle(fontSize: _fontSize)), + ], + ), + ), + FilterChip( + label: const Text('FilterChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + selected: filterSelected, + onSelected: (bool value) { + setState(() { + filterSelected = value; + }); + }, + ), + FilterChip( + label: const Text('FilterChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + selected: true, + onSelected: null, + ), + ], ), - InputChip( - selected: true, - label: const Text('InputChip'), - onSelected: (bool value) {}, - onDeleted: () {}, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 8, + children: [ + const SizedBox( + width: _textWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('InputChip', style: TextStyle(fontSize: _fontSize)), + Text('Input (M3)', style: TextStyle(fontSize: _fontSize)), + ], + ), + ), + InputChip( + label: const Text('InputChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + onSelected: (bool value) {}, + onDeleted: () {}, + ), + InputChip( + label: const Text('InputChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + isEnabled: false, + onSelected: (bool value) {}, + onDeleted: () {}, + // onDeleted: () {}, + ), + ], ), - InputChip( - selected: true, - label: const Text('InputChip'), - isEnabled: false, - onSelected: (bool value) {}, - onDeleted: () {}, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 8, + children: [ + const SizedBox( + width: _textWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Selected', style: TextStyle(fontSize: _fontSize)), + Text('Input', style: TextStyle(fontSize: _fontSize)), + ], + ), + ), + InputChip( + label: const Text('InputChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + selected: inputSelected, + showCheckmark: showCheckmark, + onSelected: (bool value) { + setState(() { + inputSelected = value; + }); + }, + onDeleted: () {}, + ), + InputChip( + label: const Text('InputChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + selected: true, + showCheckmark: showCheckmark, + isEnabled: false, + onSelected: (bool value) {}, + onDeleted: () {}, + ), + ], ), - InputChip( - label: const Text('InputChip'), - onSelected: (bool value) {}, - onDeleted: () {}, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 8, + children: [ + const SizedBox( + width: _textWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('ChoiceChip', style: TextStyle(fontSize: _fontSize)), + Text('Suggestion (M3)', + style: TextStyle(fontSize: _fontSize)), + ], + ), + ), + ChoiceChip( + label: const Text('ChoiceChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + selected: false, + onSelected: (bool value) {}, + ), + ChoiceChip( + label: const Text('ChoiceChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + selected: false, + onSelected: null, + ), + ], ), - InputChip( - label: const Text('InputChip'), - isEnabled: false, - onSelected: (bool value) {}, - onDeleted: () {}, - // onDeleted: () {}, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + runSpacing: 8, + children: [ + const SizedBox( + width: _textWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Selected', style: TextStyle(fontSize: _fontSize)), + Text('Suggestion (M3)', + style: TextStyle(fontSize: _fontSize)), + ], + ), + ), + ChoiceChip( + label: const Text('ChoiceChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + selected: choiceSelected, + onSelected: (bool value) { + setState(() { + choiceSelected = value; + }); + }, + ), + ChoiceChip( + label: const Text('ChoiceChip'), + avatar: showAvatar + ? const Icon(Icons.account_circle_rounded) + : null, + showCheckmark: showCheckmark, + selected: true, + ), + ], ), + const SizedBox(height: 8), + if (widget.showOptions) + SwitchListTile( + dense: true, + contentPadding: EdgeInsets.zero, + title: const Text('Show checkmark when selected'), + value: showCheckmark, + onChanged: (bool value) { + setState(() { + showCheckmark = value; + }); + }, + ), + if (widget.showOptions) + SwitchListTile( + dense: true, + contentPadding: EdgeInsets.zero, + title: const Text('Show optional avatar'), + value: showAvatar, + onChanged: (bool value) { + setState(() { + showAvatar = value; + }); + }, + ), ], ), ); } } -class TextInputField extends StatefulWidget { - const TextInputField({super.key}); +class TextFieldShowcase extends StatefulWidget { + const TextFieldShowcase({super.key}); @override - State createState() => _TextInputFieldState(); + State createState() => _TextFieldShowcaseState(); } -class _TextInputFieldState extends State { - late TextEditingController _textController1; - late TextEditingController _textController2; - late TextEditingController _textController3; - late TextEditingController _textController4; - bool _errorState1 = false; - bool _errorState2 = false; - bool _errorState3 = false; +class _TextFieldShowcaseState extends State { + late TextEditingController _plainFieldController; + late TextEditingController _withIconsController; + late TextEditingController _collapsedFieldController; + bool _errorStatePlain = false; + bool _errorStateWithIcons = false; + bool _forceFilled = false; + bool _forceOutlined = false; @override void initState() { super.initState(); - _textController1 = TextEditingController(); - _textController2 = TextEditingController(); - _textController3 = TextEditingController(); - _textController4 = TextEditingController(); + _plainFieldController = TextEditingController(); + _withIconsController = TextEditingController(); + _collapsedFieldController = TextEditingController(); } @override void dispose() { - _textController1.dispose(); - _textController2.dispose(); - _textController3.dispose(); - _textController4.dispose(); + _plainFieldController.dispose(); + _withIconsController.dispose(); + _collapsedFieldController.dispose(); super.dispose(); } @@ -1654,98 +2435,161 @@ class _TextInputFieldState extends State { return RepaintBoundary( child: Column( children: [ - TextField( - onChanged: (String text) { - setState(() { - if (text.contains('a') | text.isEmpty) { - _errorState1 = false; - } else { - _errorState1 = true; - } - }); - }, - key: const Key('TextField1'), - controller: _textController1, - decoration: InputDecoration( - hintText: 'Hint: Write something...', - labelText: 'Label: Underline border by default if not defined', - errorText: _errorState1 - ? "Any entry without an 'a' will trigger this error" - : null, - ), - ), - const SizedBox(height: 16), - TextField( - onChanged: (String text) { - setState(() { - if (text.contains('a') | text.isEmpty) { - _errorState2 = false; - } else { - _errorState2 = true; - } - }); - }, - key: const Key('TextField2'), - controller: _textController2, - decoration: InputDecoration( - filled: true, - hintText: 'Hint: Write something...', - labelText: 'Label: Underline border by default if not defined, ' - 'filled set true by Widget', - errorText: _errorState2 - ? "Any entry without an 'a' will trigger this error" - : null, - ), - ), - const SizedBox(height: 16), - TextField( - onChanged: (String text) { - setState(() { - if (text.contains('a') | text.isEmpty) { - _errorState3 = false; - } else { - _errorState3 = true; - } - }); - }, - key: const Key('TextField3'), - controller: _textController3, - decoration: InputDecoration( - border: const OutlineInputBorder(), - hintText: 'Hint: Write something...', - labelText: 'Label: Outline border set by Widget if not defined', - prefixIcon: const Icon(Icons.search), - suffixIcon: const Icon(Icons.info), - errorText: _errorState3 - ? "Any entry without an 'a' will trigger this error" - : null, - ), + Row( + children: [ + Expanded( + child: TextField( + onChanged: (String text) { + setState(() { + if (text.contains('a') | text.isEmpty) { + _errorStatePlain = false; + } else { + _errorStatePlain = true; + } + }); + }, + key: const Key('TextField1'), + controller: _plainFieldController, + decoration: InputDecoration( + border: _forceOutlined ? const OutlineInputBorder() : null, + filled: _forceFilled ? true : null, + hintText: 'Write something...', + labelText: 'Plain TextField', + errorText: _errorStatePlain + ? "Any entry without an 'a' will trigger this error" + : null, + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: TextField( + controller: TextEditingController(), + enabled: false, + decoration: InputDecoration( + border: _forceOutlined ? const OutlineInputBorder() : null, + filled: _forceFilled ? true : null, + labelText: 'Disabled label', + ), + ), + ), + ], ), const SizedBox(height: 16), - TextField( - key: const Key('TextField4'), - controller: _textController4, - decoration: const InputDecoration.collapsed( - hintText: 'Hint: Collapsed TextField...', - ), + Row( + children: [ + Expanded( + child: TextField( + onChanged: (String text) { + setState(() { + if (text.contains('a') | text.isEmpty) { + _errorStateWithIcons = false; + } else { + _errorStateWithIcons = true; + } + }); + }, + key: const Key('TextField2'), + controller: _withIconsController, + decoration: InputDecoration( + border: _forceOutlined ? const OutlineInputBorder() : null, + filled: _forceFilled ? true : null, + hintText: 'Write something...', + labelText: 'TextField with Icons', + prefixIcon: const Icon(Icons.search), + suffixIcon: const Icon(Icons.info), + errorText: _errorStateWithIcons + ? "Any entry without an 'a' will trigger this error" + : null, + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: TextField( + controller: + TextEditingController(text: 'Disabled entered text'), + enabled: false, + decoration: InputDecoration( + border: _forceOutlined ? const OutlineInputBorder() : null, + filled: _forceFilled ? true : null, + labelText: 'Disabled label', + prefixIcon: const Icon(Icons.search), + suffixIcon: const Icon(Icons.info), + ), + ), + ), + ], ), const SizedBox(height: 16), - TextField( - controller: TextEditingController(), - enabled: false, - decoration: const InputDecoration( - labelText: 'TextField - Disabled label', - ), + Row( + children: [ + Expanded( + child: TextField( + key: const Key('TextField3'), + controller: _collapsedFieldController, + decoration: InputDecoration.collapsed( + border: _forceOutlined ? const OutlineInputBorder() : null, + filled: _forceFilled ? true : null, + hintText: 'Collapsed TextField', + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: TextField( + key: const Key('TextField4'), + enabled: false, + controller: + TextEditingController(text: 'Disabled entered text'), + decoration: InputDecoration.collapsed( + border: _forceOutlined ? const OutlineInputBorder() : null, + filled: _forceFilled ? true : null, + hintText: 'Collapsed TextField', + ), + ), + ), + ], ), const SizedBox(height: 16), - TextField( - controller: TextEditingController(text: 'Disabled with text entry'), - enabled: false, - decoration: const InputDecoration( - labelText: 'TextField - Disabled label', - prefixIcon: Icon(Icons.search), - suffixIcon: Icon(Icons.info), - ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SwitchListTileReveal( + dense: true, + title: const Text('Force filled style'), + subtitleReveal: const Text('This is not a theme toggle. It ' + 'sets Decoration to fill on widget level. Use it to ' + 'see the look of the Flutter default filled style when ' + 'no theme is used.'), + value: _forceFilled, + onChanged: (bool value) { + setState(() { + _forceFilled = value; + }); + }, + ), + ), + const SizedBox(width: 16), + Expanded( + child: SwitchListTileReveal( + dense: true, + title: const Text('Force outlined style'), + subtitleReveal: const Text('This is not a theme toggle. It ' + 'sets Decoration border to default OutlineInputBorder() ' + 'on widget level. Use it to ' + 'see the look of the Flutter default outlined style ' + 'when no theme is used.'), + value: _forceOutlined, + onChanged: (bool value) { + setState(() { + _forceOutlined = value; + }); + }, + ), + ), + ], ), ], ), @@ -1774,7 +2618,7 @@ class AppBarShowcase extends StatelessWidget { icon: const Icon(Icons.menu), onPressed: () {}, ), - title: const Text('Standard AppBar'), + title: const Text('AppBar'), actions: [ IconButton( icon: const Icon(Icons.search), @@ -1896,7 +2740,8 @@ class _BehindAppBar extends StatelessWidget { } class SearchBarShowcase extends StatefulWidget { - const SearchBarShowcase({super.key}); + const SearchBarShowcase({super.key, this.explain = false}); + final bool explain; @override State createState() => _SearchBarShowcaseState(); @@ -1907,138 +2752,191 @@ class _SearchBarShowcaseState extends State { @override Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final TextStyle denseHeader = theme.textTheme.titleMedium!.copyWith( + fontSize: 13, + ); + final TextStyle denseBody = theme.textTheme.bodyMedium! + .copyWith(fontSize: 12, color: theme.textTheme.bodySmall!.color); + return Theme( data: Theme.of(context).copyWith( inputDecorationTheme: const InputDecorationTheme( - // border: InputBorder.none, + border: InputBorder.none, focusedBorder: InputBorder.none, - // enabledBorder: InputBorder.none, - // disabledBorder: InputBorder.none, - // errorBorder: InputBorder.none, - // focusedErrorBorder: InputBorder.none, + enabledBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, filled: false, ), ), child: Builder(builder: (BuildContext context) { return Padding( padding: const EdgeInsets.all(16.0), - child: SearchAnchor( - builder: (BuildContext context, SearchController controller) { - return SearchBar( - controller: controller, - hintText: 'Search using SearchBar', - padding: const WidgetStatePropertyAll( - EdgeInsets.symmetric(horizontal: 16.0)), - onTap: () { - controller.openView(); - }, - onChanged: (_) { - controller.openView(); - }, - leading: const Icon(Icons.search), - trailing: [ - Tooltip( - message: 'Voice search', - child: IconButton( - isSelected: isMicOn, - onPressed: () { + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.explain) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), + child: Text( + 'SearchBar', + style: denseHeader, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 8), + child: Text( + 'The M3 SearchBar can in some use cases be used instead ' + 'of an AppBar or BottomAppBar.', + style: denseBody, + ), + ), + ], + SearchAnchor( + builder: (BuildContext context, SearchController controller) { + return SearchBar( + // elevation: const WidgetStatePropertyAll(1), + controller: controller, + hintText: 'Search using SearchBar', + padding: const WidgetStatePropertyAll( + EdgeInsets.symmetric(horizontal: 16.0)), + onTap: () { + controller.openView(); + }, + onChanged: (_) { + controller.openView(); + }, + leading: const Icon(Icons.search), + trailing: [ + Tooltip( + message: 'Voice search', + child: IconButton( + isSelected: isMicOn, + onPressed: () { + setState(() { + isMicOn = !isMicOn; + }); + }, + icon: const Icon(Icons.mic_off), + selectedIcon: const Icon(Icons.mic), + ), + ) + ], + ); + }, suggestionsBuilder: + (BuildContext context, SearchController controller) { + return List.generate(7, (int index) { + final String item = 'item $index'; + return ListTile( + title: Text(item), + onTap: () { setState(() { - isMicOn = !isMicOn; + controller.closeView(item); }); }, - icon: const Icon(Icons.mic_off), - selectedIcon: const Icon(Icons.mic), - ), - ) - ], - ); - }, suggestionsBuilder: - (BuildContext context, SearchController controller) { - return List.generate(7, (int index) { - final String item = 'item $index'; - return ListTile( - title: Text(item), - onTap: () { - setState(() { - controller.closeView(item); - }); - }, - ); - }); - }), + ); + }); + }), + ], + ), ); }), ); } } -class BottomAppBarShowcase extends StatelessWidget { - const BottomAppBarShowcase({ - super.key, - this.explain = true, - }); - +class TabBarForAppBarShowcase extends StatelessWidget { + const TabBarForAppBarShowcase({super.key, this.explain = false}); final bool explain; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); + final bool isDark = theme.brightness == Brightness.dark; + final bool useM3 = theme.useMaterial3; + final ColorScheme colorScheme = theme.colorScheme; + + final Color effectiveTabBackground = theme.appBarTheme.backgroundColor ?? + (isDark + ? colorScheme.surface + : useM3 + ? colorScheme.surface + : colorScheme.primary); final TextStyle denseHeader = theme.textTheme.titleMedium!.copyWith( fontSize: 13, ); final TextStyle denseBody = theme.textTheme.bodyMedium! .copyWith(fontSize: 12, color: theme.textTheme.bodySmall!.color); + return RepaintBoundary( - child: MediaQuery.removePadding( - context: context, - removeBottom: true, - removeTop: true, + child: DefaultTabController( + length: 3, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - BottomAppBar( - child: Row( - children: [ - IconButton( - tooltip: 'Open navigation menu', - icon: const Icon(Icons.menu), - onPressed: () {}, - ), - const Spacer(), - IconButton( - tooltip: 'Search', - icon: const Icon(Icons.search), - onPressed: () {}, - ), - IconButton( - tooltip: 'Favorite', - icon: const Icon(Icons.favorite), - onPressed: () {}, - ), - ], - ), - ), - if (explain) + if (explain) ...[ Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), child: Text( - 'BottomAppBar', + 'TabBar in an AppBar', style: denseHeader, ), ), - if (explain) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), child: Text( - 'Flutter M2 past default color was ' - 'ThemeData.bottomAppBarColor. It was deprecated in ' - 'Flutter 3.7. New default is colorScheme.surface and ' - 'elevation 8. In M3 it defaults to colorScheme.surface ' - 'color, elevation 3, no shadow, but with surface elevation ' - 'tint.', + 'If the TabBar is used in an AppBar, then try style ' + 'FlexTabBarStyle forAppBar, it will fit contrast wise ' + 'here regardless of selected AppBar background color.', style: denseBody, ), + ) + ], + MediaQuery.removePadding( + context: context, + removeBottom: true, + removeTop: true, + child: Material( + color: effectiveTabBackground, + child: SizedBox( + height: 130, + child: AppBar( + leading: IconButton( + icon: const Icon(Icons.menu), + onPressed: () {}, + ), + actions: [ + IconButton( + icon: const Icon(Icons.search), + onPressed: () {}, + ), + ], + title: const Text('TabBar in AppBar'), + bottom: const TabBar( + tabs: [ + Tab( + text: 'Chat', + icon: Badge( + label: Text('18'), + child: Icon(Icons.chat_bubble), + ), + ), + Tab( + text: 'Tasks', + icon: Icon(Icons.beenhere), + ), + Tab( + text: 'Folder', + icon: Icon(Icons.create_new_folder), + ), + ], + ), + ), + ), ), + ), ], ), ), @@ -2046,36 +2944,43 @@ class BottomAppBarShowcase extends StatelessWidget { } } -class TabBarForAppBarShowcase extends StatelessWidget { - const TabBarForAppBarShowcase({super.key}); +class TabBarForBackgroundShowcase extends StatelessWidget { + const TabBarForBackgroundShowcase({super.key, this.explain = false}); + final bool explain; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - final bool isDark = theme.brightness == Brightness.dark; - final bool useM3 = theme.useMaterial3; - final ColorScheme colorScheme = theme.colorScheme; - - final Color effectiveTabBackground = theme.appBarTheme.backgroundColor ?? - (isDark - ? colorScheme.surface - : useM3 - ? colorScheme.surface - : colorScheme.primary); final TextStyle denseHeader = theme.textTheme.titleMedium!.copyWith( fontSize: 13, ); final TextStyle denseBody = theme.textTheme.bodyMedium! .copyWith(fontSize: 12, color: theme.textTheme.bodySmall!.color); - return RepaintBoundary( child: DefaultTabController( length: 3, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (explain) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: Text( + 'TabBar on a surface', + style: denseHeader, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Text( + 'If TabBar is used on surface colors, consider style ' + 'FlexTabBarStyle forBackground.', + style: denseBody, + ), + ), + ], Material( - color: effectiveTabBackground, + color: theme.colorScheme.surface, child: const SizedBox( height: 70, child: TabBar( @@ -2083,7 +2988,7 @@ class TabBarForAppBarShowcase extends StatelessWidget { Tab( text: 'Chat', icon: Badge( - label: Text('18'), + label: Text('+99'), child: Icon(Icons.chat_bubble), ), ), @@ -2099,22 +3004,6 @@ class TabBarForAppBarShowcase extends StatelessWidget { ), ), ), - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: Text( - 'TabBar in an AppBar', - style: denseHeader, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: Text( - 'If the TabBar will always be used in an AppBar, then use ' - 'style FlexTabBarStyle forAppBar (default), ' - 'it will fit contrast wise here', - style: denseBody, - ), - ), ], ), ), @@ -2122,8 +3011,10 @@ class TabBarForAppBarShowcase extends StatelessWidget { } } -class TabBarForBackgroundShowcase extends StatelessWidget { - const TabBarForBackgroundShowcase({super.key}); +class BottomAppBarShowcase extends StatelessWidget { + const BottomAppBarShowcase({super.key, this.explain = false}); + + final bool explain; @override Widget build(BuildContext context) { @@ -2134,50 +3025,49 @@ class TabBarForBackgroundShowcase extends StatelessWidget { final TextStyle denseBody = theme.textTheme.bodyMedium! .copyWith(fontSize: 12, color: theme.textTheme.bodySmall!.color); return RepaintBoundary( - child: DefaultTabController( - length: 3, + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + removeTop: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Material( - color: theme.colorScheme.surface, - child: const SizedBox( - height: 70, - child: TabBar( - tabs: [ - Tab( - text: 'Chat', - icon: Badge( - label: Text('+99'), - child: Icon(Icons.chat_bubble), - ), - ), - Tab( - text: 'Tasks', - icon: Icon(Icons.beenhere), - ), - Tab( - text: 'Folder', - icon: Icon(Icons.create_new_folder), - ), - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: Text( - 'TabBar on a surface', - style: denseHeader, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: Text( - 'If the TabBar will always be used on background and surface ' - 'colors, then use style FlexTabBarStyle forBackground, ' - 'it will fit contrast wise here', - style: denseBody, + if (explain) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: Text('BottomAppBar', style: denseHeader)), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Text( + 'Typically used as a command bar at the bottom of the ' + 'screen. Flutter M2 past default color was ' + 'ThemeData.bottomAppBarColor. It was deprecated in ' + 'Flutter 3.7. New M2 default is colorScheme.surface and ' + 'elevation 8. In M3 it defaults to colorScheme.surface ' + 'color, elevation 3, no shadow, but with surface ' + 'elevation tint.', + style: denseBody)) + ], + BottomAppBar( + child: Row( + children: [ + IconButton( + tooltip: 'Open navigation menu', + icon: const Icon(Icons.menu), + onPressed: () {}, + ), + const Spacer(), + IconButton( + tooltip: 'Search', + icon: const Icon(Icons.search), + onPressed: () {}, + ), + IconButton( + tooltip: 'Favorite', + icon: const Icon(Icons.favorite), + onPressed: () {}, + ), + ], ), ), ], @@ -2188,7 +3078,7 @@ class TabBarForBackgroundShowcase extends StatelessWidget { } class BottomNavigationBarShowcase extends StatefulWidget { - const BottomNavigationBarShowcase({super.key, this.explain = true}); + const BottomNavigationBarShowcase({super.key, this.explain = false}); final bool explain; @@ -2213,6 +3103,27 @@ class _BottomNavigationBarShowcaseState child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (widget.explain) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: Text( + 'BottomNavigationBar', + style: denseHeader, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Text( + 'Older Material 2 navigation bar, prefer using NavigationBar. ' + 'Flutter default background ' + 'color is theme canvasColor via Material. The canvasColor ' + 'typically equals colorScheme.background. Default elevation ' + 'is 8. FCS sub-theme default is colorScheme.background ' + 'and elevation 0.', + style: denseBody, + ), + ), + ], MediaQuery.removePadding( context: context, removeBottom: true, @@ -2272,25 +3183,6 @@ class _BottomNavigationBarShowcaseState ], ), ), - if (widget.explain) - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: Text( - 'BottomNavigationBar (Material 2)', - style: denseHeader, - ), - ), - if (widget.explain) - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: Text( - 'Default SDK background color is theme canvasColor via ' - 'Material. The canvasColor is typically ' - 'colorScheme.background, elevation is 8. FCS sub-theme default ' - 'is colorScheme.background and elevation 0.', - style: denseBody, - ), - ), ], ), ); @@ -2298,7 +3190,7 @@ class _BottomNavigationBarShowcaseState } class NavigationBarShowcase extends StatefulWidget { - const NavigationBarShowcase({super.key, this.explain = true}); + const NavigationBarShowcase({super.key, this.explain = false}); final bool explain; @override @@ -2320,6 +3212,26 @@ class _NavigationBarShowcaseState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (widget.explain) ...[ + Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), + child: Text( + 'NavigationBar', + style: denseHeader, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Text( + 'Material 3 navigation bar. Default background color is ' + 'surface with an onSurface overlay ' + 'color in M2, and primary in M3, with elevation 3. ' + 'FCS default is color scheme background, with used ' + 'surface blend and elevation 0.', + style: denseBody, + ), + ), + ], MediaQuery.removePadding( context: context, removeBottom: true, @@ -2376,25 +3288,6 @@ class _NavigationBarShowcaseState extends State { ], ), ), - if (widget.explain) - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: Text( - 'NavigationBar (Material 3)', - style: denseHeader, - ), - ), - if (widget.explain) - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: Text( - 'Default background color is surface with an onSurface overlay ' - 'color in M2, and primary in M3, with elevation 3. ' - 'FlexColorScheme component theme default is color scheme ' - 'background, with used surface blend and elevation 0.', - style: denseBody, - ), - ), ], ), ); @@ -2405,8 +3298,8 @@ class NavigationRailShowcase extends StatefulWidget { const NavigationRailShowcase({ super.key, this.child, - this.height = 400, - this.explain = true, + this.height = 350, + this.explain = false, }); /// A child widget that we can use to place controls on the @@ -2456,7 +3349,7 @@ class _NavigationRailShowcaseState extends State { style: denseBody, ), ), - const Divider(height: 1), + const SizedBox(height: 8), SizedBox( height: widget.height, // If we expand the rail and have a very narrow screen, it will @@ -2545,10 +3438,10 @@ class _NavigationRailShowcaseState extends State { class MenuBarShowcase extends StatelessWidget { const MenuBarShowcase({ super.key, - this.explainUsage = true, + this.explain = false, this.explainIndent = 0, }); - final bool explainUsage; + final bool explain; final double explainIndent; @override @@ -2568,7 +3461,7 @@ class MenuBarShowcase extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [ - if (explainUsage) + if (explain) Padding( padding: EdgeInsetsDirectional.fromSTEB(explainIndent, 16, 0, 0), child: Text( @@ -2576,11 +3469,11 @@ class MenuBarShowcase extends StatelessWidget { style: denseHeader, ), ), - if (explainUsage) + if (explain) Padding( padding: EdgeInsetsDirectional.fromSTEB(explainIndent, 0, 0, 8), child: Text( - 'The new M3 menus can be used in a MenuBar via SubMenuButton ' + 'The M3 menus can be used in a MenuBar via SubMenuButton ' 'and its MenuItemButton, but they can also be used in a ' 'MenuAnchor anywhere.', style: denseBody, @@ -2708,8 +3601,8 @@ class MenuBarShowcase extends StatelessWidget { } class MenuAnchorShowcase extends StatelessWidget { - const MenuAnchorShowcase({super.key, this.explainUsage = true}); - final bool explainUsage; + const MenuAnchorShowcase({super.key, this.explain = false}); + final bool explain; @override Widget build(BuildContext context) { @@ -2722,9 +3615,8 @@ class MenuAnchorShowcase extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, children: [ - if (explainUsage) + if (explain) ...[ Padding( padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), child: Text( @@ -2732,19 +3624,19 @@ class MenuAnchorShowcase extends StatelessWidget { style: denseHeader, ), ), - if (explainUsage) Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 8), child: Text( - 'The new M3 MenuAnchor used on a Container as a context menu.', + 'The M3 MenuAnchor used on a Container as a context menu.', style: denseBody, ), ), + ], const Row( children: [ Expanded( child: MenuAnchorContextMenu( - message: 'The new M3 MenuAnchor is cool!', + message: 'The M3 MenuAnchor is cool!', ), ), ], @@ -2967,7 +3859,7 @@ class _MenuAnchorContextMenuState extends State { } class DrawerShowcase extends StatelessWidget { - const DrawerShowcase({super.key, this.explain = true}); + const DrawerShowcase({super.key, this.explain = false}); final bool explain; @override @@ -2982,7 +3874,7 @@ class DrawerShowcase extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (explain) + if (explain) ...[ Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Text( @@ -2990,7 +3882,6 @@ class DrawerShowcase extends StatelessWidget { style: denseHeader, ), ), - if (explain) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), child: Text( @@ -3001,6 +3892,7 @@ class DrawerShowcase extends StatelessWidget { style: denseBody, ), ), + ], SizedBox( height: 280, child: MediaQuery.removePadding( @@ -3020,7 +3912,7 @@ class DrawerShowcase extends StatelessWidget { } class NavigationDrawerShowcase extends StatefulWidget { - const NavigationDrawerShowcase({super.key, this.explain = true}); + const NavigationDrawerShowcase({super.key, this.explain = false}); final bool explain; @@ -3046,7 +3938,7 @@ class _NavigationDrawerShowcaseState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - if (widget.explain) + if (widget.explain) ...[ Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: Text( @@ -3054,7 +3946,6 @@ class _NavigationDrawerShowcaseState extends State { style: denseHeader, ), ), - if (widget.explain) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), child: Text( @@ -3063,6 +3954,7 @@ class _NavigationDrawerShowcaseState extends State { style: denseBody, ), ), + ], MediaQuery.removePadding( context: context, removeBottom: true, @@ -3110,25 +4002,6 @@ class _NavigationDrawerShowcaseState extends State { } } -class ListTileAllShowcase extends StatelessWidget { - const ListTileAllShowcase({super.key}); - - @override - Widget build(BuildContext context) { - return const Column( - children: [ - ListTileShowcase(), - Divider(height: 1), - SwitchTileShowcase(), - Divider(height: 1), - CheckboxTileShowcase(), - Divider(height: 1), - RadioTileShowcase(), - ], - ); - } -} - class ListTileShowcase extends StatelessWidget { const ListTileShowcase({super.key}); @@ -3166,8 +4039,8 @@ class ListTileShowcase extends StatelessWidget { } } -class SwitchTileShowcase extends StatelessWidget { - const SwitchTileShowcase({super.key}); +class SwitchListTileShowcase extends StatelessWidget { + const SwitchListTileShowcase({super.key}); @override Widget build(BuildContext context) { @@ -3201,8 +4074,8 @@ class SwitchTileShowcase extends StatelessWidget { } } -class CheckboxTileShowcase extends StatelessWidget { - const CheckboxTileShowcase({super.key}); +class CheckboxListTileShowcase extends StatelessWidget { + const CheckboxListTileShowcase({super.key}); @override Widget build(BuildContext context) { @@ -3245,8 +4118,8 @@ class CheckboxTileShowcase extends StatelessWidget { } } -class RadioTileShowcase extends StatelessWidget { - const RadioTileShowcase({super.key}); +class RadioListTileShowcase extends StatelessWidget { + const RadioListTileShowcase({super.key}); @override Widget build(BuildContext context) { @@ -3531,10 +4404,15 @@ class DatePickerDialogShowcase extends StatelessWidget { return Column( children: [ AbsorbPointer( - child: DatePickerDialog( - initialDate: DateTime.now(), - firstDate: DateTime(1930), - lastDate: DateTime(2050), + child: MediaQuery.removePadding( + context: context, + removeTop: true, + removeBottom: true, + child: DatePickerDialog( + initialDate: DateTime.now(), + firstDate: DateTime(1930), + lastDate: DateTime(2050), + ), ), ), TextButton( @@ -3569,12 +4447,13 @@ class BottomSheetShowcase extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'Material BottomSheet', + 'BottomSheet', style: theme.textTheme.titleMedium, ), Text( - 'Uses Material of type canvas as default background.\n' - 'ColorScheme background in M2, but surface in M3.', + 'In M2 Flutter uses Material of type canvas, resulting in\n' + 'color surface. In M3 Flutter uses surfaceContainerLow.\n' + 'FCS defaults to surfaceContainerLow in both modes.', style: theme.textTheme.bodySmall, textAlign: TextAlign.center, ), @@ -3607,12 +4486,13 @@ class BottomSheetModalShowcase extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - 'Material Modal BottomSheet', + 'Modal BottomSheet', style: theme.textTheme.titleMedium, ), Text( - 'Uses Material of type canvas as default background.\n' - 'ColorScheme background in M2, but surface in M3.', + 'In M2 Flutter uses Material of type canvas, resulting in\n' + 'color surface. In M3 Flutter uses surfaceContainerLow.\n' + 'FCS defaults to surfaceContainerLow in both modes.', style: theme.textTheme.bodySmall, textAlign: TextAlign.center, ), @@ -3802,8 +4682,43 @@ class _SnackBarShowcaseState extends State { } } -class MaterialBannerShowcase extends StatelessWidget { - const MaterialBannerShowcase({super.key}); +class MaterialBannerShowcase extends StatefulWidget { + const MaterialBannerShowcase({super.key, this.enableShowBanner = false}); + + final bool enableShowBanner; + + @override + State createState() => _MaterialBannerShowcaseState(); +} + +class _MaterialBannerShowcaseState extends State { + int showCount = 0; + + Future _showDemoMaterialBanner( + BuildContext context, bool twoButtons, String message) async { + ScaffoldMessenger.of(context).showMaterialBanner( + MaterialBanner( + // elevation: 3, + content: Text(message), + leading: const Icon(Icons.add_alert), + actions: [ + if (twoButtons) + TextButton( + child: const Text('OK'), + onPressed: () { + ScaffoldMessenger.of(context).hideCurrentMaterialBanner(); + }, + ), + TextButton( + child: const Text('Dismiss'), + onPressed: () { + ScaffoldMessenger.of(context).hideCurrentMaterialBanner(); + }, + ), + ], + ), + ); + } @override Widget build(BuildContext context) { @@ -3811,36 +4726,70 @@ class MaterialBannerShowcase extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Divider(height: 1), MaterialBanner( - padding: const EdgeInsets.all(20), - content: const Text('Hello, I am a MaterialBanner'), + elevation: 3, + content: const Text('I am a MaterialBanner at elevation 3'), leading: const Icon(Icons.agriculture_outlined), actions: [ TextButton( - child: const Text('OPEN'), + child: const Text('Open'), onPressed: () {}, ), TextButton( - child: const Text('DISMISS'), + child: const Text('Dismiss'), onPressed: () {}, ), ], ), - const SizedBox(height: 16), - ], - ), - ); - } -} - -class MaterialShowcase extends StatelessWidget { - const MaterialShowcase({super.key, this.explain = true}); - final bool explain; - - @override - Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); + const SizedBox(height: 8), + if (widget.enableShowBanner) + Center( + child: Wrap( + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + TextButton( + child: const Text( + 'Show MaterialBanner', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + showCount++; + unawaited(_showDemoMaterialBanner( + context, false, 'A MaterialBanner ($showCount)')); + }, + ), + TextButton( + child: const Text( + 'Show two button MaterialBanner', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + showCount++; + unawaited(_showDemoMaterialBanner(context, true, + 'A MaterialBanner with two actions ($showCount)')); + }, + ), + ], + ), + ), + ], + ), + ); + } +} + +class MaterialShowcase extends StatelessWidget { + const MaterialShowcase({super.key, this.explain = false}); + final bool explain; + + static const double _width = 160; + static const double _height = 100; + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; final TextStyle denseHeader = theme.textTheme.titleMedium!.copyWith( fontSize: 13, @@ -3853,8 +4802,11 @@ class MaterialShowcase extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (explain) ...[ - Text('Material elevation and tint', style: denseHeader), Text( + 'Material widget is a lower level building block. It cannot ' + 'be themed, but it has Material-2 and Material-3 mode dependent ' + 'behavior. Material is responsible for clipping, elevation ' + 'and ink effects below its children. ' 'Material can also specify surfaceTint color, ' 'which is applied when Material is elevated, but only in ' 'Material 3 mode.', @@ -3870,144 +4822,160 @@ class MaterialShowcase extends StatelessWidget { style: denseBody, ), const SizedBox(height: 8), - Material( - type: MaterialType.canvas, - elevation: 0, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type canvas, elevation 0, default ' - 'tint and shadow', - textAlign: TextAlign.center, - style: denseHeader, - ), - ), - ), - ), - ), - const SizedBox(height: 16), - Material( - type: MaterialType.canvas, - elevation: 1, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type canvas, elevation 1, default tint ' - 'and shadow', - textAlign: TextAlign.center, - style: denseHeader, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 16, + runSpacing: 16, + children: [ + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.canvas, + elevation: 1, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type canvas, elevation 1, default tint ' + 'and default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), - ), - const SizedBox(height: 8), - Material( - type: MaterialType.canvas, - elevation: 1, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: Colors.transparent, - child: const SizedBox( - height: 60, - child: Padding( - padding: EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type canvas, elevation 1, ' - 'assigned surfaceTint and no shadow', - textAlign: TextAlign.center, + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.canvas, + elevation: 1, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: Colors.transparent, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type canvas, elevation 1, ' + 'assigned surfaceTint and transparent shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), - ), - const SizedBox(height: 8), - Material( - type: MaterialType.canvas, - elevation: 1, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: colorScheme.shadow, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type canvas, elevation 1, ' - 'assigned surfaceTint, and shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.canvas, + elevation: 1, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: colorScheme.primary, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type canvas, elevation 1, ' + 'assigned surfaceTint and primary shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), + ], ), const SizedBox(height: 16), - Material( - type: MaterialType.canvas, - elevation: 6, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type canvas, elevation 6, default tint ' - 'and shadow', - textAlign: TextAlign.center, - style: denseHeader, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 16, + runSpacing: 16, + children: [ + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.canvas, + elevation: 6, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type canvas, elevation 6, default tint ' + 'and default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), - ), - const SizedBox(height: 16), - Material( - type: MaterialType.canvas, - elevation: 6, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: Colors.transparent, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type canvas, elevation 6, ' - 'assigned surfaceTint and no shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.canvas, + elevation: 6, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: Colors.transparent, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type canvas, elevation 6, ' + 'assigned surfaceTint and transparent shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), - ), - const SizedBox(height: 16), - Material( - type: MaterialType.canvas, - elevation: 6, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: colorScheme.shadow, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type canvas, elevation 6, ' - 'assigned surfaceTint and shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.canvas, + elevation: 6, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: colorScheme.primary, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type canvas, elevation 6, ' + 'assigned surfaceTint and primary shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), + ], ), const SizedBox(height: 32), Text('Material type card', style: denseHeader), @@ -4018,144 +4986,161 @@ class MaterialShowcase extends StatelessWidget { style: denseBody, ), const SizedBox(height: 8), - Material( - type: MaterialType.card, - elevation: 0, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type card, elevation 0, default tint and shadow', - textAlign: TextAlign.center, - style: denseHeader, - ), - ), - ), - ), - ), - const SizedBox(height: 16), - Material( - type: MaterialType.card, - elevation: 1, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type card, elevation 1, default tint and shadow', - textAlign: TextAlign.center, - style: denseHeader, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 16, + runSpacing: 16, + children: [ + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.card, + elevation: 1, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type card, elevation 1, default tint ' + 'and default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), - ), - const SizedBox(height: 8), - Material( - type: MaterialType.card, - elevation: 1, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: Colors.transparent, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type card, elevation 1, ' - 'assigned surfaceTint and no shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.card, + elevation: 1, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: Colors.transparent, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type card, elevation 1, ' + 'assigned surfaceTint and transparent shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), - ), - const SizedBox(height: 8), - Material( - type: MaterialType.card, - elevation: 1, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: colorScheme.shadow, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type card, elevation 1, ' - 'assigned surfaceTint and shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.card, + elevation: 1, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: colorScheme.shadow, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type card, elevation 1, ' + 'assigned surfaceTint and primary shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), + ], ), const SizedBox(height: 16), - Material( - type: MaterialType.card, - elevation: 6, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type card, elevation 6, default tint and shadow', - textAlign: TextAlign.center, - style: denseHeader, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 16, + runSpacing: 16, + children: [ + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.card, + elevation: 6, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type card, elevation 6, default tint ' + 'and default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), - ), - const SizedBox(height: 16), - Material( - type: MaterialType.card, - elevation: 6, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: Colors.transparent, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type card, elevation 6, ' - 'assigned surfaceTint and no shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.card, + elevation: 6, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: Colors.transparent, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type card, elevation 6, ' + 'assigned surfaceTint and transparent shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), - ), - const SizedBox(height: 16), - Material( - type: MaterialType.card, - elevation: 6, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: colorScheme.shadow, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Material type card, elevation 6, ' - 'assigned surfaceTint and shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Material( + type: MaterialType.card, + elevation: 6, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: colorScheme.primary, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Material type card, elevation 6, ' + 'assigned surfaceTint and primary shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), ), ), ), - ), + ], ), - const SizedBox(height: 8), ], ), ); @@ -4163,9 +5148,12 @@ class MaterialShowcase extends StatelessWidget { } class CardShowcase extends StatelessWidget { - const CardShowcase({super.key, this.explain = true}); + const CardShowcase({super.key, this.explain = false}); final bool explain; + static const double _width = 160; + static const double _height = 100; + @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); @@ -4177,207 +5165,464 @@ class CardShowcase extends StatelessWidget { .copyWith(fontSize: 12, color: theme.textTheme.bodySmall!.color); return RepaintBoundary( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (explain) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text('Card', style: denseHeader), - ), - if (explain) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - 'Default background color comes from Material of type card, ' - 'which by default is set to theme colorScheme surface. ' - 'When useMaterial3 is true, Card gets elevation based ' - 'surfaceTint. When it is false, surfaceTint has no ' - 'effect even if specified.', - style: denseBody, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CardTypesShowcase(cardWidth: 130), + const SizedBox(height: 8), + if (explain) + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text( + 'In M2 mode default background color is theme.cardColor, ' + 'which typically is set to colorScheme.surface.\n' + 'In M3 mode before Flutter 3.22 background defaults to ' + 'surface color and it gets elevation based surfaceTint.\n' + 'In M3 mode after Flutter 3.22 background defaults to ' + 'surfaceContainerLow color and it does not get elevation ' + 'based surfaceTint by default.\n' + 'In M2 mode surfaceTint has no effect even if specified.\n' + 'Card gets elevation shadow by default in M2 and M3 mode.', + style: denseBody, + ), ), - ), - Card( - elevation: 0, - surfaceTintColor: colorScheme.surfaceTint, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 0, assigned surfaceTint and ' - 'default shadow', - textAlign: TextAlign.center, - style: denseHeader, + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 0, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 0, default surfaceTint ' + 'and default shadow', + textAlign: TextAlign.center, + style: denseHeader, + )), + ), + ), ), ), - ), - ), - ), - const SizedBox(height: 10), - Card( - elevation: 1, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 1, default surfaceTint and shadow', - textAlign: TextAlign.center, - style: denseHeader, - )), - ), - ), - ), - const SizedBox(height: 2), - Card( - elevation: 1, - surfaceTintColor: colorScheme.surfaceTint, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 1, assigned surfaceTint and ' - 'default shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 0, + surfaceTintColor: colorScheme.surfaceTint, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 0, assigned surfaceTint and ' + 'default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), ), ), - ), - ), - ), - const SizedBox(height: 2), - Card( - elevation: 1, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: Colors.transparent, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 1, assigned surfaceTint and ' - 'transparent shadow', - textAlign: TextAlign.center, - style: denseHeader, + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 0, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: Colors.transparent, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 0, assigned surfaceTint and ' + 'transparent shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), ), ), - ), + ], ), - ), - const Divider(), - Card( - elevation: 4, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 4, default surfaceTint and shadow', - textAlign: TextAlign.center, - style: denseHeader, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 1, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 1, default surfaceTint ' + 'and default shadow', + textAlign: TextAlign.center, + style: denseHeader, + )), + ), + ), ), ), - ), + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 1, + surfaceTintColor: colorScheme.surfaceTint, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 1, assigned surfaceTint and ' + 'default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), + ), + ), + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 1, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: Colors.transparent, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 1, assigned surfaceTint and ' + 'transparent shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), + ), + ), + ], ), - ), - const SizedBox(height: 4), - Card( - elevation: 4, - surfaceTintColor: colorScheme.surfaceTint, - child: SizedBox( - height: 60, - child: Center( - child: Text( - 'Card, elevation 4, assigned surfaceTint and default shadow', - textAlign: TextAlign.center, - style: denseHeader, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 4, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 4, default surfaceTint ' + 'and default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), + ), ), - ), + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 4, + surfaceTintColor: colorScheme.surfaceTint, + child: SizedBox( + height: 60, + child: Center( + child: Text( + 'Card, elevation 4, assigned surfaceTint and ' + 'default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), + ), + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 4, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: Colors.transparent, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 4, assigned surfaceTint and ' + 'transparent shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), + ), + ), + ], ), - ), - const SizedBox(height: 4), - Card( - elevation: 4, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: Colors.transparent, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 4, assigned surfaceTint and ' - 'transparent shadow', - textAlign: TextAlign.center, - style: denseHeader, + const SizedBox(height: 8), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + runSpacing: 8, + children: [ + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 10, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 10, default surfaceTint ' + 'and default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), + ), + ), + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 10, + surfaceTintColor: colorScheme.surfaceTint, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 10, assigned surfaceTint and ' + 'default shadow', + textAlign: TextAlign.center, + style: denseHeader, + ), + ), + ), + ), ), ), + SizedBox( + width: _width, + height: _height, + child: Card( + elevation: 10, + surfaceTintColor: colorScheme.surfaceTint, + shadowColor: Colors.transparent, + child: SizedBox( + height: 60, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + 'Card, elevation 10, assigned surfaceTint and ' + 'transparent shadow', + textAlign: TextAlign.center, + style: denseHeader, + )), + ), + ), + ), + ), + ], + ), + ]), + ); + } +} + +class CardTypesShowcase extends StatelessWidget { + const CardTypesShowcase({ + super.key, + this.cardWidth, + this.showThemedOutline = false, + }); + + final double? cardWidth; + final bool showThemedOutline; + + static const double _cardWidth = 115; + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final bool useMaterial3 = theme.useMaterial3; + + // (rydmike): To make the by Flutter team made custom outlined Card below + // that is a not a part of SDK configured Cards, actually follow M2/M3 + // switch, as well as on higher prio any ambient themed border radius + // the Card theme has, we need to do something like this, to get + // the correct border radius that we can use in the custom constructor + // further below. + // + // Default starting point value based on M3 and M2 mode spec values. + double borderRadius = useMaterial3 ? 12 : 4; + // Is themed? Try to get the radius from the theme and used that if it was. + final ShapeBorder? cardShape = theme.cardTheme.shape; + if (cardShape != null && cardShape is RoundedRectangleBorder) { + final BorderRadius shape = cardShape.borderRadius as BorderRadius; + borderRadius = shape.bottomLeft.x; + } + + return Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + SizedBox( + width: cardWidth ?? _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'), + ) + ], ), ), ), - const Divider(), - Card( - elevation: 10, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 10, default surfaceTint and shadow', - textAlign: TextAlign.center, - style: denseHeader, + ), + SizedBox( + width: cardWidth ?? _cardWidth, + child: Card.filled( + 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'), + ) + ], ), ), ), - const SizedBox(height: 10), - Card( - elevation: 10, - surfaceTintColor: colorScheme.surfaceTint, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 10, assigned surfaceTint and ' - 'default shadow', - textAlign: TextAlign.center, - style: denseHeader, + ), + SizedBox( + width: cardWidth ?? _cardWidth, + child: Card.outlined( + elevation: 0, + shape: RoundedRectangleBorder( + side: BorderSide(color: theme.colorScheme.outlineVariant), + borderRadius: BorderRadius.all(Radius.circular(borderRadius)), + ), + borderOnForeground: false, + 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'), + ) + ], ), ), ), - const SizedBox(height: 10), - Card( - elevation: 10, - surfaceTintColor: colorScheme.surfaceTint, - shadowColor: Colors.transparent, - child: SizedBox( - height: 60, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: Text( - 'Card, elevation 10, assigned surfaceTint and ' - 'transparent shadow', - textAlign: TextAlign.center, - style: denseHeader, - )), + ), + if (showThemedOutline) + SizedBox( + width: cardWidth ?? _cardWidth, + child: Card.outlined( + elevation: 0, + borderOnForeground: false, + 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 theme'), + ) + ], + ), ), ), ), - ], - ), + ], ); } } @@ -4423,8 +5668,7 @@ class TextThemeColumnShowcase extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Font: ${textTheme.bodyMedium!.fontFamily}', - style: - textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600)), + style: textTheme.titleSmall), _ShowTextStyle( 'Display Large ' '(${textTheme.displayLarge!.fontSize!.toStringAsFixed(0)})', diff --git a/example/lib/core/views/universal/switch_list_tile_reveal.dart b/example/lib/core/views/universal/switch_list_tile_reveal.dart new file mode 100644 index 0000000..2ee5bb4 --- /dev/null +++ b/example/lib/core/views/universal/switch_list_tile_reveal.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; + +/// A custom [SwitchListTile] that has a built-in animated custom leading action +/// as a part of [title] that reveals the [subtitleReveal] when clicked. +/// +/// This is useful when a more compact look is desired where more information +/// is provided as an optional user based reveal action. The purpose is to make +/// UI less talkative but provide easy access to additional usage explanation. +/// +/// This is a Flutter "Universal" Widget that only depends on the SDK and +/// can be dropped into any application. +class SwitchListTileReveal extends StatefulWidget { + const SwitchListTileReveal({ + super.key, + required this.value, + required this.onChanged, + this.title, + this.subtitle, + this.subtitleReveal, + this.contentPadding, + this.onTap, + this.dense, + this.revealDense, + this.enabled = true, + this.isOpen, + this.duration = const Duration(milliseconds: 200), + }); + + /// Whether this switch is checked. + /// + /// This property must not be null. + final bool value; + + /// Called when the user toggles the switch on or off. + /// + /// The switch passes the new value to the callback but does not actually + /// change state until the parent widget rebuilds the switch tile with the + /// new value. + /// + /// If null, the switch will be displayed as disabled. + /// + /// The callback provided to [onChanged] should update the state of the parent + /// [StatefulWidget] using the [State.setState] method, so that the parent + /// gets rebuilt; for example: + final ValueChanged? onChanged; + + /// The primary content of the list tile. + /// + /// Typically a [Text] widget. + /// + /// This should not wrap. To enforce the single line limit, use + /// [Text.maxLines]. + final Widget? title; + + /// Additional content displayed below the title. + /// + /// Typically a [Text] widget. + final Widget? subtitle; + + /// Additional content displayed below the subtitle in a reveal animation. + /// + /// Typically a [Text] widget. + final Widget? subtitleReveal; + + /// The [SwitchListTileReveal]'s internal padding. + /// + /// Insets a [SwitchListTileReveal]'s contents: its [title], + /// [subtitleReveal] widgets. + /// + /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. + final EdgeInsetsGeometry? contentPadding; + + /// Called when the user taps this list tile. + /// + /// Inoperative if [enabled] is false. + final GestureTapCallback? onTap; + + /// Whether this list tile and card operation is interactive. + final bool enabled; + + /// Whether this list tile is part of a vertically dense list. + /// + /// If this property is null then its value is based on [ListTileTheme.dense]. + /// + /// Dense list tiles default to a smaller height. + final bool? dense; + + /// Whether the used reveal part of the ListTile is dense. + /// + /// If not defined, defaults to true. + final bool? revealDense; + + /// Set to true to open the info section of the ListTile, to false to close + /// it. + /// + /// If not defined, defaults to false. + final bool? isOpen; + + /// The duration of the show and hide animation of child. + final Duration duration; + + @override + State createState() => _SwitchListTileRevealState(); +} + +class _SwitchListTileRevealState extends State { + late bool _isOpen; + + @override + void initState() { + super.initState(); + _isOpen = widget.isOpen ?? false; + } + + @override + void didUpdateWidget(covariant SwitchListTileReveal oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.isOpen != oldWidget.isOpen) _isOpen = widget.isOpen ?? false; + } + + void _handleTap() { + setState(() { + _isOpen = !_isOpen; + }); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SwitchListTile( + dense: widget.dense, + contentPadding: widget.contentPadding, + value: widget.value, + onChanged: widget.enabled ? widget.onChanged : null, + title: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (widget.title != null) widget.title!, + if (widget.subtitleReveal != null && widget.enabled) + IconButton( + iconSize: 20, + // ignore: avoid_bool_literals_in_conditional_expressions + isSelected: widget.enabled ? _isOpen : false, + icon: const Icon(Icons.info_outlined), + selectedIcon: const Icon(Icons.info), + onPressed: widget.enabled ? _handleTap : null, + ), + ], + ), + subtitle: widget.subtitle, + ), + AnimatedSwitcher( + duration: widget.duration, + transitionBuilder: (Widget child, Animation animation) { + return SizeTransition( + sizeFactor: animation, + axisAlignment: _isOpen ? 1 : -1, + child: child, + ); + }, + child: (_isOpen && widget.subtitleReveal != null && widget.enabled) + ? ListTile( + dense: widget.revealDense ?? true, + subtitle: widget.subtitleReveal, + onTap: widget.enabled ? _handleTap : null, + ) + : const SizedBox.shrink(), + ), + ], + ); + } +} diff --git a/example/lib/home/views/pages/home_page.dart b/example/lib/home/views/pages/home_page.dart index 2cc65e3..342734b 100644 --- a/example/lib/home/views/pages/home_page.dart +++ b/example/lib/home/views/pages/home_page.dart @@ -30,6 +30,7 @@ class HomePage extends StatelessWidget { : controller.usedVariant .tones(brightness) .monochromeSurfaces(controller.useMonoSurfaces) + .higherContrastFixed(controller.higherContrastFixedColors) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) .surfacesUseBW(isLight @@ -152,6 +153,16 @@ class HomePage extends StatelessWidget { ? null : controller.setUseMonoSurfaces, ), + SwitchListTile( + dense: true, + title: const Text('Higher contrast fixed colors'), + subtitle: const Text('tones.higherContrastFixed()'), + value: controller.higherContrastFixedColors && + !controller.usedVariant.isFlutterScheme, + onChanged: controller.usedVariant.isFlutterScheme + ? null + : controller.setHigherContrastFixedColors, + ), SwitchListTile( dense: true, title: const Text('Keep main on-colors black and white'), diff --git a/example/lib/home/views/widgets/flex_tones_popup_menu.dart b/example/lib/home/views/widgets/flex_tones_popup_menu.dart index e693a36..8d8382c 100644 --- a/example/lib/home/views/widgets/flex_tones_popup_menu.dart +++ b/example/lib/home/views/widgets/flex_tones_popup_menu.dart @@ -55,17 +55,9 @@ class FlexTonesPopupMenu extends StatelessWidget { child: ListTileReveal( contentPadding: contentPadding ?? const EdgeInsets.symmetric(horizontal: 16), - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('$title ${variant.variantName}'), - Text( - variant.description, - style: txtStyle, - ), - ], - ), - subtitle: ListTile( + title: Text('$title ${variant.variantName}'), + subtitle: Text(variant.description), + subtitleReveal: ListTile( title: Text('${variant.variantName}' ' scheme variant configuration info:'), subtitle: Text('${variant.configDetails}\n'), diff --git a/example/lib/theme/controllers/theme_controller.dart b/example/lib/theme/controllers/theme_controller.dart index 9e53def..08b5780 100644 --- a/example/lib/theme/controllers/theme_controller.dart +++ b/example/lib/theme/controllers/theme_controller.dart @@ -134,6 +134,15 @@ class ThemeController with ChangeNotifier { if (notify) notifyListeners(); } + bool _higherContrastFixedColors = false; + bool get higherContrastFixedColors => _higherContrastFixedColors; + void setHigherContrastFixedColors(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _higherContrastFixedColors) return; + _higherContrastFixedColors = value; + if (notify) notifyListeners(); + } + bool _keepSurfaceOnColorsBW = false; bool get keepSurfaceOnColorsBW => _keepSurfaceOnColorsBW; void setKeepSurfaceOnColorsBW(bool? value, [bool notify = true]) { diff --git a/example/lib/theme/model/app_theme.dart b/example/lib/theme/model/app_theme.dart index c505c5c..9ee2d31 100644 --- a/example/lib/theme/model/app_theme.dart +++ b/example/lib/theme/model/app_theme.dart @@ -50,6 +50,7 @@ class AppTheme { : controller.usedVariant .tones(Brightness.light) .monochromeSurfaces(controller.useMonoSurfaces) + .higherContrastFixed(controller.higherContrastFixedColors) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) .surfacesUseBW(controller.keepLightSurfaceColorsWhite) @@ -110,6 +111,7 @@ class AppTheme { : controller.usedVariant .tones(Brightness.dark) .monochromeSurfaces(controller.useMonoSurfaces) + .higherContrastFixed(controller.higherContrastFixedColors) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) .surfacesUseBW(controller.keepDarkSurfaceColorsBlack) diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef64..8e02df2 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/example/pubspec.lock b/example/pubspec.lock index 87fc228..214e951 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -71,7 +71,7 @@ packages: path: ".." relative: true source: path - version: "3.1.2" + version: "3.2.0" flutter: dependency: "direct main" description: flutter @@ -91,18 +91,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -123,18 +123,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" path: dependency: transitive description: @@ -200,10 +200,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" url_launcher: dependency: "direct main" description: @@ -216,10 +216,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "95d8027db36a0e52caf55680f91e33ea6aa12a3ce608c90b06f4e429a21067ac" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.3.5" + version: "6.3.10" url_launcher_ios: dependency: transitive description: @@ -232,10 +232,10 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: @@ -256,10 +256,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.3" url_launcher_windows: dependency: transitive description: @@ -280,18 +280,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index bc815b7..887e90f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: flex_seed_scheme_example description: Example that demonstrate how to use the FlexSeedScheme package. -version: 3.1.2 +version: 3.2.0 publish_to: 'none' environment: sdk: '>=3.0.0 <4.0.0' diff --git a/lib/src/flex/flex_tonal_palette.dart b/lib/src/flex/flex_tonal_palette.dart index 3276a4b..d846b20 100644 --- a/lib/src/flex/flex_tonal_palette.dart +++ b/lib/src/flex/flex_tonal_palette.dart @@ -130,10 +130,7 @@ class FlexTonalPalette { /// for all [FlexTones]. static const int commonSize = 15; - /// Extended one values in a [FlexTonalPalette]. - /// - /// Contains custom tones 2, 5 and 97 in addition to the 13 tones included - /// in the Material 3 guide tonal palette. + /// Extended tone values in a [FlexTonalPalette]. /// /// The added tones 4, 6, 12, 17, 22 and 24 are for new dark mode surfaces /// in revised Material 3 dark surface colors. Likewise added tones @@ -143,8 +140,9 @@ class FlexTonalPalette { /// The additional tones in the Material 3 specification appeared during later /// part of first half of 2023. /// - /// Tones 2, 5, and 97 are not in old or new M3 spec, but FlexSeedScheme - /// includes them to enable even more fidelity in dark and light tones. + /// Tones 2, 5, 65, 75, 84 and 97 are not in old or new M3 spec, but + /// FlexSeedScheme includes them to enable even more fidelity in dark and + /// light tones. /// /// Starting from Flutter 3.22 and FlexSeedScheme 2.0.0 the common tones /// should be avoided and extended tones used instead. The common tones are @@ -167,8 +165,11 @@ class FlexTonalPalette { 40, 50, 60, + 65, 70, + 75, 80, + 84, 87, 90, 92, @@ -191,14 +192,14 @@ class FlexTonalPalette { /// to [FlexTonalPalette.commonTones.length]. Here we instead manually set it /// to compile time const of same const list length. /// - /// Flutter SDK [TonalPalette] has 13 tones, [FlexTonalPalette] extended 27. + /// Flutter SDK [TonalPalette] has 13 tones, [FlexTonalPalette] extended 30. /// /// Starting from Flutter 3.22 and FlexSeedScheme 2.0.0 the common tones /// should be avoided and extended tones used instead. The common tones are /// kept for backwards compatibility and for cases where the original M3 /// palette is needed. The [FlexPaletteType.extended] is the new default /// for all [FlexTones]. - static const int extendedSize = 27; + static const int extendedSize = 30; /// The hue of the palette. final double hue; diff --git a/lib/src/flex/flex_tones.dart b/lib/src/flex/flex_tones.dart index a4afcb7..ef5029b 100644 --- a/lib/src/flex/flex_tones.dart +++ b/lib/src/flex/flex_tones.dart @@ -906,10 +906,10 @@ class FlexTones with Diagnosticable { /// /// The [useBW] flag is true by default, making the function effective. /// If set to false, the function is a no op and just returns the [FlexTones] - /// object unmodified. This is typically used to control applying the tint - /// removal via a controller. + /// object unmodified. This is typically used to control applying + /// modifier via a controller. /// - /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// **NOTE**: If some [FlexTones] modifiers change same properties, the used /// order in which they are applied matters. The last one applied will be /// the one that is used. FlexTones onMainsUseBW([bool useBW = true]) { @@ -947,17 +947,16 @@ class FlexTones with Diagnosticable { /// /// The [useBW] flag is true by default, making the function effective. /// If set to false, the function is a no op and just returns the [FlexTones] - /// object unmodified. This is typically used to control applying the tint - /// removal via a controller. + /// object unmodified. This is typically used to control applying + /// modifier via a controller. /// - /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// **NOTE**: If some [FlexTones] modifiers change same properties, the used /// order in which they are applied matters. The last one applied will be /// the one that is used. FlexTones onSurfacesUseBW([bool useBW = true]) { // ignore: avoid_returning_this if (!useBW) return this; return copyWith( - // onBackgroundTone: backgroundTone <= 60 ? 100 : 0, onSurfaceTone: surfaceTone <= 60 ? 100 : 0, onSurfaceVariantTone: surfaceTone <= 60 ? 100 : 0, onInverseSurfaceTone: inverseSurfaceTone <= 60 ? 100 : 0, @@ -976,10 +975,10 @@ class FlexTones with Diagnosticable { /// /// The [useBW] flag is true by default, making the function effective. /// If set to false, the function is a no op and just returns the [FlexTones] - /// object unmodified. This is typically used to control applying the tint - /// removal via a controller. + /// object unmodified. This is typically used to control applying + /// modifier via a controller. /// - /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// **NOTE**: If some [FlexTones] modifiers change same properties, the used /// order in which they are applied matters. The last one applied will be /// the one that is used. FlexTones surfacesUseBW([bool useBW = true]) { @@ -999,9 +998,9 @@ class FlexTones with Diagnosticable { /// The [useMonochrome] flag is true by default, making the function /// effective. If set to false, the function is a no op and just returns the /// [FlexTones] object unmodified. This is typically used to control applying - /// the tint removal via a controller. + /// modifier via a controller. /// - /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// **NOTE**: If some [FlexTones] modifiers change same properties, the used /// order in which they are applied matters. The last one applied will be /// the one that is used. FlexTones monochromeSurfaces([bool useMonochrome = true]) { @@ -1042,7 +1041,7 @@ class FlexTones with Diagnosticable { /// [FlexTones] object unmodified. This is typically used to control applying /// modifier via a controller. /// - /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// **NOTE**: If some [FlexTones] modifiers change same properties, the used /// order in which they are applied matters. The last one applied will be /// the one that is used. FlexTones expressiveOnContainer([bool useExpressive = true]) { @@ -1056,6 +1055,40 @@ class FlexTones with Diagnosticable { ); } + /// Returns a new [FlexTones] instance where the tones for all fixed colors + /// are modified. + /// + /// This modifier can be applied to any predefined or custom + /// [FlexTones] to make a returned instance where the tones for + /// the fixed colors `fixed`, `onFixed`, `fixedDim`, `onFixedVariant` are + /// set to 92, 6, 84, 12 instead Material-3 designs specified 90, 10, 80, 30. + /// + /// This gives us an alternative set of fixed colors with more contrast. + /// + /// **NOTE**: If some [FlexTones] modifiers change same properties, the used + /// order in which they are applied matters. The last one applied will be + /// the one that is used. + FlexTones higherContrastFixed([bool useHigherContrast = true]) { + // ignore: avoid_returning_this + if (!useHigherContrast) return this; + return copyWith( + primaryFixedTone: 92, + primaryFixedDimTone: 84, + onPrimaryFixedTone: 6, + onPrimaryFixedVariantTone: 12, + // + secondaryFixedTone: 92, + secondaryFixedDimTone: 84, + onSecondaryFixedTone: 6, + onSecondaryFixedVariantTone: 12, + // + tertiaryFixedTone: 92, + tertiaryFixedDimTone: 84, + onTertiaryFixedTone: 6, + onTertiaryFixedVariantTone: 12, + ); + } + /// Tone used for [ColorScheme.primary] from primary [FlexTonalPalette]. final int primaryTone; diff --git a/pubspec.lock b/pubspec.lock index 4b85047..b95d570 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.7.0" args: dependency: transitive description: @@ -77,18 +82,18 @@ packages: dependency: transitive description: name: coverage - sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" + sha256: "576aaab8b1abdd452e0f656c3e73da9ead9d7880e15bdc494189d9c1a1baf0db" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.0" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" fake_async: dependency: transitive description: @@ -167,18 +172,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -195,6 +200,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: "direct dev" description: @@ -207,26 +220,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: "direct main" description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" node_preamble: dependency: transitive description: @@ -295,10 +308,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -308,10 +321,10 @@ packages: dependency: transitive description: name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" source_maps: dependency: transitive description: @@ -364,26 +377,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.4" typed_data: dependency: transitive description: @@ -404,10 +417,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: @@ -420,18 +433,26 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "3.0.1" webkit_inspection_protocol: dependency: transitive description: @@ -449,5 +470,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.4.0 <4.0.0" flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 560df1f..32f61bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flex_seed_scheme description: A more flexible and powerful version of Flutter's ColorScheme.fromSeed. Use multiple seed colors, custom chroma and tone mapping. -version: 3.1.2 +version: 3.2.0 homepage: https://github.com/rydmike/flex_seed_scheme repository: https://github.com/rydmike/flex_seed_scheme issue_tracker: https://github.com/rydmike/flex_seed_scheme/issues diff --git a/test/flex_core_palette_test.dart b/test/flex_core_palette_test.dart index 7703a68..eea7324 100644 --- a/test/flex_core_palette_test.dart +++ b/test/flex_core_palette_test.dart @@ -676,6 +676,9 @@ void main() { FlexTonalPalette.extendedTones[i] != 17 && FlexTonalPalette.extendedTones[i] != 22 && FlexTonalPalette.extendedTones[i] != 24 && + FlexTonalPalette.extendedTones[i] != 65 && + FlexTonalPalette.extendedTones[i] != 75 && + FlexTonalPalette.extendedTones[i] != 84 && FlexTonalPalette.extendedTones[i] != 87 && FlexTonalPalette.extendedTones[i] != 92 && FlexTonalPalette.extendedTones[i] != 94 && @@ -708,6 +711,9 @@ void main() { FlexTonalPalette.extendedTones[i] != 17 && FlexTonalPalette.extendedTones[i] != 22 && FlexTonalPalette.extendedTones[i] != 24 && + FlexTonalPalette.extendedTones[i] != 65 && + FlexTonalPalette.extendedTones[i] != 75 && + FlexTonalPalette.extendedTones[i] != 84 && FlexTonalPalette.extendedTones[i] != 87 && FlexTonalPalette.extendedTones[i] != 92 && FlexTonalPalette.extendedTones[i] != 94 && @@ -739,6 +745,9 @@ void main() { FlexTonalPalette.extendedTones[i] != 17 && FlexTonalPalette.extendedTones[i] != 22 && FlexTonalPalette.extendedTones[i] != 24 && + FlexTonalPalette.extendedTones[i] != 65 && + FlexTonalPalette.extendedTones[i] != 75 && + FlexTonalPalette.extendedTones[i] != 84 && FlexTonalPalette.extendedTones[i] != 87 && FlexTonalPalette.extendedTones[i] != 92 && FlexTonalPalette.extendedTones[i] != 94 && @@ -770,6 +779,9 @@ void main() { FlexTonalPalette.extendedTones[i] != 17 && FlexTonalPalette.extendedTones[i] != 22 && FlexTonalPalette.extendedTones[i] != 24 && + FlexTonalPalette.extendedTones[i] != 65 && + FlexTonalPalette.extendedTones[i] != 75 && + FlexTonalPalette.extendedTones[i] != 84 && FlexTonalPalette.extendedTones[i] != 87 && FlexTonalPalette.extendedTones[i] != 92 && FlexTonalPalette.extendedTones[i] != 94 && @@ -801,6 +813,9 @@ void main() { FlexTonalPalette.extendedTones[i] != 17 && FlexTonalPalette.extendedTones[i] != 22 && FlexTonalPalette.extendedTones[i] != 24 && + FlexTonalPalette.extendedTones[i] != 65 && + FlexTonalPalette.extendedTones[i] != 75 && + FlexTonalPalette.extendedTones[i] != 84 && FlexTonalPalette.extendedTones[i] != 87 && FlexTonalPalette.extendedTones[i] != 92 && FlexTonalPalette.extendedTones[i] != 94 && @@ -832,6 +847,9 @@ void main() { FlexTonalPalette.extendedTones[i] != 17 && FlexTonalPalette.extendedTones[i] != 22 && FlexTonalPalette.extendedTones[i] != 24 && + FlexTonalPalette.extendedTones[i] != 65 && + FlexTonalPalette.extendedTones[i] != 75 && + FlexTonalPalette.extendedTones[i] != 84 && FlexTonalPalette.extendedTones[i] != 87 && FlexTonalPalette.extendedTones[i] != 92 && FlexTonalPalette.extendedTones[i] != 94 && @@ -855,6 +873,7 @@ void main() { tertiaryChroma: 24, paletteType: FlexPaletteType.extended, ); + // print(m3.asList()); test( 'FCP2.07: GIVEN FlexCorePalette.fromSeeds with 3 colors and extended ' 'EXPECT a given list result', () { @@ -874,8 +893,11 @@ void main() { 4284960932, 4286605759, 4288316379, + 4289237225, 4290158072, + 4291013887, 4291804415, + 4292463359, 4292989951, 4293516799, 4293846271, @@ -901,8 +923,11 @@ void main() { 4284636016, 4286280842, 4287991204, + 4288846514, 4289767359, + 4290688461, 4291609307, + 4292333031, 4292925167, 4293451512, 4293846270, @@ -928,8 +953,11 @@ void main() { 4286468704, 4288244345, 4290085778, + 4291006624, 4291993005, + 4292913850, 4293900488, + 4294689747, 4294954459, 4294957539, 4294959592, @@ -955,8 +983,11 @@ void main() { 4284505442, 4286150266, 4287860628, + 4288716193, 4289637038, + 4290557884, 4291478986, + 4292202709, 4292729053, 4293321190, 4293715947, @@ -982,8 +1013,11 @@ void main() { 4284570982, 4286215551, 4287926169, + 4288781478, 4289702324, + 4290557889, 4291478735, + 4292202458, 4292794595, 4293386475, 4293715697, @@ -1009,8 +1043,11 @@ void main() { 4290386458, 4292753200, 4294923337, + 4294930788, 4294936957, + 4294942612, 4294948011, + 4294951868, 4294954953, 4294957782, 4294959838, @@ -1033,6 +1070,7 @@ void main() { tertiaryChroma: null, paletteType: FlexPaletteType.extended, ); + // print(m4.asList()); test( 'FCP2.08: GIVEN FlexCorePalette.fromSeeds with 3 colors and using ' 'chroma from secondary and tertiary and extended palette ' @@ -1055,8 +1093,11 @@ void main() { 4284960932, 4286605759, 4288316379, + 4289237225, 4290158072, + 4291013887, 4291804415, + 4292463359, 4292989951, 4293516799, 4293846271, @@ -1082,8 +1123,11 @@ void main() { 4284636017, 4286280586, 4287991205, + 4288846514, 4289767360, + 4290688462, 4291609308, + 4292333031, 4292925168, 4293451513, 4293846270, @@ -1109,8 +1153,11 @@ void main() { 4286403168, 4288178809, 4290020242, + 4291006623, 4291927469, + 4292913850, 4293834952, + 4294624211, 4294954459, 4294957539, 4294959592, @@ -1136,8 +1183,11 @@ void main() { 4284505442, 4286150266, 4287860628, + 4288716193, 4289637038, + 4290557884, 4291478986, + 4292202709, 4292729053, 4293321190, 4293715947, @@ -1163,8 +1213,11 @@ void main() { 4284570982, 4286215551, 4287926169, + 4288781478, 4289702324, + 4290557889, 4291478735, + 4292202458, 4292794595, 4293386475, 4293715697, @@ -1190,8 +1243,11 @@ void main() { 4290386458, 4292753200, 4294923337, + 4294930788, 4294936957, + 4294942612, 4294948011, + 4294951868, 4294954953, 4294957782, 4294959838, @@ -1285,6 +1341,7 @@ void main() { 'FCP1.U07: GIVEN a FlexCorePalette from a extended List ' 'EXPECT it to be equal to one created from same seed Based ' 'extended one', () { + // print(m3.asList()); expect( m3, equals( @@ -1304,8 +1361,11 @@ void main() { 4284960932, 4286605759, 4288316379, + 4289237225, 4290158072, + 4291013887, 4291804415, + 4292463359, 4292989951, 4293516799, 4293846271, @@ -1331,8 +1391,11 @@ void main() { 4284636016, 4286280842, 4287991204, + 4288846514, 4289767359, + 4290688461, 4291609307, + 4292333031, 4292925167, 4293451512, 4293846270, @@ -1358,8 +1421,11 @@ void main() { 4286468704, 4288244345, 4290085778, + 4291006624, 4291993005, + 4292913850, 4293900488, + 4294689747, 4294954459, 4294957539, 4294959592, @@ -1385,8 +1451,11 @@ void main() { 4284505442, 4286150266, 4287860628, + 4288716193, 4289637038, + 4290557884, 4291478986, + 4292202709, 4292729053, 4293321190, 4293715947, @@ -1412,8 +1481,11 @@ void main() { 4284570982, 4286215551, 4287926169, + 4288781478, 4289702324, + 4290557889, 4291478735, + 4292202458, 4292794595, 4293386475, 4293715697, @@ -1439,8 +1511,11 @@ void main() { 4290386458, 4292753200, 4294923337, + 4294930788, 4294936957, + 4294942612, 4294948011, + 4294951868, 4294954953, 4294957782, 4294959838, diff --git a/test/flex_seed_scheme_test.dart b/test/flex_seed_scheme_test.dart index aeecf1f..fe46845 100644 --- a/test/flex_seed_scheme_test.dart +++ b/test/flex_seed_scheme_test.dart @@ -894,6 +894,48 @@ void main() { expect(scheme, scheme2); }); + test( + 'FCS7.014-fixedColor-l: GIVEN a SeedColorScheme.fromSeeds using ' + 'five seeds and tones map FlexTones.material for a light scheme with ' + 'error neutral and variant chroma set but with modified fixed ' + 'tones and variant tones ' + 'EXPECT scheme equal to using higherContrastFixed()', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + tones: FlexTones.material(Brightness.light).copyWith( + primaryFixedTone: 92, + primaryFixedDimTone: 84, + onPrimaryFixedTone: 6, + onPrimaryFixedVariantTone: 12, + // + secondaryFixedTone: 92, + secondaryFixedDimTone: 84, + onSecondaryFixedTone: 6, + onSecondaryFixedVariantTone: 12, + // + tertiaryFixedTone: 92, + tertiaryFixedDimTone: 84, + onTertiaryFixedTone: 6, + onTertiaryFixedVariantTone: 12, + ), + ); + final ColorScheme scheme2 = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + tones: FlexTones.material(Brightness.light).higherContrastFixed(), + ); + expect(scheme, scheme2); + }); + test( 'FCS7.015-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and variant tonalSpot for a light scheme ' @@ -1011,7 +1053,7 @@ void main() { test( 'FCS7.017-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and variant content for a light scheme ' - 'EXPECT some given checked color result', () { + 'EXPECT this given checked color result', () { final ColorScheme scheme = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -1036,7 +1078,7 @@ void main() { test( 'FCS7.018-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and variant expressive for a light scheme ' - 'EXPECT some given checked color result', () { + 'EXPECT this given checked color result', () { final ColorScheme scheme = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -1061,7 +1103,7 @@ void main() { test( 'FCS7.019-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and variant fidelity for a light scheme ' - 'EXPECT some given checked color result', () { + 'EXPECT this given checked color result', () { final ColorScheme scheme = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -1086,7 +1128,7 @@ void main() { test( 'FCS7.020-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and fruitSalad content for a light scheme ' - 'EXPECT some given checked color result', () { + 'EXPECT this given checked color result', () { final ColorScheme scheme = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -1111,7 +1153,7 @@ void main() { test( 'FCS7.021-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and monochrome content for a light scheme ' - 'EXPECT some given checked color result', () { + 'EXPECT this given checked color result', () { final ColorScheme scheme = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -1136,7 +1178,7 @@ void main() { test( 'FCS7.022-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and neutral content for a light scheme ' - 'EXPECT some given checked color result', () { + 'EXPECT this given checked color result', () { final ColorScheme scheme = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -1161,7 +1203,7 @@ void main() { test( 'FCS7.023-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and rainbow content for a light scheme ' - 'EXPECT some given checked color result', () { + 'EXPECT this given checked color result', () { final ColorScheme scheme = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -1186,7 +1228,7 @@ void main() { test( 'FCS7.024-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' 'and vibrant content for a light scheme ' - 'EXPECT some given checked color result', () { + 'EXPECT this given checked color result', () { final ColorScheme scheme = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, diff --git a/test/flex_tonal_palette_test.dart b/test/flex_tonal_palette_test.dart index 968981b..a170fd9 100644 --- a/test/flex_tonal_palette_test.dart +++ b/test/flex_tonal_palette_test.dart @@ -249,6 +249,7 @@ void main() { FlexTonalPalette.of(40, 55, FlexPaletteType.extended); // m3, is tonal palettes using TonalPalette. final TonalPalette m3 = TonalPalette.of(40, 55); + // print(m1.asList); // m4, is tonal palette from list final FlexTonalPalette m4 = FlexTonalPalette.fromList(const [ 4278190080, @@ -266,8 +267,11 @@ void main() { 4288692500, 4290795563, 4292964674, + 4294016333, 4294937692, + 4294943100, 4294948249, + 4294952367, 4294955198, 4294958030, 4294959832, @@ -296,8 +300,11 @@ void main() { 4288692500, 4290795563, 4292964674, + 4294016333, 4294937692, + 4294943100, 4294948249, + 4294952367, 4294955198, 4294958030, 4294959832, @@ -390,6 +397,9 @@ void main() { FlexTonalPalette.extendedTones[i] != 17 && FlexTonalPalette.extendedTones[i] != 22 && FlexTonalPalette.extendedTones[i] != 24 && + FlexTonalPalette.extendedTones[i] != 65 && + FlexTonalPalette.extendedTones[i] != 75 && + FlexTonalPalette.extendedTones[i] != 84 && FlexTonalPalette.extendedTones[i] != 87 && FlexTonalPalette.extendedTones[i] != 92 && FlexTonalPalette.extendedTones[i] != 94 && @@ -422,7 +432,7 @@ void main() { m4.toString(), equals( // ignore: lines_longer_than_80_chars - 'FlexTonalPalette.fromList([4278190080, 4279567104, 4280354304, 4280616704, 4280879360, 4281798144, 4282257664, 4283373568, 4284095488, 4284555008, 4285014528, 4286524160, 4288692500, 4290795563, 4292964674, 4294937692, 4294948249, 4294955198, 4294958030, 4294959832, 4294961634, 4294962663, 4294963692, 4294964465, 4294965494, 4294966271, 4294967295], FlexPaletteType.extended)'), + 'FlexTonalPalette.fromList([4278190080, 4279567104, 4280354304, 4280616704, 4280879360, 4281798144, 4282257664, 4283373568, 4284095488, 4284555008, 4285014528, 4286524160, 4288692500, 4290795563, 4292964674, 4294016333, 4294937692, 4294943100, 4294948249, 4294952367, 4294955198, 4294958030, 4294959832, 4294961634, 4294962663, 4294963692, 4294964465, 4294965494, 4294966271, 4294967295], FlexPaletteType.extended)'), ); }); test( @@ -444,8 +454,11 @@ void main() { 4288692500, 4290795563, 4292964674, + 4294016333, 4294937692, + 4294943100, 4294948249, + 4294952367, 4294955198, 4294958030, 4294959832, @@ -483,8 +496,11 @@ void main() { 4288692500, 4290795563, 4292964674, + 4294016333, 4294937692, + 4294943100, 4294948249, + 4294952367, 4294955198, 4294958030, 4294959832,