diff --git a/LICENSE b/LICENSE index 51f3128..e26d552 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,25 @@ MIT License +Copyright (c) 2022 StephenFang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. + Copyright (c) 2021 Related Code Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/PasscodeKit/Sources/PasscodeInterval.swift b/PasscodeKit/Sources/PasscodeInterval.swift new file mode 100644 index 0000000..f55d677 --- /dev/null +++ b/PasscodeKit/Sources/PasscodeInterval.swift @@ -0,0 +1,85 @@ +// +// PasscodeInterval.swift +// app +// +// Created by StephenFang on 2022/7/16. +// Copyright © 2022 KZ. All rights reserved. +// + +import Foundation + +enum PasscodeInterval: Double, CaseIterable { + case immediately = 0.0 + case oneMinute = 1.0 + case fiveMinutes = 5.0 + case tenMinutes = 10.0 + case halfAnHour = 30.0 + case anHour = 60.0 + + var localizedDescription: String { + if self == .immediately { + return PasscodeKit.vefifyPasscodeImmediately + } else if self == .anHour { + return PasscodeKit.vefifyPasscodeAfterOneHour + } else { + return String(format: PasscodeKit.vefifyPasscodeAfterMinutes, rawValue) + } + } +} + +class PasscodeKitInterval: UIViewController { + + fileprivate let tableView = UITableView(frame: .zero, style: .insetGrouped) + fileprivate var selectedRow = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Require Password" + + navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil) + + view.addSubview(tableView) + tableView.frame = view.bounds + tableView.delegate = self + tableView.dataSource = self + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let selectedRow = PasscodeInterval.allCases.firstIndex(where: { $0.rawValue == PasscodeKit.passcodeInterval() + }) { + self.selectedRow = selectedRow + } + tableView.selectRow(at: IndexPath(item: selectedRow, section: 0), animated: false, scrollPosition: .top) + } +} + +extension IntervalViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return PasscodeInterval.allCases.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell") + if (cell == nil) { cell = UITableViewCell(style: .default, reuseIdentifier: "cell") } + + cell.selectionStyle = .none + cell.textLabel?.text = PasscodeInterval.allCases[indexPath.item].localizedDescription + cell.accessoryType = indexPath.row == selectedRow ? .checkmark : .none + + return cell + } +} + +extension IntervalViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + PasscodeKit.passcodeInterval(PasscodeInterval.allCases[indexPath.item].rawValue) + + selectedRow = indexPath.row + tableView.reloadData() + } +} diff --git a/PasscodeKit/Sources/PasscodeKit.swift b/PasscodeKit/Sources/PasscodeKit.swift index 4d60343..1b1a281 100755 --- a/PasscodeKit/Sources/PasscodeKit.swift +++ b/PasscodeKit/Sources/PasscodeKit.swift @@ -22,6 +22,8 @@ import CryptoKit @objc optional func passcodeCheckedButDisabled() @objc optional func passcodeEnteredSuccessfully() @objc optional func passcodeMaximumFailedAttempts() + + @objc optional func passcodeIntervalChanged() } //----------------------------------------------------------------------------------------------------------------------------------------------- @@ -32,29 +34,34 @@ public class PasscodeKit: NSObject { return instance }() - public static var passcodeLength = 4 - public static var allowedFailedAttempts = 3 - - public static var textColor = UIColor.darkText - public static var backgroundColor = UIColor.lightGray - - public static var failedTextColor = UIColor.white - public static var failedBackgroundColor = UIColor.systemRed - - public static var titleEnterPasscode = "Enter Passcode" - public static var titleCreatePasscode = "Create Passcode" - public static var titleChangePasscode = "Change Passcode" - public static var titleRemovePasscode = "Remove Passcode" - - public static var textEnterPasscode = "Enter your passcode" - public static var textVerifyPasscode = "Verify your passcode" - public static var textEnterOldPasscode = "Enter your old passcode" - public static var textEnterNewPasscode = "Enter your new passcode" - public static var textVerifyNewPasscode = "Verify your new passcode" - public static var textFailedPasscode = "%d Failed Passcode Attempts" - public static var textPasscodeMismatch = "Passcodes did not match. Try again." - public static var textTouchIDAccessReason = "Please use Touch ID to unlock the app" - + public static var passcodeLength = 4 + public static var allowedFailedAttempts = 3 + + public static var textColor = UIColor.darkText + public static var backgroundColor = UIColor.lightGray + + public static var failedTextColor = UIColor.white + public static var failedBackgroundColor = UIColor.systemRed + + public static var titleEnterPasscode = "Enter Passcode" + public static var titleCreatePasscode = "Create Passcode" + public static var titleChangePasscode = "Change Passcode" + public static var titleRemovePasscode = "Remove Passcode" + + public static var textEnterPasscode = "Enter your passcode" + public static var textVerifyPasscode = "Verify your passcode" + public static var textEnterOldPasscode = "Enter your old passcode" + public static var textEnterNewPasscode = "Enter your new passcode" + public static var textVerifyNewPasscode = "Verify your new passcode" + public static var textFailedPasscode = "%d Failed Passcode Attempts" + public static var textPasscodeMismatch = "Passcodes did not match. Try again." + public static var textBiometricAccessReason = "Please use Face ID (or Touch ID) to unlock the app" + public static var textBiometricAccessTip = "Allow to use Face ID (or Touch ID) to unlock the app." + + public static var vefifyPasscodeImmediately = "Immediately" + public static var vefifyPasscodeAfterMinutes = "After %.f minutes" + public static var vefifyPasscodeAfterOneHour = "After an hour" + public static var delegate: PasscodeKitDelegate? //------------------------------------------------------------------------------------------------------------------------------------------- @@ -99,15 +106,20 @@ extension PasscodeKit { let didFinishLaunching = UIApplication.didFinishLaunchingNotification let willEnterForeground = UIApplication.willEnterForegroundNotification + let willResignActive = UIApplication.willResignActiveNotification NotificationCenter.default.addObserver(self, selector: #selector(verifyPasscode), name: didFinishLaunching, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(verifyPasscode), name: willEnterForeground, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(verifyInterval), name: willResignActive, object: nil) } //------------------------------------------------------------------------------------------------------------------------------------------- @objc private func verifyPasscode() { if (PasscodeKit.enabled()) { + if (!PasscodeKit.verifiedTimeExpired()) { + return + } if let viewController = topViewController() { if (noPasscodePresented(viewController)) { presentPasscodeVerify(viewController) @@ -117,6 +129,13 @@ extension PasscodeKit { PasscodeKit.delegate?.passcodeCheckedButDisabled?() } } + + //------------------------------------------------------------------------------------------------------------------------------------------- + @objc private func verifyInterval() { + if (PasscodeKit.enabled()) { + PasscodeKit.verifiedTimeInterval(Date()) + } + } //------------------------------------------------------------------------------------------------------------------------------------------- private func presentPasscodeVerify(_ viewController: UIViewController) { @@ -135,10 +154,10 @@ extension PasscodeKit { 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 } + 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 @@ -192,6 +211,14 @@ extension PasscodeKit { let navController = PasscodeKitNavController(rootViewController: passcodeKitRemove) viewController.present(navController, animated: true) } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func changeInterval(_ viewController: UINavigationController) { + + let passcodeKitInterval = PasscodeKitInterval() + passcodeKitInterval.delegate = viewController as? PasscodeKitDelegate + viewController.pushViewController(passcodeKitInterval, animated: true) + } } // MARK: - Passcode methods @@ -229,6 +256,8 @@ extension PasscodeKit { UserDefaults.standard.removeObject(forKey: "PasscodeValue") UserDefaults.standard.removeObject(forKey: "PasscodeBiometric") + UserDefaults.standard.removeObject(forKey: "PasscodeInterval") + UserDefaults.standard.removeObject(forKey: "PasscodeVerifiedInterval") } //------------------------------------------------------------------------------------------------------------------------------------------- @@ -253,6 +282,40 @@ extension PasscodeKit { } return text } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func passcodeInterval() -> Double { + return UserDefaults.standard.double(forKey: "PasscodeInterval") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func passcodeInterval(_ interval: Double) { + UserDefaults.standard.set(interval, forKey: "PasscodeInterval") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func passcodeLocalizedInterval() -> String { + return PasscodeInterval.allCases.first(where: { $0.rawValue == PasscodeKit.passcodeInterval()})?.localizedDescription ?? PasscodeKit.vefifyPasscodeImmediately + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func verifiedTimeInterval() -> Double { + return UserDefaults.standard.double(forKey: "PasscodeVerifiedInterval") + } + + + //------------------------------------------------------------------------------------------------------------------------------------------- + public class func verifiedTimeInterval(_ date: Date) { + UserDefaults.standard.set(date.timeIntervalSince1970, forKey: "PasscodeVerifiedInterval") + } + + //------------------------------------------------------------------------------------------------------------------------------------------- + private class func verifiedTimeExpired() -> Bool { + let now = Date().timeIntervalSince1970 + let prev = verifiedTimeInterval() + let seconds: Double = abs(now - prev) + return seconds >= passcodeInterval() + } } // MARK: - PasscodeKitNavController @@ -269,7 +332,15 @@ class PasscodeKitNavController: UINavigationController { self.modalPresentationStyle = .fullScreen } - navigationBar.isTranslucent = false + if #available(iOS 15.0, *) { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + UINavigationBar.appearance().standardAppearance = appearance + UINavigationBar.appearance().scrollEdgeAppearance = appearance + UINavigationBar.appearance().compactAppearance = appearance + } else { + navigationBar.isTranslucent = false + } } //------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/PasscodeKit/Sources/PasscodeKitInterval.swift b/PasscodeKit/Sources/PasscodeKitInterval.swift new file mode 100644 index 0000000..bff27be --- /dev/null +++ b/PasscodeKit/Sources/PasscodeKitInterval.swift @@ -0,0 +1,89 @@ +// +// PasscodeInterval.swift +// app +// +// Created by StephenFang on 2022/7/16. +// Copyright © 2022 KZ. All rights reserved. +// + +import UIKit + +enum PasscodeInterval: Double, CaseIterable { + case immediately = 0.0 + case oneMinute = 1.0 + case fiveMinutes = 5.0 + case tenMinutes = 10.0 + case halfAnHour = 30.0 + case anHour = 60.0 + + var localizedDescription: String { + if self == .immediately { + return PasscodeKit.vefifyPasscodeImmediately + } else if self == .anHour { + return PasscodeKit.vefifyPasscodeAfterOneHour + } else { + return String(format: PasscodeKit.vefifyPasscodeAfterMinutes, rawValue) + } + } +} + +class PasscodeKitInterval: UIViewController { + + fileprivate let tableView = UITableView(frame: .zero, style: .insetGrouped) + + private var selectedRow = 0 + + var delegate: PasscodeKitDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Require Password" + + navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil) + + view.addSubview(tableView) + tableView.frame = view.bounds + tableView.delegate = self + tableView.dataSource = self + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let selectedRow = PasscodeInterval.allCases.firstIndex(where: { $0.rawValue == PasscodeKit.passcodeInterval() + }) { + self.selectedRow = selectedRow + } + tableView.selectRow(at: IndexPath(item: selectedRow, section: 0), animated: false, scrollPosition: .top) + } +} + +extension PasscodeKitInterval: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return PasscodeInterval.allCases.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell") + if (cell == nil) { cell = UITableViewCell(style: .default, reuseIdentifier: "cell") } + + cell.selectionStyle = .none + cell.textLabel?.text = PasscodeInterval.allCases[indexPath.item].localizedDescription + cell.accessoryType = indexPath.row == selectedRow ? .checkmark : .none + + return cell + } +} + +extension PasscodeKitInterval: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + PasscodeKit.passcodeInterval(PasscodeInterval.allCases[indexPath.item].rawValue) + delegate?.passcodeIntervalChanged?() + + selectedRow = indexPath.row + tableView.reloadData() + } +} diff --git a/PasscodeKit/Sources/PasscodeKitVerify.swift b/PasscodeKit/Sources/PasscodeKitVerify.swift index f4b33d5..9fbe4d5 100755 --- a/PasscodeKit/Sources/PasscodeKitVerify.swift +++ b/PasscodeKit/Sources/PasscodeKitVerify.swift @@ -51,7 +51,7 @@ extension PasscodeKitVerify { let context = LAContext() if (PasscodeKit.biometric()) && (context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)) { - let reason = PasscodeKit.textTouchIDAccessReason + let reason = PasscodeKit.textBiometricAccessReason context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, error in DispatchQueue.main.async { self.actionBiometric(success) diff --git a/PasscodeKit/app.xcodeproj/project.pbxproj b/PasscodeKit/app.xcodeproj/project.pbxproj index 2979f35..97bf242 100644 --- a/PasscodeKit/app.xcodeproj/project.pbxproj +++ b/PasscodeKit/app.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 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 */; }; + 4D200CB028831D0200C8DF4D /* PasscodeKitInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D200CAF28831D0200C8DF4D /* PasscodeKitInterval.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -38,6 +39,7 @@ 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 = ""; }; + 4D200CAF28831D0200C8DF4D /* PasscodeKitInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeKitInterval.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,6 +62,7 @@ 29D97806261DB37700C80DBD /* PasscodeKitRemove.swift */, 29D97801261DB37700C80DBD /* PasscodeKitText.swift */, 29D97803261DB37700C80DBD /* PasscodeKitVerify.swift */, + 4D200CAF28831D0200C8DF4D /* PasscodeKitInterval.swift */, ); path = Sources; sourceTree = ""; @@ -185,6 +188,7 @@ 29D9780C261DB37700C80DBD /* PasscodeKitRemove.swift in Sources */, 29D97807261DB37700C80DBD /* PasscodeKitText.swift in Sources */, 29D9780B261DB37700C80DBD /* PasscodeKitChange.swift in Sources */, + 4D200CB028831D0200C8DF4D /* PasscodeKitInterval.swift in Sources */, 29D97808261DB37700C80DBD /* PasscodeKit.swift in Sources */, 29D97810261DB39C00C80DBD /* PasscodeView.swift in Sources */, 29D9780A261DB37700C80DBD /* PasscodeKitCreate.swift in Sources */, diff --git a/PasscodeKit/app/PasscodeView.swift b/PasscodeKit/app/PasscodeView.swift old mode 100644 new mode 100755 index 7849e14..8ac45c2 --- a/PasscodeKit/app/PasscodeView.swift +++ b/PasscodeKit/app/PasscodeView.swift @@ -19,10 +19,10 @@ class PasscodeView: UIViewController { @IBOutlet private var cellTurnPasscode: UITableViewCell! @IBOutlet private var cellChangePasscode: UITableViewCell! @IBOutlet private var cellBiometric: UITableViewCell! + @IBOutlet private var cellInterval: UITableViewCell! + @IBOutlet private var switchBiometric: UISwitch! - @IBOutlet private var switchBiometric: UISwitch! - //------------------------------------------------------------------------------------------------------------------------------------------- override func viewDidLoad() { super.viewDidLoad() @@ -31,7 +31,7 @@ class PasscodeView: UIViewController { switchBiometric.addTarget(self, action: #selector(actionBiometric), for: .valueChanged) } - //------------------------------------------------------------------------------------------------------------------------------------------- + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -40,7 +40,7 @@ class PasscodeView: UIViewController { } // MARK: - User actions - //------------------------------------------------------------------------------------------------------------------------------------------- + func actionTurnPasscode() { if (PasscodeKit.enabled()) { @@ -49,33 +49,41 @@ class PasscodeView: UIViewController { PasscodeKit.createPasscode(self) } } - - //------------------------------------------------------------------------------------------------------------------------------------------- + func actionChangePasscode() { if (PasscodeKit.enabled()) { PasscodeKit.changePasscode(self) } } + + + func actionChangeInterval() { - //------------------------------------------------------------------------------------------------------------------------------------------- - @objc func actionBiometric() { + if (PasscodeKit.enabled()) { + PasscodeKit.changeInterval(self.navigationController!) + } + } + @objc func actionBiometric() { PasscodeKit.biometric(switchBiometric.isOn) } // MARK: - Helper methods - //------------------------------------------------------------------------------------------------------------------------------------------- - func updateViewDetails() { + func updateViewDetails() { if (PasscodeKit.enabled()) { cellTurnPasscode.textLabel?.text = "Turn Passcode Off" cellChangePasscode.textLabel?.textColor = UIColor.systemBlue + cellInterval.textLabel?.textColor = UIColor.systemBlue } else { cellTurnPasscode.textLabel?.text = "Turn Passcode On" cellChangePasscode.textLabel?.textColor = UIColor.lightGray + cellInterval.textLabel?.textColor = UIColor.lightGray } - + + cellInterval.detailTextLabel?.text = PasscodeKit.passcodeLocalizedInterval() + switchBiometric.isOn = PasscodeKit.biometric() tableView.reloadData() @@ -83,53 +91,40 @@ class PasscodeView: UIViewController { } // MARK: - UITableViewDataSource -//----------------------------------------------------------------------------------------------------------------------------------------------- -extension PasscodeView: UITableViewDataSource { - //------------------------------------------------------------------------------------------------------------------------------------------- +extension PasscodeView: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - - return PasscodeKit.enabled() ? 2 : 1 + return PasscodeKit.enabled() ? 3 : 1 } - //------------------------------------------------------------------------------------------------------------------------------------------- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if (section == 0) { return 2 } - if (section == 1) { return 1 } - + if (section == 1) { return 1 } + if (section == 2) { 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." } - + if (section == 2) { return PasscodeKit.textBiometricAccessTip } 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 } - + if (indexPath.section == 1) && (indexPath.row == 0) { return cellInterval } + if (indexPath.section == 2) && (indexPath.row == 0) { return cellBiometric } return UITableViewCell() } } // MARK: - UITableViewDelegate -//----------------------------------------------------------------------------------------------------------------------------------------------- -extension PasscodeView: 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() } + if (indexPath.section == 1) && (indexPath.row == 0) { actionChangeInterval() } } } diff --git a/PasscodeKit/app/PasscodeView.xib b/PasscodeKit/app/PasscodeView.xib old mode 100644 new mode 100755 index fbf9424..522111d --- a/PasscodeKit/app/PasscodeView.xib +++ b/PasscodeKit/app/PasscodeView.xib @@ -1,10 +1,11 @@ - + - + + @@ -12,6 +13,7 @@ + @@ -57,7 +59,7 @@ - + @@ -92,7 +94,7 @@ - + @@ -106,10 +108,38 @@ + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 137fa53..119b0cb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ +## DIFFERENCES + +1. Added support for 'Require Passcode' + + + + +2. Added UINavigationBarAppearance support for iOS15 + + (Before & After) + + + + + Also see [PR](https://github.com/relatedcode/PasscodeKit/pull/2) here. + +3. Refined localization strings + +- Changed 'textTouchIDAccessReason' to 'textBiometricAccessReason' +- Added 'textBiometricAccessTip' + ## OVERVIEW PasscodeKit is a lightweight and easy-to-use, in-app Passcode implementation for iOS. @@ -11,7 +32,7 @@ PasscodeKit is a lightweight and easy-to-use, in-app Passcode implementation for To use PasscodeKit with [CocoaPods](https://cocoapods.org), specify in your Podfile: ```ruby -pod 'PasscodeKit' +pod 'PasscodeKit', :git => 'https://github.com/iamStephenFang/PasscodeKit.git', :branch => 'master' ``` ### Manually @@ -24,7 +45,7 @@ If you prefer not to use any of the dependency managers, you can integrate `Pass ## QUICKSTART -To activate the PasscodeKit in your codebase, you need to start it right after the app is launched. The best practice to do it in the AppDelegate `didFinishLaunchingWithOptions` method. +To activate the PasscodeKit in your codebase, you need to start it right after the app is launched. The best practice to do it in the AppDelegate `didFinishLaunchingWithOptions` method. ```swift PasscodeKit.start() @@ -52,6 +73,12 @@ PasscodeKit.removePasscode(self) +The following `PasscodeKitDelegate` methods can be used to receive notifications when the password interval is changed. + +```swift +func passcodeIntervalChanged() +``` + ## CUSTOMIZATION The following settings are available for customizing the passcode-related user experience. @@ -83,7 +110,14 @@ PasscodeKit.textEnterNewPasscode = "Enter your new passcode" PasscodeKit.textVerifyNewPasscode = "Verify your new passcode" PasscodeKit.textFailedPasscode = "%d Failed Passcode Attempts" PasscodeKit.textPasscodeMismatch = "Passcodes did not match. Try again." -PasscodeKit.textTouchIDAccessReason = "Please use Touch ID to unlock the app" +PasscodeKit.textBiometricAccessReason = "Please use Touch ID to unlock the app" +PasscodeKit.textBiometricAccessTip = "Allow to use Face ID (or Touch ID) to unlock the app" +``` + +```swift +PasscodeKit.vefifyPasscodeImmediately = "Immediately" +PasscodeKit.vefifyPasscodeAfterMinutes = "After %.f minutes" +PasscodeKit.vefifyPasscodeAfterOneHour = "After an hour" ``` ## CONFIGURATION @@ -96,6 +130,26 @@ PasscodeKit supports both TouchID and FaceID. If you're using FaceID, be sure to MIT License +Copyright (c) 2022 StephenFang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. + Copyright (c) 2021 Related Code Permission is hereby granted, free of charge, to any person obtaining a copy