1. 07 Feb, 2024 1 commit
  2. 06 Feb, 2024 2 commits
  3. 02 Feb, 2024 1 commit
    • Hans Muller's avatar
      Reland: Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder (#142762) · c6f2cea6
      Hans Muller authored
      Reland https://github.com/flutter/flutter/pull/141818 with a fix for a special case: If only `background` is specified for `TextButton.styleFrom` or `OutlinedButton.styleFrom` it applies the button's disabled state, i.e. as if the same value had been specified for disabledBackgroundColor.
      
      The change relative to #141818 is the indicated line below:
      ```dart
      final MaterialStateProperty<Color?>? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) {
        (null, null) => null,
        (_, null) => MaterialStatePropertyAll<Color?>(backgroundColor), // ADDED THIS LINE
        (_, _) => _TextButtonDefaultColor(backgroundColor, disabledBackgroundColor),
      };
        ```
      
      This backwards incompatibility cropped up in an internal test, see internal Google issue b/323399158.
      c6f2cea6
  4. 01 Feb, 2024 2 commits
    • auto-submit[bot]'s avatar
      Reverts "Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder" (#142748) · 07ca92a6
      auto-submit[bot] authored
      Reverts flutter/flutter#141818
      Initiated by: XilaiZhang
      This change reverts the following previous change:
      Original Description:
      Fixes https://github.com/flutter/flutter/issues/139456, https://github.com/flutter/flutter/issues/130335, https://github.com/flutter/flutter/issues/89563.
      
      Two new properties have been added to ButtonStyle to make it possible to insert arbitrary state-dependent widgets in a button's background or foreground. These properties can be specified for an individual button, using the style parameter, or for all buttons using a button theme's style parameter.
      
      The new ButtonStyle properties are `backgroundBuilder` and `foregroundBuilder` and their (function) types are:
      
      ```dart
      typedef ButtonLayerBuilder = Widget Function(
        BuildContext context,
        Set<MaterialState> states,
        Widget? child
      );
      ```
      
      The new builder functions are called whenever the button is built and the `states` parameter communicates the pressed/hovered/etc state fo the button.
      
      ## `backgroundBuilder`
      
      Creates a widget that becomes the child of the button's Material and whose child is the rest of the button, including the button's `child` parameter.  By default the returned widget is clipped to the Material's ButtonStyle.shape.
      
      The `backgroundBuilder` can be used to add a gradient to the button's background. Here's an example that creates a yellow/orange gradient background:
      
      ![opaque-gradient-bg](https://github.com/flutter/flutter/assets/1377460/80df8368-e7cf-49ef-aee7-2776a573644c)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            return DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(colors: [Colors.orange, Colors.yellow]),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      Because the background widget becomes the child of the button's Material, if it's opaque (as it is in this case) then it obscures the overlay highlights which are painted on the button's Material. To ensure that the highlights show through one can decorate the background with an `Ink` widget.  This version also overrides the overlay color to be (shades of) red, because that makes the highlights look a little nicer with the yellow/orange background.
      
      ![ink-gradient-bg](https://github.com/flutter/flutter/assets/1377460/68a49733-f30e-44a1-a948-dc8cc95e1716)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          overlayColor: Colors.red,
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            return Ink(
              decoration: BoxDecoration(
                gradient: LinearGradient(colors: [Colors.orange, Colors.yellow]),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      Now the button's overlay highlights are painted on the Ink widget. An Ink widget isn't needed if the background is sufficiently translucent. This version of the example creates a translucent backround widget. 
      
      ![translucent-graident-bg](https://github.com/flutter/flutter/assets/1377460/3b016e1f-200a-4d07-8111-e20d29f18014)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          overlayColor: Colors.red,
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            return DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(colors: [
                  Colors.orange.withOpacity(0.5),
                  Colors.yellow.withOpacity(0.5),
                ]),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      One can also decorate the background with an image. In this example, the button's background is an burlap texture image. The foreground color has been changed to black to make the button's text a little clearer relative to the mottled brown backround.
      
      ![burlap-bg](https://github.com/flutter/flutter/assets/1377460/f2f61ab1-10d9-43a4-bd63-beecdce33b45)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          foregroundColor: Colors.black,
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            return Ink(
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: NetworkImage(burlapUrl),
                  fit: BoxFit.cover,
                ),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      The background widget can depend on the `states` parameter. In this example the blue/orange gradient flips horizontally when the button is hovered/pressed.
      
      ![gradient-flip](https://github.com/flutter/flutter/assets/1377460/c6c6fe26-ae47-445b-b82d-4605d9583bd8)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final Color color1 = Colors.blue.withOpacity(0.5);
            final Color color2 = Colors.orange.withOpacity(0.5);
            return DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: switch (states.contains(MaterialState.hovered)) {
                    true => <Color>[color1, color2],
                    false => <Color>[color2, color1],
                  },
                ),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      The preceeding examples have not included a BoxDecoration border because ButtonStyle already supports `ButtonStyle.shape` and `ButtonStyle.side` parameters that can be uesd to define state-dependent borders. Borders defined with the ButtonStyle side parameter match the button's shape. To add a border that changes color when the button is hovered or pressed, one must specify the side property using `copyWith`, since there's no `styleFrom` shorthand for this case.
      
      ![border-gradient-bg](https://github.com/flutter/flutter/assets/1377460/63cffcd3-0dcf-4eb1-aed5-d14adf1e57f6)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          foregroundColor: Colors.indigo,
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final Color color1 = Colors.blue.withOpacity(0.5);
            final Color color2 = Colors.orange.withOpacity(0.5);
            return DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: switch (states.contains(MaterialState.hovered)) {
                    true => <Color>[color1, color2],
                    false => <Color>[color2, color1],
                  },
                ),
              ),
              child: child,
            );
          },
        ).copyWith(
          side: MaterialStateProperty.resolveWith<BorderSide?>((Set<MaterialState> states) {
            if (states.contains(MaterialState.hovered)) {
              return BorderSide(width: 3, color: Colors.yellow);
            }
            return null; // defer to the default
          }),
        ),
        child: Text('Text Button'),
      )
      ```
      
      Although all of the examples have created a ButtonStyle locally and only applied it to one button, they could have configured the `ThemeData.textButtonTheme` instead and applied the style to all TextButtons. And, of course, all of this works for all of the ButtonStyleButton classes, not just TextButton.
      
      ## `foregroundBuilder`
      
      Creates a Widget that contains the button's child parameter. The returned widget is clipped by the button's [ButtonStyle.shape] inset by the button's [ButtonStyle.padding] and aligned by the button's [ButtonStyle.alignment].
      
      The `foregroundBuilder` can be used to wrap the button's child, e.g. with a border or a `ShaderMask` or as a state-dependent substitute for the child.
      
      This example adds a border that's just applied to the child. The border only appears when the button is hovered/pressed.
      
      ![border-fg](https://github.com/flutter/flutter/assets/1377460/687a3245-fe68-4983-a04e-5fcc77f8aa21)
      
      ```dart
      ElevatedButton(
        onPressed: () {},
        style: ElevatedButton.styleFrom(
          foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final ColorScheme colorScheme = Theme.of(context).colorScheme;
            return DecoratedBox(
              decoration: BoxDecoration(
                border: states.contains(MaterialState.hovered)
                  ? Border(bottom: BorderSide(color: colorScheme.primary))
                  : Border(), // essentially "no border"
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      The foregroundBuilder can be used with `ShaderMask` to change the way the button's child is rendered. In this example the ShaderMask's gradient causes the button's child to fade out on top.
      
      ![shader_mask_fg](https://github.com/flutter/flutter/assets/1377460/54010f24-e65d-4551-ae58-712135df3d8d)
      
      ```dart
      ElevatedButton(
        onPressed: () { },
        style: ElevatedButton.styleFrom(
          foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final ColorScheme colorScheme = Theme.of(context).colorScheme;
            return ShaderMask(
              shaderCallback: (Rect bounds) {
                return LinearGradient(
                  begin: Alignment.bottomCenter,
                  end: Alignment.topCenter,
                  colors: <Color>[
                    colorScheme.primary,
                    colorScheme.primaryContainer,
                  ],
                ).createShader(bounds);
              },
              blendMode: BlendMode.srcATop,
              child: child,
            );
          },
        ),
        child:  const Text('Elevated Button'),
      )
      ```
      
      A commonly requested configuration for butttons has the developer provide images, one for pressed/hovered/normal state. You can use the foregroundBuilder to create a button that fades between a normal image and another image when the button is pressed. In this case the foregroundBuilder doesn't use the child it's passed, even though we've provided the required TextButton child parameter.
      
      ![image-button](https://github.com/flutter/flutter/assets/1377460/f5b1a22f-43ce-4be3-8e70-06de4c958380)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final String url = states.contains(MaterialState.pressed) ? smiley2Url : smiley1Url;
            return AnimatedContainer(
              width: 100,
              height: 100,
              duration: Duration(milliseconds: 300),
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: NetworkImage(url),
                  fit: BoxFit.contain,
                ),
              ),
            );
          },
        ),
        child: Text('No Child'),
      )
      ```
      
      In this example the button's default overlay appears when the button is hovered and pressed. Another image can be used to indicate the hovered state and the default overlay can be defeated by specifying `Colors.transparent` for the `overlayColor`:
      
      ![image-per-state](https://github.com/flutter/flutter/assets/1377460/7ab9da2f-f661-4374-b395-c2e0c7c4cf13)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          overlayColor: Colors.transparent,
          foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            String url = states.contains(MaterialState.hovered) ? smiley3Url : smiley1Url;
            if (states.contains(MaterialState.pressed)) {
              url = smiley2Url;
            }
            return AnimatedContainer(
              width: 100,
              height: 100,
              duration: Duration(milliseconds: 300),
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: NetworkImage(url),
                  fit: BoxFit.contain,
                ),
              ),
            );
          },
        ),
        child: Text('No Child'),
      )
      ```
      07ca92a6
    • Hans Muller's avatar
      Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder (#141818) · ff6c8f5d
      Hans Muller authored
      Fixes https://github.com/flutter/flutter/issues/139456, https://github.com/flutter/flutter/issues/130335, https://github.com/flutter/flutter/issues/89563.
      
      Two new properties have been added to ButtonStyle to make it possible to insert arbitrary state-dependent widgets in a button's background or foreground. These properties can be specified for an individual button, using the style parameter, or for all buttons using a button theme's style parameter.
      
      The new ButtonStyle properties are `backgroundBuilder` and `foregroundBuilder` and their (function) types are:
      
      ```dart
      typedef ButtonLayerBuilder = Widget Function(
        BuildContext context,
        Set<MaterialState> states,
        Widget? child
      );
      ```
      
      The new builder functions are called whenever the button is built and the `states` parameter communicates the pressed/hovered/etc state fo the button.
      
      ## `backgroundBuilder`
      
      Creates a widget that becomes the child of the button's Material and whose child is the rest of the button, including the button's `child` parameter.  By default the returned widget is clipped to the Material's ButtonStyle.shape.
      
      The `backgroundBuilder` can be used to add a gradient to the button's background. Here's an example that creates a yellow/orange gradient background:
      
      ![opaque-gradient-bg](https://github.com/flutter/flutter/assets/1377460/80df8368-e7cf-49ef-aee7-2776a573644c)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            return DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(colors: [Colors.orange, Colors.yellow]),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      Because the background widget becomes the child of the button's Material, if it's opaque (as it is in this case) then it obscures the overlay highlights which are painted on the button's Material. To ensure that the highlights show through one can decorate the background with an `Ink` widget.  This version also overrides the overlay color to be (shades of) red, because that makes the highlights look a little nicer with the yellow/orange background.
      
      ![ink-gradient-bg](https://github.com/flutter/flutter/assets/1377460/68a49733-f30e-44a1-a948-dc8cc95e1716)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          overlayColor: Colors.red,
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            return Ink(
              decoration: BoxDecoration(
                gradient: LinearGradient(colors: [Colors.orange, Colors.yellow]),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      Now the button's overlay highlights are painted on the Ink widget. An Ink widget isn't needed if the background is sufficiently translucent. This version of the example creates a translucent backround widget. 
      
      ![translucent-graident-bg](https://github.com/flutter/flutter/assets/1377460/3b016e1f-200a-4d07-8111-e20d29f18014)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          overlayColor: Colors.red,
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            return DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(colors: [
                  Colors.orange.withOpacity(0.5),
                  Colors.yellow.withOpacity(0.5),
                ]),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      One can also decorate the background with an image. In this example, the button's background is an burlap texture image. The foreground color has been changed to black to make the button's text a little clearer relative to the mottled brown backround.
      
      ![burlap-bg](https://github.com/flutter/flutter/assets/1377460/f2f61ab1-10d9-43a4-bd63-beecdce33b45)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          foregroundColor: Colors.black,
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            return Ink(
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: NetworkImage(burlapUrl),
                  fit: BoxFit.cover,
                ),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      The background widget can depend on the `states` parameter. In this example the blue/orange gradient flips horizontally when the button is hovered/pressed.
      
      ![gradient-flip](https://github.com/flutter/flutter/assets/1377460/c6c6fe26-ae47-445b-b82d-4605d9583bd8)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final Color color1 = Colors.blue.withOpacity(0.5);
            final Color color2 = Colors.orange.withOpacity(0.5);
            return DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: switch (states.contains(MaterialState.hovered)) {
                    true => <Color>[color1, color2],
                    false => <Color>[color2, color1],
                  },
                ),
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      The preceeding examples have not included a BoxDecoration border because ButtonStyle already supports `ButtonStyle.shape` and `ButtonStyle.side` parameters that can be uesd to define state-dependent borders. Borders defined with the ButtonStyle side parameter match the button's shape. To add a border that changes color when the button is hovered or pressed, one must specify the side property using `copyWith`, since there's no `styleFrom` shorthand for this case.
      
      ![border-gradient-bg](https://github.com/flutter/flutter/assets/1377460/63cffcd3-0dcf-4eb1-aed5-d14adf1e57f6)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          foregroundColor: Colors.indigo,
          backgroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final Color color1 = Colors.blue.withOpacity(0.5);
            final Color color2 = Colors.orange.withOpacity(0.5);
            return DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: switch (states.contains(MaterialState.hovered)) {
                    true => <Color>[color1, color2],
                    false => <Color>[color2, color1],
                  },
                ),
              ),
              child: child,
            );
          },
        ).copyWith(
          side: MaterialStateProperty.resolveWith<BorderSide?>((Set<MaterialState> states) {
            if (states.contains(MaterialState.hovered)) {
              return BorderSide(width: 3, color: Colors.yellow);
            }
            return null; // defer to the default
          }),
        ),
        child: Text('Text Button'),
      )
      ```
      
      Although all of the examples have created a ButtonStyle locally and only applied it to one button, they could have configured the `ThemeData.textButtonTheme` instead and applied the style to all TextButtons. And, of course, all of this works for all of the ButtonStyleButton classes, not just TextButton.
      
      ## `foregroundBuilder`
      
      Creates a Widget that contains the button's child parameter. The returned widget is clipped by the button's [ButtonStyle.shape] inset by the button's [ButtonStyle.padding] and aligned by the button's [ButtonStyle.alignment].
      
      The `foregroundBuilder` can be used to wrap the button's child, e.g. with a border or a `ShaderMask` or as a state-dependent substitute for the child.
      
      This example adds a border that's just applied to the child. The border only appears when the button is hovered/pressed.
      
      ![border-fg](https://github.com/flutter/flutter/assets/1377460/687a3245-fe68-4983-a04e-5fcc77f8aa21)
      
      ```dart
      ElevatedButton(
        onPressed: () {},
        style: ElevatedButton.styleFrom(
          foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final ColorScheme colorScheme = Theme.of(context).colorScheme;
            return DecoratedBox(
              decoration: BoxDecoration(
                border: states.contains(MaterialState.hovered)
                  ? Border(bottom: BorderSide(color: colorScheme.primary))
                  : Border(), // essentially "no border"
              ),
              child: child,
            );
          },
        ),
        child: Text('Text Button'),
      )
      ```
      
      The foregroundBuilder can be used with `ShaderMask` to change the way the button's child is rendered. In this example the ShaderMask's gradient causes the button's child to fade out on top.
      
      ![shader_mask_fg](https://github.com/flutter/flutter/assets/1377460/54010f24-e65d-4551-ae58-712135df3d8d)
      
      ```dart
      ElevatedButton(
        onPressed: () { },
        style: ElevatedButton.styleFrom(
          foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final ColorScheme colorScheme = Theme.of(context).colorScheme;
            return ShaderMask(
              shaderCallback: (Rect bounds) {
                return LinearGradient(
                  begin: Alignment.bottomCenter,
                  end: Alignment.topCenter,
                  colors: <Color>[
                    colorScheme.primary,
                    colorScheme.primaryContainer,
                  ],
                ).createShader(bounds);
              },
              blendMode: BlendMode.srcATop,
              child: child,
            );
          },
        ),
        child:  const Text('Elevated Button'),
      )
      ```
      
      A commonly requested configuration for butttons has the developer provide images, one for pressed/hovered/normal state. You can use the foregroundBuilder to create a button that fades between a normal image and another image when the button is pressed. In this case the foregroundBuilder doesn't use the child it's passed, even though we've provided the required TextButton child parameter.
      
      ![image-button](https://github.com/flutter/flutter/assets/1377460/f5b1a22f-43ce-4be3-8e70-06de4c958380)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            final String url = states.contains(MaterialState.pressed) ? smiley2Url : smiley1Url;
            return AnimatedContainer(
              width: 100,
              height: 100,
              duration: Duration(milliseconds: 300),
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: NetworkImage(url),
                  fit: BoxFit.contain,
                ),
              ),
            );
          },
        ),
        child: Text('No Child'),
      )
      ```
      
      In this example the button's default overlay appears when the button is hovered and pressed. Another image can be used to indicate the hovered state and the default overlay can be defeated by specifying `Colors.transparent` for the `overlayColor`:
      
      ![image-per-state](https://github.com/flutter/flutter/assets/1377460/7ab9da2f-f661-4374-b395-c2e0c7c4cf13)
      
      ```dart
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          overlayColor: Colors.transparent,
          foregroundBuilder: (BuildContext context, Set<MaterialState> states, Widget? child) {
            String url = states.contains(MaterialState.hovered) ? smiley3Url : smiley1Url;
            if (states.contains(MaterialState.pressed)) {
              url = smiley2Url;
            }
            return AnimatedContainer(
              width: 100,
              height: 100,
              duration: Duration(milliseconds: 300),
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: NetworkImage(url),
                  fit: BoxFit.contain,
                ),
              ),
            );
          },
        ),
        child: Text('No Child'),
      )
      ```
      ff6c8f5d
  5. 26 Jan, 2024 1 commit
  6. 12 Jan, 2024 2 commits
  7. 03 Jan, 2024 1 commit
    • Furkan Acar's avatar
      Add `SegmentedButton.styleFrom` (#137542) · 83ac7605
      Furkan Acar authored
      fixes https://github.com/flutter/flutter/issues/138289
      
      ---
      
      SegmentedButtom.styleFrom has been added to the segment button, so there is no longer any need to the button style from the beginning. It works like ElevatedButton.styleFrom only I added selectedForegroundColor, selectedBackgroundColor. In this way, the user will be able to change the color first without checking the MaterialState states. I added tests of the same controls.
      
      #129215 I opened this problem myself, but I was rejected because I handled too many items in a PR. For now, I wrote a structure that only handles MaterialStates instead of users.
      
      old (still avaliable)
      <img width="626" alt="image" src="https://github.com/flutter/flutter/assets/65075121/9446b13b-c355-4d20-bda2-c47a23d42d4f">
      
      new (just an option for developer)
      <img width="483" alt="image" src="https://github.com/flutter/flutter/assets/65075121/0a645257-4c83-4029-9484-bd746c02265f">
      
      ### Code sample
      
      <details>
      <summary>expand to view the code sample</summary> 
      
      ```dart
      import 'package:flutter/material.dart';
      
      /// Flutter code sample for [SegmentedButton].
      
      void main() {
        runApp(const SegmentedButtonApp());
      }
      
      enum Calendar { day, week, month, year }
      
      class SegmentedButtonApp extends StatefulWidget {
        const SegmentedButtonApp({super.key});
      
        @override
        State<SegmentedButtonApp> createState() => _SegmentedButtonAppState();
      }
      
      class _SegmentedButtonAppState extends State<SegmentedButtonApp> {
        Calendar calendarView = Calendar.day;
      
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            theme: ThemeData(useMaterial3: true),
            home: Scaffold(
              body: Center(
                child: SegmentedButton<Calendar>(
                  style: SegmentedButton.styleFrom(
                    foregroundColor: Colors.amber,
                    visualDensity: VisualDensity.comfortable,
                  ),
                  // style: const ButtonStyle(
                  //   foregroundColor: MaterialStatePropertyAll<Color>(Colors.deepPurple),
                  //   visualDensity: VisualDensity.comfortable,
                  // ),
                  segments: const <ButtonSegment<Calendar>>[
                    ButtonSegment<Calendar>(
                        value: Calendar.day,
                        label: Text('Day'),
                        icon: Icon(Icons.calendar_view_day)),
                    ButtonSegment<Calendar>(
                        value: Calendar.week,
                        label: Text('Week'),
                        icon: Icon(Icons.calendar_view_week)),
                    ButtonSegment<Calendar>(
                        value: Calendar.month,
                        label: Text('Month'),
                        icon: Icon(Icons.calendar_view_month)),
                    ButtonSegment<Calendar>(
                        value: Calendar.year,
                        label: Text('Year'),
                        icon: Icon(Icons.calendar_today)),
                  ],
                  selected: <Calendar>{calendarView},
                  onSelectionChanged: (Set<Calendar> newSelection) {
                    setState(() {
                      calendarView = newSelection.first;
                    });
                  },
                ),
              ),
            ),
          );
        }
      }
      
      ```
      
      </details>
      83ac7605
  8. 11 Dec, 2023 2 commits
    • Nate's avatar
      Implement `switch` expressions in `examples/` and `animation/` (#139882) · 140f5eef
      Nate authored
      Thanks so much for approving the previous PR (#139048) a couple weeks ago!
      
      This one is the same, except it's covering files in `examples/` and `packages/flutter/lib/src/animation/`.
      
      (solving issue #136139)
      140f5eef
    • Renzo Olivares's avatar
      Fix SelectionArea select-word edge cases (#136920) · f60e54b2
      Renzo Olivares authored
      This change fixes issues with screen order comparison logic when rects are encompassed within each other. This was causing issues when trying to select text that includes inline `WidgetSpan`s inside of a `SelectionArea`.
      
      * Adds `boundingBoxes` to `Selectable` for a more precise hit testing region.
      
      Fixes #132821
      Fixes updating selection edge by word boundary when widget spans are involved.
      Fixes crash when sending select word selection event to an unselectable element.
      f60e54b2
  9. 06 Dec, 2023 1 commit
    • Taha Tesser's avatar
      Add `AnimationStyle` to `ExpansionTile` (#139664) · f794cf9d
      Taha Tesser authored
      fixes [Expose animation parameters for the [ExpansionTile] widget](https://github.com/flutter/flutter/issues/138047)
      
      ### Description
      Add `AnimationStyle` to the `ExpansionTile` widget to override the default expand and close animation.
      
      Syntax:
      ```dart
              child: ExpansionTile(
                title: const Text('Tap to expand'),
                expansionAnimationStyle: AnimationStyle(
                  duration: Durations.extralong1,
                  curve: Easing.emphasizedAccelerate,
                ),
                children: const <Widget>[FlutterLogo(size: 200)],
              ),
      ```
      
      ### Code sample
      
      <details>
      <summary>expand to view the code sample</summary> 
      
      ```dart
      // Copyright 2014 The Flutter Authors. 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';
      
      /// Flutter code sample for [ExpansionTile] and [AnimationStyle].
      
      void main() {
        runApp(const ExpansionTileAnimationStyleApp());
      }
      
      enum AnimationStyles { defaultStyle, custom, none }
      const List<(AnimationStyles, String)> animationStyleSegments = <(AnimationStyles, String)>[
        (AnimationStyles.defaultStyle, 'Default'),
        (AnimationStyles.custom, 'Custom'),
        (AnimationStyles.none, 'None'),
      ];
      
      class ExpansionTileAnimationStyleApp extends StatefulWidget {
        const ExpansionTileAnimationStyleApp({super.key});
      
        @override
        State<ExpansionTileAnimationStyleApp> createState() => _ExpansionTileAnimationStyleAppState();
      }
      
      class _ExpansionTileAnimationStyleAppState extends State<ExpansionTileAnimationStyleApp> {
        Set<AnimationStyles> _animationStyleSelection = <AnimationStyles>{AnimationStyles.defaultStyle};
        AnimationStyle? _animationStyle;
      
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            home: Scaffold(
              body: SafeArea(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    SegmentedButton<AnimationStyles>(
                      selected: _animationStyleSelection,
                      onSelectionChanged: (Set<AnimationStyles> styles) {
                        setState(() {
                          _animationStyleSelection = styles;
                          switch (styles.first) {
                            case AnimationStyles.defaultStyle:
                              _animationStyle = null;
                            case AnimationStyles.custom:
                              _animationStyle = AnimationStyle(
                                curve: Easing.emphasizedAccelerate,
                                duration: Durations.extralong1,
                              );
                            case AnimationStyles.none:
                              _animationStyle = AnimationStyle.noAnimation;
                          }
                        });
                      },
                      segments: animationStyleSegments
                        .map<ButtonSegment<AnimationStyles>>(((AnimationStyles, String) shirt) {
                          return ButtonSegment<AnimationStyles>(value: shirt.$1, label: Text(shirt.$2));
                        })
                        .toList(),
                    ),
                    const SizedBox(height: 20),
                    ExpansionTile(
                      expansionAnimationStyle: _animationStyle,
                      title: const Text('ExpansionTile'),
                      children: const <Widget>[
                        ListTile(title: Text('Expanded Item 1')),
                        ListTile(title: Text('Expanded Item 2')),
                      ],
                    )
                  ],
                ),
              ),
            ),
          );
        }
      }
      ```
      
      </details>
      
      Related to https://github.com/flutter/flutter/pull/138721.
      f794cf9d
  10. 20 Nov, 2023 1 commit
  11. 17 Nov, 2023 1 commit
    • auto-submit[bot]'s avatar
      Reverts "Introduce `AnimationStyle`" (#138628) · 0135a331
      auto-submit[bot] authored
      Reverts flutter/flutter#137945
      Initiated by: HansMuller
      This change reverts the following previous change:
      Original Description:
      This PR introduces `AnimationStyle`, it is used to override default animation curves and durations in several widgets.
      
      fixes  [Add the ability to customize MaterialApp theme animation duration](https://github.com/flutter/flutter/issues/78372)
      fixes [Allow customization of showMenu transition animation curves and duration](https://github.com/flutter/flutter/issues/135638)
      
      Here is an example where popup menu curve and transition duration is overriden:
      
      ```dart
                popUpAnimationStyle: AnimationStyle(
                  curve: Easing.emphasizedAccelerate,
                  duration: Durations.medium4,
                ),
      ```
      
      Set `AnimationStyle.noAnimation` to disable animation.
      ```dart
          return MaterialApp(
            themeAnimationStyle: AnimationStyle.noAnimation,
      ```
      0135a331
  12. 16 Nov, 2023 1 commit
    • Taha Tesser's avatar
      Introduce `AnimationStyle` (#137945) · 19e284f8
      Taha Tesser authored
      This PR introduces `AnimationStyle`, it is used to override default animation curves and durations in several widgets.
      
      fixes  [Add the ability to customize MaterialApp theme animation duration](https://github.com/flutter/flutter/issues/78372)
      fixes [Allow customization of showMenu transition animation curves and duration](https://github.com/flutter/flutter/issues/135638)
      
      Here is an example where popup menu curve and transition duration is overriden:
      
      ```dart
                popUpAnimationStyle: AnimationStyle(
                  curve: Easing.emphasizedAccelerate,
                  duration: Durations.medium4,
                ),
      ```
      
      Set `AnimationStyle.noAnimation` to disable animation.
      ```dart
          return MaterialApp(
            themeAnimationStyle: AnimationStyle.noAnimation,
      ```
      19e284f8
  13. 07 Nov, 2023 1 commit
    • Qun Cheng's avatar
      Adaptive `Switch` (#130425) · ed70f4e2
      Qun Cheng authored
      Currently, `Switch.factory` delegates to `CupertinoSwitch` when platform
      is iOS or macOS. This PR is to:
      * have the factory configure the Material `Switch` for the expected look
      and feel.
      * introduce `Adaptation` class to customize themes for the adaptive
      components.
      ed70f4e2
  14. 03 Nov, 2023 3 commits
  15. 01 Nov, 2023 1 commit
    • Qun Cheng's avatar
      Add `Card.filled` and `Card.outlined` factory methods (#136229) · 4a0f261b
      Qun Cheng authored
      Fixes #119401
      
      This PR is to:
      * add `Card.filled` and `Card.outlined` factory methods so that we can use tokens for these two types of cards to generate default theme instead of providing hard-corded values in example.
      * update card.2.dart example.
      * add test file for card.2.dart example.
      * fix some mismatch caused by editing the auto-generated defaults by hand in navigation_bar.dart and navigation_drawer.dart.
      4a0f261b
  16. 26 Oct, 2023 1 commit
  17. 24 Oct, 2023 1 commit
    • cui fliter's avatar
      fix some typos (#137144) · c5e71b3e
      cui fliter authored
      *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.*
      
      fix some typos
      
      *List which issues are fixed by this PR. You must list at least one issue.*
      
      *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
      c5e71b3e
  18. 18 Oct, 2023 1 commit
  19. 12 Oct, 2023 1 commit
    • Greg Spencer's avatar
      Allow `TapRegion` to consume tap events (#136305) · 22b0a62a
      Greg Spencer authored
      ## Description
      
      In order for `MenuAnchor` menus to be able to not pass on the taps that close their menus, `TapRegion` needed a way to consume them.  This change adds a flag to the `TapRegion`, `consumeOutsideTap` that will consume taps that occur outside of the region if the flag is set (it is false by default). The same flag is added to `MenuAnchor` to allow selecting the behavior for menus.
      
      `TapRegion` consumes the tap event by registering with the gesture arena and immediately resolving the tap as accepted if any regions in a group have `consumeOutsideTap` set to true.
      
      This PR also deprecates `MenuAnchor.anchorTapClosesMenu`, since it is a much more limited version of the same feature that only applied to the anchor itself, and even then only applied to closing the menu, not passing along the tap.  The same functionality can now be implemented by handling a tap on the anchor widget and checking to see if the menu is open before closing it.
      
      ## Related Issues
       - https://github.com/flutter/flutter/issues/135327
      
      ## Tests
       - Added tests for `TapRegion` to make sure taps are consumed properly.
      22b0a62a
  20. 20 Sep, 2023 1 commit
  21. 18 Sep, 2023 1 commit
  22. 11 Sep, 2023 1 commit
  23. 08 Sep, 2023 1 commit
  24. 01 Sep, 2023 1 commit
    • Andrea Cioni's avatar
      Add an example for `InputChip` generated by user input (#130645) · 400702d1
      Andrea Cioni authored
      New example for `InputChip` that demonstrate how to create/delete them based on user text inputs.
      
      The sample application shows a custom text area where user can enter text. After the user has typed and hits _Enter_ the text will be replaced with an `InputChip` that contains that text. Is it possible to continue typing and add more chips in this way. All of them will be placed in a scrollable horizontal row. Also is it possible to have suggestion displayed below the text input field in case the typed text match some of the available suggestions.
      
      Issue I'm trying to solve:
      
      - https://github.com/flutter/flutter/issues/128247
      
      **Code structure:**
      
      The example app is composed of 2 main components that find places inside `MainScreen`:
      
       - `ChipsInput`
       - `ListView`
      
      `ChipsInput` emulates a `TextField` where you can enter text. This text field accepts also a list of values of generic type T (`Topping` in my example), that gets rendered as `InputChip` inside the text field, before the text inserted by the user. This widgets is basically an `InputDecorator` widget that implements `TextInputClient` to get `TextEditingValue` events from the user keyboard. At the end of the input field there is another component, the `TextCursor`, that is displayed just when the user give the focus to the field and emulates the carrets that `TextField` has.
      
      There are also some available callbacks that the user can use to capture events in the `ChipsInput` field like: `onChanged`, `onChipTapped`, `onSubmitted` and `onTextChanged`. This last callback is used to build a list of suggestion that will be placed just below the `ChipsInput` field inside the `ListView`.
      400702d1
  25. 30 Aug, 2023 1 commit
  26. 29 Aug, 2023 2 commits
  27. 21 Aug, 2023 1 commit
  28. 17 Aug, 2023 1 commit
  29. 15 Aug, 2023 1 commit
    • Ian Hickson's avatar
      PaginatedDataTable improvements (#131374) · ccdf8264
      Ian Hickson authored
      - slightly improved assert message when row cell counts don't match column count.
      - more breadcrumbs in API documentation. more documentation in general.
      - added more documentation for the direction of the "ascending" arrow.
      - two samples for PaginatedDataTable.
      - make PaginatedDataTable support hot reloading across changes to the number of columns.
      - introduce matrix3MoreOrLessEquals. An earlier version of this PR used it in tests, but eventually it was not needed. The function seems useful to keep though.
      ccdf8264
  30. 14 Aug, 2023 1 commit
  31. 08 Aug, 2023 1 commit
  32. 04 Aug, 2023 1 commit
    • Justin McCandless's avatar
      Predictive back support for root routes (#120385) · dedd100e
      Justin McCandless authored
      This PR aims to support Android's predictive back gesture when popping the entire Flutter app.  Predictive route transitions between routes inside of a Flutter app will come later.
      
      <img width="200" src="https://user-images.githubusercontent.com/389558/217918109-945febaa-9086-41cc-a476-1a189c7831d8.gif" />
      
      ### Trying it out
      
      If you want to try this feature yourself, here are the necessary steps:
      
        1. Run Android 33 or above.
        1. Enable the feature flag for predictive back on the device under "Developer
           options".
        1. Create a Flutter project, or clone [my example project](https://github.com/justinmc/flutter_predictive_back_examples).
        1. Set `android:enableOnBackInvokedCallback="true"` in
           android/app/src/main/AndroidManifest.xml (already done in the example project).
        1. Check out this branch.
        1. Run the app. Perform a back gesture (swipe from the left side of the
           screen).
      
      You should see the predictive back animation like in the animation above and be able to commit or cancel it.
      
      ### go_router support
      
      go_router works with predictive back out of the box because it uses a Navigator internally that dispatches NavigationNotifications!
      
      ~~go_router can be supported by adding a listener to the router and updating SystemNavigator.setFrameworkHandlesBack.~~
      
      Similar to with nested Navigators, nested go_routers is supported by using a PopScope widget.
      
      <details>
      
      <summary>Full example of nested go_routers</summary>
      
      ```dart
      // Copyright 2014 The Flutter Authors. 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:go_router/go_router.dart';
      
      import 'package:flutter/material.dart';
      import 'package:flutter/scheduler.dart';
      
      void main() => runApp(_MyApp());
      
      class _MyApp extends StatelessWidget {
        final GoRouter router = GoRouter(
          routes: <RouteBase>[
            GoRoute(
              path: '/',
              builder: (BuildContext context, GoRouterState state) => _HomePage(),
            ),
            GoRoute(
              path: '/nested_navigators',
              builder: (BuildContext context, GoRouterState state) => _NestedGoRoutersPage(),
            ),
          ],
        );
      
        @override
        Widget build(BuildContext context) {
          return MaterialApp.router(
            routerConfig: router,
          );
        }
      }
      
      class _HomePage extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
              title: const Text('Nested Navigators Example'),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  const Text('Home Page'),
                  const Text('A system back gesture here will exit the app.'),
                  const SizedBox(height: 20.0),
                  ListTile(
                    title: const Text('Nested go_router route'),
                    subtitle: const Text('This route has another go_router in addition to the one used with MaterialApp above.'),
                    onTap: () {
                      context.push('/nested_navigators');
                    },
                  ),
                ],
              ),
            ),
          );
        }
      }
      
      class _NestedGoRoutersPage extends StatefulWidget {
        @override
        State<_NestedGoRoutersPage> createState() => _NestedGoRoutersPageState();
      }
      
      class _NestedGoRoutersPageState extends State<_NestedGoRoutersPage> {
        late final GoRouter _router;
        final GlobalKey<NavigatorState> _nestedNavigatorKey = GlobalKey<NavigatorState>();
      
        // If the nested navigator has routes that can be popped, then we want to
        // block the root navigator from handling the pop so that the nested navigator
        // can handle it instead.
        bool get _popEnabled {
          // canPop will throw an error if called before build. Is this the best way
          // to avoid that?
          return _nestedNavigatorKey.currentState == null ? true : !_router.canPop();
        }
      
        void _onRouterChanged() {
          // Here the _router reports the location correctly, but canPop is still out
          // of date.  Hence the post frame callback.
          SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
            setState(() {});
          });
        }
      
        @override
        void initState() {
          super.initState();
      
          final BuildContext rootContext = context;
          _router = GoRouter(
            navigatorKey: _nestedNavigatorKey,
            routes: [
              GoRoute(
                path: '/',
                builder: (BuildContext context, GoRouterState state) => _LinksPage(
                  title: 'Nested once - home route',
                  backgroundColor: Colors.indigo,
                  onBack: () {
                    rootContext.pop();
                  },
                  buttons: <Widget>[
                    TextButton(
                      onPressed: () {
                        context.push('/two');
                      },
                      child: const Text('Go to another route in this nested Navigator'),
                    ),
                  ],
                ),
              ),
              GoRoute(
                path: '/two',
                builder: (BuildContext context, GoRouterState state) => _LinksPage(
                  backgroundColor: Colors.indigo.withBlue(255),
                  title: 'Nested once - page two',
                ),
              ),
            ],
          );
      
          _router.addListener(_onRouterChanged);
        }
      
        @override
        void dispose() {
          _router.removeListener(_onRouterChanged);
          super.dispose();
        }
      
        @override
        Widget build(BuildContext context) {
          return PopScope(
            popEnabled: _popEnabled,
            onPopped: (bool success) {
              if (success) {
                return;
              }
              _router.pop();
            },
            child: Router<Object>.withConfig(
              restorationScopeId: 'router-2',
              config: _router,
            ),
          );
        }
      }
      
      class _LinksPage extends StatelessWidget {
        const _LinksPage ({
          required this.backgroundColor,
          this.buttons = const <Widget>[],
          this.onBack,
          required this.title,
        });
      
        final Color backgroundColor;
        final List<Widget> buttons;
        final VoidCallback? onBack;
        final String title;
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            backgroundColor: backgroundColor,
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(title),
                  //const Text('A system back here will go back to Nested Navigators Page One'),
                  ...buttons,
                  TextButton(
                    onPressed: onBack ?? () {
                      context.pop();
                    },
                    child: const Text('Go back'),
                  ),
                ],
              ),
            ),
          );
        }
      }
      ```
      
      </details>
      
      ### Resources
      
      Fixes https://github.com/flutter/flutter/issues/109513
      Depends on engine PR https://github.com/flutter/engine/pull/39208 :heavy_check_mark: 
      Design doc: https://docs.google.com/document/d/1BGCWy1_LRrXEB6qeqTAKlk-U2CZlKJ5xI97g45U7azk/edit#
      Migration guide: https://github.com/flutter/website/pull/8952
      dedd100e
  33. 20 Jul, 2023 1 commit