| // Copyright 2019 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'; | |
| typedef AsyncVoidCallback = Future<void> Function(); | |
| /// An [ElevatedButton] whose [onPressed] returns a [Future], and which | |
| /// overlays a progress indicator when the button is pressed until the | |
| /// future completes. | |
| /// | |
| /// Technically this violates the Material design guidelines six ways | |
| /// to Sunday but... | |
| class ProgressButton extends StatefulWidget { | |
| const ProgressButton({super.key, this.child, this.onPressed}); | |
| final Widget? child; | |
| final AsyncVoidCallback? onPressed; | |
| @override | |
| State<ProgressButton> createState() => _ProgressButtonState(); | |
| } | |
| class _ProgressButtonState extends State<ProgressButton> { | |
| bool _busy = false; | |
| void _handlePressed() async { | |
| if (_busy) { | |
| return; | |
| } | |
| setState(() { | |
| _busy = true; | |
| }); | |
| try { | |
| await widget.onPressed!(); | |
| } finally { | |
| if (mounted) { | |
| setState(() { | |
| _busy = false; | |
| }); | |
| } | |
| } | |
| } | |
| static const Widget _progressIndicator = Padding( | |
| padding: EdgeInsets.all(12.0), | |
| child: Center( | |
| child: AspectRatio(aspectRatio: 1.0, child: CircularProgressIndicator()), | |
| ), | |
| ); | |
| @override | |
| Widget build(BuildContext context) { | |
| return Stack( | |
| children: <Widget>[ | |
| ElevatedButton( | |
| onPressed: | |
| _busy // dartfmt will soon require this new formatting | |
| ? null | |
| : widget.onPressed != | |
| null // dartfmt will soon require this new formatting | |
| ? _handlePressed | |
| : null, | |
| child: widget.child, | |
| ), | |
| if (_busy) | |
| const Positioned( | |
| top: 0.0, | |
| left: 0.0, | |
| right: 0.0, | |
| bottom: 0.0, | |
| child: _progressIndicator, | |
| ), | |
| ], | |
| ); | |
| } | |
| } |