From c1d216e98654fea0ec22ab49f9582674a8524e0b Mon Sep 17 00:00:00 2001 From: Related Code Date: Wed, 7 Apr 2021 15:20:24 +0200 Subject: [PATCH] 1.0.0 --- PasscodeKit.podspec | 18 + PasscodeKit/Sources/PasscodeKit.swift | 292 +++++++++++++ PasscodeKit/Sources/PasscodeKitChange.swift | 227 ++++++++++ PasscodeKit/Sources/PasscodeKitCreate.swift | 148 +++++++ PasscodeKit/Sources/PasscodeKitRemove.swift | 152 +++++++ PasscodeKit/Sources/PasscodeKitText.swift | 84 ++++ PasscodeKit/Sources/PasscodeKitVerify.swift | 176 ++++++++ PasscodeKit/app.xcodeproj/project.pbxproj | 403 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + PasscodeKit/app/AppDelegate.swift | 59 +++ .../AppIcon.appiconset/0076.png | Bin 0 -> 4431 bytes .../AppIcon.appiconset/0120.png | Bin 0 -> 6936 bytes .../AppIcon.appiconset/0152.png | Bin 0 -> 7831 bytes .../AppIcon.appiconset/0167.png | Bin 0 -> 8903 bytes .../AppIcon.appiconset/0180.png | Bin 0 -> 9547 bytes .../AppIcon.appiconset/1024.png | Bin 0 -> 42860 bytes .../AppIcon.appiconset/Contents.json | 104 +++++ PasscodeKit/app/Assets.xcassets/Contents.json | 6 + .../app/Base.lproj/LaunchScreen.storyboard | 27 ++ PasscodeKit/app/Info.plist | 41 ++ PasscodeKit/app/PasscodeView.swift | 135 ++++++ PasscodeKit/app/PasscodeView.xib | 115 +++++ PasscodeKit/app/ViewController.swift | 76 ++++ PasscodeKit/app/ViewController.xib | 28 ++ README.md | 1 + VERSION.txt | 1 + 27 files changed, 2108 insertions(+) create mode 100644 PasscodeKit.podspec create mode 100755 PasscodeKit/Sources/PasscodeKit.swift create mode 100755 PasscodeKit/Sources/PasscodeKitChange.swift create mode 100755 PasscodeKit/Sources/PasscodeKitCreate.swift create mode 100755 PasscodeKit/Sources/PasscodeKitRemove.swift create mode 100755 PasscodeKit/Sources/PasscodeKitText.swift create mode 100755 PasscodeKit/Sources/PasscodeKitVerify.swift create mode 100644 PasscodeKit/app.xcodeproj/project.pbxproj create mode 100644 PasscodeKit/app.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 PasscodeKit/app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 PasscodeKit/app/AppDelegate.swift create mode 100644 PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0076.png create mode 100644 PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0120.png create mode 100644 PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0152.png create mode 100644 PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0167.png create mode 100644 PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0180.png create mode 100644 PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/1024.png create mode 100644 PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100755 PasscodeKit/app/Assets.xcassets/Contents.json create mode 100644 PasscodeKit/app/Base.lproj/LaunchScreen.storyboard create mode 100644 PasscodeKit/app/Info.plist create mode 100644 PasscodeKit/app/PasscodeView.swift create mode 100644 PasscodeKit/app/PasscodeView.xib create mode 100644 PasscodeKit/app/ViewController.swift create mode 100644 PasscodeKit/app/ViewController.xib create mode 100644 VERSION.txt diff --git a/PasscodeKit.podspec b/PasscodeKit.podspec new file mode 100644 index 0000000..b55bb31 --- /dev/null +++ b/PasscodeKit.podspec @@ -0,0 +1,18 @@ +Pod::Spec.new do |s| + s.name = 'PasscodeKit' + s.version = '1.0.0' + s.license = 'MIT' + + s.summary = 'A lightweight and easy-to-use Passcode Kit for iOS.' + s.homepage = 'https://relatedcode.com' + s.author = { 'Related Code' => 'info@relatedcode.com' } + + s.source = { :git => 'https://github.com/relatedcode/PasscodeKit.git', :tag => s.version } + s.source_files = 'PasscodeKit/Sources/*.swift' + + s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' } + + s.swift_version = '5.0' + s.platform = :ios, '12.0' + s.requires_arc = true +end diff --git a/PasscodeKit/Sources/PasscodeKit.swift b/PasscodeKit/Sources/PasscodeKit.swift new file mode 100755 index 0000000..ea98982 --- /dev/null +++ b/PasscodeKit/Sources/PasscodeKit.swift @@ -0,0 +1,292 @@ +// +// Copyright (c) 2021 Related Code - https://relatedcode.com +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit +import CryptoKit + +//----------------------------------------------------------------------------------------------------------------------------------------------- +@objc public protocol PasscodeKitDelegate { + + @objc optional func passcodeCreated(_ passcode: String) + @objc optional func passcodeChanged(_ passcode: String) + @objc optional func passcodeRemoved() + + @objc optional func passcodeCheckedButDisabled() + @objc optional func passcodeEnteredSuccessfully() + @objc optional func passcodeMaximumFailedAttempts() +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +public class PasscodeKit: NSObject { + + static let shared: PasscodeKit = { + let instance = PasscodeKit() + return instance + }() + + static var passcodeLength = 4 + static var allowedFailedAttempts = 3 + + static var textColor = UIColor.darkText + static var backgroundColor = UIColor.lightGray + + static var failedTextColor = UIColor.white + static var failedBackgroundColor = UIColor.systemRed + + static var titleEnterPasscode = "Enter Passcode" + static var titleCreatePasscode = "Create Passcode" + static var titleChangePasscode = "Change Passcode" + static var titleRemovePasscode = "Remove Passcode" + + static var textEnterPasscode = "Enter your passcode" + static var textVerifyPasscode = "Verify your passcode" + static var textEnterOldPasscode = "Enter your old passcode" + static var textEnterNewPasscode = "Enter your new passcode" + static var textVerifyNewPasscode = "Verify your new passcode" + static var textFailedPasscode = "%d Failed Passcode Attempts" + static var textPasscodeMismatch = "Passcodes did not match. Try again." + static var textTouchIDAccessReason = "Please use Touch ID to unlock the app" + + public static var delegate: PasscodeKitDelegate? + + //------------------------------------------------------------------------------------------------------------------------------------------- + public override init() { + + super.init() + + if #available(iOS 13.0, *) { + PasscodeKit.textColor = UIColor.label + PasscodeKit.backgroundColor = UIColor.systemGroupedBackground + } else { + PasscodeKit.backgroundColor = UIColor.groupTableViewBackground + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func start() { + + shared.start() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func dismiss() { + + if (PasscodeKit.enabled()) { + if let navigationController = shared.topViewController() as? UINavigationController { + if let presentedView = navigationController.viewControllers.first { + if (presentedView is PasscodeKitVerify) { + presentedView.dismiss(animated: true) + } + } + } + } + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKit { + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func start() { + + let didFinishLaunching = UIApplication.didFinishLaunchingNotification + let willEnterForeground = UIApplication.willEnterForegroundNotification + + NotificationCenter.default.addObserver(self, selector: #selector(verifyPasscode), name: didFinishLaunching, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(verifyPasscode), name: willEnterForeground, object: nil) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func verifyPasscode() { + + if (PasscodeKit.enabled()) { + if let viewController = topViewController() { + if (noPasscodePresented(viewController)) { + presentPasscodeVerify(viewController) + } + } + } else { + PasscodeKit.delegate?.passcodeCheckedButDisabled?() + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func presentPasscodeVerify(_ viewController: UIViewController) { + + DispatchQueue.main.async { + let passcodeKitVerify = PasscodeKitVerify() + passcodeKitVerify.delegate = PasscodeKit.delegate + let navController = PasscodeKitNavController(rootViewController: passcodeKitVerify) + viewController.present(navController, animated: false) + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func noPasscodePresented(_ viewController: UIViewController) -> Bool { + + var result = true + if let navigationController = viewController as? UINavigationController { + if let presentedView = navigationController.viewControllers.first { + if (presentedView is PasscodeKitCreate) { result = false } + if (presentedView is PasscodeKitChange) { result = false } + if (presentedView is PasscodeKitRemove) { result = false } + if (presentedView is PasscodeKitVerify) { result = false } + } + } + return result + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func topViewController() -> UIViewController? { + + var keyWindow: UIWindow? + + if #available(iOS 13.0, *) { + keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow } + } else { + keyWindow = UIApplication.shared.keyWindow + } + + var viewController = keyWindow?.rootViewController + while (viewController?.presentedViewController != nil) { + viewController = viewController?.presentedViewController + } + return viewController + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKit { + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func createPasscode(_ viewController: UIViewController) { + + let passcodeKitCreate = PasscodeKitCreate() + passcodeKitCreate.delegate = viewController as? PasscodeKitDelegate + let navController = PasscodeKitNavController(rootViewController: passcodeKitCreate) + viewController.present(navController, animated: true) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func changePasscode(_ viewController: UIViewController) { + + let passcodeKitChange = PasscodeKitChange() + passcodeKitChange.delegate = viewController as? PasscodeKitDelegate + let navController = PasscodeKitNavController(rootViewController: passcodeKitChange) + viewController.present(navController, animated: true) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func removePasscode(_ viewController: UIViewController) { + + let passcodeKitRemove = PasscodeKitRemove() + passcodeKitRemove.delegate = viewController as? PasscodeKitDelegate + let navController = PasscodeKitNavController(rootViewController: passcodeKitRemove) + viewController.present(navController, animated: true) + } +} + +// MARK: - Passcode methods +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKit { + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func enabled() -> Bool { + + return (UserDefaults.standard.string(forKey: "PasscodeValue") != nil) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func verify(_ passcode: String) -> Bool { + + if (passcode != "") { + return (UserDefaults.standard.string(forKey: "PasscodeValue") == sha256(passcode)) + } + return (UserDefaults.standard.string(forKey: "PasscodeValue") == nil) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func update(_ passcode: String) { + + if (passcode != "") { + UserDefaults.standard.set(sha256(passcode), forKey: "PasscodeValue") + } else { + UserDefaults.standard.removeObject(forKey: "PasscodeValue") + UserDefaults.standard.removeObject(forKey: "PasscodeBiometric") + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func remove() { + + UserDefaults.standard.removeObject(forKey: "PasscodeValue") + UserDefaults.standard.removeObject(forKey: "PasscodeBiometric") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func biometric() -> Bool { + + return UserDefaults.standard.bool(forKey: "PasscodeBiometric") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func biometric(_ value: Bool) { + + UserDefaults.standard.set(value, forKey: "PasscodeBiometric") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private class func sha256(_ text: String) -> String { + + if #available(iOS 13.0, *) { + let data = Data(text.utf8) + let hash = SHA256.hash(data: data) + return hash.compactMap { String(format: "%02x", $0) }.joined() + } + return text + } +} + +// MARK: - PasscodeKitNavController +//----------------------------------------------------------------------------------------------------------------------------------------------- +class PasscodeKitNavController: UINavigationController { + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidLoad() { + + super.viewDidLoad() + + if #available(iOS 13.0, *) { + self.isModalInPresentation = true + self.modalPresentationStyle = .fullScreen + } + + navigationBar.isTranslucent = false + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + + return .portrait + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + + return .portrait + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override var shouldAutorotate: Bool { + + return false + } +} diff --git a/PasscodeKit/Sources/PasscodeKitChange.swift b/PasscodeKit/Sources/PasscodeKitChange.swift new file mode 100755 index 0000000..a0c4e3f --- /dev/null +++ b/PasscodeKit/Sources/PasscodeKitChange.swift @@ -0,0 +1,227 @@ +// +// Copyright (c) 2021 Related Code - https://relatedcode.com +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +//----------------------------------------------------------------------------------------------------------------------------------------------- +private enum PasscodeState { + + case change1 + case change2 + case change3 + case complete +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +class PasscodeKitChange: UIViewController { + + private var state: PasscodeState! + private var passcode = "" + + private var failedAttempts = 0 + private var isPasscodeMismatch = false + + private var viewPasscode = UIView() + private var textPasscode: PasscodeKitText! + private var labelInfo = UILabel() + private var labelPasscodeMismatch = UILabel() + private var labelFailedAttempts = UILabel() + + var delegate: PasscodeKitDelegate? + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidLoad() { + + super.viewDidLoad() + + title = PasscodeKit.titleChangePasscode + + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel)) + + setupUI() + updateUI() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidAppear(_ animated: Bool) { + + super.viewDidAppear(animated) + textPasscode.becomeFirstResponder() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func actionCancel() { + + dismiss(animated: true) + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKitChange { + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func setupUI() { + + state = .change1 + + view.backgroundColor = PasscodeKit.backgroundColor + + viewPasscode.frame = CGRect(x: 0, y: 200, width: UIScreen.main.bounds.width, height: 120) + view.addSubview(viewPasscode) + + labelInfo.textAlignment = .center + labelInfo.textColor = PasscodeKit.textColor + labelInfo.font = UIFont.systemFont(ofSize: 17) + labelInfo.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 30) + viewPasscode.addSubview(labelInfo) + + if (textPasscode != nil) && (textPasscode.superview != nil) { + textPasscode.removeFromSuperview() + } + textPasscode = PasscodeKitText() + textPasscode.frame = CGRect(x: 0, y: 45, width: UIScreen.main.bounds.width, height: 30) + textPasscode.addTarget(self, action: #selector(textFieldDidChangeEditing(_:)), for: .editingChanged) + viewPasscode.addSubview(textPasscode) + + labelPasscodeMismatch.text = PasscodeKit.textPasscodeMismatch + labelPasscodeMismatch.textAlignment = .center + labelPasscodeMismatch.textColor = PasscodeKit.textColor + labelPasscodeMismatch.font = UIFont.systemFont(ofSize: 15) + labelPasscodeMismatch.frame = CGRect(x: 0, y: 90, width: UIScreen.main.bounds.width, height: 30) + labelPasscodeMismatch.isHidden = true + viewPasscode.addSubview(labelPasscodeMismatch) + + labelFailedAttempts.textAlignment = .center + labelFailedAttempts.textColor = PasscodeKit.failedTextColor + labelFailedAttempts.backgroundColor = PasscodeKit.failedBackgroundColor + labelFailedAttempts.font = UIFont.systemFont(ofSize: 15) + labelFailedAttempts.frame = CGRect(x: (UIScreen.main.bounds.width - 225) / 2, y: 90, width: 225, height: 30) + labelFailedAttempts.layer.cornerRadius = 15 + labelFailedAttempts.isHidden = true + labelFailedAttempts.clipsToBounds = true + viewPasscode.addSubview(labelFailedAttempts) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func updateUI() { + + if (state == .change1) { labelInfo.text = PasscodeKit.textEnterOldPasscode } + if (state == .change2) { labelInfo.text = PasscodeKit.textEnterNewPasscode } + if (state == .change3) { labelInfo.text = PasscodeKit.textVerifyNewPasscode } + + if (state == .change3) { + isPasscodeMismatch = false + } + + failedAttempts = 0 + textPasscode.text = "" + textPasscode.becomeFirstResponder() + labelPasscodeMismatch.isHidden = !isPasscodeMismatch + labelFailedAttempts.isHidden = true + animateViewPasscode() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func animateViewPasscode() { + + let originalXPos = viewPasscode.frame.origin.x + viewPasscode.frame.origin.x = originalXPos + (isPasscodeMismatch ? -250 : 250) + UIView.animate(withDuration: 0.15) { + self.viewPasscode.frame.origin.x = originalXPos + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func setupUIFailed() { + + let animation = CABasicAnimation(keyPath: "position") + animation.duration = 0.09 + animation.repeatCount = 2 + animation.isRemovedOnCompletion = true + animation.autoreverses = true + animation.fromValue = CGPoint(x: textPasscode.center.x - 10, y: textPasscode.center.y) + animation.toValue = CGPoint(x: textPasscode.center.x + 10, y: textPasscode.center.y) + textPasscode.layer.add(animation, forKey: "position") + + failedAttempts += 1 + labelFailedAttempts.isHidden = false + labelFailedAttempts.text = String(format: PasscodeKit.textFailedPasscode, failedAttempts) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + self.textPasscode.text = "" + } + + if (failedAttempts >= PasscodeKit.allowedFailedAttempts) { + delegate?.passcodeMaximumFailedAttempts?() + } + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKitChange { + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func textFieldDidChangeEditing(_ textField: UITextField) { + + let current = textField.text ?? "" + + if (current.count >= PasscodeKit.passcodeLength) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.actionPasscode(current) + } + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func actionPasscode(_ current: String) { + + if (state == .change1) { + actionVerify(current) + } else if (state == .change2) { + actionChange(current) + } else if (state == .change3) { + actionConfirm(current) + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func actionVerify(_ current: String) { + + if (PasscodeKit.verify(current)) { + state = .change2 + updateUI() + } else { + setupUIFailed() + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func actionChange(_ current: String) { + + state = .change3 + passcode = current + updateUI() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func actionConfirm(_ current: String) { + + if (passcode == current) { + PasscodeKit.update(passcode) + delegate?.passcodeChanged?(passcode) + dismiss(animated: true) + } else { + isPasscodeMismatch = true + state = .change2 + updateUI() + } + } +} diff --git a/PasscodeKit/Sources/PasscodeKitCreate.swift b/PasscodeKit/Sources/PasscodeKitCreate.swift new file mode 100755 index 0000000..db9d0e8 --- /dev/null +++ b/PasscodeKit/Sources/PasscodeKitCreate.swift @@ -0,0 +1,148 @@ +// +// Copyright (c) 2021 Related Code - https://relatedcode.com +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +//----------------------------------------------------------------------------------------------------------------------------------------------- +class PasscodeKitCreate: UIViewController { + + private var passcode = "" + private var isPasscodeMismatch = false + + private var viewPasscode = UIView() + private var textPasscode: PasscodeKitText! + private var labelInfo = UILabel() + private var labelPasscodeMismatch = UILabel() + + var delegate: PasscodeKitDelegate? + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidLoad() { + + super.viewDidLoad() + + title = PasscodeKit.titleCreatePasscode + + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel)) + + setupUI() + updateUI() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidAppear(_ animated: Bool) { + + super.viewDidAppear(animated) + textPasscode.becomeFirstResponder() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func actionCancel() { + + dismiss(animated: true) + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKitCreate { + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func setupUI() { + + view.backgroundColor = PasscodeKit.backgroundColor + + viewPasscode.frame = CGRect(x: 0, y: 200, width: UIScreen.main.bounds.width, height: 120) + view.addSubview(viewPasscode) + + labelInfo.textAlignment = .center + labelInfo.textColor = PasscodeKit.textColor + labelInfo.font = UIFont.systemFont(ofSize: 17) + labelInfo.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 30) + viewPasscode.addSubview(labelInfo) + + if (textPasscode != nil) && (textPasscode.superview != nil) { + textPasscode.removeFromSuperview() + } + textPasscode = PasscodeKitText() + textPasscode.frame = CGRect(x: 0, y: 45, width: UIScreen.main.bounds.width, height: 30) + textPasscode.addTarget(self, action: #selector(textFieldDidChangeEditing(_:)), for: .editingChanged) + viewPasscode.addSubview(textPasscode) + + labelPasscodeMismatch.text = PasscodeKit.textPasscodeMismatch + labelPasscodeMismatch.textAlignment = .center + labelPasscodeMismatch.textColor = PasscodeKit.textColor + labelPasscodeMismatch.font = UIFont.systemFont(ofSize: 15) + labelPasscodeMismatch.frame = CGRect(x: 0, y: 90, width: UIScreen.main.bounds.width, height: 30) + labelPasscodeMismatch.isHidden = true + viewPasscode.addSubview(labelPasscodeMismatch) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func updateUI() { + + if (passcode == "") { + labelInfo.text = PasscodeKit.textEnterPasscode + } else { + labelInfo.text = PasscodeKit.textVerifyPasscode + isPasscodeMismatch = false + } + + textPasscode.text = "" + textPasscode.becomeFirstResponder() + labelPasscodeMismatch.isHidden = !isPasscodeMismatch + animateViewPasscode() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func animateViewPasscode() { + + let originalXPos = viewPasscode.frame.origin.x + viewPasscode.frame.origin.x = originalXPos + (isPasscodeMismatch ? -250 : 250) + UIView.animate(withDuration: 0.15) { + self.viewPasscode.frame.origin.x = originalXPos + } + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKitCreate { + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func textFieldDidChangeEditing(_ textField: UITextField) { + + let current = textField.text ?? "" + + if (current.count >= PasscodeKit.passcodeLength) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.actionPasscode(current) + } + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func actionPasscode(_ current: String) { + + if (passcode == "") { + passcode = current + updateUI() + } else { + if (passcode == current) { + PasscodeKit.update(passcode) + delegate?.passcodeCreated?(passcode) + dismiss(animated: true) + } else { + isPasscodeMismatch = true + passcode = "" + updateUI() + } + } + } +} diff --git a/PasscodeKit/Sources/PasscodeKitRemove.swift b/PasscodeKit/Sources/PasscodeKitRemove.swift new file mode 100755 index 0000000..db10e2c --- /dev/null +++ b/PasscodeKit/Sources/PasscodeKitRemove.swift @@ -0,0 +1,152 @@ +// +// Copyright (c) 2021 Related Code - https://relatedcode.com +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +//----------------------------------------------------------------------------------------------------------------------------------------------- +class PasscodeKitRemove: UIViewController { + + private var failedAttempts = 0 + + private var viewPasscode = UIView() + private var textPasscode: PasscodeKitText! + private var labelInfo = UILabel() + private var labelFailedAttempts = UILabel() + + var delegate: PasscodeKitDelegate? + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidLoad() { + + super.viewDidLoad() + + title = PasscodeKit.titleRemovePasscode + + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(actionCancel)) + + setupUI() + updateUI() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidAppear(_ animated: Bool) { + + super.viewDidAppear(animated) + textPasscode.becomeFirstResponder() + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func actionCancel() { + + dismiss(animated: true) + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKitRemove { + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func setupUI() { + + view.backgroundColor = PasscodeKit.backgroundColor + + viewPasscode.frame = CGRect(x: 0, y: 200, width: UIScreen.main.bounds.width, height: 120) + view.addSubview(viewPasscode) + + labelInfo.textAlignment = .center + labelInfo.textColor = PasscodeKit.textColor + labelInfo.font = UIFont.systemFont(ofSize: 17) + labelInfo.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 30) + viewPasscode.addSubview(labelInfo) + + if (textPasscode != nil) && (textPasscode.superview != nil) { + textPasscode.removeFromSuperview() + } + textPasscode = PasscodeKitText() + textPasscode.frame = CGRect(x: 0, y: 45, width: UIScreen.main.bounds.width, height: 30) + textPasscode.addTarget(self, action: #selector(textFieldDidChangeEditing(_:)), for: .editingChanged) + viewPasscode.addSubview(textPasscode) + + labelFailedAttempts.textAlignment = .center + labelFailedAttempts.textColor = PasscodeKit.failedTextColor + labelFailedAttempts.backgroundColor = PasscodeKit.failedBackgroundColor + labelFailedAttempts.font = UIFont.systemFont(ofSize: 15) + labelFailedAttempts.frame = CGRect(x: (UIScreen.main.bounds.width - 225) / 2, y: 90, width: 225, height: 30) + labelFailedAttempts.layer.cornerRadius = 15 + labelFailedAttempts.isHidden = true + labelFailedAttempts.clipsToBounds = true + viewPasscode.addSubview(labelFailedAttempts) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func updateUI() { + + labelInfo.text = PasscodeKit.textEnterPasscode + + failedAttempts = 0 + textPasscode.text = "" + textPasscode.becomeFirstResponder() + labelFailedAttempts.isHidden = true + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func setupUIFailed() { + + let animation = CABasicAnimation(keyPath: "position") + animation.duration = 0.09 + animation.repeatCount = 2 + animation.isRemovedOnCompletion = true + animation.autoreverses = true + animation.fromValue = CGPoint(x: textPasscode.center.x - 10, y: textPasscode.center.y) + animation.toValue = CGPoint(x: textPasscode.center.x + 10, y: textPasscode.center.y) + textPasscode.layer.add(animation, forKey: "position") + + failedAttempts += 1 + labelFailedAttempts.isHidden = false + labelFailedAttempts.text = String(format: PasscodeKit.textFailedPasscode, failedAttempts) + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + self.textPasscode.text = "" + } + + if (failedAttempts >= PasscodeKit.allowedFailedAttempts) { + delegate?.passcodeMaximumFailedAttempts?() + } + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKitRemove { + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func textFieldDidChangeEditing(_ textField: UITextField) { + + let current = textField.text ?? "" + + if (current.count >= PasscodeKit.passcodeLength) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.actionPasscode(current) + } + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func actionPasscode(_ current: String) { + + if (PasscodeKit.verify(current)) { + PasscodeKit.remove() + delegate?.passcodeRemoved?() + dismiss(animated: true) + } else { + setupUIFailed() + } + } +} diff --git a/PasscodeKit/Sources/PasscodeKitText.swift b/PasscodeKit/Sources/PasscodeKitText.swift new file mode 100755 index 0000000..221d619 --- /dev/null +++ b/PasscodeKit/Sources/PasscodeKitText.swift @@ -0,0 +1,84 @@ +// +// Copyright (c) 2021 Related Code - https://relatedcode.com +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +//----------------------------------------------------------------------------------------------------------------------------------------------- +class PasscodeKitText: UITextField { + + private let radius: CGFloat = 8 + private let spacing: CGFloat = 20 + + //------------------------------------------------------------------------------------------------------------------------------------------- + override init(frame: CGRect) { + + super.init(frame: frame) + + tintColor = .clear + textColor = .clear + borderStyle = .none + keyboardType = .numberPad + textContentType = .password + + font = UIFont.systemFont(ofSize: 0) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + required init?(coder: NSCoder) { + + super.init(coder: coder) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + + return false + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func layoutSubviews() { + + super.layoutSubviews() + + layer.sublayers?.forEach { layer in + if (layer.name == "PasscodeKitSublayer") { + layer.removeFromSuperlayer() + } + } + + let currentLength = text?.count ?? 0 + let passcodeLength = PasscodeKit.passcodeLength + + let circles = CGFloat(passcodeLength) + let layerWidth = (2 * radius * circles) + spacing * (circles - 1) + let layerXPos = (frame.size.width - layerWidth) / 2 + let layerYPos = (frame.size.height - 2 * radius) / 2 + + let xlayer = CALayer() + xlayer.frame = CGRect(x: layerXPos, y: layerYPos, width: layerWidth, height: 2 * radius) + xlayer.name = "PasscodeKitSublayer" + layer.addSublayer(xlayer) + + let circleCenter = CGPoint(x: radius, y: radius) + let circlePath = UIBezierPath(arcCenter: circleCenter, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: false) + let circleColor = PasscodeKit.textColor.cgColor + + for i in 0..= PasscodeKit.allowedFailedAttempts) { + delegate?.passcodeMaximumFailedAttempts?() + } + } +} + +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeKitVerify { + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func textFieldDidChangeEditing(_ textField: UITextField) { + + let current = textField.text ?? "" + + if (current.count >= PasscodeKit.passcodeLength) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.actionPasscode(current) + } + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private func actionPasscode(_ current: String) { + + if (PasscodeKit.verify(current)) { + delegate?.passcodeEnteredSuccessfully?() + dismiss(animated: true) + } else { + setupUIFailed() + } + } +} diff --git a/PasscodeKit/app.xcodeproj/project.pbxproj b/PasscodeKit/app.xcodeproj/project.pbxproj new file mode 100644 index 0000000..81a4797 --- /dev/null +++ b/PasscodeKit/app.xcodeproj/project.pbxproj @@ -0,0 +1,403 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 295B8482248C1C5B003E8AE6 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 295B8480248C1C5B003E8AE6 /* ViewController.xib */; }; + 295B8483248C1C5B003E8AE6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 295B8481248C1C5B003E8AE6 /* ViewController.swift */; }; + 29D29EF91D9A59E4006CA074 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D29EF81D9A59E4006CA074 /* AppDelegate.swift */; }; + 29D29F011D9A59E4006CA074 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29D29F001D9A59E4006CA074 /* Assets.xcassets */; }; + 29D29F041D9A59E4006CA074 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 29D29F021D9A59E4006CA074 /* LaunchScreen.storyboard */; }; + 29D97807261DB37700C80DBD /* PasscodeKitText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D97801261DB37700C80DBD /* PasscodeKitText.swift */; }; + 29D97808261DB37700C80DBD /* PasscodeKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D97802261DB37700C80DBD /* PasscodeKit.swift */; }; + 29D97809261DB37700C80DBD /* PasscodeKitVerify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D97803261DB37700C80DBD /* PasscodeKitVerify.swift */; }; + 29D9780A261DB37700C80DBD /* PasscodeKitCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D97804261DB37700C80DBD /* PasscodeKitCreate.swift */; }; + 29D9780B261DB37700C80DBD /* PasscodeKitChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D97805261DB37700C80DBD /* PasscodeKitChange.swift */; }; + 29D9780C261DB37700C80DBD /* PasscodeKitRemove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */; }; + 29D97810261DB39C00C80DBD /* PasscodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D9780E261DB39C00C80DBD /* PasscodeView.swift */; }; + 29D97811261DB39C00C80DBD /* PasscodeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29D9780F261DB39C00C80DBD /* PasscodeView.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 295B8480248C1C5B003E8AE6 /* ViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ViewController.xib; sourceTree = ""; }; + 295B8481248C1C5B003E8AE6 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 29D29EF11D9A59E4006CA074 /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 29D29EF81D9A59E4006CA074 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 29D29F001D9A59E4006CA074 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 29D29F031D9A59E4006CA074 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 29D29F051D9A59E4006CA074 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 29D97801261DB37700C80DBD /* PasscodeKitText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitText.swift; sourceTree = ""; }; + 29D97802261DB37700C80DBD /* PasscodeKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKit.swift; sourceTree = ""; }; + 29D97803261DB37700C80DBD /* PasscodeKitVerify.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitVerify.swift; sourceTree = ""; }; + 29D97804261DB37700C80DBD /* PasscodeKitCreate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitCreate.swift; sourceTree = ""; }; + 29D97805261DB37700C80DBD /* PasscodeKitChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitChange.swift; sourceTree = ""; }; + 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitRemove.swift; sourceTree = ""; }; + 29D9780E261DB39C00C80DBD /* PasscodeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeView.swift; sourceTree = ""; }; + 29D9780F261DB39C00C80DBD /* PasscodeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PasscodeView.xib; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 29D29EEE1D9A59E4006CA074 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 299876D9248FCB290025E297 /* Sources */ = { + isa = PBXGroup; + children = ( + 29D97802261DB37700C80DBD /* PasscodeKit.swift */, + 29D97805261DB37700C80DBD /* PasscodeKitChange.swift */, + 29D97804261DB37700C80DBD /* PasscodeKitCreate.swift */, + 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */, + 29D97801261DB37700C80DBD /* PasscodeKitText.swift */, + 29D97803261DB37700C80DBD /* PasscodeKitVerify.swift */, + ); + path = Sources; + sourceTree = ""; + }; + 29D29EE81D9A59E4006CA074 = { + isa = PBXGroup; + children = ( + 299876D9248FCB290025E297 /* Sources */, + 29D29EF31D9A59E4006CA074 /* app */, + 29D29EF21D9A59E4006CA074 /* Products */, + ); + sourceTree = ""; + }; + 29D29EF21D9A59E4006CA074 /* Products */ = { + isa = PBXGroup; + children = ( + 29D29EF11D9A59E4006CA074 /* app.app */, + ); + name = Products; + sourceTree = ""; + }; + 29D29EF31D9A59E4006CA074 /* app */ = { + isa = PBXGroup; + children = ( + 29D29EF81D9A59E4006CA074 /* AppDelegate.swift */, + 29D29F001D9A59E4006CA074 /* Assets.xcassets */, + 29D29F051D9A59E4006CA074 /* Info.plist */, + 29D29F021D9A59E4006CA074 /* LaunchScreen.storyboard */, + 29D9780E261DB39C00C80DBD /* PasscodeView.swift */, + 29D9780F261DB39C00C80DBD /* PasscodeView.xib */, + 295B8481248C1C5B003E8AE6 /* ViewController.swift */, + 295B8480248C1C5B003E8AE6 /* ViewController.xib */, + ); + path = app; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 29D29EF01D9A59E4006CA074 /* app */ = { + isa = PBXNativeTarget; + buildConfigurationList = 29D29F081D9A59E4006CA074 /* Build configuration list for PBXNativeTarget "app" */; + buildPhases = ( + 29D29EED1D9A59E4006CA074 /* Sources */, + 29D29EEE1D9A59E4006CA074 /* Frameworks */, + 29D29EEF1D9A59E4006CA074 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = app; + productName = app; + productReference = 29D29EF11D9A59E4006CA074 /* app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29D29EE91D9A59E4006CA074 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1240; + ORGANIZATIONNAME = KZ; + TargetAttributes = { + 29D29EF01D9A59E4006CA074 = { + CreatedOnToolsVersion = 8.0; + LastSwiftMigration = 1000; + ProvisioningStyle = Manual; + SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 1; + }; + com.apple.Keychain = { + enabled = 1; + }; + com.apple.Push = { + enabled = 1; + }; + }; + }; + }; + }; + buildConfigurationList = 29D29EEC1D9A59E4006CA074 /* Build configuration list for PBXProject "app" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 29D29EE81D9A59E4006CA074; + productRefGroup = 29D29EF21D9A59E4006CA074 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 29D29EF01D9A59E4006CA074 /* app */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 29D29EEF1D9A59E4006CA074 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29D29F041D9A59E4006CA074 /* LaunchScreen.storyboard in Resources */, + 295B8482248C1C5B003E8AE6 /* ViewController.xib in Resources */, + 29D97811261DB39C00C80DBD /* PasscodeView.xib in Resources */, + 29D29F011D9A59E4006CA074 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 29D29EED1D9A59E4006CA074 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 29D97809261DB37700C80DBD /* PasscodeKitVerify.swift in Sources */, + 295B8483248C1C5B003E8AE6 /* ViewController.swift in Sources */, + 29D9780C261DB37700C80DBD /* PasscodeKitRemove.swift in Sources */, + 29D97807261DB37700C80DBD /* PasscodeKitText.swift in Sources */, + 29D9780B261DB37700C80DBD /* PasscodeKitChange.swift in Sources */, + 29D97808261DB37700C80DBD /* PasscodeKit.swift in Sources */, + 29D97810261DB39C00C80DBD /* PasscodeView.swift in Sources */, + 29D9780A261DB37700C80DBD /* PasscodeKitCreate.swift in Sources */, + 29D29EF91D9A59E4006CA074 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 29D29F021D9A59E4006CA074 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 29D29F031D9A59E4006CA074 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 29D29F061D9A59E4006CA074 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 29D29F071D9A59E4006CA074 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 29D29F091D9A59E4006CA074 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = NO; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; + CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = ""; + INFOPLIST_FILE = app/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.relatedcode.passcodekit; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; + SWIFT_SUPPRESS_WARNINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 29D29F0A1D9A59E4006CA074 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = NO; + CLANG_WARN_DOCUMENTATION_COMMENTS = NO; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; + CODE_SIGN_ENTITLEMENTS = ""; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = ""; + INFOPLIST_FILE = app/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = com.relatedcode.passcodekit; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 29D29EEC1D9A59E4006CA074 /* Build configuration list for PBXProject "app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29D29F061D9A59E4006CA074 /* Debug */, + 29D29F071D9A59E4006CA074 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 29D29F081D9A59E4006CA074 /* Build configuration list for PBXNativeTarget "app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 29D29F091D9A59E4006CA074 /* Debug */, + 29D29F0A1D9A59E4006CA074 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29D29EE91D9A59E4006CA074 /* Project object */; +} diff --git a/PasscodeKit/app.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PasscodeKit/app.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..6bddad8 --- /dev/null +++ b/PasscodeKit/app.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/PasscodeKit/app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PasscodeKit/app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/PasscodeKit/app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PasscodeKit/app/AppDelegate.swift b/PasscodeKit/app/AppDelegate.swift new file mode 100644 index 0000000..65c53f6 --- /dev/null +++ b/PasscodeKit/app/AppDelegate.swift @@ -0,0 +1,59 @@ +// +// Copyright (c) 2021 Related Code - https://relatedcode.com +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +//----------------------------------------------------------------------------------------------------------------------------------------------- +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + //------------------------------------------------------------------------------------------------------------------------------------------- + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + + PasscodeKit.delegate = self + PasscodeKit.start() + + window = UIWindow(frame: UIScreen.main.bounds) + + let viewController = ViewController(nibName: "ViewController", bundle: nil) + let navController = UINavigationController(rootViewController: viewController) + + window?.rootViewController = navController + window?.makeKeyAndVisible() + + return true + } +} + +// MARK: - PasscodeKitDelegate +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension AppDelegate: PasscodeKitDelegate { + + //------------------------------------------------------------------------------------------------------------------------------------------- + func passcodeCheckedButDisabled() { + + print(#function) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + func passcodeEnteredSuccessfully() { + + print(#function) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + func passcodeMaximumFailedAttempts() { + + print(#function) + } +} diff --git a/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0076.png b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0076.png new file mode 100644 index 0000000000000000000000000000000000000000..f48271e806f0903d84ee64ae57da67ce12b0ac3d GIT binary patch literal 4431 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 H$2sgjbo^1K literal 0 HcmV?d00001 diff --git a/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0120.png b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0120.png new file mode 100644 index 0000000000000000000000000000000000000000..3a3447d2d36a2ac6047dd01540ab33d483a236bc GIT binary patch literal 6936 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 K=KW*SSN{iq;zXJN literal 0 HcmV?d00001 diff --git a/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0180.png b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0180.png new file mode 100644 index 0000000000000000000000000000000000000000..5b64085feb81f3f8dc1eb4810942c6c0cd20beb3 GIT binary patch literal 9547 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 V-bGp%-T%o)Szbf#y^KZJ{{hFa9P9u9 literal 0 HcmV?d00001 diff --git a/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/1024.png b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000000000000000000000000000000000000..13e7d42682cb82a83bc349cf43cf20099f28bb4f GIT binary patch literal 42860 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

