app_lock.dart 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. /// A widget which handles app lifecycle events for showing and hiding a lock screen.
  4. /// This should wrap around a `MyApp` widget (or equivalent).
  5. ///
  6. /// [lockScreen] is a [Widget] which should be a screen for handling login logic and
  7. /// calling `AppLock.of(context).didUnlock();` upon a successful login.
  8. ///
  9. /// [builder] is a [Function] taking an [Object] as its argument and should return a
  10. /// [Widget]. The [Object] argument is provided by the [lockScreen] calling
  11. /// `AppLock.of(context).didUnlock();` with an argument. [Object] can then be injected
  12. /// in to your `MyApp` widget (or equivalent).
  13. ///
  14. /// [enabled] determines wether or not the [lockScreen] should be shown on app launch
  15. /// and subsequent app pauses. This can be changed later on using `AppLock.of(context).enable();`,
  16. /// `AppLock.of(context).disable();` or the convenience method `AppLock.of(context).setEnabled(enabled);`
  17. /// using a bool argument.
  18. ///
  19. /// [backgroundLockLatency] determines how much time is allowed to pass when
  20. /// the app is in the background state before the [lockScreen] widget should be
  21. /// shown upon returning. It defaults to instantly.
  22. class AppLock extends StatefulWidget {
  23. final Widget Function(Object) builder;
  24. final Widget lockScreen;
  25. final bool enabled;
  26. final Duration backgroundLockLatency;
  27. final ThemeData themeData;
  28. const AppLock({
  29. Key key,
  30. @required this.builder,
  31. @required this.lockScreen,
  32. this.enabled = true,
  33. this.backgroundLockLatency = const Duration(seconds: 0),
  34. this.themeData,
  35. }) : super(key: key);
  36. static _AppLockState of(BuildContext context) =>
  37. context.findAncestorStateOfType<_AppLockState>();
  38. @override
  39. _AppLockState createState() => _AppLockState();
  40. }
  41. class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
  42. static final GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
  43. bool _didUnlockForAppLaunch;
  44. bool _isLocked;
  45. bool _enabled;
  46. Timer _backgroundLockLatencyTimer;
  47. @override
  48. void initState() {
  49. WidgetsBinding.instance.addObserver(this);
  50. this._didUnlockForAppLaunch = !this.widget.enabled;
  51. this._isLocked = false;
  52. this._enabled = this.widget.enabled;
  53. super.initState();
  54. }
  55. @override
  56. void didChangeAppLifecycleState(AppLifecycleState state) {
  57. if (!this._enabled) {
  58. return;
  59. }
  60. if (state == AppLifecycleState.paused &&
  61. (!this._isLocked && this._didUnlockForAppLaunch)) {
  62. this._backgroundLockLatencyTimer =
  63. Timer(this.widget.backgroundLockLatency, () => this.showLockScreen());
  64. }
  65. if (state == AppLifecycleState.resumed) {
  66. this._backgroundLockLatencyTimer?.cancel();
  67. }
  68. super.didChangeAppLifecycleState(state);
  69. }
  70. @override
  71. void dispose() {
  72. WidgetsBinding.instance.removeObserver(this);
  73. this._backgroundLockLatencyTimer?.cancel();
  74. super.dispose();
  75. }
  76. @override
  77. Widget build(BuildContext context) {
  78. return MaterialApp(
  79. home: this.widget.enabled ? this._lockScreen : this.widget.builder(null),
  80. navigatorKey: _navigatorKey,
  81. theme: widget.themeData,
  82. routes: {
  83. '/lock-screen': (context) => this._lockScreen,
  84. '/unlocked': (context) =>
  85. this.widget.builder(ModalRoute.of(context).settings.arguments)
  86. },
  87. );
  88. }
  89. Widget get _lockScreen {
  90. return WillPopScope(
  91. child: this.widget.lockScreen,
  92. onWillPop: () => Future.value(false),
  93. );
  94. }
  95. /// Causes `AppLock` to either pop the [lockScreen] if the app is already running
  96. /// or instantiates widget returned from the [builder] method if the app is cold
  97. /// launched.
  98. ///
  99. /// [args] is an optional argument which will get passed to the [builder] method
  100. /// when built. Use this when you want to inject objects created from the
  101. /// [lockScreen] in to the rest of your app so you can better guarantee that some
  102. /// objects, services or databases are already instantiated before using them.
  103. void didUnlock([Object args]) {
  104. if (this._didUnlockForAppLaunch) {
  105. this._didUnlockOnAppPaused();
  106. } else {
  107. this._didUnlockOnAppLaunch(args);
  108. }
  109. }
  110. /// Makes sure that [AppLock] shows the [lockScreen] on subsequent app pauses if
  111. /// [enabled] is true of makes sure it isn't shown on subsequent app pauses if
  112. /// [enabled] is false.
  113. ///
  114. /// This is a convenience method for calling the [enable] or [disable] method based
  115. /// on [enabled].
  116. void setEnabled(bool enabled) {
  117. if (enabled) {
  118. this.enable();
  119. } else {
  120. this.disable();
  121. }
  122. }
  123. /// Makes sure that [AppLock] shows the [lockScreen] on subsequent app pauses.
  124. void enable() {
  125. setState(() {
  126. this._enabled = true;
  127. });
  128. }
  129. /// Makes sure that [AppLock] doesn't show the [lockScreen] on subsequent app pauses.
  130. void disable() {
  131. setState(() {
  132. this._enabled = false;
  133. });
  134. }
  135. /// Manually show the [lockScreen].
  136. Future<void> showLockScreen() {
  137. this._isLocked = true;
  138. return _navigatorKey.currentState.pushNamed('/lock-screen');
  139. }
  140. void _didUnlockOnAppLaunch(Object args) {
  141. this._didUnlockForAppLaunch = true;
  142. _navigatorKey.currentState
  143. .pushReplacementNamed('/unlocked', arguments: args);
  144. }
  145. void _didUnlockOnAppPaused() {
  146. this._isLocked = false;
  147. _navigatorKey.currentState.pop();
  148. }
  149. }