diff --git a/material_3_demo/ios/Flutter/Debug.xcconfig b/material_3_demo/ios/Flutter/Debug.xcconfig index 592ceee85..ec97fc6f3 100644 --- a/material_3_demo/ios/Flutter/Debug.xcconfig +++ b/material_3_demo/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/material_3_demo/ios/Flutter/Release.xcconfig b/material_3_demo/ios/Flutter/Release.xcconfig index 592ceee85..c4855bfe2 100644 --- a/material_3_demo/ios/Flutter/Release.xcconfig +++ b/material_3_demo/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/material_3_demo/ios/Podfile b/material_3_demo/ios/Podfile new file mode 100644 index 000000000..fdcc671eb --- /dev/null +++ b/material_3_demo/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/material_3_demo/lib/color_palettes_screen.dart b/material_3_demo/lib/color_palettes_screen.dart index 5380390e3..db399f817 100644 --- a/material_3_demo/lib/color_palettes_screen.dart +++ b/material_3_demo/lib/color_palettes_screen.dart @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; const Widget divider = SizedBox(height: 10); @@ -45,18 +47,46 @@ class ColorPalettesScreen extends StatelessWidget { ); } + Widget dynamicColorNotice() => RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: Theme.of(context).textTheme.bodySmall, + children: [ + const TextSpan( + text: 'To create color schemes based on a ' + 'platform\'s implementation of dynamic color, ' + 'use the '), + TextSpan( + text: 'dynamic_color', + style: const TextStyle(decoration: TextDecoration.underline), + recognizer: TapGestureRecognizer() + ..onTap = () async { + final url = Uri.parse( + 'https://pub.dev/packages/dynamic_color', + ); + if (!await launchUrl(url)) { + throw Exception('Could not launch $url'); + } + }, + ), + const TextSpan(text: ' package.'), + ], + ), + ); + return Expanded( child: LayoutBuilder(builder: (context, constraints) { if (constraints.maxWidth < narrowScreenWidthThreshold) { return SingleChildScrollView( child: Column( children: [ + dynamicColorNotice(), divider, - schemeLabel('Light Theme'), + schemeLabel('Light ColorScheme'), schemeView(lightTheme), divider, divider, - schemeLabel('Dark Theme'), + schemeLabel('Dark ColorScheme'), schemeView(darkTheme), ], ), @@ -65,23 +95,28 @@ class ColorPalettesScreen extends StatelessWidget { return SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(top: 5), - child: Row( + child: Column( children: [ - Expanded( - child: Column( - children: [ - schemeLabel('Light Theme'), - schemeView(lightTheme), - ], - ), - ), - Expanded( - child: Column( - children: [ - schemeLabel('Dark Theme'), - schemeView(darkTheme), - ], - ), + dynamicColorNotice(), + Row( + children: [ + Expanded( + child: Column( + children: [ + schemeLabel('Light ColorScheme'), + schemeView(lightTheme), + ], + ), + ), + Expanded( + child: Column( + children: [ + schemeLabel('Dark ColorScheme'), + schemeView(darkTheme), + ], + ), + ), + ], ), ], ), diff --git a/material_3_demo/lib/component_screen.dart b/material_3_demo/lib/component_screen.dart index a1aeb7d70..ff3c8bd01 100644 --- a/material_3_demo/lib/component_screen.dart +++ b/material_3_demo/lib/component_screen.dart @@ -112,6 +112,9 @@ class Containment extends StatelessWidget { BottomSheetSection(), Cards(), Dialogs(), + Dividers(), + // TODO: Add Lists, https://github.com/flutter/flutter/issues/114006 + // TODO: Add Side sheets, https://github.com/flutter/flutter/issues/119328 ]); } } @@ -130,7 +133,10 @@ class Navigation extends StatelessWidget { isExampleBar: true, ), NavigationDrawers(scaffoldKey: scaffoldKey), + const NavigationRails(), + // TODO: Add Search https://github.com/flutter/flutter/issues/117483 const Tabs(), + const TopAppBars(), ]); } } @@ -141,12 +147,14 @@ class Selection extends StatelessWidget { @override Widget build(BuildContext context) { return const ComponentGroupDecoration(label: 'Selection', children: [ + Checkboxes(), Chips(), - DropdownMenus(), + // TODO: Add Date pickers https://github.com/flutter/flutter/issues/101481 + Menus(), Radios(), - Checkboxes(), Sliders(), Switches(), + // TODO: Add Time pickers https://github.com/flutter/flutter/issues/101480 ]); } } @@ -179,14 +187,11 @@ class _ButtonsState extends State { 'Use ElevatedButton, FilledButton, FilledButton.tonal, OutlinedButton, or TextButton', child: SingleChildScrollView( scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: tinySpacing), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, children: const [ ButtonsWithoutIcon(isDisabled: false), - colDivider, ButtonsWithIcon(), - colDivider, ButtonsWithoutIcon(isDisabled: true), ], ), @@ -202,33 +207,39 @@ class ButtonsWithoutIcon extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - children: [ - ElevatedButton( - onPressed: isDisabled ? null : () {}, - child: const Text('Elevated'), - ), - const SizedBox(width: tinySpacing), - FilledButton( - onPressed: isDisabled ? null : () {}, - child: const Text('Filled'), - ), - const SizedBox(width: tinySpacing), - FilledButton.tonal( - onPressed: isDisabled ? null : () {}, - child: const Text('Filled tonal'), - ), - const SizedBox(width: tinySpacing), - OutlinedButton( - onPressed: isDisabled ? null : () {}, - child: const Text('Outlined'), - ), - const SizedBox(width: tinySpacing), - TextButton( - onPressed: isDisabled ? null : () {}, - child: const Text('Text'), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Elevated'), + ), + colDivider, + FilledButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Filled'), + ), + colDivider, + FilledButton.tonal( + onPressed: isDisabled ? null : () {}, + child: const Text('Filled tonal'), + ), + colDivider, + OutlinedButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Outlined'), + ), + colDivider, + TextButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Text'), + ), + ], ), - ], + ), ); } } @@ -238,38 +249,44 @@ class ButtonsWithIcon extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - children: [ - ElevatedButton.icon( - onPressed: () {}, - icon: const Icon(Icons.add), - label: const Text('Icon'), - ), - const SizedBox(width: tinySpacing), - FilledButton.icon( - onPressed: () {}, - label: const Text('Icon'), - icon: const Icon(Icons.add), - ), - const SizedBox(width: tinySpacing), - FilledButton.tonalIcon( - onPressed: () {}, - label: const Text('Icon'), - icon: const Icon(Icons.add), - ), - const SizedBox(width: tinySpacing), - OutlinedButton.icon( - onPressed: () {}, - icon: const Icon(Icons.add), - label: const Text('Icon'), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + colDivider, + FilledButton.icon( + onPressed: () {}, + label: const Text('Icon'), + icon: const Icon(Icons.add), + ), + colDivider, + FilledButton.tonalIcon( + onPressed: () {}, + label: const Text('Icon'), + icon: const Icon(Icons.add), + ), + colDivider, + OutlinedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + colDivider, + TextButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ) + ], ), - const SizedBox(width: tinySpacing), - TextButton.icon( - onPressed: () {}, - icon: const Icon(Icons.add), - label: const Text('Icon'), - ) - ], + ), ); } } @@ -597,21 +614,74 @@ class _DialogsState extends State { ); } + void openFullscreenDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => Dialog.fullscreen( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Scaffold( + appBar: AppBar( + title: const Text('Full-screen dialog'), + centerTitle: false, + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + ), + ), + ); + } + @override Widget build(BuildContext context) { - return Center( - child: ComponentDecoration( - label: 'Dialog', - tooltipMessage: 'Use AlertDialog or SimpleDialog', - child: UnconstrainedBox( - child: TextButton( + return ComponentDecoration( + label: 'Dialog', + tooltipMessage: + 'Use showDialog with Dialog.fullscreen, AlertDialog, or SimpleDialog', + child: Wrap( + alignment: WrapAlignment.spaceBetween, + children: [ + TextButton( child: const Text( 'Show dialog', style: TextStyle(fontWeight: FontWeight.bold), ), onPressed: () => openDialog(context), ), - ), + TextButton( + child: const Text( + 'Show full-screen dialog', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () => openFullscreenDialog(context), + ), + ], + ), + ); + } +} + +class Dividers extends StatelessWidget { + const Dividers({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Dividers', + tooltipMessage: 'Use Divider or VerticalDivider', + child: Column( + children: const [ + Divider(key: Key('divider')), + ], ), ); } @@ -1299,7 +1369,7 @@ class SegmentedButtons extends StatelessWidget { Widget build(BuildContext context) { return ComponentDecoration( label: 'Segmented buttons', - tooltipMessage: 'Use SegmentedButton', + tooltipMessage: 'Use SegmentedButton', child: Column( children: const [ SingleChoice(), @@ -1400,7 +1470,8 @@ class SnackBarSection extends StatelessWidget { Widget build(BuildContext context) { return ComponentDecoration( label: 'Snackbar', - tooltipMessage: 'Use SnackBar', + tooltipMessage: + 'Use ScaffoldMessenger.of(context).showSnackBar with SnackBar', child: TextButton( onPressed: () { final snackBar = SnackBar( @@ -1425,9 +1496,17 @@ class SnackBarSection extends StatelessWidget { } } -class BottomSheetSection extends StatelessWidget { +class BottomSheetSection extends StatefulWidget { const BottomSheetSection({super.key}); + @override + State createState() => _BottomSheetSectionState(); +} + +class _BottomSheetSectionState extends State { + bool isNonModalBottomSheetOpen = false; + PersistentBottomSheetController? _nonModalBottomSheetController; + @override Widget build(BuildContext context) { List buttonList = [ @@ -1463,31 +1542,77 @@ class BottomSheetSection extends StatelessWidget { return ComponentDecoration( label: 'Bottom sheet', tooltipMessage: 'Use showModalBottomSheet or showBottomSheet', - child: TextButton( - child: const Text( - 'Show bottom sheet', - style: TextStyle(fontWeight: FontWeight.bold), - ), - onPressed: () { - showModalBottomSheet( - context: context, - // TODO: Remove when this is in the framework https://github.com/flutter/flutter/issues/118619 - constraints: const BoxConstraints(maxWidth: 640), - builder: (context) { - return SizedBox( - height: 150, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: ListView( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: buttonList, - ), - ), + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + TextButton( + child: const Text( + 'Show modal bottom sheet', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + showModalBottomSheet( + context: context, + // TODO: Remove when this is in the framework https://github.com/flutter/flutter/issues/118619 + constraints: const BoxConstraints(maxWidth: 640), + builder: (context) { + return SizedBox( + height: 150, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: buttonList, + ), + ), + ); + }, ); }, - ); - }, + ), + TextButton( + child: Text( + isNonModalBottomSheetOpen + ? 'Hide bottom sheet' + : 'Show bottom sheet', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + if (isNonModalBottomSheetOpen) { + _nonModalBottomSheetController?.close(); + setState(() { + isNonModalBottomSheetOpen = false; + }); + return; + } else { + setState(() { + isNonModalBottomSheetOpen = true; + }); + } + + _nonModalBottomSheetController = showBottomSheet( + elevation: 8.0, + context: context, + // TODO: Remove when this is in the framework https://github.com/flutter/flutter/issues/118619 + constraints: const BoxConstraints(maxWidth: 640), + builder: (context) { + return SizedBox( + height: 150, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: buttonList, + ), + ), + ); + }, + ); + }, + ), + ], ), ); } @@ -1516,10 +1641,7 @@ class BottomAppBars extends StatelessWidget { bottomNavigationBar: BottomAppBar( child: Row( children: [ - IconButton( - icon: const Icon(Icons.more_vert), - onPressed: () {}, - ), + const IconButtonAnchorExample(), IconButton( tooltip: 'Search', icon: const Icon(Icons.search), @@ -1541,6 +1663,94 @@ class BottomAppBars extends StatelessWidget { } } +class IconButtonAnchorExample extends StatelessWidget { + const IconButtonAnchorExample({super.key}); + + @override + Widget build(BuildContext context) { + return MenuAnchor( + builder: (context, controller, child) { + return IconButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.more_vert), + ); + }, + menuChildren: [ + MenuItemButton( + child: const Text('Menu 1'), + onPressed: () {}, + ), + MenuItemButton( + child: const Text('Menu 2'), + onPressed: () {}, + ), + SubmenuButton( + menuChildren: [ + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.1'), + ), + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.2'), + ), + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.3'), + ), + ], + child: const Text('Menu 3'), + ), + ], + ); + } +} + +class ButtonAnchorExample extends StatelessWidget { + const ButtonAnchorExample({super.key}); + + @override + Widget build(BuildContext context) { + return MenuAnchor( + builder: (context, controller, child) { + return FilledButton.tonal( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + child: const Text('Show menu'), + ); + }, + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(Icons.people_alt_outlined), + child: const Text('Item 1'), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(Icons.remove_red_eye_outlined), + child: const Text('Item 2'), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(Icons.refresh), + onPressed: () {}, + child: const Text('Item 3'), + ), + ], + ); + } +} + class NavigationDrawers extends StatelessWidget { const NavigationDrawers({super.key, required this.scaffoldKey}); final GlobalKey scaffoldKey; @@ -1549,15 +1759,21 @@ class NavigationDrawers extends StatelessWidget { Widget build(BuildContext context) { return ComponentDecoration( label: 'Navigation drawer', - tooltipMessage: 'Use NavigationDrawer', - child: UnconstrainedBox( - child: TextButton( - child: const Text('Show navigation drawer', - style: TextStyle(fontWeight: FontWeight.bold)), - onPressed: () { - scaffoldKey.currentState!.openEndDrawer(); - }, - ), + tooltipMessage: + 'Use NavigationDrawer. For modal navigation drawers, see Scaffold.endDrawer', + child: Column( + children: [ + const SizedBox(height: 520, child: NavigationDrawerSection()), + colDivider, + colDivider, + TextButton( + child: const Text('Show modal navigation drawer', + style: TextStyle(fontWeight: FontWeight.bold)), + onPressed: () { + scaffoldKey.currentState!.openEndDrawer(); + }, + ), + ], ), ); } @@ -1598,10 +1814,7 @@ class _NavigationDrawerSectionState extends State { selectedIcon: destination.selectedIcon, ); }), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 28), - child: Divider(), - ), + const Divider(indent: 28, endIndent: 28), Padding( padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), child: Text( @@ -1645,6 +1858,57 @@ const List labelDestinations = [ ExampleDestination('Work', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), ]; +class NavigationRails extends StatelessWidget { + const NavigationRails({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Navigation rail', + tooltipMessage: 'Use NavigationRail', + child: IntrinsicWidth( + child: SizedBox(height: 420, child: NavigationRailSection())), + ); + } +} + +class NavigationRailSection extends StatefulWidget { + const NavigationRailSection({super.key}); + + @override + State createState() => _NavigationRailSectionState(); +} + +class _NavigationRailSectionState extends State { + int navRailIndex = 0; + + @override + Widget build(BuildContext context) { + return NavigationRail( + onDestinationSelected: (selectedIndex) { + setState(() { + navRailIndex = selectedIndex; + }); + }, + elevation: 4, + leading: FloatingActionButton( + child: const Icon(Icons.create), onPressed: () {}), + groupAlignment: 0.0, + selectedIndex: navRailIndex, + labelType: NavigationRailLabelType.selected, + destinations: [ + ...destinations.map((destination) { + return NavigationRailDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + ], + ); + } +} + class Tabs extends StatefulWidget { const Tabs({super.key}); @@ -1690,6 +1954,7 @@ class _TabsState extends State with TickerProviderStateMixin { ), ], ), + // TODO: Showcase secondary tab bar https://github.com/flutter/flutter/issues/111962 ), ), ), @@ -1697,14 +1962,84 @@ class _TabsState extends State with TickerProviderStateMixin { } } -class DropdownMenus extends StatefulWidget { - const DropdownMenus({super.key}); +class TopAppBars extends StatelessWidget { + const TopAppBars({super.key}); + + static final actions = [ + IconButton(icon: const Icon(Icons.attach_file), onPressed: () {}), + IconButton(icon: const Icon(Icons.event), onPressed: () {}), + IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}), + ]; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Top app bars', + tooltipMessage: + 'Use AppBar, SliverAppBar, SliverAppBar.medium, or SliverAppBar.large', + child: Column( + children: [ + AppBar( + title: const Text('Center-aligned'), + leading: const BackButton(), + actions: [ + IconButton( + iconSize: 32, + icon: const Icon(Icons.account_circle_outlined), + onPressed: () {}, + ), + ], + centerTitle: true, + ), + colDivider, + AppBar( + title: const Text('Small'), + leading: const BackButton(), + actions: actions, + centerTitle: false, + ), + colDivider, + SizedBox( + height: 100, + child: CustomScrollView( + slivers: [ + SliverAppBar.medium( + title: const Text('Medium'), + leading: const BackButton(), + actions: actions, + ), + const SliverFillRemaining(), + ], + ), + ), + colDivider, + SizedBox( + height: 130, + child: CustomScrollView( + slivers: [ + SliverAppBar.large( + title: const Text('Large'), + leading: const BackButton(), + actions: actions, + ), + const SliverFillRemaining(), + ], + ), + ), + ], + ), + ); + } +} + +class Menus extends StatefulWidget { + const Menus({super.key}); @override - State createState() => _DropdownMenusState(); + State createState() => _MenusState(); } -class _DropdownMenusState extends State { +class _MenusState extends State { final TextEditingController colorController = TextEditingController(); final TextEditingController iconController = TextEditingController(); IconLabel? selectedIcon = IconLabel.smile; @@ -1727,43 +2062,56 @@ class _DropdownMenusState extends State { } return ComponentDecoration( - label: 'Dropdown menus', - tooltipMessage: 'Use DropdownMenu', - child: Wrap( - alignment: WrapAlignment.spaceAround, - runAlignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.center, - spacing: smallSpacing, - runSpacing: smallSpacing, + label: 'Menus', + tooltipMessage: 'Use MenuAnchor or DropdownMenu', + child: Column( children: [ - DropdownMenu( - controller: colorController, - label: const Text('Color'), - enableFilter: true, - dropdownMenuEntries: colorEntries, - inputDecorationTheme: const InputDecorationTheme(filled: true), - onSelected: (color) { - setState(() { - selectedColor = color; - }); - }, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + ButtonAnchorExample(), + rowDivider, + IconButtonAnchorExample(), + ], ), - DropdownMenu( - initialSelection: IconLabel.smile, - controller: iconController, - leadingIcon: const Icon(Icons.search), - label: const Text('Icon'), - dropdownMenuEntries: iconEntries, - onSelected: (icon) { - setState(() { - selectedIcon = icon; - }); - }, + colDivider, + Wrap( + alignment: WrapAlignment.spaceAround, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: smallSpacing, + runSpacing: smallSpacing, + children: [ + DropdownMenu( + controller: colorController, + label: const Text('Color'), + enableFilter: true, + dropdownMenuEntries: colorEntries, + inputDecorationTheme: const InputDecorationTheme(filled: true), + onSelected: (color) { + setState(() { + selectedColor = color; + }); + }, + ), + DropdownMenu( + initialSelection: IconLabel.smile, + controller: iconController, + leadingIcon: const Icon(Icons.search), + label: const Text('Icon'), + dropdownMenuEntries: iconEntries, + onSelected: (icon) { + setState(() { + selectedIcon = icon; + }); + }, + ), + Icon( + selectedIcon?.icon, + color: selectedColor?.color ?? Colors.grey.withOpacity(0.5), + ) + ], ), - Icon( - selectedIcon?.icon, - color: selectedColor?.color ?? Colors.grey.withOpacity(0.5), - ) ], ), ); diff --git a/material_3_demo/macos/Flutter/Flutter-Debug.xcconfig b/material_3_demo/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b60..4b81f9b2d 100644 --- a/material_3_demo/macos/Flutter/Flutter-Debug.xcconfig +++ b/material_3_demo/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/material_3_demo/macos/Flutter/Flutter-Release.xcconfig b/material_3_demo/macos/Flutter/Flutter-Release.xcconfig index c2efd0b60..5caa9d157 100644 --- a/material_3_demo/macos/Flutter/Flutter-Release.xcconfig +++ b/material_3_demo/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/material_3_demo/macos/Flutter/GeneratedPluginRegistrant.swift b/material_3_demo/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817a5..8236f5728 100644 --- a/material_3_demo/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/material_3_demo/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/material_3_demo/macos/Podfile b/material_3_demo/macos/Podfile new file mode 100644 index 000000000..c795730db --- /dev/null +++ b/material_3_demo/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/material_3_demo/pubspec.yaml b/material_3_demo/pubspec.yaml index 63371e70b..09c335396 100644 --- a/material_3_demo/pubspec.yaml +++ b/material_3_demo/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: sdk: flutter cupertino_icons: ^1.0.2 + url_launcher: ^6.1.8 dev_dependencies: flutter_test: diff --git a/material_3_demo/test/color_screen_test.dart b/material_3_demo/test/color_screen_test.dart index 92a21673d..a29b089f7 100644 --- a/material_3_demo/test/color_screen_test.dart +++ b/material_3_demo/test/color_screen_test.dart @@ -18,8 +18,8 @@ void main() { addTearDown(tester.binding.window.clearPhysicalSizeTestValue); await tester.pumpWidget(const MaterialApp(home: Material3Demo())); - expect(find.text('Light Theme'), findsNothing); - expect(find.text('Dark Theme'), findsNothing); + expect(find.text('Light ColorScheme'), findsNothing); + expect(find.text('Dark ColorScheme'), findsNothing); expect(find.byType(NavigationBar), findsOneWidget); Finder colorIconOnBar = find.descendant( of: find.byType(NavigationBar), @@ -35,8 +35,8 @@ void main() { matching: find.widgetWithIcon(NavigationDestination, Icons.format_paint)); expect(selectedColorIconOnBar, findsOneWidget); - expect(find.text('Light Theme'), findsOneWidget); - expect(find.text('Dark Theme'), findsOneWidget); + expect(find.text('Light ColorScheme'), findsOneWidget); + expect(find.text('Dark ColorScheme'), findsOneWidget); }); testWidgets( @@ -47,9 +47,8 @@ void main() { addTearDown(tester.binding.window.clearPhysicalSizeTestValue); await tester.pumpWidget(const MaterialApp(home: Material3Demo())); await tester.pumpAndSettle(); - expect(find.text('Light Theme'), findsNothing); - expect(find.text('Dark Theme'), findsNothing); - expect(find.byType(NavigationRail), findsOneWidget); + expect(find.text('Light ColorScheme'), findsNothing); + expect(find.text('Dark ColorScheme'), findsNothing); Finder colorIconOnRail = find.descendant( of: find.byType(NavigationRail), matching: find.byIcon(Icons.format_paint_outlined)); @@ -61,16 +60,16 @@ void main() { of: find.byType(NavigationRail), matching: find.byIcon(Icons.format_paint)); expect(selectedColorIconOnRail, findsOneWidget); - expect(find.text('Light Theme'), findsOneWidget); - expect(find.text('Dark Theme'), findsOneWidget); + expect(find.text('Light ColorScheme'), findsOneWidget); + expect(find.text('Dark ColorScheme'), findsOneWidget); }); testWidgets('Color screen shows correct content', (tester) async { await tester.pumpWidget(MaterialApp( home: Scaffold(body: Row(children: const [ColorPalettesScreen()])), )); - expect(find.text('Light Theme'), findsOneWidget); - expect(find.text('Dark Theme'), findsOneWidget); + expect(find.text('Light ColorScheme'), findsOneWidget); + expect(find.text('Dark ColorScheme'), findsOneWidget); expect(find.byType(ColorGroup, skipOffstage: false), findsNWidgets(14)); }); } diff --git a/material_3_demo/test/component_screen_test.dart b/material_3_demo/test/component_screen_test.dart index 769b87e81..e5f820995 100644 --- a/material_3_demo/test/component_screen_test.dart +++ b/material_3_demo/test/component_screen_test.dart @@ -21,7 +21,7 @@ void main() { expect(find.widgetWithIcon(AppBar, Icons.palette_outlined), findsOneWidget); // Elements on the component screen - // Buttons + // Common buttons expect(find.widgetWithText(ElevatedButton, 'Elevated'), findsNWidgets(2)); expect(find.widgetWithText(FilledButton, 'Filled'), findsNWidgets(2)); expect(find.widgetWithText(FilledButton, 'Filled tonal'), findsNWidgets(2)); @@ -29,52 +29,102 @@ void main() { expect(find.widgetWithText(TextButton, 'Text'), findsNWidgets(2)); expect(find.widgetWithText(Buttons, 'Icon'), findsNWidgets(5)); - // IconButtons - expect(find.byType(IconToggleButton), findsNWidgets(8)); - // FABs - expect(find.byType(FloatingActionButton), - findsNWidgets(5)); // 2 more shows up in the bottom app bar. + expect( + find.byType(FloatingActionButton), + findsNWidgets( + 6)); // 2 more show up in the bottom app bar. 1 more in the navigation rail expect(find.widgetWithText(FloatingActionButton, 'Create'), findsOneWidget); - // Chips - expect(find.byType(ActionChip), - findsNWidgets(4)); // includes Assist and Suggestion chip. - expect(find.byType(FilterChip), findsNWidgets(2)); - expect(find.byType(InputChip), findsNWidgets(2)); + // Icon buttons + expect(find.byType(IconToggleButton), findsNWidgets(8)); + + // Segmented buttons + expect(find.byType(SegmentedButton), findsOneWidget); + expect(find.byType(SegmentedButton), findsOneWidget); + + // Badges + expect(find.byType(Badge), findsNWidgets(4)); + + // Progress indicators + Finder circularProgressIndicator = find.byType(CircularProgressIndicator); + expect(circularProgressIndicator, findsOneWidget); + Finder linearProgressIndicator = find.byType(LinearProgressIndicator); + expect(linearProgressIndicator, findsOneWidget); + + // Snackbar + expect(find.widgetWithText(TextButton, 'Show snackbar'), findsOneWidget); + + // Bottom sheet + expect(find.widgetWithText(TextButton, 'Show modal bottom sheet'), + findsOneWidget); + expect( + find.widgetWithText(TextButton, 'Show bottom sheet'), findsOneWidget); // Cards expect(find.widgetWithText(Cards, 'Elevated'), findsOneWidget); expect(find.widgetWithText(Cards, 'Filled'), findsOneWidget); expect(find.widgetWithText(Cards, 'Outlined'), findsOneWidget); - // TextFields - expect(find.widgetWithText(TextField, 'Disabled'), findsNWidgets(2)); - expect(find.widgetWithText(TextField, 'Filled'), findsNWidgets(2)); - expect(find.widgetWithText(TextField, 'Outlined'), findsNWidgets(2)); + // Dialogs + expect(find.widgetWithText(TextButton, 'Show dialog'), findsOneWidget); + expect(find.widgetWithText(TextButton, 'Show full-screen dialog'), + findsOneWidget); - // Alert Dialog - Finder dialogExample = find.widgetWithText(TextButton, 'Show dialog'); - expect(dialogExample, findsOneWidget); + // Dividers + expect(find.byKey(const Key('divider')), findsOneWidget); - // Switches - Finder switchExample = find.byType(Switch); - expect(switchExample, findsNWidgets(4)); + // Bottom app bar + expect(find.byType(BottomAppBar), findsOneWidget); + + // Navigation bar + // Third one is off screen in the scaffold + expect(find.byType(NavigationBar), findsNWidgets(3)); + + // Navigation drawer + expect(find.byType(Drawer), findsOneWidget); + expect(find.widgetWithText(TextButton, 'Show modal navigation drawer'), + findsOneWidget); + + // Navigation rail + // Second one is off screen in the scaffold + expect(find.byType(NavigationRail), findsNWidgets(2)); + + // Tabs + expect(find.byType(TabBar), findsOneWidget); + + // Top app bars + expect(find.byType(AppBar), findsNWidgets(6)); // Checkboxes Finder checkboxExample = find.byType(CheckboxListTile); expect(checkboxExample, findsNWidgets(4)); + // Chips + expect(find.byType(ActionChip), + findsNWidgets(4)); // includes Assist and Suggestion chip. + expect(find.byType(FilterChip), findsNWidgets(2)); + expect(find.byType(InputChip), findsNWidgets(2)); + + // Menus + expect(find.byType(MenuAnchor), findsNWidgets(5)); + expect(find.byType(DropdownMenu), findsOneWidget); + expect(find.byType(DropdownMenu), findsOneWidget); + // Radios - // TODO(guidezpl): Figure out why this isn't working - // Finder radioExample = find.byType(RadioListTile); - // expect(radioExample, findsNWidgets(4)); + Finder radioExample = find.byType(RadioListTile); + expect(radioExample, findsNWidgets(3)); - // ProgressIndicator - Finder circularProgressIndicator = find.byType(CircularProgressIndicator); - expect(circularProgressIndicator, findsOneWidget); - Finder linearProgressIndicator = find.byType(LinearProgressIndicator); - expect(linearProgressIndicator, findsOneWidget); + // Sliders + expect(find.byType(Slider), findsNWidgets(2)); + + // Switches + expect(find.byType(Switch), findsNWidgets(4)); + + // TextFields + expect(find.widgetWithText(TextField, 'Disabled'), findsNWidgets(2)); + expect(find.widgetWithText(TextField, 'Filled'), findsNWidgets(2)); + expect(find.widgetWithText(TextField, 'Outlined'), findsNWidgets(2)); }); testWidgets( @@ -107,7 +157,7 @@ void main() { // When screen width is greater than or equal to 1000, NavigationRail will show. // At the same time, the NavigationBar will NOT show. - expect(find.byType(NavigationRail), findsOneWidget); + expect(find.byType(NavigationRail), findsNWidgets(2)); expect(find.byType(Tooltip, skipOffstage: false), findsWidgets); expect(find.widgetWithText(NavigationRail, 'Components'), findsOneWidget); expect(find.widgetWithText(NavigationRail, 'Color'), findsOneWidget); @@ -205,7 +255,8 @@ void main() { NavigationDestination, Icons.format_paint_outlined)); await tester.tap(secondScreenIcon); await tester.pumpAndSettle(const Duration(microseconds: 500)); - BuildContext lightThemeText = tester.element(find.text('Light Theme')); + BuildContext lightThemeText = + tester.element(find.text('Light ColorScheme')); expect(Theme.of(lightThemeText).useMaterial3, false); Finder thirdScreenIcon = find.descendant( of: find.byType(NavigationBar), @@ -249,7 +300,9 @@ void main() { BuildContext appBar2 = tester.element(find.byType(AppBar).first); BuildContext body2 = tester.element(find.byType(Scaffold).first); - BuildContext navigationRail2 = tester.element(find.byType(NavigationRail)); + BuildContext navigationRail2 = tester.element( + find.widgetWithIcon(NavigationRail, Icons.format_paint_outlined)); + expect(darkIcon, findsNothing); expect(lightIcon, findsOneWidget); expect(Theme.of(appBar2).brightness, Brightness.dark); @@ -266,13 +319,12 @@ void main() { Finder menuIcon = find.descendant( of: find.byType(AppBar), matching: find.widgetWithIcon(IconButton, Icons.palette_outlined)); - BuildContext appBar = tester.element(find.byType(AppBar).first); + BuildContext appBar = tester + .element(find.widgetWithIcon(AppBar, Icons.palette_outlined).first); BuildContext body = tester.element(find.byType(Scaffold).first); - BuildContext navigationRail = tester.element(find.byType(NavigationRail)); expect(Theme.of(appBar).primaryColor, m3BaseColor); expect(Theme.of(body).primaryColor, m3BaseColor); - expect(Theme.of(navigationRail).primaryColor, m3BaseColor); await tester.tap(menuIcon); await tester.pumpAndSettle(); await tester.tap(find.text('Blue').last); @@ -280,11 +332,9 @@ void main() { BuildContext appBar2 = tester.element(find.byType(AppBar).first); BuildContext body2 = tester.element(find.byType(Scaffold).first); - BuildContext navigationRail2 = tester.element(find.byType(NavigationRail)); ThemeData expectedTheme = ThemeData(colorSchemeSeed: Colors.blue); expect(Theme.of(appBar2).primaryColor, expectedTheme.primaryColor); expect(Theme.of(body2).primaryColor, expectedTheme.primaryColor); - expect(Theme.of(navigationRail2).primaryColor, expectedTheme.primaryColor); }); } diff --git a/material_3_demo/test/elevation_screen_test.dart b/material_3_demo/test/elevation_screen_test.dart index 14f3cac2c..737a355a6 100644 --- a/material_3_demo/test/elevation_screen_test.dart +++ b/material_3_demo/test/elevation_screen_test.dart @@ -43,7 +43,6 @@ void main() { addTearDown(tester.binding.window.clearPhysicalSizeTestValue); await tester.pumpWidget(const MaterialApp(home: Material3Demo())); expect(find.text('Surface Tint Color Only'), findsNothing); - expect(find.byType(NavigationRail), findsOneWidget); Finder tintIconOnRail = find.descendant( of: find.byType(NavigationRail), matching: find.byIcon(Icons.invert_colors_on_outlined)); diff --git a/material_3_demo/test/typography_screen_test.dart b/material_3_demo/test/typography_screen_test.dart index 257a37e5b..234c14b3d 100644 --- a/material_3_demo/test/typography_screen_test.dart +++ b/material_3_demo/test/typography_screen_test.dart @@ -42,7 +42,6 @@ void main() { addTearDown(tester.binding.window.clearPhysicalSizeTestValue); await tester.pumpWidget(const MaterialApp(home: Material3Demo())); expect(find.text('Display Large'), findsNothing); - expect(find.byType(NavigationRail), findsOneWidget); Finder textIconOnRail = find.descendant( of: find.byType(NavigationRail), matching: find.byIcon(Icons.text_snippet_outlined)); diff --git a/material_3_demo/windows/flutter/generated_plugin_registrant.cc b/material_3_demo/windows/flutter/generated_plugin_registrant.cc index 8b6d4680a..4f7884874 100644 --- a/material_3_demo/windows/flutter/generated_plugin_registrant.cc +++ b/material_3_demo/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/material_3_demo/windows/flutter/generated_plugins.cmake b/material_3_demo/windows/flutter/generated_plugins.cmake index b93c4c30c..88b22e5c7 100644 --- a/material_3_demo/windows/flutter/generated_plugins.cmake +++ b/material_3_demo/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST