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..7beae1f 100644 --- a/android/src/main/kotlin/org/jezequel/secure_application/SecureApplicationPlugin.kt +++ b/android/src/main/kotlin/org/jezequel/secure_application/SecureApplicationPlugin.kt @@ -85,6 +85,12 @@ public class SecureApplicationPlugin: FlutterPlugin, MethodCallHandler, Activity } else if (call.method == "open") { activity?.window?.clearFlags(LayoutParams.FLAG_SECURE) 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/ios/Classes/SwiftSecureApplicationPlugin.swift b/ios/Classes/SwiftSecureApplicationPlugin.swift index 2c59faa..36b4356 100644 --- a/ios/Classes/SwiftSecureApplicationPlugin.swift +++ b/ios/Classes/SwiftSecureApplicationPlugin.swift @@ -4,7 +4,9 @@ import UIKit public class SwiftSecureApplicationPlugin: NSObject, FlutterPlugin { var secured = false; var opacity: CGFloat = 0.2; - + var useLaunchImage: Bool = false; + var backgroundColor: UIColor = UIColor.white; + var backgroundTask: UIBackgroundTaskIdentifier! internal let registrar: FlutterPluginRegistrar @@ -28,29 +30,51 @@ public class SwiftSecureApplicationPlugin: NSObject, FlutterPlugin { 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 + if (useLaunchImage) { + if let existingView = window.viewWithTag(99697) { + window.bringSubviewToFront(existingView) + return + } else { + let imageView = UIImageView.init(frame: window.bounds) + imageView.tag = 99697 + imageView.backgroundColor = backgroundColor + 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)) + } } 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)) + 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 = backgroundColor.withAlphaComponent(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() @@ -72,30 +96,79 @@ public class SwiftSecureApplicationPlugin: NSObject, FlutterPlugin { 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 + 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 - } + } 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() - - }) + }).first { + if let colorView = window.viewWithTag(99699), let blurrView = window.viewWithTag(99698) { + UIView.animate(withDuration: 0.5, animations: { + colorView.alpha = 0.0 + }, completion: { finished in + colorView.removeFromSuperview() + blurrView.removeFromSuperview() + }) + } + + if let imageView = window.viewWithTag(99697) { + UIView.animate(withDuration: 0.3, animations: { + imageView.alpha = 0.0 + }, completion: { finished in + imageView.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_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)), ), ),