diff --git a/README.md b/README.md
index b9ee402..d9d335f 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,22 @@
**previously nammed secure_window**
+Forked from [neckaros](https://github.com/neckaros/secure_application) & [stevenspiel](https://github.com/stevenspiel/secure_application)
+
+### Extended features:
+- Able to use LaunchImage in background in IOS
+- LaunchImage and BlurrEffect can work together
+
# secure_application
This plugin allow you to protect your application content from view on demand
-
+
Pluggin in iOS is in swift
Pluggin in Android is in Kotlin / AndroidX libraries
-Pluggin is also working for web
+Pluggin is also working for web
Plugin work for windows (will lock when you minimize the window and lock screen)
@@ -19,15 +25,31 @@ Plugin work for windows (will lock when you minimize the window and lock screen)
### Installation
Add `secure_application` as a dependency in your pubspec.yaml file ([what?](https://pub.dev/packages/secure_application#-installing-tab-)).
+To use this branch, in `pubspec.yaml`:
+```
+...
+dependencies:
+ ...
+ secure_application:
+ git:
+ url: https://github.com/eddyuan/secure_application
+```
+
+### Use useLaunchImageIOS
+Open your_project/ios in xcode, add LaunchImage if not already exist
+
+
### Import
Import secure_application:
+
```dart
import 'package:secure_application/secure_application.dart';
```
Add a top level SecureApplication
+
```dart
SecureApplication(
onNeedUnlock: (secure) => print(
@@ -37,9 +59,13 @@ SecureApplication(
```
Put the content you want to protect in a SecureGate (could be the whole app)
+
```dart
SecureGate(
+ useLaunchImageIOS: true,
blurr: 5,
+ opacity: 0.5,
+ backgroundColor: Colors.white,
lockedBuilder: (context, secureNotifier) => Center(
child: RaisedButton(
child: Text('test'),
@@ -50,8 +76,11 @@ SecureGate(
```
## Tips
+
### Placement
+
Best place to add the secure application is directly **inside** the MaterialApp by using its builder:
+
```dart
class MyApp extends StatelessWidget {
final navigatorKey = GlobalKey();
@@ -131,12 +160,14 @@ class MyApp extends StatelessWidget {
}
```
-Notice 3 importants part here:
-* Secure application is in MaterialApp/Builder so just above your app naviagator
-* I wrap the route i want to protect in onGenerateRoute with a **SecureGate** but you could put it anywhare you want if you only want to protect part of your page
-* Ask validation will display a dialog above everything to ask is you want to unlock app or not
+Notice 3 importants part here:
+
+- Secure application is in MaterialApp/Builder so just above your app naviagator
+- I wrap the route i want to protect in onGenerateRoute with a **SecureGate** but you could put it anywhare you want if you only want to protect part of your page
+- Ask validation will display a dialog above everything to ask is you want to unlock app or not
### react to failed auth
+
```dart
class SecureReacting extends StatefulWidget {
@override
@@ -175,13 +206,13 @@ class _SecureReactingState extends State {
}
```
-Notice 2 importants part here:
-* You are subscribing to a stream so don't forget to unsubscribe in onDispose
-* Since your animation from pop can take some time you might want also to have a locked variable here to display different content during transition
-
+Notice 2 importants part here:
+- You are subscribing to a stream so don't forget to unsubscribe in onDispose
+- Since your animation from pop can take some time you might want also to have a locked variable here to display different content during transition
## API Docs
+
[API Reference](https://pub.dev/documentation/secure_application/latest/)
## Basic understanding
@@ -189,30 +220,35 @@ Notice 2 importants part here:
The library is mainly controller via the [SecureApplicationController](https://pub.dev/documentation/secure_application/latest/secure_application_controller/SecureApplicationController-class.html) which can be
### secured
+
if the user switch app or leave app the content will not be visible in the app switcher
and when it goes back to the app it will **lock** it
### locked
+
the child of the **SecureGate**s will be hidden bellow the blurry barrier
### paused
+
even if secured **SecureGate**s will not activate when user comes back to the app
### authenticated
+
last authentication status. To help you manage visibility of some elements of your UI
depending on auth status
-* **secureApplicationController.authFailed()** will set it to **false**
-* **secureApplicationController.authLogout()** will set it to **false**
-* **secureApplicationController.authSuccess()** will set it to **true**
+- **secureApplicationController.authFailed()** will set it to **false**
+- **secureApplicationController.authLogout()** will set it to **false**
+- **secureApplicationController.authSuccess()** will set it to **true**
You could use the authenticationEvents for the same purpose as it is a BehaviorSubject stream
### Streams
+
There is two different BehaviorStream (emit last value when you subscribe):
-* **authenticationEvents**: When there is a successful or unsucessful authentification (you can use it for example to clean your app if authentification is not successful)
-* **lockEvents**: Will be called when the application lock or unlock. Usefull for example to pause media when the app lock
+- **authenticationEvents**: When there is a successful or unsucessful authentification (you can use it for example to clean your app if authentification is not successful)
+- **lockEvents**: Will be called when the application lock or unlock. Usefull for example to pause media when the app lock
## Example
@@ -225,21 +261,25 @@ This tool does not impose a way to authenticate the user to give them back acces
You are free to use your own Widget/Workflow to allow user to see their content once locked (cf **SecureApplication** widget argument [onNeedUnlock](https://pub.dev/documentation/secure_application/latest/secure_application/SecureApplication/onNeedUnlock.html))
Therefore you can use any method you like:
-* Your own Widget for code Authentication
-* biometrics with the [local_auth](https://pub.dev/packages/local_auth) package
-* ...
+
+- Your own Widget for code Authentication
+- biometrics with the [local_auth](https://pub.dev/packages/local_auth) package
+- ...
## Android
+
On Android as soon as you **secure** the application the user will not be able to capture the screen in the app (even if unlocked)
We might give an option to allow screenshot as an option if need arise
## iOS
+
Contrary to Android we create a native frosted view over your app content so that content is not visible in the app switcher.
When the user gets back to the app we wait for ~500ms to remove this view to allow time for the app to woke and flutter gate to draw
# Widgets
## SecureApplication
+
[Api Doc](https://pub.dev/documentation/secure_application/latest/secure_application/SecureApplication-class.html)
this widget is **required** and need to be a parent of any Gate
it provides to its descendant a SecureApplicationProvider that allow you to secure or open the application
@@ -247,6 +287,7 @@ it provides to its descendant a SecureApplicationProvider that allow you to secu
You can pass you own initialized SecureApplicationController if you want to set default values
## SecureGate
+
[Api Doc](https://pub.dev/documentation/secure_application/latest/secure_gate/SecureGate-class.html)
The **child** of this widget will be below a blurry barrier (control the amount of **blurr** and **opacity** with its arguments)
if the provided SecureApplicationController is **locked**
@@ -254,20 +295,26 @@ if the provided SecureApplicationController is **locked**
# Native workings
## Android
+
When **locked** we set the secure flag to true
+
```kotlin
activity?.window?.addFlags(LayoutParams.FLAG_SECURE)
```
+
When **opened** we remove the secure flag
+
```kotlin
activity?.window?.clearFlags(LayoutParams.FLAG_SECURE)
```
## iOS
+
When app will become inactive we add a top view with a blurr filter
We remove this app 500ms after the app become active to avoid your content form being breifly visible
# Because we all want to see code in a Readme
+
```dart
Widget build(BuildContext context) {
var width = MediaQuery.of(context).size.width * 0.8;
@@ -397,4 +444,4 @@ Widget build(BuildContext context) {
),
);
}
-```
\ No newline at end of file
+```
diff --git a/android/src/main/kotlin/org/jezequel/secure_application/SecureApplicationPlugin.kt b/android/src/main/kotlin/org/jezequel/secure_application/SecureApplicationPlugin.kt
index 4f0b3ac..32b0682 100644
--- a/android/src/main/kotlin/org/jezequel/secure_application/SecureApplicationPlugin.kt
+++ b/android/src/main/kotlin/org/jezequel/secure_application/SecureApplicationPlugin.kt
@@ -84,7 +84,13 @@ public class SecureApplicationPlugin: FlutterPlugin, MethodCallHandler, Activity
result.success(true)
} else if (call.method == "open") {
activity?.window?.clearFlags(LayoutParams.FLAG_SECURE)
- result.success(true)
+ result.success(true)
+ } else if (call.method == "useLaunchImage") {
+ // This is currently not possible on Android, as far as I am aware
+ result.success(true)
+ } else if (call.method == "backgroundColor") {
+ // This is currently not possible on Android, as far as I am aware
+ result.success(true)
} else {
result.success(true)
}
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 9367d48..8d4492f 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 8.0
+ 9.0
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 1a3f02c..5c8a216 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -14,9 +14,9 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/secure_application/ios"
SPEC CHECKSUMS:
- Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
+ Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
secure_application: 27d424e8c2e770f63e38e280b5a51f921aa9b0c8
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
-COCOAPODS: 1.10.1
+COCOAPODS: 1.11.3
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index d605d68..40faf6d 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
@@ -156,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1020;
+ LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140c..3db53b6 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
UIViewControllerBasedStatusBarAppearance
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 2c8ce77..97e93fd 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -30,7 +30,7 @@ class _MyAppState extends State {
@override
Widget build(BuildContext context) {
- var width = MediaQuery.of(context).size.width * 0.8;
+ final double width = MediaQuery.of(context).size.width * 0.2;
return MaterialApp(
home: SecureApplication(
nativeRemoveDelay: 1000,
@@ -69,6 +69,8 @@ class _MyAppState extends State {
.listen((s) => history.add(
'${DateTime.now().toIso8601String()} - ${s ? 'locked' : 'unlocked'}'));
return SecureGate(
+ useLaunchImageIOS: true,
+ backgroundColor: Colors.white,
blurr: blurr,
opacity: opacity,
lockedBuilder: (context, secureNotifier) => Center(
@@ -89,46 +91,44 @@ class _MyAppState extends State {
appBar: AppBar(
title: const Text('Secure Window Example'),
),
- body: Center(
- child: Builder(builder: (context) {
- var valueNotifier = SecureApplicationProvider.of(context);
- if (valueNotifier == null)
- throw new Exception(
- 'Unable to find secure application context');
- return ListView(
- children: [
- Text('This is secure content'),
- ValueListenableBuilder(
- valueListenable: valueNotifier,
- builder: (context, state, _) => state.secured
- ? Column(
- children: [
- ElevatedButton(
- onPressed: () => valueNotifier.open(),
- child: Text('Open app'),
- ),
- state.paused
- ? ElevatedButton(
- onPressed: () =>
- valueNotifier.unpause(),
- child: Text('resume security'),
- )
- : ElevatedButton(
- onPressed: () =>
- valueNotifier.pause(),
- child: Text('pause security'),
- ),
- ],
- )
- : ElevatedButton(
- onPressed: () => valueNotifier.secure(),
- child: Text('Secure app'),
- ),
- ),
- if (failedAuth == null)
- Text(
- 'Lock the app then switch to another app and come back'),
- if (failedAuth != null)
+ body: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Center(
+ child: Builder(builder: (context) {
+ var valueNotifier = SecureApplicationProvider.of(context);
+ if (valueNotifier == null)
+ throw new Exception(
+ 'Unable to find secure application context');
+ return ListView(
+ children: [
+ Text('This is secure content'),
+ ValueListenableBuilder(
+ valueListenable: valueNotifier,
+ builder: (context, state, _) => state.secured
+ ? Column(
+ children: [
+ ElevatedButton(
+ onPressed: () => valueNotifier.open(),
+ child: Text('Open app'),
+ ),
+ state.paused
+ ? ElevatedButton(
+ onPressed: () =>
+ valueNotifier.unpause(),
+ child: Text('resume security'),
+ )
+ : ElevatedButton(
+ onPressed: () =>
+ valueNotifier.pause(),
+ child: Text('pause security'),
+ ),
+ ],
+ )
+ : ElevatedButton(
+ onPressed: () => valueNotifier.secure(),
+ child: Text('Secure app'),
+ ),
+ ),
failedAuth
? Text(
'Auth failed we cleaned sensitive data',
@@ -138,55 +138,57 @@ class _MyAppState extends State {
'Auth success',
style: TextStyle(color: Colors.green),
),
- FlutterLogo(
- size: width,
- ),
- StreamBuilder(
- stream: valueNotifier.authenticationEvents,
- builder: (context, snapshot) =>
- Text('Last auth status is: ${snapshot.data}'),
- ),
- ElevatedButton(
- onPressed: () => valueNotifier.lock(),
- child: Text('manually lock'),
- ),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Row(
- children: [
- Text('Blurr:'),
- Expanded(
- child: Slider(
- value: blurr,
- min: 0,
- max: 100,
- onChanged: (v) => setState(() => blurr = v)),
- ),
- Text(blurr.floor().toString()),
- ],
+ FlutterLogo(
+ size: width,
),
- ),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Row(
- children: [
- Text('opacity:'),
- Expanded(
- child: Slider(
- value: opacity,
- min: 0,
- max: 1,
- onChanged: (v) =>
- setState(() => opacity = v)),
- ),
- Text((opacity * 100).floor().toString() + "%"),
- ],
+ StreamBuilder(
+ stream: valueNotifier.authenticationEvents,
+ builder: (context, snapshot) =>
+ Text('Last auth status is: ${snapshot.data}'),
),
- ),
- ...history.map((h) => Text(h)).toList(),
- ],
- );
- }),
+ ElevatedButton(
+ onPressed: () => valueNotifier.lock(),
+ child: Text('manually lock'),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ children: [
+ Text('Blurr:'),
+ Expanded(
+ child: Slider(
+ value: blurr,
+ min: 0,
+ max: 100,
+ onChanged: (v) =>
+ setState(() => blurr = v)),
+ ),
+ Text(blurr.floor().toString()),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ children: [
+ Text('opacity:'),
+ Expanded(
+ child: Slider(
+ value: opacity,
+ min: 0,
+ max: 1,
+ onChanged: (v) =>
+ setState(() => opacity = v)),
+ ),
+ Text((opacity * 100).floor().toString() + "%"),
+ ],
+ ),
+ ),
+ ...history.map((h) => Text(h)).toList(),
+ ],
+ );
+ }),
+ ),
),
),
);
diff --git a/example/pubspec.lock b/example/pubspec.lock
index b5e1268..cc06998 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -42,7 +42,7 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
- version: "1.15.0"
+ version: "1.16.0"
cupertino_icons:
dependency: "direct main"
description:
@@ -56,7 +56,7 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0"
+ version: "1.3.0"
flutter:
dependency: "direct main"
description: flutter
@@ -85,7 +85,7 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
- version: "0.6.3"
+ version: "0.6.4"
matcher:
dependency: transitive
description:
@@ -93,6 +93,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.4"
meta:
dependency: transitive
description:
@@ -106,14 +113,14 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0"
+ version: "1.8.1"
rxdart:
dependency: transitive
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
- version: "0.26.0"
+ version: "0.27.4"
secure_application:
dependency: "direct dev"
description:
@@ -132,7 +139,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.1"
+ version: "1.8.2"
stack_trace:
dependency: transitive
description:
@@ -167,21 +174,14 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.4.3"
- typed_data:
- dependency: transitive
- description:
- name: typed_data
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.0"
+ version: "0.4.9"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.1"
+ version: "2.1.2"
sdks:
- dart: ">=2.14.0 <3.0.0"
+ dart: ">=2.17.0-0 <3.0.0"
flutter: ">=1.20.0"
diff --git a/example/screenshot/launch_image.png b/example/screenshot/launch_image.png
new file mode 100644
index 0000000..1db4e10
Binary files /dev/null and b/example/screenshot/launch_image.png differ
diff --git a/example/screenshot/xcode_setting.png b/example/screenshot/xcode_setting.png
new file mode 100644
index 0000000..8a9b3f5
Binary files /dev/null and b/example/screenshot/xcode_setting.png differ
diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc
index b359ee1..3a931bf 100644
--- a/example/windows/flutter/generated_plugin_registrant.cc
+++ b/example/windows/flutter/generated_plugin_registrant.cc
@@ -2,6 +2,8 @@
// Generated file. Do not edit.
//
+// clang-format off
+
#include "generated_plugin_registrant.h"
#include
diff --git a/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h
index 9846246..dc139d8 100644
--- a/example/windows/flutter/generated_plugin_registrant.h
+++ b/example/windows/flutter/generated_plugin_registrant.h
@@ -2,6 +2,8 @@
// Generated file. Do not edit.
//
+// clang-format off
+
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake
index 6a5fe7f..b331b9c 100644
--- a/example/windows/flutter/generated_plugins.cmake
+++ b/example/windows/flutter/generated_plugins.cmake
@@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
secure_application
)
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
@@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/ios/Classes/SwiftSecureApplicationPlugin.swift b/ios/Classes/SwiftSecureApplicationPlugin.swift
index 2fab50e..61f6338 100644
--- a/ios/Classes/SwiftSecureApplicationPlugin.swift
+++ b/ios/Classes/SwiftSecureApplicationPlugin.swift
@@ -2,100 +2,264 @@ import Flutter
import UIKit
public class SwiftSecureApplicationPlugin: NSObject, FlutterPlugin {
- var secured = false;
- var opacity: CGFloat = 0.2;
-
- var backgroundTask: UIBackgroundTaskIdentifier!
-
- internal let registrar: FlutterPluginRegistrar
-
- init(registrar: FlutterPluginRegistrar) {
- self.registrar = registrar
- super.init()
- registrar.addApplicationDelegate(self)
- }
-
+ var secured = false;
+ var opacity: CGFloat = 0.2;
+ var useLaunchImage: Bool = false;
+ var backgroundColor: UIColor = UIColor.white;
+
+ var backgroundTask: UIBackgroundTaskIdentifier!
+
+// let IMAGE_VIEW_TAG = 99697;
+// let BLUR_VIEW_TAG = 99698;
+// let COLOR_VIEW_TAG = 99699;
+
+ var lockView: UIView?
+ var isInFadeIn: Bool = false
+
+ // let logoWidthToScreenWidthRatio: CGFloat = 0.5
+ // let logoWidthToHeightRatio: CGFloat = 1.0
+ let animationDuration: CFTimeInterval = 0.3
+
+ internal let registrar: FlutterPluginRegistrar
+
+ init(registrar: FlutterPluginRegistrar) {
+ self.registrar = registrar
+ super.init()
+ registrar.addApplicationDelegate(self)
+ }
+
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "secure_application", binaryMessenger: registrar.messenger())
let instance = SwiftSecureApplicationPlugin(registrar: registrar)
registrar.addMethodCallDelegate(instance, channel: channel)
}
-
+
+ private func createLockView(window: UIWindow, isDisplayingLogo: Bool) {
+ dismissLockView()
+
+ lockView = UIView(frame: window.bounds);
+ lockView?.alpha = 0.0
+ window.addSubview(lockView!)
+
+ let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.extraLight)
+ let blurView = UIVisualEffectView(effect: blurEffect)
+ blurView.frame = window.bounds
+ lockView!.addSubview(blurView)
+
+ let opacityView = UIView(frame: window.bounds)
+ opacityView.backgroundColor = backgroundColor
+ opacityView.alpha = opacity
+ lockView!.addSubview(opacityView)
+
+ if isDisplayingLogo {
+ // let logoWidth = window.frame.width * logoWidthToScreenWidthRatio
+ // let logoHeight = logoWidth / logoWidthToHeightRatio
+ // let logoRect = CGRect(x: (window.frame.width - logoWidth) * 0.5,
+ // y: (window.frame.height - logoHeight) * 0.5,
+ // width: logoWidth,
+ // height: logoHeight)
+ let logoView = UIImageView(image: UIImage(named: "LaunchImage"))
+ logoView.frame = window.bounds
+ logoView.contentMode = .center
+ // logoView.frame = logoRect
+ lockView!.addSubview(logoView)
+ }
+
+ isInFadeIn = true
+ lockView?.layer.removeAllAnimations()
+ UIView.transition(with: lockView!,
+ duration: animationDuration,
+ options: .transitionCrossDissolve,
+ animations: {
+ self.lockView?.alpha = 1.0
+ self.isInFadeIn = false
+ window.snapshotView(afterScreenUpdates: true)
+ })
+ }
+
+ private func dismissLockView() {
+ guard lockView != nil else {
+ return
+ }
+
+ lockView?.layer.removeAllAnimations()
+ UIView.transition(with: lockView!,
+ duration: animationDuration,
+ options: .transitionCrossDissolve,
+ animations: {
+ self.lockView?.alpha = 0.0
+ }) { (finished) in
+ if finished && self.lockView != nil && self.isInFadeIn {
+ for subview in self.lockView!.subviews { subview.removeFromSuperview() }
+ self.lockView?.removeFromSuperview()
+ self.lockView = nil
+ }
+ }
+ }
+
+// public func applicationDidBecomeActive(_ application: UIApplication) {
+// dismissLockView()
+// }
+
public func applicationWillResignActive(_ application: UIApplication) {
if ( secured ) {
- self.registerBackgroundTask()
- UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch()
- if let window = UIApplication.shared.windows.filter({ (w) -> Bool in
- return w.isHidden == false
- }).first {
- if let existingView = window.viewWithTag(99699), let existingBlurrView = window.viewWithTag(99698) {
- window.bringSubviewToFront(existingView)
- window.bringSubviewToFront(existingBlurrView)
- return
- } else {
- let colorView = UIView(frame: window.bounds);
- colorView.tag = 99699
- colorView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
- colorView.backgroundColor = UIColor(white: 1, alpha: opacity)
- window.addSubview(colorView)
- window.bringSubviewToFront(colorView)
-
- let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.extraLight)
- let blurEffectView = UIVisualEffectView(effect: blurEffect)
- blurEffectView.frame = window.bounds
- blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
-
- blurEffectView.tag = 99698
-
- window.addSubview(blurEffectView)
- window.bringSubviewToFront(blurEffectView)
- window.snapshotView(afterScreenUpdates: true)
- RunLoop.current.run(until: Date(timeIntervalSinceNow:0.5))
- }
- }
- self.endBackgroundTask()
+ self.registerBackgroundTask()
+ UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch()
+ if let window = UIApplication.shared.windows.filter({ (w) -> Bool in
+ return w.isHidden == false
+ }).first {
+ createLockView(window: window, isDisplayingLogo: useLaunchImage)
+ // if (!useLaunchImage) {
+ // if let existingColorView = window.viewWithTag(COLOR_VIEW_TAG) {
+ // existingColorView.frame = window.bounds
+ // existingColorView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ // window.bringSubviewToFront(existingColorView)
+ // } else {
+ // let colorView = UIView(frame: window.bounds);
+ // colorView.tag = COLOR_VIEW_TAG
+ // colorView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ // colorView.backgroundColor = backgroundColor.withAlphaComponent(opacity)
+ // window.addSubview(colorView)
+ // window.bringSubviewToFront(colorView)
+ // }
+ // }
+ //
+ // if let existingBlurrView = window.viewWithTag(BLUR_VIEW_TAG) {
+ // existingBlurrView.frame = window.bounds
+ // existingBlurrView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ // window.bringSubviewToFront(existingBlurrView)
+ // } else {
+ // let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.extraLight)
+ // let blurEffectView = UIVisualEffectView(effect: blurEffect)
+ // blurEffectView.frame = window.bounds
+ // blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ // blurEffectView.tag = BLUR_VIEW_TAG
+ // window.addSubview(blurEffectView)
+ // window.bringSubviewToFront(blurEffectView)
+ // }
+ //
+ // if (useLaunchImage) {
+ // if let existingImageView = window.viewWithTag(IMAGE_VIEW_TAG) {
+ // existingImageView.frame = window.bounds
+ // existingImageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ // existingImageView.backgroundColor = backgroundColor.withAlphaComponent(opacity)
+ // existingImageView.clipsToBounds = true
+ // existingImageView.contentMode = .center
+ // window.bringSubviewToFront(existingImageView)
+ // } else {
+ // let imageView = UIImageView.init(frame: window.bounds)
+ // imageView.tag = IMAGE_VIEW_TAG
+ // imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ // imageView.backgroundColor = backgroundColor.withAlphaComponent(opacity)
+ // imageView.clipsToBounds = true
+ // imageView.contentMode = .center
+ // imageView.image = UIImage(named: "LaunchImage")
+ // imageView.isMultipleTouchEnabled = true
+ // imageView.translatesAutoresizingMaskIntoConstraints = false
+ // window.addSubview(imageView)
+ // window.bringSubviewToFront(imageView)
+ // }
+ // }
+ //
+ // window.snapshotView(afterScreenUpdates: true)
+ // RunLoop.current.run(until: Date(timeIntervalSinceNow:0.5))
+ }
+ self.endBackgroundTask()
}
}
- func registerBackgroundTask() {
- self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
- self?.endBackgroundTask()
- }
- assert(self.backgroundTask != UIBackgroundTaskIdentifier.invalid)
- }
-
- func endBackgroundTask() {
- print("Background task ended.")
- UIApplication.shared.endBackgroundTask(backgroundTask)
- backgroundTask = UIBackgroundTaskIdentifier.invalid
+ func registerBackgroundTask() {
+ self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
+ self?.endBackgroundTask()
}
-
+ assert(self.backgroundTask != UIBackgroundTaskIdentifier.invalid)
+ }
+
+ func endBackgroundTask() {
+ print("Background task ended.")
+ UIApplication.shared.endBackgroundTask(backgroundTask)
+ backgroundTask = UIBackgroundTaskIdentifier.invalid
+ }
+
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if (call.method == "secure") {
- secured = true;
- if let args = call.arguments as? Dictionary,
- let opacity = args["opacity"] as? NSNumber {
- self.opacity = opacity as! CGFloat
+ secured = true;
+ if let args = call.arguments as? Dictionary {
+ if let opacity = args["opacity"] as? NSNumber {
+ self.opacity = opacity as! CGFloat
}
+
+ if let useLaunchImage = args["useLaunchImage"] as? Bool {
+ self.useLaunchImage = useLaunchImage
+ }
+
+ if let backgroundColor = args["backgroundColor"] as? String {
+ self.backgroundColor = hexStringToUIColor(hex: backgroundColor)
+ }
+ }
} else if (call.method == "open") {
- secured = false;
- } else if (call.method == "opacity") {
- if let args = call.arguments as? Dictionary,
- let opacity = args["opacity"] as? NSNumber {
- self.opacity = opacity as! CGFloat
- }
+ secured = false;
+ } else if (call.method == "opacity") {
+ if let args = call.arguments as? Dictionary,
+ let opacity = args["opacity"] as? NSNumber {
+ self.opacity = opacity as! CGFloat
+ }
+ } else if (call.method == "backgroundColor") {
+ if let args = call.arguments as? Dictionary,
+ let backgroundColor = args["backgroundColor"] as? String {
+ self.backgroundColor = hexStringToUIColor(hex: backgroundColor)
+ }
+ } else if (call.method == "useLaunchImage") {
+ if let args = call.arguments as? Dictionary,
+ let useLaunchImage = args["useLaunchImage"] as? Bool {
+ self.useLaunchImage = useLaunchImage
+ }
} else if (call.method == "unlock") {
- if let window = UIApplication.shared.windows.filter({ (w) -> Bool in
- return w.isHidden == false
- }).first, let view = window.viewWithTag(99699), let blurrView = window.viewWithTag(99698) {
- UIView.animate(withDuration: 0.5, animations: {
- view.alpha = 0.0
- blurrView.alpha = 0.0
- }, completion: { finished in
- view.removeFromSuperview()
- blurrView.removeFromSuperview()
-
- })
- }
+ dismissLockView()
+ // if let window = UIApplication.shared.windows.filter({ (w) -> Bool in
+ // return w.isHidden == false
+ // }).first {
+ // if let colorView = window.viewWithTag(COLOR_VIEW_TAG) {
+ // UIView.animate(withDuration: 0.4, animations: {
+ // colorView.alpha = 0.0
+ // }, completion: { finished in
+ // colorView.removeFromSuperview()
+ // })
+ // }
+ //
+ // if let imageView = window.viewWithTag(IMAGE_VIEW_TAG) {
+ // UIView.animate(withDuration: 0.4, animations: {
+ // imageView.alpha = 0.0
+ // }, completion: { finished in
+ // imageView.removeFromSuperview()
+ // })
+ // }
+ //
+ // if let blurrView = window.viewWithTag(BLUR_VIEW_TAG) {
+ // blurrView.removeFromSuperview()
+ // }
+ // }
+ }
+ }
+
+ func hexStringToUIColor (hex:String) -> UIColor {
+ var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
+
+ if (cString.hasPrefix("#")) {
+ cString.remove(at: cString.startIndex)
+ }
+
+ if ((cString.count) != 6) {
+ return UIColor.gray
}
+
+ var rgbValue:UInt64 = 0
+ Scanner(string: cString).scanHexInt64(&rgbValue)
+
+ return UIColor(
+ red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
+ green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
+ blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
+ alpha: CGFloat(1.0)
+ )
}
}
diff --git a/lib/secure_application.dart b/lib/secure_application.dart
index 3ab34a0..57f7362 100644
--- a/lib/secure_application.dart
+++ b/lib/secure_application.dart
@@ -99,7 +99,7 @@ class _SecureApplicationState extends State
}
});
super.initState();
- WidgetsBinding.instance!.addObserver(this);
+ WidgetsBinding.instance.addObserver(this);
SecureApplicationNative.registerForEvents(
secureApplicationController.lockIfSecured,
secureApplicationController.unlock);
@@ -109,7 +109,7 @@ class _SecureApplicationState extends State
void dispose() {
_authStreamSubscription?.cancel();
super.dispose();
- WidgetsBinding.instance!.removeObserver(this);
+ WidgetsBinding.instance.removeObserver(this);
}
@override
@@ -131,7 +131,7 @@ class _SecureApplicationState extends State
if (authStatus != null)
secureApplicationController.sendAuthenticationEvent(authStatus);
- WidgetsBinding.instance!.addPostFrameCallback((_) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
secureApplicationController.unpause();
});
}
diff --git a/lib/secure_application_native.dart b/lib/secure_application_native.dart
index 6f710c0..69934c8 100644
--- a/lib/secure_application_native.dart
+++ b/lib/secure_application_native.dart
@@ -1,6 +1,6 @@
import 'dart:async';
+import 'dart:ui';
-import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
class SecureApplicationNative {
@@ -45,4 +45,16 @@ class SecureApplicationNative {
static Future opacity(double opacity) {
return _channel.invokeMethod('opacity', {"opacity": opacity});
}
+
+ static Future useLaunchImageIOS(bool useLaunchImage) {
+ return _channel.invokeMethod('useLaunchImage', {
+ 'useLaunchImage': useLaunchImage,
+ });
+ }
+
+ static Future backgroundColor(Color color) {
+ return _channel.invokeMethod('backgroundColor', {
+ 'backgroundColor': '#${color.value.toRadixString(16).substring(2, 8)}',
+ });
+ }
}
diff --git a/lib/secure_gate.dart b/lib/secure_gate.dart
index 0c51b82..bef6fb7 100644
--- a/lib/secure_gate.dart
+++ b/lib/secure_gate.dart
@@ -1,9 +1,9 @@
import 'dart:ui';
import 'package:flutter/material.dart';
+import 'package:secure_application/secure_application_controller.dart';
import 'package:secure_application/secure_application_native.dart';
import 'package:secure_application/secure_application_provider.dart';
-import 'package:secure_application/secure_application_controller.dart';
/// it will display a blurr over your content if locked
///
@@ -27,13 +27,30 @@ class SecureGate extends StatefulWidget {
/// default to 0.6
final double opacity;
+ /// Whether to use the application's LaunchImage in the app switcher.
+ ///
+ /// For this to work, you MUST have a LaunchImage ImageSet in your iOS folder,
+ /// just like a newly generated flutter application (at ios/Runner/Assets.xcassets/LaunchImage.imageset)
+ /// More info here: https://docs.flutter.dev/development/ui/advanced/splash-screen#ios-launch-screen
+ ///
+ /// If this is true, [opacity] and [blurr] are ignored (iOS only).
+ ///
+ /// Only available on iOS. It is not possible on Android, as far as I'm aware
+ final bool useLaunchImageIOS;
+
+ /// The background color in the app switcher.
+ final Color? backgroundColor;
+
const SecureGate({
Key? key,
required this.child,
this.blurr = 20,
this.opacity = 0.6,
this.lockedBuilder,
+ this.useLaunchImageIOS = false,
+ this.backgroundColor,
}) : super(key: key);
+
@override
_SecureGateState createState() => _SecureGateState();
}
@@ -50,6 +67,10 @@ class _SecureGateState extends State
AnimationController(vsync: this, duration: kThemeAnimationDuration * 2)
..addListener(_handleChange);
SecureApplicationNative.opacity(widget.opacity);
+ SecureApplicationNative.useLaunchImageIOS(widget.useLaunchImageIOS);
+ if (widget.backgroundColor != null) {
+ SecureApplicationNative.backgroundColor(widget.backgroundColor!);
+ }
super.initState();
}
@@ -70,6 +91,13 @@ class _SecureGateState extends State
if (oldWidget.opacity != widget.opacity) {
SecureApplicationNative.opacity(widget.opacity);
}
+ if (oldWidget.useLaunchImageIOS != widget.useLaunchImageIOS) {
+ SecureApplicationNative.useLaunchImageIOS(widget.useLaunchImageIOS);
+ }
+ if (oldWidget.backgroundColor != widget.backgroundColor &&
+ widget.backgroundColor != null) {
+ SecureApplicationNative.backgroundColor(widget.backgroundColor!);
+ }
}
void _sercureNotified() {
@@ -108,7 +136,7 @@ class _SecureGateState extends State
sigmaY: widget.blurr * _gateVisibility.value),
child: Container(
decoration: BoxDecoration(
- color: Colors.grey.shade200
+ color: (widget.backgroundColor ?? Colors.grey.shade200)
.withOpacity(widget.opacity * _gateVisibility.value)),
),
),
diff --git a/pubspec.lock b/pubspec.lock
index 0de7638..5aadeaf 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -42,14 +42,14 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
- version: "1.15.0"
+ version: "1.16.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0"
+ version: "1.3.0"
flutter:
dependency: "direct main"
description: flutter
@@ -61,7 +61,7 @@ packages:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.2"
+ version: "2.0.6"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -78,7 +78,7 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
- version: "0.6.3"
+ version: "0.6.4"
matcher:
dependency: transitive
description:
@@ -86,6 +86,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.4"
meta:
dependency: transitive
description:
@@ -99,14 +106,14 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0"
+ version: "1.8.1"
rxdart:
dependency: "direct main"
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
- version: "0.26.0"
+ version: "0.27.4"
sky_engine:
dependency: transitive
description: flutter
@@ -118,7 +125,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.1"
+ version: "1.8.2"
stack_trace:
dependency: transitive
description:
@@ -153,21 +160,14 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.4.3"
- typed_data:
- dependency: transitive
- description:
- name: typed_data
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.0"
+ version: "0.4.9"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.1"
+ version: "2.1.2"
sdks:
- dart: ">=2.14.0 <3.0.0"
- flutter: ">=1.20.0"
+ dart: ">=2.17.0-0 <3.0.0"
+ flutter: ">=2.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 0ad5727..4daa6a9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -14,7 +14,7 @@ dependencies:
flutter_web_plugins:
sdk: flutter
flutter_plugin_android_lifecycle: ^2.0.0
- rxdart: ^0.26.0
+ rxdart: ^0.27.4
dev_dependencies:
flutter_test: