// 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 [TextButton].

void main() {
  runApp(const TextButtonExampleApp());
}

class TextButtonExampleApp extends StatefulWidget {
  const TextButtonExampleApp({super.key});

  @override
  State<TextButtonExampleApp> createState() => _TextButtonExampleAppState();
}

class _TextButtonExampleAppState extends State<TextButtonExampleApp> {
  bool darkMode = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      themeMode: darkMode ? ThemeMode.dark : ThemeMode.light,
      theme: ThemeData(brightness: Brightness.light),
      darkTheme: ThemeData(brightness: Brightness.dark),
      home: Scaffold(
        body: Padding(
          padding: const EdgeInsets.all(16),
          child: TextButtonExample(
            darkMode: darkMode,
            updateDarkMode: (bool value) {
              setState(() {
                darkMode = value;
              });
            },
          ),
        ),
      ),
    );
  }
}

class TextButtonExample extends StatefulWidget {
  const TextButtonExample({
    super.key,
    required this.darkMode,
    required this.updateDarkMode,
  });

  final bool darkMode;
  final ValueChanged<bool> updateDarkMode;

  @override
  State<TextButtonExample> createState() => _TextButtonExampleState();
}

class _TextButtonExampleState extends State<TextButtonExample> {
  TextDirection textDirection = TextDirection.ltr;
  ThemeMode themeMode = ThemeMode.light;
  late final ScrollController scrollController;
  Future<void>? currentAction;

  static const Widget verticalSpacer = SizedBox(height: 16);
  static const Widget horizontalSpacer = SizedBox(width: 32);

  static const ImageProvider grassImage = NetworkImage(
    'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_grass.jpeg',
  );
  static const ImageProvider defaultImage = NetworkImage(
    'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_nhu_default.png',
  );
  static const ImageProvider hoveredImage = NetworkImage(
    'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_nhu_hovered.png',
  );
  static const ImageProvider pressedImage = NetworkImage(
    'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_nhu_pressed.png',
  );
  static const ImageProvider runningImage = NetworkImage(
    'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_nhu_end.png',
  );

  @override
  void initState() {
    scrollController = ScrollController();
    super.initState();
  }

