Format code with SwiftFormat automatically in every build

This commit is contained in:
Danny Moesch 2020-06-28 21:25:40 +02:00 committed by Mingshen Sun
parent f167ab7549
commit 7f9f0e43b2
100 changed files with 1124 additions and 1063 deletions

1
.swift-version Normal file
View file

@ -0,0 +1 @@
5.3

96
.swiftformat Normal file
View file

@ -0,0 +1,96 @@
# Exclude folders
--exclude Carthage,go,Pods
# Enabled rules
--rules andOperator, anyObjectProtocol, blankLinesAroundMark, blankLinesAtEndOfScope, blankLinesAtStartOfScope, blankLinesBetweenScopes, braces, consecutiveBlankLines, consecutiveSpaces, duplicateImports, elseOnSameLine, emptyBraces, fileHeader, hoistPatternLet, indent, isEmpty, leadingDelimiters, linebreakAtEndOfFile, linebreaks, modifierOrder, numberFormatting, preferKeyPath, redundantBackticks, redundantBreak, redundantExtensionACL, redundantFileprivate, redundantGet, redundantInit, redundantLet, redundantLetError, redundantNilInit, redundantObjc, redundantParens, redundantPattern, redundantRawValues, redundantReturn, redundantSelf, redundantVoidReturnType, semicolons, sortedImports, spaceAroundBraces, spaceAroundBrackets, spaceAroundComments, spaceAroundGenerics, spaceAroundOperators, spaceAroundParens, spaceInsideBraces, spaceInsideBrackets, spaceInsideComments, spaceInsideGenerics, spaceInsideParens, strongOutlets, strongifiedSelf, todos, trailingClosures, trailingCommas, trailingSpace, typeSugar, unusedArguments, void, wrap, wrapArguments, wrapAttributes, yodaConditions
# Formatting options
## Use allman indentation style: "true" or "false" (default)
--allman false
## Binary grouping,threshold (default: 4,8) or "none", "ignore"
--binarygrouping 4,8
## Closing paren position: "balanced" (default) or "same-line"
--closingparen balanced
## Commas in collection literals: "always" (default) or "inline"
--commas always
## Decimal grouping,threshold (default: 3,6) or "none", "ignore"
--decimalgrouping 3,6
## Placement of else/catch: "same-line" (default) or "next-line"
--elseposition same-line
## Case of 'e' in numbers: "lowercase" or "uppercase" (default)
--exponentcase lowercase
## Group exponent digits: "enabled" or "disabled" (default)
--exponentgrouping disabled
## Group digits after '.': "enabled" or "disabled" (default)
--fractiongrouping disabled
## Function @attributes: "preserve", "prev-line", or "same-line"
--funcattributes prev-line
## Guard else: "same-line", "next-line" or "auto" (default)
--guardelse auto
## Header comments: "strip", "ignore", or the text you wish use
--header ignore
## Hex grouping,threshold (default: 4,8) or "none", "ignore"
--hexgrouping 4,8
## Casing for hex literals: "uppercase" (default) or "lowercase"
--hexliteralcase uppercase
## #if indenting: "indent" (default), "no-indent" or "outdent"
--ifdef indent
## "testable-top", "testable-bottom" or "alphabetized" (default)
--importgrouping testable-bottom
## Number of spaces to indent, or "tab" to use tabs
--indent 4
## Indent cases inside a switch: "true" or "false" (default)
--indentcase false
## Linebreak character to use: "cr", "crlf" or "lf" (default)
--linebreaks lf
## Maximum length of a line before wrapping. defaults to "none"
--maxwidth none
## Comma-delimited list of modifiers in preferred order
--modifierorder
## Comma-delimited list of operators without surrounding space
--nospaceoperators
## Comma-delimited list of operators that shouldn't be wrapped
--nowrapoperators
## Octal grouping,threshold (default: 4,8) or "none", "ignore"
--octalgrouping 4,8
## Spacing for operator funcs: "spaced" (default) or "no-space"
--operatorfunc spaced
## let/var placement in patterns: "hoist" (default) or "inline"
--patternlet hoist
## Explicit self: "insert", "remove" (default) or "init-only"
--self init-only
## Comma-delimited list of functions with @autoclosure arguments
--selfrequired
## Allow semicolons: "never" or "inline" (default)
--semicolons inline
## Use ? for Optionals "always" (default) or "except-properties"
--shortoptionals always
## Align code independently of tab width. defaults to "enabled"
--smarttabs enabled
## "closure-only", "unnamed-only" or "always" (default)
--stripunusedargs always
## The width of a tab character. Defaults to "unspecified"
--tabwidth 4
## Comma-delimited list of functions that use trailing closures
--trailingclosures
## Trim trailing space: "always" (default) or "nonblank-lines"
--trimwhitespace always
## Type @attributes: "preserve", "prev-line", or "same-line"
--typeattributes preserve
## Property @attributes: "preserve", "prev-line", or "same-line"
--varattributes same-line
## How Void types are represented: "void" (default) or "tuple"
--voidtype void
## Wrap all arguments: "before-first", "after-first", "preserve"
--wraparguments preserve
## Wrap array/dict: "before-first", "after-first", "preserve"
--wrapcollections preserve
## Wrap func params: "before-first", "after-first", "preserve"
--wrapparameters preserve
## Xcode indent guard/enum: "enabled" or "disabled" (default)
--xcodeindentation disabled
## Swap yoda values: "always" (default) or "literals-only"
--yodaswap always

View file

@ -6,6 +6,7 @@ addons:
- go - go
- gnupg2 - gnupg2
- pass - pass
- swiftformat
before_install: before_install:
- echo -e "machine github.com\n login $GITHUB_ACCESS_TOKEN" >> ~/.netrc - echo -e "machine github.com\n login $GITHUB_ACCESS_TOKEN" >> ~/.netrc
install: install:

View file

@ -1080,6 +1080,7 @@
A26700191EEC450100176B8A /* Embed App Extensions */, A26700191EEC450100176B8A /* Embed App Extensions */,
A26075921EEC6F34005DB03E /* Embed Frameworks */, A26075921EEC6F34005DB03E /* Embed Frameworks */,
9AF6A4F532EB900EE22C80EA /* [CP] Embed Pods Frameworks */, 9AF6A4F532EB900EE22C80EA /* [CP] Embed Pods Frameworks */,
3005F34F24A9143C000519B5 /* ShellScript */,
); );
buildRules = ( buildRules = (
); );
@ -1321,6 +1322,23 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
3005F34F24A9143C000519B5 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "SWIFTFORMAT_VERSION=\"0.45.*\"\n\nif which swiftformat > /dev/null; then\n if [[ \"$(swiftformat --version)\" == $SWIFTFORMAT_VERSION ]]; then\n swiftformat .\n else\n echo \"Failure: SwiftFormat $SWIFTFORMAT_VERSION is required. Install it or update the build script to use a newer version.\"\n exit 1\n fi\nelse\n echo \"Failure: SwiftFormat not installed. Get it via 'brew install swiftformat'.\"\n exit 2\nfi\n";
};
3EFC287772C1D2B2762FAC45 /* [CP] Check Pods Manifest.lock */ = { 3EFC287772C1D2B2762FAC45 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;

View file

@ -6,15 +6,14 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import CoreData import CoreData
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
import SwiftyUserDefaults import SwiftyUserDefaults
import UIKit
@UIApplicationMain @UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate { class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow? var window: UIWindow?
enum ViewTag: Int { enum ViewTag: Int {
case blur = 100, appicon case blur = 100, appicon
@ -25,50 +24,51 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return presenter return presenter
}() }()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch. // Override point for customization after application launch.
SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 100)) SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 100))
passcodeLockPresenter.present(windowLevel: UIApplication.shared.windows.last?.windowLevel.rawValue) passcodeLockPresenter.present(windowLevel: UIApplication.shared.windows.last?.windowLevel.rawValue)
if let shortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem { if let shortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
if shortcutItem.type == Globals.bundleIdentifier + ".search" { if shortcutItem.type == Globals.bundleIdentifier + ".search" {
self.perform(#selector(postSearchNotification), with: nil, afterDelay: 0.4) perform(#selector(postSearchNotification), with: nil, afterDelay: 0.4)
} }
} }
return true return true
} }
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { func application(_: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let _ = window?.rootViewController as? PasscodeLockViewController { if let _ = window?.rootViewController as? PasscodeLockViewController {
window?.frame = UIScreen.main.bounds window?.frame = UIScreen.main.bounds
} }
return .all return .all
} }
@objc func postSearchNotification() { @objc
func postSearchNotification() {
NotificationCenter.default.post(name: .passwordSearch, object: nil) NotificationCenter.default.post(name: .passwordSearch, object: nil)
} }
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { func application(_: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler _: @escaping (Bool) -> Void) {
if shortcutItem.type == Globals.bundleIdentifier + ".search" { if shortcutItem.type == Globals.bundleIdentifier + ".search" {
let tabBarController = self.window!.rootViewController as! UITabBarController let tabBarController = window!.rootViewController as! UITabBarController
tabBarController.selectedIndex = 0 tabBarController.selectedIndex = 0
let navigationController = tabBarController.selectedViewController as! UINavigationController let navigationController = tabBarController.selectedViewController as! UINavigationController
navigationController.popToRootViewController(animated: false) navigationController.popToRootViewController(animated: false)
self.perform(#selector(postSearchNotification), with: nil, afterDelay: 0.4) perform(#selector(postSearchNotification), with: nil, afterDelay: 0.4)
} }
} }
func applicationWillResignActive(_ application: UIApplication) { func applicationWillResignActive(_: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
// Display a blur effect view // Display a blur effect view
let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light) let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
let blurEffectView = UIVisualEffectView(effect: blurEffect) let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = (self.window?.frame)! blurEffectView.frame = (window?.frame)!
blurEffectView.tag = ViewTag.blur.rawValue blurEffectView.tag = ViewTag.blur.rawValue
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.window?.addSubview(blurEffectView) window?.addSubview(blurEffectView)
// Display the Pass icon in the middle of the screen // Display the Pass icon in the middle of the screen
let iconsDictionary = Bundle.main.infoDictionary?["CFBundleIcons"] as? NSDictionary let iconsDictionary = Bundle.main.infoDictionary?["CFBundleIcons"] as? NSDictionary
@ -78,33 +78,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
let appIconView = UIImageView(image: appIcon) let appIconView = UIImageView(image: appIcon)
appIconView.layer.cornerRadius = (appIcon?.size.height)! / 5 appIconView.layer.cornerRadius = (appIcon?.size.height)! / 5
appIconView.layer.masksToBounds = true appIconView.layer.masksToBounds = true
appIconView.center = (self.window?.center)! appIconView.center = (window?.center)!
appIconView.tag = ViewTag.appicon.rawValue appIconView.tag = ViewTag.appicon.rawValue
self.window?.addSubview(appIconView) window?.addSubview(appIconView)
} }
func applicationDidEnterBackground(_ application: UIApplication) { func applicationDidEnterBackground(_: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
} }
func applicationWillEnterForeground(_ application: UIApplication) { func applicationWillEnterForeground(_: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
passcodeLockPresenter.present(windowLevel: UIApplication.shared.windows.last?.windowLevel.rawValue) passcodeLockPresenter.present(windowLevel: UIApplication.shared.windows.last?.windowLevel.rawValue)
} }
func applicationDidBecomeActive(_ application: UIApplication) { func applicationDidBecomeActive(_: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
self.window?.viewWithTag(ViewTag.appicon.rawValue)?.removeFromSuperview() window?.viewWithTag(ViewTag.appicon.rawValue)?.removeFromSuperview()
self.window?.viewWithTag(ViewTag.blur.rawValue)?.removeFromSuperview() window?.viewWithTag(ViewTag.blur.rawValue)?.removeFromSuperview()
} }
func applicationWillTerminate(_ application: UIApplication) { func applicationWillTerminate(_: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates. // Saves changes in the application's managed object context before the application terminates.
self.saveContext() saveContext()
} }
// MARK: - Core Data stack // MARK: - Core Data stack
@ -123,7 +122,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
try! FileManager.default.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil) try! FileManager.default.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil)
} }
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: URL(fileURLWithPath: Globals.dbPath))] container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: URL(fileURLWithPath: Globals.dbPath))]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in container.loadPersistentStores(completionHandler: { _, error in
if let error = error as NSError? { if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately. // Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
@ -157,6 +156,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
} }
} }
} }
} }

View file

@ -6,11 +6,10 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class AboutRepositoryTableViewController: BasicStaticTableViewController { class AboutRepositoryTableViewController: BasicStaticTableViewController {
private static let VALUE_NOT_AVAILABLE = "ValueNotAvailable".localize() private static let VALUE_NOT_AVAILABLE = "ValueNotAvailable".localize()
private var needRefresh = false private var needRefresh = false
@ -18,6 +17,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
let indicator = UIActivityIndicatorView(style: .gray) let indicator = UIActivityIndicatorView(style: .gray)
return indicator return indicator
}() }()
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
override func viewDidLoad() { override func viewDidLoad() {
@ -41,10 +41,9 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
} }
private func setTableData() { private func setTableData() {
// clear current contents (if any) // clear current contents (if any)
self.tableData.removeAll(keepingCapacity: true) tableData.removeAll(keepingCapacity: true)
self.tableView.reloadData() tableView.reloadData()
indicator.startAnimating() indicator.startAnimating()
// reload the table // reload the table
@ -67,8 +66,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
[.style: CellDataStyle.value1, .accessoryType: type, .title: "LocalCommits".localize(), .detailText: localCommits], [.style: CellDataStyle.value1, .accessoryType: type, .title: "LocalCommits".localize(), .detailText: localCommits],
[.style: CellDataStyle.value1, .accessoryType: type, .title: "LastSynced".localize(), .detailText: lastSynced], [.style: CellDataStyle.value1, .accessoryType: type, .title: "LastSynced".localize(), .detailText: lastSynced],
[.style: CellDataStyle.value1, .accessoryType: type, .title: "Commits".localize(), .detailText: commits], [.style: CellDataStyle.value1, .accessoryType: type, .title: "Commits".localize(), .detailText: commits],
[.title: "CommitLogs".localize(), .action: "segue", .link: "showCommitLogsSegue"], [.title: "CommitLogs".localize(), .action: "segue", .link: "showCommitLogsSegue"]],
],
] ]
strongSelf.indicator.stopAnimating() strongSelf.indicator.stopAnimating()
strongSelf.tableView.reloadData() strongSelf.tableView.reloadData()
@ -79,15 +77,15 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
private func numberOfPasswordsString() -> String { private func numberOfPasswordsString() -> String {
let formatter = NumberFormatter() let formatter = NumberFormatter()
formatter.numberStyle = NumberFormatter.Style.decimal formatter.numberStyle = NumberFormatter.Style.decimal
return formatter.string(from: NSNumber(value: self.passwordStore.numberOfPasswords)) ?? "" return formatter.string(from: NSNumber(value: passwordStore.numberOfPasswords)) ?? ""
} }
private func sizeOfRepositoryString() -> String { private func sizeOfRepositoryString() -> String {
return ByteCountFormatter.string(fromByteCount: Int64(self.passwordStore.sizeOfRepositoryByteCount), countStyle: ByteCountFormatter.CountStyle.file) ByteCountFormatter.string(fromByteCount: Int64(passwordStore.sizeOfRepositoryByteCount), countStyle: ByteCountFormatter.CountStyle.file)
} }
private func lastSyncedTimeString() -> String { private func lastSyncedTimeString() -> String {
guard let date = self.passwordStore.lastSyncedTime else { guard let date = passwordStore.lastSyncedTime else {
return "SyncAgain?".localize() return "SyncAgain?".localize()
} }
let formatter = DateFormatter() let formatter = DateFormatter()
@ -103,7 +101,8 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
return AboutRepositoryTableViewController.VALUE_NOT_AVAILABLE return AboutRepositoryTableViewController.VALUE_NOT_AVAILABLE
} }
@objc func setNeedRefresh() { @objc
func setNeedRefresh() {
needRefresh = true needRefresh = true
} }
} }

View file

@ -9,17 +9,16 @@
import UIKit import UIKit
class AboutTableViewController: BasicStaticTableViewController { class AboutTableViewController: BasicStaticTableViewController {
override func viewDidLoad() { override func viewDidLoad() {
tableData = [ tableData = [
// section 0 // section 0
[[.title: "Website".localize(), .action: "link", .link: "https://github.com/mssun/pass-ios.git"], [[.title: "Website".localize(), .action: "link", .link: "https://github.com/mssun/pass-ios.git"],
[.title: "Help".localize(), .action: "link", .link: "https://github.com/mssun/passforios/wiki"], [.title: "Help".localize(), .action: "link", .link: "https://github.com/mssun/passforios/wiki"],
[.title: "ContactDeveloper".localize(), .action: "link", .link: "mailto:developer@passforios.mssun.me?subject=Pass%20for%20iOS"],], [.title: "ContactDeveloper".localize(), .action: "link", .link: "mailto:developer@passforios.mssun.me?subject=Pass%20for%20iOS"]],
// section 1, // section 1,
[[.title: "OpenSourceComponents".localize(), .action: "segue", .link: "showOpenSourceComponentsSegue"], [[.title: "OpenSourceComponents".localize(), .action: "segue", .link: "showOpenSourceComponentsSegue"],
[.title: "SpecialThanks".localize(), .action: "segue", .link: "showSpecialThanksSegue"],], [.title: "SpecialThanks".localize(), .action: "segue", .link: "showSpecialThanksSegue"]],
] ]
super.viewDidLoad() super.viewDidLoad()
} }
@ -39,11 +38,10 @@ class AboutTableViewController: BasicStaticTableViewController {
return nil return nil
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 1 { if section == 1 {
return "Acknowledgements".localize().uppercased() return "Acknowledgements".localize().uppercased()
} }
return nil return nil
} }
} }

View file

@ -6,8 +6,8 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class AddPasswordTableViewController: PasswordEditorTableViewController { class AddPasswordTableViewController: PasswordEditorTableViewController {
var defaultDirPrefix = "" var defaultDirPrefix = ""
@ -17,7 +17,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
tableData[0][0][PasswordEditorCellKey.content] = defaultDirPrefix tableData[0][0][PasswordEditorCellKey.content] = defaultDirPrefix
} }
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { override func shouldPerformSegue(withIdentifier identifier: String, sender _: Any?) -> Bool {
if identifier == "saveAddPasswordSegue" { if identifier == "saveAddPasswordSegue" {
// check PGP key // check PGP key
guard PGPAgent.shared.isPrepared else { guard PGPAgent.shared.isPrepared else {

View file

@ -6,16 +6,15 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
import UIKit
class AdvancedSettingsTableViewController: UITableViewController { class AdvancedSettingsTableViewController: UITableViewController {
@IBOutlet var encryptInASCIIArmoredTableViewCell: UITableViewCell!
@IBOutlet weak var encryptInASCIIArmoredTableViewCell: UITableViewCell! @IBOutlet var gitSignatureTableViewCell: UITableViewCell!
@IBOutlet weak var gitSignatureTableViewCell: UITableViewCell! @IBOutlet var eraseDataTableViewCell: UITableViewCell!
@IBOutlet weak var eraseDataTableViewCell: UITableViewCell! @IBOutlet var discardChangesTableViewCell: UITableViewCell!
@IBOutlet weak var discardChangesTableViewCell: UITableViewCell!
let passwordStore = PasswordStore.shared let passwordStore = PasswordStore.shared
let encryptInASCIIArmoredSwitch: UISwitch = { let encryptInASCIIArmoredSwitch: UISwitch = {
@ -37,10 +36,10 @@ class AdvancedSettingsTableViewController: UITableViewController {
private func setGitSignatureText() { private func setGitSignatureText() {
let gitSignatureName = passwordStore.gitSignatureForNow?.name ?? "" let gitSignatureName = passwordStore.gitSignatureForNow?.name ?? ""
let gitSignatureEmail = passwordStore.gitSignatureForNow?.email ?? "" let gitSignatureEmail = passwordStore.gitSignatureForNow?.email ?? ""
self.gitSignatureTableViewCell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote) gitSignatureTableViewCell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote)
self.gitSignatureTableViewCell.detailTextLabel?.text = "\(gitSignatureName) <\(gitSignatureEmail)>" gitSignatureTableViewCell.detailTextLabel?.text = "\(gitSignatureName) <\(gitSignatureEmail)>"
if Defaults.gitSignatureName == nil && Defaults.gitSignatureEmail == nil { if Defaults.gitSignatureName == nil, Defaults.gitSignatureEmail == nil {
self.gitSignatureTableViewCell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .body) gitSignatureTableViewCell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .body)
gitSignatureTableViewCell.detailTextLabel?.text = "NotSet".localize() gitSignatureTableViewCell.detailTextLabel?.text = "NotSet".localize()
} }
} }
@ -49,7 +48,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
tableView.deselectRow(at: indexPath, animated: true) tableView.deselectRow(at: indexPath, animated: true)
if tableView.cellForRow(at: indexPath) == eraseDataTableViewCell { if tableView.cellForRow(at: indexPath) == eraseDataTableViewCell {
let alert = UIAlertController(title: "ErasePasswordStoreData?".localize(), message: "EraseExplanation.".localize(), preferredStyle: UIAlertController.Style.alert) let alert = UIAlertController(title: "ErasePasswordStoreData?".localize(), message: "EraseExplanation.".localize(), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive, handler: {[unowned self] (action) -> Void in alert.addAction(UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive, handler: { [unowned self] (_) -> Void in
SVProgressHUD.show(withStatus: "Erasing...".localize()) SVProgressHUD.show(withStatus: "Erasing...".localize())
self.passwordStore.erase() self.passwordStore.erase()
self.navigationController!.popViewController(animated: true) self.navigationController!.popViewController(animated: true)
@ -57,10 +56,10 @@ class AdvancedSettingsTableViewController: UITableViewController {
SVProgressHUD.dismiss(withDelay: 1) SVProgressHUD.dismiss(withDelay: 1)
})) }))
alert.addAction(UIAlertAction.dismiss()) alert.addAction(UIAlertAction.dismiss())
self.present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} else if tableView.cellForRow(at: indexPath) == discardChangesTableViewCell { } else if tableView.cellForRow(at: indexPath) == discardChangesTableViewCell {
let alert = UIAlertController(title: "DiscardAllLocalChanges?".localize(), message: "DiscardExplanation.".localize(), preferredStyle: UIAlertController.Style.alert) let alert = UIAlertController(title: "DiscardAllLocalChanges?".localize(), message: "DiscardExplanation.".localize(), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "DiscardAllLocalChanges".localize(), style: UIAlertAction.Style.destructive, handler: {[unowned self] (action) -> Void in alert.addAction(UIAlertAction(title: "DiscardAllLocalChanges".localize(), style: UIAlertAction.Style.destructive, handler: { [unowned self] (_) -> Void in
SVProgressHUD.show(withStatus: "Resetting...".localize()) SVProgressHUD.show(withStatus: "Resetting...".localize())
do { do {
let numberDiscarded = try self.passwordStore.reset() let numberDiscarded = try self.passwordStore.reset()
@ -73,15 +72,17 @@ class AdvancedSettingsTableViewController: UITableViewController {
})) }))
alert.addAction(UIAlertAction.dismiss()) alert.addAction(UIAlertAction.dismiss())
self.present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} }
} }
@objc func encryptInASCIIArmoredAction(_ sender: Any?) { @objc
func encryptInASCIIArmoredAction(_: Any?) {
Defaults.encryptInArmored = encryptInASCIIArmoredSwitch.isOn Defaults.encryptInArmored = encryptInASCIIArmoredSwitch.isOn
} }
@IBAction func saveGitConfigSetting(segue: UIStoryboardSegue) { @IBAction
func saveGitConfigSetting(segue: UIStoryboardSegue) {
if let controller = segue.source as? GitConfigSettingsTableViewController { if let controller = segue.source as? GitConfigSettingsTableViewController {
if let gitSignatureName = controller.nameTextField.text, if let gitSignatureName = controller.nameTextField.text,
let gitSignatureEmail = controller.emailTextField.text { let gitSignatureEmail = controller.emailTextField.text {
@ -91,5 +92,4 @@ class AdvancedSettingsTableViewController: UITableViewController {
setGitSignatureText() setGitSignatureText()
} }
} }
} }

View file

@ -6,11 +6,10 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import SafariServices
import MessageUI import MessageUI
import passKit import passKit
import SafariServices
import UIKit
enum CellDataType { enum CellDataType {
case link, segue, empty, detail case link, segue, empty, detail
@ -25,7 +24,7 @@ enum CellDataKey {
} }
class BasicStaticTableViewController: UITableViewController, MFMailComposeViewControllerDelegate { class BasicStaticTableViewController: UITableViewController, MFMailComposeViewControllerDelegate {
var tableData = [[Dictionary<CellDataKey, Any>]]() var tableData = [[[CellDataKey: Any]]]()
var navigationItemTitle: String? var navigationItemTitle: String?
override func viewDidLoad() { override func viewDidLoad() {
@ -35,12 +34,12 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
} }
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in _: UITableView) -> Int {
return tableData.count tableData.count
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData[section].count tableData[section].count
} }
override func didReceiveMemoryWarning() { override func didReceiveMemoryWarning() {
@ -48,8 +47,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
// Dispose of any resources that can be recreated. // Dispose of any resources that can be recreated.
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData = tableData[indexPath.section][indexPath.row] let cellData = tableData[indexPath.section][indexPath.row]
let cellDataStyle = cellData[CellDataKey.style] as? CellDataStyle let cellDataStyle = cellData[CellDataKey.style] as? CellDataStyle
var cell: UITableViewCell? var cell: UITableViewCell?
@ -76,7 +74,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
return cell ?? UITableViewCell() return cell ?? UITableViewCell()
} }
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { override func tableView(_: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let cellData = tableData[indexPath.section][indexPath.row] let cellData = tableData[indexPath.section][indexPath.row]
let selector = cellData[CellDataKey.detailDisclosureAction] as? Selector let selector = cellData[CellDataKey.detailDisclosureAction] as? Selector
perform(selector, with: cellData[CellDataKey.detailDisclosureData]) perform(selector, with: cellData[CellDataKey.detailDisclosureData])
@ -108,7 +106,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
} }
case "http", "https": case "http", "https":
let svc = SFSafariViewController(url: URL(string: link)!, entersReaderIfAvailable: false) let svc = SFSafariViewController(url: URL(string: link)!, entersReaderIfAvailable: false)
self.present(svc, animated: true, completion: nil) present(svc, animated: true, completion: nil)
default: default:
break break
} }
@ -123,10 +121,10 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
mailVC.setToRecipients(recipients) mailVC.setToRecipients(recipients)
mailVC.setSubject(subject) mailVC.setSubject(subject)
mailVC.setMessageBody("", isHTML: false) mailVC.setMessageBody("", isHTML: false)
self.present(mailVC, animated: true, completion: nil) present(mailVC, animated: true, completion: nil)
} }
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith _: MFMailComposeResult, error _: Error?) {
controller.dismiss(animated: true) controller.dismiss(animated: true)
} }
} }

View file

@ -6,9 +6,9 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import ObjectiveGit import ObjectiveGit
import passKit import passKit
import UIKit
class CommitLogsTableViewController: UITableViewController { class CommitLogsTableViewController: UITableViewController {
var commits: [GTCommit] = [] var commits: [GTCommit] = []
@ -18,12 +18,12 @@ class CommitLogsTableViewController: UITableViewController {
super.viewDidLoad() super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateCommitLogs), name: .passwordStoreUpdated, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(updateCommitLogs), name: .passwordStoreUpdated, object: nil)
commits = getCommitLogs() commits = getCommitLogs()
self.tableView.estimatedRowHeight = 50 tableView.estimatedRowHeight = 50
self.tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
return commits.count commits.count
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -42,7 +42,8 @@ class CommitLogsTableViewController: UITableViewController {
return cell return cell
} }
@objc func updateCommitLogs() { @objc
func updateCommitLogs() {
commits = getCommitLogs() commits = getCommitLogs()
tableView.reloadData() tableView.reloadData()
} }

View file

