diff --git a/material_3_demo/lib/home.dart b/material_3_demo/lib/home.dart deleted file mode 100644 index fec4b4f2e..000000000 --- a/material_3_demo/lib/home.dart +++ /dev/null @@ -1,863 +0,0 @@ -// Copyright 2021 The Flutter team. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; - -import 'color_palettes_screen.dart'; -import 'component_screen.dart'; -import 'constants.dart'; -import 'elevation_screen.dart'; -import 'typography_screen.dart'; - -class Home extends StatefulWidget { - const Home({ - super.key, - required this.useLightMode, - required this.useMaterial3, - required this.colorSelected, - required this.handleBrightnessChange, - required this.handleMaterialVersionChange, - required this.handleColorSelect, - required this.handleImageSelect, - required this.colorSelectionMethod, - required this.imageSelected, - }); - - final bool useLightMode; - final bool useMaterial3; - final ColorSeed colorSelected; - final ColorImageProvider imageSelected; - final ColorSelectionMethod colorSelectionMethod; - - final void Function(bool useLightMode) handleBrightnessChange; - final void Function() handleMaterialVersionChange; - final void Function(int value) handleColorSelect; - final void Function(int value) handleImageSelect; - - @override - State createState() => _HomeState(); -} - -class _HomeState extends State with SingleTickerProviderStateMixin { - final GlobalKey scaffoldKey = GlobalKey(); - late final AnimationController controller; - late final CurvedAnimation railAnimation; - bool controllerInitialized = false; - bool showMediumSizeLayout = false; - bool showLargeSizeLayout = false; - - int screenIndex = ScreenSelected.component.value; - - @override - initState() { - super.initState(); - controller = AnimationController( - duration: Duration(milliseconds: transitionLength.toInt() * 2), - value: 0, - vsync: this, - ); - railAnimation = CurvedAnimation( - parent: controller, - curve: const Interval(0.5, 1.0), - ); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - final double width = MediaQuery.of(context).size.width; - final AnimationStatus status = controller.status; - if (width > mediumWidthBreakpoint) { - if (width > largeWidthBreakpoint) { - showMediumSizeLayout = false; - showLargeSizeLayout = true; - } else { - showMediumSizeLayout = true; - showLargeSizeLayout = false; - } - if (status != AnimationStatus.forward && - status != AnimationStatus.completed) { - controller.forward(); - } - } else { - showMediumSizeLayout = false; - showLargeSizeLayout = false; - if (status != AnimationStatus.reverse && - status != AnimationStatus.dismissed) { - controller.reverse(); - } - } - if (!controllerInitialized) { - controllerInitialized = true; - controller.value = width > mediumWidthBreakpoint ? 1 : 0; - } - } - - void handleScreenChanged(int screenSelected) { - setState(() { - screenIndex = screenSelected; - }); - } - - Widget createScreenFor( - ScreenSelected screenSelected, - bool showNavBarExample, - ) => switch (screenSelected) { - ScreenSelected.component => Expanded( - child: OneTwoTransition( - animation: railAnimation, - one: FirstComponentList( - showNavBottomBar: showNavBarExample, - scaffoldKey: scaffoldKey, - showSecondList: showMediumSizeLayout || showLargeSizeLayout, - ), - two: SecondComponentList(scaffoldKey: scaffoldKey), - ), - ), - ScreenSelected.color => const ColorPalettesScreen(), - ScreenSelected.typography => const TypographyScreen(), - ScreenSelected.elevation => const ElevationScreen(), - }; - - PreferredSizeWidget createAppBar() { - return AppBar( - title: - widget.useMaterial3 - ? const Text('Material 3') - : const Text('Material 2'), - actions: - !showMediumSizeLayout && !showLargeSizeLayout - ? [ - _BrightnessButton( - handleBrightnessChange: widget.handleBrightnessChange, - ), - _Material3Button( - handleMaterialVersionChange: - widget.handleMaterialVersionChange, - ), - _ColorSeedButton( - handleColorSelect: widget.handleColorSelect, - colorSelected: widget.colorSelected, - colorSelectionMethod: widget.colorSelectionMethod, - ), - _ColorImageButton( - handleImageSelect: widget.handleImageSelect, - imageSelected: widget.imageSelected, - colorSelectionMethod: widget.colorSelectionMethod, - ), - ] - : [Container()], - ); - } - - Widget _trailingActions() => Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Flexible( - child: _BrightnessButton( - handleBrightnessChange: widget.handleBrightnessChange, - showTooltipBelow: false, - ), - ), - Flexible( - child: _Material3Button( - handleMaterialVersionChange: widget.handleMaterialVersionChange, - showTooltipBelow: false, - ), - ), - Flexible( - child: _ColorSeedButton( - handleColorSelect: widget.handleColorSelect, - colorSelected: widget.colorSelected, - colorSelectionMethod: widget.colorSelectionMethod, - ), - ), - Flexible( - child: _ColorImageButton( - handleImageSelect: widget.handleImageSelect, - imageSelected: widget.imageSelected, - colorSelectionMethod: widget.colorSelectionMethod, - ), - ), - ], - ); - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: controller, - builder: (context, child) { - return NavigationTransition( - scaffoldKey: scaffoldKey, - animationController: controller, - railAnimation: railAnimation, - appBar: createAppBar(), - body: createScreenFor( - ScreenSelected.values[screenIndex], - controller.value == 1, - ), - navigationRail: NavigationRail( - extended: showLargeSizeLayout, - destinations: navRailDestinations, - selectedIndex: screenIndex, - onDestinationSelected: (index) { - setState(() { - screenIndex = index; - handleScreenChanged(screenIndex); - }); - }, - trailing: Expanded( - child: Padding( - padding: const EdgeInsets.only(bottom: 20), - child: - showLargeSizeLayout - ? _ExpandedTrailingActions( - useLightMode: widget.useLightMode, - handleBrightnessChange: widget.handleBrightnessChange, - useMaterial3: widget.useMaterial3, - handleMaterialVersionChange: - widget.handleMaterialVersionChange, - handleImageSelect: widget.handleImageSelect, - handleColorSelect: widget.handleColorSelect, - colorSelectionMethod: widget.colorSelectionMethod, - imageSelected: widget.imageSelected, - colorSelected: widget.colorSelected, - ) - : _trailingActions(), - ), - ), - ), - navigationBar: NavigationBars( - onSelectItem: (index) { - setState(() { - screenIndex = index; - handleScreenChanged(screenIndex); - }); - }, - selectedIndex: screenIndex, - isExampleBar: false, - ), - ); - }, - ); - } -} - -class _BrightnessButton extends StatelessWidget { - const _BrightnessButton({ - required this.handleBrightnessChange, - this.showTooltipBelow = true, - }); - - final Function handleBrightnessChange; - final bool showTooltipBelow; - - @override - Widget build(BuildContext context) { - final isBright = Theme.of(context).brightness == Brightness.light; - return Tooltip( - preferBelow: showTooltipBelow, - message: 'Toggle brightness', - child: IconButton( - icon: - isBright - ? const Icon(Icons.dark_mode_outlined) - : const Icon(Icons.light_mode_outlined), - onPressed: () => handleBrightnessChange(!isBright), - ), - ); - } -} - -class _Material3Button extends StatelessWidget { - const _Material3Button({ - required this.handleMaterialVersionChange, - this.showTooltipBelow = true, - }); - - final void Function() handleMaterialVersionChange; - final bool showTooltipBelow; - - @override - Widget build(BuildContext context) { - final useMaterial3 = Theme.of(context).useMaterial3; - return Tooltip( - preferBelow: showTooltipBelow, - message: 'Switch to Material ${useMaterial3 ? 2 : 3}', - child: IconButton( - icon: - useMaterial3 - ? const Icon(Icons.filter_2) - : const Icon(Icons.filter_3), - onPressed: handleMaterialVersionChange, - ), - ); - } -} - -class _ColorSeedButton extends StatelessWidget { - const _ColorSeedButton({ - required this.handleColorSelect, - required this.colorSelected, - required this.colorSelectionMethod, - }); - - final void Function(int) handleColorSelect; - final ColorSeed colorSelected; - final ColorSelectionMethod colorSelectionMethod; - - @override - Widget build(BuildContext context) { - return PopupMenuButton( - icon: const Icon(Icons.palette_outlined), - tooltip: 'Select a seed color', - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - itemBuilder: (context) { - return List.generate(ColorSeed.values.length, (index) { - ColorSeed currentColor = ColorSeed.values[index]; - - return PopupMenuItem( - value: index, - enabled: - currentColor != colorSelected || - colorSelectionMethod != ColorSelectionMethod.colorSeed, - child: Wrap( - children: [ - Padding( - padding: const EdgeInsets.only(left: 10), - child: Icon( - currentColor == colorSelected && - colorSelectionMethod != ColorSelectionMethod.image - ? Icons.color_lens - : Icons.color_lens_outlined, - color: currentColor.color, - ), - ), - Padding( - padding: const EdgeInsets.only(left: 20), - child: Text(currentColor.label), - ), - ], - ), - ); - }); - }, - onSelected: handleColorSelect, - ); - } -} - -class _ColorImageButton extends StatelessWidget { - const _ColorImageButton({ - required this.handleImageSelect, - required this.imageSelected, - required this.colorSelectionMethod, - }); - - final void Function(int) handleImageSelect; - final ColorImageProvider imageSelected; - final ColorSelectionMethod colorSelectionMethod; - - @override - Widget build(BuildContext context) { - return PopupMenuButton( - icon: const Icon(Icons.image_outlined), - tooltip: 'Select a color extraction image', - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - itemBuilder: (context) { - return List.generate(ColorImageProvider.values.length, (index) { - final currentImageProvider = ColorImageProvider.values[index]; - - return PopupMenuItem( - value: index, - enabled: - currentImageProvider != imageSelected || - colorSelectionMethod != ColorSelectionMethod.image, - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 48), - child: Padding( - padding: const EdgeInsets.all(4.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.0), - child: Image( - image: NetworkImage(currentImageProvider.url), - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 20), - child: Text(currentImageProvider.label), - ), - ], - ), - ); - }); - }, - onSelected: handleImageSelect, - ); - } -} - -class _ExpandedTrailingActions extends StatelessWidget { - const _ExpandedTrailingActions({ - required this.useLightMode, - required this.handleBrightnessChange, - required this.useMaterial3, - required this.handleMaterialVersionChange, - required this.handleColorSelect, - required this.handleImageSelect, - required this.imageSelected, - required this.colorSelected, - required this.colorSelectionMethod, - }); - - final void Function(bool) handleBrightnessChange; - final void Function() handleMaterialVersionChange; - final void Function(int) handleImageSelect; - final void Function(int) handleColorSelect; - - final bool useLightMode; - final bool useMaterial3; - - final ColorImageProvider imageSelected; - final ColorSeed colorSelected; - final ColorSelectionMethod colorSelectionMethod; - - @override - Widget build(BuildContext context) { - final screenHeight = MediaQuery.of(context).size.height; - final trailingActionsBody = Container( - constraints: const BoxConstraints.tightFor(width: 250), - padding: const EdgeInsets.symmetric(horizontal: 30), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - const Text('Brightness'), - Expanded(child: Container()), - Switch( - value: useLightMode, - onChanged: (value) { - handleBrightnessChange(value); - }, - ), - ], - ), - Row( - children: [ - useMaterial3 - ? const Text('Material 3') - : const Text('Material 2'), - Expanded(child: Container()), - Switch( - value: useMaterial3, - onChanged: (_) { - handleMaterialVersionChange(); - }, - ), - ], - ), - const Divider(), - _ExpandedColorSeedAction( - handleColorSelect: handleColorSelect, - colorSelected: colorSelected, - colorSelectionMethod: colorSelectionMethod, - ), - const Divider(), - _ExpandedImageColorAction( - handleImageSelect: handleImageSelect, - imageSelected: imageSelected, - colorSelectionMethod: colorSelectionMethod, - ), - ], - ), - ); - return screenHeight > 740 - ? trailingActionsBody - : SingleChildScrollView(child: trailingActionsBody); - } -} - -class _ExpandedColorSeedAction extends StatelessWidget { - const _ExpandedColorSeedAction({ - required this.handleColorSelect, - required this.colorSelected, - required this.colorSelectionMethod, - }); - - final void Function(int) handleColorSelect; - final ColorSeed colorSelected; - final ColorSelectionMethod colorSelectionMethod; - - @override - Widget build(BuildContext context) { - return ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 200.0), - child: GridView.count( - crossAxisCount: 3, - children: List.generate( - ColorSeed.values.length, - (i) => IconButton( - icon: const Icon(Icons.radio_button_unchecked), - color: ColorSeed.values[i].color, - isSelected: - colorSelected.color == ColorSeed.values[i].color && - colorSelectionMethod == ColorSelectionMethod.colorSeed, - selectedIcon: const Icon(Icons.circle), - onPressed: () { - handleColorSelect(i); - }, - tooltip: ColorSeed.values[i].label, - ), - ), - ), - ); - } -} - -class _ExpandedImageColorAction extends StatelessWidget { - const _ExpandedImageColorAction({ - required this.handleImageSelect, - required this.imageSelected, - required this.colorSelectionMethod, - }); - - final void Function(int) handleImageSelect; - final ColorImageProvider imageSelected; - final ColorSelectionMethod colorSelectionMethod; - - @override - Widget build(BuildContext context) { - return ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 150.0), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: GridView.count( - crossAxisCount: 3, - children: List.generate( - ColorImageProvider.values.length, - (i) => Tooltip( - message: ColorImageProvider.values[i].name, - child: InkWell( - borderRadius: BorderRadius.circular(4.0), - onTap: () => handleImageSelect(i), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Material( - borderRadius: BorderRadius.circular(4.0), - elevation: - imageSelected == ColorImageProvider.values[i] && - colorSelectionMethod == - ColorSelectionMethod.image - ? 3 - : 0, - child: Padding( - padding: const EdgeInsets.all(4.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(4.0), - child: Image( - image: NetworkImage(ColorImageProvider.values[i].url), - ), - ), - ), - ), - ), - ), - ), - ), - ), - ), - ); - } -} - -class NavigationTransition extends StatefulWidget { - const NavigationTransition({ - super.key, - required this.scaffoldKey, - required this.animationController, - required this.railAnimation, - required this.navigationRail, - required this.navigationBar, - required this.appBar, - required this.body, - }); - - final GlobalKey scaffoldKey; - final AnimationController animationController; - final CurvedAnimation railAnimation; - final Widget navigationRail; - final Widget navigationBar; - final PreferredSizeWidget appBar; - final Widget body; - - @override - State createState() => _NavigationTransitionState(); -} - -class _NavigationTransitionState extends State { - late final AnimationController controller; - late final CurvedAnimation railAnimation; - late final ReverseAnimation barAnimation; - bool controllerInitialized = false; - bool showDivider = false; - - @override - void initState() { - super.initState(); - - controller = widget.animationController; - railAnimation = widget.railAnimation; - - barAnimation = ReverseAnimation( - CurvedAnimation(parent: controller, curve: const Interval(0.0, 0.5)), - ); - } - - @override - Widget build(BuildContext context) { - final ColorScheme colorScheme = Theme.of(context).colorScheme; - - return Scaffold( - key: widget.scaffoldKey, - appBar: widget.appBar, - body: Row( - children: [ - RailTransition( - animation: railAnimation, - backgroundColor: colorScheme.surface, - child: widget.navigationRail, - ), - widget.body, - ], - ), - bottomNavigationBar: BarTransition( - animation: barAnimation, - backgroundColor: colorScheme.surface, - child: widget.navigationBar, - ), - endDrawer: const NavigationDrawerSection(), - ); - } -} - -final List navRailDestinations = - appBarDestinations - .map( - (destination) => NavigationRailDestination( - icon: Tooltip(message: destination.label, child: destination.icon), - selectedIcon: Tooltip( - message: destination.label, - child: destination.selectedIcon, - ), - label: Text(destination.label), - ), - ) - .toList(); - -class SizeAnimation extends CurvedAnimation { - SizeAnimation(Animation parent) - : super( - parent: parent, - curve: const Interval(0.2, 0.8, curve: Curves.easeInOutCubicEmphasized), - reverseCurve: Interval( - 0, - 0.2, - curve: Curves.easeInOutCubicEmphasized.flipped, - ), - ); -} - -class OffsetAnimation extends CurvedAnimation { - OffsetAnimation(Animation parent) - : super( - parent: parent, - curve: const Interval(0.4, 1.0, curve: Curves.easeInOutCubicEmphasized), - reverseCurve: Interval( - 0, - 0.2, - curve: Curves.easeInOutCubicEmphasized.flipped, - ), - ); -} - -class RailTransition extends StatefulWidget { - const RailTransition({ - super.key, - required this.animation, - required this.backgroundColor, - required this.child, - }); - - final Animation animation; - final Widget child; - final Color backgroundColor; - - @override - State createState() => _RailTransition(); -} - -class _RailTransition extends State { - late Animation offsetAnimation; - late Animation widthAnimation; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - // The animations are only rebuilt by this method when the text - // direction changes because this widget only depends on Directionality. - final bool ltr = Directionality.of(context) == TextDirection.ltr; - - widthAnimation = Tween( - begin: 0, - end: 1, - ).animate(SizeAnimation(widget.animation)); - - offsetAnimation = Tween( - begin: ltr ? const Offset(-1, 0) : const Offset(1, 0), - end: Offset.zero, - ).animate(OffsetAnimation(widget.animation)); - } - - @override - Widget build(BuildContext context) { - return ClipRect( - child: DecoratedBox( - decoration: BoxDecoration(color: widget.backgroundColor), - child: Align( - alignment: Alignment.topLeft, - widthFactor: widthAnimation.value, - child: FractionalTranslation( - translation: offsetAnimation.value, - child: widget.child, - ), - ), - ), - ); - } -} - -class BarTransition extends StatefulWidget { - const BarTransition({ - super.key, - required this.animation, - required this.backgroundColor, - required this.child, - }); - - final Animation animation; - final Color backgroundColor; - final Widget child; - - @override - State createState() => _BarTransition(); -} - -class _BarTransition extends State { - late final Animation offsetAnimation; - late final Animation heightAnimation; - - @override - void initState() { - super.initState(); - - offsetAnimation = Tween( - begin: const Offset(0, 1), - end: Offset.zero, - ).animate(OffsetAnimation(widget.animation)); - - heightAnimation = Tween( - begin: 0, - end: 1, - ).animate(SizeAnimation(widget.animation)); - } - - @override - Widget build(BuildContext context) { - return ClipRect( - child: DecoratedBox( - decoration: BoxDecoration(color: widget.backgroundColor), - child: Align( - alignment: Alignment.topLeft, - heightFactor: heightAnimation.value, - child: FractionalTranslation( - translation: offsetAnimation.value, - child: widget.child, - ), - ), - ), - ); - } -} - -class OneTwoTransition extends StatefulWidget { - const OneTwoTransition({ - super.key, - required this.animation, - required this.one, - required this.two, - }); - - final Animation animation; - final Widget one; - final Widget two; - - @override - State createState() => _OneTwoTransitionState(); -} - -class _OneTwoTransitionState extends State { - late final Animation offsetAnimation; - late final Animation widthAnimation; - - @override - void initState() { - super.initState(); - - offsetAnimation = Tween( - begin: const Offset(1, 0), - end: Offset.zero, - ).animate(OffsetAnimation(widget.animation)); - - widthAnimation = Tween( - begin: 0, - end: mediumWidthBreakpoint, - ).animate(SizeAnimation(widget.animation)); - } - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Flexible(flex: mediumWidthBreakpoint.toInt(), child: widget.one), - if (widthAnimation.value.toInt() > 0) ...[ - Flexible( - flex: widthAnimation.value.toInt(), - child: FractionalTranslation( - translation: offsetAnimation.value, - child: widget.two, - ), - ), - ], - ], - ); - } -} diff --git a/material_3_demo/lib/main.dart b/material_3_demo/lib/main.dart index a2b1b9319..653844e5f 100644 --- a/material_3_demo/lib/main.dart +++ b/material_3_demo/lib/main.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; -import 'constants.dart'; -import 'home.dart'; +import 'src/constants.dart'; +import 'src/home.dart'; void main() async { runApp(const App()); @@ -19,14 +19,14 @@ class App extends StatefulWidget { } class _AppState extends State { - bool useMaterial3 = true; - ThemeMode themeMode = ThemeMode.system; - ColorSeed colorSelected = ColorSeed.baseColor; - ColorImageProvider imageSelected = ColorImageProvider.leaves; - ColorScheme? imageColorScheme = const ColorScheme.light(); - ColorSelectionMethod colorSelectionMethod = ColorSelectionMethod.colorSeed; + bool _useMaterial3 = true; + ThemeMode _themeMode = ThemeMode.system; + ColorSeed _colorSelected = ColorSeed.baseColor; + ColorImageProvider _imageSelected = ColorImageProvider.leaves; + ColorScheme? _imageColorScheme = const ColorScheme.light(); + ColorSelectionMethod _colorSelectionMethod = ColorSelectionMethod.colorSeed; - bool get useLightMode => switch (themeMode) { + bool get _useLightMode => switch (_themeMode) { ThemeMode.system => View.of(context).platformDispatcher.platformBrightness == Brightness.light, @@ -34,34 +34,34 @@ class _AppState extends State { ThemeMode.dark => false, }; - void handleBrightnessChange(bool useLightMode) { + void _handleBrightnessChange(bool useLightMode) { setState(() { - themeMode = useLightMode ? ThemeMode.light : ThemeMode.dark; + _themeMode = useLightMode ? ThemeMode.light : ThemeMode.dark; }); } - void handleMaterialVersionChange() { + void _handleMaterialVersionChange() { setState(() { - useMaterial3 = !useMaterial3; + _useMaterial3 = !_useMaterial3; }); } - void handleColorSelect(int value) { + void _handleColorSelect(int value) { setState(() { - colorSelectionMethod = ColorSelectionMethod.colorSeed; - colorSelected = ColorSeed.values[value]; + _colorSelectionMethod = ColorSelectionMethod.colorSeed; + _colorSelected = ColorSeed.values[value]; }); } - void handleImageSelect(int value) { + void _handleImageSelect(int value) { final String url = ColorImageProvider.values[value].url; ColorScheme.fromImageProvider(provider: NetworkImage(url)).then(( newScheme, ) { setState(() { - colorSelectionMethod = ColorSelectionMethod.image; - imageSelected = ColorImageProvider.values[value]; - imageColorScheme = newScheme; + _colorSelectionMethod = ColorSelectionMethod.image; + _imageSelected = ColorImageProvider.values[value]; + _imageColorScheme = newScheme; }); }); } @@ -71,37 +71,37 @@ class _AppState extends State { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Material 3', - themeMode: themeMode, + themeMode: _themeMode, theme: ThemeData( colorSchemeSeed: - colorSelectionMethod == ColorSelectionMethod.colorSeed - ? colorSelected.color + _colorSelectionMethod == ColorSelectionMethod.colorSeed + ? _colorSelected.color : null, colorScheme: - colorSelectionMethod == ColorSelectionMethod.image - ? imageColorScheme + _colorSelectionMethod == ColorSelectionMethod.image + ? _imageColorScheme : null, - useMaterial3: useMaterial3, + useMaterial3: _useMaterial3, brightness: Brightness.light, ), darkTheme: ThemeData( colorSchemeSeed: - colorSelectionMethod == ColorSelectionMethod.colorSeed - ? colorSelected.color - : imageColorScheme!.primary, - useMaterial3: useMaterial3, + _colorSelectionMethod == ColorSelectionMethod.colorSeed + ? _colorSelected.color + : _imageColorScheme!.primary, + useMaterial3: _useMaterial3, brightness: Brightness.dark, ), home: Home( - useLightMode: useLightMode, - useMaterial3: useMaterial3, - colorSelected: colorSelected, - imageSelected: imageSelected, - handleBrightnessChange: handleBrightnessChange, - handleMaterialVersionChange: handleMaterialVersionChange, - handleColorSelect: handleColorSelect, - handleImageSelect: handleImageSelect, - colorSelectionMethod: colorSelectionMethod, + useLightMode: _useLightMode, + useMaterial3: _useMaterial3, + colorSelected: _colorSelected, + imageSelected: _imageSelected, + handleBrightnessChange: _handleBrightnessChange, + handleMaterialVersionChange: _handleMaterialVersionChange, + handleColorSelect: _handleColorSelect, + handleImageSelect: _handleImageSelect, + colorSelectionMethod: _colorSelectionMethod, ), ); } diff --git a/material_3_demo/lib/src/animations.dart b/material_3_demo/lib/src/animations.dart new file mode 100644 index 000000000..301dba529 --- /dev/null +++ b/material_3_demo/lib/src/animations.dart @@ -0,0 +1,31 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +class SizeAnimation extends CurvedAnimation { + SizeAnimation(Animation parent) + : super( + parent: parent, + curve: const Interval(0.2, 0.8, curve: Curves.easeInOutCubicEmphasized), + reverseCurve: Interval( + 0, + 0.2, + curve: Curves.easeInOutCubicEmphasized.flipped, + ), + ); +} + +class OffsetAnimation extends CurvedAnimation { + OffsetAnimation(Animation parent) + : super( + parent: parent, + curve: const Interval(0.4, 1.0, curve: Curves.easeInOutCubicEmphasized), + reverseCurve: Interval( + 0, + 0.2, + curve: Curves.easeInOutCubicEmphasized.flipped, + ), + ); +} diff --git a/material_3_demo/lib/src/bar_transition.dart b/material_3_demo/lib/src/bar_transition.dart new file mode 100644 index 000000000..5a0412236 --- /dev/null +++ b/material_3_demo/lib/src/bar_transition.dart @@ -0,0 +1,59 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'package:flutter/widgets.dart'; + +import 'animations.dart'; + +class BarTransition extends StatefulWidget { + const BarTransition({ + super.key, + required this.animation, + required this.backgroundColor, + required this.child, + }); + + final Animation animation; + final Color backgroundColor; + final Widget child; + + @override + State createState() => _BarTransition(); +} + +class _BarTransition extends State { + late final Animation offsetAnimation; + late final Animation heightAnimation; + + @override + void initState() { + super.initState(); + + offsetAnimation = Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate(OffsetAnimation(widget.animation)); + + heightAnimation = Tween( + begin: 0, + end: 1, + ).animate(SizeAnimation(widget.animation)); + } + + @override + Widget build(BuildContext context) { + return ClipRect( + child: DecoratedBox( + decoration: BoxDecoration(color: widget.backgroundColor), + child: Align( + alignment: Alignment.topLeft, + heightFactor: heightAnimation.value, + child: FractionalTranslation( + translation: offsetAnimation.value, + child: widget.child, + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/buttons.dart b/material_3_demo/lib/src/buttons.dart new file mode 100644 index 000000000..a5b1df758 --- /dev/null +++ b/material_3_demo/lib/src/buttons.dart @@ -0,0 +1,173 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'constants.dart'; + +class BrightnessButton extends StatelessWidget { + const BrightnessButton({ + super.key, + required this.handleBrightnessChange, + this.showTooltipBelow = true, + }); + + final void Function(bool useLightMode) handleBrightnessChange; + final bool showTooltipBelow; + + @override + Widget build(BuildContext context) { + final isBright = Theme.of(context).brightness == Brightness.light; + return Tooltip( + preferBelow: showTooltipBelow, + message: 'Toggle brightness', + child: IconButton( + icon: + isBright + ? const Icon(Icons.dark_mode_outlined) + : const Icon(Icons.light_mode_outlined), + onPressed: () => handleBrightnessChange(!isBright), + ), + ); + } +} + +class Material3Button extends StatelessWidget { + const Material3Button({ + super.key, + required this.handleMaterialVersionChange, + this.showTooltipBelow = true, + }); + + final void Function() handleMaterialVersionChange; + final bool showTooltipBelow; + + @override + Widget build(BuildContext context) { + final useMaterial3 = Theme.of(context).useMaterial3; + return Tooltip( + preferBelow: showTooltipBelow, + message: 'Switch to Material ${useMaterial3 ? 2 : 3}', + child: IconButton( + icon: + useMaterial3 + ? const Icon(Icons.filter_2) + : const Icon(Icons.filter_3), + onPressed: handleMaterialVersionChange, + ), + ); + } +} + +class ColorSeedButton extends StatelessWidget { + const ColorSeedButton({ + super.key, + required this.handleColorSelect, + required this.colorSelected, + required this.colorSelectionMethod, + }); + + final void Function(int) handleColorSelect; + final ColorSeed colorSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + icon: const Icon(Icons.palette_outlined), + tooltip: 'Select a seed color', + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + itemBuilder: (context) { + return List.generate(ColorSeed.values.length, (index) { + ColorSeed currentColor = ColorSeed.values[index]; + + return PopupMenuItem( + value: index, + enabled: + currentColor != colorSelected || + colorSelectionMethod != ColorSelectionMethod.colorSeed, + child: Wrap( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Icon( + currentColor == colorSelected && + colorSelectionMethod != ColorSelectionMethod.image + ? Icons.color_lens + : Icons.color_lens_outlined, + color: currentColor.color, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: Text(currentColor.label), + ), + ], + ), + ); + }); + }, + onSelected: handleColorSelect, + ); + } +} + +class ColorImageButton extends StatelessWidget { + const ColorImageButton({ + super.key, + required this.handleImageSelect, + required this.imageSelected, + required this.colorSelectionMethod, + }); + + final void Function(int) handleImageSelect; + final ColorImageProvider imageSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + icon: const Icon(Icons.image_outlined), + tooltip: 'Select a color extraction image', + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + itemBuilder: (context) { + return List.generate(ColorImageProvider.values.length, (index) { + final currentImageProvider = ColorImageProvider.values[index]; + + return PopupMenuItem( + value: index, + enabled: + currentImageProvider != imageSelected || + colorSelectionMethod != ColorSelectionMethod.image, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 48), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image( + image: NetworkImage(currentImageProvider.url), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: Text(currentImageProvider.label), + ), + ], + ), + ); + }); + }, + onSelected: handleImageSelect, + ); + } +} diff --git a/material_3_demo/lib/color_box.dart b/material_3_demo/lib/src/color_box.dart similarity index 100% rename from material_3_demo/lib/color_box.dart rename to material_3_demo/lib/src/color_box.dart diff --git a/material_3_demo/lib/color_palettes_screen.dart b/material_3_demo/lib/src/color_palettes_screen.dart similarity index 99% rename from material_3_demo/lib/color_palettes_screen.dart rename to material_3_demo/lib/src/color_palettes_screen.dart index 05373e640..0f915753e 100644 --- a/material_3_demo/lib/color_palettes_screen.dart +++ b/material_3_demo/lib/src/color_palettes_screen.dart @@ -4,9 +4,10 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:material_3_demo/scheme.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'scheme.dart'; + const Widget divider = SizedBox(height: 10); // If screen content width is greater or equal to this value, the light and dark diff --git a/material_3_demo/lib/component_screen.dart b/material_3_demo/lib/src/component_screen.dart similarity index 100% rename from material_3_demo/lib/component_screen.dart rename to material_3_demo/lib/src/component_screen.dart diff --git a/material_3_demo/lib/constants.dart b/material_3_demo/lib/src/constants.dart similarity index 100% rename from material_3_demo/lib/constants.dart rename to material_3_demo/lib/src/constants.dart diff --git a/material_3_demo/lib/elevation_screen.dart b/material_3_demo/lib/src/elevation_screen.dart similarity index 100% rename from material_3_demo/lib/elevation_screen.dart rename to material_3_demo/lib/src/elevation_screen.dart diff --git a/material_3_demo/lib/src/expanded_color_seed_action.dart b/material_3_demo/lib/src/expanded_color_seed_action.dart new file mode 100644 index 000000000..241b86e5c --- /dev/null +++ b/material_3_demo/lib/src/expanded_color_seed_action.dart @@ -0,0 +1,45 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'constants.dart'; + +class ExpandedColorSeedAction extends StatelessWidget { + const ExpandedColorSeedAction({ + super.key, + required this.handleColorSelect, + required this.colorSelected, + required this.colorSelectionMethod, + }); + + final void Function(int) handleColorSelect; + final ColorSeed colorSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200.0), + child: GridView.count( + crossAxisCount: 3, + children: List.generate( + ColorSeed.values.length, + (i) => IconButton( + icon: const Icon(Icons.radio_button_unchecked), + color: ColorSeed.values[i].color, + isSelected: + colorSelected.color == ColorSeed.values[i].color && + colorSelectionMethod == ColorSelectionMethod.colorSeed, + selectedIcon: const Icon(Icons.circle), + onPressed: () { + handleColorSelect(i); + }, + tooltip: ColorSeed.values[i].label, + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/expanded_image_color_action.dart b/material_3_demo/lib/src/expanded_image_color_action.dart new file mode 100644 index 000000000..7c99add66 --- /dev/null +++ b/material_3_demo/lib/src/expanded_image_color_action.dart @@ -0,0 +1,78 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'constants.dart'; + +class ExpandedImageColorAction extends StatelessWidget { + const ExpandedImageColorAction({ + super.key, + required this.handleImageSelect, + required this.imageSelected, + required this.colorSelectionMethod, + }); + + final void Function(int) handleImageSelect; + final ColorImageProvider imageSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 150.0), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: GridView.count( + crossAxisCount: 3, + children: List.generate( + ColorImageProvider.values.length, + (i) => _ImageButton( + index: i, + select: + imageSelected == ColorImageProvider.values[i] && + colorSelectionMethod == ColorSelectionMethod.image + ? null + : () => handleImageSelect(i), + ), + ), + ), + ), + ); + } +} + +class _ImageButton extends StatelessWidget { + const _ImageButton({required this.index, required this.select}); + + final void Function()? select; + final int index; + + @override + Widget build(BuildContext context) { + return Tooltip( + message: ColorImageProvider.values[index].name, + child: InkWell( + borderRadius: BorderRadius.circular(4.0), + onTap: select, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Material( + borderRadius: BorderRadius.circular(4.0), + elevation: select != null ? 3 : 0, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(4.0), + child: Image( + image: NetworkImage(ColorImageProvider.values[index].url), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/expanded_trailing_actions.dart b/material_3_demo/lib/src/expanded_trailing_actions.dart new file mode 100644 index 000000000..c4bf0460f --- /dev/null +++ b/material_3_demo/lib/src/expanded_trailing_actions.dart @@ -0,0 +1,92 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'constants.dart'; +import 'expanded_color_seed_action.dart'; +import 'expanded_image_color_action.dart'; + +class ExpandedTrailingActions extends StatelessWidget { + const ExpandedTrailingActions({ + super.key, + required this.useLightMode, + required this.handleBrightnessChange, + required this.useMaterial3, + required this.handleMaterialVersionChange, + required this.handleColorSelect, + required this.handleImageSelect, + required this.imageSelected, + required this.colorSelected, + required this.colorSelectionMethod, + }); + + final void Function(bool) handleBrightnessChange; + final void Function() handleMaterialVersionChange; + final void Function(int) handleImageSelect; + final void Function(int) handleColorSelect; + + final bool useLightMode; + final bool useMaterial3; + + final ColorImageProvider imageSelected; + final ColorSeed colorSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final trailingActionsBody = Container( + constraints: const BoxConstraints.tightFor(width: 250), + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + const Text('Brightness'), + Expanded(child: Container()), + Switch( + value: useLightMode, + onChanged: (value) { + handleBrightnessChange(value); + }, + ), + ], + ), + Row( + children: [ + useMaterial3 + ? const Text('Material 3') + : const Text('Material 2'), + Expanded(child: Container()), + Switch( + value: useMaterial3, + onChanged: (_) { + handleMaterialVersionChange(); + }, + ), + ], + ), + const Divider(), + ExpandedColorSeedAction( + handleColorSelect: handleColorSelect, + colorSelected: colorSelected, + colorSelectionMethod: colorSelectionMethod, + ), + const Divider(), + ExpandedImageColorAction( + handleImageSelect: handleImageSelect, + imageSelected: imageSelected, + colorSelectionMethod: colorSelectionMethod, + ), + ], + ), + ); + return screenHeight > 740 + ? trailingActionsBody + : SingleChildScrollView(child: trailingActionsBody); + } +} diff --git a/material_3_demo/lib/src/home.dart b/material_3_demo/lib/src/home.dart new file mode 100644 index 000000000..ec11e61ef --- /dev/null +++ b/material_3_demo/lib/src/home.dart @@ -0,0 +1,269 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'buttons.dart'; +import 'color_palettes_screen.dart'; +import 'component_screen.dart'; +import 'constants.dart'; +import 'elevation_screen.dart'; +import 'expanded_trailing_actions.dart'; +import 'navigation_transition.dart'; +import 'one_two_transition.dart'; +import 'typography_screen.dart'; + +class Home extends StatefulWidget { + const Home({ + super.key, + required this.useLightMode, + required this.useMaterial3, + required this.colorSelected, + required this.handleBrightnessChange, + required this.handleMaterialVersionChange, + required this.handleColorSelect, + required this.handleImageSelect, + required this.colorSelectionMethod, + required this.imageSelected, + }); + + final bool useLightMode; + final bool useMaterial3; + final ColorSeed colorSelected; + final ColorImageProvider imageSelected; + final ColorSelectionMethod colorSelectionMethod; + + final void Function(bool useLightMode) handleBrightnessChange; + final void Function() handleMaterialVersionChange; + final void Function(int value) handleColorSelect; + final void Function(int value) handleImageSelect; + + @override + State createState() => _HomeState(); +} + +class _HomeState extends State with SingleTickerProviderStateMixin { + final GlobalKey scaffoldKey = GlobalKey(); + late final AnimationController controller; + late final CurvedAnimation railAnimation; + bool controllerInitialized = false; + bool showMediumSizeLayout = false; + bool showLargeSizeLayout = false; + + int screenIndex = ScreenSelected.component.value; + + @override + initState() { + super.initState(); + controller = AnimationController( + duration: Duration(milliseconds: transitionLength.toInt() * 2), + value: 0, + vsync: this, + ); + railAnimation = CurvedAnimation( + parent: controller, + curve: const Interval(0.5, 1.0), + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final double width = MediaQuery.of(context).size.width; + final AnimationStatus status = controller.status; + if (width > mediumWidthBreakpoint) { + if (width > largeWidthBreakpoint) { + showMediumSizeLayout = false; + showLargeSizeLayout = true; + } else { + showMediumSizeLayout = true; + showLargeSizeLayout = false; + } + if (status != AnimationStatus.forward && + status != AnimationStatus.completed) { + controller.forward(); + } + } else { + showMediumSizeLayout = false; + showLargeSizeLayout = false; + if (status != AnimationStatus.reverse && + status != AnimationStatus.dismissed) { + controller.reverse(); + } + } + if (!controllerInitialized) { + controllerInitialized = true; + controller.value = width > mediumWidthBreakpoint ? 1 : 0; + } + } + + void handleScreenChanged(int screenSelected) { + setState(() { + screenIndex = screenSelected; + }); + } + + Widget createScreenFor( + ScreenSelected screenSelected, + bool showNavBarExample, + ) => switch (screenSelected) { + ScreenSelected.component => Expanded( + child: OneTwoTransition( + animation: railAnimation, + one: FirstComponentList( + showNavBottomBar: showNavBarExample, + scaffoldKey: scaffoldKey, + showSecondList: showMediumSizeLayout || showLargeSizeLayout, + ), + two: SecondComponentList(scaffoldKey: scaffoldKey), + ), + ), + ScreenSelected.color => const ColorPalettesScreen(), + ScreenSelected.typography => const TypographyScreen(), + ScreenSelected.elevation => const ElevationScreen(), + }; + + PreferredSizeWidget _createAppBar() { + return AppBar( + title: + widget.useMaterial3 + ? const Text('Material 3') + : const Text('Material 2'), + actions: + !showMediumSizeLayout && !showLargeSizeLayout + ? [ + BrightnessButton( + handleBrightnessChange: widget.handleBrightnessChange, + ), + Material3Button( + handleMaterialVersionChange: + widget.handleMaterialVersionChange, + ), + ColorSeedButton( + handleColorSelect: widget.handleColorSelect, + colorSelected: widget.colorSelected, + colorSelectionMethod: widget.colorSelectionMethod, + ), + ColorImageButton( + handleImageSelect: widget.handleImageSelect, + imageSelected: widget.imageSelected, + colorSelectionMethod: widget.colorSelectionMethod, + ), + ] + : [Container()], + ); + } + + Widget _trailingActions() => Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: BrightnessButton( + handleBrightnessChange: widget.handleBrightnessChange, + showTooltipBelow: false, + ), + ), + Flexible( + child: Material3Button( + handleMaterialVersionChange: widget.handleMaterialVersionChange, + showTooltipBelow: false, + ), + ), + Flexible( + child: ColorSeedButton( + handleColorSelect: widget.handleColorSelect, + colorSelected: widget.colorSelected, + colorSelectionMethod: widget.colorSelectionMethod, + ), + ), + Flexible( + child: ColorImageButton( + handleImageSelect: widget.handleImageSelect, + imageSelected: widget.imageSelected, + colorSelectionMethod: widget.colorSelectionMethod, + ), + ), + ], + ); + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return NavigationTransition( + scaffoldKey: scaffoldKey, + animationController: controller, + railAnimation: railAnimation, + appBar: _createAppBar(), + body: createScreenFor( + ScreenSelected.values[screenIndex], + controller.value == 1, + ), + navigationRail: NavigationRail( + extended: showLargeSizeLayout, + destinations: _navRailDestinations, + selectedIndex: screenIndex, + onDestinationSelected: (index) { + setState(() { + screenIndex = index; + handleScreenChanged(screenIndex); + }); + }, + trailing: Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 20), + child: + showLargeSizeLayout + ? ExpandedTrailingActions( + useLightMode: widget.useLightMode, + handleBrightnessChange: widget.handleBrightnessChange, + useMaterial3: widget.useMaterial3, + handleMaterialVersionChange: + widget.handleMaterialVersionChange, + handleImageSelect: widget.handleImageSelect, + handleColorSelect: widget.handleColorSelect, + colorSelectionMethod: widget.colorSelectionMethod, + imageSelected: widget.imageSelected, + colorSelected: widget.colorSelected, + ) + : _trailingActions(), + ), + ), + ), + navigationBar: NavigationBars( + onSelectItem: (index) { + setState(() { + screenIndex = index; + handleScreenChanged(screenIndex); + }); + }, + selectedIndex: screenIndex, + isExampleBar: false, + ), + ); + }, + ); + } +} + +final List _navRailDestinations = appBarDestinations + .map( + (destination) => NavigationRailDestination( + icon: Tooltip(message: destination.label, child: destination.icon), + selectedIcon: Tooltip( + message: destination.label, + child: destination.selectedIcon, + ), + label: Text(destination.label), + ), + ) + .toList(growable: false); diff --git a/material_3_demo/lib/src/navigation_transition.dart b/material_3_demo/lib/src/navigation_transition.dart new file mode 100644 index 000000000..2159a60c5 --- /dev/null +++ b/material_3_demo/lib/src/navigation_transition.dart @@ -0,0 +1,79 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'bar_transition.dart'; +import 'component_screen.dart'; +import 'rail_transition.dart'; + +class NavigationTransition extends StatefulWidget { + const NavigationTransition({ + super.key, + required this.scaffoldKey, + required this.animationController, + required this.railAnimation, + required this.navigationRail, + required this.navigationBar, + required this.appBar, + required this.body, + }); + + final GlobalKey scaffoldKey; + final AnimationController animationController; + final CurvedAnimation railAnimation; + final Widget navigationRail; + final Widget navigationBar; + final PreferredSizeWidget appBar; + final Widget body; + + @override + State createState() => _NavigationTransitionState(); +} + +class _NavigationTransitionState extends State { + late final AnimationController controller; + late final CurvedAnimation railAnimation; + late final ReverseAnimation barAnimation; + bool controllerInitialized = false; + bool showDivider = false; + + @override + void initState() { + super.initState(); + + controller = widget.animationController; + railAnimation = widget.railAnimation; + + barAnimation = ReverseAnimation( + CurvedAnimation(parent: controller, curve: const Interval(0.0, 0.5)), + ); + } + + @override + Widget build(BuildContext context) { + final ColorScheme colorScheme = Theme.of(context).colorScheme; + + return Scaffold( + key: widget.scaffoldKey, + appBar: widget.appBar, + body: Row( + children: [ + RailTransition( + animation: railAnimation, + backgroundColor: colorScheme.surface, + child: widget.navigationRail, + ), + widget.body, + ], + ), + bottomNavigationBar: BarTransition( + animation: barAnimation, + backgroundColor: colorScheme.surface, + child: widget.navigationBar, + ), + endDrawer: const NavigationDrawerSection(), + ); + } +} diff --git a/material_3_demo/lib/src/one_two_transition.dart b/material_3_demo/lib/src/one_two_transition.dart new file mode 100644 index 000000000..1027249e2 --- /dev/null +++ b/material_3_demo/lib/src/one_two_transition.dart @@ -0,0 +1,62 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import 'animations.dart'; +import 'constants.dart'; + +class OneTwoTransition extends StatefulWidget { + const OneTwoTransition({ + super.key, + required this.animation, + required this.one, + required this.two, + }); + + final Animation animation; + final Widget one; + final Widget two; + + @override + State createState() => _OneTwoTransitionState(); +} + +class _OneTwoTransitionState extends State { + late final Animation offsetAnimation; + late final Animation widthAnimation; + + @override + void initState() { + super.initState(); + + offsetAnimation = Tween( + begin: const Offset(1, 0), + end: Offset.zero, + ).animate(OffsetAnimation(widget.animation)); + + widthAnimation = Tween( + begin: 0, + end: mediumWidthBreakpoint, + ).animate(SizeAnimation(widget.animation)); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Flexible(flex: mediumWidthBreakpoint.toInt(), child: widget.one), + if (widthAnimation.value.toInt() > 0) ...[ + Flexible( + flex: widthAnimation.value.toInt(), + child: FractionalTranslation( + translation: offsetAnimation.value, + child: widget.two, + ), + ), + ], + ], + ); + } +} diff --git a/material_3_demo/lib/src/rail_transition.dart b/material_3_demo/lib/src/rail_transition.dart new file mode 100644 index 000000000..f8e3ed3a9 --- /dev/null +++ b/material_3_demo/lib/src/rail_transition.dart @@ -0,0 +1,64 @@ +// Copyright 2021 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +import 'animations.dart'; + +class RailTransition extends StatefulWidget { + const RailTransition({ + super.key, + required this.animation, + required this.backgroundColor, + required this.child, + }); + + final Animation animation; + final Widget child; + final Color backgroundColor; + + @override + State createState() => _RailTransition(); +} + +class _RailTransition extends State { + late Animation offsetAnimation; + late Animation widthAnimation; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + // The animations are only rebuilt by this method when the text + // direction changes because this widget only depends on Directionality. + final bool ltr = Directionality.of(context) == TextDirection.ltr; + + widthAnimation = Tween( + begin: 0, + end: 1, + ).animate(SizeAnimation(widget.animation)); + + offsetAnimation = Tween( + begin: ltr ? const Offset(-1, 0) : const Offset(1, 0), + end: Offset.zero, + ).animate(OffsetAnimation(widget.animation)); + } + + @override + Widget build(BuildContext context) { + return ClipRect( + child: DecoratedBox( + decoration: BoxDecoration(color: widget.backgroundColor), + child: Align( + alignment: Alignment.topLeft, + widthFactor: widthAnimation.value, + child: FractionalTranslation( + translation: offsetAnimation.value, + child: widget.child, + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/scheme.dart b/material_3_demo/lib/src/scheme.dart similarity index 99% rename from material_3_demo/lib/scheme.dart rename to material_3_demo/lib/src/scheme.dart index 46282f96d..66f604e83 100644 --- a/material_3_demo/lib/scheme.dart +++ b/material_3_demo/lib/src/scheme.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; -import '../color_box.dart'; +import 'color_box.dart'; class SchemePreview extends StatefulWidget { const SchemePreview({ diff --git a/material_3_demo/lib/typography_screen.dart b/material_3_demo/lib/src/typography_screen.dart similarity index 100% rename from material_3_demo/lib/typography_screen.dart rename to material_3_demo/lib/src/typography_screen.dart diff --git a/material_3_demo/test/color_screen_test.dart b/material_3_demo/test/color_screen_test.dart index 63df10753..0f525961e 100644 --- a/material_3_demo/test/color_screen_test.dart +++ b/material_3_demo/test/color_screen_test.dart @@ -7,10 +7,10 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:material_3_demo/color_box.dart'; -import 'package:material_3_demo/color_palettes_screen.dart'; import 'package:material_3_demo/main.dart'; -import 'package:material_3_demo/scheme.dart'; +import 'package:material_3_demo/src/color_box.dart'; +import 'package:material_3_demo/src/color_palettes_screen.dart'; +import 'package:material_3_demo/src/scheme.dart'; import 'component_screen_test.dart'; diff --git a/material_3_demo/test/component_screen_test.dart b/material_3_demo/test/component_screen_test.dart index 9f4f092fc..5bd1615d8 100644 --- a/material_3_demo/test/component_screen_test.dart +++ b/material_3_demo/test/component_screen_test.dart @@ -5,8 +5,8 @@ // ignore_for_file: avoid_types_on_closure_parameters import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:material_3_demo/component_screen.dart'; import 'package:material_3_demo/main.dart'; +import 'package:material_3_demo/src/component_screen.dart'; void main() { testWidgets('Default main page shows all M3 components', (tester) async { diff --git a/material_3_demo/test/elevation_screen_test.dart b/material_3_demo/test/elevation_screen_test.dart index 8ea23e0b8..24e209785 100644 --- a/material_3_demo/test/elevation_screen_test.dart +++ b/material_3_demo/test/elevation_screen_test.dart @@ -5,8 +5,8 @@ // ignore_for_file: avoid_types_on_closure_parameters import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:material_3_demo/elevation_screen.dart'; import 'package:material_3_demo/main.dart'; +import 'package:material_3_demo/src/elevation_screen.dart'; import 'component_screen_test.dart'; diff --git a/material_3_demo/test/typography_screen_test.dart b/material_3_demo/test/typography_screen_test.dart index 18fa9d1c7..bb7bfd737 100644 --- a/material_3_demo/test/typography_screen_test.dart +++ b/material_3_demo/test/typography_screen_test.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:material_3_demo/main.dart'; -import 'package:material_3_demo/typography_screen.dart'; +import 'package:material_3_demo/src/typography_screen.dart'; import 'component_screen_test.dart';