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 0000000..f48271e Binary files /dev/null and b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0076.png differ 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 0000000..3a3447d Binary files /dev/null and b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0120.png differ diff --git a/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0152.png b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0152.png new file mode 100644 index 0000000..a86e587 Binary files /dev/null and b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0152.png differ diff --git a/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0167.png b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0167.png new file mode 100644 index 0000000..4dd6653 Binary files /dev/null and b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0167.png differ 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 0000000..5b64085 Binary files /dev/null and b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/0180.png differ 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 0000000..13e7d42 Binary files /dev/null and b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/Contents.json b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..453d50b --- /dev/null +++ b/PasscodeKit/app/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,104 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "0120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "0180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "0076.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "0152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "0167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PasscodeKit/app/Assets.xcassets/Contents.json b/PasscodeKit/app/Assets.xcassets/Contents.json new file mode 100755 index 0000000..da4a164 --- /dev/null +++ b/PasscodeKit/app/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/PasscodeKit/app/Base.lproj/LaunchScreen.storyboard b/PasscodeKit/app/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..786c89e --- /dev/null +++ b/PasscodeKit/app/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 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