├── .fvmrc ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── flutter_app_lock_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── integration_test │ └── integration_test.dart ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── app │ │ └── app.dart │ ├── main.dart │ └── screens │ │ ├── lock_screen.dart │ │ └── my_home_page.dart ├── pubspec.lock ├── pubspec.yaml └── test_driver │ └── integration_test.dart ├── lib ├── flutter_app_lock.dart └── src │ ├── app_lock.dart │ └── no_animation_page.dart ├── pubspec.yaml └── test └── src └── app_lock_test.dart /.fvmrc: -------------------------------------------------------------------------------- 1 | { 2 | "flutter": "3.24.5" 3 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Please paste the minimum code needed to demonstrate the bug that can be pasted into a single `main.dart` file. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Additional context (optional)** 20 | Add any other context about the problem here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | .vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | 32 | # FVM Version Cache 33 | .fvm/ -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 6928314d505d2bb4777be05e45d7808a5aa91d2a 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 4.2.0+2 - 18th November 2024 2 | 3 | **NOTE:** Use 4.2.0 or 4.2.0+2 only as 4.2.0+1 was mistakenly published from a WIP branch 4 | 5 | - Ability to change the background lock latency from a descendant using `AppLock.of(context).setBackgroundLockLatency(duration);` (closes [pull request #27](https://github.com/tomalabaster/flutter_app_lock/pull/27)) 6 | - **Deprecation:** `enabled` is now deprecated and will be removed in the next major versions, please use `initiallyEnabled` instead (provides clarity based on discussion on [pull request #27](https://github.com/tomalabaster/flutter_app_lock/pull/27)) 7 | - **Deprecation:** `backgroundLockLatency` is now deprecated and will be removed in the next major versions, please use `initalBackgroundLockLatency` instead (provides clarity based on discussion on [pull request #27](https://github.com/tomalabaster/flutter_app_lock/pull/27)) 8 | 9 | Thank you to [@jakobleck](https://github.com/jakobleck) and [@Bptmn](https://github.com/Bptmn) for helping to drive this forward through an initial pull request, suggestions and conversation! 10 | 11 | ## 4.2.0+1 - 18th November 2024 12 | 13 | - Due to an admin error, 4.2.0+1 was meant to only contain documentation updates but instead was published with a WIP rewrite at [this git commit](https://github.com/tomalabaster/flutter_app_lock/tree/8a767af95823ca21e43184bc3544ac37ebfe89aa) 14 | 15 | ## 4.2.0 - 18th November 2024 16 | 17 | - Exact same code as 4.2.0+2 above just without all of the documentation updates 18 | 19 | ## 4.1.1+1 - 30th December 2023 20 | 21 | - Updates to README 22 | 23 | ## 4.1.1 - 30th December 2023 24 | 25 | - Fix for app still locking if the app resumes before the end of the background lock latency 26 | 27 | ## 4.1.0 - 30th December 2023 28 | 29 | - Inactive state! 30 | - When the app becomes inactive (e.g. viewing the device's recent app switcher or notification center) you can now show a custom screen (see [issue #6](https://github.com/tomalabaster/flutter_app_lock/issues/6) for limitations) 31 | - This can be used instead of or along side the existing lock screen mechanism 32 | - **Deprecation:** `lockScreen` is now deprecated and will be removed in the next major version, please use `lockScreenBuilder` instead (closes [issue #23](https://github.com/tomalabaster/flutter_app_lock/issues/23)) 33 | 34 | ## 4.0.1 - 25th December 2023 35 | 36 | - **Breaking change:** requires Flutter 3.16.0 or greater 37 | - **Breaking change:** requires Dart 3.0.0 or greater 38 | - Using `AppLifecycleState.hidden` instead of `AppLifecycleState.paused` 39 | - Package upgrades and deprecation fixes 40 | 41 | ## 4.0.0 - 25th December 2023 42 | 43 | Version `4.0.0` general availabilty, see changes in `4.0.0-dev.0 - 21st November 2022` below. 44 | 45 | ## 4.0.0-dev.0 - 21st November 2022 46 | 47 | **`MaterialApp` is no longer used under the hood!** 48 | 49 | Version `4.0.0` uses a `Navigator` directly instead of a `MaterialApp`. The new required use of `AppLock` allows you to make use of your own `MaterialApp`'s theming. 50 | 51 | Old: 52 | ```dart 53 | void main() { 54 | runApp(AppLock( 55 | builder: (arg) => MyApp(data: arg), 56 | lockScreen: LockScreen(), 57 | )); 58 | } 59 | ``` 60 | 61 | New: 62 | ```dart 63 | void main() { 64 | runApp(MyApp()); 65 | } 66 | 67 | class MyApp extends StatelessWidget { 68 | ... 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | return MaterialApp( 73 | ... 74 | builder: (context, child) => AppLock( 75 | builder: (context, arg) => child!, 76 | lockScreen: LockScreen(), 77 | ), 78 | ... 79 | ); 80 | } 81 | } 82 | ``` 83 | 84 | **Breaking changes:** 85 | - `builder` now requires a callback which receives a `BuildContext` and an `Object?` 86 | - `theme` is no longer available 87 | 88 | **Non-breaking changes:** 89 | - `AppLock` now exposes a `launchArg` property which is an `Object?` 90 | 91 | ## 3.0.0 - 28th October 2022 92 | 93 | Flutter 3 support! 94 | 95 | Flutter 3 was always supported, but the `!` operator has been removed from calls to `WidgetsBinding.instance` which was causing an annoying warning. 96 | 97 | All packages have been upgraded and the example project has also been upgraded. 98 | 99 | ## 2.0.0 - 28th July 2021 100 | 101 | Migrated to null-safety! 102 | 103 | There are now also integration tests in the example project which can be run using `flutter test integration_test/integration_tests.dart`. 104 | 105 | ## 1.5.0 - 1st April 2021 106 | 107 | Added the ability to override the `theme` property of the `MaterialApp` which `AppLock` uses internally. 108 | 109 | ```dart 110 | runApp(AppLock( 111 | ... 112 | theme: ThemeData( 113 | textTheme: TextTheme( 114 | headline1: TextStyle(fontSize: 32), 115 | ), 116 | ), 117 | )); 118 | ``` 119 | 120 | `debugShowCheckedModeBanner` has also been set to false. 121 | 122 | Thank you to [@vishnukvmd](https://github.com/vishnukvmd) and [@dshukertjr](https://github.com/dshukertjr) for contributing these changes! 123 | 124 | ## 1.4.0+1 - 4th Oct 2020 125 | 126 | Minor updates to docs. 127 | 128 | ## 1.4.0 - 4th Oct 2020 129 | 130 | New functionality to specify a period of time between the app going into the background state and when the lock screen should be shown. 131 | 132 | ```dart 133 | runApp(AppLock( 134 | ..., 135 | backgroundLockLatency: const Duration(seconds: 30), 136 | )); 137 | ``` 138 | 139 | This allows the app to go into the background state for the specified duration without causing the lock screen to be shown. 140 | 141 | ## 1.3.1 - 16th May 2020 142 | 143 | `showLockScreen` is now a `Future`. 144 | 145 | ```dart 146 | await AppLock.of(context).showLockScreen(); 147 | 148 | print('Did unlock!'); 149 | ``` 150 | 151 | Thank you to [@rdev-software](https://github.com/rdev-software) for contributing this change! 152 | 153 | ## 1.3.0 - 16th May 2020 154 | 155 | New functionality to show the lock screen on-demand. 156 | 157 | ```dart 158 | AppLock.of(context).showLockScreen(); 159 | ``` 160 | 161 | ## 1.2.0+1 - 21st Feb 2020 162 | 163 | Update to description. 164 | 165 | ## 1.2.0 - 21st Dec 2019 166 | 167 | New functionality to enable or disable the `lockScreen` at launch and on-demand. 168 | 169 | ```dart 170 | runApp(AppLock( 171 | builder: ..., 172 | lockScreen: ..., 173 | enabled: false, 174 | )); 175 | ``` 176 | 177 | ```dart 178 | AppLock.of(context).enable(); 179 | AppLock.of(context).disable(); 180 | ``` 181 | 182 | ## 1.1.0+2 - 21st Dec 2019 183 | 184 | - Removing deprecating `child` method in preference for the `builder` method. 185 | - Updating Flutter version constraints 186 | 187 | ## 1.1.0+1 - 15th Dec 2019 188 | 189 | Deprecating `child` method in preference for the `builder` method - simply a name change. 190 | 191 | ## 1.1.0 - 15th Dec 2019 192 | 193 | **Breaking change** 194 | 195 | An argument can now be passed in to the `AppLock` method `didUnlock` and is accessible through the builder method, `child` - this should be considered a **breaking change** as the builder method, `child` requires a parameter even if null is passed in to `didUnlock`. 196 | 197 | ## 1.0.0 - 15th Dec 2019 198 | 199 | Initial release 200 | 201 | Use `AppLock` to provide lock screen functionality to you Flutter apps. 202 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tom Alabaster 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_app_lock 2 | 3 | A Flutter package for showing a lock screen on app open and app pause. 4 | 5 | If the app is launching, the lock screen is shown first and then the rest of the app is instantiated once a successful unlock has occured. 6 | 7 | If the user is returning to the app after it has already launched, the lock screen is shown on top of your app and can't be dismissed until another successful unlock. 8 | 9 | ## Getting Started 10 | 11 | In your flutter project add the dependency: 12 | 13 | ```yaml 14 | dependencies: 15 | ... 16 | flutter_app_lock: ^4.2.0+2 17 | ``` 18 | 19 | For help getting started with Flutter, view the online documentation. 20 | 21 | ## Usage example 22 | 23 | ```dart 24 | void main() { 25 | runApp(MyApp()); 26 | } 27 | 28 | class MyApp extends StatelessWidget { 29 | ... 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return MaterialApp( 34 | ..., 35 | builder: (context, child) => AppLock( 36 | builder: (context, arg) => child!, 37 | lockScreenBuilder: (context) => LockScreen(), 38 | ), 39 | ); 40 | } 41 | } 42 | ``` 43 | 44 | Simply use your `MaterialApp`'s `builder` property to return an `AppLock` widget, passing `child` to its own `builder` property. 45 | 46 | **Note:** While I mention `MaterialApp` throughout this README, this is also compatible with `CupertinoApp` and `WidgetsApp` widgets. 47 | 48 | `LockScreen` is your own widget implementing your own login logic which should call the following once a successful login has occured. 49 | 50 | ```dart 51 | AppLock.of(context)!.didUnlock(); 52 | ``` 53 | 54 | This will now make the `MaterialApp`'s `Navigator` or `Router` the top most widget. 55 | 56 | ### Theme 57 | 58 | Because `AppLock` is expected to be returned within your `MaterialApp`'s `builder` method, your existing `theme` and `theme` variants will style the widget returned from `lockScreenBuilder` automatically. 59 | 60 | ```dart 61 | MaterialApp( 62 | ..., 63 | theme: ThemeData( 64 | ..., 65 | ), 66 | builder: (context, child) => AppLock( 67 | builder: (context, arg) => child!, 68 | lockScreenBuilder: (context) => LockScreen(), 69 | ), 70 | ); 71 | ``` 72 | 73 | ## Enabling and disabling 74 | 75 | It is possible to enable and disable the showing of the widget returned from `lockScreenBuilder` on app launch and on-demand. 76 | 77 | ```dart 78 | MaterialApp( 79 | ..., 80 | builder: (context, child) => AppLock( 81 | builder: (context, arg) => child!, 82 | lockScreenBuilder: (context) => LockScreen(), 83 | initiallyEnabled: false, 84 | ), 85 | ); 86 | ``` 87 | 88 | The above will cause `child` (your `MaterialApp`'s `Navigator` or `Router`) to be built instantly and the widget returned from `lockScreenBuilder` will never be shown. The default for `initiallyEnabled` is `true`. 89 | 90 | You can then enable the showing of the widget returned from `lockScreenBuilder` later on by doing: 91 | 92 | ```dart 93 | AppLock.of(context)!.enable(); 94 | ``` 95 | 96 | This will now cause the widget returned from `lockScreenBuilder` to be shown on app pauses. 97 | 98 | If you wanted to disable the showing of the widget returned from `lockScreenBuilder` again you can simply do: 99 | 100 | ```dart 101 | AppLock.of(context)!.disable(); 102 | ``` 103 | 104 | There is also a convenience method: 105 | 106 | ```dart 107 | AppLock.of(context)!.setEnabled(true); 108 | AppLock.of(context)!.setEnabled(false); 109 | ``` 110 | 111 | ## Passing arguments 112 | 113 | In some scenarios, it might be appropriate to unlock a database or create some other objects from the widget returned from `lockScreenBuilder` and then expose them to your app further down the tree, so you can better guarantee that services are instantiated or databases are opened/unlocked. 114 | 115 | You can do this by passing in an argument to the `didUnlock` method on `AppLock`: 116 | 117 | ```dart 118 | var database = await openDatabase(...); 119 | 120 | AppLock.of(context)!.didUnlock(database); 121 | ``` 122 | 123 | This object is then available as part of the `AppLock` builder method, `builder`: 124 | 125 | ```dart 126 | MaterialApp( 127 | ..., 128 | builder: (context, child) => AppLock( 129 | builder: (context, arg) => child!, // arg is the `database` object passed in to `didUnlock` 130 | lockScreenBuilder: (context) => LockScreen(), 131 | ), 132 | ); 133 | ``` 134 | 135 | It is also available by calling `AppLock.of(context)!.launchArg`. 136 | 137 | ## Manually showing the lock screen 138 | 139 | In some scenarios, you might want to manually trigger the lock screen to show. 140 | 141 | You can do this by calling: 142 | 143 | ```dart 144 | AppLock.of(context)!.showLockScreen(); 145 | ``` 146 | 147 | If you want to wait until the user has successfully unlocked again, `showLockScreen` returns a `Future` so you can `await` this method call. 148 | 149 | ```dart 150 | await AppLock.of(context)!.showLockScreen(); 151 | 152 | print('Did unlock!'); 153 | ``` 154 | 155 | ## Background lock latency 156 | 157 | It might be useful for apps to not require the lock screen to be shown immediately after entering the background state. You can now specify how long the app is allowed to be in the background before requiring the lock screen to be shown: 158 | 159 | ```dart 160 | MaterialApp( 161 | ..., 162 | builder: (context, child) => AppLock( 163 | ..., 164 | initialBackgroundLockLatency: const Duration(seconds: 30), 165 | ), 166 | ); 167 | ``` 168 | 169 | The above example allows the app to be in the background for up to 30 seconds without requiring the lock screen to be shown. 170 | 171 | It's also possible to change this at runtime using the method: 172 | 173 | ```dart 174 | AppLock.of(context)!.setBackgroundLockLatency(const Duration(seconds: 5)); 175 | ``` 176 | 177 | ## Inactive statuses (e.g. app switcher) 178 | 179 | When the app becomes "inactive", for example by launching the device app switcher or device notification center, you can now configure a widget to be shown when this occurs: 180 | 181 | ```dart 182 | MaterialApp( 183 | ..., 184 | builder: (context, child) => AppLock( 185 | ..., 186 | inactiveBuilder: (context) => InactiveScreen(), 187 | ), 188 | ); 189 | ``` 190 | 191 | `InactiveScreen` is your own widget implementing your own requirements for a screen shown while the app is inactive. 192 | 193 | [There are limitations to this as noted on issue #6 on GitHub](https://github.com/tomalabaster/flutter_app_lock/issues/6#issuecomment-1872616800). 194 | 195 | ## Tests 196 | 197 | Integration tests have been introduced in the example project and were used to confirm the behaviour hasn't changed since the move to null-safety. 198 | 199 | They can be run by running `flutter test integration_test/integration_tests.dart` in a terminal. 200 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 17 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 18 | - platform: android 19 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 20 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 21 | - platform: ios 22 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 23 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_app_lock_example 2 | 3 | ```dart 4 | void main() { 5 | runApp(AppLock( 6 | builder: (args) => MyApp( 7 | data: args, 8 | ), 9 | lockScreenBuilder: (context) => LockScreen(), 10 | initiallyEnabled: false, // default is true, first app launches you probably want false 11 | initialBackgroundLockLatency: const Duration(seconds: 30), // default is 0 seconds (immediately) 12 | inactiveBuilder: (context) => InactiveScreen(), 13 | )); 14 | } 15 | ``` 16 | 17 | ```dart 18 | class _MyHomePageState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBar( 23 | title: Text(widget.title), 24 | ), 25 | body: Column( 26 | children: [ 27 | ElevatedButton( 28 | child: const Text('Set app lock enabled'), 29 | onPressed: () => AppLock.of(context)!.enable(), 30 | ), 31 | ElevatedButton( 32 | child: const Text('Set app lock disabled'), 33 | onPressed: () => AppLock.of(context)!.disable(), 34 | ), 35 | ElevatedButton( 36 | child: const Text('Manually show lock screen'), 37 | onPressed: () => AppLock.of(context)!.showLockScreen(), 38 | ), 39 | ElevatedButton( 40 | child: const Text('Manually show lock screen (awaiting)'), 41 | onPressed: () async { 42 | await AppLock.of(context)!.showLockScreen(); 43 | 44 | print('Did unlock!'); 45 | }, 46 | ), 47 | ElevatedButton( 48 | child: const Text('Changing background lock latency'), 49 | onPressed: () => AppLock.of(context)! 50 | .setBackgroundLockLatency(const Duration(seconds: 5)), 51 | ), 52 | ], 53 | ), 54 | ); 55 | } 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | android { 9 | namespace = "com.example.flutter_app_lock_example" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_1_8 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.example.flutter_app_lock_example" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/flutter_app_lock_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_app_lock_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.1.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /example/integration_test/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app_lock/flutter_app_lock.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import 'package:flutter_app_lock_example/main.dart' as app; 6 | import 'package:integration_test/integration_test.dart'; 7 | 8 | final myHomePage = find.byKey(const Key('MyHomePage')); 9 | final lockScreen = find.byKey(const Key('LockScreen')); 10 | final inactiveScreen = find.byKey(const Key('InactiveScreen')); 11 | final showButton = find.byKey(const Key('ShowButton')); 12 | final passwordField = find.byKey(const Key('PasswordField')); 13 | final unlockButton = find.byKey(const Key('UnlockButton')); 14 | final enableButton = find.byKey(const Key('EnableButton')); 15 | final disableButton = find.byKey(const Key('DisableButton')); 16 | 17 | Future enterCorrectPassword(WidgetTester tester) async { 18 | await tester.pumpAndSettle(); 19 | await tester.enterText(passwordField, '0000'); 20 | await tester.tap(unlockButton); 21 | await tester.pumpAndSettle(); 22 | } 23 | 24 | Future enterIncorrectPassword(WidgetTester tester) async { 25 | await tester.pumpAndSettle(); 26 | await tester.enterText(passwordField, 'incorrect password'); 27 | await tester.tap(unlockButton); 28 | await tester.pumpAndSettle(); 29 | } 30 | 31 | Future enableAfterLaunch(WidgetTester tester) async { 32 | await tester.pumpAndSettle(); 33 | await tester.tap(enableButton); 34 | await tester.pumpAndSettle(); 35 | } 36 | 37 | Future changeBackgroundLockLatencyAfterLaunch( 38 | WidgetTester tester, Duration duration) async { 39 | await tester.pumpAndSettle(); 40 | tester 41 | .state(find.byType(AppLock)) 42 | .setBackgroundLockLatency(duration); 43 | await tester.pumpAndSettle(); 44 | } 45 | 46 | Future disableAfterLaunch(WidgetTester tester) async { 47 | await tester.pumpAndSettle(); 48 | await tester.tap(disableButton); 49 | await tester.pumpAndSettle(); 50 | } 51 | 52 | Future enterBackgroundForDuration( 53 | WidgetTester tester, Duration duration) async { 54 | tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.hidden); 55 | 56 | if (tester.binding is IntegrationTestWidgetsFlutterBinding) { 57 | await Future.delayed(duration); 58 | } else { 59 | await tester.pumpAndSettle(duration); 60 | } 61 | 62 | tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); 63 | 64 | await tester.pumpAndSettle(); 65 | } 66 | 67 | Future becomeInactiveForDuration( 68 | WidgetTester tester, Duration duration) async { 69 | tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive); 70 | 71 | if (tester.binding is IntegrationTestWidgetsFlutterBinding) { 72 | await Future.delayed(duration); 73 | } else { 74 | await tester.pumpAndSettle(duration); 75 | } 76 | 77 | await tester.pumpAndSettle(); 78 | } 79 | 80 | Future becomeResumed(WidgetTester tester) async { 81 | tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); 82 | 83 | await tester.pumpAndSettle(); 84 | } 85 | 86 | void main() { 87 | group('Given an active lock screen', () { 88 | group('When entering a correct password', () { 89 | testWidgets('The home screen is visible', (WidgetTester tester) async { 90 | app.main(initiallyEnabled: true); 91 | 92 | await enterCorrectPassword(tester); 93 | 94 | expect(myHomePage, findsOneWidget); 95 | }); 96 | 97 | testWidgets('The lock screen is no longer visible', 98 | (WidgetTester tester) async { 99 | app.main(initiallyEnabled: true); 100 | 101 | await enterCorrectPassword(tester); 102 | 103 | expect(lockScreen, findsNothing); 104 | }); 105 | 106 | testWidgets('Some data is made available to the rest of the app', 107 | (WidgetTester tester) async { 108 | app.main(initiallyEnabled: true); 109 | 110 | await enterCorrectPassword(tester); 111 | 112 | expect(find.textContaining('some data'), findsOneWidget); 113 | }); 114 | }); 115 | 116 | group('When entering an incorrect password', () { 117 | testWidgets('The home screen is still not visible', 118 | (WidgetTester tester) async { 119 | app.main(initiallyEnabled: true); 120 | 121 | await enterIncorrectPassword(tester); 122 | 123 | expect(myHomePage, findsNothing); 124 | }); 125 | 126 | testWidgets('The lock screen remains visible', 127 | (WidgetTester tester) async { 128 | app.main(initiallyEnabled: true); 129 | 130 | await enterIncorrectPassword(tester); 131 | 132 | expect(lockScreen, findsOneWidget); 133 | }); 134 | }); 135 | }); 136 | 137 | group('Given an app with AppLock disabled', () { 138 | group('When the app is launched', () { 139 | testWidgets('The home screen is visible', (WidgetTester tester) async { 140 | app.main(initiallyEnabled: false); 141 | 142 | await tester.pumpAndSettle(); 143 | 144 | expect(myHomePage, findsOneWidget); 145 | }); 146 | 147 | testWidgets('The lock screen is not visible', 148 | (WidgetTester tester) async { 149 | app.main(initiallyEnabled: false); 150 | 151 | await tester.pumpAndSettle(); 152 | 153 | expect(lockScreen, findsNothing); 154 | }); 155 | }); 156 | 157 | group('When enabling it after launch', () { 158 | group( 159 | 'And the app has been in the background for longer than the specified duration', 160 | () { 161 | testWidgets('The lock screen should be shown', 162 | (WidgetTester tester) async { 163 | app.main( 164 | initiallyEnabled: false, 165 | initialBackgroundLockLatency: const Duration(seconds: 1)); 166 | 167 | await enableAfterLaunch(tester); 168 | await enterBackgroundForDuration(tester, const Duration(seconds: 2)); 169 | 170 | expect(lockScreen, findsOneWidget); 171 | }); 172 | }); 173 | 174 | group('And the app becomes inactive', () { 175 | group('And there is an inactive builder set', () { 176 | testWidgets('The widget from the inactive builder should be shown', 177 | (widgetTester) async { 178 | app.main( 179 | initiallyEnabled: false, 180 | initialBackgroundLockLatency: const Duration(seconds: 2)); 181 | 182 | await enableAfterLaunch(widgetTester); 183 | await becomeInactiveForDuration( 184 | widgetTester, const Duration(seconds: 1)); 185 | 186 | expect(inactiveScreen, findsOne); 187 | }); 188 | 189 | testWidgets('The lock screen should not be shown', 190 | (widgetTester) async { 191 | app.main( 192 | initiallyEnabled: false, 193 | initialBackgroundLockLatency: const Duration(seconds: 2)); 194 | 195 | await enableAfterLaunch(widgetTester); 196 | await becomeInactiveForDuration( 197 | widgetTester, const Duration(seconds: 1)); 198 | 199 | expect(lockScreen, findsNothing); 200 | }); 201 | }); 202 | }); 203 | 204 | group( 205 | 'And the background lock latency is changed at runtime and the app has been in the background for longer than the new specified duration', 206 | () { 207 | testWidgets( 208 | 'The lock screen should be shown if inactive for longer than the new background lock latency', 209 | (widgetTester) async { 210 | app.main( 211 | initiallyEnabled: false, 212 | initialBackgroundLockLatency: Duration.zero); 213 | 214 | await enableAfterLaunch(widgetTester); 215 | await changeBackgroundLockLatencyAfterLaunch( 216 | widgetTester, const Duration(seconds: 5)); 217 | await enterBackgroundForDuration( 218 | widgetTester, const Duration(seconds: 6)); 219 | 220 | expect(lockScreen, findsOneWidget); 221 | }); 222 | }); 223 | 224 | group( 225 | 'And the background lock latency is changed at runtime and the app has been in the background for less than the new specified duration', 226 | () { 227 | testWidgets( 228 | 'The lock screen should not be shown if inactive for less than the new background lock latency', 229 | (widgetTester) async { 230 | app.main( 231 | initiallyEnabled: false, 232 | initialBackgroundLockLatency: Duration.zero); 233 | 234 | await enableAfterLaunch(widgetTester); 235 | await changeBackgroundLockLatencyAfterLaunch( 236 | widgetTester, const Duration(seconds: 5)); 237 | await enterBackgroundForDuration( 238 | widgetTester, const Duration(seconds: 2)); 239 | 240 | expect(lockScreen, findsNothing); 241 | }); 242 | }); 243 | }); 244 | 245 | group('When asked to show', () { 246 | testWidgets('The lock screen is visible', (WidgetTester tester) async { 247 | app.main(initiallyEnabled: false); 248 | 249 | await tester.pumpAndSettle(); 250 | await tester.tap(showButton); 251 | await tester.pumpAndSettle(); 252 | 253 | expect(lockScreen, findsOneWidget); 254 | }); 255 | }); 256 | 257 | group('When the app becomes inactive', () { 258 | group('And there is an inactive builder set', () { 259 | testWidgets('The widget from the inactive builder should not be shown', 260 | (widgetTester) async { 261 | app.main( 262 | initiallyEnabled: false, 263 | initialBackgroundLockLatency: const Duration(seconds: 2)); 264 | 265 | await becomeInactiveForDuration( 266 | widgetTester, const Duration(seconds: 1)); 267 | 268 | expect(inactiveScreen, findsNothing); 269 | }); 270 | 271 | testWidgets('The lock screen should not be shown', 272 | (widgetTester) async { 273 | app.main( 274 | initiallyEnabled: false, 275 | initialBackgroundLockLatency: const Duration(seconds: 2)); 276 | 277 | await becomeInactiveForDuration( 278 | widgetTester, const Duration(seconds: 1)); 279 | 280 | expect(lockScreen, findsNothing); 281 | }); 282 | }); 283 | }); 284 | }); 285 | 286 | group('Given an app with AppLock enabled', () { 287 | group('When the app is launched', () { 288 | testWidgets('The home screen is not visible', 289 | (WidgetTester tester) async { 290 | app.main(initiallyEnabled: true); 291 | 292 | await tester.pumpAndSettle(); 293 | 294 | expect(myHomePage, findsNothing); 295 | }); 296 | 297 | testWidgets('The lock screen is visible', (WidgetTester tester) async { 298 | app.main(initiallyEnabled: true); 299 | 300 | await tester.pumpAndSettle(); 301 | 302 | expect(lockScreen, findsOneWidget); 303 | }); 304 | }); 305 | 306 | group('When disabling it after launch', () { 307 | testWidgets( 308 | 'The lock screen isn\'t shown when the app has been in background for longer than the specified duration', 309 | (WidgetTester tester) async { 310 | app.main( 311 | initiallyEnabled: true, 312 | initialBackgroundLockLatency: const Duration(seconds: 1)); 313 | 314 | await enterCorrectPassword(tester); 315 | await disableAfterLaunch(tester); 316 | await enterBackgroundForDuration(tester, const Duration(seconds: 2)); 317 | 318 | expect(lockScreen, findsNothing); 319 | }); 320 | }); 321 | 322 | group( 323 | 'When the app has been in the background for less than the specified duration', 324 | () { 325 | testWidgets('The lock screen is not visible', 326 | (WidgetTester tester) async { 327 | app.main( 328 | initiallyEnabled: false, 329 | initialBackgroundLockLatency: const Duration(seconds: 2)); 330 | 331 | await enableAfterLaunch(tester); 332 | await enterBackgroundForDuration(tester, const Duration(seconds: 1)); 333 | 334 | expect(lockScreen, findsNothing); 335 | }); 336 | }); 337 | 338 | group( 339 | 'When the app has been in the background for longer than the specified duration', 340 | () { 341 | testWidgets('The lock screen is visible', (WidgetTester tester) async { 342 | app.main( 343 | initiallyEnabled: false, 344 | initialBackgroundLockLatency: const Duration(seconds: 1)); 345 | 346 | await enableAfterLaunch(tester); 347 | await enterBackgroundForDuration(tester, const Duration(seconds: 2)); 348 | 349 | expect(lockScreen, findsOneWidget); 350 | }); 351 | }); 352 | 353 | group('When the app becomes inactive', () { 354 | group('And there is an inactive builder set', () { 355 | testWidgets('The widget from the inactive builder should not be shown', 356 | (widgetTester) async { 357 | app.main( 358 | initiallyEnabled: true, 359 | initialBackgroundLockLatency: const Duration(seconds: 2)); 360 | 361 | await becomeInactiveForDuration( 362 | widgetTester, const Duration(seconds: 1)); 363 | 364 | expect(inactiveScreen, findsNothing); 365 | }); 366 | 367 | testWidgets('The lock screen should still be shown', 368 | (widgetTester) async { 369 | app.main( 370 | initiallyEnabled: true, 371 | initialBackgroundLockLatency: const Duration(seconds: 2)); 372 | 373 | await becomeInactiveForDuration( 374 | widgetTester, const Duration(seconds: 1)); 375 | 376 | expect(lockScreen, findsOneWidget); 377 | }); 378 | }); 379 | }); 380 | }); 381 | } 382 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - integration_test (0.0.1): 4 | - Flutter 5 | 6 | DEPENDENCIES: 7 | - Flutter (from `Flutter`) 8 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 9 | 10 | EXTERNAL SOURCES: 11 | Flutter: 12 | :path: Flutter 13 | integration_test: 14 | :path: ".symlinks/plugins/integration_test/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 18 | integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 19 | 20 | PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 21 | 22 | COCOAPODS: 1.15.2 23 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 116A9E18FE17165A29BD59F9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B9B904614390E064D33269B /* Pods_Runner.framework */; }; 11 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 09FDCAFCE8922B326D78D16F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 34 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 35 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 38 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 40 | 7B9B904614390E064D33269B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 42 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | B24079BC841DF677BF2065E5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 49 | B9884B7A38183AA2C8759A89 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 116A9E18FE17165A29BD59F9 /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 4560343548810A3FD3909F26 /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 7B9B904614390E064D33269B /* Pods_Runner.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | 73144A6B6BC85F597990386A /* Pods */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | B24079BC841DF677BF2065E5 /* Pods-Runner.debug.xcconfig */, 76 | B9884B7A38183AA2C8759A89 /* Pods-Runner.release.xcconfig */, 77 | 09FDCAFCE8922B326D78D16F /* Pods-Runner.profile.xcconfig */, 78 | ); 79 | path = Pods; 80 | sourceTree = ""; 81 | }; 82 | 9740EEB11CF90186004384FC /* Flutter */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 86 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 87 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 88 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 89 | ); 90 | name = Flutter; 91 | sourceTree = ""; 92 | }; 93 | 97C146E51CF9000F007C117D = { 94 | isa = PBXGroup; 95 | children = ( 96 | 9740EEB11CF90186004384FC /* Flutter */, 97 | 97C146F01CF9000F007C117D /* Runner */, 98 | 97C146EF1CF9000F007C117D /* Products */, 99 | 73144A6B6BC85F597990386A /* Pods */, 100 | 4560343548810A3FD3909F26 /* Frameworks */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 97C146EF1CF9000F007C117D /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 97C146EE1CF9000F007C117D /* Runner.app */, 108 | ); 109 | name = Products; 110 | sourceTree = ""; 111 | }; 112 | 97C146F01CF9000F007C117D /* Runner */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 116 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 117 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 118 | 97C147021CF9000F007C117D /* Info.plist */, 119 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 120 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 121 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 122 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 123 | ); 124 | path = Runner; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | 97C146ED1CF9000F007C117D /* Runner */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 133 | buildPhases = ( 134 | 6010AE8BFC4D8AA5F8934A3A /* [CP] Check Pods Manifest.lock */, 135 | 9740EEB61CF901F6004384FC /* Run Script */, 136 | 97C146EA1CF9000F007C117D /* Sources */, 137 | 97C146EB1CF9000F007C117D /* Frameworks */, 138 | 97C146EC1CF9000F007C117D /* Resources */, 139 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 140 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 141 | 32FE320BCF30CC545D5591C3 /* [CP] Embed Pods Frameworks */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = Runner; 148 | productName = Runner; 149 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 97C146E61CF9000F007C117D /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | BuildIndependentTargetsInParallel = YES; 159 | LastUpgradeCheck = 1510; 160 | ORGANIZATIONNAME = ""; 161 | TargetAttributes = { 162 | 97C146ED1CF9000F007C117D = { 163 | CreatedOnToolsVersion = 7.3.1; 164 | LastSwiftMigration = 1100; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 169 | compatibilityVersion = "Xcode 9.3"; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 97C146E51CF9000F007C117D; 177 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 97C146ED1CF9000F007C117D /* Runner */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | 97C146EC1CF9000F007C117D /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 192 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 193 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 194 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXShellScriptBuildPhase section */ 201 | 32FE320BCF30CC545D5591C3 /* [CP] Embed Pods Frameworks */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputFileListPaths = ( 207 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 208 | ); 209 | name = "[CP] Embed Pods Frameworks"; 210 | outputFileListPaths = ( 211 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | shellPath = /bin/sh; 215 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 216 | showEnvVarsInLog = 0; 217 | }; 218 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 219 | isa = PBXShellScriptBuildPhase; 220 | alwaysOutOfDate = 1; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | ); 224 | inputPaths = ( 225 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 226 | ); 227 | name = "Thin Binary"; 228 | outputPaths = ( 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | shellPath = /bin/sh; 232 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 233 | }; 234 | 6010AE8BFC4D8AA5F8934A3A /* [CP] Check Pods Manifest.lock */ = { 235 | isa = PBXShellScriptBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | ); 239 | inputFileListPaths = ( 240 | ); 241 | inputPaths = ( 242 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 243 | "${PODS_ROOT}/Manifest.lock", 244 | ); 245 | name = "[CP] Check Pods Manifest.lock"; 246 | outputFileListPaths = ( 247 | ); 248 | outputPaths = ( 249 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | shellPath = /bin/sh; 253 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 254 | showEnvVarsInLog = 0; 255 | }; 256 | 9740EEB61CF901F6004384FC /* Run Script */ = { 257 | isa = PBXShellScriptBuildPhase; 258 | alwaysOutOfDate = 1; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | ); 262 | inputPaths = ( 263 | ); 264 | name = "Run Script"; 265 | outputPaths = ( 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | shellPath = /bin/sh; 269 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 270 | }; 271 | /* End PBXShellScriptBuildPhase section */ 272 | 273 | /* Begin PBXSourcesBuildPhase section */ 274 | 97C146EA1CF9000F007C117D /* Sources */ = { 275 | isa = PBXSourcesBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 279 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXSourcesBuildPhase section */ 284 | 285 | /* Begin PBXVariantGroup section */ 286 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 287 | isa = PBXVariantGroup; 288 | children = ( 289 | 97C146FB1CF9000F007C117D /* Base */, 290 | ); 291 | name = Main.storyboard; 292 | sourceTree = ""; 293 | }; 294 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 295 | isa = PBXVariantGroup; 296 | children = ( 297 | 97C147001CF9000F007C117D /* Base */, 298 | ); 299 | name = LaunchScreen.storyboard; 300 | sourceTree = ""; 301 | }; 302 | /* End PBXVariantGroup section */ 303 | 304 | /* Begin XCBuildConfiguration section */ 305 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 310 | CLANG_ANALYZER_NONNULL = YES; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 316 | CLANG_WARN_BOOL_CONVERSION = YES; 317 | CLANG_WARN_COMMA = YES; 318 | CLANG_WARN_CONSTANT_CONVERSION = YES; 319 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INFINITE_RECURSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 327 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 330 | CLANG_WARN_STRICT_PROTOTYPES = YES; 331 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 332 | CLANG_WARN_UNREACHABLE_CODE = YES; 333 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 334 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 335 | COPY_PHASE_STRIP = NO; 336 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 337 | ENABLE_NS_ASSERTIONS = NO; 338 | ENABLE_STRICT_OBJC_MSGSEND = YES; 339 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 340 | GCC_C_LANGUAGE_STANDARD = gnu99; 341 | GCC_NO_COMMON_BLOCKS = YES; 342 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 343 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 344 | GCC_WARN_UNDECLARED_SELECTOR = YES; 345 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 346 | GCC_WARN_UNUSED_FUNCTION = YES; 347 | GCC_WARN_UNUSED_VARIABLE = YES; 348 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 349 | MTL_ENABLE_DEBUG_INFO = NO; 350 | SDKROOT = iphoneos; 351 | SUPPORTED_PLATFORMS = iphoneos; 352 | TARGETED_DEVICE_FAMILY = "1,2"; 353 | VALIDATE_PRODUCT = YES; 354 | }; 355 | name = Profile; 356 | }; 357 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 358 | isa = XCBuildConfiguration; 359 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 360 | buildSettings = { 361 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 362 | CLANG_ENABLE_MODULES = YES; 363 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 364 | DEVELOPMENT_TEAM = 6FW572AX8T; 365 | ENABLE_BITCODE = NO; 366 | INFOPLIST_FILE = Runner/Info.plist; 367 | LD_RUNPATH_SEARCH_PATHS = ( 368 | "$(inherited)", 369 | "@executable_path/Frameworks", 370 | ); 371 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 372 | PRODUCT_NAME = "$(TARGET_NAME)"; 373 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 374 | SWIFT_VERSION = 5.0; 375 | VERSIONING_SYSTEM = "apple-generic"; 376 | }; 377 | name = Profile; 378 | }; 379 | 97C147031CF9000F007C117D /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ALWAYS_SEARCH_USER_PATHS = NO; 383 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 384 | CLANG_ANALYZER_NONNULL = YES; 385 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 386 | CLANG_CXX_LIBRARY = "libc++"; 387 | CLANG_ENABLE_MODULES = YES; 388 | CLANG_ENABLE_OBJC_ARC = YES; 389 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 390 | CLANG_WARN_BOOL_CONVERSION = YES; 391 | CLANG_WARN_COMMA = YES; 392 | CLANG_WARN_CONSTANT_CONVERSION = YES; 393 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 394 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INFINITE_RECURSION = YES; 398 | CLANG_WARN_INT_CONVERSION = YES; 399 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 400 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 401 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 403 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 404 | CLANG_WARN_STRICT_PROTOTYPES = YES; 405 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 406 | CLANG_WARN_UNREACHABLE_CODE = YES; 407 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 408 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 409 | COPY_PHASE_STRIP = NO; 410 | DEBUG_INFORMATION_FORMAT = dwarf; 411 | ENABLE_STRICT_OBJC_MSGSEND = YES; 412 | ENABLE_TESTABILITY = YES; 413 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 414 | GCC_C_LANGUAGE_STANDARD = gnu99; 415 | GCC_DYNAMIC_NO_PIC = NO; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_OPTIMIZATION_LEVEL = 0; 418 | GCC_PREPROCESSOR_DEFINITIONS = ( 419 | "DEBUG=1", 420 | "$(inherited)", 421 | ); 422 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 423 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 424 | GCC_WARN_UNDECLARED_SELECTOR = YES; 425 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 426 | GCC_WARN_UNUSED_FUNCTION = YES; 427 | GCC_WARN_UNUSED_VARIABLE = YES; 428 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 429 | MTL_ENABLE_DEBUG_INFO = YES; 430 | ONLY_ACTIVE_ARCH = YES; 431 | SDKROOT = iphoneos; 432 | TARGETED_DEVICE_FAMILY = "1,2"; 433 | }; 434 | name = Debug; 435 | }; 436 | 97C147041CF9000F007C117D /* Release */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | ALWAYS_SEARCH_USER_PATHS = NO; 440 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 441 | CLANG_ANALYZER_NONNULL = YES; 442 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 443 | CLANG_CXX_LIBRARY = "libc++"; 444 | CLANG_ENABLE_MODULES = YES; 445 | CLANG_ENABLE_OBJC_ARC = YES; 446 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 447 | CLANG_WARN_BOOL_CONVERSION = YES; 448 | CLANG_WARN_COMMA = YES; 449 | CLANG_WARN_CONSTANT_CONVERSION = YES; 450 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 451 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 452 | CLANG_WARN_EMPTY_BODY = YES; 453 | CLANG_WARN_ENUM_CONVERSION = YES; 454 | CLANG_WARN_INFINITE_RECURSION = YES; 455 | CLANG_WARN_INT_CONVERSION = YES; 456 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 457 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 458 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 459 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 460 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 461 | CLANG_WARN_STRICT_PROTOTYPES = YES; 462 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 463 | CLANG_WARN_UNREACHABLE_CODE = YES; 464 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 465 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 466 | COPY_PHASE_STRIP = NO; 467 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 468 | ENABLE_NS_ASSERTIONS = NO; 469 | ENABLE_STRICT_OBJC_MSGSEND = YES; 470 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 471 | GCC_C_LANGUAGE_STANDARD = gnu99; 472 | GCC_NO_COMMON_BLOCKS = YES; 473 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 474 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 475 | GCC_WARN_UNDECLARED_SELECTOR = YES; 476 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 477 | GCC_WARN_UNUSED_FUNCTION = YES; 478 | GCC_WARN_UNUSED_VARIABLE = YES; 479 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 480 | MTL_ENABLE_DEBUG_INFO = NO; 481 | SDKROOT = iphoneos; 482 | SUPPORTED_PLATFORMS = iphoneos; 483 | SWIFT_COMPILATION_MODE = wholemodule; 484 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 485 | TARGETED_DEVICE_FAMILY = "1,2"; 486 | VALIDATE_PRODUCT = YES; 487 | }; 488 | name = Release; 489 | }; 490 | 97C147061CF9000F007C117D /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 493 | buildSettings = { 494 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 495 | CLANG_ENABLE_MODULES = YES; 496 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 497 | DEVELOPMENT_TEAM = 6FW572AX8T; 498 | ENABLE_BITCODE = NO; 499 | INFOPLIST_FILE = Runner/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = ( 501 | "$(inherited)", 502 | "@executable_path/Frameworks", 503 | ); 504 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 505 | PRODUCT_NAME = "$(TARGET_NAME)"; 506 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 507 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 508 | SWIFT_VERSION = 5.0; 509 | VERSIONING_SYSTEM = "apple-generic"; 510 | }; 511 | name = Debug; 512 | }; 513 | 97C147071CF9000F007C117D /* Release */ = { 514 | isa = XCBuildConfiguration; 515 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 516 | buildSettings = { 517 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 518 | CLANG_ENABLE_MODULES = YES; 519 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 520 | DEVELOPMENT_TEAM = 6FW572AX8T; 521 | ENABLE_BITCODE = NO; 522 | INFOPLIST_FILE = Runner/Info.plist; 523 | LD_RUNPATH_SEARCH_PATHS = ( 524 | "$(inherited)", 525 | "@executable_path/Frameworks", 526 | ); 527 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 528 | PRODUCT_NAME = "$(TARGET_NAME)"; 529 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 530 | SWIFT_VERSION = 5.0; 531 | VERSIONING_SYSTEM = "apple-generic"; 532 | }; 533 | name = Release; 534 | }; 535 | /* End XCBuildConfiguration section */ 536 | 537 | /* Begin XCConfigurationList section */ 538 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 539 | isa = XCConfigurationList; 540 | buildConfigurations = ( 541 | 97C147031CF9000F007C117D /* Debug */, 542 | 97C147041CF9000F007C117D /* Release */, 543 | 249021D3217E4FDB00AE95B9 /* Profile */, 544 | ); 545 | defaultConfigurationIsVisible = 0; 546 | defaultConfigurationName = Release; 547 | }; 548 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 549 | isa = XCConfigurationList; 550 | buildConfigurations = ( 551 | 97C147061CF9000F007C117D /* Debug */, 552 | 97C147071CF9000F007C117D /* Release */, 553 | 249021D4217E4FDB00AE95B9 /* Profile */, 554 | ); 555 | defaultConfigurationIsVisible = 0; 556 | defaultConfigurationName = Release; 557 | }; 558 | /* End XCConfigurationList section */ 559 | }; 560 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 561 | } 562 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomalabaster/flutter_app_lock/83d7f6bc968cf9ac6f893113a76a7895f02143c3/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Example 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | flutter_app_lock_example 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UIApplicationSupportsIndirectInputEvents 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/app/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app_lock/flutter_app_lock.dart'; 3 | 4 | import '../screens/lock_screen.dart'; 5 | import '../screens/my_home_page.dart'; 6 | 7 | class MyApp extends StatelessWidget { 8 | final bool initiallyEnabled; 9 | 10 | @visibleForTesting 11 | final Duration initialBackgroundLockLatency; 12 | 13 | const MyApp({ 14 | super.key, 15 | this.initiallyEnabled = false, 16 | required this.initialBackgroundLockLatency, 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return MaterialApp( 22 | title: 'Flutter Demo', 23 | theme: ThemeData( 24 | primarySwatch: Colors.blue, 25 | ), 26 | builder: (context, child) => AppLock( 27 | builder: (context, arg) => child!, 28 | lockScreenBuilder: (context) => const LockScreen( 29 | key: Key('LockScreen'), 30 | ), 31 | initiallyEnabled: initiallyEnabled, 32 | initialBackgroundLockLatency: initialBackgroundLockLatency, 33 | inactiveBuilder: (context) => const Scaffold( 34 | key: Key('InactiveScreen'), 35 | body: Center( 36 | child: FlutterLogo(size: 80), 37 | ), 38 | ), 39 | ), 40 | home: const MyHomePage( 41 | key: Key('MyHomePage'), 42 | title: 'Flutter Demo Home Page', 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'app/app.dart'; 4 | 5 | void main({ 6 | bool initiallyEnabled = false, 7 | @visibleForTesting 8 | Duration initialBackgroundLockLatency = const Duration(seconds: 30), 9 | }) { 10 | runApp(MyApp( 11 | initiallyEnabled: initiallyEnabled, 12 | initialBackgroundLockLatency: initialBackgroundLockLatency, 13 | )); 14 | } 15 | -------------------------------------------------------------------------------- /example/lib/screens/lock_screen.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: library_private_types_in_public_api 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_app_lock/flutter_app_lock.dart'; 5 | 6 | class LockScreen extends StatefulWidget { 7 | const LockScreen({super.key}); 8 | 9 | @override 10 | _LockScreenState createState() => _LockScreenState(); 11 | } 12 | 13 | class _LockScreenState extends State { 14 | late final TextEditingController _textEditingController; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | 20 | _textEditingController = TextEditingController(); 21 | } 22 | 23 | @override 24 | void dispose() { 25 | _textEditingController.dispose(); 26 | 27 | super.dispose(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | body: Padding( 34 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 35 | child: Column( 36 | mainAxisAlignment: MainAxisAlignment.center, 37 | children: [ 38 | TextField( 39 | key: const Key('PasswordField'), 40 | controller: _textEditingController, 41 | ), 42 | ElevatedButton( 43 | key: const Key('UnlockButton'), 44 | child: const Text('Go'), 45 | onPressed: () { 46 | if (_textEditingController.text == '0000') { 47 | AppLock.of(context)!.didUnlock('some data'); 48 | } 49 | }, 50 | ) 51 | ], 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/lib/screens/my_home_page.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: library_private_types_in_public_api 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_app_lock/flutter_app_lock.dart'; 6 | 7 | class MyHomePage extends StatefulWidget { 8 | const MyHomePage({ 9 | super.key, 10 | required this.title, 11 | }); 12 | 13 | final String title; 14 | 15 | @override 16 | _MyHomePageState createState() => _MyHomePageState(); 17 | } 18 | 19 | class _MyHomePageState extends State { 20 | int _counter = 0; 21 | 22 | void _incrementCounter() { 23 | setState(() { 24 | _counter++; 25 | }); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | appBar: AppBar( 32 | title: Text(widget.title), 33 | ), 34 | body: Center( 35 | child: Column( 36 | mainAxisAlignment: MainAxisAlignment.center, 37 | children: [ 38 | const Text( 39 | 'You have pushed the button this many times:', 40 | ), 41 | Text( 42 | '$_counter', 43 | style: Theme.of(context).textTheme.headlineMedium, 44 | ), 45 | Text( 46 | 'App unlocked with the following data: ${AppLock.of(context)!.launchArg}'), 47 | ElevatedButton( 48 | key: const Key('EnableButton'), 49 | child: const Text('Set app lock enabled'), 50 | onPressed: () => AppLock.of(context)!.enable(), 51 | ), 52 | ElevatedButton( 53 | key: const Key('DisableButton'), 54 | child: const Text('Set app lock disabled'), 55 | onPressed: () => AppLock.of(context)!.disable(), 56 | ), 57 | ElevatedButton( 58 | key: const Key('ShowButton'), 59 | child: const Text('Manually show lock screen'), 60 | onPressed: () => AppLock.of(context)!.showLockScreen(), 61 | ), 62 | ElevatedButton( 63 | key: const Key('AwaitShowButton'), 64 | child: const Text('Manually show lock screen (awaiting)'), 65 | onPressed: () async { 66 | await AppLock.of(context)!.showLockScreen(); 67 | 68 | if (kDebugMode) { 69 | print('Did unlock!'); 70 | } 71 | }, 72 | ), 73 | ElevatedButton( 74 | key: const Key('SetBackgroundLockLatency'), 75 | child: const Text('Changing background lock latency'), 76 | onPressed: () => AppLock.of(context)! 77 | .setBackgroundLockLatency(const Duration(seconds: 5)), 78 | ), 79 | ], 80 | ), 81 | ), 82 | floatingActionButton: FloatingActionButton( 83 | onPressed: _incrementCounter, 84 | tooltip: 'Increment', 85 | child: const Icon(Icons.add), 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | cupertino_icons: 45 | dependency: "direct main" 46 | description: 47 | name: cupertino_icons 48 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.0.8" 52 | fake_async: 53 | dependency: transitive 54 | description: 55 | name: fake_async 56 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.3.1" 60 | file: 61 | dependency: transitive 62 | description: 63 | name: file 64 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "7.0.0" 68 | flutter: 69 | dependency: "direct main" 70 | description: flutter 71 | source: sdk 72 | version: "0.0.0" 73 | flutter_app_lock: 74 | dependency: "direct main" 75 | description: 76 | path: ".." 77 | relative: true 78 | source: path 79 | version: "4.2.0+2" 80 | flutter_driver: 81 | dependency: "direct dev" 82 | description: flutter 83 | source: sdk 84 | version: "0.0.0" 85 | flutter_lints: 86 | dependency: "direct dev" 87 | description: 88 | name: flutter_lints 89 | sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" 90 | url: "https://pub.dev" 91 | source: hosted 92 | version: "3.0.2" 93 | flutter_test: 94 | dependency: "direct dev" 95 | description: flutter 96 | source: sdk 97 | version: "0.0.0" 98 | fuchsia_remote_debug_protocol: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.0" 103 | integration_test: 104 | dependency: "direct dev" 105 | description: flutter 106 | source: sdk 107 | version: "0.0.0" 108 | leak_tracker: 109 | dependency: transitive 110 | description: 111 | name: leak_tracker 112 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "10.0.5" 116 | leak_tracker_flutter_testing: 117 | dependency: transitive 118 | description: 119 | name: leak_tracker_flutter_testing 120 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "3.0.5" 124 | leak_tracker_testing: 125 | dependency: transitive 126 | description: 127 | name: leak_tracker_testing 128 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "3.0.1" 132 | lints: 133 | dependency: transitive 134 | description: 135 | name: lints 136 | sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "3.0.0" 140 | matcher: 141 | dependency: transitive 142 | description: 143 | name: matcher 144 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "0.12.16+1" 148 | material_color_utilities: 149 | dependency: transitive 150 | description: 151 | name: material_color_utilities 152 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "0.11.1" 156 | meta: 157 | dependency: transitive 158 | description: 159 | name: meta 160 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "1.15.0" 164 | path: 165 | dependency: transitive 166 | description: 167 | name: path 168 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "1.9.0" 172 | platform: 173 | dependency: transitive 174 | description: 175 | name: platform 176 | sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "3.1.5" 180 | process: 181 | dependency: transitive 182 | description: 183 | name: process 184 | sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "5.0.2" 188 | sky_engine: 189 | dependency: transitive 190 | description: flutter 191 | source: sdk 192 | version: "0.0.99" 193 | source_span: 194 | dependency: transitive 195 | description: 196 | name: source_span 197 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 198 | url: "https://pub.dev" 199 | source: hosted 200 | version: "1.10.0" 201 | stack_trace: 202 | dependency: transitive 203 | description: 204 | name: stack_trace 205 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 206 | url: "https://pub.dev" 207 | source: hosted 208 | version: "1.11.1" 209 | stream_channel: 210 | dependency: transitive 211 | description: 212 | name: stream_channel 213 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 214 | url: "https://pub.dev" 215 | source: hosted 216 | version: "2.1.2" 217 | string_scanner: 218 | dependency: transitive 219 | description: 220 | name: string_scanner 221 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "1.2.0" 225 | sync_http: 226 | dependency: transitive 227 | description: 228 | name: sync_http 229 | sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "0.3.1" 233 | term_glyph: 234 | dependency: transitive 235 | description: 236 | name: term_glyph 237 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 238 | url: "https://pub.dev" 239 | source: hosted 240 | version: "1.2.1" 241 | test_api: 242 | dependency: transitive 243 | description: 244 | name: test_api 245 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" 246 | url: "https://pub.dev" 247 | source: hosted 248 | version: "0.7.2" 249 | vector_math: 250 | dependency: transitive 251 | description: 252 | name: vector_math 253 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "2.1.4" 257 | vm_service: 258 | dependency: transitive 259 | description: 260 | name: vm_service 261 | sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" 262 | url: "https://pub.dev" 263 | source: hosted 264 | version: "14.2.5" 265 | webdriver: 266 | dependency: transitive 267 | description: 268 | name: webdriver 269 | sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" 270 | url: "https://pub.dev" 271 | source: hosted 272 | version: "3.0.3" 273 | sdks: 274 | dart: ">=3.3.0 <4.0.0" 275 | flutter: ">=3.18.0-18.0.pre.54" 276 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_app_lock_example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | # In Windows, build-name is used as the major, minor, and patch parts 19 | # of the product and file versions while build-number is used as the build suffix. 20 | version: 1.0.0+1 21 | 22 | environment: 23 | sdk: ">=3.0.0 <4.0.0" 24 | 25 | # Dependencies specify other packages that your package needs in order to work. 26 | # To automatically upgrade your package dependencies to the latest versions 27 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 28 | # dependencies can be manually updated by changing the version numbers below to 29 | # the latest version available on pub.dev. To see which dependencies have newer 30 | # versions available, run `flutter pub outdated`. 31 | dependencies: 32 | flutter_app_lock: 33 | path: ../ 34 | flutter: 35 | sdk: flutter 36 | 37 | # The following adds the Cupertino Icons font to your application. 38 | # Use with the CupertinoIcons class for iOS style icons. 39 | cupertino_icons: ^1.0.6 40 | 41 | dev_dependencies: 42 | integration_test: 43 | sdk: flutter 44 | flutter_test: 45 | sdk: flutter 46 | flutter_driver: 47 | sdk: flutter 48 | 49 | # The "flutter_lints" package below contains a set of recommended lints to 50 | # encourage good coding practices. The lint set provided by the package is 51 | # activated in the `analysis_options.yaml` file located at the root of your 52 | # package. See that file for information about deactivating specific lint 53 | # rules and activating additional ones. 54 | flutter_lints: ^3.0.1 55 | 56 | # For information on the generic Dart part of this file, see the 57 | # following page: https://dart.dev/tools/pub/pubspec 58 | 59 | # The following section is specific to Flutter packages. 60 | flutter: 61 | # The following line ensures that the Material Icons font is 62 | # included with your application, so that you can use the icons in 63 | # the material Icons class. 64 | uses-material-design: true 65 | 66 | # To add assets to your application, add an assets section, like this: 67 | # assets: 68 | # - images/a_dot_burr.jpeg 69 | # - images/a_dot_ham.jpeg 70 | 71 | # An image asset can refer to one or more resolution-specific "variants", see 72 | # https://flutter.dev/assets-and-images/#resolution-aware 73 | 74 | # For details regarding adding assets from package dependencies, see 75 | # https://flutter.dev/assets-and-images/#from-packages 76 | 77 | # To add custom fonts to your application, add a fonts section here, 78 | # in this "flutter" section. Each entry in this list should have a 79 | # "family" key with the font family name, and a "fonts" key with a 80 | # list giving the asset and other descriptors for the font. For 81 | # example: 82 | # fonts: 83 | # - family: Schyler 84 | # fonts: 85 | # - asset: fonts/Schyler-Regular.ttf 86 | # - asset: fonts/Schyler-Italic.ttf 87 | # style: italic 88 | # - family: Trajan Pro 89 | # fonts: 90 | # - asset: fonts/TrajanPro.ttf 91 | # - asset: fonts/TrajanPro_Bold.ttf 92 | # weight: 700 93 | # 94 | # For details regarding fonts from package dependencies, 95 | # see https://flutter.dev/custom-fonts/#from-packages 96 | -------------------------------------------------------------------------------- /example/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /lib/flutter_app_lock.dart: -------------------------------------------------------------------------------- 1 | library flutter_app_lock; 2 | 3 | export 'src/app_lock.dart'; 4 | -------------------------------------------------------------------------------- /lib/src/app_lock.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_app_lock/src/no_animation_page.dart'; 5 | 6 | /// A widget which handles app lifecycle events for showing and hiding a lock 7 | /// screen. This should wrap around a `MyApp` widget (or equivalent). 8 | /// 9 | /// [lockScreen] (or preferably the [Widget] returned from [lockScreenBuilder]) 10 | /// is a [Widget] which should be a screen for handling login logic and calling 11 | /// `AppLock.of(context).didUnlock();` upon a successful login. 12 | /// 13 | /// [builder] is a [Function] taking an [Object] as its argument and should 14 | /// return a [Widget]. The [Object] argument is provided by the [lockScreen] 15 | /// (or preferably the [Widget] returned from [lockScreenBuilder]) calling 16 | /// `AppLock.of(context).didUnlock();` with an argument. [Object] can then be 17 | /// injected in to your `MyApp` widget (or equivalent). 18 | /// 19 | /// [initiallyEnabled] determines wether or not the [lockScreen] (or preferably 20 | /// the [Widget] returned from [lockScreenBuilder]) should be shown on app 21 | /// launch and subsequent app pauses. This can be changed later on using 22 | /// `AppLock.of(context).enable();`, `AppLock.of(context).disable();` or the 23 | /// convenience method `AppLock.of(context).setEnabled(enabled);` using a 24 | /// [bool] argument. 25 | /// 26 | /// [initialBackgroundLockLatency] determines how much time is allowed to pass 27 | /// when the app is in the background state before the [lockScreen] (or 28 | /// preferably the [Widget] returned from [lockScreenBuilder]) widget should be 29 | /// shown upon returning. It defaults to instantly. This can be changed later 30 | /// on using `AppLock.of(context).setBackgroundLockLatency(duration);` using a 31 | /// [Duration] argument. 32 | class AppLock extends StatefulWidget { 33 | final Widget Function(BuildContext context, Object? launchArg) builder; 34 | final Widget? lockScreen; 35 | final WidgetBuilder? lockScreenBuilder; 36 | final WidgetBuilder? inactiveBuilder; 37 | final bool _initiallyEnabled; 38 | final Duration _initialBackgroundLockLatency; 39 | 40 | const AppLock({ 41 | super.key, 42 | required this.builder, 43 | @Deprecated( 44 | 'Use `lockScreenBuilder` instead. `lockScreen` will be removed in version 5.0.0.') 45 | this.lockScreen, 46 | this.lockScreenBuilder, 47 | this.inactiveBuilder, 48 | @Deprecated( 49 | 'Use `initiallyEnabled` instead. `enabled` will be removed in version 5.0.0.') 50 | bool? enabled, 51 | bool? initiallyEnabled, 52 | @Deprecated( 53 | 'Use `initialBackgroundLockLatency` instead. `backgroundLockLatency` will be removed in version 5.0.0.') 54 | Duration? backgroundLockLatency, 55 | Duration? initialBackgroundLockLatency, 56 | }) : _initiallyEnabled = initiallyEnabled ?? enabled ?? true, 57 | _initialBackgroundLockLatency = initialBackgroundLockLatency ?? 58 | backgroundLockLatency ?? 59 | Duration.zero, 60 | assert( 61 | (lockScreen == null && lockScreenBuilder != null) || 62 | (lockScreen != null && lockScreenBuilder == null), 63 | 'Only 1 of either `lockScreenBuilder` or `lockScreen` should be set.'), 64 | assert( 65 | (enabled == null && initiallyEnabled != null) || 66 | (enabled != null && initiallyEnabled == null), 67 | 'Only 1 of either `initiallyEnabled` or `enabled` should be set.'), 68 | assert( 69 | (backgroundLockLatency == null && 70 | initialBackgroundLockLatency != null) || 71 | (backgroundLockLatency != null && 72 | initialBackgroundLockLatency == null), 73 | 'Only 1 of either `initialBackgroundLockLatency` or `backgroundLockLatency` should be set.'); 74 | 75 | static AppLockState? of(BuildContext context) => 76 | context.findAncestorStateOfType(); 77 | 78 | @override 79 | AppLockState createState() => AppLockState(); 80 | } 81 | 82 | class AppLockState extends State with WidgetsBindingObserver { 83 | late bool _didUnlockForAppLaunch; 84 | late bool _locked; 85 | late bool _enabled; 86 | late bool _inactive; 87 | 88 | late Duration _backgroundLockLatency; 89 | 90 | Timer? _backgroundLockLatencyTimer; 91 | 92 | Object? _launchArg; 93 | 94 | Completer? _didUnlockCompleter; 95 | 96 | @override 97 | void initState() { 98 | super.initState(); 99 | 100 | WidgetsBinding.instance.addObserver(this); 101 | 102 | _didUnlockForAppLaunch = !widget._initiallyEnabled; 103 | _locked = widget._initiallyEnabled; 104 | _enabled = widget._initiallyEnabled; 105 | _inactive = false; 106 | 107 | _backgroundLockLatency = widget._initialBackgroundLockLatency; 108 | } 109 | 110 | @override 111 | void didChangeAppLifecycleState(AppLifecycleState state) { 112 | super.didChangeAppLifecycleState(state); 113 | 114 | if (!_enabled) { 115 | return; 116 | } 117 | 118 | if (state == AppLifecycleState.hidden && !_locked) { 119 | _backgroundLockLatencyTimer?.cancel(); 120 | _backgroundLockLatencyTimer = 121 | Timer(_backgroundLockLatency, () => showLockScreen()); 122 | } 123 | 124 | if (state == AppLifecycleState.resumed) { 125 | _backgroundLockLatencyTimer?.cancel(); 126 | } 127 | 128 | setState(() { 129 | _inactive = state == AppLifecycleState.inactive; 130 | }); 131 | } 132 | 133 | @override 134 | void dispose() { 135 | WidgetsBinding.instance.removeObserver(this); 136 | 137 | _backgroundLockLatencyTimer?.cancel(); 138 | 139 | super.dispose(); 140 | } 141 | 142 | @override 143 | Widget build(BuildContext context) { 144 | return Navigator( 145 | onPopPage: (route, result) => route.didPop(result), 146 | pages: [ 147 | if (_didUnlockForAppLaunch) 148 | MaterialPage( 149 | key: const ValueKey('App'), 150 | child: widget.builder(context, _launchArg), 151 | ), 152 | if (_locked) 153 | MaterialPage( 154 | key: const ValueKey('LockScreen'), 155 | child: _lockScreen, 156 | ) 157 | else if (_inactive && widget.inactiveBuilder != null) 158 | NoAnimationPage( 159 | key: const ValueKey('InactiveScreen'), 160 | child: widget.inactiveBuilder!(context), 161 | ), 162 | ], 163 | ); 164 | } 165 | 166 | Widget get _lockScreen { 167 | return PopScope( 168 | canPop: false, 169 | child: (widget.lockScreenBuilder?.call(context) ?? widget.lockScreen)!, 170 | ); 171 | } 172 | 173 | /// Causes `AppLock` to either pop the [AppLock.lockScreen] (or preferably 174 | /// the [Widget] returned from [AppLock.lockScreenBuilder]) if the app is 175 | /// already running or instantiates widget returned from the 176 | /// [AppLock.builder] method if the app is cold launched. 177 | /// 178 | /// [launchArg] is an optional argument which will get passed to the 179 | /// [AppLock.builder] method when built. Use this when you want to inject 180 | /// objects created from the [AppLock.lockScreen] (or preferably the [Widget] 181 | /// returned from [AppLock.lockScreenBuilder]) in to the rest of your app so 182 | /// you can better guarantee that some objects, services or databases are 183 | /// already instantiated before using them. 184 | void didUnlock([Object? launchArg]) { 185 | if (_didUnlockForAppLaunch) { 186 | _didUnlockOnAppPaused(); 187 | } else { 188 | _didUnlockOnAppLaunch(launchArg); 189 | } 190 | 191 | _didUnlockCompleter?.complete(); 192 | } 193 | 194 | /// Makes sure that [AppLock] shows the [AppLock.lockScreen] (or preferably 195 | /// the [Widget] returned from [AppLock.lockScreenBuilder]) on subsequent app 196 | /// pauses if [enabled] is true of makes sure it isn't shown on subsequent 197 | /// app pauses if [enabled] is false. 198 | /// 199 | /// This is a convenience method for calling the [enable] or [disable] method 200 | /// based on [enabled]. 201 | void setEnabled(bool enabled) { 202 | if (enabled) { 203 | enable(); 204 | } else { 205 | disable(); 206 | } 207 | } 208 | 209 | /// Makes sure that [AppLock] shows the [lockScreen] (or preferably the 210 | /// [Widget] returned from [lockScreenBuilder]) on subsequent app pauses. 211 | void enable() { 212 | setState(() { 213 | _enabled = true; 214 | }); 215 | } 216 | 217 | /// Makes sure that [AppLock] doesn't show the [AppLock.lockScreen] (or 218 | /// preferably the [Widget] returned from [AppLock.lockScreenBuilder]) on 219 | /// subsequent app pauses. 220 | void disable() { 221 | setState(() { 222 | _enabled = false; 223 | }); 224 | } 225 | 226 | /// Manually show the [AppLock.lockScreen] (or preferably the [Widget] 227 | /// returned from [AppLock.lockScreenBuilder]). 228 | Future showLockScreen() async { 229 | if (_locked && _didUnlockCompleter != null) { 230 | return _didUnlockCompleter!.future; 231 | } 232 | 233 | _didUnlockCompleter = Completer(); 234 | 235 | setState(() { 236 | _locked = true; 237 | }); 238 | 239 | return _didUnlockCompleter!.future; 240 | } 241 | 242 | /// Change the background lock latency after `AppLock` has been created. 243 | void setBackgroundLockLatency(Duration backgroundLockLatency) => 244 | _backgroundLockLatency = backgroundLockLatency; 245 | 246 | /// An argument that is passed to [didUnlock] for the first time after showing 247 | /// [AppLock.lockScreen] (or preferably the [Widget] returned from 248 | /// [AppLock.lockScreenBuilder]) on launch. 249 | Object? get launchArg => _launchArg; 250 | 251 | void _didUnlockOnAppLaunch(Object? launchArg) { 252 | setState(() { 253 | _launchArg = launchArg; 254 | _didUnlockForAppLaunch = true; 255 | _locked = false; 256 | }); 257 | } 258 | 259 | void _didUnlockOnAppPaused() { 260 | setState(() { 261 | _locked = false; 262 | }); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /lib/src/no_animation_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NoAnimationPage extends Page { 4 | const NoAnimationPage({ 5 | required this.child, 6 | this.maintainState = true, 7 | this.fullscreenDialog = false, 8 | this.allowSnapshotting = true, 9 | super.key, 10 | super.name, 11 | super.arguments, 12 | super.restorationId, 13 | }); 14 | 15 | final Widget child; 16 | final bool maintainState; 17 | final bool fullscreenDialog; 18 | final bool allowSnapshotting; 19 | 20 | @override 21 | Route createRoute(BuildContext context) => _NoAnimationPageRoute( 22 | page: this, allowSnapshotting: allowSnapshotting); 23 | } 24 | 25 | class _NoAnimationPageRoute extends PageRoute { 26 | _NoAnimationPageRoute({ 27 | required NoAnimationPage page, 28 | super.allowSnapshotting, 29 | }) : super(settings: page) { 30 | assert(opaque); 31 | } 32 | 33 | NoAnimationPage get _page => settings as NoAnimationPage; 34 | 35 | @override 36 | bool get maintainState => _page.maintainState; 37 | 38 | @override 39 | bool get fullscreenDialog => _page.fullscreenDialog; 40 | 41 | @override 42 | String get debugLabel => '${super.debugLabel}(${_page.name})'; 43 | 44 | @override 45 | Color? get barrierColor => null; 46 | 47 | @override 48 | String? get barrierLabel => null; 49 | 50 | @override 51 | Widget buildPage(BuildContext context, Animation animation, 52 | Animation secondaryAnimation) => 53 | _page.child; 54 | 55 | @override 56 | Duration get transitionDuration => Duration.zero; 57 | } 58 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_app_lock 2 | description: A Flutter package for showing a lock screen on app open and app pause. 3 | version: 4.2.0+2 4 | homepage: https://github.com/tomalabaster/flutter_app_lock 5 | 6 | environment: 7 | sdk: ">=3.0.0 <4.0.0" 8 | flutter: ">=3.16.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | flutter_lints: ^3.0.1 18 | -------------------------------------------------------------------------------- /test/src/app_lock_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app_lock/flutter_app_lock.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | Future setAppLifecycleToHidden(WidgetTester widgetTester) async { 6 | widgetTester.binding.handleAppLifecycleStateChanged(AppLifecycleState.hidden); 7 | 8 | await widgetTester.pumpAndSettle(); 9 | } 10 | 11 | Future setAppLifecycleToInactive(WidgetTester widgetTester) async { 12 | widgetTester.binding 13 | .handleAppLifecycleStateChanged(AppLifecycleState.inactive); 14 | 15 | await widgetTester.pumpAndSettle(); 16 | } 17 | 18 | Future setAppLifecycleToResumed(WidgetTester widgetTester) async { 19 | widgetTester.binding 20 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 21 | 22 | await widgetTester.pumpAndSettle(); 23 | } 24 | 25 | void enableAppLockAfterLaunch(WidgetTester widgetTester) { 26 | widgetTester.state(find.byType(AppLock)).enable(); 27 | } 28 | 29 | void main() { 30 | group('Given an AppLock widget', () { 31 | group('When it is enabled', () { 32 | late Widget sut; 33 | 34 | setUp(() { 35 | sut = MaterialApp( 36 | builder: (context, child) => AppLock( 37 | initialBackgroundLockLatency: const Duration(seconds: 1), 38 | initiallyEnabled: true, 39 | builder: (context, launchArg) => KeyedSubtree( 40 | key: const Key('Unlocked'), 41 | child: child!, 42 | ), 43 | lockScreenBuilder: (context) => const Scaffold( 44 | key: Key('LockScreen'), 45 | ), 46 | inactiveBuilder: (context) => const Scaffold( 47 | key: Key('InactiveScreen'), 48 | ), 49 | ), 50 | home: const Scaffold(), 51 | ); 52 | }); 53 | 54 | testWidgets('The lock screen should be shown', (widgetTester) async { 55 | await widgetTester.pumpWidget(sut); 56 | 57 | expect(find.byKey(const Key('LockScreen')), findsOneWidget); 58 | }); 59 | 60 | testWidgets('The unlocked app should not be shown', (widgetTester) async { 61 | await widgetTester.pumpWidget(sut); 62 | 63 | expect(find.byKey(const Key('Unlocked')), findsNothing); 64 | }); 65 | 66 | testWidgets('The inactive screen should not be shown', 67 | (widgetTester) async { 68 | await widgetTester.pumpWidget(sut); 69 | 70 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 71 | }); 72 | }); 73 | 74 | group( 75 | 'When it is enabled and the app becomes inactive without an inactive builder set', 76 | () { 77 | late Widget sut; 78 | 79 | setUp(() { 80 | sut = MaterialApp( 81 | builder: (context, child) => AppLock( 82 | initialBackgroundLockLatency: const Duration(seconds: 1), 83 | initiallyEnabled: true, 84 | builder: (context, launchArg) => KeyedSubtree( 85 | key: const Key('Unlocked'), 86 | child: child!, 87 | ), 88 | lockScreenBuilder: (context) => const Scaffold( 89 | key: Key('LockScreen'), 90 | ), 91 | ), 92 | home: const Scaffold(), 93 | ); 94 | }); 95 | 96 | testWidgets('The lock screen should be shown', (widgetTester) async { 97 | addTearDown(() { 98 | widgetTester.binding 99 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 100 | }); 101 | 102 | await widgetTester.pumpWidget(sut); 103 | 104 | await setAppLifecycleToInactive(widgetTester); 105 | 106 | expect(find.byKey(const Key('LockScreen')), findsOneWidget); 107 | }); 108 | 109 | testWidgets('The unlocked app should not be shown', (widgetTester) async { 110 | addTearDown(() { 111 | widgetTester.binding 112 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 113 | }); 114 | 115 | await widgetTester.pumpWidget(sut); 116 | 117 | await setAppLifecycleToInactive(widgetTester); 118 | 119 | expect(find.byKey(const Key('Unlocked')), findsNothing); 120 | }); 121 | 122 | testWidgets('The inactive screen should not be shown', 123 | (widgetTester) async { 124 | addTearDown(() { 125 | widgetTester.binding 126 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 127 | }); 128 | 129 | await widgetTester.pumpWidget(sut); 130 | 131 | await setAppLifecycleToInactive(widgetTester); 132 | 133 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 134 | }); 135 | }); 136 | 137 | group( 138 | 'When it is enabled and the app becomes inactive with an inactive builder set', 139 | () { 140 | late Widget sut; 141 | 142 | setUp(() { 143 | sut = MaterialApp( 144 | builder: (context, child) => AppLock( 145 | initialBackgroundLockLatency: const Duration(seconds: 1), 146 | initiallyEnabled: true, 147 | builder: (context, launchArg) => KeyedSubtree( 148 | key: const Key('Unlocked'), 149 | child: child!, 150 | ), 151 | lockScreenBuilder: (context) => const Scaffold( 152 | key: Key('LockScreen'), 153 | ), 154 | inactiveBuilder: (context) => const Scaffold( 155 | key: Key('InactiveScreen'), 156 | ), 157 | ), 158 | home: const Scaffold(), 159 | ); 160 | }); 161 | 162 | testWidgets('The lock screen should be shown', (widgetTester) async { 163 | addTearDown(() { 164 | widgetTester.binding 165 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 166 | }); 167 | 168 | await widgetTester.pumpWidget(sut); 169 | 170 | await setAppLifecycleToInactive(widgetTester); 171 | 172 | expect(find.byKey(const Key('LockScreen')), findsOneWidget); 173 | }); 174 | 175 | testWidgets('The unlocked app should not be shown', (widgetTester) async { 176 | addTearDown(() { 177 | widgetTester.binding 178 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 179 | }); 180 | 181 | await widgetTester.pumpWidget(sut); 182 | 183 | await setAppLifecycleToInactive(widgetTester); 184 | 185 | expect(find.byKey(const Key('Unlocked')), findsNothing); 186 | }); 187 | 188 | testWidgets('The inactive screen should not be shown', 189 | (widgetTester) async { 190 | addTearDown(() { 191 | widgetTester.binding 192 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 193 | }); 194 | 195 | await widgetTester.pumpWidget(sut); 196 | 197 | await setAppLifecycleToInactive(widgetTester); 198 | 199 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 200 | }); 201 | }); 202 | 203 | group('When it is enabled and the app transitions from inactive to resumed', 204 | () { 205 | late Widget sut; 206 | 207 | setUp(() { 208 | sut = MaterialApp( 209 | builder: (context, child) => AppLock( 210 | initialBackgroundLockLatency: const Duration(seconds: 1), 211 | initiallyEnabled: true, 212 | builder: (context, launchArg) => KeyedSubtree( 213 | key: const Key('Unlocked'), 214 | child: child!, 215 | ), 216 | lockScreenBuilder: (context) => const Scaffold( 217 | key: Key('LockScreen'), 218 | ), 219 | inactiveBuilder: (context) => const Scaffold( 220 | key: Key('InactiveScreen'), 221 | ), 222 | ), 223 | home: const Scaffold(), 224 | ); 225 | }); 226 | 227 | testWidgets('The lock screen should be shown', (widgetTester) async { 228 | addTearDown(() { 229 | widgetTester.binding 230 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 231 | }); 232 | 233 | await widgetTester.pumpWidget(sut); 234 | 235 | await setAppLifecycleToInactive(widgetTester); 236 | await setAppLifecycleToResumed(widgetTester); 237 | 238 | expect(find.byKey(const Key('LockScreen')), findsOneWidget); 239 | }); 240 | 241 | testWidgets('The unlocked app should not be shown', (widgetTester) async { 242 | addTearDown(() { 243 | widgetTester.binding 244 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 245 | }); 246 | 247 | await widgetTester.pumpWidget(sut); 248 | 249 | await setAppLifecycleToInactive(widgetTester); 250 | await setAppLifecycleToResumed(widgetTester); 251 | 252 | expect(find.byKey(const Key('Unlocked')), findsNothing); 253 | }); 254 | 255 | testWidgets('The inactive screen should not be shown', 256 | (widgetTester) async { 257 | addTearDown(() { 258 | widgetTester.binding 259 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 260 | }); 261 | 262 | await widgetTester.pumpWidget(sut); 263 | 264 | await setAppLifecycleToInactive(widgetTester); 265 | await setAppLifecycleToResumed(widgetTester); 266 | 267 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 268 | }); 269 | }); 270 | 271 | group( 272 | 'When it is enabled and the app transitions from inactive to hidden for longer than the lock duration and back to inactive and resumed', 273 | () { 274 | late Widget sut; 275 | 276 | setUp(() { 277 | sut = MaterialApp( 278 | builder: (context, child) => AppLock( 279 | initialBackgroundLockLatency: const Duration(seconds: 1), 280 | initiallyEnabled: true, 281 | builder: (context, launchArg) => KeyedSubtree( 282 | key: const Key('Unlocked'), 283 | child: child!, 284 | ), 285 | lockScreenBuilder: (context) => const Scaffold( 286 | key: Key('LockScreen'), 287 | ), 288 | inactiveBuilder: (context) => const Scaffold( 289 | key: Key('InactiveScreen'), 290 | ), 291 | ), 292 | home: const Scaffold(), 293 | ); 294 | }); 295 | 296 | testWidgets('The lock screen should be shown', (widgetTester) async { 297 | addTearDown(() { 298 | widgetTester.binding 299 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 300 | }); 301 | 302 | await widgetTester.pumpWidget(sut); 303 | 304 | await setAppLifecycleToInactive(widgetTester); 305 | await setAppLifecycleToHidden(widgetTester); 306 | 307 | await widgetTester.pumpAndSettle(const Duration(seconds: 2)); 308 | 309 | await setAppLifecycleToInactive(widgetTester); 310 | await setAppLifecycleToResumed(widgetTester); 311 | 312 | expect(find.byKey(const Key('LockScreen')), findsOneWidget); 313 | }); 314 | 315 | testWidgets('The unlocked app should not be shown', (widgetTester) async { 316 | addTearDown(() { 317 | widgetTester.binding 318 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 319 | }); 320 | 321 | await widgetTester.pumpWidget(sut); 322 | 323 | await setAppLifecycleToInactive(widgetTester); 324 | await setAppLifecycleToHidden(widgetTester); 325 | 326 | await widgetTester.pumpAndSettle(const Duration(seconds: 2)); 327 | 328 | await setAppLifecycleToInactive(widgetTester); 329 | await setAppLifecycleToResumed(widgetTester); 330 | 331 | expect(find.byKey(const Key('Unlocked')), findsNothing); 332 | }); 333 | 334 | testWidgets('The inactive screen should not be shown', 335 | (widgetTester) async { 336 | addTearDown(() { 337 | widgetTester.binding 338 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 339 | }); 340 | 341 | await widgetTester.pumpWidget(sut); 342 | 343 | await setAppLifecycleToInactive(widgetTester); 344 | await setAppLifecycleToHidden(widgetTester); 345 | 346 | await widgetTester.pumpAndSettle(const Duration(seconds: 2)); 347 | 348 | await setAppLifecycleToInactive(widgetTester); 349 | await setAppLifecycleToResumed(widgetTester); 350 | 351 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 352 | }); 353 | }); 354 | 355 | group( 356 | 'When it is enabled and the app transitions from inactive to hidden for less than the lock duration and back to inactive and resumed', 357 | () { 358 | late Widget sut; 359 | 360 | setUp(() { 361 | sut = MaterialApp( 362 | builder: (context, child) => AppLock( 363 | initialBackgroundLockLatency: const Duration(seconds: 2), 364 | initiallyEnabled: true, 365 | builder: (context, launchArg) => KeyedSubtree( 366 | key: const Key('Unlocked'), 367 | child: child!, 368 | ), 369 | lockScreenBuilder: (context) => const Scaffold( 370 | key: Key('LockScreen'), 371 | ), 372 | inactiveBuilder: (context) => const Scaffold( 373 | key: Key('InactiveScreen'), 374 | ), 375 | ), 376 | home: const Scaffold(), 377 | ); 378 | }); 379 | 380 | testWidgets('The lock screen should be shown', (widgetTester) async { 381 | addTearDown(() { 382 | widgetTester.binding 383 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 384 | }); 385 | 386 | await widgetTester.pumpWidget(sut); 387 | 388 | await setAppLifecycleToInactive(widgetTester); 389 | await setAppLifecycleToHidden(widgetTester); 390 | 391 | await widgetTester.pumpAndSettle(const Duration(seconds: 1)); 392 | 393 | await setAppLifecycleToInactive(widgetTester); 394 | await setAppLifecycleToResumed(widgetTester); 395 | 396 | expect(find.byKey(const Key('LockScreen')), findsOneWidget); 397 | }); 398 | 399 | testWidgets('The unlocked app should not be shown', (widgetTester) async { 400 | addTearDown(() { 401 | widgetTester.binding 402 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 403 | }); 404 | 405 | await widgetTester.pumpWidget(sut); 406 | 407 | await setAppLifecycleToInactive(widgetTester); 408 | await setAppLifecycleToHidden(widgetTester); 409 | 410 | await widgetTester.pumpAndSettle(const Duration(seconds: 1)); 411 | 412 | await setAppLifecycleToInactive(widgetTester); 413 | await setAppLifecycleToResumed(widgetTester); 414 | 415 | expect(find.byKey(const Key('Unlocked')), findsNothing); 416 | }); 417 | 418 | testWidgets('The inactive screen should not be shown', 419 | (widgetTester) async { 420 | addTearDown(() { 421 | widgetTester.binding 422 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 423 | }); 424 | 425 | await widgetTester.pumpWidget(sut); 426 | 427 | await setAppLifecycleToInactive(widgetTester); 428 | await setAppLifecycleToHidden(widgetTester); 429 | 430 | await widgetTester.pumpAndSettle(const Duration(seconds: 1)); 431 | 432 | await setAppLifecycleToInactive(widgetTester); 433 | await setAppLifecycleToResumed(widgetTester); 434 | 435 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 436 | }); 437 | }); 438 | 439 | group( 440 | 'When it is disabled and then enabled at runtime and the background lock latency is changed at runtime and the app transitions from inactive to hidden for longer than the new lock duration and back to inactive and resumed', 441 | () { 442 | late Widget sut; 443 | 444 | final GlobalKey appLockKey = GlobalKey(); 445 | 446 | setUp(() { 447 | sut = MaterialApp( 448 | builder: (context, child) => AppLock( 449 | key: appLockKey, 450 | initialBackgroundLockLatency: Duration.zero, 451 | initiallyEnabled: false, 452 | builder: (context, launchArg) => KeyedSubtree( 453 | key: const Key('Unlocked'), 454 | child: child!, 455 | ), 456 | lockScreenBuilder: (context) => const Scaffold( 457 | key: Key('LockScreen'), 458 | ), 459 | inactiveBuilder: (context) => const Scaffold( 460 | key: Key('InactiveScreen'), 461 | ), 462 | ), 463 | home: const Scaffold(), 464 | ); 465 | }); 466 | 467 | testWidgets( 468 | 'The lock screen should be shown if inactive for longer than the new background lock latency', 469 | (widgetTester) async { 470 | addTearDown(() { 471 | widgetTester.binding 472 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 473 | }); 474 | 475 | await widgetTester.pumpWidget(sut); 476 | 477 | enableAppLockAfterLaunch(widgetTester); 478 | 479 | appLockKey.currentState! 480 | .setBackgroundLockLatency(const Duration(seconds: 5)); 481 | 482 | await setAppLifecycleToInactive(widgetTester); 483 | await setAppLifecycleToHidden(widgetTester); 484 | 485 | await widgetTester.pumpAndSettle(const Duration(seconds: 6)); 486 | 487 | await setAppLifecycleToInactive(widgetTester); 488 | await setAppLifecycleToResumed(widgetTester); 489 | 490 | expect(find.byKey(const Key('LockScreen')), findsOneWidget); 491 | }); 492 | }); 493 | 494 | group( 495 | 'When it is disabled and then enabled at runtime and the background lock latency is changed at runtime and the app transitions from inactive to hidden for less than the new lock duration and back to inactive and resumed', 496 | () { 497 | late Widget sut; 498 | 499 | final GlobalKey appLockKey = GlobalKey(); 500 | 501 | setUp(() { 502 | sut = MaterialApp( 503 | builder: (context, child) => AppLock( 504 | key: appLockKey, 505 | initialBackgroundLockLatency: Duration.zero, 506 | initiallyEnabled: false, 507 | builder: (context, launchArg) => KeyedSubtree( 508 | key: const Key('Unlocked'), 509 | child: child!, 510 | ), 511 | lockScreenBuilder: (context) => const Scaffold( 512 | key: Key('LockScreen'), 513 | ), 514 | inactiveBuilder: (context) => const Scaffold( 515 | key: Key('InactiveScreen'), 516 | ), 517 | ), 518 | home: const Scaffold(), 519 | ); 520 | }); 521 | 522 | testWidgets( 523 | 'The lock screen should not be shown if inactive for less than the new background lock latency', 524 | (widgetTester) async { 525 | addTearDown(() { 526 | widgetTester.binding 527 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 528 | }); 529 | 530 | await widgetTester.pumpWidget(sut); 531 | 532 | enableAppLockAfterLaunch(widgetTester); 533 | 534 | appLockKey.currentState! 535 | .setBackgroundLockLatency(const Duration(seconds: 5)); 536 | 537 | await setAppLifecycleToInactive(widgetTester); 538 | await setAppLifecycleToHidden(widgetTester); 539 | 540 | await widgetTester.pumpAndSettle(const Duration(seconds: 2)); 541 | 542 | await setAppLifecycleToInactive(widgetTester); 543 | await setAppLifecycleToResumed(widgetTester); 544 | 545 | expect(find.byKey(const Key('LockScreen')), findsNothing); 546 | }); 547 | }); 548 | 549 | group('When it is disabled', () { 550 | late Widget sut; 551 | 552 | setUp(() { 553 | sut = MaterialApp( 554 | builder: (context, child) => AppLock( 555 | initialBackgroundLockLatency: const Duration(seconds: 1), 556 | initiallyEnabled: false, 557 | builder: (context, launchArg) => KeyedSubtree( 558 | key: const Key('Unlocked'), 559 | child: child!, 560 | ), 561 | lockScreenBuilder: (context) => const Scaffold( 562 | key: Key('LockScreen'), 563 | ), 564 | inactiveBuilder: (context) => const Scaffold( 565 | key: Key('InactiveScreen'), 566 | ), 567 | ), 568 | home: const Scaffold(), 569 | ); 570 | }); 571 | 572 | testWidgets('The lock screen should not be shown', (widgetTester) async { 573 | await widgetTester.pumpWidget(sut); 574 | 575 | expect(find.byKey(const Key('LockScreen')), findsNothing); 576 | }); 577 | 578 | testWidgets('The unlocked app should be shown', (widgetTester) async { 579 | await widgetTester.pumpWidget(sut); 580 | 581 | expect(find.byKey(const Key('Unlocked')), findsOneWidget); 582 | }); 583 | 584 | testWidgets('The inactive screen should not be shown', 585 | (widgetTester) async { 586 | await widgetTester.pumpWidget(sut); 587 | 588 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 589 | }); 590 | }); 591 | 592 | group( 593 | 'When it is disabled and then enabled at runtime and the app becomes inactive without an inactive builder set', 594 | () { 595 | late Widget sut; 596 | 597 | setUp(() { 598 | sut = MaterialApp( 599 | builder: (context, child) => AppLock( 600 | initialBackgroundLockLatency: const Duration(seconds: 1), 601 | initiallyEnabled: false, 602 | builder: (context, launchArg) => KeyedSubtree( 603 | key: const Key('Unlocked'), 604 | child: child!, 605 | ), 606 | lockScreenBuilder: (context) => const Scaffold( 607 | key: Key('LockScreen'), 608 | ), 609 | ), 610 | home: const Scaffold(), 611 | ); 612 | }); 613 | 614 | testWidgets('The lock screen should not be shown', (widgetTester) async { 615 | addTearDown(() { 616 | widgetTester.binding 617 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 618 | }); 619 | 620 | await widgetTester.pumpWidget(sut); 621 | 622 | enableAppLockAfterLaunch(widgetTester); 623 | 624 | await setAppLifecycleToInactive(widgetTester); 625 | 626 | expect(find.byKey(const Key('LockScreen')), findsNothing); 627 | }); 628 | 629 | testWidgets('The unlocked app should be shown', (widgetTester) async { 630 | addTearDown(() { 631 | widgetTester.binding 632 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 633 | }); 634 | 635 | await widgetTester.pumpWidget(sut); 636 | 637 | enableAppLockAfterLaunch(widgetTester); 638 | 639 | await setAppLifecycleToInactive(widgetTester); 640 | 641 | expect(find.byKey(const Key('Unlocked')), findsOneWidget); 642 | }); 643 | 644 | testWidgets('The inactive screen should not be shown', 645 | (widgetTester) async { 646 | addTearDown(() { 647 | widgetTester.binding 648 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 649 | }); 650 | 651 | await widgetTester.pumpWidget(sut); 652 | 653 | enableAppLockAfterLaunch(widgetTester); 654 | 655 | await setAppLifecycleToInactive(widgetTester); 656 | 657 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 658 | }); 659 | }); 660 | 661 | group( 662 | 'When it is disabled and then enabled at runtime and the app becomes inactive with an inactive builder set', 663 | () { 664 | late Widget sut; 665 | 666 | setUp(() { 667 | sut = MaterialApp( 668 | builder: (context, child) => AppLock( 669 | initialBackgroundLockLatency: const Duration(seconds: 1), 670 | initiallyEnabled: false, 671 | builder: (context, launchArg) => KeyedSubtree( 672 | key: const Key('Unlocked'), 673 | child: child!, 674 | ), 675 | lockScreenBuilder: (context) => const Scaffold( 676 | key: Key('LockScreen'), 677 | ), 678 | inactiveBuilder: (context) => const Scaffold( 679 | key: Key('InactiveScreen'), 680 | ), 681 | ), 682 | home: const Scaffold(), 683 | ); 684 | }); 685 | 686 | testWidgets('The lock screen should not be shown', (widgetTester) async { 687 | addTearDown(() { 688 | widgetTester.binding 689 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 690 | }); 691 | 692 | await widgetTester.pumpWidget(sut); 693 | 694 | enableAppLockAfterLaunch(widgetTester); 695 | 696 | await setAppLifecycleToInactive(widgetTester); 697 | 698 | expect(find.byKey(const Key('LockScreen')), findsNothing); 699 | }); 700 | 701 | testWidgets('The unlocked app should not be shown', (widgetTester) async { 702 | addTearDown(() { 703 | widgetTester.binding 704 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 705 | }); 706 | 707 | await widgetTester.pumpWidget(sut); 708 | 709 | enableAppLockAfterLaunch(widgetTester); 710 | 711 | await setAppLifecycleToInactive(widgetTester); 712 | 713 | expect(find.byKey(const Key('Unlocked')), findsNothing); 714 | }); 715 | 716 | testWidgets('The inactive screen should be shown', (widgetTester) async { 717 | addTearDown(() { 718 | widgetTester.binding 719 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 720 | }); 721 | 722 | await widgetTester.pumpWidget(sut); 723 | 724 | enableAppLockAfterLaunch(widgetTester); 725 | 726 | await setAppLifecycleToInactive(widgetTester); 727 | 728 | expect(find.byKey(const Key('InactiveScreen')), findsOneWidget); 729 | }); 730 | }); 731 | 732 | group( 733 | 'When it is disabled and then enabled at runtime and the app transitions from inactive to resumed', 734 | () { 735 | late Widget sut; 736 | 737 | setUp(() { 738 | sut = MaterialApp( 739 | builder: (context, child) => AppLock( 740 | initialBackgroundLockLatency: const Duration(seconds: 1), 741 | initiallyEnabled: false, 742 | builder: (context, launchArg) => KeyedSubtree( 743 | key: const Key('Unlocked'), 744 | child: child!, 745 | ), 746 | lockScreenBuilder: (context) => const Scaffold( 747 | key: Key('LockScreen'), 748 | ), 749 | inactiveBuilder: (context) => const Scaffold( 750 | key: Key('InactiveScreen'), 751 | ), 752 | ), 753 | home: const Scaffold(), 754 | ); 755 | }); 756 | 757 | testWidgets('The lock screen should not be shown', (widgetTester) async { 758 | addTearDown(() { 759 | widgetTester.binding 760 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 761 | }); 762 | 763 | await widgetTester.pumpWidget(sut); 764 | 765 | enableAppLockAfterLaunch(widgetTester); 766 | 767 | await setAppLifecycleToInactive(widgetTester); 768 | await setAppLifecycleToResumed(widgetTester); 769 | 770 | expect(find.byKey(const Key('LockScreen')), findsNothing); 771 | }); 772 | 773 | testWidgets('The unlocked app should be shown', (widgetTester) async { 774 | addTearDown(() { 775 | widgetTester.binding 776 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 777 | }); 778 | 779 | await widgetTester.pumpWidget(sut); 780 | 781 | enableAppLockAfterLaunch(widgetTester); 782 | 783 | await setAppLifecycleToInactive(widgetTester); 784 | await setAppLifecycleToResumed(widgetTester); 785 | 786 | expect(find.byKey(const Key('Unlocked')), findsOneWidget); 787 | }); 788 | 789 | testWidgets('The inactive screen should not be shown', 790 | (widgetTester) async { 791 | addTearDown(() { 792 | widgetTester.binding 793 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 794 | }); 795 | 796 | await widgetTester.pumpWidget(sut); 797 | 798 | enableAppLockAfterLaunch(widgetTester); 799 | 800 | await setAppLifecycleToInactive(widgetTester); 801 | await setAppLifecycleToResumed(widgetTester); 802 | 803 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 804 | }); 805 | }); 806 | 807 | group( 808 | 'When it is disabled and then enabled at runtime and the app transitions from inactive to hidden for longer than the lock duration and back to inactive and resumed', 809 | () { 810 | late Widget sut; 811 | 812 | setUp(() { 813 | sut = MaterialApp( 814 | builder: (context, child) => AppLock( 815 | initialBackgroundLockLatency: const Duration(seconds: 1), 816 | initiallyEnabled: false, 817 | builder: (context, launchArg) => KeyedSubtree( 818 | key: const Key('Unlocked'), 819 | child: child!, 820 | ), 821 | lockScreenBuilder: (context) => const Scaffold( 822 | key: Key('LockScreen'), 823 | ), 824 | inactiveBuilder: (context) => const Scaffold( 825 | key: Key('InactiveScreen'), 826 | ), 827 | ), 828 | home: const Scaffold(), 829 | ); 830 | }); 831 | 832 | testWidgets('The lock screen should be shown', (widgetTester) async { 833 | addTearDown(() { 834 | widgetTester.binding 835 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 836 | }); 837 | 838 | await widgetTester.pumpWidget(sut); 839 | 840 | enableAppLockAfterLaunch(widgetTester); 841 | 842 | await setAppLifecycleToInactive(widgetTester); 843 | await setAppLifecycleToHidden(widgetTester); 844 | 845 | await widgetTester.pumpAndSettle(const Duration(seconds: 2)); 846 | 847 | await setAppLifecycleToInactive(widgetTester); 848 | await setAppLifecycleToResumed(widgetTester); 849 | 850 | expect(find.byKey(const Key('LockScreen')), findsOneWidget); 851 | }); 852 | 853 | testWidgets('The unlocked app should not be shown', (widgetTester) async { 854 | addTearDown(() { 855 | widgetTester.binding 856 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 857 | }); 858 | 859 | await widgetTester.pumpWidget(sut); 860 | 861 | enableAppLockAfterLaunch(widgetTester); 862 | 863 | await setAppLifecycleToInactive(widgetTester); 864 | await setAppLifecycleToHidden(widgetTester); 865 | 866 | await widgetTester.pumpAndSettle(const Duration(seconds: 2)); 867 | 868 | await setAppLifecycleToInactive(widgetTester); 869 | await setAppLifecycleToResumed(widgetTester); 870 | 871 | expect(find.byKey(const Key('Unlocked')), findsNothing); 872 | }); 873 | 874 | testWidgets('The inactive screen should not be shown', 875 | (widgetTester) async { 876 | addTearDown(() { 877 | widgetTester.binding 878 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 879 | }); 880 | 881 | await widgetTester.pumpWidget(sut); 882 | 883 | enableAppLockAfterLaunch(widgetTester); 884 | 885 | await setAppLifecycleToInactive(widgetTester); 886 | await setAppLifecycleToHidden(widgetTester); 887 | 888 | await widgetTester.pumpAndSettle(const Duration(seconds: 2)); 889 | 890 | await setAppLifecycleToInactive(widgetTester); 891 | await setAppLifecycleToResumed(widgetTester); 892 | 893 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 894 | }); 895 | }); 896 | 897 | group( 898 | 'When it is disabled and then enabled at runtime and the app transitions from inactive to hidden for less than the lock duration and back to inactive and resumed', 899 | () { 900 | late Widget sut; 901 | 902 | setUp(() { 903 | sut = MaterialApp( 904 | builder: (context, child) => AppLock( 905 | initialBackgroundLockLatency: const Duration(seconds: 2), 906 | initiallyEnabled: false, 907 | builder: (context, launchArg) => KeyedSubtree( 908 | key: const Key('Unlocked'), 909 | child: child!, 910 | ), 911 | lockScreenBuilder: (context) => const Scaffold( 912 | key: Key('LockScreen'), 913 | ), 914 | inactiveBuilder: (context) => const Scaffold( 915 | key: Key('InactiveScreen'), 916 | ), 917 | ), 918 | home: const Scaffold(), 919 | ); 920 | }); 921 | 922 | testWidgets('The lock screen should not be shown', (widgetTester) async { 923 | addTearDown(() { 924 | widgetTester.binding 925 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 926 | }); 927 | 928 | await widgetTester.pumpWidget(sut); 929 | 930 | enableAppLockAfterLaunch(widgetTester); 931 | 932 | await setAppLifecycleToInactive(widgetTester); 933 | await setAppLifecycleToHidden(widgetTester); 934 | 935 | await widgetTester.pumpAndSettle(const Duration(seconds: 1)); 936 | 937 | await setAppLifecycleToInactive(widgetTester); 938 | await setAppLifecycleToResumed(widgetTester); 939 | 940 | expect(find.byKey(const Key('LockScreen')), findsNothing); 941 | }); 942 | 943 | testWidgets('The unlocked app should be shown', (widgetTester) async { 944 | addTearDown(() { 945 | widgetTester.binding 946 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 947 | }); 948 | 949 | await widgetTester.pumpWidget(sut); 950 | 951 | enableAppLockAfterLaunch(widgetTester); 952 | 953 | await setAppLifecycleToInactive(widgetTester); 954 | await setAppLifecycleToHidden(widgetTester); 955 | 956 | await widgetTester.pumpAndSettle(const Duration(seconds: 1)); 957 | 958 | await setAppLifecycleToInactive(widgetTester); 959 | await setAppLifecycleToResumed(widgetTester); 960 | 961 | expect(find.byKey(const Key('Unlocked')), findsOneWidget); 962 | }); 963 | 964 | testWidgets('The inactive screen should not be shown', 965 | (widgetTester) async { 966 | addTearDown(() { 967 | widgetTester.binding 968 | .handleAppLifecycleStateChanged(AppLifecycleState.resumed); 969 | }); 970 | 971 | await widgetTester.pumpWidget(sut); 972 | 973 | enableAppLockAfterLaunch(widgetTester); 974 | 975 | await setAppLifecycleToInactive(widgetTester); 976 | await setAppLifecycleToHidden(widgetTester); 977 | 978 | await widgetTester.pumpAndSettle(const Duration(seconds: 1)); 979 | 980 | await setAppLifecycleToInactive(widgetTester); 981 | await setAppLifecycleToResumed(widgetTester); 982 | 983 | expect(find.byKey(const Key('InactiveScreen')), findsNothing); 984 | }); 985 | }); 986 | }); 987 | } 988 | --------------------------------------------------------------------------------