  @override
  void dispose() {
    scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    final ColorScheme colorScheme = theme.colorScheme;

    // Adapt colors that are not part of the color scheme to
    // the current dark/light mode. Used to define TextButton #7's
    // gradients.
    final (
      Color color1,
      Color color2,
      Color color3,
    ) = switch (colorScheme.brightness) {
      Brightness.light => (Colors.blue, Colors.orange, Colors.yellow),
      Brightness.dark => (Colors.purple, Colors.cyan, Colors.yellow),
    };

    // This gradient's appearance reflects the button's state.
    // Always return a gradient decoration so that AnimatedContainer
    // can interpolate in between. Used by TextButton #7.
    Decoration? statesToDecoration(Set<WidgetState> states) {
      if (states.contains(WidgetState.pressed)) {
        return BoxDecoration(
          gradient: LinearGradient(
            colors: <Color>[color2, color2],
          ), // solid fill
        );
      }
      return BoxDecoration(
        gradient: LinearGradient(
          colors: switch (states.contains(WidgetState.hovered)) {
            true => <Color>[color1, color2],
            false => <Color>[color2, color1],
          },
        ),
      );
    }

    // To make this method a little easier to read, the buttons that
    // appear in the two columns to the right of the demo switches
    // Card are broken out below.

    final List<Widget> columnOneButtons = <Widget>[
      TextButton(onPressed: () {}, child: const Text('Enabled')),
      verticalSpacer,

      const TextButton(onPressed: null, child: Text('Disabled')),
      verticalSpacer,

      TextButton.icon(
        onPressed: () {},
        icon: const Icon(Icons.access_alarm),
        label: const Text('TextButton.icon #1'),
      ),
      verticalSpacer,

      // Override the foreground and background colors.
      //
      // In this example, and most of the ones that follow, we're using
      // the TextButton.styleFrom() convenience method to create a ButtonStyle.
      // The styleFrom method is a little easier because it creates
      // ButtonStyle WidgetStateProperty parameters for you.
      // In this case, Specifying foregroundColor overrides the text,
      // icon and overlay (splash and highlight) colors a little differently
      // depending on the button's state. BackgroundColor is just the background
      // color for all states.
      TextButton.icon(
        style: TextButton.styleFrom(
          foregroundColor: colorScheme.onError,
          backgroundColor: colorScheme.error,
        ),
        onPressed: () {},
        icon: const Icon(Icons.access_alarm),
        label: const Text('TextButton.icon #2'),
      ),
      verticalSpacer,

      // Override the button's shape and its border.
      //
      // In this case we've specified a shape that has border - the
      // RoundedRectangleBorder's side parameter. If the styleFrom
      // side parameter was also specified, or if the TextButtonTheme
      // defined above included a side parameter, then that would
      // override the RoundedRectangleBorder's side.
      TextButton(
        style: TextButton.styleFrom(
          shape: RoundedRectangleBorder(
            borderRadius: const BorderRadius.all(Radius.circular(8)),
            side: BorderSide(color: colorScheme.primary, width: 5),
          ),
        ),
        onPressed: () {},
        child: const Text('TextButton #3'),
      ),
      verticalSpacer,

      // Override overlay: the ink splash and highlight colors.
      //
      // The styleFrom method turns the specified overlayColor
      // into a value MaterialStyleProperty<Color> ButtonStyle.overlay
      // value that uses opacities depending on the button's state.
      // If the overlayColor was Colors.transparent, no splash
      // or highlights would be shown.
      TextButton(
        style: TextButton.styleFrom(overlayColor: Colors.yellow),
        onPressed: () {},
        child: const Text('TextButton #4'),
      ),
    ];

    final List<Widget> columnTwoButtons = <Widget>[
      // Override the foregroundBuilder: apply a ShaderMask.
      //
      // Apply a ShaderMask to the button's child. This kind of thing
      // can be applied to one button easily enough by just wrapping the
      // button's child directly. However to affect all buttons in this
      // way you can specify a similar foregroundBuilder in a TextButton
      // theme or the MaterialApp theme's ThemeData.textButtonTheme.
      TextButton(
        style: TextButton.styleFrom(
          foregroundBuilder:
              (BuildContext context, Set<WidgetState> states, Widget? child) {
                return ShaderMask(
                  shaderCallback: (Rect bounds) {
                    return LinearGradient(
                      begin: Alignment.bottomCenter,
                      end: Alignment.topCenter,
                      colors: <Color>[
                        colorScheme.primary,
                        colorScheme.onPrimary,
                      ],
                    ).createShader(bounds);
                  },
                  blendMode: BlendMode.srcATop,
                  child: child,
                );
              },
        ),
        onPressed: () {},
        child: const Text('TextButton #5'),
      ),
      verticalSpacer,

      // Override the foregroundBuilder: add an underline.
      //
      // Add a border around button's child. In this case the
      // border only appears when the button is hovered or pressed
      // (if it's pressed it's always hovered too). Not that this
      // border is different than the one specified with the styleFrom
      // side parameter (or the ButtonStyle.side property). The foregroundBuilder
      // is applied to a widget that contains the child and has already
      // included the button's padding. It is unaffected by the button's shape.
      // The styleFrom side parameter controls the button's outermost border and it
      // outlines the button's shape.
      TextButton(
        style: TextButton.styleFrom(
          foregroundBuilder:
              (BuildContext context, Set<WidgetState> states, Widget? child) {
                return DecoratedBox(
                  decoration: BoxDecoration(
                    border: states.contains(WidgetState.hovered)
                        ? Border(bottom: BorderSide(color: colorScheme.primary))
                        : const Border(), // essentially "no border"
                  ),
                  child: child,
                );
              },
        ),
        onPressed: () {},
        child: const Text('TextButton #6'),
      ),
      verticalSpacer,

      // Override the backgroundBuilder to add a state specific gradient background
      // and add an outline that only appears when the button is hovered or pressed.
      //
      // The gradient background decoration is computed by the statesToDecoration()
      // method. The gradient flips horizontally when the button is hovered (watch
      // closely). Because we want the outline to only appear when the button is hovered
      // we can't use the styleFrom() side parameter, because that creates the same
      // outline for all states. The ButtonStyle.copyWith() method is used to add
      // a WidgetState<BorderSide?> property that does the right thing.
      //
      // The gradient background is translucent - all of the colors have opacity 0.5 -
      // so the overlay's splash and highlight colors are visible even though they're
      // drawn on the Material widget that's effectively behind the background. The
      // border is also translucent, so if you look carefully, you'll see that the
      // background - which is part of the button's Material but is drawn on top of the
      // the background gradient - shows through the border.
      TextButton(
        onPressed: () {},
        style:
            TextButton.styleFrom(
              overlayColor: color2,
              backgroundBuilder:
                  (
                    BuildContext context,
                    Set<WidgetState> states,
                    Widget? child,
                  ) {
                    return AnimatedContainer(
                      duration: const Duration(milliseconds: 500),
                      decoration: statesToDecoration(states),
                      child: child,
                    );
                  },
            ).copyWith(
              side: WidgetStateProperty.resolveWith<BorderSide?>((
                Set<WidgetState> states,
              ) {
                if (states.contains(WidgetState.hovered)) {
                  return BorderSide(width: 3, color: color3);
                }
                return null; // defer to the default
              }),
            ),
        child: const Text('TextButton #7'),
      ),
      verticalSpacer,

      // Override the backgroundBuilder to add a grass image background.
      //
      // The image is clipped to the button's shape. We've included an Ink widget
      // because the background image is opaque and would otherwise obscure the splash
      // and highlight overlays that are painted on the button's Material widget
      // by default. They're drawn on the Ink widget instead. The foreground color
      // was overridden as well because white shows up a little better on the mottled
      // green background.
      TextButton(
        onPressed: () {},
        style: TextButton.styleFrom(
          foregroundColor: Colors.white,
          backgroundBuilder:
              (BuildContext context, Set<WidgetState> states, Widget? child) {
                return Ink(
                  decoration: const BoxDecoration(
                    image: DecorationImage(
                      image: grassImage,
                      fit: BoxFit.cover,
                    ),
                  ),
                  child: child,
                );
              },
        ),
        child: const Text('TextButton #8'),
      ),
      verticalSpacer,

      // Override the foregroundBuilder to specify images for the button's pressed
      // hovered and default states. We switch to an additional image while the
      // button's callback is "running".
      //
      // This is an example of completely changing the default appearance of a button
      // by specifying images for each state and by turning off the overlays by
      // overlayColor: Colors.transparent. AnimatedContainer takes care of the
      // fade in and out segues between images.
      //
      // This foregroundBuilder function ignores its child parameter. Unfortunately
      // TextButton's child parameter is required, so we still have
      // to provide one.
      TextButton(
        onPressed: () async {
          // This is slightly complicated so that if the user presses the button
          // while the current Future.delayed action is running, the currentAction
          // flag is only reset to null after the _new_ action completes.
          late final Future<void> thisAction;
          thisAction = Future<void>.delayed(const Duration(seconds: 1), () {
            if (currentAction == thisAction) {
              setState(() {
                currentAction = null;
              });
            }
          });
          setState(() {
            currentAction = thisAction;
          });
        },
        style: TextButton.styleFrom(
          overlayColor: Colors.transparent,
          foregroundBuilder:
              (BuildContext context, Set<WidgetState> states, Widget? child) {
                late final ImageProvider image;
                if (currentAction != null) {
                  image = runningImage;
                } else if (states.contains(WidgetState.pressed)) {
                  image = pressedImage;
                } else if (states.contains(WidgetState.hovered)) {
                  image = hoveredImage;
                } else {
                  image = defaultImage;
                }
                return AnimatedContainer(
                  width: 64,
                  height: 64,
                  duration: const Duration(milliseconds: 300),
                  curve: Curves.fastOutSlowIn,
                  decoration: BoxDecoration(
                    image: DecorationImage(image: image, fit: BoxFit.contain),
                  ),
                );
              },
        ),
        child: const Text('This child is not used'),
      ),
    ];

    return Row(
      children: <Widget>[
        // The dark/light and LTR/RTL switches. We use the updateDarkMode function
        // provided by the parent TextButtonExampleApp to rebuild the MaterialApp
        // in the appropriate dark/light ThemeMdoe. The directionality of the rest
        // of the UI is controlled by the Directionality widget below, and the
        // textDirection local state variable.
        TextButtonExampleSwitches(
          darkMode: widget.darkMode,
          updateDarkMode: widget.updateDarkMode,
          textDirection: textDirection,
          updateRTL: (bool value) {
            setState(() {
              textDirection = value ? TextDirection.rtl : TextDirection.ltr;
            });
          },
        ),
        horizontalSpacer,

        Expanded(
          child: Scrollbar(
            controller: scrollController,
            thumbVisibility: true,
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              controller: scrollController,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  Directionality(
                    textDirection: textDirection,
                    child: Column(children: columnOneButtons),
                  ),
                  horizontalSpacer,

                  Directionality(
                    textDirection: textDirection,
                    child: Column(children: columnTwoButtons),
                  ),
                  horizontalSpacer,
                ],
              ),
            ),
          ),
        ),
      ],
    );
  }
}

class TextButtonExampleSwitches extends StatelessWidget {
  const TextButtonExampleSwitches({
    super.key,
    required this.darkMode,
    required this.updateDarkMode,
    required this.textDirection,
    required this.updateRTL,
  });

  final bool darkMode;
  final ValueChanged<bool> updateDarkMode;
  final TextDirection textDirection;
  final ValueChanged<bool> updateRTL;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: IntrinsicWidth(
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  const Expanded(child: Text('Dark Mode')),
                  const SizedBox(width: 4),
                  Switch(value: darkMode, onChanged: updateDarkMode),
                ],
              ),
              const SizedBox(height: 16),
              Row(
                children: <Widget>[
                  const Expanded(child: Text('RTL Text')),
                  const SizedBox(width: 4),
                  Switch(
                    value: textDirection == TextDirection.rtl,
                    onChanged: updateRTL,
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}
