backup.
This commit is contained in:
98
lib/utils/double_back_to_close_app.dart
Normal file
98
lib/utils/double_back_to_close_app.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
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`.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user