@ -6,11 +6,11 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class EditPasswordTableViewController: PasswordEditorTableViewController { class EditPasswordTableViewController: PasswordEditorTableViewController {
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { override func shouldPerformSegue(withIdentifier identifier: String, sender _: Any?) -> Bool {
if identifier == "saveEditPasswordSegue" { if identifier == "saveEditPasswordSegue" {
// check name // check name
guard checkName() else { guard checkName() else {

View file

@ -6,8 +6,8 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class GeneralSettingsTableViewController: BasicStaticTableViewController { class GeneralSettingsTableViewController: BasicStaticTableViewController {
let passwordStore = PasswordStore.shared let passwordStore = PasswordStore.shared
@ -67,25 +67,23 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
override func viewDidLoad() { override func viewDidLoad() {
tableData = [ tableData = [
// section 0 // section 0
[[.title: "AboutRepository".localize(), .action: "segue", .link: "showAboutRepositorySegue"],], [[.title: "AboutRepository".localize(), .action: "segue", .link: "showAboutRepositorySegue"]],
// section 1 // section 1
[ [
[.title: "RememberPgpKeyPassphrase".localize(), .action: "none",], [.title: "RememberPgpKeyPassphrase".localize(), .action: "none"],
[.title: "RememberGitCredentialPassphrase".localize(), .action: "none",], [.title: "RememberGitCredentialPassphrase".localize(), .action: "none"],
], ],
// section 2 // section 2
[ [
[.title: "ShowFolders".localize(), .action: "none",], [.title: "ShowFolders".localize(), .action: "none"],
[.title: "HidePasswordImages".localize(), .action: "none",], [.title: "HidePasswordImages".localize(), .action: "none"],
[.title: "HideUnknownFields".localize(), .action: "none",], [.title: "HideUnknownFields".localize(), .action: "none"],
[.title: "HideOtpFields".localize(), .action: "none",], [.title: "HideOtpFields".localize(), .action: "none"],
], ],
] ]
super.viewDidLoad() super.viewDidLoad()
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -144,43 +142,50 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
return cell return cell
} }
@objc func tapHideUnknownSwitchDetailButton(_ sender: Any?) { @objc
func tapHideUnknownSwitchDetailButton(_: Any?) {
let alertMessage = "HideUnknownFieldsExplanation.".localize() let alertMessage = "HideUnknownFieldsExplanation.".localize()
let alertTitle = "HideUnknownFields".localize() let alertTitle = "HideUnknownFields".localize()
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil) Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
} }
@objc func tapHideOTPSwitchDetailButton(_ sender: Any?) { @objc
func tapHideOTPSwitchDetailButton(_: Any?) {
let keywordsString = Constants.OTP_KEYWORDS.joined(separator: ", ") let keywordsString = Constants.OTP_KEYWORDS.joined(separator: ", ")
let alertMessage = "HideOtpFieldsExplanation.".localize(keywordsString) let alertMessage = "HideOtpFieldsExplanation.".localize(keywordsString)
let alertTitle = "HideOtpFields".localize() let alertTitle = "HideOtpFields".localize()
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil) Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
} }
@objc func tapHidePasswordImagesSwitchDetailButton(_ sender: Any?) { @objc
func tapHidePasswordImagesSwitchDetailButton(_: Any?) {
let alertMessage = "HidePasswordImagesExplanation.".localize() let alertMessage = "HidePasswordImagesExplanation.".localize()
let alertTitle = "HidePasswordImages".localize() let alertTitle = "HidePasswordImages".localize()
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil) Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
} }
@objc func hideUnknownSwitchAction(_ sender: Any?) { @objc
func hideUnknownSwitchAction(_: Any?) {
Defaults.isHideUnknownOn = hideUnknownSwitch.isOn Defaults.isHideUnknownOn = hideUnknownSwitch.isOn
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil) NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
} }
@objc func hideOTPSwitchAction(_ sender: Any?) { @objc
func hideOTPSwitchAction(_: Any?) {
Defaults.isHideOTPOn = hideOTPSwitch.isOn Defaults.isHideOTPOn = hideOTPSwitch.isOn
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil) NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
} }
@objc func rememberPGPPassphraseSwitchAction(_ sender: Any?) { @objc
func rememberPGPPassphraseSwitchAction(_: Any?) {
Defaults.isRememberPGPPassphraseOn = rememberPGPPassphraseSwitch.isOn Defaults.isRememberPGPPassphraseOn = rememberPGPPassphraseSwitch.isOn
if rememberPGPPassphraseSwitch.isOn == false { if rememberPGPPassphraseSwitch.isOn == false {
AppKeychain.shared.removeAllContent(withPrefix: Globals.pgpKeyPassphrase) AppKeychain.shared.removeAllContent(withPrefix: Globals.pgpKeyPassphrase)
} }
} }
@objc func rememberGitCredentialPassphraseSwitchAction(_ sender: Any?) { @objc
func rememberGitCredentialPassphraseSwitchAction(_: Any?) {
Defaults.isRememberGitCredentialPassphraseOn = rememberGitCredentialPassphraseSwitch.isOn Defaults.isRememberGitCredentialPassphraseOn = rememberGitCredentialPassphraseSwitch.isOn
if rememberGitCredentialPassphraseSwitch.isOn == false { if rememberGitCredentialPassphraseSwitch.isOn == false {
passwordStore.gitSSHPrivateKeyPassphrase = nil passwordStore.gitSSHPrivateKeyPassphrase = nil
@ -188,14 +193,15 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
} }
} }
@objc func showFolderSwitchAction(_ sender: Any?) { @objc
func showFolderSwitchAction(_: Any?) {
Defaults.isShowFolderOn = showFolderSwitch.isOn Defaults.isShowFolderOn = showFolderSwitch.isOn
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil) NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
} }
@objc func hidePasswordImagesSwitchAction(_ sender: Any?) { @objc
func hidePasswordImagesSwitchAction(_: Any?) {
Defaults.isHidePasswordImagesOn = hidePasswordImagesSwitch.isOn Defaults.isHidePasswordImagesOn = hidePasswordImagesSwitch.isOn
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil) NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
} }
} }

View file

@ -6,14 +6,14 @@
// Copyright © 2017 Yishi Lin. All rights reserved. // Copyright © 2017 Yishi Lin. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class GitConfigSettingsTableViewController: UITableViewController { class GitConfigSettingsTableViewController: UITableViewController {
let passwordStore = PasswordStore.shared let passwordStore = PasswordStore.shared
@IBOutlet weak var nameTextField: UITextField! @IBOutlet var nameTextField: UITextField!
@IBOutlet weak var emailTextField: UITextField! @IBOutlet var emailTextField: UITextField!
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -26,7 +26,7 @@ class GitConfigSettingsTableViewController: UITableViewController {
emailTextField.text = Defaults.gitSignatureEmail emailTextField.text = Defaults.gitSignatureEmail
} }
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { override func shouldPerformSegue(withIdentifier identifier: String, sender _: Any?) -> Bool {
if identifier == "saveGitConfigSettingSegue" { if identifier == "saveGitConfigSettingSegue" {
let name = nameTextField.text!.isEmpty ? Globals.gitSignatureDefaultName : nameTextField.text! let name = nameTextField.text!.isEmpty ? Globals.gitSignatureDefaultName : nameTextField.text!
let email = emailTextField.text!.isEmpty ? Globals.gitSignatureDefaultEmail : nameTextField.text! let email = emailTextField.text!.isEmpty ? Globals.gitSignatureDefaultEmail : nameTextField.text!
@ -38,4 +38,3 @@ class GitConfigSettingsTableViewController: UITableViewController {
return true return true
} }
} }

View file

@ -6,21 +6,20 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
import UIKit
class GitRepositorySettingsTableViewController: UITableViewController { class GitRepositorySettingsTableViewController: UITableViewController {
// MARK: - View Outlet // MARK: - View Outlet
@IBOutlet weak var gitURLTextField: UITextField! @IBOutlet var gitURLTextField: UITextField!
@IBOutlet weak var usernameTextField: UITextField! @IBOutlet var usernameTextField: UITextField!
@IBOutlet weak var branchNameTextField: UITextField! @IBOutlet var branchNameTextField: UITextField!
@IBOutlet weak var authSSHKeyCell: UITableViewCell! @IBOutlet var authSSHKeyCell: UITableViewCell!
@IBOutlet weak var authPasswordCell: UITableViewCell! @IBOutlet var authPasswordCell: UITableViewCell!
@IBOutlet weak var gitURLCell: UITableViewCell! @IBOutlet var gitURLCell: UITableViewCell!
@IBOutlet weak var gitRepositoryURLTabelViewCell: UITableViewCell! @IBOutlet var gitRepositoryURLTabelViewCell: UITableViewCell!
// MARK: - Properties // MARK: - Properties
@ -33,20 +32,23 @@ class GitRepositorySettingsTableViewController: UITableViewController {
updateAuthenticationMethodCheckView(for: newValue) updateAuthenticationMethodCheckView(for: newValue)
} }
} }
private var gitUrl: URL { private var gitUrl: URL {
get { Defaults.gitURL } get { Defaults.gitURL }
set { Defaults.gitURL = newValue } set { Defaults.gitURL = newValue }
} }
private var gitBranchName: String { private var gitBranchName: String {
get { Defaults.gitBranchName } get { Defaults.gitBranchName }
set { Defaults.gitBranchName = newValue } set { Defaults.gitBranchName = newValue }
} }
private var gitUsername: String { private var gitUsername: String {
get { Defaults.gitUsername } get { Defaults.gitUsername }
set { Defaults.gitUsername = newValue } set { Defaults.gitUsername = newValue }
} }
private var gitCredential: GitCredential { private var gitCredential: GitCredential {
get {
switch Defaults.gitAuthenticationMethod { switch Defaults.gitAuthenticationMethod {
case .password: case .password:
return GitCredential(credential: .http(userName: Defaults.gitUsername)) return GitCredential(credential: .http(userName: Defaults.gitUsername))
@ -55,15 +57,14 @@ class GitRepositorySettingsTableViewController: UITableViewController {
return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey)) return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey))
} }
} }
}
// MARK: - View Controller Lifecycle // MARK: - View Controller Lifecycle
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
gitURLTextField.text = self.gitUrl.absoluteString gitURLTextField.text = gitUrl.absoluteString
usernameTextField.text = self.gitUsername usernameTextField.text = gitUsername
branchNameTextField.text = self.gitBranchName branchNameTextField.text = gitBranchName
sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel
authSSHKeyCell.accessoryType = .detailButton authSSHKeyCell.accessoryType = .detailButton
} }
@ -94,7 +95,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) let cell = tableView.cellForRow(at: indexPath)
if cell == authPasswordCell { if cell == authPasswordCell {
self.gitAuthenticationMethod = .password gitAuthenticationMethod = .password
} else if cell == authSSHKeyCell { } else if cell == authSSHKeyCell {
if !AppKeychain.shared.contains(key: SshKey.PRIVATE.getKeychainKey()) { if !AppKeychain.shared.contains(key: SshKey.PRIVATE.getKeychainKey()) {
Utils.alert(title: "CannotSelectSshKey".localize(), message: "PleaseSetupSshKeyFirst.".localize(), controller: self) Utils.alert(title: "CannotSelectSshKey".localize(), message: "PleaseSetupSshKeyFirst.".localize(), controller: self)
@ -108,7 +109,8 @@ class GitRepositorySettingsTableViewController: UITableViewController {
// MARK: - Segue Handlers // MARK: - Segue Handlers
@IBAction func save(_ sender: Any) { @IBAction
func save(_: Any) {
guard let gitURLTextFieldText = gitURLTextField.text, let gitURL = URL(string: gitURLTextFieldText.trimmed) else { guard let gitURLTextFieldText = gitURLTextField.text, let gitURL = URL(string: gitURLTextFieldText.trimmed) else {
Utils.alert(title: "CannotSave".localize(), message: "SetGitRepositoryUrl".localize(), controller: self) Utils.alert(title: "CannotSave".localize(), message: "SetGitRepositoryUrl".localize(), controller: self)
return return
@ -137,9 +139,9 @@ class GitRepositorySettingsTableViewController: UITableViewController {
} }
} }
self.gitUrl = gitURL gitUrl = gitURL
self.gitBranchName = branchName.trimmed gitBranchName = branchName.trimmed
self.gitUsername = (gitURL.user ?? usernameTextField.text ?? "git").trimmed gitUsername = (gitURL.user ?? usernameTextField.text ?? "git").trimmed
if passwordStore.repositoryExists() { if passwordStore.repositoryExists() {
let overwriteAlert: UIAlertController = { let overwriteAlert: UIAlertController = {
@ -150,7 +152,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
alert.addAction(UIAlertAction.cancel()) alert.addAction(UIAlertAction.cancel())
return alert return alert
}() }()
self.present(overwriteAlert, animated: true) present(overwriteAlert, animated: true)
} else { } else {
cloneAndSegueIfSuccess() cloneAndSegueIfSuccess()
} }
@ -159,15 +161,15 @@ class GitRepositorySettingsTableViewController: UITableViewController {
private func cloneAndSegueIfSuccess() { private func cloneAndSegueIfSuccess() {
// Remember git credential password/passphrase temporarily, ask whether users want this after a successful clone. // Remember git credential password/passphrase temporarily, ask whether users want this after a successful clone.
Defaults.isRememberGitCredentialPassphraseOn = true Defaults.isRememberGitCredentialPassphraseOn = true
DispatchQueue.global(qos: .userInitiated).async() { DispatchQueue.global(qos: .userInitiated).async {
do { do {
let transferProgressBlock: (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void = { (git_transfer_progress, _) in let transferProgressBlock: (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void = { git_transfer_progress, _ in
let gitTransferProgress = git_transfer_progress.pointee let gitTransferProgress = git_transfer_progress.pointee
let progress = Float(gitTransferProgress.received_objects) / Float(gitTransferProgress.total_objects) let progress = Float(gitTransferProgress.received_objects) / Float(gitTransferProgress.total_objects)
SVProgressHUD.showProgress(progress, status: "Cloning Remote Repository") SVProgressHUD.showProgress(progress, status: "Cloning Remote Repository")
} }
let checkoutProgressBlock: (String, UInt, UInt) -> Void = { (_, completedSteps, totalSteps) in let checkoutProgressBlock: (String, UInt, UInt) -> Void = { _, completedSteps, totalSteps in
let progress = Float(completedSteps) / Float(totalSteps) let progress = Float(completedSteps) / Float(totalSteps)
SVProgressHUD.showProgress(progress, status: "CheckingOutBranch".localize(self.gitBranchName)) SVProgressHUD.showProgress(progress, status: "CheckingOutBranch".localize(self.gitBranchName))
} }
@ -179,7 +181,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
transferProgressBlock: transferProgressBlock, transferProgressBlock: transferProgressBlock,
checkoutProgressBlock: checkoutProgressBlock) checkoutProgressBlock: checkoutProgressBlock)
SVProgressHUD.dismiss() { SVProgressHUD.dismiss {
let savePassphraseAlert: UIAlertController = { let savePassphraseAlert: UIAlertController = {
let alert = UIAlertController(title: "Done".localize(), message: "WantToSaveGitCredential?".localize(), preferredStyle: .alert) let alert = UIAlertController(title: "Done".localize(), message: "WantToSaveGitCredential?".localize(), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "No".localize(), style: .default) { _ in alert.addAction(UIAlertAction(title: "No".localize(), style: .default) { _ in
@ -199,7 +201,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
} }
} }
} catch { } catch {
SVProgressHUD.dismiss() { SVProgressHUD.dismiss {
let error = error as NSError let error = error as NSError
var message = error.localizedDescription var message = error.localizedDescription
if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError { if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError {
@ -213,7 +215,8 @@ class GitRepositorySettingsTableViewController: UITableViewController {
} }
} }
@IBAction func importSSHKey(segue: UIStoryboardSegue) { @IBAction
func importSSHKey(segue: UIStoryboardSegue) {
guard let sourceController = segue.source as? KeyImporter, sourceController.isReadyToUse() else { guard let sourceController = segue.source as? KeyImporter, sourceController.isReadyToUse() else {
return return
} }
@ -278,7 +281,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
} }
private func requestCredentialPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? { private func requestCredentialPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
return requestGitCredentialPassword(credential: credential, lastPassword: lastPassword, controller: self) requestGitCredentialPassword(credential: credential, lastPassword: lastPassword, controller: self)
} }
private func updateAuthenticationMethodCheckView(for method: GitAuthenticationMethod) { private func updateAuthenticationMethodCheckView(for method: GitAuthenticationMethod) {
@ -306,12 +309,11 @@ class GitRepositorySettingsTableViewController: UITableViewController {
} }
extension GitRepositorySettingsTableViewController: KeyImporter { extension GitRepositorySettingsTableViewController: KeyImporter {
static let keySource = KeySource.itunes static let keySource = KeySource.itunes
static let label = "ITunesFileSharing".localize() static let label = "ITunesFileSharing".localize()
func isReadyToUse() -> Bool { func isReadyToUse() -> Bool {
return KeyFileManager.PrivateSsh.doesKeyFileExist() KeyFileManager.PrivateSsh.doesKeyFileExist()
} }
func importKeys() throws { func importKeys() throws {

View file

@ -9,7 +9,6 @@
import passKit import passKit
protocol KeyImporter { protocol KeyImporter {
static var keySource: KeySource { get } static var keySource: KeySource { get }
static var label: String { get } static var label: String { get }
@ -24,9 +23,8 @@ protocol KeyImporter {
} }
extension KeyImporter { extension KeyImporter {
static var isCurrentKeySource: Bool { static var isCurrentKeySource: Bool {
return Defaults.gitSSHKeySource == Self.keySource Defaults.gitSSHKeySource == Self.keySource
} }
static var menuLabel: String { static var menuLabel: String {

View file

@ -6,11 +6,10 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import SafariServices import SafariServices
import UIKit
class OpenSourceComponentsTableViewController: BasicStaticTableViewController { class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
private static let openSourceComponents = [ private static let openSourceComponents = [
["FavIcon", ["FavIcon",
"https://github.com/bitserf/FavIcon", "https://github.com/bitserf/FavIcon",
@ -48,12 +47,13 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
.link: item[1], .link: item[1],
.accessoryType: UITableViewCell.AccessoryType.detailDisclosureButton, .accessoryType: UITableViewCell.AccessoryType.detailDisclosureButton,
.detailDisclosureAction: #selector(actOnDetailDisclosureButton(_:)), .detailDisclosureAction: #selector(actOnDetailDisclosureButton(_:)),
.detailDisclosureData: item[2] .detailDisclosureData: item[2],
]) ])
} }
} }
@objc func actOnDetailDisclosureButton(_ sender: Any?) { @objc
func actOnDetailDisclosureButton(_ sender: Any?) {
if let link = sender as? String, let url = URL(string: link) { if let link = sender as? String, let url = URL(string: link) {
let svc = SFSafariViewController(url: url, entersReaderIfAvailable: false) let svc = SFSafariViewController(url: url, entersReaderIfAvailable: false)
present(svc, animated: true) present(svc, animated: true)

View file

@ -6,15 +6,14 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController, UITextViewDelegate, QRScannerControllerDelegate { class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
@IBOutlet var armorPublicKeyTextView: UITextView!
@IBOutlet weak var armorPublicKeyTextView: UITextView! @IBOutlet var armorPrivateKeyTextView: UITextView!
@IBOutlet weak var armorPrivateKeyTextView: UITextView! @IBOutlet var scanPublicKeyCell: UITableViewCell!
@IBOutlet weak var scanPublicKeyCell: UITableViewCell! @IBOutlet var scanPrivateKeyCell: UITableViewCell!
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
var armorPublicKey: String? var armorPublicKey: String?
var armorPrivateKey: String? var armorPrivateKey: String?
@ -23,45 +22,47 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
enum KeyType { enum KeyType {
case publicKey, privateKey case publicKey, privateKey
} }
var keyType = KeyType.publicKey var keyType = KeyType.publicKey
var segments = [String]() var segments = [String]()
var message = "" var message = ""
func reset(keytype: KeyType) { func reset(keytype: KeyType) {
self.keyType = keytype keyType = keytype
self.segments.removeAll() segments.removeAll()
message = "LookingForStartingFrame.".localize() message = "LookingForStartingFrame.".localize()
} }
func addSegment(segment: String) -> (accept: Bool, message: String) { func addSegment(segment: String) -> (accept: Bool, message: String) {
let keyTypeStr = self.keyType == .publicKey ? "Public" : "Private" let keyTypeStr = keyType == .publicKey ? "Public" : "Private"
let theOtherKeyTypeStr = self.keyType == .publicKey ? "Private" : "Public" let theOtherKeyTypeStr = keyType == .publicKey ? "Private" : "Public"
// Skip duplicated segments. // Skip duplicated segments.
guard segment != self.segments.last else { guard segment != segments.last else {
return (accept: false, message: self.message) return (accept: false, message: message)
} }
// Check whether we have found the first block. // Check whether we have found the first block.
guard !self.segments.isEmpty || segment.contains("-----BEGIN PGP \(keyTypeStr.uppercased()) KEY BLOCK-----") else { guard !segments.isEmpty || segment.contains("-----BEGIN PGP \(keyTypeStr.uppercased()) KEY BLOCK-----") else {
// Check whether we are scanning the wrong key type. // Check whether we are scanning the wrong key type.
if segment.contains("-----BEGIN PGP \(theOtherKeyTypeStr.uppercased()) KEY BLOCK-----") { if segment.contains("-----BEGIN PGP \(theOtherKeyTypeStr.uppercased()) KEY BLOCK-----") {
self.message = "Scan\(keyTypeStr)Key.".localize() message = "Scan\(keyTypeStr)Key.".localize()
} }
return (accept: false, message: self.message) return (accept: false, message: message)
} }
// Update the list of scanned segment and return. // Update the list of scanned segment and return.
self.segments.append(segment) segments.append(segment)
if segment.contains("-----END PGP \(keyTypeStr.uppercased()) KEY BLOCK-----") { if segment.contains("-----END PGP \(keyTypeStr.uppercased()) KEY BLOCK-----") {
self.message = "Done".localize() message = "Done".localize()
return (accept: true, message: self.message) return (accept: true, message: message)
} else { } else {
self.message = "ScannedQrCodes(%d)".localize(self.segments.count) message = "ScannedQrCodes(%d)".localize(segments.count)
return (accept: false, message: self.message) return (accept: false, message: message)
} }
} }
} }
var scanned = ScannedPGPKey() var scanned = ScannedPGPKey()
override func viewDidLoad() { override func viewDidLoad() {
@ -76,13 +77,14 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
scanPrivateKeyCell?.accessoryType = .disclosureIndicator scanPrivateKeyCell?.accessoryType = .disclosureIndicator
} }
@IBAction func save(_ sender: Any) { @IBAction
func save(_: Any) {
armorPublicKey = armorPublicKeyTextView.text armorPublicKey = armorPublicKeyTextView.text
armorPrivateKey = armorPrivateKeyTextView.text armorPrivateKey = armorPrivateKeyTextView.text
self.saveImportedKeys() saveImportedKeys()
} }
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { func textView(_: UITextView, shouldChangeTextIn _: NSRange, replacementText text: String) -> Bool {
if text == UIPasteboard.general.string { if text == UIPasteboard.general.string {
// user pastes something, do the copy here again and clear the pasteboard in 45s // user pastes something, do the copy here again and clear the pasteboard in 45s
SecurePasteboard.shared.copy(textToCopy: text) SecurePasteboard.shared.copy(textToCopy: text)
@ -94,21 +96,23 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
let selectedCell = tableView.cellForRow(at: indexPath) let selectedCell = tableView.cellForRow(at: indexPath)
if selectedCell == scanPublicKeyCell { if selectedCell == scanPublicKeyCell {
scanned.reset(keytype: ScannedPGPKey.KeyType.publicKey) scanned.reset(keytype: ScannedPGPKey.KeyType.publicKey)
self.performSegue(withIdentifier: "showPGPScannerSegue", sender: self) performSegue(withIdentifier: "showPGPScannerSegue", sender: self)
} else if selectedCell == scanPrivateKeyCell { } else if selectedCell == scanPrivateKeyCell {
scanned.reset(keytype: ScannedPGPKey.KeyType.privateKey) scanned.reset(keytype: ScannedPGPKey.KeyType.privateKey)
self.performSegue(withIdentifier: "showPGPScannerSegue", sender: self) performSegue(withIdentifier: "showPGPScannerSegue", sender: self)
} }
tableView.deselectRow(at: indexPath, animated: true) tableView.deselectRow(at: indexPath, animated: true)
} }
// MARK: - QRScannerControllerDelegate Methods // MARK: - QRScannerControllerDelegate Methods
func checkScannedOutput(line: String) -> (accept: Bool, message: String) { func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
return scanned.addSegment(segment: line) return scanned.addSegment(segment: line)
} }
// MARK: - QRScannerControllerDelegate Methods // MARK: - QRScannerControllerDelegate Methods
func handleScannedOutput(line: String) {
func handleScannedOutput(line _: String) {
let key = scanned.segments.joined(separator: "") let key = scanned.segments.joined(separator: "")
switch scanned.keyType { switch scanned.keyType {
case .publicKey: case .publicKey:
@ -118,7 +122,7 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
} }
} }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { override func prepare(for segue: UIStoryboardSegue, sender _: Any?) {
if segue.identifier == "showPGPScannerSegue" { if segue.identifier == "showPGPScannerSegue" {
if let navController = segue.destination as? UINavigationController { if let navController = segue.destination as? UINavigationController {
if let viewController = navController.topViewController as? QRScannerController { if let viewController = navController.topViewController as? QRScannerController {
@ -132,7 +136,6 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
} }
extension PGPKeyArmorImportTableViewController: PGPKeyImporter { extension PGPKeyArmorImportTableViewController: PGPKeyImporter {
static let keySource = KeySource.armor static let keySource = KeySource.armor
static let label = "AsciiArmorEncryptedKey".localize() static let label = "AsciiArmorEncryptedKey".localize()

View file

@ -9,18 +9,18 @@
import passKit import passKit
class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController { class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController {
@IBOutlet var pgpPublicKeyFile: UITableViewCell!
@IBOutlet var pgpPrivateKeyFile: UITableViewCell!
@IBOutlet weak var pgpPublicKeyFile: UITableViewCell! private var publicKey: String?
@IBOutlet weak var pgpPrivateKeyFile: UITableViewCell! private var privateKey: String?
private var publicKey: String? = nil
private var privateKey: String? = nil
private enum KeyType { case none, `private`, `public` } private enum KeyType { case none, `private`, `public` }
private var currentlyPicking = KeyType.none private var currentlyPicking = KeyType.none
@IBAction func save(_ sender: Any) { @IBAction
self.saveImportedKeys() func save(_: Any) {
saveImportedKeys()
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -43,7 +43,6 @@ class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController {
} }
extension PGPKeyFileImportTableViewController: UIDocumentPickerDelegate { extension PGPKeyFileImportTableViewController: UIDocumentPickerDelegate {
func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt url: [URL]) { func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt url: [URL]) {
guard let url = url.first else { guard let url = url.first else {
return return
@ -78,12 +77,11 @@ extension PGPKeyFileImportTableViewController: UIDocumentPickerDelegate {
} }
extension PGPKeyFileImportTableViewController: PGPKeyImporter { extension PGPKeyFileImportTableViewController: PGPKeyImporter {
static let keySource = KeySource.file static let keySource = KeySource.file
static let label = "LoadFromFiles".localize() static let label = "LoadFromFiles".localize()
func isReadyToUse() -> Bool { func isReadyToUse() -> Bool {
return validate(key: publicKey) && validate(key: privateKey) validate(key: publicKey) && validate(key: privateKey)
} }
func importKeys() throws { func importKeys() throws {

View file

@ -9,19 +9,15 @@
import passKit import passKit
protocol PGPKeyImporter: KeyImporter { protocol PGPKeyImporter: KeyImporter {
func doAfterImport() func doAfterImport()
func saveImportedKeys() func saveImportedKeys()
} }
extension PGPKeyImporter { extension PGPKeyImporter {
static var isCurrentKeySource: Bool { static var isCurrentKeySource: Bool {
return Defaults.pgpKeySource == Self.keySource Defaults.pgpKeySource == Self.keySource
} }
func doAfterImport() { func doAfterImport() {}
}
} }

View file

@ -6,13 +6,12 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class PGPKeyUrlImportTableViewController: AutoCellHeightUITableViewController { class PGPKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
@IBOutlet var pgpPublicKeyURLTextField: UITextField!
@IBOutlet weak var pgpPublicKeyURLTextField: UITextField! @IBOutlet var pgpPrivateKeyURLTextField: UITextField!
@IBOutlet weak var pgpPrivateKeyURLTextField: UITextField!
var pgpPrivateKeyURL: URL? var pgpPrivateKeyURL: URL?
var pgpPublicKeyURL: URL? var pgpPublicKeyURL: URL?
@ -23,7 +22,8 @@ class PGPKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
pgpPrivateKeyURLTextField.text = Defaults.pgpPrivateKeyURL?.absoluteString pgpPrivateKeyURLTextField.text = Defaults.pgpPrivateKeyURL?.absoluteString
} }
@IBAction func save(_ sender: Any) { @IBAction
func save(_: Any) {
guard let publicKeyURLText = pgpPublicKeyURLTextField.text, guard let publicKeyURLText = pgpPublicKeyURLTextField.text,
let publicKeyURL = URL(string: publicKeyURLText), let publicKeyURL = URL(string: publicKeyURLText),
let privateKeyURLText = pgpPrivateKeyURLTextField.text, let privateKeyURLText = pgpPrivateKeyURLTextField.text,
@ -36,17 +36,16 @@ class PGPKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
} }
pgpPrivateKeyURL = privateKeyURL pgpPrivateKeyURL = privateKeyURL
pgpPublicKeyURL = publicKeyURL pgpPublicKeyURL = publicKeyURL
self.saveImportedKeys() saveImportedKeys()
} }
} }
extension PGPKeyUrlImportTableViewController: PGPKeyImporter { extension PGPKeyUrlImportTableViewController: PGPKeyImporter {
static let keySource = KeySource.url static let keySource = KeySource.url
static let label = "DownloadFromUrl".localize() static let label = "DownloadFromUrl".localize()
func isReadyToUse() -> Bool { func isReadyToUse() -> Bool {
return validate(pgpKeyUrl: pgpPublicKeyURLTextField.text ?? "") validate(pgpKeyUrl: pgpPublicKeyURLTextField.text ?? "")
&& validate(pgpKeyUrl: pgpPrivateKeyURLTextField.text ?? "") && validate(pgpKeyUrl: pgpPrivateKeyURLTextField.text ?? "")
} }

View file

@ -6,10 +6,10 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import FavIcon import FavIcon
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
import UIKit
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate { class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
var passwordEntity: PasswordEntity? var passwordEntity: PasswordEntity?
@ -52,7 +52,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
tableView.addGestureRecognizer(tapGesture) tableView.addGestureRecognizer(tapGesture)
tapGesture.delegate = self tapGesture.delegate = self
tableView.contentInset = UIEdgeInsets.init(top: -36, left: 0, bottom: 44, right: 0); tableView.contentInset = UIEdgeInsets(top: -36, left: 0, bottom: 44, right: 0)
tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 52 tableView.estimatedRowHeight = 52
@ -66,8 +66,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let image = UIImage(data: imageData as Data) let image = UIImage(data: imageData as Data)
passwordImage = image passwordImage = image
} }
self.decryptThenShowPassword() decryptThenShowPassword()
self.setupOneTimePasswordAutoRefresh() setupOneTimePasswordAutoRefresh()
// pop the current view because this password might be "discarded" // pop the current view because this password might be "discarded"
NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
@ -78,16 +78,17 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
if self.shouldPopCurrentView { if shouldPopCurrentView {
let alert = UIAlertController(title: "Notice".localize(), message: "PreviousChangesDiscarded.".localize(), preferredStyle: UIAlertController.Style.alert) let alert = UIAlertController(title: "Notice".localize(), message: "PreviousChangesDiscarded.".localize(), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction.okAndPopView(controller: self)) alert.addAction(UIAlertAction.okAndPopView(controller: self))
self.present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} }
} }
@objc private func decryptThenShowPassword(keyID: String? = nil) { @objc
private func decryptThenShowPassword(keyID: String? = nil) {
guard let passwordEntity = passwordEntity else { guard let passwordEntity = passwordEntity else {
Utils.alert(title: "CannotShowPassword".localize(), message: "PasswordDoesNotExist".localize(), controller: self, handler: {(UIAlertAction) -> Void in Utils.alert(title: "CannotShowPassword".localize(), message: "PasswordDoesNotExist".localize(), controller: self, handler: { (_) -> Void in
self.navigationController!.popViewController(animated: true) self.navigationController!.popViewController(animated: true)
}) })
return return
@ -98,7 +99,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self) let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
self.password = try self.passwordStore.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase) self.password = try self.passwordStore.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
self.showPassword() self.showPassword()
} catch AppError.PgpPrivateKeyNotFound(let key) { } catch let AppError.PgpPrivateKeyNotFound(key) {
DispatchQueue.main.async { DispatchQueue.main.async {
// alert: cancel or try again // alert: cancel or try again
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert) let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
@ -141,7 +142,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
private func setupOneTimePasswordAutoRefresh() { private func setupOneTimePasswordAutoRefresh() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak self] timer in [weak self] _ in
// bail out of the timer code if the object has been freed // bail out of the timer code if the object has been freed
guard let strongSelf = self, guard let strongSelf = self,
let otpType = strongSelf.password?.otpType, let otpType = strongSelf.password?.otpType,
@ -163,33 +164,35 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
@objc private func pressEdit(_ sender: Any?) { @objc
private func pressEdit(_: Any?) {
performSegue(withIdentifier: "editPasswordSegue", sender: self) performSegue(withIdentifier: "editPasswordSegue", sender: self)
} }
@objc private func setShouldPopCurrentView() { @objc
self.shouldPopCurrentView = true private func setShouldPopCurrentView() {
shouldPopCurrentView = true
} }
@IBAction private func cancelEditPassword(segue: UIStoryboardSegue) { @IBAction
private func cancelEditPassword(segue _: UIStoryboardSegue) {}
} @IBAction
private func saveEditPassword(segue _: UIStoryboardSegue) {
@IBAction private func saveEditPassword(segue: UIStoryboardSegue) { if password!.changed != 0 {
if self.password!.changed != 0 { saveEditPassword(password: password!)
self.saveEditPassword(password: self.password!)
} }
} }
private func saveEditPassword(password: Password, keyID: String? = nil) { private func saveEditPassword(password: Password, keyID: String? = nil) {
SVProgressHUD.show(withStatus: "Saving".localize()) SVProgressHUD.show(withStatus: "Saving".localize())
do { do {
self.passwordEntity = try self.passwordStore.edit(passwordEntity: self.passwordEntity!, password: password, keyID: keyID) passwordEntity = try passwordStore.edit(passwordEntity: passwordEntity!, password: password, keyID: keyID)
self.setTableData() setTableData()
self.tableView.reloadData() tableView.reloadData()
SVProgressHUD.showSuccess(withStatus: "Success".localize()) SVProgressHUD.showSuccess(withStatus: "Success".localize())
SVProgressHUD.dismiss(withDelay: 1) SVProgressHUD.dismiss(withDelay: 1)
} catch AppError.PgpPublicKeyNotFound(let key) { } catch let AppError.PgpPublicKeyNotFound(key) {
DispatchQueue.main.async { DispatchQueue.main.async {
// alert: cancel or select keys // alert: cancel or select keys
SVProgressHUD.dismiss() SVProgressHUD.dismiss()
@ -210,17 +213,18 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
@IBAction private func deletePassword(segue: UIStoryboardSegue) { @IBAction
private func deletePassword(segue _: UIStoryboardSegue) {
do { do {
try passwordStore.delete(passwordEntity: passwordEntity!) try passwordStore.delete(passwordEntity: passwordEntity!)
} catch { } catch {
Utils.alert(title: "Error".localize(), message: error.localizedDescription, controller: self, completion: nil) Utils.alert(title: "Error".localize(), message: error.localizedDescription, controller: self, completion: nil)
} }
let _ = navigationController?.popViewController(animated: true) _ = navigationController?.popViewController(animated: true)
} }
private func setTableData() { private func setTableData() {
self.tableData = Array<TableSection>() tableData = [TableSection]()
// name section // name section
var section = TableSection(type: .name) var section = TableSection(type: .name)
@ -239,7 +243,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
section.item.append(Constants.PASSWORD_KEYWORD => password.password) section.item.append(Constants.PASSWORD_KEYWORD => password.password)
tableData.append(section) tableData.append(section)
// addition section // addition section
// show one time password // show one time password
@ -254,7 +257,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
// show additional information // show additional information
let filteredAdditionKeys = password.getFilteredAdditions() let filteredAdditionKeys = password.getFilteredAdditions()
if filteredAdditionKeys.count > 0 { if !filteredAdditionKeys.isEmpty {
section = TableSection(type: .addition, header: "Additions".localize()) section = TableSection(type: .addition, header: "Additions".localize())
section.item.append(contentsOf: filteredAdditionKeys) section.item.append(contentsOf: filteredAdditionKeys)
tableData.append(section) tableData.append(section)
@ -264,10 +267,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
section = TableSection(type: .misc) section = TableSection(type: .misc)
section.item.append(AdditionField(title: "ShowRaw".localize())) section.item.append(AdditionField(title: "ShowRaw".localize()))
tableData.append(section) tableData.append(section)
} }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { override func prepare(for segue: UIStoryboardSegue, sender _: Any?) {
if segue.identifier == "editPasswordSegue" { if segue.identifier == "editPasswordSegue" {
if let controller = segue.destination as? UINavigationController { if let controller = segue.destination as? UINavigationController {
if let editController = controller.viewControllers.first as? EditPasswordTableViewController { if let editController = controller.viewControllers.first as? EditPasswordTableViewController {
@ -311,11 +313,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
@objc private func tapMenu(recognizer: UITapGestureRecognizer) { @objc
private func tapMenu(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizer.State.ended { if recognizer.state == UIGestureRecognizer.State.ended {
let tapLocation = recognizer.location(in: self.tableView) let tapLocation = recognizer.location(in: tableView)
if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) { if let tapIndexPath = tableView.indexPathForRow(at: tapLocation) {
if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) as? LabelTableViewCell { if let tappedCell = tableView.cellForRow(at: tapIndexPath) as? LabelTableViewCell {
tappedCell.becomeFirstResponder() tappedCell.becomeFirstResponder()
let menuController = UIMenuController.shared let menuController = UIMenuController.shared
let revealItem = UIMenuItem(title: "Reveal".localize(), action: #selector(LabelTableViewCell.revealPassword(_:))) let revealItem = UIMenuItem(title: "Reveal".localize(), action: #selector(LabelTableViewCell.revealPassword(_:)))
@ -330,22 +333,22 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { func gestureRecognizer(_: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.isKind(of: UIButton.classForCoder()) { if touch.view!.isKind(of: UIButton.classForCoder()) {
return false return false
} }
return true return true
} }
@IBAction func back(segue:UIStoryboardSegue) { @IBAction
} func back(segue _: UIStoryboardSegue) {}
func getNextHOTP() { func getNextHOTP() {
guard password != nil, passwordEntity != nil, password?.otpType == .hotp else { guard password != nil, passwordEntity != nil, password?.otpType == .hotp else {
DispatchQueue.main.async { DispatchQueue.main.async {
Utils.alert(title: "Error".localize(), message: "GetNextPasswordOfNonHotp.".localize(), controller: self, completion: nil) Utils.alert(title: "Error".localize(), message: "GetNextPasswordOfNonHotp.".localize(), controller: self, completion: nil)
} }
return; return
} }
// copy HOTP to pasteboard (will update counter) // copy HOTP to pasteboard (will update counter)
@ -356,7 +359,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
// commit the change of HOTP counter // commit the change of HOTP counter
if password!.changed != 0 { if password!.changed != 0 {
do { do {
self.passwordEntity = try self.passwordStore.edit(passwordEntity: self.passwordEntity!, password: self.password!) passwordEntity = try passwordStore.edit(passwordEntity: passwordEntity!, password: password!)
SVProgressHUD.showSuccess(withStatus: "PasswordCopied".localize() | "CounterUpdated".localize()) SVProgressHUD.showSuccess(withStatus: "PasswordCopied".localize() | "CounterUpdated".localize())
SVProgressHUD.dismiss(withDelay: 1) SVProgressHUD.dismiss(withDelay: 1)
} catch { } catch {
@ -384,19 +387,19 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
return from return from
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in _: UITableView) -> Int {
return tableData.count tableData.count
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData[section].item.count tableData[section].item.count
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionIndex = indexPath.section let sectionIndex = indexPath.section
let rowIndex = indexPath.row let rowIndex = indexPath.row
let tableDataItem = tableData[sectionIndex].item[rowIndex] let tableDataItem = tableData[sectionIndex].item[rowIndex]
switch(tableData[sectionIndex].type) { switch tableData[sectionIndex].type {
case .name: case .name:
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordDetailTitleTableViewCell", for: indexPath) as! PasswordDetailTitleTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "passwordDetailTitleTableViewCell", for: indexPath) as! PasswordDetailTitleTableViewCell
if !Defaults.isHidePasswordImagesOn { if !Defaults.isHidePasswordImagesOn {
@ -453,8 +456,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
detailTextLabel.text = "HiddenFields(%d)".localize(numberOfHiddenFields) detailTextLabel.text = "HiddenFields(%d)".localize(numberOfHiddenFields)
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
return tableData[section].header tableData[section].header
} }
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
@ -464,7 +467,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
footerLabel.numberOfLines = 0 footerLabel.numberOfLines = 0
footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote) footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
footerLabel.textColor = UIColor.gray footerLabel.textColor = UIColor.gray
let dateString = self.passwordStore.getLatestUpdateInfo(filename: password!.url.path) let dateString = passwordStore.getLatestUpdateInfo(filename: password!.url.path)
footerLabel.text = "LastUpdated".localize(dateString) footerLabel.text = "LastUpdated".localize(dateString)
view.addSubview(footerLabel) view.addSubview(footerLabel)
return view return view
@ -472,15 +475,15 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
return nil return nil
} }
override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) { override func tableView(_: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender _: Any?) {
if action == #selector(copy(_:)) { if action == #selector(copy(_:)) {
SecurePasteboard.shared.copy(textToCopy: tableData[indexPath.section].item[indexPath.row].content) SecurePasteboard.shared.copy(textToCopy: tableData[indexPath.section].item[indexPath.row].content)
} }
} }
override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { override func tableView(_: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender _: Any?) -> Bool {
let section = tableData[indexPath.section] let section = tableData[indexPath.section]
switch(section.type) { switch section.type {
case .main, .addition: case .main, .addition:
return action == #selector(UIResponderStandardEditActions.copy(_:)) return action == #selector(UIResponderStandardEditActions.copy(_:))
default: default:
@ -488,8 +491,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
} }
override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool { override func tableView(_: UITableView, shouldShowMenuForRowAt _: IndexPath) -> Bool {
return true true
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

View file

@ -6,10 +6,10 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import SafariServices
import OneTimePassword import OneTimePassword
import passKit import passKit
import SafariServices
import UIKit
enum PasswordEditorCellType: Equatable { enum PasswordEditorCellType: Equatable {
case nameCell case nameCell
@ -30,13 +30,11 @@ enum PasswordEditorCellKey {
} }
protocol PasswordSettingSliderTableViewCellDelegate { protocol PasswordSettingSliderTableViewCellDelegate {
func generateAndCopyPassword() func generateAndCopyPassword()
} }
class PasswordEditorTableViewController: UITableViewController { class PasswordEditorTableViewController: UITableViewController {
var tableData = [[[PasswordEditorCellKey: Any]]]()
var tableData = [[Dictionary<PasswordEditorCellKey, Any>]]()
var password: Password? var password: Password?
private var navigationItemTitle: String? private var navigationItemTitle: String?
@ -86,7 +84,7 @@ class PasswordEditorTableViewController: UITableViewController {
passwordFlavorCell?.textLabel?.text = "PasswordGeneratorFlavor".localize() passwordFlavorCell?.textLabel?.text = "PasswordGeneratorFlavor".localize()
passwordFlavorCell?.selectionStyle = .none passwordFlavorCell?.selectionStyle = .none
let passwordFlavorSelector = UISegmentedControl(items: PasswordGeneratorFlavor.allCases.map { $0.localized }) let passwordFlavorSelector = UISegmentedControl(items: PasswordGeneratorFlavor.allCases.map(\.localized))
passwordFlavorSelector.selectedSegmentIndex = PasswordGeneratorFlavor.allCases.firstIndex(of: passwordGenerator.flavor)! passwordFlavorSelector.selectedSegmentIndex = PasswordGeneratorFlavor.allCases.firstIndex(of: passwordGenerator.flavor)!
passwordFlavorSelector.addTarget(self, action: #selector(flavorChanged), for: .valueChanged) passwordFlavorSelector.addTarget(self, action: #selector(flavorChanged), for: .valueChanged)
passwordFlavorCell?.accessoryView = passwordFlavorSelector passwordFlavorCell?.accessoryView = passwordFlavorSelector
@ -126,10 +124,10 @@ class PasswordEditorTableViewController: UITableViewController {
], ],
[ [
[.type: PasswordEditorCellType.scanQRCodeCell], [.type: PasswordEditorCellType.scanQRCodeCell],
] ],
] ]
if self.password != nil { if password != nil {
tableData[additionsSection + 1].append([.type: PasswordEditorCellType.deletePasswordCell]) tableData[additionsSection + 1].append([.type: PasswordEditorCellType.deletePasswordCell])
} }
updateTableData(withRespectTo: passwordGenerator.flavor) updateTableData(withRespectTo: passwordGenerator.flavor)
@ -206,11 +204,11 @@ class PasswordEditorTableViewController: UITableViewController {
} }
} }
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { override func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat {
return 44 44
} }
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { override func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch tableData[indexPath.section][indexPath.row][PasswordEditorCellKey.type] as! PasswordEditorCellType { switch tableData[indexPath.section][indexPath.row][PasswordEditorCellKey.type] as! PasswordEditorCellType {
case .passwordLengthCell, .passwordGroupsCell: case .passwordLengthCell, .passwordGroupsCell:
return 42 return 42
@ -221,11 +219,11 @@ class PasswordEditorTableViewController: UITableViewController {
} }
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in _: UITableView) -> Int {
return tableData.count tableData.count
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == passwordSection, hidePasswordSettings { if section == passwordSection, hidePasswordSettings {
// hide the password section, only the password should be shown // hide the password section, only the password should be shown
return 1 return 1
@ -234,12 +232,12 @@ class PasswordEditorTableViewController: UITableViewController {
} }
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionHeaderTitles[section] sectionHeaderTitles[section]
} }
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { override func tableView(_: UITableView, titleForFooterInSection section: Int) -> String? {
return sectionFooterTitles[section] sectionFooterTitles[section]
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -248,13 +246,13 @@ class PasswordEditorTableViewController: UITableViewController {
if selectedCell == deletePasswordCell { if selectedCell == deletePasswordCell {
let alert = UIAlertController(title: "DeletePassword?".localize(), message: nil, preferredStyle: UIAlertController.Style.alert) let alert = UIAlertController(title: "DeletePassword?".localize(), message: nil, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Delete".localize(), style: UIAlertAction.Style.destructive, handler: {[unowned self] (action) -> Void in alert.addAction(UIAlertAction(title: "Delete".localize(), style: UIAlertAction.Style.destructive, handler: { [unowned self] (_) -> Void in
self.performSegue(withIdentifier: "deletePasswordSegue", sender: self) self.performSegue(withIdentifier: "deletePasswordSegue", sender: self)
})) }))
alert.addAction(UIAlertAction.cancel()) alert.addAction(UIAlertAction.cancel())
self.present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} else if selectedCell == scanQRCodeCell { } else if selectedCell == scanQRCodeCell {
self.performSegue(withIdentifier: "showQRScannerSegue", sender: self) performSegue(withIdentifier: "showQRScannerSegue", sender: self)
} }
} }
@ -276,11 +274,12 @@ class PasswordEditorTableViewController: UITableViewController {
} }
} }
private func isPasswordDelimiterCellData(data: Dictionary<PasswordEditorCellKey, Any>) -> Bool { private func isPasswordDelimiterCellData(data: [PasswordEditorCellKey: Any]) -> Bool {
return (data[.type] as? PasswordEditorCellType) == .some(.passwordGroupsCell) (data[.type] as? PasswordEditorCellType) == .some(.passwordGroupsCell)
} }
@objc func flavorChanged(_ sender: UISegmentedControl) { @objc
func flavorChanged(_ sender: UISegmentedControl) {
let flavor = PasswordGeneratorFlavor.allCases[sender.selectedSegmentIndex] let flavor = PasswordGeneratorFlavor.allCases[sender.selectedSegmentIndex]
guard passwordGenerator.flavor != flavor else { guard passwordGenerator.flavor != flavor else {
return return
@ -327,7 +326,7 @@ class PasswordEditorTableViewController: UITableViewController {
additionsCell?.setContent(content: additionsString) additionsCell?.setContent(content: additionsString)
} }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { override func prepare(for segue: UIStoryboardSegue, sender _: Any?) {
if segue.identifier == "showQRScannerSegue" { if segue.identifier == "showQRScannerSegue" {
if let navController = segue.destination as? UINavigationController { if let navController = segue.destination as? UINavigationController {
if let viewController = navController.topViewController as? QRScannerController { if let viewController = navController.topViewController as? QRScannerController {
@ -382,8 +381,8 @@ class PasswordEditorTableViewController: UITableViewController {
} }
// MARK: - FillPasswordTableViewCellDelegate // MARK: - FillPasswordTableViewCellDelegate
extension PasswordEditorTableViewController: FillPasswordTableViewCellDelegate {
extension PasswordEditorTableViewController: FillPasswordTableViewCellDelegate {
// generate password, copy to pasteboard, and set the cell // generate password, copy to pasteboard, and set the cell
// check whether the current password looks like an OTP field // check whether the current password looks like an OTP field
func generateAndCopyPassword() { func generateAndCopyPassword() {
@ -393,9 +392,9 @@ extension PasswordEditorTableViewController: FillPasswordTableViewCellDelegate {
self.generateAndCopyPasswordNoOtpCheck() self.generateAndCopyPasswordNoOtpCheck()
})) }))
alert.addAction(UIAlertAction.cancel()) alert.addAction(UIAlertAction.cancel())
self.present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} else { } else {
self.generateAndCopyPasswordNoOtpCheck() generateAndCopyPasswordNoOtpCheck()
} }
} }
@ -407,11 +406,12 @@ extension PasswordEditorTableViewController: FillPasswordTableViewCellDelegate {
} }
// MARK: - PasswordSettingSliderTableViewCellDelegate // MARK: - PasswordSettingSliderTableViewCellDelegate
extension PasswordEditorTableViewController: PasswordSettingSliderTableViewCellDelegate {} extension PasswordEditorTableViewController: PasswordSettingSliderTableViewCellDelegate {}
// MARK: - QRScannerControllerDelegate // MARK: - QRScannerControllerDelegate
extension PasswordEditorTableViewController: QRScannerControllerDelegate {
extension PasswordEditorTableViewController: QRScannerControllerDelegate {
func checkScannedOutput(line: String) -> (accept: Bool, message: String) { func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
if let url = URL(string: line), let _ = Token(url: url) { if let url = URL(string: line), let _ = Token(url: url) {
return (accept: true, message: "ValidTokenUrl".localize()) return (accept: true, message: "ValidTokenUrl".localize())
@ -426,31 +426,31 @@ extension PasswordEditorTableViewController: QRScannerControllerDelegate {
} }
// MARK: - SFSafariViewControllerDelegate // MARK: - SFSafariViewControllerDelegate
extension PasswordEditorTableViewController: SFSafariViewControllerDelegate {
func safariViewControllerDidFinish(_ controller: SFSafariViewController) { extension PasswordEditorTableViewController: SFSafariViewControllerDelegate {
let copiedLinesSplit = UIPasteboard.general.string?.components(separatedBy: CharacterSet.whitespacesAndNewlines).filter({ !$0.isEmpty }) func safariViewControllerDidFinish(_: SFSafariViewController) {
let copiedLinesSplit = UIPasteboard.general.string?.components(separatedBy: CharacterSet.whitespacesAndNewlines).filter { !$0.isEmpty }
if copiedLinesSplit?.count ?? 0 > 0 { if copiedLinesSplit?.count ?? 0 > 0 {
let generatedPassword = copiedLinesSplit![0] let generatedPassword = copiedLinesSplit![0]
let alert = UIAlertController(title: "WannaUseIt?".localize(), message: "", preferredStyle: UIAlertController.Style.alert) let alert = UIAlertController(title: "WannaUseIt?".localize(), message: "", preferredStyle: UIAlertController.Style.alert)
let message = NSMutableAttributedString(string: "\("SeemsLikeYouHaveCopiedSomething.".localize()) \("FirstStringIs:".localize())\n") let message = NSMutableAttributedString(string: "\("SeemsLikeYouHaveCopiedSomething.".localize()) \("FirstStringIs:".localize())\n")
message.append(Utils.attributedPassword(plainPassword: generatedPassword)) message.append(Utils.attributedPassword(plainPassword: generatedPassword))
alert.setValue(message, forKey: "attributedMessage") alert.setValue(message, forKey: "attributedMessage")
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertAction.Style.default, handler: {[unowned self] (action) -> Void in alert.addAction(UIAlertAction(title: "Yes", style: UIAlertAction.Style.default, handler: { [unowned self] (_) -> Void in
// update tableData so to make sure reloadData() works correctly // update tableData so to make sure reloadData() works correctly
self.tableData[self.passwordSection][0][PasswordEditorCellKey.content] = generatedPassword self.tableData[self.passwordSection][0][PasswordEditorCellKey.content] = generatedPassword
// update cell manually, no need to call reloadData() // update cell manually, no need to call reloadData()
self.fillPasswordCell?.setContent(content: generatedPassword) self.fillPasswordCell?.setContent(content: generatedPassword)
})) }))
alert.addAction(UIAlertAction.cancel()) alert.addAction(UIAlertAction.cancel())
self.present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} }
} }
} }
// MARK: - UITextFieldDelegate // MARK: - UITextFieldDelegate
extension PasswordEditorTableViewController: UITextFieldDelegate {
extension PasswordEditorTableViewController: UITextFieldDelegate {
// update tableData so to make sure reloadData() works correctly // update tableData so to make sure reloadData() works correctly
func textFieldDidEndEditing(_ textField: UITextField) { func textFieldDidEndEditing(_ textField: UITextField) {
if textField == nameCell?.contentTextField { if textField == nameCell?.contentTextField {
@ -471,8 +471,8 @@ extension PasswordEditorTableViewController: UITextFieldDelegate {
} }
// MARK: - UITextViewDelegate // MARK: - UITextViewDelegate
extension PasswordEditorTableViewController: UITextViewDelegate {
extension PasswordEditorTableViewController: UITextViewDelegate {
// update tableData so to make sure reloadData() works correctly // update tableData so to make sure reloadData() works correctly
func textViewDidEndEditing(_ textView: UITextView) { func textViewDidEndEditing(_ textView: UITextView) {
if textView == additionsCell?.contentTextView { if textView == additionsCell?.contentTextView {

View file

@ -6,16 +6,16 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
import UIKit
fileprivate let hideSectionHeaderThreshold = 6 // hide section header if passwords count is less than the threshold private let hideSectionHeaderThreshold = 6 // hide section header if passwords count is less than the threshold
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate, UISearchBarDelegate { class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate, UISearchBarDelegate {
private var passwordsTableEntries: [PasswordTableEntry] = [] private var passwordsTableEntries: [PasswordTableEntry] = []
private var passwordsTableAllEntries: [PasswordTableEntry] = [] private var passwordsTableAllEntries: [PasswordTableEntry] = []
private var parentPasswordEntity: PasswordEntity? = nil private var parentPasswordEntity: PasswordEntity?
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
private let keychain = AppKeychain.shared private let keychain = AppKeychain.shared
@ -30,7 +30,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
private var gitCredential: GitCredential { private var gitCredential: GitCredential {
get {
switch Defaults.gitAuthenticationMethod { switch Defaults.gitAuthenticationMethod {
case .password: case .password:
return GitCredential(credential: .http(userName: Defaults.gitUsername)) return GitCredential(credential: .http(userName: Defaults.gitUsername))
@ -39,7 +38,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey)) return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey))
} }
} }
}
private lazy var searchController: UISearchController = { private lazy var searchController: UISearchController = {
let uiSearchController = UISearchController(searchResultsController: nil) let uiSearchController = UISearchController(searchResultsController: nil)
@ -49,11 +47,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
uiSearchController.searchBar.sizeToFit() uiSearchController.searchBar.sizeToFit()
return uiSearchController return uiSearchController
}() }()
private lazy var syncControl: UIRefreshControl = { private lazy var syncControl: UIRefreshControl = {
let syncControl = UIRefreshControl() let syncControl = UIRefreshControl()
syncControl.addTarget(self, action: #selector(handleRefresh(_:)), for: UIControl.Event.valueChanged) syncControl.addTarget(self, action: #selector(handleRefresh(_:)), for: UIControl.Event.valueChanged)
return syncControl return syncControl
}() }()
private lazy var searchBarView: UIView? = { private lazy var searchBarView: UIView? = {
guard #available(iOS 11, *) else { guard #available(iOS 11, *) else {
let uiView = UIView(frame: CGRect(x: 0, y: 64, width: self.view.bounds.width, height: 44)) let uiView = UIView(frame: CGRect(x: 0, y: 64, width: self.view.bounds.width, height: 44))
@ -62,6 +62,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
return nil return nil
}() }()
private lazy var backUIBarButtonItem: UIBarButtonItem = { private lazy var backUIBarButtonItem: UIBarButtonItem = {
let backUIButton = UIButton(type: .system) let backUIButton = UIButton(type: .system)
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
@ -116,16 +117,16 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
return transition return transition
}() }()
@IBOutlet weak var tableView: UITableView! @IBOutlet var tableView: UITableView!
private func initPasswordsTableEntries(parent: PasswordEntity?) { private func initPasswordsTableEntries(parent: PasswordEntity?) {
let passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false) let passwordAllEntities = passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableAllEntries = passwordAllEntities.compactMap { passwordsTableAllEntries = passwordAllEntities.compactMap {
PasswordTableEntry($0) PasswordTableEntry($0)
} }
let passwordEntities = Defaults.isShowFolderOn ? let passwordEntities = Defaults.isShowFolderOn ?
self.passwordStore.fetchPasswordEntityCoreData(parent: parent) : passwordStore.fetchPasswordEntityCoreData(parent: parent) :
passwordAllEntities passwordAllEntities
passwordsTableEntries = passwordEntities.compactMap { passwordsTableEntries = passwordEntities.compactMap {
PasswordTableEntry($0) PasswordTableEntry($0)
@ -134,10 +135,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
parentPasswordEntity = parent parentPasswordEntity = parent
} }
@IBAction func cancelAddPassword(segue: UIStoryboardSegue) { @IBAction
func cancelAddPassword(segue _: UIStoryboardSegue) {}
} @IBAction
@IBAction func saveAddPassword(segue: UIStoryboardSegue) { func saveAddPassword(segue: UIStoryboardSegue) {
if let controller = segue.source as? AddPasswordTableViewController { if let controller = segue.source as? AddPasswordTableViewController {
addPassword(password: controller.password!) addPassword(password: controller.password!)
} }
@ -149,13 +151,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
SVProgressHUD.show(withStatus: "Saving".localize()) SVProgressHUD.show(withStatus: "Saving".localize())
DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.global(qos: .userInitiated).async {
do { do {
let _ = try self.passwordStore.add(password: password, keyID: keyID) _ = try self.passwordStore.add(password: password, keyID: keyID)
DispatchQueue.main.async { DispatchQueue.main.async {
// will trigger reloadTableView() by a notification // will trigger reloadTableView() by a notification
SVProgressHUD.showSuccess(withStatus: "Done".localize()) SVProgressHUD.showSuccess(withStatus: "Done".localize())
SVProgressHUD.dismiss(withDelay: 1) SVProgressHUD.dismiss(withDelay: 1)
} }
} catch AppError.PgpPublicKeyNotFound(let key) { } catch let AppError.PgpPublicKeyNotFound(key) {
DispatchQueue.main.async { DispatchQueue.main.async {
// alert: cancel or select keys // alert: cancel or select keys
SVProgressHUD.dismiss() SVProgressHUD.dismiss()
@ -190,13 +192,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
do { do {
try self.passwordStore.pullRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, progressBlock: {(git_transfer_progress, stop) in try self.passwordStore.pullRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, progressBlock: { git_transfer_progress, _ in
DispatchQueue.main.async { DispatchQueue.main.async {
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects) / Float(git_transfer_progress.pointee.total_objects), status: "PullingFromRemoteRepository".localize()) SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects) / Float(git_transfer_progress.pointee.total_objects), status: "PullingFromRemoteRepository".localize())
} }
}) })
if self.passwordStore.numberOfLocalCommits > 0 { if self.passwordStore.numberOfLocalCommits > 0 {
try self.passwordStore.pushRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, transferProgressBlock: {(current, total, bytes, stop) in try self.passwordStore.pushRepository(credential: self.gitCredential, requestCredentialPassword: self.requestCredentialPassword, transferProgressBlock: { current, total, _, _ in
DispatchQueue.main.async { DispatchQueue.main.async {
SVProgressHUD.showProgress(Float(current) / Float(total), status: "PushingToRemoteRepository".localize()) SVProgressHUD.showProgress(Float(current) / Float(total), status: "PushingToRemoteRepository".localize())
} }
@ -232,7 +234,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
super.viewDidAppear(animated) super.viewDidAppear(animated)
if Defaults.isShowFolderOn { if Defaults.isShowFolderOn {
searchController.searchBar.scopeButtonTitles = SearchBarScope.allCases.map { $0.localizedName } searchController.searchBar.scopeButtonTitles = SearchBarScope.allCases.map(\.localizedName)
} else { } else {
searchController.searchBar.scopeButtonTitles = nil searchController.searchBar.scopeButtonTitles = nil
} }
@ -251,7 +253,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
navigationItem.hidesSearchBarWhenScrolling = false navigationItem.hidesSearchBarWhenScrolling = false
} else { } else {
// Fallback on earlier versions // Fallback on earlier versions
tableView.contentInset = UIEdgeInsets.init(top: 44, left: 0, bottom: 0, right: 0) tableView.contentInset = UIEdgeInsets(top: 44, left: 0, bottom: 0, right: 0)
view.addSubview(searchBarView!) view.addSubview(searchBarView!)
} }
navigationItem.title = "PasswordStore".localize() navigationItem.title = "PasswordStore".localize()
@ -272,16 +274,17 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: UIApplication.willEnterForegroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: UIApplication.willEnterForegroundNotification, object: nil)
// listen to the swipe back guesture // listen to the swipe back guesture
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture)) let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeGesture))
swipeRight.direction = UISwipeGestureRecognizer.Direction.right swipeRight.direction = UISwipeGestureRecognizer.Direction.right
self.view.addGestureRecognizer(swipeRight) view.addGestureRecognizer(swipeRight)
} }
@objc func didTapNavigationBar(_ sender: UITapGestureRecognizer) { @objc
let location = sender.location(in: self.navigationController?.navigationBar) func didTapNavigationBar(_ sender: UITapGestureRecognizer) {
let hitView = self.navigationController?.navigationBar.hitTest(location, with: nil) let location = sender.location(in: navigationController?.navigationBar)
let hitView = navigationController?.navigationBar.hitTest(location, with: nil)
guard !(hitView is UIControl) else { return } guard !(hitView is UIControl) else { return }
guard (passwordStore.numberOfLocalCommits != 0) else { return } guard passwordStore.numberOfLocalCommits != 0 else { return }
let ac = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let ac = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let allAction = UIAlertAction(title: "All Passwords", style: .default) { _ in let allAction = UIAlertAction(title: "All Passwords", style: .default) { _ in
@ -289,7 +292,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
let unsyncedAction = UIAlertAction(title: "Unsynced Passwords", style: .default) { _ in let unsyncedAction = UIAlertAction(title: "Unsynced Passwords", style: .default) { _ in
let filteredPasswordsTableEntries = self.passwordsTableEntries.filter { entry in let filteredPasswordsTableEntries = self.passwordsTableEntries.filter { entry in
return !entry.synced !entry.synced
} }
self.reloadTableView(data: filteredPasswordsTableEntries, label: .unsynced) self.reloadTableView(data: filteredPasswordsTableEntries, label: .unsynced)
} }
@ -299,7 +302,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
ac.addAction(unsyncedAction) ac.addAction(unsyncedAction)
ac.addAction(cancelAction) ac.addAction(cancelAction)
self.present(ac, animated: true, completion: nil) present(ac, animated: true, completion: nil)
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
@ -310,7 +313,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
// Add gesture recognizer to the navigation bar when the view is about to appear // Add gesture recognizer to the navigation bar when the view is about to appear
self.navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer) navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer)
// This allows controlls in the navigation bar to continue receiving touches // This allows controlls in the navigation bar to continue receiving touches
tapNavigationBarGestureRecognizer.cancelsTouchesInView = false tapNavigationBarGestureRecognizer.cancelsTouchesInView = false
@ -318,9 +321,9 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
tableView.refreshControl = passwordStore.repositoryExists() ? syncControl : nil tableView.refreshControl = passwordStore.repositoryExists() ? syncControl : nil
} }
override func viewWillDisappear(_ animated: Bool) { override func viewWillDisappear(_: Bool) {
// Remove gesture recognizer from navigation bar when view is about to disappear // Remove gesture recognizer from navigation bar when view is about to disappear
self.navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer) navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer)
} }
override func viewWillLayoutSubviews() { override func viewWillLayoutSubviews() {
@ -332,12 +335,12 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
} }
func numberOfSections(in tableView: UITableView) -> Int { func numberOfSections(in _: UITableView) -> Int {
return sections.count sections.count
} }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].entries.count sections[section].entries.count
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -372,7 +375,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry { private func getPasswordEntry(by indexPath: IndexPath) -> PasswordTableEntry {
return sections[indexPath.section].entries[indexPath.row] sections[indexPath.section].entries[indexPath.row]
} }
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -390,16 +393,18 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
} }
@objc func respondToSwipeGesture(gesture: UIGestureRecognizer) { @objc
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer { if let swipeGesture = gesture as? UISwipeGestureRecognizer {
// swipe right -> swipe back // swipe right -> swipe back
if swipeGesture.direction == .right && parentPasswordEntity != nil { if swipeGesture.direction == .right, parentPasswordEntity != nil {
self.backAction(nil) backAction(nil)
} }
} }
} }
@objc func backAction(_ sender: Any?) { @objc
func backAction(_: Any?) {
guard Defaults.isShowFolderOn else { return } guard Defaults.isShowFolderOn else { return }
var anim: CATransition? = transitionFromLeft var anim: CATransition? = transitionFromLeft
if parentPasswordEntity == nil { if parentPasswordEntity == nil {
@ -408,13 +413,15 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
reloadTableView(parent: parentPasswordEntity?.parent, anim: anim) reloadTableView(parent: parentPasswordEntity?.parent, anim: anim)
} }
@objc func addPasswordAction(_ sender: Any?) { @objc
func addPasswordAction(_: Any?) {
if shouldPerformSegue(withIdentifier: "addPasswordSegue", sender: self) { if shouldPerformSegue(withIdentifier: "addPasswordSegue", sender: self) {
performSegue(withIdentifier: "addPasswordSegue", sender: self) performSegue(withIdentifier: "addPasswordSegue", sender: self)
} }
} }
@objc func longPressAction(_ gesture: UILongPressGestureRecognizer) { @objc
func longPressAction(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizer.State.began { if gesture.state == UIGestureRecognizer.State.began {
let touchPoint = gesture.location(in: tableView) let touchPoint = gesture.location(in: tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) { if let indexPath = tableView.indexPathForRow(at: touchPoint) {
@ -424,31 +431,31 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
private func hideSectionHeader() -> Bool { private func hideSectionHeader() -> Bool {
if passwordsTableEntries.count < hideSectionHeaderThreshold || self.searchController.isActive { if passwordsTableEntries.count < hideSectionHeaderThreshold || searchController.isActive {
return true return true
} }
return false return false
} }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
if hideSectionHeader() { if hideSectionHeader() {
return nil return nil
} }
return sections[section].title return sections[section].title
} }
func sectionIndexTitles(for tableView: UITableView) -> [String]? { func sectionIndexTitles(for _: UITableView) -> [String]? {
if hideSectionHeader() { if hideSectionHeader() {
return nil return nil
} }
return sections.map { $0.title } return sections.map(\.title)
} }
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { func tableView(_: UITableView, sectionForSectionIndexTitle _: String, at index: Int) -> Int {
return index index
} }
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { func tableView(_: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
decryptThenCopyPassword(from: indexPath) decryptThenCopyPassword(from: indexPath)
} }
@ -460,7 +467,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity
UIImpactFeedbackGenerator(style: .medium).impactOccurred() UIImpactFeedbackGenerator(style: .medium).impactOccurred()
SVProgressHUD.dismiss() SVProgressHUD.dismiss()
self.decryptPassword(passwordEntity: passwordEntity) decryptPassword(passwordEntity: passwordEntity)
} }
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) { private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
@ -476,7 +483,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
SVProgressHUD.showSuccess(withStatus: "PasswordCopiedToPasteboard.".localize()) SVProgressHUD.showSuccess(withStatus: "PasswordCopiedToPasteboard.".localize())
SVProgressHUD.dismiss(withDelay: 0.6) SVProgressHUD.dismiss(withDelay: 0.6)
} }
} catch AppError.PgpPrivateKeyNotFound(let key) { } catch let AppError.PgpPrivateKeyNotFound(key) {
DispatchQueue.main.async { DispatchQueue.main.async {
// alert: cancel or try again // alert: cancel or try again
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert) let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
@ -496,7 +503,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
} }
private func generateSections(item: [PasswordTableEntry]) { private func generateSections(item: [PasswordTableEntry]) {
let collation = UILocalizedIndexedCollation.current() let collation = UILocalizedIndexedCollation.current()
let sectionTitles = collation.sectionIndexTitles let sectionTitles = collation.sectionIndexTitles
@ -521,14 +527,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
// only keep non-empty sections // only keep non-empty sections
sections = newSections.filter {$0.entries.count > 0} sections = newSections.filter { !$0.entries.isEmpty }
} }
@objc func actOnSearchNotification() { @objc
func actOnSearchNotification() {
searchController.searchBar.becomeFirstResponder() searchController.searchBar.becomeFirstResponder()
} }
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "showPasswordDetail" { if identifier == "showPasswordDetail" {
guard PGPAgent.shared.isPrepared else { guard PGPAgent.shared.isPrepared else {
@ -540,7 +546,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
return false return false
} }
} else if identifier == "addPasswordSegue" { } else if identifier == "addPasswordSegue" {
guard PGPAgent.shared.isPrepared && self.passwordStore.storeRepository != nil else { guard PGPAgent.shared.isPrepared && passwordStore.storeRepository != nil else {
Utils.alert(title: "CannotAddPassword".localize(), message: "MakeSurePgpAndGitProperlySet.".localize(), controller: self, completion: nil) Utils.alert(title: "CannotAddPassword".localize(), message: "MakeSurePgpAndGitProperlySet.".localize(), controller: self, completion: nil)
return false return false
} }
@ -551,7 +557,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPasswordDetail" { if segue.identifier == "showPasswordDetail" {
if let viewController = segue.destination as? PasswordDetailTableViewController { if let viewController = segue.destination as? PasswordDetailTableViewController {
let selectedIndexPath = self.tableView.indexPath(for: sender as! UITableViewCell)! let selectedIndexPath = tableView.indexPath(for: sender as! UITableViewCell)!
let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity
viewController.passwordEntity = passwordEntity viewController.passwordEntity = passwordEntity
} }
@ -568,7 +574,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
func filterContentForSearchText(searchText: String, scope: SearchBarScope = .all) { func filterContentForSearchText(searchText: String, scope: SearchBarScope = .all) {
var entries: [PasswordTableEntry] = scope == .all ? passwordsTableAllEntries : passwordsTableEntries var entries: [PasswordTableEntry] = scope == .all ? passwordsTableAllEntries : passwordsTableEntries
if searchController.isActive && searchController.searchBar.text != "" { if searchController.isActive, searchController.searchBar.text != "" {
entries = entries.filter { $0.match(searchText) } entries = entries.filter { $0.match(searchText) }
} }
reloadTableView(data: entries) reloadTableView(data: entries)
@ -587,7 +593,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
if #available(iOS 11, *) { if #available(iOS 11, *) {
navigationController?.navigationBar.prefersLargeTitles = false navigationController?.navigationBar.prefersLargeTitles = false
} }
self.navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer) navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer)
} else { } else {
navigationItem.leftBarButtonItem = nil navigationItem.leftBarButtonItem = nil
switch label { switch label {
@ -599,17 +605,17 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
if #available(iOS 11, *) { if #available(iOS 11, *) {
navigationController?.navigationBar.prefersLargeTitles = true navigationController?.navigationBar.prefersLargeTitles = true
} }
self.navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer) navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer)
} }
navigationItem.rightBarButtonItem = addPasswordUIBarButtonItem navigationItem.rightBarButtonItem = addPasswordUIBarButtonItem
// set the password table // set the password table
generateSections(item: data) generateSections(item: data)
if anim != nil { if anim != nil {
self.tableView.layer.add(anim!, forKey: "UITableViewReloadDataAnimationKey") tableView.layer.add(anim!, forKey: "UITableViewReloadDataAnimationKey")
} }
tableView.reloadData() tableView.reloadData()
self.tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey") tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey")
// set the sync control title // set the sync control title
let atribbutedTitle = "LastSynced".localize() + ": \(lastSyncedTimeString())" let atribbutedTitle = "LastSynced".localize() + ": \(lastSyncedTimeString())"
@ -617,7 +623,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
private func lastSyncedTimeString() -> String { private func lastSyncedTimeString() -> String {
guard let date = self.passwordStore.lastSyncedTime else { guard let date = passwordStore.lastSyncedTime else {
return "SyncAgain?".localize() return "SyncAgain?".localize()
} }
let formatter = DateFormatter() let formatter = DateFormatter()
@ -631,7 +637,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
reloadTableView(data: passwordsTableEntries, label: label, anim: anim) reloadTableView(data: passwordsTableEntries, label: label, anim: anim)
} }
@objc func actOnReloadTableViewRelatedNotification() { @objc
func actOnReloadTableViewRelatedNotification() {
DispatchQueue.main.async { [weak weakSelf = self] in DispatchQueue.main.async { [weak weakSelf = self] in
guard let strongSelf = weakSelf else { return } guard let strongSelf = weakSelf else { return }
// Reset selectedScopeButtonIndex to make sure the correct reloadTableView // Reset selectedScopeButtonIndex to make sure the correct reloadTableView
@ -641,36 +648,37 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
} }
@objc func handleRefresh(_ syncControl: UIRefreshControl) { @objc
func handleRefresh(_: UIRefreshControl) {
syncPasswords() syncPasswords()
} }
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { func tabBarController(_: UITabBarController, didSelect viewController: UIViewController) {
if viewController == self.navigationController { if viewController == navigationController {
let currentTime = Date().timeIntervalSince1970 let currentTime = Date().timeIntervalSince1970
let duration = currentTime - self.tapTabBarTime let duration = currentTime - tapTabBarTime
self.tapTabBarTime = currentTime tapTabBarTime = currentTime
if duration < 0.35 { if duration < 0.35 {
let topIndexPath = IndexPath(row: 0, section: 0) let topIndexPath = IndexPath(row: 0, section: 0)
if tableView.numberOfSections > 0 { if tableView.numberOfSections > 0 {
tableView.scrollToRow(at: topIndexPath, at: .bottom, animated: true) tableView.scrollToRow(at: topIndexPath, at: .bottom, animated: true)
} }
self.tapTabBarTime = 0 tapTabBarTime = 0
return return
} }
backAction(self) backAction(self)
} }
} }
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) { func searchBar(_: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
// update the default search scope // update the default search scope
Defaults.searchDefault = SearchBarScope(rawValue: selectedScope) Defaults.searchDefault = SearchBarScope(rawValue: selectedScope)
updateSearchResults(for: searchController) updateSearchResults(for: searchController)
} }
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { func searchBarShouldBeginEditing(_: UISearchBar) -> Bool {
// set the default search scope to "all" // set the default search scope to "all"
if Defaults.isShowFolderOn && Defaults.searchDefault == .all { if Defaults.isShowFolderOn, Defaults.searchDefault == .all {
searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.all.rawValue searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.all.rawValue
} else { } else {
searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.current.rawValue searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.current.rawValue
@ -678,7 +686,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
return true return true
} }
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool { func searchBarShouldEndEditing(_: UISearchBar) -> Bool {
// set the default search scope to "current" // set the default search scope to "current"
searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.current.rawValue searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.current.rawValue
updateSearchResults(for: searchController) updateSearchResults(for: searchController)
@ -686,13 +694,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
private func requestCredentialPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? { private func requestCredentialPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
return requestGitCredentialPassword(credential: credential, lastPassword: lastPassword, controller: self) requestGitCredentialPassword(credential: credential, lastPassword: lastPassword, controller: self)
} }
} }
extension PasswordsViewController: UISearchResultsUpdating { extension PasswordsViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) { func updateSearchResults(for searchController: UISearchController) {
let scope = SearchBarScope(rawValue: searchController.searchBar.selectedScopeButtonIndex) ?? .all let scope = SearchBarScope(rawValue: searchController.searchBar.selectedScopeButtonIndex) ?? .all
filterContentForSearchText(searchText: searchController.searchBar.text!, scope: scope) filterContentForSearchText(searchText: searchController.searchBar.text!, scope: scope)
@ -700,8 +706,7 @@ extension PasswordsViewController: UISearchResultsUpdating {
} }
extension PasswordsViewController: CAAnimationDelegate { extension PasswordsViewController: CAAnimationDelegate {
func animationDidStart(_: CAAnimation) {
func animationDidStart(_ anim: CAAnimation) {
view.window?.backgroundColor = Colors.systemBackground view.window?.backgroundColor = Colors.systemBackground
view.layer.backgroundColor = Colors.systemBackground.cgColor view.layer.backgroundColor = Colors.systemBackground.cgColor
} }

View file

@ -6,11 +6,11 @@
// Copyright © 2017 Yishi Lin. All rights reserved. // Copyright © 2017 Yishi Lin. All rights reserved.
// //
import UIKit
import AVFoundation import AVFoundation
import OneTimePassword import OneTimePassword
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
import UIKit
protocol QRScannerControllerDelegate { protocol QRScannerControllerDelegate {
func checkScannedOutput(line: String) -> (accept: Bool, message: String) func checkScannedOutput(line: String) -> (accept: Bool, message: String)
@ -18,8 +18,7 @@ protocol QRScannerControllerDelegate {
} }
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate { class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
@IBOutlet var scannerOutput: UILabel!
@IBOutlet weak var scannerOutput: UILabel!
var captureSession: AVCaptureSession? var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer? var videoPreviewLayer: AVCaptureVideoPreviewLayer?
@ -93,15 +92,12 @@ class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDeleg
// Dispose of any resources that can be recreated. // Dispose of any resources that can be recreated.
} }
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods // MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
if let metadataObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject, if let metadataObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
supportedCodeTypes.contains(metadataObj.type), supportedCodeTypes.contains(metadataObj.type),
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj) { let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj) {
// draw a bounds on the found QR code // draw a bounds on the found QR code
qrCodeFrameView?.frame = barCodeObject.bounds qrCodeFrameView?.frame = barCodeObject.bounds

View file

@ -6,12 +6,11 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class RawPasswordViewController: UIViewController { class RawPasswordViewController: UIViewController {
@IBOutlet var rawPasswordTextView: UITextView!
@IBOutlet weak var rawPasswordTextView: UITextView!
var password: Password? var password: Password?
override func viewDidLoad() { override func viewDidLoad() {

View file

@ -6,13 +6,12 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController, UITextViewDelegate, QRScannerControllerDelegate { class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
@IBOutlet var armorPrivateKeyTextView: UITextView!
@IBOutlet weak var armorPrivateKeyTextView: UITextView! @IBOutlet var scanPrivateKeyCell: UITableViewCell!
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
var gitSSHPrivateKeyPassphrase: String? var gitSSHPrivateKeyPassphrase: String?
var armorPrivateKey: String? var armorPrivateKey: String?
@ -22,32 +21,33 @@ class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
var message = "" var message = ""
func reset() { func reset() {
self.segments.removeAll() segments.removeAll()
message = "LookingForStartingFrame.".localize() message = "LookingForStartingFrame.".localize()
} }
func addSegment(segment: String) -> (accept: Bool, message: String) { func addSegment(segment: String) -> (accept: Bool, message: String) {
// Skip duplicated segments. // Skip duplicated segments.
guard segment != self.segments.last else { guard segment != segments.last else {
return (accept: false, message: self.message) return (accept: false, message: message)
} }
// Check whether we have found the first block. // Check whether we have found the first block.
guard !self.segments.isEmpty || segment.contains("-----BEGIN") else { guard !segments.isEmpty || segment.contains("-----BEGIN") else {
return (accept: false, message: self.message) return (accept: false, message: message)
} }
// Update the list of scanned segment and return. // Update the list of scanned segment and return.
self.segments.append(segment) segments.append(segment)
if segment.range(of: "-----END.*KEY-----", options: .regularExpression, range: nil, locale: nil) != nil { if segment.range(of: "-----END.*KEY-----", options: .regularExpression, range: nil, locale: nil) != nil {
self.message = "Done".localize() message = "Done".localize()
return (accept: true, message: self.message) return (accept: true, message: message)
} else { } else {
self.message = "ScannedQrCodes(%d)".localize(self.segments.count) message = "ScannedQrCodes(%d)".localize(segments.count)
return (accept: false, message: self.message) return (accept: false, message: message)
} }
} }
} }
var scanned = ScannedSSHKey() var scanned = ScannedSSHKey()
override func viewDidLoad() { override func viewDidLoad() {
@ -59,12 +59,13 @@ class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
scanPrivateKeyCell?.accessoryType = .disclosureIndicator scanPrivateKeyCell?.accessoryType = .disclosureIndicator
} }
@IBAction func doneButtonTapped(_ sender: Any) { @IBAction
func doneButtonTapped(_: Any) {
armorPrivateKey = armorPrivateKeyTextView.text armorPrivateKey = armorPrivateKeyTextView.text
performSegue(withIdentifier: "importSSHKeySegue", sender: self) performSegue(withIdentifier: "importSSHKeySegue", sender: self)
} }
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { func textView(_: UITextView, shouldChangeTextIn _: NSRange, replacementText text: String) -> Bool {
if text == UIPasteboard.general.string { if text == UIPasteboard.general.string {
// user pastes something, do the copy here again and clear the pasteboard in 45s // user pastes something, do the copy here again and clear the pasteboard in 45s
SecurePasteboard.shared.copy(textToCopy: text) SecurePasteboard.shared.copy(textToCopy: text)
@ -76,23 +77,24 @@ class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
let selectedCell = tableView.cellForRow(at: indexPath) let selectedCell = tableView.cellForRow(at: indexPath)
if selectedCell == scanPrivateKeyCell { if selectedCell == scanPrivateKeyCell {
scanned.reset() scanned.reset()
self.performSegue(withIdentifier: "showSSHScannerSegue", sender: self) performSegue(withIdentifier: "showSSHScannerSegue", sender: self)
} }
tableView.deselectRow(at: indexPath, animated: true) tableView.deselectRow(at: indexPath, animated: true)
} }
// MARK: - QRScannerControllerDelegate Methods // MARK: - QRScannerControllerDelegate Methods
func checkScannedOutput(line: String) -> (accept: Bool, message: String) { func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
return scanned.addSegment(segment: line) return scanned.addSegment(segment: line)
} }
// MARK: - QRScannerControllerDelegate Methods // MARK: - QRScannerControllerDelegate Methods
func handleScannedOutput(line: String) {
func handleScannedOutput(line _: String) {
armorPrivateKeyTextView.text = scanned.segments.joined(separator: "") armorPrivateKeyTextView.text = scanned.segments.joined(separator: "")
} }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { override func prepare(for segue: UIStoryboardSegue, sender _: Any?) {
if segue.identifier == "showSSHScannerSegue" { if segue.identifier == "showSSHScannerSegue" {
if let navController = segue.destination as? UINavigationController { if let navController = segue.destination as? UINavigationController {
if let viewController = navController.topViewController as? QRScannerController { if let viewController = navController.topViewController as? QRScannerController {
@ -104,13 +106,11 @@ class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
} }
} }
@IBAction private func cancelSSHScanner(segue: UIStoryboardSegue) { @IBAction
private func cancelSSHScanner(segue _: UIStoryboardSegue) {}
}
} }
extension SSHKeyArmorImportTableViewController: KeyImporter { extension SSHKeyArmorImportTableViewController: KeyImporter {
static let keySource = KeySource.armor static let keySource = KeySource.armor
static let label = "AsciiArmorEncryptedKey".localize() static let label = "AsciiArmorEncryptedKey".localize()

View file

@ -10,12 +10,12 @@ import passKit
import SVProgressHUD import SVProgressHUD
class SSHKeyFileImportTableViewController: AutoCellHeightUITableViewController { class SSHKeyFileImportTableViewController: AutoCellHeightUITableViewController {
@IBOutlet var sshPrivateKeyFile: UITableViewCell!
@IBOutlet weak var sshPrivateKeyFile: UITableViewCell! private var privateKey: String?
private var privateKey: String? = nil @IBAction
func doneButtonTapped(_: Any) {
@IBAction func doneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "importSSHKeySegue", sender: self) performSegue(withIdentifier: "importSSHKeySegue", sender: self)
} }
@ -35,7 +35,6 @@ class SSHKeyFileImportTableViewController: AutoCellHeightUITableViewController {
} }
extension SSHKeyFileImportTableViewController: UIDocumentPickerDelegate { extension SSHKeyFileImportTableViewController: UIDocumentPickerDelegate {
func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt url: [URL]) { func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt url: [URL]) {
guard let url = url.first else { guard let url = url.first else {
return return
@ -61,7 +60,6 @@ extension SSHKeyFileImportTableViewController: UIDocumentPickerDelegate {
} }
extension SSHKeyFileImportTableViewController: KeyImporter { extension SSHKeyFileImportTableViewController: KeyImporter {
static let keySource = KeySource.file static let keySource = KeySource.file
static let label = "LoadFromFiles".localize() static let label = "LoadFromFiles".localize()

View file

@ -6,12 +6,11 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
class SSHKeyUrlImportTableViewController: AutoCellHeightUITableViewController { class SSHKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
@IBOutlet var privateKeyURLTextField: UITextField!
@IBOutlet weak var privateKeyURLTextField: UITextField!
var sshPrivateKeyURL: URL? var sshPrivateKeyURL: URL?
@ -20,7 +19,8 @@ class SSHKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
privateKeyURLTextField.text = Defaults.gitSSHPrivateKeyURL?.absoluteString privateKeyURLTextField.text = Defaults.gitSSHPrivateKeyURL?.absoluteString
} }
@IBAction func doneButtonTapped(_ sender: UIButton) { @IBAction
func doneButtonTapped(_: UIButton) {
guard let text = privateKeyURLTextField.text, guard let text = privateKeyURLTextField.text,
let privateKeyURL = URL(string: text) else { let privateKeyURL = URL(string: text) else {
Utils.alert(title: "CannotSave".localize(), message: "SetPrivateKeyUrl.".localize(), controller: self) Utils.alert(title: "CannotSave".localize(), message: "SetPrivateKeyUrl.".localize(), controller: self)
@ -41,7 +41,6 @@ class SSHKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
} }
extension SSHKeyUrlImportTableViewController: KeyImporter { extension SSHKeyUrlImportTableViewController: KeyImporter {
static let keySource = KeySource.url static let keySource = KeySource.url
static let label = "DownloadFromUrl".localize() static let label = "DownloadFromUrl".localize()

View file

@ -10,15 +10,16 @@ import UIKit
class SettingsSplitViewController: UISplitViewController, UISplitViewControllerDelegate { class SettingsSplitViewController: UISplitViewController, UISplitViewControllerDelegate {
override func viewDidLoad() { override func viewDidLoad() {
self.delegate = self delegate = self
self.preferredDisplayMode = .allVisible preferredDisplayMode = .allVisible
} }
func splitViewController( func splitViewController(
_ splitViewController: UISplitViewController, _: UISplitViewController,
collapseSecondary secondaryViewController: UIViewController, collapseSecondary _: UIViewController,
onto primaryViewController: UIViewController) -> Bool { onto _: UIViewController
) -> Bool {
// Return true to prevent UIKit from applying its default behavior // Return true to prevent UIKit from applying its default behavior
return true true
} }
} }

View file

@ -6,26 +6,27 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import SVProgressHUD
import CoreData import CoreData
import passKit import passKit
import SVProgressHUD
import UIKit
class SettingsTableViewController: UITableViewController, UITabBarControllerDelegate { class SettingsTableViewController: UITableViewController, UITabBarControllerDelegate {
@IBOutlet weak var pgpKeyTableViewCell: UITableViewCell! @IBOutlet var pgpKeyTableViewCell: UITableViewCell!
@IBOutlet weak var passcodeTableViewCell: UITableViewCell! @IBOutlet var passcodeTableViewCell: UITableViewCell!
@IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell! @IBOutlet var passwordRepositoryTableViewCell: UITableViewCell!
var setPasscodeLockAlert: UIAlertController? var setPasscodeLockAlert: UIAlertController?
let passwordStore = PasswordStore.shared let passwordStore = PasswordStore.shared
let keychain = AppKeychain.shared let keychain = AppKeychain.shared
var passcodeLock = PasscodeLock.shared var passcodeLock = PasscodeLock.shared
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { func tabBarController(_: UITabBarController, didSelect _: UIViewController) {
navigationController?.popViewController(animated: true) navigationController?.popViewController(animated: true)
} }
@IBAction func savePGPKey(segue: UIStoryboardSegue) { @IBAction
func savePGPKey(segue: UIStoryboardSegue) {
guard let sourceController = segue.source as? PGPKeyImporter, sourceController.isReadyToUse() else { guard let sourceController = segue.source as? PGPKeyImporter, sourceController.isReadyToUse() else {
return return
} }
@ -58,19 +59,20 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
} }
} }
@IBAction func saveGitServerSetting(segue: UIStoryboardSegue) { @IBAction
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults.gitURL.host func saveGitServerSetting(segue _: UIStoryboardSegue) {
passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults.gitURL.host
} }
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(SettingsTableViewController.actOnPasswordStoreErasedNotification), name: .passwordStoreErased, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(SettingsTableViewController.actOnPasswordStoreErasedNotification), name: .passwordStoreErased, object: nil)
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults.gitURL.host passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults.gitURL.host
setPGPKeyTableViewCellDetailText() setPGPKeyTableViewCellDetailText()
setPasscodeLockCell() setPasscodeLockCell()
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_: Bool) {
super.viewWillAppear(true) super.viewWillAppear(true)
tabBarController!.delegate = self tabBarController!.delegate = self
setPasswordRepositoryTableViewCellDetailText() setPasswordRepositoryTableViewCellDetailText()
@ -78,9 +80,9 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
private func setPasscodeLockCell() { private func setPasscodeLockCell() {
if passcodeLock.hasPasscode { if passcodeLock.hasPasscode {
self.passcodeTableViewCell.detailTextLabel?.text = "On".localize() passcodeTableViewCell.detailTextLabel?.text = "On".localize()
} else { } else {
self.passcodeTableViewCell.detailTextLabel?.text = "Off".localize() passcodeTableViewCell.detailTextLabel?.text = "Off".localize()
} }
} }
@ -107,7 +109,8 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
passwordRepositoryTableViewCell.detailTextLabel?.text = host passwordRepositoryTableViewCell.detailTextLabel?.text = host
} }
@objc func actOnPasswordStoreErasedNotification() { @objc
func actOnPasswordStoreErasedNotification() {
setPGPKeyTableViewCellDetailText() setPGPKeyTableViewCellDetailText()
setPasswordRepositoryTableViewCellDetailText() setPasswordRepositoryTableViewCellDetailText()
setPasscodeLockCell() setPasscodeLockCell()
@ -180,7 +183,6 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let passcodeRemoveViewController = PasscodeLockViewController() let passcodeRemoveViewController = PasscodeLockViewController()
let removePasscodeAction = UIAlertAction(title: "RemovePasscode".localize(), style: .destructive) { [weak self] _ in let removePasscodeAction = UIAlertAction(title: "RemovePasscode".localize(), style: .destructive) { [weak self] _ in
passcodeRemoveViewController.successCallback = { passcodeRemoveViewController.successCallback = {
self?.passcodeLock.delete() self?.passcodeLock.delete()
@ -198,10 +200,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
optionMenu.addAction(UIAlertAction.cancel()) optionMenu.addAction(UIAlertAction.cancel())
optionMenu.popoverPresentationController?.sourceView = passcodeTableViewCell optionMenu.popoverPresentationController?.sourceView = passcodeTableViewCell
optionMenu.popoverPresentationController?.sourceRect = passcodeTableViewCell.bounds optionMenu.popoverPresentationController?.sourceRect = passcodeTableViewCell.bounds
self.present(optionMenu, animated: true, completion: nil) present(optionMenu, animated: true, completion: nil)
} }
@objc func alertTextFieldDidChange(_ sender: UITextField) { @objc
func alertTextFieldDidChange(_ sender: UITextField) {
// check whether we should enable the Save button in setPasscodeLockAlert // check whether we should enable the Save button in setPasscodeLockAlert
if let setPasscodeLockAlert = self.setPasscodeLockAlert, if let setPasscodeLockAlert = self.setPasscodeLockAlert,
let setPasscodeLockAlertTextFields0 = setPasscodeLockAlert.textFields?[0], let setPasscodeLockAlertTextFields0 = setPasscodeLockAlert.textFields?[0],
@ -230,7 +233,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}) })
// save action // save action
let saveAction = UIAlertAction(title: "Save".localize(), style: .default) { (action:UIAlertAction) -> Void in let saveAction = UIAlertAction(title: "Save".localize(), style: .default) { (_: UIAlertAction) -> Void in
let passcode: String = self.setPasscodeLockAlert!.textFields![0].text! let passcode: String = self.setPasscodeLockAlert!.textFields![0].text!
self.passcodeLock.save(passcode: passcode) self.passcodeLock.save(passcode: passcode)
// refresh the passcode lock cell ("On") // refresh the passcode lock cell ("On")
@ -244,17 +247,16 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
// present // present
setPasscodeLockAlert?.addAction(saveAction) setPasscodeLockAlert?.addAction(saveAction)
setPasscodeLockAlert?.addAction(cancelAction) setPasscodeLockAlert?.addAction(cancelAction)
self.present(setPasscodeLockAlert!, animated: true, completion: nil) present(setPasscodeLockAlert!, animated: true, completion: nil)
} }
} }
extension SettingsTableViewController: PGPKeyImporter { extension SettingsTableViewController: PGPKeyImporter {
static let keySource = KeySource.itunes static let keySource = KeySource.itunes
static let label = "ITunesFileSharing".localize() static let label = "ITunesFileSharing".localize()
func isReadyToUse() -> Bool { func isReadyToUse() -> Bool {
return KeyFileManager.PublicPgp.doesKeyFileExist() && KeyFileManager.PrivatePgp.doesKeyFileExist() KeyFileManager.PublicPgp.doesKeyFileExist() && KeyFileManager.PrivatePgp.doesKeyFileExist()
} }
func importKeys() throws { func importKeys() throws {

View file

@ -7,8 +7,8 @@
// //
import Foundation import Foundation
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
public func requestGitCredentialPassword(credential: GitCredential.Credential, public func requestGitCredentialPassword(credential: GitCredential.Credential,
lastPassword: String?, lastPassword: String?,
@ -27,21 +27,21 @@ public func requestGitCredentialPassword(credential: GitCredential.Credential,
DispatchQueue.main.async { DispatchQueue.main.async {
SVProgressHUD.dismiss() SVProgressHUD.dismiss()
let alert = UIAlertController(title: "Password".localize(), message: message, preferredStyle: .alert) let alert = UIAlertController(title: "Password".localize(), message: message, preferredStyle: .alert)
alert.addTextField() { alert.addTextField {
$0.text = lastPassword ?? "" $0.text = lastPassword ?? ""
$0.isSecureTextEntry = true $0.isSecureTextEntry = true
} }
alert.addAction(UIAlertAction.ok() { _ in alert.addAction(UIAlertAction.ok { _ in
password = alert.textFields?.first?.text password = alert.textFields?.first?.text
sem.signal() sem.signal()
}) })
alert.addAction(UIAlertAction.cancel() { _ in alert.addAction(UIAlertAction.cancel { _ in
password = nil password = nil
sem.signal() sem.signal()
}) })
controller.present(alert, animated: true) controller.present(alert, animated: true)
} }
let _ = sem.wait(timeout: .distantFuture) _ = sem.wait(timeout: .distantFuture)
return password return password
} }

View file

@ -25,7 +25,7 @@ class SecurePasteboard {
// exit the existing background task, if any // exit the existing background task, if any
if backgroundTaskID != UIBackgroundTaskIdentifier.invalid { if backgroundTaskID != UIBackgroundTaskIdentifier.invalid {
UIApplication.shared.endBackgroundTask(UIBackgroundTaskIdentifier.invalid) UIApplication.shared.endBackgroundTask(UIBackgroundTaskIdentifier.invalid)
self.backgroundTaskID = UIBackgroundTaskIdentifier.invalid backgroundTaskID = UIBackgroundTaskIdentifier.invalid
} }
backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in
@ -40,5 +40,4 @@ class SecurePasteboard {
self?.backgroundTaskID = UIBackgroundTaskIdentifier.invalid self?.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
} }
} }
} }

View file

@ -7,8 +7,8 @@
// //
import Foundation import Foundation
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
extension Utils { extension Utils {
static func alert(title: String, message: String, controller: UIViewController, handler: ((UIAlertAction) -> Void)? = nil, completion: (() -> Void)? = nil) { static func alert(title: String, message: String, controller: UIViewController, handler: ((UIAlertAction) -> Void)? = nil, completion: (() -> Void)? = nil) {

View file

@ -6,8 +6,8 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import passKit import passKit
import UIKit
protocol FillPasswordTableViewCellDelegate { protocol FillPasswordTableViewCellDelegate {
func generateAndCopyPassword() func generateAndCopyPassword()
@ -15,12 +15,11 @@ protocol FillPasswordTableViewCellDelegate {
} }
class FillPasswordTableViewCell: UITableViewCell, ContentProvider { class FillPasswordTableViewCell: UITableViewCell, ContentProvider {
@IBOutlet var contentTextField: UITextField!
@IBOutlet weak var contentTextField: UITextField!
var delegate: FillPasswordTableViewCellDelegate? var delegate: FillPasswordTableViewCellDelegate?
@IBOutlet weak var settingButton: UIButton! @IBOutlet var settingButton: UIButton!
@IBOutlet weak var generateButton: UIButton! @IBOutlet var generateButton: UIButton!
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
@ -32,21 +31,24 @@ class FillPasswordTableViewCell: UITableViewCell, ContentProvider {
generateButton.imageView?.contentMode = .scaleAspectFit generateButton.imageView?.contentMode = .scaleAspectFit
} }
@IBAction func generatePassword(_ sender: UIButton) { @IBAction
self.delegate?.generateAndCopyPassword() func generatePassword(_: UIButton) {
delegate?.generateAndCopyPassword()
} }
@IBAction func showHidePasswordSettings() { @IBAction
self.delegate?.showHidePasswordSettings() func showHidePasswordSettings() {
delegate?.showHidePasswordSettings()
} }
// re-color // re-color
@IBAction func textFieldDidChange(_ sender: UITextField) { @IBAction
func textFieldDidChange(_ sender: UITextField) {
contentTextField.attributedText = Utils.attributedPassword(plainPassword: sender.text ?? "") contentTextField.attributedText = Utils.attributedPassword(plainPassword: sender.text ?? "")
} }
func getContent() -> String? { func getContent() -> String? {
return contentTextField.attributedText?.string contentTextField.attributedText?.string
} }
func setContent(content: String?) { func setContent(content: String?) {

View file

@ -6,9 +6,9 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import UIKit
import SVProgressHUD
import passKit import passKit
import SVProgressHUD
import UIKit
struct LabelTableViewCellData { struct LabelTableViewCellData {
var title: String var title: String
@ -16,9 +16,8 @@ struct LabelTableViewCellData {
} }
class LabelTableViewCell: UITableViewCell { class LabelTableViewCell: UITableViewCell {
@IBOutlet var contentLabel: UILabel!
@IBOutlet weak var contentLabel: UILabel! @IBOutlet var titleLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel!
private enum CellType { private enum CellType {
case password, URL, HOTP, other case password, URL, HOTP, other
@ -74,12 +73,10 @@ class LabelTableViewCell: UITableViewCell {
} }
override var canBecomeFirstResponder: Bool { override var canBecomeFirstResponder: Bool {
get { true
return true
}
} }
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { override func canPerformAction(_ action: Selector, withSender _: Any?) -> Bool {
switch type { switch type {
case .password: case .password:
if isReveal { if isReveal {
@ -100,11 +97,12 @@ class LabelTableViewCell: UITableViewCell {
} }
} }
override func copy(_ sender: Any?) { override func copy(_: Any?) {
SecurePasteboard.shared.copy(textToCopy: cellData?.content) SecurePasteboard.shared.copy(textToCopy: cellData?.content)
} }
@objc func revealPassword(_ sender: Any?) { @objc
func revealPassword(_: Any?) {
let plainPassword = cellData?.content ?? "" let plainPassword = cellData?.content ?? ""
if type == .password { if type == .password {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword) contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
@ -115,7 +113,8 @@ class LabelTableViewCell: UITableViewCell {
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal) passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal)
} }
@objc func concealPassword(_ sender: Any?) { @objc
func concealPassword(_: Any?) {
if type == .password { if type == .password {
if cellData?.content.isEmpty == false { if cellData?.content.isEmpty == false {
contentLabel.text = Globals.passwordDots contentLabel.text = Globals.passwordDots
@ -130,7 +129,8 @@ class LabelTableViewCell: UITableViewCell {
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal) passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
} }
@objc func reversePasswordDisplay(_ sender: Any?) { @objc
func reversePasswordDisplay(_ sender: Any?) {
if isReveal { if isReveal {
// conceal // conceal
concealPassword(sender) concealPassword(sender)
@ -140,19 +140,21 @@ class LabelTableViewCell: UITableViewCell {
} }
} }
@objc func openLink(_ sender: Any?) { @objc
func openLink(_: Any?) {
// if isURLCell, passwordTableView should not be nil // if isURLCell, passwordTableView should not be nil
delegatePasswordTableView!.openLink(to: cellData?.content) delegatePasswordTableView!.openLink(to: cellData?.content)
} }
@objc func getNextHOTP(_ sender: Any?) { @objc
func getNextHOTP(_: Any?) {
// if isHOTPCell, passwordTableView should not be nil // if isHOTPCell, passwordTableView should not be nil
delegatePasswordTableView!.getNextHOTP() delegatePasswordTableView!.getNextHOTP()
} }
private func updateButtons() { private func updateButtons() {
// total width and height of a button // total width and height of a button
let height = min(self.bounds.height, 36.0) let height = min(bounds.height, 36.0)
let width = max(height * 0.8, Globals.tableCellButtonSize) let width = max(height * 0.8, Globals.tableCellButtonSize)
// margins (between button boundary and icon) // margins (between button boundary and icon)
@ -167,7 +169,7 @@ class LabelTableViewCell: UITableViewCell {
passwordDisplayButton!.frame = CGRect(x: 0, y: 0, width: width, height: height) passwordDisplayButton!.frame = CGRect(x: 0, y: 0, width: width, height: height)
passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal) passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsets.init(top: marginY, left: marginX, bottom: marginY, right: marginX) passwordDisplayButton!.contentEdgeInsets = UIEdgeInsets(top: marginY, left: marginX, bottom: marginY, right: marginX)
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControl.Event.touchUpInside) passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControl.Event.touchUpInside)
buttons = passwordDisplayButton buttons = passwordDisplayButton
} }
@ -177,7 +179,7 @@ class LabelTableViewCell: UITableViewCell {
nextButton.frame = CGRect(x: 0, y: 0, width: width, height: height) nextButton.frame = CGRect(x: 0, y: 0, width: width, height: height)
nextButton.setImage(#imageLiteral(resourceName: "Refresh"), for: .normal) nextButton.setImage(#imageLiteral(resourceName: "Refresh"), for: .normal)
nextButton.imageView?.contentMode = .scaleAspectFit nextButton.imageView?.contentMode = .scaleAspectFit
nextButton.contentEdgeInsets = UIEdgeInsets.init(top: marginY, left: marginX, bottom: marginY, right: marginX) nextButton.contentEdgeInsets = UIEdgeInsets(top: marginY, left: marginX, bottom: marginY, right: marginX)
nextButton.addTarget(self, action: #selector(getNextHOTP), for: UIControl.Event.touchUpInside) nextButton.addTarget(self, action: #selector(getNextHOTP), for: UIControl.Event.touchUpInside)
// password button // password button
@ -186,7 +188,7 @@ class LabelTableViewCell: UITableViewCell {
passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal) passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsets.init(top: marginY, left: marginX, bottom: marginY, right: marginX) passwordDisplayButton!.contentEdgeInsets = UIEdgeInsets(top: marginY, left: marginX, bottom: marginY, right: marginX)
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControl.Event.touchUpInside) passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControl.Event.touchUpInside)
buttons = UIView() buttons = UIView()
@ -197,6 +199,6 @@ class LabelTableViewCell: UITableViewCell {
passwordDisplayButton = nil passwordDisplayButton = nil
buttons = nil buttons = nil
} }
self.accessoryView = buttons accessoryView = buttons
} }
} }

View file

@ -9,13 +9,12 @@
import UIKit import UIKit
class PasswordDetailTitleTableViewCell: UITableViewCell { class PasswordDetailTitleTableViewCell: UITableViewCell {
@IBOutlet weak var categoryLabel: UILabel! @IBOutlet var categoryLabel: UILabel!
@IBOutlet weak var nameLabel: UILabel! @IBOutlet var nameLabel: UILabel!
@IBOutlet weak var passwordImageImageView: UIImageView! @IBOutlet var passwordImageImageView: UIImageView!
@IBOutlet var labelImageConstraint: NSLayoutConstraint! @IBOutlet var labelImageConstraint: NSLayoutConstraint!
@IBOutlet var labelCellConstraint: NSLayoutConstraint! @IBOutlet var labelCellConstraint: NSLayoutConstraint!
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
} }
@ -25,5 +24,4 @@ class PasswordDetailTitleTableViewCell: UITableViewCell {
// Configure the view for the selected state // Configure the view for the selected state
} }
} }

View file

@ -10,7 +10,6 @@ import passKit
import UIKit import UIKit
class SliderTableViewCell: UITableViewCell { class SliderTableViewCell: UITableViewCell {
@IBOutlet var titleLabel: UILabel! @IBOutlet var titleLabel: UILabel!
@IBOutlet var valueLabel: UILabel! @IBOutlet var valueLabel: UILabel!
@IBOutlet var slider: UISlider! @IBOutlet var slider: UISlider!
@ -20,7 +19,8 @@ class SliderTableViewCell: UITableViewCell {
private var delegate: PasswordSettingSliderTableViewCellDelegate! private var delegate: PasswordSettingSliderTableViewCellDelegate!
@IBAction func handleSliderValueChange(_ sender: UISlider) { @IBAction
func handleSliderValueChange(_ sender: UISlider) {
let newRoundedValue = Int(sender.value) let newRoundedValue = Int(sender.value)
// Proceed only if the rounded value gets updated. // Proceed only if the rounded value gets updated.
guard checker(newRoundedValue) else { guard checker(newRoundedValue) else {
@ -67,9 +67,8 @@ class SliderTableViewCell: UITableViewCell {
} }
extension SliderTableViewCell: ContentProvider { extension SliderTableViewCell: ContentProvider {
func getContent() -> String? { func getContent() -> String? {
return nil nil
} }
func setContent(content _: String?) {} func setContent(content _: String?) {}

View file

@ -10,7 +10,6 @@ import passKit
import UIKit import UIKit
class SwitchTableViewCell: UITableViewCell { class SwitchTableViewCell: UITableViewCell {
@IBOutlet var titleLabel: UILabel! @IBOutlet var titleLabel: UILabel!
@IBOutlet var controlSwitch: UISwitch! @IBOutlet var controlSwitch: UISwitch!
@ -18,7 +17,8 @@ class SwitchTableViewCell: UITableViewCell {
private var delegate: PasswordSettingSliderTableViewCellDelegate! private var delegate: PasswordSettingSliderTableViewCellDelegate!
@IBAction func switchValueChanged(_: Any) { @IBAction
func switchValueChanged(_: Any) {
updater(controlSwitch.isOn) updater(controlSwitch.isOn)
delegate.generateAndCopyPassword() delegate.generateAndCopyPassword()
} }

View file

@ -9,11 +9,10 @@
import UIKit import UIKit
class TextFieldTableViewCell: UITableViewCell, ContentProvider { class TextFieldTableViewCell: UITableViewCell, ContentProvider {
@IBOutlet var contentTextField: UITextField!
@IBOutlet weak var contentTextField: UITextField!
func getContent() -> String? { func getContent() -> String? {
return contentTextField.text contentTextField.text
} }
func setContent(content: String?) { func setContent(content: String?) {

View file

@ -9,17 +9,16 @@
import UIKit import UIKit
class TextViewTableViewCell: UITableViewCell, ContentProvider { class TextViewTableViewCell: UITableViewCell, ContentProvider {
@IBOutlet var contentTextView: UITextView!
@IBOutlet weak var contentTextView: UITextView!
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
self.contentTextView.textContainer.lineFragmentPadding = 0 contentTextView.textContainer.lineFragmentPadding = 0
self.contentTextView.textContainerInset = .zero contentTextView.textContainerInset = .zero
} }
func getContent() -> String? { func getContent() -> String? {
return contentTextView.text contentTextView.text
} }
func setContent(content: String?) { func setContent(content: String?) {

View file

@ -6,11 +6,10 @@
Copyright © 2019 Bob Sun. All rights reserved. Copyright © 2019 Bob Sun. All rights reserved.
*/ */
import UIKit
import passKit import passKit
import UIKit
class UICodeHighlightingLabel: UILocalizedLabel { class UICodeHighlightingLabel: UILocalizedLabel {
private static let CODE_ATTRIBUTES: [NSAttributedString.Key: Any] = [.font: UIFont(name: "Menlo-Regular", size: 12)!] private static let CODE_ATTRIBUTES: [NSAttributedString.Key: Any] = [.font: UIFont(name: "Menlo-Regular", size: 12)!]
private static let ATTRIBUTED_NEWLINE = NSAttributedString(string: "\n") private static let ATTRIBUTED_NEWLINE = NSAttributedString(string: "\n")
@ -43,4 +42,3 @@ class UICodeHighlightingLabel: UILocalizedLabel {
return formattedText return formattedText
} }
} }

View file

@ -6,11 +6,10 @@
Copyright © 2019 Bob Sun. All rights reserved. Copyright © 2019 Bob Sun. All rights reserved.
*/ */
import UIKit
import passKit import passKit
import UIKit
class UILocalizedLabel: UILabel { class UILocalizedLabel: UILabel {
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
text = text?.localize() text = text?.localize()

View file

@ -10,8 +10,8 @@ import AuthenticationServices
import passKit import passKit
class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate { class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
@IBOutlet weak var searchBar: UISearchBar! @IBOutlet var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView! @IBOutlet var tableView: UITableView!
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
private let keychain = AppKeychain.shared private let keychain = AppKeychain.shared
@ -32,7 +32,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
*/ */
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
// clean up the search bar // clean up the search bar
guard serviceIdentifiers.count > 0 else { guard !serviceIdentifiers.isEmpty else {
searchBar.text = "" searchBar.text = ""
searchBar.becomeFirstResponder() searchBar.becomeFirstResponder()
searchBarSearchButtonClicked(searchBar) searchBarSearchButtonClicked(searchBar)
@ -41,7 +41,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
// get the domain // get the domain
var identifier = serviceIdentifiers[0].identifier var identifier = serviceIdentifiers[0].identifier
if !identifier.hasPrefix("http://") && !identifier.hasPrefix("https://") { if !identifier.hasPrefix("http://"), !identifier.hasPrefix("https://") {
identifier = "http://" + identifier identifier = "http://" + identifier
} }
let url = URL(string: identifier)?.host ?? "" let url = URL(string: identifier)?.host ?? ""
@ -82,8 +82,9 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
} }
*/ */
@IBAction func cancel(_ sender: AnyObject?) { @IBAction
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) func cancel(_: AnyObject?) {
extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue))
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
@ -107,7 +108,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
private func initPasswordsTableEntries() { private func initPasswordsTableEntries() {
filteredPasswordsTableEntries.removeAll() filteredPasswordsTableEntries.removeAll()
let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false) let passwordEntities = passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableEntries = passwordEntities.compactMap { passwordsTableEntries = passwordEntities.compactMap {
PasswordTableEntry($0) PasswordTableEntry($0)
} }
@ -129,7 +130,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
} }
// select row -> extension returns (with username and password) // select row -> extension returns (with username and password)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
let entry = getPasswordEntry(by: indexPath) let entry = getPasswordEntry(by: indexPath)
guard PGPAgent.shared.isPrepared else { guard PGPAgent.shared.isPrepared else {
@ -139,7 +140,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
let passwordEntity = entry.passwordEntity let passwordEntity = entry.passwordEntity
UIImpactFeedbackGenerator(style: .medium).impactOccurred() UIImpactFeedbackGenerator(style: .medium).impactOccurred()
self.decryptPassword(passwordEntity: passwordEntity) decryptPassword(passwordEntity: passwordEntity)
} }
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) { private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
@ -154,7 +155,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
let passwordCredential = ASPasswordCredential(user: username, password: password) let passwordCredential = ASPasswordCredential(user: username, password: password)
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential) self.extensionContext.completeRequest(withSelectedCredential: passwordCredential)
} }
} catch AppError.PgpPrivateKeyNotFound(let key) { } catch let AppError.PgpPrivateKeyNotFound(key) {
DispatchQueue.main.async { DispatchQueue.main.async {
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert) let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction.cancelAndPopView(controller: self)) alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
@ -173,21 +174,21 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
} }
} }
func numberOfSectionsInTableView(tableView: UITableView) -> Int { func numberOfSectionsInTableView(tableView _: UITableView) -> Int {
return 1 1
} }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
if searchActive { if searchActive {
return filteredPasswordsTableEntries.count return filteredPasswordsTableEntries.count
} }
return passwordsTableEntries.count; return passwordsTableEntries.count
} }
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = "" searchBar.text = ""
searchActive = false searchActive = false
self.tableView.reloadData() tableView.reloadData()
} }
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
@ -197,10 +198,10 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
} else { } else {
searchActive = false searchActive = false
} }
self.tableView.reloadData() tableView.reloadData()
} }
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { func searchBar(_ searchBar: UISearchBar, textDidChange _: String) {
searchBarSearchButtonClicked(searchBar) searchBarSearchButtonClicked(searchBar)
} }

View file

@ -6,23 +6,26 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import AuthenticationServices
import Foundation import Foundation
import passKit import passKit
import AuthenticationServices
// cancel means cancel the extension // cancel means cancel the extension
class PasscodeLockViewControllerForExtension: PasscodeLockViewController { class PasscodeLockViewControllerForExtension: PasscodeLockViewController {
var originalExtensionContest: ASCredentialProviderExtensionContext? var originalExtensionContest: ASCredentialProviderExtensionContext?
public convenience init(extensionContext: ASCredentialProviderExtensionContext?) { public convenience init(extensionContext: ASCredentialProviderExtensionContext?) {
self.init() self.init()
originalExtensionContest = extensionContext self.originalExtensionContest = extensionContext
} }
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
cancelButton?.removeTarget(nil, action: nil, for: .allEvents) cancelButton?.removeTarget(nil, action: nil, for: .allEvents)
cancelButton?.addTarget(self, action: #selector(cancelExtension), for: .touchUpInside) cancelButton?.addTarget(self, action: #selector(cancelExtension), for: .touchUpInside)
} }
@objc func cancelExtension() {
@objc
func cancelExtension() {
originalExtensionContest?.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) originalExtensionContest?.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue))
} }
} }
@ -34,7 +37,7 @@ class PasscodeExtensionDisplay {
public init(extensionContext: ASCredentialProviderExtensionContext?) { public init(extensionContext: ASCredentialProviderExtensionContext?) {
self.extensionContext = extensionContext self.extensionContext = extensionContext
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext) self.passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
passcodeLockVC.dismissCompletionCallback = { [weak self] in passcodeLockVC.dismissCompletionCallback = { [weak self] in
self?.dismiss() self?.dismiss()
} }
@ -43,14 +46,14 @@ class PasscodeExtensionDisplay {
// present the passcode lock view if passcode is set and the view controller is not presented // present the passcode lock view if passcode is set and the view controller is not presented
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) { public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else { guard PasscodeLock.shared.hasPasscode, !isPasscodePresented == true else {
return return
} }
isPasscodePresented = true isPasscodePresented = true
extensionVC.present(passcodeLockVC, animated: true, completion: nil) extensionVC.present(passcodeLockVC, animated: true, completion: nil)
} }
public func dismiss(animated: Bool = true) { public func dismiss(animated _: Bool = true) {
isPasscodePresented = false isPasscodePresented = false
} }
} }

View file

@ -11,8 +11,8 @@ import MobileCoreServices
import passKit import passKit
class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate { class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate {
@IBOutlet weak var searchBar: UISearchBar! @IBOutlet var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView! @IBOutlet var tableView: UITableView!
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
private let keychain = AppKeychain.shared private let keychain = AppKeychain.shared
@ -35,7 +35,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
private func initPasswordsTableEntries() { private func initPasswordsTableEntries() {
filteredPasswordsTableEntries.removeAll() filteredPasswordsTableEntries.removeAll()
let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false) let passwordEntities = passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableEntries = passwordEntities.map { passwordsTableEntries = passwordEntities.map {
PasswordTableEntry($0) PasswordTableEntry($0)
} }
@ -67,11 +67,11 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
for provider in itemProviders { for provider in itemProviders {
// search using the extensionContext inputs // search using the extensionContext inputs
if provider.hasItemConformingToTypeIdentifier(OnePasswordExtensionActions.findLogin) { if provider.hasItemConformingToTypeIdentifier(OnePasswordExtensionActions.findLogin) {
provider.loadItem(forTypeIdentifier: OnePasswordExtensionActions.findLogin, options: nil, completionHandler: { (item, error) -> Void in provider.loadItem(forTypeIdentifier: OnePasswordExtensionActions.findLogin, options: nil, completionHandler: { (item, _) -> Void in
let dictionary = item as! NSDictionary let dictionary = item as! NSDictionary
var url: String? var url: String?
if var urlString = dictionary[OnePasswordExtensionKey.URLStringKey] as? String { if var urlString = dictionary[OnePasswordExtensionKey.URLStringKey] as? String {
if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") { if !urlString.hasPrefix("http://"), !urlString.hasPrefix("https://") {
urlString = "http://" + urlString urlString = "http://" + urlString
} }
url = URL(string: urlString)?.host url = URL(string: urlString)?.host
@ -84,14 +84,13 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
self?.searchBarSearchButtonClicked((self?.searchBar)!) self?.searchBarSearchButtonClicked((self?.searchBar)!)
} }
}) })
} } else if provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) {
else if provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) { provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil, completionHandler: { (item, _) -> Void in
provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil, completionHandler: { (item, error) -> Void in
var url: String? var url: String?
if let dictionary = item as? NSDictionary, if let dictionary = item as? NSDictionary,
let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary, let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary,
var urlString = results[OnePasswordExtensionKey.URLStringKey] as? String { var urlString = results[OnePasswordExtensionKey.URLStringKey] as? String {
if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") { if !urlString.hasPrefix("http://"), !urlString.hasPrefix("https://") {
urlString = "http://" + urlString urlString = "http://" + urlString
} }
url = URL(string: urlString)?.host url = URL(string: urlString)?.host
@ -105,7 +104,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
} }
}) })
} else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { } else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (item, error) -> Void in provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (item, _) -> Void in
let url = (item as? NSURL)!.host let url = (item as? NSURL)!.host
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.extensionAction = .fillBrowser self?.extensionAction = .fillBrowser
@ -137,7 +136,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
} }
// select row -> extension returns (with username and password) // select row -> extension returns (with username and password)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
let entry = getPasswordEntry(by: indexPath) let entry = getPasswordEntry(by: indexPath)
guard PGPAgent.shared.isPrepared else { guard PGPAgent.shared.isPrepared else {
@ -147,7 +146,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
let passwordEntity = entry.passwordEntity let passwordEntity = entry.passwordEntity
UIImpactFeedbackGenerator(style: .medium).impactOccurred() UIImpactFeedbackGenerator(style: .medium).impactOccurred()
self.decryptPassword(passwordEntity: passwordEntity) decryptPassword(passwordEntity: passwordEntity)
} }
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) { private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
@ -180,7 +179,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
} }
} }
} catch AppError.PgpPrivateKeyNotFound(let key) { } catch let AppError.PgpPrivateKeyNotFound(key) {
DispatchQueue.main.async { DispatchQueue.main.async {
// alert: cancel or try again // alert: cancel or try again
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert) let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
@ -200,26 +199,26 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
} }
} }
func numberOfSectionsInTableView(tableView _: UITableView) -> Int {
func numberOfSectionsInTableView(tableView: UITableView) -> Int { 1
return 1
} }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
if searchActive { if searchActive {
return filteredPasswordsTableEntries.count return filteredPasswordsTableEntries.count
} }
return passwordsTableEntries.count; return passwordsTableEntries.count
} }
@IBAction func cancelExtension(_ sender: Any) { @IBAction
func cancelExtension(_: Any) {
extensionContext!.completeRequest(returningItems: [], completionHandler: nil) extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
} }
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = "" searchBar.text = ""
searchActive = false searchActive = false
self.tableView.reloadData() tableView.reloadData()
} }
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
@ -229,10 +228,10 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
} else { } else {
searchActive = false searchActive = false
} }
self.tableView.reloadData() tableView.reloadData()
} }
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { func searchBar(_ searchBar: UISearchBar, textDidChange _: String) {
searchBarSearchButtonClicked(searchBar) searchBarSearchButtonClicked(searchBar)
} }

View file

@ -14,14 +14,17 @@ class PasscodeLockViewControllerForExtension: PasscodeLockViewController {
var originalExtensionContest: NSExtensionContext? var originalExtensionContest: NSExtensionContext?
public convenience init(extensionContext: NSExtensionContext?) { public convenience init(extensionContext: NSExtensionContext?) {
self.init() self.init()
originalExtensionContest = extensionContext self.originalExtensionContest = extensionContext
} }
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
cancelButton?.removeTarget(nil, action: nil, for: .allEvents) cancelButton?.removeTarget(nil, action: nil, for: .allEvents)
cancelButton?.addTarget(self, action: #selector(cancelExtension), for: .touchUpInside) cancelButton?.addTarget(self, action: #selector(cancelExtension), for: .touchUpInside)
} }
@objc func cancelExtension() {
@objc
func cancelExtension() {
originalExtensionContest?.completeRequest(returningItems: [], completionHandler: nil) originalExtensionContest?.completeRequest(returningItems: [], completionHandler: nil)
} }
} }
@ -33,7 +36,7 @@ class PasscodeExtensionDisplay {
public init(extensionContext: NSExtensionContext?) { public init(extensionContext: NSExtensionContext?) {
self.extensionContext = extensionContext self.extensionContext = extensionContext
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext) self.passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
passcodeLockVC.dismissCompletionCallback = { [weak self] in passcodeLockVC.dismissCompletionCallback = { [weak self] in
self?.dismiss() self?.dismiss()
} }
@ -42,14 +45,14 @@ class PasscodeExtensionDisplay {
// present the passcode lock view if passcode is set and the view controller is not presented // present the passcode lock view if passcode is set and the view controller is not presented
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) { public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else { guard PasscodeLock.shared.hasPasscode, !isPasscodePresented == true else {
return return
} }
isPasscodePresented = true isPasscodePresented = true
extensionVC.present(passcodeLockVC, animated: true, completion: nil) extensionVC.present(passcodeLockVC, animated: true, completion: nil)
} }
public func dismiss(animated: Bool = true) { public func dismiss(animated _: Bool = true) {
isPasscodePresented = false isPasscodePresented = false
} }
} }

View file

@ -9,7 +9,6 @@
import Crypto import Crypto
struct GopenPGPInterface: PGPInterface { struct GopenPGPInterface: PGPInterface {
private static let errorMapping: [String: Error] = [ private static let errorMapping: [String: Error] = [
"gopenpgp: error in unlocking key: openpgp: invalid data: private key checksum failure": AppError.WrongPassphrase, "gopenpgp: error in unlocking key: openpgp: invalid data: private key checksum failure": AppError.WrongPassphrase,
"openpgp: incorrect key": AppError.KeyExpiredOrIncompatible, "openpgp: incorrect key": AppError.KeyExpiredOrIncompatible,
@ -43,7 +42,6 @@ struct GopenPGPInterface: PGPInterface {
} }
privateKeys[k.getFingerprint().lowercased()] = k privateKeys[k.getFingerprint().lowercased()] = k
} }
} }
func extractKeysFromArmored(str: String) -> [String] { func extractKeysFromArmored(str: String) -> [String] {
@ -73,7 +71,7 @@ struct GopenPGPInterface: PGPInterface {
} }
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data? { func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data? {
guard let e = privateKeys.first(where: { (key, _) in key.hasSuffix(keyID.lowercased()) }), guard let e = privateKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) }),
let privateKey = privateKeys[e.key] else { let privateKey = privateKeys[e.key] else {
throw AppError.Decryption throw AppError.Decryption
} }
@ -97,7 +95,7 @@ struct GopenPGPInterface: PGPInterface {
} }
func encrypt(plainData: Data, keyID: String) throws -> Data { func encrypt(plainData: Data, keyID: String) throws -> Data {
guard let e = publicKeys.first(where: { (key, _) in key.hasSuffix(keyID.lowercased()) }), guard let e = publicKeys.first(where: { key, _ in key.hasSuffix(keyID.lowercased()) }),
let publicKey = publicKeys[e.key] else { let publicKey = publicKeys[e.key] else {
throw AppError.Encryption throw AppError.Encryption
} }
@ -124,11 +122,11 @@ struct GopenPGPInterface: PGPInterface {
} }
var keyID: [String] { var keyID: [String] {
return publicKeys.keys.map({ $0.uppercased() }) publicKeys.keys.map { $0.uppercased() }
} }
var shortKeyID: [String] { var shortKeyID: [String] {
return publicKeys.keys.map({ $0.suffix(8).uppercased()}) publicKeys.keys.map { $0.suffix(8).uppercased() }
} }
private func createPgpMessage(from encryptedData: Data) -> CryptoPGPMessage? { private func createPgpMessage(from encryptedData: Data) -> CryptoPGPMessage? {

View file

@ -9,7 +9,6 @@
import ObjectivePGP import ObjectivePGP
struct ObjectivePGPInterface: PGPInterface { struct ObjectivePGPInterface: PGPInterface {
private let publicKey: Key private let publicKey: Key
private let privateKey: Key private let privateKey: Key
@ -30,11 +29,11 @@ struct ObjectivePGPInterface: PGPInterface {
self.privateKey = privateKey self.privateKey = privateKey
} }
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data? { func decrypt(encryptedData: Data, keyID _: String, passphrase: String) throws -> Data? {
return try ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys) { _ in passphrase } try ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys) { _ in passphrase }
} }
func encrypt(plainData: Data, keyID: String) throws -> Data { func encrypt(plainData: Data, keyID _: String) throws -> Data {
let encryptedData = try ObjectivePGP.encrypt(plainData, addSignature: false, using: keyring.keys, passphraseForKey: nil) let encryptedData = try ObjectivePGP.encrypt(plainData, addSignature: false, using: keyring.keys, passphraseForKey: nil)
if Defaults.encryptInArmored { if Defaults.encryptInArmored {
return Armor.armored(encryptedData, as: .message).data(using: .ascii)! return Armor.armored(encryptedData, as: .message).data(using: .ascii)!
@ -51,10 +50,10 @@ struct ObjectivePGPInterface: PGPInterface {
} }
var keyID: [String] { var keyID: [String] {
return keyring.keys.map({ $0.keyID.longIdentifier }) keyring.keys.map(\.keyID.longIdentifier)
} }
var shortKeyID: [String] { var shortKeyID: [String] {
return keyring.keys.map({ $0.keyID.shortIdentifier }) keyring.keys.map(\.keyID.shortIdentifier)
} }
} }

View file

@ -7,7 +7,6 @@
// //
public class PGPAgent { public class PGPAgent {
public static let shared: PGPAgent = PGPAgent() public static let shared: PGPAgent = PGPAgent()
private let keyStore: KeyStore private let keyStore: KeyStore
@ -52,7 +51,7 @@ public class PGPAgent {
throw AppError.Decryption throw AppError.Decryption
} }
var keyID = keyID; var keyID = keyID
if !pgpInterface.containsPrivateKey(with: keyID) { if !pgpInterface.containsPrivateKey(with: keyID) {
if pgpInterface.keyID.count == 1 { if pgpInterface.keyID.count == 1 {
keyID = pgpInterface.keyID.first! keyID = pgpInterface.keyID.first!
@ -62,8 +61,8 @@ public class PGPAgent {
} }
// Remember the previous status and set the current status // Remember the previous status and set the current status
let previousDecryptStatus = self.latestDecryptStatus let previousDecryptStatus = latestDecryptStatus
self.latestDecryptStatus = false latestDecryptStatus = false
// Get the PGP key passphrase. // Get the PGP key passphrase.
var passphrase = "" var passphrase = ""
@ -77,7 +76,7 @@ public class PGPAgent {
return nil return nil
} }
// The decryption step has succeed. // The decryption step has succeed.
self.latestDecryptStatus = true latestDecryptStatus = true
return result return result
} }
@ -98,7 +97,7 @@ public class PGPAgent {
} }
public var isPrepared: Bool { public var isPrepared: Bool {
return keyStore.contains(key: PgpKey.PUBLIC.getKeychainKey()) keyStore.contains(key: PgpKey.PUBLIC.getKeychainKey())
&& keyStore.contains(key: PgpKey.PRIVATE.getKeychainKey()) && keyStore.contains(key: PgpKey.PRIVATE.getKeychainKey())
} }

View file

@ -7,7 +7,6 @@
// //
protocol PGPInterface { protocol PGPInterface {
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data? func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data?
func encrypt(plainData: Data, keyID: String) throws -> Data func encrypt(plainData: Data, keyID: String) throws -> Data

View file

@ -11,9 +11,8 @@
import UIKit import UIKit
open class PasscodeLockPresenter { open class PasscodeLockPresenter {
private var mainWindow: UIWindow?
fileprivate var mainWindow: UIWindow? private var passcodeLockWindow: UIWindow?
fileprivate var passcodeLockWindow: UIWindow?
public init(mainWindow window: UIWindow?) { public init(mainWindow window: UIWindow?) {
self.mainWindow = window self.mainWindow = window
@ -27,7 +26,7 @@ open class PasscodeLockPresenter {
// new window // new window
mainWindow?.endEditing(true) mainWindow?.endEditing(true)
passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame) passcodeLockWindow = UIWindow(frame: mainWindow!.frame)
moveWindowsToFront(windowLevel: windowLevel) moveWindowsToFront(windowLevel: windowLevel)
passcodeLockWindow?.isHidden = false passcodeLockWindow?.isHidden = false
@ -46,7 +45,7 @@ open class PasscodeLockPresenter {
passcodeLockWindow?.rootViewController = nil passcodeLockWindow?.rootViewController = nil
} }
fileprivate func moveWindowsToFront(windowLevel: CGFloat?) { private func moveWindowsToFront(windowLevel: CGFloat?) {
let windowLevel = windowLevel ?? UIWindow.Level.normal.rawValue let windowLevel = windowLevel ?? UIWindow.Level.normal.rawValue
let maxWinLevel = max(windowLevel, UIWindow.Level.normal.rawValue) let maxWinLevel = max(windowLevel, UIWindow.Level.normal.rawValue)
passcodeLockWindow?.windowLevel = UIWindow.Level(rawValue: maxWinLevel + 1) passcodeLockWindow?.windowLevel = UIWindow.Level(rawValue: maxWinLevel + 1)

View file

@ -8,11 +8,10 @@
// Inspired by SwiftPasscodeLock created by Yanko Dimitrov. // Inspired by SwiftPasscodeLock created by Yanko Dimitrov.
// //
import UIKit
import LocalAuthentication import LocalAuthentication
import UIKit
open class PasscodeLockViewController: UIViewController, UITextFieldDelegate { open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
open var dismissCompletionCallback: (() -> Void)? open var dismissCompletionCallback: (() -> Void)?
open var successCallback: (() -> Void)? open var successCallback: (() -> Void)?
open var cancelCallback: (() -> Void)? open var cancelCallback: (() -> Void)?
@ -26,7 +25,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
open override func loadView() { override open func loadView() {
super.loadView() super.loadView()
let passcodeTextField = UITextField() let passcodeTextField = UITextField()
@ -35,9 +34,9 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
passcodeTextField.isSecureTextEntry = true passcodeTextField.isSecureTextEntry = true
passcodeTextField.clearButtonMode = UITextField.ViewMode.whileEditing passcodeTextField.clearButtonMode = UITextField.ViewMode.whileEditing
passcodeTextField.delegate = self passcodeTextField.delegate = self
passcodeTextField.addTarget(self, action: #selector(self.passcodeTextFieldDidChange(_:)), for: UIControl.Event.editingChanged) passcodeTextField.addTarget(self, action: #selector(passcodeTextFieldDidChange(_:)), for: UIControl.Event.editingChanged)
passcodeTextField.translatesAutoresizingMaskIntoConstraints = false passcodeTextField.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(passcodeTextField) view.addSubview(passcodeTextField)
self.passcodeTextField = passcodeTextField self.passcodeTextField = passcodeTextField
view.backgroundColor = Colors.systemBackground view.backgroundColor = Colors.systemBackground
@ -50,7 +49,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
biometryAuthButton.addTarget(self, action: #selector(bioButtonPressedAction(_:)), for: .touchUpInside) biometryAuthButton.addTarget(self, action: #selector(bioButtonPressedAction(_:)), for: .touchUpInside)
biometryAuthButton.isHidden = true biometryAuthButton.isHidden = true
biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(biometryAuthButton) view.addSubview(biometryAuthButton)
self.biometryAuthButton = biometryAuthButton self.biometryAuthButton = biometryAuthButton
let myContext = LAContext() let myContext = LAContext()
@ -71,19 +70,19 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
forgotPasscodeButton.setTitleColor(Colors.systemBlue, for: .normal) forgotPasscodeButton.setTitleColor(Colors.systemBlue, for: .normal)
forgotPasscodeButton.addTarget(self, action: #selector(forgotPasscodeButtonPressedAction(_:)), for: .touchUpInside) forgotPasscodeButton.addTarget(self, action: #selector(forgotPasscodeButtonPressedAction(_:)), for: .touchUpInside)
// hide the forgotPasscodeButton if the native app is running // hide the forgotPasscodeButton if the native app is running
forgotPasscodeButton.isHidden = self.isCancellable forgotPasscodeButton.isHidden = isCancellable
forgotPasscodeButton.translatesAutoresizingMaskIntoConstraints = false forgotPasscodeButton.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(forgotPasscodeButton) view.addSubview(forgotPasscodeButton)
self.forgotPasscodeButton = forgotPasscodeButton self.forgotPasscodeButton = forgotPasscodeButton
let cancelButton = UIButton(type: .custom) let cancelButton = UIButton(type: .custom)
cancelButton.setTitle("Cancel".localize(), for: .normal) cancelButton.setTitle("Cancel".localize(), for: .normal)
cancelButton.setTitleColor(Colors.systemBlue, for: .normal) cancelButton.setTitleColor(Colors.systemBlue, for: .normal)
cancelButton.addTarget(self, action: #selector(passcodeLockDidCancel), for: .touchUpInside) cancelButton.addTarget(self, action: #selector(passcodeLockDidCancel), for: .touchUpInside)
cancelButton.isHidden = !self.isCancellable cancelButton.isHidden = !isCancellable
cancelButton.translatesAutoresizingMaskIntoConstraints = false cancelButton.translatesAutoresizingMaskIntoConstraints = false
cancelButton.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.left cancelButton.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.left
self.view.addSubview(cancelButton) view.addSubview(cancelButton)
self.cancelButton = cancelButton self.cancelButton = cancelButton
// Display the Pass icon in the middle of the screen // Display the Pass icon in the middle of the screen
@ -94,49 +93,48 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
appIconView.translatesAutoresizingMaskIntoConstraints = false appIconView.translatesAutoresizingMaskIntoConstraints = false
appIconView.layer.cornerRadius = appIconSize / 5 appIconView.layer.cornerRadius = appIconSize / 5
appIconView.layer.masksToBounds = true appIconView.layer.masksToBounds = true
self.view?.addSubview(appIconView) view?.addSubview(appIconView)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
passcodeTextField.widthAnchor.constraint(equalToConstant: 250), passcodeTextField.widthAnchor.constraint(equalToConstant: 250),
passcodeTextField.heightAnchor.constraint(equalToConstant: 40), passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
passcodeTextField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), passcodeTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
passcodeTextField.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -20), passcodeTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20),
// above passocde // above passocde
appIconView.widthAnchor.constraint(equalToConstant: appIconSize), appIconView.widthAnchor.constraint(equalToConstant: appIconSize),
appIconView.heightAnchor.constraint(equalToConstant: appIconSize), appIconView.heightAnchor.constraint(equalToConstant: appIconSize),
appIconView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), appIconView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
appIconView.bottomAnchor.constraint(equalTo: passcodeTextField.topAnchor, constant: -appIconSize), appIconView.bottomAnchor.constraint(equalTo: passcodeTextField.topAnchor, constant: -appIconSize),
// below passcode // below passcode
biometryAuthButton.widthAnchor.constraint(equalToConstant: 250), biometryAuthButton.widthAnchor.constraint(equalToConstant: 250),
biometryAuthButton.heightAnchor.constraint(equalToConstant: 40), biometryAuthButton.heightAnchor.constraint(equalToConstant: 40),
biometryAuthButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), biometryAuthButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
biometryAuthButton.topAnchor.constraint(equalTo: passcodeTextField.bottomAnchor), biometryAuthButton.topAnchor.constraint(equalTo: passcodeTextField.bottomAnchor),
// cancel (top-left of the screen) // cancel (top-left of the screen)
cancelButton.widthAnchor.constraint(equalToConstant: 150), cancelButton.widthAnchor.constraint(equalToConstant: 150),
cancelButton.heightAnchor.constraint(equalToConstant: 40), cancelButton.heightAnchor.constraint(equalToConstant: 40),
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor), cancelButton.topAnchor.constraint(equalTo: view.safeTopAnchor),
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20), cancelButton.leftAnchor.constraint(equalTo: view.safeLeftAnchor, constant: 20),
// bottom of the screen // bottom of the screen
forgotPasscodeButton.widthAnchor.constraint(equalToConstant: 250), forgotPasscodeButton.widthAnchor.constraint(equalToConstant: 250),
forgotPasscodeButton.heightAnchor.constraint(equalToConstant: 40), forgotPasscodeButton.heightAnchor.constraint(equalToConstant: 40),
forgotPasscodeButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), forgotPasscodeButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
forgotPasscodeButton.bottomAnchor.constraint(equalTo: self.view.safeBottomAnchor, constant: -40) forgotPasscodeButton.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: -40),
]) ])
// dismiss keyboard when tapping anywhere // dismiss keyboard when tapping anywhere
let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing)) let tap = UITapGestureRecognizer(target: view, action: #selector(UIView.endEditing))
view.addGestureRecognizer(tap) view.addGestureRecognizer(tap)
} }
open override func viewDidLoad() { override open func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
} }
open override func viewDidAppear(_ animated: Bool) { override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
if let biometryAuthButton = biometryAuthButton { if let biometryAuthButton = biometryAuthButton {
self.bioButtonPressedAction(biometryAuthButton) bioButtonPressedAction(biometryAuthButton)
} }
} }
@ -167,16 +165,18 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
dismissPasscodeLock(completionHandler: successCallback) dismissPasscodeLock(completionHandler: successCallback)
} }
@objc func passcodeLockDidCancel() { @objc
func passcodeLockDidCancel() {
dismissPasscodeLock(completionHandler: cancelCallback) dismissPasscodeLock(completionHandler: cancelCallback)
} }
@objc func bioButtonPressedAction(_ uiButton: UIButton) { @objc
func bioButtonPressedAction(_: UIButton) {
let myContext = LAContext() let myContext = LAContext()
let myLocalizedReasonString = "AuthenticationNeeded.".localize() let myLocalizedReasonString = "AuthenticationNeeded.".localize()
var authError: NSError? var authError: NSError?
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) { if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, _ in
if success { if success {
DispatchQueue.main.async { DispatchQueue.main.async {
// user authenticated successfully, take appropriate action // user authenticated successfully, take appropriate action
@ -187,9 +187,10 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
} }
} }
@objc func forgotPasscodeButtonPressedAction(_ uiButton: UIButton) { @objc
func forgotPasscodeButtonPressedAction(_: UIButton) {
let alert = UIAlertController(title: "ResetPass".localize(), message: "ResetPassExplanation.".localize(), preferredStyle: UIAlertController.Style.alert) let alert = UIAlertController(title: "ResetPass".localize(), message: "ResetPassExplanation.".localize(), preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive, handler: {[unowned self] (action) -> Void in alert.addAction(UIAlertAction(title: "ErasePasswordStoreData".localize(), style: UIAlertAction.Style.destructive, handler: { [unowned self] (_) -> Void in
let myContext = LAContext() let myContext = LAContext()
var error: NSError? var error: NSError?
// If the device passcode is not set, reset the app. // If the device passcode is not set, reset the app.
@ -199,7 +200,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
return return
} }
// If the device passcode is set, authentication is required. // If the device passcode is set, authentication is required.
myContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "ErasePasswordStoreData".localize()) { (success, error) in myContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "ErasePasswordStoreData".localize()) { success, error in
if success { if success {
DispatchQueue.main.async { DispatchQueue.main.async {
// User authenticated successfully, take appropriate action // User authenticated successfully, take appropriate action
@ -214,25 +215,26 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
} }
})) }))
alert.addAction(UIAlertAction.dismiss()) alert.addAction(UIAlertAction.dismiss())
self.present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} }
public override func textFieldShouldReturn(_ textField: UITextField) -> Bool { override public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == passcodeTextField { if textField == passcodeTextField {
if !PasscodeLock.shared.check(passcode: textField.text ?? "") { if !PasscodeLock.shared.check(passcode: textField.text ?? "") {
self.passcodeTextField?.placeholder = passcodeTextField?.placeholder =
"TryAgain".localize() "TryAgain".localize()
self.passcodeTextField?.text = "" passcodeTextField?.text = ""
self.passcodeTextField?.shake() passcodeTextField?.shake()
} }
} }
textField.resignFirstResponder() textField.resignFirstResponder()
return true return true
} }
@objc func passcodeTextFieldDidChange(_ textField: UITextField) { @objc
func passcodeTextFieldDidChange(_ textField: UITextField) {
if PasscodeLock.shared.check(passcode: textField.text ?? "") { if PasscodeLock.shared.check(passcode: textField.text ?? "") {
self.passcodeLockDidSucceed() passcodeLockDidSucceed()
} }
} }

View file

@ -7,7 +7,6 @@
// //
extension Array { extension Array {
func slices(count: UInt) -> [ArraySlice<Element>] { func slices(count: UInt) -> [ArraySlice<Element>] {
guard count != 0 else { guard count != 0 else {
return [] return []

View file

@ -8,18 +8,18 @@
extension String { extension String {
public func localize() -> String { public func localize() -> String {
return NSLocalizedString(self, value: "#\(self)#", comment: "") NSLocalizedString(self, value: "#\(self)#", comment: "")
} }
public func localize(_ firstValue: CVarArg) -> String { public func localize(_ firstValue: CVarArg) -> String {
return String(format: localize(), firstValue) String(format: localize(), firstValue)
} }
public func localize(_ firstValue: CVarArg, _ secondValue: CVarArg) -> String { public func localize(_ firstValue: CVarArg, _ secondValue: CVarArg) -> String {
return String(format: localize(), firstValue, secondValue) String(format: localize(), firstValue, secondValue)
} }
public func localize(_ error: Error) -> String { public func localize(_ error: Error) -> String {
return localize(error.localizedDescription) localize(error.localizedDescription)
} }
} }

View file

@ -8,7 +8,7 @@
extension String { extension String {
public var trimmed: String { public var trimmed: String {
return trimmingCharacters(in: .whitespacesAndNewlines) trimmingCharacters(in: .whitespacesAndNewlines)
} }
public func stringByAddingPercentEncodingForRFC3986() -> String? { public func stringByAddingPercentEncodingForRFC3986() -> String? {
@ -19,12 +19,12 @@ extension String {
} }
public func splitByNewline() -> [String] { public func splitByNewline() -> [String] {
return split(omittingEmptySubsequences: false) { $0 == "\n" || $0 == "\r\n" }.map(String.init) split(omittingEmptySubsequences: false) { $0 == "\n" || $0 == "\r\n" }.map(String.init)
} }
} }
extension String { extension String {
public static func | (left: String, right: String) -> String { public static func | (left: String, right: String) -> String {
return right.isEmpty ? left : left + "\n" + right right.isEmpty ? left : left + "\n" + right
} }
} }

View file

@ -6,12 +6,12 @@
// Copyright © 2020 Bob Sun. All rights reserved. // Copyright © 2020 Bob Sun. All rights reserved.
// //
import UIKit
import Foundation import Foundation
import UIKit
extension UIAlertAction { extension UIAlertAction {
public static func cancelAndPopView(controller: UIViewController) -> UIAlertAction { public static func cancelAndPopView(controller: UIViewController) -> UIAlertAction {
return cancel() { _ in cancel { _ in
controller.navigationController?.popViewController(animated: true) controller.navigationController?.popViewController(animated: true)
} }
} }
@ -24,7 +24,7 @@ extension UIAlertAction {
cancel(with: "Dismiss", handler: handler) cancel(with: "Dismiss", handler: handler)
} }
public static func cancel(with title: String, handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction { public static func cancel(with _: String, handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction {
UIAlertAction(title: "Cancel".localize(), style: .cancel, handler: handler) UIAlertAction(title: "Cancel".localize(), style: .cancel, handler: handler)
} }
@ -33,7 +33,7 @@ extension UIAlertAction {
} }
public static func okAndPopView(controller: UIViewController) -> UIAlertAction { public static func okAndPopView(controller: UIViewController) -> UIAlertAction {
return ok() { _ in ok { _ in
controller.navigationController?.popViewController(animated: true) controller.navigationController?.popViewController(animated: true)
} }
} }
@ -41,13 +41,12 @@ extension UIAlertAction {
public static func selectKey(controller: UIViewController, handler: ((UIAlertAction) -> Void)?) -> UIAlertAction { public static func selectKey(controller: UIViewController, handler: ((UIAlertAction) -> Void)?) -> UIAlertAction {
UIAlertAction(title: "Select Key", style: .default) { _ in UIAlertAction(title: "Select Key", style: .default) { _ in
let selectKeyAlert = UIAlertController(title: "Select from imported keys", message: nil, preferredStyle: .actionSheet) let selectKeyAlert = UIAlertController(title: "Select from imported keys", message: nil, preferredStyle: .actionSheet)
try? PGPAgent.shared.getShortKeyID().forEach({ k in try? PGPAgent.shared.getShortKeyID().forEach { k in
let action = UIAlertAction(title: k, style: .default, handler: handler) let action = UIAlertAction(title: k, style: .default, handler: handler)
selectKeyAlert.addAction(action) selectKeyAlert.addAction(action)
}) }
selectKeyAlert.addAction(UIAlertAction.cancelAndPopView(controller: controller)) selectKeyAlert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
controller.present(selectKeyAlert, animated: true, completion: nil) controller.present(selectKeyAlert, animated: true, completion: nil)
} }
} }
} }

View file

@ -14,7 +14,7 @@ private var kAssociationKeyNextField: UInt8 = 0
extension UITextField { extension UITextField {
@IBOutlet var nextField: UITextField? { @IBOutlet var nextField: UITextField? {
get { get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
} }
set(newField) { set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN) objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)

View file

@ -7,7 +7,8 @@
// //
extension UIViewController { extension UIViewController {
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { @objc
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField.nextField != nil { if textField.nextField != nil {
textField.nextField?.becomeFirstResponder() textField.nextField?.becomeFirstResponder()
} else { } else {

View file

@ -9,13 +9,12 @@
import Foundation import Foundation
extension UIView { extension UIView {
// Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically // Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically
var safeTopAnchor: NSLayoutYAxisAnchor { var safeTopAnchor: NSLayoutYAxisAnchor {
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide.topAnchor return self.safeAreaLayoutGuide.topAnchor
} else { } else {
return self.topAnchor return topAnchor
} }
} }
@ -23,7 +22,7 @@ extension UIView {
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide.leftAnchor return self.safeAreaLayoutGuide.leftAnchor
} else { } else {
return self.leftAnchor return leftAnchor
} }
} }
@ -31,7 +30,7 @@ extension UIView {
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide.rightAnchor return self.safeAreaLayoutGuide.rightAnchor
} else { } else {
return self.rightAnchor return rightAnchor
} }
} }
@ -39,7 +38,7 @@ extension UIView {
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide.bottomAnchor return self.safeAreaLayoutGuide.bottomAnchor
} else { } else {
return self.bottomAnchor return bottomAnchor
} }
} }
} }

View file

@ -9,7 +9,6 @@
import KeychainAccess import KeychainAccess
public class AppKeychain: KeyStore { public class AppKeychain: KeyStore {
public static let shared = AppKeychain() public static let shared = AppKeychain()
private let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier) private let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
@ -25,15 +24,15 @@ public class AppKeychain: KeyStore {
} }
public func contains(key: String) -> Bool { public func contains(key: String) -> Bool {
return (try? keychain.contains(key)) ?? false (try? keychain.contains(key)) ?? false
} }
public func get(for key: String) -> Data? { public func get(for key: String) -> Data? {
return try? keychain.getData(key) try? keychain.getData(key)
} }
public func get(for key: String) -> String? { public func get(for key: String) -> String? {
return try? keychain.getString(key) try? keychain.getString(key)
} }
public func removeContent(for key: String) { public func removeContent(for key: String) {

View file

@ -10,7 +10,6 @@ import Foundation
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516 // https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
public extension FileManager { public extension FileManager {
/// This method calculates the accumulated size of a directory on the volume in bytes. /// This method calculates the accumulated size of a directory on the volume in bytes.
/// ///
/// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy, /// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
@ -20,7 +19,6 @@ public extension FileManager {
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of /// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
/// directories, hard links, ...). /// directories, hard links, ...).
func allocatedSizeOfDirectoryAtURL(directoryURL: URL) throws -> UInt64 { func allocatedSizeOfDirectoryAtURL(directoryURL: URL) throws -> UInt64 {
// We'll sum up content size here: // We'll sum up content size here:
var accumulatedSize = UInt64(0) var accumulatedSize = UInt64(0)
@ -38,7 +36,6 @@ public extension FileManager {
return false return false
} }
// We have to enumerate all directory contents, including subdirectories. // We have to enumerate all directory contents, including subdirectories.
let enumerator = self.enumerator(at: directoryURL, let enumerator = self.enumerator(at: directoryURL,
includingPropertiesForKeys: prefetchedProperties, includingPropertiesForKeys: prefetchedProperties,
@ -91,4 +88,3 @@ public extension FileManager {
return accumulatedSize return accumulatedSize
} }
} }

View file

@ -48,9 +48,10 @@ public final class Globals {
public extension Bundle { public extension Bundle {
var releaseVersionNumber: String? { var releaseVersionNumber: String? {
return infoDictionary?["CFBundleShortVersionString"] as? String infoDictionary?["CFBundleShortVersionString"] as? String
} }
var buildVersionNumber: String? { var buildVersionNumber: String? {
return infoDictionary?["CFBundleVersion"] as? String infoDictionary?["CFBundleVersion"] as? String
} }
} }

View file

@ -45,6 +45,6 @@ public class KeyFileManager {
} }
public func doesKeyFileExist() -> Bool { public func doesKeyFileExist() -> Bool {
return FileManager.default.fileExists(atPath: keyPath) FileManager.default.fileExists(atPath: keyPath)
} }
} }

View file

@ -7,7 +7,6 @@
// //
public class Utils { public class Utils {
public static func copyToPasteboard(textToCopy: String?) { public static func copyToPasteboard(textToCopy: String?) {
guard textToCopy != nil else { guard textToCopy != nil else {
return return
@ -16,7 +15,7 @@ public class Utils {
} }
public static func attributedPassword(plainPassword: String) -> NSAttributedString { public static func attributedPassword(plainPassword: String) -> NSAttributedString {
let attributedPassword = NSMutableAttributedString.init(string: plainPassword) let attributedPassword = NSMutableAttributedString(string: plainPassword)
// draw all digits in the password into red // draw all digits in the password into red
// draw all punctuation characters in the password into blue // draw all punctuation characters in the password into blue
for (index, element) in plainPassword.unicodeScalars.enumerated() { for (index, element) in plainPassword.unicodeScalars.enumerated() {
@ -40,24 +39,24 @@ public class Utils {
} }
public static func createRequestPGPKeyPassphraseHandler(controller: UIViewController) -> (String) -> String { public static func createRequestPGPKeyPassphraseHandler(controller: UIViewController) -> (String) -> String {
return { keyID in { keyID in
let sem = DispatchSemaphore(value: 0) let sem = DispatchSemaphore(value: 0)
var passphrase = "" var passphrase = ""
DispatchQueue.main.async { DispatchQueue.main.async {
let title = "Passphrase".localize() + " (\(keyID.suffix(8)))" let title = "Passphrase".localize() + " (\(keyID.suffix(8)))"
let message = "FillInPgpPassphrase.".localize() let message = "FillInPgpPassphrase.".localize()
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction.ok() { _ in alert.addAction(UIAlertAction.ok { _ in
passphrase = alert.textFields?.first?.text ?? "" passphrase = alert.textFields?.first?.text ?? ""
sem.signal() sem.signal()
}) })
alert.addTextField() { textField in alert.addTextField { textField in
textField.text = AppKeychain.shared.get(for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID)) ?? "" textField.text = AppKeychain.shared.get(for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID)) ?? ""
textField.isSecureTextEntry = true textField.isSecureTextEntry = true
} }
controller.present(alert, animated: true) controller.present(alert, animated: true)
} }
let _ = sem.wait(timeout: DispatchTime.distantFuture) _ = sem.wait(timeout: DispatchTime.distantFuture)
if Defaults.isRememberPGPPassphraseOn { if Defaults.isRememberPGPPassphraseOn {
AppKeychain.shared.add(string: passphrase, for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID)) AppKeychain.shared.add(string: passphrase, for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID))
} }
@ -65,4 +64,3 @@ public class Utils {
} }
} }
} }

View file

@ -25,7 +25,7 @@ public struct GitCredential {
public func credentialProvider(requestCredentialPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider { public func credentialProvider(requestCredentialPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider {
var attempts = 0 var attempts = 0
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
var credential: GTCredential? = nil var credential: GTCredential?
switch self.credential { switch self.credential {
case let .http(userName): case let .http(userName):
@ -72,10 +72,9 @@ public struct GitCredential {
public func delete() { public func delete() {
switch credential { switch credential {
case .http: case .http:
self.passwordStore.gitPassword = nil passwordStore.gitPassword = nil
case .ssh: case .ssh:
self.passwordStore.gitSSHPrivateKeyPassphrase = nil passwordStore.gitSSHPrivateKeyPassphrase = nil
} }
} }
} }

View file

@ -23,7 +23,7 @@ public class PasscodeLock {
} }
public var hasPasscode: Bool { public var hasPasscode: Bool {
return passcode != nil passcode != nil
} }
public func save(passcode: String) { public func save(passcode: String) {
@ -32,7 +32,7 @@ public class PasscodeLock {
} }
public func check(passcode: String) -> Bool { public func check(passcode: String) -> Bool {
return self.passcode == passcode self.passcode == passcode
} }
public func delete() { public func delete() {

View file

@ -6,11 +6,10 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import OneTimePassword
import Base32 import Base32
import OneTimePassword
public class Password { public class Password {
public var name: String public var name: String
public var url: URL public var url: URL
public var plainText: String public var plainText: String
@ -29,47 +28,47 @@ public class Password {
} }
public var namePath: String { public var namePath: String {
return url.deletingPathExtension().path url.deletingPathExtension().path
} }
public var nameFromPath: String? { public var nameFromPath: String? {
return url.deletingPathExtension().path.split(separator: "/").last.map { String($0) } url.deletingPathExtension().path.split(separator: "/").last.map { String($0) }
} }
public var password: String { public var password: String {
return parser.firstLine parser.firstLine
} }
public var plainData: Data { public var plainData: Data {
return plainText.data(using: .utf8)! plainText.data(using: .utf8)!
} }
public var additionsPlainText: String { public var additionsPlainText: String {
return parser.additionsSection parser.additionsSection
} }
public var username: String? { public var username: String? {
return getAdditionValue(withKey: Constants.USERNAME_KEYWORD) getAdditionValue(withKey: Constants.USERNAME_KEYWORD)
} }
public var login: String? { public var login: String? {
return getAdditionValue(withKey: Constants.LOGIN_KEYWORD) getAdditionValue(withKey: Constants.LOGIN_KEYWORD)
} }
public var urlString: String? { public var urlString: String? {
return getAdditionValue(withKey: Constants.URL_KEYWORD) getAdditionValue(withKey: Constants.URL_KEYWORD)
} }
public var currentOtp: String? { public var currentOtp: String? {
return otpToken?.currentPassword otpToken?.currentPassword
} }
public var numberOfUnknowns: Int { public var numberOfUnknowns: Int {
return additions.map { $0.title }.filter(Constants.isUnknown).count additions.map(\.title).filter(Constants.isUnknown).count
} }
public var numberOfOtpRelated: Int { public var numberOfOtpRelated: Int {
return additions.map { $0.title }.filter(Constants.isOtpKeyword).count - (firstLineIsOTPField ? 1 : 0) additions.map(\.title).filter(Constants.isOtpKeyword).count - (firstLineIsOTPField ? 1 : 0)
} }
public init(name: String, url: URL, plainText: String) { public init(name: String, url: URL, plainText: String) {
@ -119,7 +118,7 @@ public class Password {
} }
public func getFilteredAdditions() -> [AdditionField] { public func getFilteredAdditions() -> [AdditionField] {
return additions.filter { field in additions.filter { field in
let title = field.title.lowercased() let title = field.title.lowercased()
return title != Constants.USERNAME_KEYWORD return title != Constants.USERNAME_KEYWORD
&& title != Constants.LOGIN_KEYWORD && title != Constants.LOGIN_KEYWORD
@ -195,11 +194,11 @@ public class Password {
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret)) newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
var lines: [String] = [] var lines: [String] = []
self.plainText.enumerateLines() { line, _ in plainText.enumerateLines { line, _ in
let (key, _) = Parser.getKeyValuePair(from: line) let (key, _) = Parser.getKeyValuePair(from: line)
if !Constants.OTP_KEYWORDS.contains(key ?? "") { if !Constants.OTP_KEYWORDS.contains(key ?? "") {
lines.append(line) lines.append(line)
} else if key == Constants.OTPAUTH && newOtpauth != nil { } else if key == Constants.OTPAUTH, newOtpauth != nil {
lines.append(newOtpauth!) lines.append(newOtpauth!)
// set to nil to prevent duplication // set to nil to prevent duplication
newOtpauth = nil newOtpauth = nil
@ -208,10 +207,10 @@ public class Password {
if newOtpauth != nil { if newOtpauth != nil {
lines.append(newOtpauth!) lines.append(newOtpauth!)
} }
self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n")) updatePassword(name: name, url: url, plainText: lines.joined(separator: "\n"))
// get and return the password // get and return the password
return self.otpToken?.currentPassword return otpToken?.currentPassword
} }
public func getUsernameForCompletion() -> String { public func getUsernameForCompletion() -> String {

View file

@ -10,7 +10,6 @@ import Foundation
import SwiftyUserDefaults import SwiftyUserDefaults
extension PasswordEntity { extension PasswordEntity {
public var nameWithCategory: String { public var nameWithCategory: String {
if let p = path, p.hasSuffix(".gpg") { if let p = path, p.hasSuffix(".gpg") {
return String(p.prefix(upTo: p.index(p.endIndex, offsetBy: -4))) return String(p.prefix(upTo: p.index(p.endIndex, offsetBy: -4)))
@ -19,7 +18,7 @@ extension PasswordEntity {
} }
public func getCategoryText() -> String { public func getCategoryText() -> String {
return getCategoryArray().joined(separator: " > ") getCategoryArray().joined(separator: " > ")
} }
public func getCategoryArray() -> [String] { public func getCategoryArray() -> [String] {
@ -44,17 +43,16 @@ extension PasswordEntity {
// manually write models instead auto generation. // manually write models instead auto generation.
public func getImage() -> Data? { public func getImage() -> Data? {
return image image
} }
public func getName() -> String { public func getName() -> String {
// unwrap non-optional core data // unwrap non-optional core data
return name ?? "" name ?? ""
} }
public func getPath() -> String { public func getPath() -> String {
// unwrap non-optional core data // unwrap non-optional core data
return path ?? "" path ?? ""
} }
} }

View file

@ -6,12 +6,12 @@
// Copyright © 2017 Bob Sun. All rights reserved. // Copyright © 2017 Bob Sun. All rights reserved.
// //
import Foundation
import CoreData import CoreData
import UIKit import Foundation
import SwiftyUserDefaults
import ObjectiveGit
import KeychainAccess import KeychainAccess
import ObjectiveGit
import SwiftyUserDefaults
import UIKit
public class PasswordStore { public class PasswordStore {
public static let shared = PasswordStore() public static let shared = PasswordStore()
@ -21,29 +21,26 @@ public class PasswordStore {
dateFormatter.timeStyle = .short dateFormatter.timeStyle = .short
return dateFormatter return dateFormatter
}() }()
public var storeURL: URL public var storeURL: URL
public var tempStoreURL: URL { public var tempStoreURL: URL {
get {
URL(fileURLWithPath: "\(storeURL.path)-temp") URL(fileURLWithPath: "\(storeURL.path)-temp")
} }
}
public var storeRepository: GTRepository? public var storeRepository: GTRepository?
public var gitSignatureForNow: GTSignature? { public var gitSignatureForNow: GTSignature? {
get {
let gitSignatureName = Defaults.gitSignatureName ?? Globals.gitSignatureDefaultName let gitSignatureName = Defaults.gitSignatureName ?? Globals.gitSignatureDefaultName
let gitSignatureEmail = Defaults.gitSignatureEmail ?? Globals.gitSignatureDefaultEmail let gitSignatureEmail = Defaults.gitSignatureEmail ?? Globals.gitSignatureDefaultEmail
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date()) return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())
} }
}
public var gitPassword: String? { public var gitPassword: String? {
set { set {
AppKeychain.shared.add(string: newValue, for: Globals.gitPassword) AppKeychain.shared.add(string: newValue, for: Globals.gitPassword)
} }
get { get {
return AppKeychain.shared.get(for: Globals.gitPassword) AppKeychain.shared.get(for: Globals.gitPassword)
} }
} }
@ -52,12 +49,12 @@ public class PasswordStore {
AppKeychain.shared.add(string: newValue, for: Globals.gitSSHPrivateKeyPassphrase) AppKeychain.shared.add(string: newValue, for: Globals.gitSSHPrivateKeyPassphrase)
} }
get { get {
return AppKeychain.shared.get(for: Globals.gitSSHPrivateKeyPassphrase) AppKeychain.shared.get(for: Globals.gitSSHPrivateKeyPassphrase)
} }
} }
private let fm = FileManager.default private let fm = FileManager.default
lazy private var context: NSManagedObjectContext = { private lazy var context: NSManagedObjectContext = {
let modelURL = Bundle(identifier: Globals.passKitBundleIdentifier)!.url(forResource: "pass", withExtension: "momd")! let modelURL = Bundle(identifier: Globals.passKitBundleIdentifier)!.url(forResource: "pass", withExtension: "momd")!
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
let container = NSPersistentContainer(name: "pass", managedObjectModel: managedObjectModel!) let container = NSPersistentContainer(name: "pass", managedObjectModel: managedObjectModel!)
@ -65,7 +62,7 @@ public class PasswordStore {
try! FileManager.default.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil) try! FileManager.default.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil)
} }
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: URL(fileURLWithPath: Globals.dbPath))] container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: URL(fileURLWithPath: Globals.dbPath))]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in container.loadPersistentStores(completionHandler: { _, error in
if let error = error as NSError? { if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately. // Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
@ -85,34 +82,34 @@ public class PasswordStore {
}() }()
public var numberOfPasswords: Int { public var numberOfPasswords: Int {
return self.fetchPasswordEntityCoreData(withDir: false).count fetchPasswordEntityCoreData(withDir: false).count
} }
public var sizeOfRepositoryByteCount: UInt64 { public var sizeOfRepositoryByteCount: UInt64 {
return (try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)) ?? 0 (try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: storeURL)) ?? 0
} }
public var numberOfLocalCommits: Int { public var numberOfLocalCommits: Int {
return (try? getLocalCommits())?.flatMap { $0.count } ?? 0 (try? getLocalCommits()).map(\.count) ?? 0
} }
public var lastSyncedTime: Date? { public var lastSyncedTime: Date? {
return Defaults.lastSyncedTime Defaults.lastSyncedTime
} }
public var numberOfCommits: UInt? { public var numberOfCommits: UInt? {
return storeRepository?.numberOfCommits(inCurrentBranch: nil) storeRepository?.numberOfCommits(inCurrentBranch: nil)
} }
init(url: URL = URL(fileURLWithPath: "\(Globals.repositoryPath)")) { init(url: URL = URL(fileURLWithPath: "\(Globals.repositoryPath)")) {
storeURL = url self.storeURL = url
// Migration // Migration
importExistingKeysIntoKeychain() importExistingKeysIntoKeychain()
do { do {
if fm.fileExists(atPath: storeURL.path) { if fm.fileExists(atPath: storeURL.path) {
try storeRepository = GTRepository.init(url: storeURL) try self.storeRepository = GTRepository(url: storeURL)
} }
} catch { } catch {
print(error) print(error)
@ -183,7 +180,7 @@ public class PasswordStore {
do { do {
let credentialProvider = try credential.credentialProvider(requestCredentialPassword: requestCredentialPassword) let credentialProvider = try credential.credentialProvider(requestCredentialPassword: requestCredentialPassword)
let options = [GTRepositoryCloneOptionsCredentialProvider: credentialProvider] let options = [GTRepositoryCloneOptionsCredentialProvider: credentialProvider]
try self.cloneRepository(remoteRepoURL: remoteRepoURL, options: options, branchName: branchName, transferProgressBlock: transferProgressBlock, checkoutProgressBlock: checkoutProgressBlock) try cloneRepository(remoteRepoURL: remoteRepoURL, options: options, branchName: branchName, transferProgressBlock: transferProgressBlock, checkoutProgressBlock: checkoutProgressBlock)
} catch { } catch {
credential.delete() credential.delete()
throw (error) throw (error)
@ -198,8 +195,8 @@ public class PasswordStore {
completion: @escaping () -> Void = {}) throws { completion: @escaping () -> Void = {}) throws {
try? fm.removeItem(at: storeURL) try? fm.removeItem(at: storeURL)
try? fm.removeItem(at: tempStoreURL) try? fm.removeItem(at: tempStoreURL)
self.gitPassword = nil gitPassword = nil
self.gitSSHPrivateKeyPassphrase = nil gitSSHPrivateKeyPassphrase = nil
do { do {
storeRepository = try GTRepository.clone(from: remoteRepoURL, storeRepository = try GTRepository.clone(from: remoteRepoURL,
toWorkingDirectory: tempStoreURL, toWorkingDirectory: tempStoreURL,
@ -253,7 +250,7 @@ public class PasswordStore {
let remote = try GTRemote(name: "origin", in: storeRepository) let remote = try GTRemote(name: "origin", in: storeRepository)
try storeRepository.pull(storeRepository.currentBranch(), from: remote, withOptions: options, progress: progressBlock) try storeRepository.pull(storeRepository.currentBranch(), from: remote, withOptions: options, progress: progressBlock)
Defaults.lastSyncedTime = Date() Defaults.lastSyncedTime = Date()
self.setAllSynced() setAllSynced()
DispatchQueue.main.async { DispatchQueue.main.async {
self.updatePasswordEntityCoreData() self.updatePasswordEntityCoreData()
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
@ -263,7 +260,7 @@ public class PasswordStore {
private func updatePasswordEntityCoreData() { private func updatePasswordEntityCoreData() {
deleteCoreData(entityName: "PasswordEntity") deleteCoreData(entityName: "PasswordEntity")
do { do {
var q = try fm.contentsOfDirectory(atPath: self.storeURL.path).filter { var q = try fm.contentsOfDirectory(atPath: storeURL.path).filter {
!$0.hasPrefix(".") !$0.hasPrefix(".")
}.map { (filename) -> PasswordEntity in }.map { (filename) -> PasswordEntity in
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
@ -276,7 +273,7 @@ public class PasswordStore {
passwordEntity.parent = nil passwordEntity.parent = nil
return passwordEntity return passwordEntity
} }
while q.count > 0 { while !q.isEmpty {
let e = q.first! let e = q.first!
q.remove(at: 0) q.remove(at: 0)
guard !e.name!.hasPrefix(".") else { guard !e.name!.hasPrefix(".") else {
@ -309,7 +306,7 @@ public class PasswordStore {
} catch { } catch {
print(error) print(error)
} }
self.saveUpdatedContext() saveUpdatedContext()
} }
public func getRecentCommits(count: Int) throws -> [GTCommit] { public func getRecentCommits(count: Int) throws -> [GTCommit] {
@ -353,7 +350,6 @@ public class PasswordStore {
} }
} }
public func fetchUnsyncedPasswords() -> [PasswordEntity] { public func fetchUnsyncedPasswords() -> [PasswordEntity] {
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity") let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0) passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
@ -367,11 +363,11 @@ public class PasswordStore {
public func setAllSynced() { public func setAllSynced() {
let passwordEntities = fetchUnsyncedPasswords() let passwordEntities = fetchUnsyncedPasswords()
if passwordEntities.count > 0 { if !passwordEntities.isEmpty {
for passwordEntity in passwordEntities { for passwordEntity in passwordEntities {
passwordEntity.synced = true passwordEntity.synced = true
} }
self.saveUpdatedContext() saveUpdatedContext()
} }
} }
@ -392,8 +388,7 @@ public class PasswordStore {
return PasswordStore.dateFormatter.string(from: lastCommitDate) return PasswordStore.dateFormatter.string(from: lastCommitDate)
} }
public func updateRemoteRepo() { public func updateRemoteRepo() {}
}
private func gitAdd(path: String) throws { private func gitAdd(path: String) throws {
guard let storeRepository = storeRepository else { guard let storeRepository = storeRepository else {
@ -490,20 +485,20 @@ public class PasswordStore {
paths.append(passwordURL.path) paths.append(passwordURL.path)
passwordURL = passwordURL.deletingLastPathComponent() passwordURL = passwordURL.deletingLastPathComponent()
// better identify errors before saving a new password // better identify errors before saving a new password
if passwordURL.path != "." && passwordURL.path.count >= previousPathLength { if passwordURL.path != ".", passwordURL.path.count >= previousPathLength {
throw AppError.WrongPasswordFilename throw AppError.WrongPasswordFilename
} }
previousPathLength = passwordURL.path.count previousPathLength = passwordURL.path.count
} }
paths.reverse() paths.reverse()
var parentPasswordEntity: PasswordEntity? = nil var parentPasswordEntity: PasswordEntity?
for path in paths { for path in paths {
let isDir = !path.hasSuffix(".gpg") let isDir = !path.hasSuffix(".gpg")
if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) { if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) {
passwordEntity.synced = false passwordEntity.synced = false
parentPasswordEntity = passwordEntity parentPasswordEntity = passwordEntity
} else { } else {
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as! PasswordEntity let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
let pathURL = URL(string: path.stringByAddingPercentEncodingForRFC3986()!)! let pathURL = URL(string: path.stringByAddingPercentEncodingForRFC3986()!)!
if isDir { if isDir {
passwordEntity.name = pathURL.lastPathComponent passwordEntity.name = pathURL.lastPathComponent
@ -518,16 +513,16 @@ public class PasswordStore {
} }
} }
self.saveUpdatedContext() saveUpdatedContext()
return parentPasswordEntity return parentPasswordEntity
} }
public func add(password: Password, keyID: String? = nil) throws -> PasswordEntity? { public func add(password: Password, keyID: String? = nil) throws -> PasswordEntity? {
try createDirectoryTree(at: password.url) try createDirectoryTree(at: password.url)
let saveURL = storeURL.appendingPathComponent(password.url.path) let saveURL = storeURL.appendingPathComponent(password.url.path)
try self.encrypt(password: password, keyID: keyID).write(to: saveURL) try encrypt(password: password, keyID: keyID).write(to: saveURL)
try gitAdd(path: password.url.path) try gitAdd(path: password.url.path)
let _ = try gitCommit(message: "AddPassword.".localize(password.url.deletingPathExtension().path)) _ = try gitCommit(message: "AddPassword.".localize(password.url.deletingPathExtension().path))
let newPasswordEntity = try addPasswordEntities(password: password) let newPasswordEntity = try addPasswordEntities(password: password)
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
return newPasswordEntity return newPasswordEntity
@ -538,7 +533,7 @@ public class PasswordStore {
try gitRm(path: deletedFileURL.path) try gitRm(path: deletedFileURL.path)
try deletePasswordEntities(passwordEntity: passwordEntity) try deletePasswordEntities(passwordEntity: passwordEntity)
try deleteDirectoryTree(at: deletedFileURL) try deleteDirectoryTree(at: deletedFileURL)
let _ = try gitCommit(message: "RemovePassword.".localize(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!)) _ = try gitCommit(message: "RemovePassword.".localize(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!))
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
} }
@ -548,12 +543,12 @@ public class PasswordStore {
if password.changed & PasswordChange.content.rawValue != 0 { if password.changed & PasswordChange.content.rawValue != 0 {
let saveURL = storeURL.appendingPathComponent(url.path) let saveURL = storeURL.appendingPathComponent(url.path)
try self.encrypt(password: password, keyID: keyID).write(to: saveURL) try encrypt(password: password, keyID: keyID).write(to: saveURL)
try gitAdd(path: url.path) try gitAdd(path: url.path)
let _ = try gitCommit(message: "EditPassword.".localize(url.deletingPathExtension().path.removingPercentEncoding!)) _ = try gitCommit(message: "EditPassword.".localize(url.deletingPathExtension().path.removingPercentEncoding!))
newPasswordEntity = passwordEntity newPasswordEntity = passwordEntity
newPasswordEntity?.synced = false newPasswordEntity?.synced = false
self.saveUpdatedContext() saveUpdatedContext()
} }
if password.changed & PasswordChange.path.rawValue != 0 { if password.changed & PasswordChange.path.rawValue != 0 {
@ -568,7 +563,7 @@ public class PasswordStore {
// delete // delete
try deleteDirectoryTree(at: deletedFileURL) try deleteDirectoryTree(at: deletedFileURL)
try deletePasswordEntities(passwordEntity: passwordEntity) try deletePasswordEntities(passwordEntity: passwordEntity)
let _ = try gitCommit(message: "RenamePassword.".localize(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!, password.url.deletingPathExtension().path.removingPercentEncoding!)) _ = try gitCommit(message: "RenamePassword.".localize(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!, password.url.deletingPathExtension().path.removingPercentEncoding!))
} }
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
return newPasswordEntity return newPasswordEntity
@ -576,12 +571,13 @@ public class PasswordStore {
private func deletePasswordEntities(passwordEntity: PasswordEntity) throws { private func deletePasswordEntities(passwordEntity: PasswordEntity) throws {
var current: PasswordEntity? = passwordEntity var current: PasswordEntity? = passwordEntity
while current != nil && (current!.children!.count == 0 || !current!.isDir) { // swiftformat:disable:next isEmpty
while current != nil, current!.children!.count == 0 || !current!.isDir {
let parent = current!.parent let parent = current!.parent
self.context.delete(current!) context.delete(current!)
current = parent current = parent
} }
self.saveUpdatedContext() saveUpdatedContext()
} }
public func saveUpdatedContext() { public func saveUpdatedContext() {
@ -659,7 +655,7 @@ public class PasswordStore {
} }
// get a list of local commits // get a list of local commits
if let localCommits = try getLocalCommits(), if let localCommits = try getLocalCommits(),
localCommits.count > 0 { !localCommits.isEmpty {
// get the oldest local commit // get the oldest local commit
guard let firstLocalCommit = localCommits.last, guard let firstLocalCommit = localCommits.last,
firstLocalCommit.parents.count == 1, firstLocalCommit.parents.count == 1,
@ -667,8 +663,8 @@ public class PasswordStore {
throw AppError.GitReset throw AppError.GitReset
} }
try storeRepository.reset(to: newHead, resetType: .hard) try storeRepository.reset(to: newHead, resetType: .hard)
self.setAllSynced() setAllSynced()
self.updatePasswordEntityCoreData() updatePasswordEntityCoreData()
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil) NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
@ -678,7 +674,6 @@ public class PasswordStore {
} }
} }
private func getLocalCommits() throws -> [GTCommit]? { private func getLocalCommits() throws -> [GTCommit]? {
guard let storeRepository = storeRepository else { guard let storeRepository = storeRepository else {
throw AppError.RepositoryNotSet throw AppError.RepositoryNotSet
@ -727,8 +722,8 @@ public class PasswordStore {
public func findGPGID(from url: URL) -> String { public func findGPGID(from url: URL) -> String {
var path = url var path = url
while !FileManager.default.fileExists(atPath: path.appendingPathComponent(".gpg-id").path) while !FileManager.default.fileExists(atPath: path.appendingPathComponent(".gpg-id").path),
&& path.path != "file:///" { path.path != "file:///" {
path = path.deletingLastPathComponent() path = path.deletingLastPathComponent()
} }
path = path.appendingPathComponent(".gpg-id") path = path.appendingPathComponent(".gpg-id")

View file

@ -24,16 +24,16 @@ public class PasswordTableEntry: NSObject {
} }
public func match(_ searchText: String) -> Bool { public func match(_ searchText: String) -> Bool {
return PasswordTableEntry.match(nameWithCategory: passwordEntity.nameWithCategory, searchText: searchText) PasswordTableEntry.match(nameWithCategory: passwordEntity.nameWithCategory, searchText: searchText)
} }
public static func match(nameWithCategory: String, searchText: String) -> Bool { public static func match(nameWithCategory: String, searchText: String) -> Bool {
let titleSplit = nameWithCategory.split { !($0.isLetter || $0.isNumber || $0 == ".") } let titleSplit = nameWithCategory.split { !($0.isLetter || $0.isNumber || $0 == ".") }
for str in titleSplit { for str in titleSplit {
if (str.localizedCaseInsensitiveContains(searchText)) { if str.localizedCaseInsensitiveContains(searchText) {
return true return true
} }
if (searchText.localizedCaseInsensitiveContains(str)) { if searchText.localizedCaseInsensitiveContains(str) {
return true return true
} }
} }
@ -41,4 +41,3 @@ public class PasswordTableEntry: NSObject {
return false return false
} }
} }

View file

@ -7,7 +7,6 @@
// //
public struct AdditionField: Hashable { public struct AdditionField: Hashable {
public let title: String, content: String public let title: String, content: String
public init(title: String = "", content: String = "") { public init(title: String = "", content: String = "") {
@ -16,7 +15,7 @@ public struct AdditionField: Hashable {
} }
var asString: String { var asString: String {
return title.isEmpty ? content : title + ": " + content title.isEmpty ? content : title + ": " + content
} }
var asTuple: (String, String) { var asTuple: (String, String) {
@ -25,21 +24,20 @@ public struct AdditionField: Hashable {
} }
extension AdditionField { extension AdditionField {
static func | (left: String, right: AdditionField) -> String { static func | (left: String, right: AdditionField) -> String {
return left | right.asString left | right.asString
} }
static func | (left: AdditionField, right: String) -> String { static func | (left: AdditionField, right: String) -> String {
return left.asString | right left.asString | right
} }
static func | (left: AdditionField, right: AdditionField) -> String { static func | (left: AdditionField, right: AdditionField) -> String {
return left.asString | right left.asString | right
} }
} }
infix operator =>: MultiplicationPrecedence infix operator =>: MultiplicationPrecedence
public func => (key: String, value: String) -> AdditionField { public func => (key: String, value: String) -> AdditionField {
return AdditionField(title: key, content: value) AdditionField(title: key, content: value)
} }

View file

@ -7,7 +7,6 @@
// //
public struct Constants { public struct Constants {
static let OTP_SECRET = "otp_secret" static let OTP_SECRET = "otp_secret"
static let OTP_TYPE = "otp_type" static let OTP_TYPE = "otp_type"
static let OTP_ALGORITHM = "otp_algorithm" static let OTP_ALGORITHM = "otp_algorithm"
@ -54,7 +53,7 @@ public struct Constants {
} }
static func isOtpKeyword(_ keyword: String) -> Bool { static func isOtpKeyword(_ keyword: String) -> Bool {
return OTP_KEYWORDS.contains(keyword.lowercased()) OTP_KEYWORDS.contains(keyword.lowercased())
} }
static func isUnknown(_ string: String) -> Bool { static func isUnknown(_ string: String) -> Bool {
@ -63,10 +62,10 @@ public struct Constants {
} }
static func unknown(_ number: UInt) -> String { static func unknown(_ number: UInt) -> String {
return "\(UNKNOWN) \(number)" "\(UNKNOWN) \(number)"
} }
static func getSeparator(breakingLines: Bool) -> String { static func getSeparator(breakingLines: Bool) -> String {
return breakingLines ? MULTILINE_WITH_LINE_BREAK_SEPARATOR : MULTILINE_WITHOUT_LINE_BREAK_SEPARATOR breakingLines ? MULTILINE_WITH_LINE_BREAK_SEPARATOR : MULTILINE_WITHOUT_LINE_BREAK_SEPARATOR
} }
} }

View file

@ -14,7 +14,7 @@ public enum OTPType: String {
case none = "None" case none = "None"
var description: String { var description: String {
return rawValue.localize() rawValue.localize()
} }
init(token: Token?) { init(token: Token?) {

View file

@ -7,7 +7,6 @@
// //
class Parser { class Parser {
let firstLine: String let firstLine: String
let additionsSection: String let additionsSection: String
let purgedAdditionalLines: [String] let purgedAdditionalLines: [String]
@ -19,8 +18,8 @@ class Parser {
let splittedPlainText = plainText.splitByNewline() let splittedPlainText = plainText.splitByNewline()
firstLine = splittedPlainText.first! firstLine = splittedPlainText.first!
additionsSection = splittedPlainText[1...].joined(separator: "\n") self.additionsSection = splittedPlainText[1...].joined(separator: "\n")
purgedAdditionalLines = splittedPlainText[1...].filter { !$0.isEmpty } self.purgedAdditionalLines = splittedPlainText[1...].filter { !$0.isEmpty }
} }
private func getAdditionFields() -> [AdditionField] { private func getAdditionFields() -> [AdditionField] {
@ -57,7 +56,7 @@ class Parser {
} }
let initialBlanks = String(repeating: Constants.BLANK, count: numberInitialBlanks) let initialBlanks = String(repeating: Constants.BLANK, count: numberInitialBlanks)
while i < purgedAdditionalLines.count && purgedAdditionalLines[i].starts(with: initialBlanks) { while i < purgedAdditionalLines.count, purgedAdditionalLines[i].starts(with: initialBlanks) {
result.append(String(purgedAdditionalLines[i].dropFirst(numberInitialBlanks))) result.append(String(purgedAdditionalLines[i].dropFirst(numberInitialBlanks)))
result.append(Constants.getSeparator(breakingLines: !removingLineBreaks)) result.append(Constants.getSeparator(breakingLines: !removingLineBreaks))
i += 1 i += 1

View file

@ -27,7 +27,6 @@ import OneTimePassword
/// * digits: `6` (default: `6`, optional) /// * digits: `6` (default: `6`, optional)
/// ///
class TokenBuilder { class TokenBuilder {
private var name: String = "" private var name: String = ""
private var secret: Data? private var secret: Data?
private var type: OTPType = .totp private var type: OTPType = .totp
@ -80,7 +79,6 @@ class TokenBuilder {
return self return self
} }
func build() -> Token? { func build() -> Token? {
guard secret != nil, digits != nil else { guard secret != nil, digits != nil else {
return nil return nil

View file

@ -7,7 +7,6 @@
// //
public struct PasswordGenerator: Codable { public struct PasswordGenerator: Codable {
private static let digits = "0123456789" private static let digits = "0123456789"
private static let letters = "abcdefghijklmnopqrstuvwxyz" private static let letters = "abcdefghijklmnopqrstuvwxyz"
private static let capitalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" private static let capitalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@ -114,7 +113,7 @@ public struct PasswordGenerator: Codable {
} }
private func selectRandomly(count: Int, from string: String) -> [Character] { private func selectRandomly(count: Int, from string: String) -> [Character] {
return (0 ..< count).map { _ in string.randomElement()! } (0 ..< count).map { _ in string.randomElement()! }
} }
} }

View file

@ -13,7 +13,7 @@ public enum PasswordGeneratorFlavor: String {
case xkcd = "XKCD" case xkcd = "XKCD"
public var localized: String { public var localized: String {
return rawValue.localize() rawValue.localize()
} }
public var longNameLocalized: String { public var longNameLocalized: String {

View file

@ -8,11 +8,11 @@
import XCTest import XCTest
// swiftformat:disable:next sortedImports
@testable import passKit @testable import passKit
@testable import Crypto @testable import Crypto
class CryptoFrameworkTest: XCTestCase { class CryptoFrameworkTest: XCTestCase {
typealias MessageConverter = (CryptoPGPMessage, NSErrorPointer) -> CryptoPGPMessage? typealias MessageConverter = (CryptoPGPMessage, NSErrorPointer) -> CryptoPGPMessage?
private let testText = "Hello World!" private let testText = "Hello World!"

View file

@ -6,8 +6,8 @@
// Copyright © 2019 Bob Sun. All rights reserved. // Copyright © 2019 Bob Sun. All rights reserved.
// //
import XCTest
import SwiftyUserDefaults import SwiftyUserDefaults
import XCTest
@testable import passKit @testable import passKit
@ -165,4 +165,3 @@ class PGPAgentTest: XCTestCase {
try KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: privateKey) try KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: privateKey)
} }
} }

View file

@ -11,7 +11,6 @@ import XCTest
@testable import passKit @testable import passKit
class ArraySlicesTest: XCTestCase { class ArraySlicesTest: XCTestCase {
func testZeroCount() { func testZeroCount() {
XCTAssertEqual([1, 2, 3].slices(count: 0), []) XCTAssertEqual([1, 2, 3].slices(count: 0), [])
} }

View file

@ -11,7 +11,6 @@ import XCTest
@testable import passKit @testable import passKit
class StringUtilitiesTest: XCTestCase { class StringUtilitiesTest: XCTestCase {
func testTrimmed() { func testTrimmed() {
[ [
(" ", ""), (" ", ""),

View file

@ -6,8 +6,8 @@
// //
import Foundation import Foundation
import XCTest
import ObjectiveGit import ObjectiveGit
import XCTest
@testable import passKit @testable import passKit
@ -18,8 +18,8 @@ class PasswordStoreTest: XCTestCase {
} }
return [GTRepositoryCloneOptionsCredentialProvider: credentialProvider] return [GTRepositoryCloneOptionsCredentialProvider: credentialProvider]
}() }()
let remoteRepoURL = URL(string: "https://github.com/mssun/passforios-password-store.git")!
let remoteRepoURL = URL(string: "https://github.com/mssun/passforios-password-store.git")!
func testCloneAndDecryptMultiKeys() throws { func testCloneAndDecryptMultiKeys() throws {
let url = URL(fileURLWithPath: "\(Globals.repositoryPath)-test") let url = URL(fileURLWithPath: "\(Globals.repositoryPath)-test")
@ -38,8 +38,8 @@ class PasswordStoreTest: XCTestCase {
[ [
("work/github.com", "4712286271220DB299883EA7062E678DA1024DAE"), ("work/github.com", "4712286271220DB299883EA7062E678DA1024DAE"),
("personal/github.com", "787EAE1A5FA3E749AA34CC6AA0645EBED862027E") ("personal/github.com", "787EAE1A5FA3E749AA34CC6AA0645EBED862027E"),
].forEach {(path, id) in ].forEach { path, id in
let keyID = findGPGID(from: url.appendingPathComponent(path)) let keyID = findGPGID(from: url.appendingPathComponent(path))
XCTAssertEqual(keyID, id) XCTAssertEqual(keyID, id)
} }
@ -63,11 +63,8 @@ class PasswordStoreTest: XCTestCase {
passwordStore.erase() passwordStore.erase()
} }
private func decrypt(passwordStore: PasswordStore, path: String, passphrase: String) throws -> Password { private func decrypt(passwordStore: PasswordStore, path: String, passphrase _: String) throws -> Password {
let entity = passwordStore.getPasswordEntity(by: path, isDir: false)! let entity = passwordStore.getPasswordEntity(by: path, isDir: false)!
return try passwordStore.decrypt(passwordEntity: entity, requestPGPKeyPassphrase: requestPGPKeyPassphrase) return try passwordStore.decrypt(passwordEntity: entity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
} }
} }

View file

@ -11,7 +11,6 @@ import XCTest
@testable import passKit @testable import passKit
class PasswordTableEntryTest: XCTestCase { class PasswordTableEntryTest: XCTestCase {
func testExample() { func testExample() {
let nameWithCategoryList = [ let nameWithCategoryList = [
"github", "github",
@ -27,11 +26,11 @@ class PasswordTableEntryTest: XCTestCase {
] ]
let searchTextList1 = [ let searchTextList1 = [
"github.com", "github.com",
"www.github.com" "www.github.com",
] ]
let searchTextList2 = [ let searchTextList2 = [
"xx.com", "xx.com",
"www.xx.com" "www.xx.com",
] ]
for nameWithCategory in nameWithCategoryList { for nameWithCategory in nameWithCategoryList {
@ -43,5 +42,4 @@ class PasswordTableEntryTest: XCTestCase {
} }
} }
} }
} }

View file

@ -11,7 +11,6 @@ import XCTest
@testable import passKit @testable import passKit
class PasswordTest: XCTestCase { class PasswordTest: XCTestCase {
func testUrl() { func testUrl() {
let password = getPasswordObjectWith(content: "") let password = getPasswordObjectWith(content: "")
@ -245,7 +244,6 @@ class PasswordTest: XCTestCase {
XCTAssertEqual(password.nameFromPath, "exampleusername") XCTAssertEqual(password.nameFromPath, "exampleusername")
} }
func testMultilineValues() { func testMultilineValues() {
let lineBreakField = "with line breaks" => "|\n This is \n text spread over \n multiple lines! " let lineBreakField = "with line breaks" => "|\n This is \n text spread over \n multiple lines! "
let noLineBreakField = "without line breaks" => " > \n This is \n text spread over\n multiple lines!" let noLineBreakField = "without line breaks" => " > \n This is \n text spread over\n multiple lines!"

View file

@ -11,7 +11,6 @@ import XCTest
@testable import passKit @testable import passKit
class AdditionFieldTest: XCTestCase { class AdditionFieldTest: XCTestCase {
func testAdditionField() { func testAdditionField() {
let field1 = AdditionField(title: "key", content: "value") let field1 = AdditionField(title: "key", content: "value")
let field2 = AdditionField(title: "no content") let field2 = AdditionField(title: "no content")

View file

@ -11,7 +11,6 @@ import XCTest
@testable import passKit @testable import passKit
class ConstantsTest: XCTestCase { class ConstantsTest: XCTestCase {
func testIsOtpRelated() { func testIsOtpRelated() {
XCTAssert(Constants.isOtpRelated(line: "otpauth://something")) XCTAssert(Constants.isOtpRelated(line: "otpauth://something"))
XCTAssert(Constants.isOtpRelated(line: "otp_algorithm: algorithm")) XCTAssert(Constants.isOtpRelated(line: "otp_algorithm: algorithm"))

View file

@ -12,7 +12,6 @@ import XCTest
@testable import passKit @testable import passKit
class OTPTypeTest: XCTestCase { class OTPTypeTest: XCTestCase {
func testInitFromToken() { func testInitFromToken() {
let secret = "secret".data(using: .utf8)! let secret = "secret".data(using: .utf8)!

View file

@ -6,11 +6,10 @@
// Copyright © 2018 Bob Sun. All rights reserved. // Copyright © 2018 Bob Sun. All rights reserved.
// //
@testable import passKit
import XCTest import XCTest
@testable import passKit
class ParserTest: XCTestCase { class ParserTest: XCTestCase {
func testInit() { func testInit() {
[ [
("", "", "", []), ("", "", "", []),

View file

@ -13,7 +13,6 @@ import XCTest
@testable import passKit @testable import passKit
class TokenBuilderTest: XCTestCase { class TokenBuilderTest: XCTestCase {
private let SECRET = "secret" private let SECRET = "secret"
private let DIGITS = Constants.DEFAULT_DIGITS private let DIGITS = Constants.DEFAULT_DIGITS
private let TIMER = Generator.Factor.timer(period: Constants.DEFAULT_PERIOD) private let TIMER = Generator.Factor.timer(period: Constants.DEFAULT_PERIOD)

View file

@ -11,10 +11,9 @@ import XCTest
@testable import passKit @testable import passKit
class PasswordGeneratorFlavorTest: XCTestCase { class PasswordGeneratorFlavorTest: XCTestCase {
func testLengthLimits() { func testLengthLimits() {
// Ensure properly chosen length limits. So this check no longer needs to be performed in the code. // Ensure properly chosen length limits. So this check no longer needs to be performed in the code.
PasswordGeneratorFlavor.allCases.map { $0.lengthLimits }.forEach { PasswordGeneratorFlavor.allCases.map(\.lengthLimits).forEach {
XCTAssertLessThanOrEqual($0.min, $0.max) XCTAssertLessThanOrEqual($0.min, $0.max)
} }
} }

View file

@ -11,7 +11,6 @@ import XCTest
@testable import passKit @testable import passKit
class PasswordGeneratorTest: XCTestCase { class PasswordGeneratorTest: XCTestCase {
func testLimitedLength() { func testLimitedLength() {
[ [
PasswordGenerator(length: 15), PasswordGenerator(length: 15),

View file

@ -21,15 +21,15 @@ class DictBasedKeychain: KeyStore {
} }
public func contains(key: String) -> Bool { public func contains(key: String) -> Bool {
return store[key] != nil store[key] != nil
} }
public func get(for key: String) -> Data? { public func get(for key: String) -> Data? {
return store[key] as? Data store[key] as? Data
} }
public func get(for key: String) -> String? { public func get(for key: String) -> String? {
return store[key] as? String store[key] as? String
} }
public func removeContent(for key: String) { public func removeContent(for key: String) {

View file

@ -29,7 +29,7 @@ let MULTILINE_BLOCK_START = "multiline block" => "|"
let MULTILINE_LINE_START = "multiline line" => ">" let MULTILINE_LINE_START = "multiline line" => ">"
func getPasswordObjectWith(content: String, url: URL? = nil) -> Password { func getPasswordObjectWith(content: String, url: URL? = nil) -> Password {
return Password(name: "password", url: url ?? PASSWORD_URL, plainText: content) Password(name: "password", url: url ?? PASSWORD_URL, plainText: content)
} }
func assertDefaults(in password: Password, with passwordString: String, and additions: String, func assertDefaults(in password: Password, with passwordString: String, and additions: String,
@ -45,10 +45,10 @@ func assertDefaults(in password: Password, with passwordString: String, and addi
infix operator : AdditionPrecedence infix operator : AdditionPrecedence
func (field: AdditionField, password: Password) -> Bool { func (field: AdditionField, password: Password) -> Bool {
return password.getFilteredAdditions().contains(field) password.getFilteredAdditions().contains(field)
} }
infix operator : AdditionPrecedence infix operator : AdditionPrecedence
func (field: AdditionField, password: Password) -> Bool { func (field: AdditionField, password: Password) -> Bool {
return !(field password) !(field password)
} }

View file

@ -11,7 +11,6 @@ import XCTest
@testable import passKit @testable import passKit
struct PGPTestSet { struct PGPTestSet {
fileprivate static var ALL_TEST_SETS: [String: PGPTestSet] = [:] fileprivate static var ALL_TEST_SETS: [String: PGPTestSet] = [:]
let publicKey: String let publicKey: String

View file

@ -10,7 +10,6 @@ import XCTest
@testable import passKit @testable import passKit
class passKitTests: XCTestCase { class passKitTests: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
@ -28,9 +27,8 @@ class passKitTests: XCTestCase {
func testPerformanceExample() { func testPerformanceExample() {
// This is an example of a performance test case. // This is an example of a performance test case.
self.measure { measure {
// Put the code you want to measure the time of here. // Put the code you want to measure the time of here.
} }
} }
} }

View file

@ -10,7 +10,6 @@ import Intents
import passKit import passKit
class IntentHandler: INExtension { class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any { override func handler(for intent: INIntent) -> Any {
guard intent is SyncRepositoryIntent else { guard intent is SyncRepositoryIntent else {
fatalError("Unhandled intent type \(intent).") fatalError("Unhandled intent type \(intent).")

View file

@ -10,7 +10,6 @@ import Intents
import passKit import passKit
public class SyncRepositoryIntentHandler: NSObject, SyncRepositoryIntentHandling { public class SyncRepositoryIntentHandler: NSObject, SyncRepositoryIntentHandling {
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
private let keychain = AppKeychain.shared private let keychain = AppKeychain.shared
@ -24,7 +23,7 @@ public class SyncRepositoryIntentHandler: NSObject, SyncRepositoryIntentHandling
} }
} }
public func handle(intent: SyncRepositoryIntent, completion: @escaping (SyncRepositoryIntentResponse) -> Void) { public func handle(intent _: SyncRepositoryIntent, completion: @escaping (SyncRepositoryIntentResponse) -> Void) {
guard passwordStore.repositoryExists() else { guard passwordStore.repositoryExists() else {
completion(SyncRepositoryIntentResponse(code: .noRepository, userActivity: nil)) completion(SyncRepositoryIntentResponse(code: .noRepository, userActivity: nil))
return return

View file

@ -9,7 +9,6 @@
import XCTest import XCTest
class passTests: XCTestCase { class passTests: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class. // Put setup code here. This method is called before the invocation of each test method in the class.
@ -27,9 +26,8 @@ class passTests: XCTestCase {
func testPerformanceExample() { func testPerformanceExample() {
// This is an example of a performance test case. // This is an example of a performance test case.
self.measure { measure {
// Put the code you want to measure the time of here. // Put the code you want to measure the time of here.
} }
} }
} }