Files
flutter_wisetronic/lib/utils/double_back_to_close_app.dart
2020-12-23 00:43:59 -05:00

98 lines
3.3 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
/// Allows the user to close the app by double tapping the back-button.
///
/// You must specify a [SnackBar], so it can be shown when the user taps the
/// back-button. Notice that the value you set for [SnackBar.duration] is going
/// to be considered to decide whether the snack-bar is currently visible or
/// not.
///
/// Since the back-button is an Android feature, this Widget is going to be
/// nothing but the own [child] if the current platform is anything but Android.
class DoubleBackToCloseApp extends StatefulWidget {
/// The [SnackBar] shown when the user taps the back-button.
final SnackBar snackBar;
/// The widget below this widget in the tree.
final Widget child;
/// Creates a widget that allows the user to close the app by double tapping
/// the back-button.
const DoubleBackToCloseApp({
Key key,
@required this.snackBar,
@required this.child,
}) : assert(snackBar != null),
assert(child != null),
super(key: key);
@override
_DoubleBackToCloseAppState createState() => _DoubleBackToCloseAppState();
}
class _DoubleBackToCloseAppState extends State<DoubleBackToCloseApp> {
/// The last time the user tapped Android's back-button.
DateTime _lastTimeBackButtonWasTapped;
/// Returns whether the current platform is Android.
bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
/// Returns whether the [DoubleBackToCloseApp.snackBar] is currently visible.
///
/// The snack-bar is going to be considered visible if the duration of the
/// snack-bar is greater than the difference from now to the
/// [_lastTimeBackButtonWasTapped].
///
/// This is not quite accurate since the snack-bar could've been dismissed by
/// the user, so this algorithm needs to be improved, as described in #2.
bool get _isSnackBarVisible =>
(_lastTimeBackButtonWasTapped != null) &&
(widget.snackBar.duration >
DateTime.now().difference(_lastTimeBackButtonWasTapped));
/// Returns whether the next back navigation of this route will be handled
/// internally.
///
/// Returns true when there's a widget that inserted an entry into the
/// local-history of the current route, in order to handle pop. This is done
/// by [Drawer], for example, so it can close on pop.
bool get _willHandlePopInternally =>
ModalRoute.of(context).willHandlePopInternally;
@override
Widget build(BuildContext context) {
_ensureThatContextContainsScaffold();
if (_isAndroid) {
return WillPopScope(
onWillPop: _handleWillPop,
child: widget.child,
);
} else {
return widget.child;
}
}
/// Handles [WillPopScope.onWillPop].
Future<bool> _handleWillPop() async {
if (_isSnackBarVisible || _willHandlePopInternally) {
return true;
} else {
_lastTimeBackButtonWasTapped = DateTime.now();
// Scaffold.of(context).showSnackBar(widget.snackBar);
ScaffoldMessenger.of(context).showSnackBar(widget.snackBar);
return false;
}
}
/// Throws a [FlutterError] if this widget was not wrapped in a [Scaffold].
void _ensureThatContextContainsScaffold() {
if (Scaffold.maybeOf(context) == null) {
throw FlutterError(
'`DoubleBackToCloseApp` must be wrapped in a `Scaffold`.',
);
}
}
}