&4Q(B+0s9u zp=bojm3<<%P)w&L7#=x%T*S`-W<7{OJG9`pYg5;Es^%NKCPlzRV{1g@ReUS5AzqDq z*;+*@D$1}xmwU>kH-DB-B+O6W zOzdYP^WY!k@-s*E8UqJF)+JS&*r(C1~~HF4b_r?|`~p7VgG6?|Ea@aCT4QYV-r zh9|p~4n&7iGTm+AKF3Zi|s`Q^Y&$ShX%;1;%c`pWM~&vBAK!am^)d1u4XoCid|ZQ@FE zs^V@Dj9u}>+MR3(awa3UxVv6nz$ijv<#qn*FT^Jo#AGr(#|M8h+TiX7*(H%3(8aj! zaxObGXb*>!Kr)(HBFgwsDwcNcUhp&A0Fp>cxyE$jF)sbg(v)sxAj!1i!d2c(j|am3 z*3ZGK&%kkw#Z^VF3%M4yjyahIW^{+Mno4dpcg{mZxD zH+8o2R;|(;Xl^4|^@<5$$k2aV?|IK`I9jLx7xrI4Cw!l>h8qIWK=mdb3VYI~qhE5^IAfhH|Ew z>(GDFP7mw2XLZ4N`y&i2u@ZiDT98ZExFkQ{Rnx^wK87%N{XCM_{~V5XpthS?VV97I z_kJ>57OV2*)Z!Vf(lsD?`9$#rT%?G`?t32{grRa z)&1^&pv3uL{Nk^z;=Gq(VV0$A7L1i)N_{?Q{^#7ap$qU+R(WS^Y!$QbLp=g#!9W{?<`$O4nFoaR z(PYKw!>D;uG%3$z>|Ccu+^L0Mt^UpOr9Dh>9_n!_4BkpEUe~=;F2{0Yrt2Re8s2io zP1$geMd~KCPD1%GQM0*{V#mR)zcVM4cf&wik+ozS^hBtiSGR*6vFTPlXL~y(!$rtQ zT>SPI{`?oRCW+3Iysd(i(}P<86vJU2gJ1i&_|^XZ?B8Vqz>VoE&82@I`Ijza|1!hB zLV{4>U(N8ZI{a51{;LlE^%?$kl>YS&|Mi;xbqN0d9UtM+-%pZUsd%dPU$-Y|9L~xb z$I9I<9Csjxy4U{(kObV!PRCr4`nazzOfvJ)-mFcMM_jNciN-sM(Gv%2sk=J6F2{HLa(bv{GM + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PasscodeKit/app/Info.plist b/PasscodeKit/app/Info.plist new file mode 100644 index 0000000..86a3e4c --- /dev/null +++ b/PasscodeKit/app/Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + PasscodeKit + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + + + diff --git a/PasscodeKit/app/PasscodeView.swift b/PasscodeKit/app/PasscodeView.swift new file mode 100644 index 0000000..7849e14 --- /dev/null +++ b/PasscodeKit/app/PasscodeView.swift @@ -0,0 +1,135 @@ +// +// Copyright (c) 2021 Related Code - https://relatedcode.com +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +//----------------------------------------------------------------------------------------------------------------------------------------------- +class PasscodeView: UIViewController { + + @IBOutlet private var tableView: UITableView! + + @IBOutlet private var cellTurnPasscode: UITableViewCell! + @IBOutlet private var cellChangePasscode: UITableViewCell! + @IBOutlet private var cellBiometric: UITableViewCell! + + @IBOutlet private var switchBiometric: UISwitch! + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidLoad() { + + super.viewDidLoad() + title = "Passcode" + + switchBiometric.addTarget(self, action: #selector(actionBiometric), for: .valueChanged) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewWillAppear(_ animated: Bool) { + + super.viewWillAppear(animated) + + updateViewDetails() + } + + // MARK: - User actions + //------------------------------------------------------------------------------------------------------------------------------------------- + func actionTurnPasscode() { + + if (PasscodeKit.enabled()) { + PasscodeKit.removePasscode(self) + } else { + PasscodeKit.createPasscode(self) + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + func actionChangePasscode() { + + if (PasscodeKit.enabled()) { + PasscodeKit.changePasscode(self) + } + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc func actionBiometric() { + + PasscodeKit.biometric(switchBiometric.isOn) + } + + // MARK: - Helper methods + //------------------------------------------------------------------------------------------------------------------------------------------- + func updateViewDetails() { + + if (PasscodeKit.enabled()) { + cellTurnPasscode.textLabel?.text = "Turn Passcode Off" + cellChangePasscode.textLabel?.textColor = UIColor.systemBlue + } else { + cellTurnPasscode.textLabel?.text = "Turn Passcode On" + cellChangePasscode.textLabel?.textColor = UIColor.lightGray + } + + switchBiometric.isOn = PasscodeKit.biometric() + + tableView.reloadData() + } +} + +// MARK: - UITableViewDataSource +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeView: UITableViewDataSource { + + //------------------------------------------------------------------------------------------------------------------------------------------- + func numberOfSections(in tableView: UITableView) -> Int { + + return PasscodeKit.enabled() ? 2 : 1 + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + if (section == 0) { return 2 } + if (section == 1) { return 1 } + + return 0 + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + + if (section == 1) { return "Allow to use Face ID (or Touch ID) to unlock the app." } + + return nil + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + if (indexPath.section == 0) && (indexPath.row == 0) { return cellTurnPasscode } + if (indexPath.section == 0) && (indexPath.row == 1) { return cellChangePasscode } + if (indexPath.section == 1) && (indexPath.row == 0) { return cellBiometric } + + return UITableViewCell() + } +} + +// MARK: - UITableViewDelegate +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeView: UITableViewDelegate { + + //------------------------------------------------------------------------------------------------------------------------------------------- + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + tableView.deselectRow(at: indexPath, animated: true) + + if (indexPath.section == 0) && (indexPath.row == 0) { actionTurnPasscode() } + if (indexPath.section == 0) && (indexPath.row == 1) { actionChangePasscode() } + } +} diff --git a/PasscodeKit/app/PasscodeView.xib b/PasscodeKit/app/PasscodeView.xib new file mode 100644 index 0000000..fbf9424 --- /dev/null +++ b/PasscodeKit/app/PasscodeView.xib @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PasscodeKit/app/ViewController.swift b/PasscodeKit/app/ViewController.swift new file mode 100644 index 0000000..7f7ccfb --- /dev/null +++ b/PasscodeKit/app/ViewController.swift @@ -0,0 +1,76 @@ +// +// Copyright (c) 2021 Related Code - https://relatedcode.com +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import UIKit + +//----------------------------------------------------------------------------------------------------------------------------------------------- +class ViewController: UITableViewController { + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewDidLoad() { + + super.viewDidLoad() + title = "PasscodeKit" + + navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil) + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewWillAppear(_ animated: Bool) { + + super.viewWillAppear(animated) + tableView.reloadData() + } +} + +// MARK: - UITableViewDataSource +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension ViewController { + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func numberOfSections(in tableView: UITableView) -> Int { + + return 1 + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + return 1 + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell") + if (cell == nil) { cell = UITableViewCell(style: .value1, reuseIdentifier: "cell") } + + cell.textLabel?.text = "Passcode Lock" + cell.detailTextLabel?.text = PasscodeKit.enabled() ? "On" : "Off" + cell.accessoryType = .disclosureIndicator + + return cell + } +} + +// MARK: - UITableViewDelegate +//----------------------------------------------------------------------------------------------------------------------------------------------- +extension ViewController { + + //------------------------------------------------------------------------------------------------------------------------------------------- + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + tableView.deselectRow(at: indexPath, animated: true) + + let passcodeView = PasscodeView() + navigationController?.pushViewController(passcodeView, animated: true) + } +} diff --git a/PasscodeKit/app/ViewController.xib b/PasscodeKit/app/ViewController.xib new file mode 100644 index 0000000..6d1040a --- /dev/null +++ b/PasscodeKit/app/ViewController.xib @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 7dfe587..2c72a68 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # PasscodeKit + A lightweight and easy-to-use Passcode Kit for iOS. diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +1.0.0