lock_screen.dart 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import "dart:io";
  2. import 'package:flutter/material.dart';
  3. import 'package:logging/logging.dart';
  4. import "package:photos/l10n/l10n.dart";
  5. import 'package:photos/ui/common/gradient_button.dart';
  6. import 'package:photos/ui/tools/app_lock.dart';
  7. import 'package:photos/utils/auth_util.dart';
  8. class LockScreen extends StatefulWidget {
  9. const LockScreen({Key? key}) : super(key: key);
  10. @override
  11. State<LockScreen> createState() => _LockScreenState();
  12. }
  13. class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
  14. final _logger = Logger("LockScreen");
  15. bool _isShowingLockScreen = false;
  16. bool _hasPlacedAppInBackground = false;
  17. bool _hasAuthenticationFailed = false;
  18. int? lastAuthenticatingTime;
  19. @override
  20. void initState() {
  21. _logger.info("initiatingState");
  22. super.initState();
  23. WidgetsBinding.instance.addObserver(this);
  24. WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
  25. if (isNonMobileIOSDevice()) {
  26. _logger.info('ignore init for non mobile iOS device');
  27. return;
  28. }
  29. _showLockScreen(source: "postFrameInit");
  30. });
  31. }
  32. @override
  33. Widget build(BuildContext context) {
  34. return Scaffold(
  35. body: Center(
  36. child: Column(
  37. crossAxisAlignment: CrossAxisAlignment.center,
  38. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  39. children: [
  40. Stack(
  41. alignment: Alignment.center,
  42. children: [
  43. Opacity(
  44. opacity: 0.2,
  45. child: Image.asset('assets/loading_photos_background.png'),
  46. ),
  47. SizedBox(
  48. width: 180,
  49. child: GradientButton(
  50. text: context.l10n.unlock,
  51. iconData: Icons.lock_open_outlined,
  52. onTap: () async {
  53. _showLockScreen(source: "tapUnlock");
  54. },
  55. ),
  56. ),
  57. ],
  58. ),
  59. ],
  60. ),
  61. ),
  62. );
  63. }
  64. bool isNonMobileIOSDevice() {
  65. if (Platform.isAndroid) {
  66. return false;
  67. }
  68. var shortestSide = MediaQuery.of(context).size.shortestSide;
  69. return shortestSide > 600 ? true : false;
  70. }
  71. @override
  72. void didChangeAppLifecycleState(AppLifecycleState state) {
  73. _logger.info(state.toString());
  74. if (state == AppLifecycleState.resumed && !_isShowingLockScreen) {
  75. // This is triggered either when the lock screen is dismissed or when
  76. // the app is brought to foreground
  77. _hasPlacedAppInBackground = false;
  78. final bool didAuthInLast5Seconds = lastAuthenticatingTime != null &&
  79. DateTime.now().millisecondsSinceEpoch - lastAuthenticatingTime! <
  80. 5000;
  81. if (!_hasAuthenticationFailed && !didAuthInLast5Seconds) {
  82. // Show the lock screen again only if the app is resuming from the
  83. // background, and not when the lock screen was explicitly dismissed
  84. Future.delayed(
  85. Duration.zero,
  86. () => _showLockScreen(source: "lifeCycle"),
  87. );
  88. } else {
  89. _hasAuthenticationFailed = false; // Reset failure state
  90. }
  91. } else if (state == AppLifecycleState.paused ||
  92. state == AppLifecycleState.inactive) {
  93. // This is triggered either when the lock screen pops up or when
  94. // the app is pushed to background
  95. if (!_isShowingLockScreen) {
  96. _hasPlacedAppInBackground = true;
  97. _hasAuthenticationFailed = false; // reset failure state
  98. }
  99. }
  100. }
  101. @override
  102. void dispose() {
  103. _logger.info('disposing');
  104. WidgetsBinding.instance.removeObserver(this);
  105. super.dispose();
  106. }
  107. Future<void> _showLockScreen({String source = ''}) async {
  108. final int id = DateTime.now().millisecondsSinceEpoch;
  109. _logger.info("Showing lock screen $source $id");
  110. try {
  111. _isShowingLockScreen = true;
  112. final result = await requestAuthentication(
  113. context,
  114. context.l10n.authToViewYourMemories,
  115. );
  116. _logger.finest("LockScreen Result $result $id");
  117. _isShowingLockScreen = false;
  118. if (result) {
  119. lastAuthenticatingTime = DateTime.now().millisecondsSinceEpoch;
  120. AppLock.of(context)!.didUnlock();
  121. } else {
  122. if (!_hasPlacedAppInBackground) {
  123. // Treat this as a failure only if user did not explicitly
  124. // put the app in background
  125. _hasAuthenticationFailed = true;
  126. _logger.info("Authentication failed");
  127. }
  128. }
  129. } catch (e, s) {
  130. _isShowingLockScreen = false;
  131. _logger.severe(e, s);
  132. }
  133. }
  134. }