Format code with SwiftFormat automatically in every build
This commit is contained in:
parent
f167ab7549
commit
7f9f0e43b2
100 changed files with 1124 additions and 1063 deletions
1
.swift-version
Normal file
1
.swift-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
5.3
|
||||
96
.swiftformat
Normal file
96
.swiftformat
Normal 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
|
||||
|
|
@ -6,6 +6,7 @@ addons:
|
|||
- go
|
||||
- gnupg2
|
||||
- pass
|
||||
- swiftformat
|
||||
before_install:
|
||||
- echo -e "machine github.com\n login $GITHUB_ACCESS_TOKEN" >> ~/.netrc
|
||||
install:
|
||||
|
|
|
|||
|
|
@ -1080,6 +1080,7 @@
|
|||
A26700191EEC450100176B8A /* Embed App Extensions */,
|
||||
A26075921EEC6F34005DB03E /* Embed Frameworks */,
|
||||
9AF6A4F532EB900EE22C80EA /* [CP] Embed Pods Frameworks */,
|
||||
3005F34F24A9143C000519B5 /* ShellScript */,
|
||||
);
|
||||
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";
|
||||
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 */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
|
|
|||
|
|
@ -6,15 +6,14 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
import SwiftyUserDefaults
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
enum ViewTag: Int {
|
||||
case blur = 100, appicon
|
||||
|
|
@ -25,50 +24,51 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
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.
|
||||
SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 100))
|
||||
passcodeLockPresenter.present(windowLevel: UIApplication.shared.windows.last?.windowLevel.rawValue)
|
||||
if let shortcutItem = launchOptions?[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
|
||||
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
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
|
||||
func application(_: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
|
||||
if let _ = window?.rootViewController as? PasscodeLockViewController {
|
||||
window?.frame = UIScreen.main.bounds
|
||||
}
|
||||
return .all
|
||||
}
|
||||
|
||||
@objc func postSearchNotification() {
|
||||
@objc
|
||||
func postSearchNotification() {
|
||||
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" {
|
||||
let tabBarController = self.window!.rootViewController as! UITabBarController
|
||||
let tabBarController = window!.rootViewController as! UITabBarController
|
||||
tabBarController.selectedIndex = 0
|
||||
let navigationController = tabBarController.selectedViewController as! UINavigationController
|
||||
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.
|
||||
// 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
|
||||
let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
|
||||
let blurEffectView = UIVisualEffectView(effect: blurEffect)
|
||||
blurEffectView.frame = (self.window?.frame)!
|
||||
blurEffectView.frame = (window?.frame)!
|
||||
blurEffectView.tag = ViewTag.blur.rawValue
|
||||
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
self.window?.addSubview(blurEffectView)
|
||||
window?.addSubview(blurEffectView)
|
||||
|
||||
// Display the Pass icon in the middle of the screen
|
||||
let iconsDictionary = Bundle.main.infoDictionary?["CFBundleIcons"] as? NSDictionary
|
||||
|
|
@ -78,33 +78,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
let appIconView = UIImageView(image: appIcon)
|
||||
appIconView.layer.cornerRadius = (appIcon?.size.height)! / 5
|
||||
appIconView.layer.masksToBounds = true
|
||||
appIconView.center = (self.window?.center)!
|
||||
appIconView.center = (window?.center)!
|
||||
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.
|
||||
// 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.
|
||||
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.
|
||||
|
||||
self.window?.viewWithTag(ViewTag.appicon.rawValue)?.removeFromSuperview()
|
||||
self.window?.viewWithTag(ViewTag.blur.rawValue)?.removeFromSuperview()
|
||||
window?.viewWithTag(ViewTag.appicon.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:.
|
||||
// Saves changes in the application's managed object context before the application terminates.
|
||||
self.saveContext()
|
||||
saveContext()
|
||||
}
|
||||
|
||||
// MARK: - Core Data stack
|
||||
|
|
@ -123,7 +122,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
try! FileManager.default.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
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? {
|
||||
// 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.
|
||||
|
|
@ -144,7 +143,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
// MARK: - Core Data Saving support
|
||||
|
||||
func saveContext () {
|
||||
func saveContext() {
|
||||
let context = persistentContainer.viewContext
|
||||
if context.hasChanges {
|
||||
do {
|
||||
|
|
@ -157,6 +156,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
||||
|
||||
private static let VALUE_NOT_AVAILABLE = "ValueNotAvailable".localize()
|
||||
|
||||
private var needRefresh = false
|
||||
|
|
@ -18,6 +17,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
|||
let indicator = UIActivityIndicatorView(style: .gray)
|
||||
return indicator
|
||||
}()
|
||||
|
||||
private let passwordStore = PasswordStore.shared
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -41,10 +41,9 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
|||
}
|
||||
|
||||
private func setTableData() {
|
||||
|
||||
// clear current contents (if any)
|
||||
self.tableData.removeAll(keepingCapacity: true)
|
||||
self.tableView.reloadData()
|
||||
tableData.removeAll(keepingCapacity: true)
|
||||
tableView.reloadData()
|
||||
indicator.startAnimating()
|
||||
|
||||
// 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: "LastSynced".localize(), .detailText: lastSynced],
|
||||
[.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.tableView.reloadData()
|
||||
|
|
@ -79,15 +77,15 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
|||
private func numberOfPasswordsString() -> String {
|
||||
let formatter = NumberFormatter()
|
||||
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 {
|
||||
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 {
|
||||
guard let date = self.passwordStore.lastSyncedTime else {
|
||||
guard let date = passwordStore.lastSyncedTime else {
|
||||
return "SyncAgain?".localize()
|
||||
}
|
||||
let formatter = DateFormatter()
|
||||
|
|
@ -103,7 +101,8 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
|||
return AboutRepositoryTableViewController.VALUE_NOT_AVAILABLE
|
||||
}
|
||||
|
||||
@objc func setNeedRefresh() {
|
||||
@objc
|
||||
func setNeedRefresh() {
|
||||
needRefresh = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,16 @@
|
|||
import UIKit
|
||||
|
||||
class AboutTableViewController: BasicStaticTableViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
tableData = [
|
||||
// section 0
|
||||
[[.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: "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,
|
||||
[[.title: "OpenSourceComponents".localize(), .action: "segue", .link: "showOpenSourceComponentsSegue"],
|
||||
[.title: "SpecialThanks".localize(), .action: "segue", .link: "showSpecialThanksSegue"],],
|
||||
[.title: "SpecialThanks".localize(), .action: "segue", .link: "showSpecialThanksSegue"]],
|
||||
]
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
|
@ -39,11 +38,10 @@ class AboutTableViewController: BasicStaticTableViewController {
|
|||
return nil
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
if section == 1 {
|
||||
return "Acknowledgements".localize().uppercased()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class AddPasswordTableViewController: PasswordEditorTableViewController {
|
||||
var defaultDirPrefix = ""
|
||||
|
|
@ -17,7 +17,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
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" {
|
||||
// check PGP key
|
||||
guard PGPAgent.shared.isPrepared else {
|
||||
|
|
|
|||
|
|
@ -6,16 +6,15 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
|
||||
class AdvancedSettingsTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var encryptInASCIIArmoredTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var gitSignatureTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var eraseDataTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var discardChangesTableViewCell: UITableViewCell!
|
||||
@IBOutlet var encryptInASCIIArmoredTableViewCell: UITableViewCell!
|
||||
@IBOutlet var gitSignatureTableViewCell: UITableViewCell!
|
||||
@IBOutlet var eraseDataTableViewCell: UITableViewCell!
|
||||
@IBOutlet var discardChangesTableViewCell: UITableViewCell!
|
||||
let passwordStore = PasswordStore.shared
|
||||
|
||||
let encryptInASCIIArmoredSwitch: UISwitch = {
|
||||
|
|
@ -37,10 +36,10 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
private func setGitSignatureText() {
|
||||
let gitSignatureName = passwordStore.gitSignatureForNow?.name ?? ""
|
||||
let gitSignatureEmail = passwordStore.gitSignatureForNow?.email ?? ""
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.text = "\(gitSignatureName) <\(gitSignatureEmail)>"
|
||||
if Defaults.gitSignatureName == nil && Defaults.gitSignatureEmail == nil {
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
gitSignatureTableViewCell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||
gitSignatureTableViewCell.detailTextLabel?.text = "\(gitSignatureName) <\(gitSignatureEmail)>"
|
||||
if Defaults.gitSignatureName == nil, Defaults.gitSignatureEmail == nil {
|
||||
gitSignatureTableViewCell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
gitSignatureTableViewCell.detailTextLabel?.text = "NotSet".localize()
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +48,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
if tableView.cellForRow(at: indexPath) == eraseDataTableViewCell {
|
||||
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())
|
||||
self.passwordStore.erase()
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
|
|
@ -57,10 +56,10 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}))
|
||||
alert.addAction(UIAlertAction.dismiss())
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
present(alert, animated: true, completion: nil)
|
||||
} else if tableView.cellForRow(at: indexPath) == discardChangesTableViewCell {
|
||||
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())
|
||||
do {
|
||||
let numberDiscarded = try self.passwordStore.reset()
|
||||
|
|
@ -73,15 +72,17 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
|
||||
}))
|
||||
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
|
||||
}
|
||||
|
||||
@IBAction func saveGitConfigSetting(segue: UIStoryboardSegue) {
|
||||
@IBAction
|
||||
func saveGitConfigSetting(segue: UIStoryboardSegue) {
|
||||
if let controller = segue.source as? GitConfigSettingsTableViewController {
|
||||
if let gitSignatureName = controller.nameTextField.text,
|
||||
let gitSignatureEmail = controller.emailTextField.text {
|
||||
|
|
@ -91,5 +92,4 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
setGitSignatureText()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SafariServices
|
||||
import MessageUI
|
||||
import passKit
|
||||
|
||||
import SafariServices
|
||||
import UIKit
|
||||
|
||||
enum CellDataType {
|
||||
case link, segue, empty, detail
|
||||
|
|
@ -25,7 +24,7 @@ enum CellDataKey {
|
|||
}
|
||||
|
||||
class BasicStaticTableViewController: UITableViewController, MFMailComposeViewControllerDelegate {
|
||||
var tableData = [[Dictionary<CellDataKey, Any>]]()
|
||||
var tableData = [[[CellDataKey: Any]]]()
|
||||
var navigationItemTitle: String?
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -35,12 +34,12 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
|||
}
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return tableData.count
|
||||
override func numberOfSections(in _: UITableView) -> Int {
|
||||
tableData.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return tableData[section].count
|
||||
override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
tableData[section].count
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
|
|
@ -48,8 +47,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
|||
// 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 cellDataStyle = cellData[CellDataKey.style] as? CellDataStyle
|
||||
var cell: UITableViewCell?
|
||||
|
|
@ -76,7 +74,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
|||
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 selector = cellData[CellDataKey.detailDisclosureAction] as? Selector
|
||||
perform(selector, with: cellData[CellDataKey.detailDisclosureData])
|
||||
|
|
@ -108,7 +106,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
|||
}
|
||||
case "http", "https":
|
||||
let svc = SFSafariViewController(url: URL(string: link)!, entersReaderIfAvailable: false)
|
||||
self.present(svc, animated: true, completion: nil)
|
||||
present(svc, animated: true, completion: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
@ -123,10 +121,10 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
|||
mailVC.setToRecipients(recipients)
|
||||
mailVC.setSubject(subject)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import ObjectiveGit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class CommitLogsTableViewController: UITableViewController {
|
||||
var commits: [GTCommit] = []
|
||||
|
|
@ -18,12 +18,12 @@ class CommitLogsTableViewController: UITableViewController {
|
|||
super.viewDidLoad()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateCommitLogs), name: .passwordStoreUpdated, object: nil)
|
||||
commits = getCommitLogs()
|
||||
self.tableView.estimatedRowHeight = 50
|
||||
self.tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 50
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return commits.count
|
||||
override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
|
||||
commits.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
|
@ -42,7 +42,8 @@ class CommitLogsTableViewController: UITableViewController {
|
|||
return cell
|
||||
}
|
||||
|
||||
@objc func updateCommitLogs() {
|
||||
@objc
|
||||
func updateCommitLogs() {
|
||||
commits = getCommitLogs()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class EditPasswordTableViewController: PasswordEditorTableViewController {
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender _: Any?) -> Bool {
|
||||
if identifier == "saveEditPasswordSegue" {
|
||||
// check name
|
||||
guard checkName() else {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||
let passwordStore = PasswordStore.shared
|
||||
|
|
@ -67,37 +67,35 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
override func viewDidLoad() {
|
||||
tableData = [
|
||||
// section 0
|
||||
[[.title: "AboutRepository".localize(), .action: "segue", .link: "showAboutRepositorySegue"],],
|
||||
[[.title: "AboutRepository".localize(), .action: "segue", .link: "showAboutRepositorySegue"]],
|
||||
|
||||
// section 1
|
||||
[
|
||||
[.title: "RememberPgpKeyPassphrase".localize(), .action: "none",],
|
||||
[.title: "RememberGitCredentialPassphrase".localize(), .action: "none",],
|
||||
[.title: "RememberPgpKeyPassphrase".localize(), .action: "none"],
|
||||
[.title: "RememberGitCredentialPassphrase".localize(), .action: "none"],
|
||||
],
|
||||
|
||||
|
||||
// section 2
|
||||
[
|
||||
[.title: "ShowFolders".localize(), .action: "none",],
|
||||
[.title: "HidePasswordImages".localize(), .action: "none",],
|
||||
[.title: "HideUnknownFields".localize(), .action: "none",],
|
||||
[.title: "HideOtpFields".localize(), .action: "none",],
|
||||
[.title: "ShowFolders".localize(), .action: "none"],
|
||||
[.title: "HidePasswordImages".localize(), .action: "none"],
|
||||
[.title: "HideUnknownFields".localize(), .action: "none"],
|
||||
[.title: "HideOtpFields".localize(), .action: "none"],
|
||||
],
|
||||
|
||||
]
|
||||
super.viewDidLoad()
|
||||
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
switch cell.textLabel!.text! {
|
||||
case "HideUnknownFields".localize():
|
||||
cell.accessoryType = .none
|
||||
let detailButton = UIButton(type: .detailDisclosure)
|
||||
hideUnknownSwitch.frame = CGRect(x: detailButton.bounds.width+10, y: 0, width: hideUnknownSwitch.bounds.width, height: hideUnknownSwitch.bounds.height)
|
||||
hideUnknownSwitch.frame = CGRect(x: detailButton.bounds.width + 10, y: 0, width: hideUnknownSwitch.bounds.width, height: hideUnknownSwitch.bounds.height)
|
||||
detailButton.frame = CGRect(x: 0, y: 5, width: detailButton.bounds.width, height: detailButton.bounds.height)
|
||||
detailButton.addTarget(self, action: #selector(GeneralSettingsTableViewController.tapHideUnknownSwitchDetailButton(_:)), for: UIControl.Event.touchDown)
|
||||
let accessoryView = UIView(frame: CGRect(x: 0, y: 0, width: detailButton.bounds.width + hideUnknownSwitch.bounds.width+10, height: hideUnknownSwitch.bounds.height))
|
||||
let accessoryView = UIView(frame: CGRect(x: 0, y: 0, width: detailButton.bounds.width + hideUnknownSwitch.bounds.width + 10, height: hideUnknownSwitch.bounds.height))
|
||||
accessoryView.addSubview(detailButton)
|
||||
accessoryView.addSubview(hideUnknownSwitch)
|
||||
cell.accessoryView = accessoryView
|
||||
|
|
@ -106,10 +104,10 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
case "HideOtpFields".localize():
|
||||
cell.accessoryType = .none
|
||||
let detailButton = UIButton(type: .detailDisclosure)
|
||||
hideOTPSwitch.frame = CGRect(x: detailButton.bounds.width+10, y: 0, width: hideOTPSwitch.bounds.width, height: hideOTPSwitch.bounds.height)
|
||||
hideOTPSwitch.frame = CGRect(x: detailButton.bounds.width + 10, y: 0, width: hideOTPSwitch.bounds.width, height: hideOTPSwitch.bounds.height)
|
||||
detailButton.frame = CGRect(x: 0, y: 5, width: detailButton.bounds.width, height: detailButton.bounds.height)
|
||||
detailButton.addTarget(self, action: #selector(GeneralSettingsTableViewController.tapHideOTPSwitchDetailButton(_:)), for: UIControl.Event.touchDown)
|
||||
let accessoryView = UIView(frame: CGRect(x: 0, y: 0, width: detailButton.bounds.width + hideOTPSwitch.bounds.width+10, height: hideOTPSwitch.bounds.height))
|
||||
let accessoryView = UIView(frame: CGRect(x: 0, y: 0, width: detailButton.bounds.width + hideOTPSwitch.bounds.width + 10, height: hideOTPSwitch.bounds.height))
|
||||
accessoryView.addSubview(detailButton)
|
||||
accessoryView.addSubview(hideOTPSwitch)
|
||||
cell.accessoryView = accessoryView
|
||||
|
|
@ -130,10 +128,10 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
case "HidePasswordImages".localize():
|
||||
cell.accessoryType = .none
|
||||
let detailButton = UIButton(type: .detailDisclosure)
|
||||
hidePasswordImagesSwitch.frame = CGRect(x: detailButton.bounds.width+10, y: 0, width: hidePasswordImagesSwitch.bounds.width, height: hidePasswordImagesSwitch.bounds.height)
|
||||
hidePasswordImagesSwitch.frame = CGRect(x: detailButton.bounds.width + 10, y: 0, width: hidePasswordImagesSwitch.bounds.width, height: hidePasswordImagesSwitch.bounds.height)
|
||||
detailButton.frame = CGRect(x: 0, y: 5, width: detailButton.bounds.width, height: detailButton.bounds.height)
|
||||
detailButton.addTarget(self, action: #selector(GeneralSettingsTableViewController.tapHidePasswordImagesSwitchDetailButton(_:)), for: UIControl.Event.touchDown)
|
||||
let accessoryView = UIView(frame: CGRect(x: 0, y: 0, width: detailButton.bounds.width + hidePasswordImagesSwitch.bounds.width+10, height: hidePasswordImagesSwitch.bounds.height))
|
||||
let accessoryView = UIView(frame: CGRect(x: 0, y: 0, width: detailButton.bounds.width + hidePasswordImagesSwitch.bounds.width + 10, height: hidePasswordImagesSwitch.bounds.height))
|
||||
accessoryView.addSubview(detailButton)
|
||||
accessoryView.addSubview(hidePasswordImagesSwitch)
|
||||
cell.accessoryView = accessoryView
|
||||
|
|
@ -144,43 +142,50 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
return cell
|
||||
}
|
||||
|
||||
@objc func tapHideUnknownSwitchDetailButton(_ sender: Any?) {
|
||||
@objc
|
||||
func tapHideUnknownSwitchDetailButton(_: Any?) {
|
||||
let alertMessage = "HideUnknownFieldsExplanation.".localize()
|
||||
let alertTitle = "HideUnknownFields".localize()
|
||||
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 alertMessage = "HideOtpFieldsExplanation.".localize(keywordsString)
|
||||
let alertTitle = "HideOtpFields".localize()
|
||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||
}
|
||||
|
||||
@objc func tapHidePasswordImagesSwitchDetailButton(_ sender: Any?) {
|
||||
@objc
|
||||
func tapHidePasswordImagesSwitchDetailButton(_: Any?) {
|
||||
let alertMessage = "HidePasswordImagesExplanation.".localize()
|
||||
let alertTitle = "HidePasswordImages".localize()
|
||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||
}
|
||||
|
||||
@objc func hideUnknownSwitchAction(_ sender: Any?) {
|
||||
@objc
|
||||
func hideUnknownSwitchAction(_: Any?) {
|
||||
Defaults.isHideUnknownOn = hideUnknownSwitch.isOn
|
||||
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
||||
}
|
||||
|
||||
@objc func hideOTPSwitchAction(_ sender: Any?) {
|
||||
@objc
|
||||
func hideOTPSwitchAction(_: Any?) {
|
||||
Defaults.isHideOTPOn = hideOTPSwitch.isOn
|
||||
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
||||
}
|
||||
|
||||
@objc func rememberPGPPassphraseSwitchAction(_ sender: Any?) {
|
||||
@objc
|
||||
func rememberPGPPassphraseSwitchAction(_: Any?) {
|
||||
Defaults.isRememberPGPPassphraseOn = rememberPGPPassphraseSwitch.isOn
|
||||
if rememberPGPPassphraseSwitch.isOn == false {
|
||||
AppKeychain.shared.removeAllContent(withPrefix: Globals.pgpKeyPassphrase)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func rememberGitCredentialPassphraseSwitchAction(_ sender: Any?) {
|
||||
@objc
|
||||
func rememberGitCredentialPassphraseSwitchAction(_: Any?) {
|
||||
Defaults.isRememberGitCredentialPassphraseOn = rememberGitCredentialPassphraseSwitch.isOn
|
||||
if rememberGitCredentialPassphraseSwitch.isOn == false {
|
||||
passwordStore.gitSSHPrivateKeyPassphrase = nil
|
||||
|
|
@ -188,14 +193,15 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
}
|
||||
}
|
||||
|
||||
@objc func showFolderSwitchAction(_ sender: Any?) {
|
||||
@objc
|
||||
func showFolderSwitchAction(_: Any?) {
|
||||
Defaults.isShowFolderOn = showFolderSwitch.isOn
|
||||
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
|
||||
}
|
||||
|
||||
@objc func hidePasswordImagesSwitchAction(_ sender: Any?) {
|
||||
@objc
|
||||
func hidePasswordImagesSwitchAction(_: Any?) {
|
||||
Defaults.isHidePasswordImagesOn = hidePasswordImagesSwitch.isOn
|
||||
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
// Copyright © 2017 Yishi Lin. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class GitConfigSettingsTableViewController: UITableViewController {
|
||||
let passwordStore = PasswordStore.shared
|
||||
|
||||
@IBOutlet weak var nameTextField: UITextField!
|
||||
@IBOutlet weak var emailTextField: UITextField!
|
||||
@IBOutlet var nameTextField: UITextField!
|
||||
@IBOutlet var emailTextField: UITextField!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
|
@ -26,7 +26,7 @@ class GitConfigSettingsTableViewController: UITableViewController {
|
|||
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" {
|
||||
let name = nameTextField.text!.isEmpty ? Globals.gitSignatureDefaultName : nameTextField.text!
|
||||
let email = emailTextField.text!.isEmpty ? Globals.gitSignatureDefaultEmail : nameTextField.text!
|
||||
|
|
@ -38,4 +38,3 @@ class GitConfigSettingsTableViewController: UITableViewController {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,21 +6,20 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
|
||||
class GitRepositorySettingsTableViewController: UITableViewController {
|
||||
// MARK: - View Outlet
|
||||
|
||||
@IBOutlet weak var gitURLTextField: UITextField!
|
||||
@IBOutlet weak var usernameTextField: UITextField!
|
||||
@IBOutlet weak var branchNameTextField: UITextField!
|
||||
@IBOutlet weak var authSSHKeyCell: UITableViewCell!
|
||||
@IBOutlet weak var authPasswordCell: UITableViewCell!
|
||||
@IBOutlet weak var gitURLCell: UITableViewCell!
|
||||
@IBOutlet weak var gitRepositoryURLTabelViewCell: UITableViewCell!
|
||||
@IBOutlet var gitURLTextField: UITextField!
|
||||
@IBOutlet var usernameTextField: UITextField!
|
||||
@IBOutlet var branchNameTextField: UITextField!
|
||||
@IBOutlet var authSSHKeyCell: UITableViewCell!
|
||||
@IBOutlet var authPasswordCell: UITableViewCell!
|
||||
@IBOutlet var gitURLCell: UITableViewCell!
|
||||
@IBOutlet var gitRepositoryURLTabelViewCell: UITableViewCell!
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
|
|
@ -33,27 +32,29 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
updateAuthenticationMethodCheckView(for: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private var gitUrl: URL {
|
||||
get { Defaults.gitURL }
|
||||
set { Defaults.gitURL = newValue }
|
||||
}
|
||||
|
||||
private var gitBranchName: String {
|
||||
get { Defaults.gitBranchName }
|
||||
set { Defaults.gitBranchName = newValue }
|
||||
}
|
||||
|
||||
private var gitUsername: String {
|
||||
get { Defaults.gitUsername }
|
||||
set { Defaults.gitUsername = newValue }
|
||||
}
|
||||
|
||||
private var gitCredential: GitCredential {
|
||||
get {
|
||||
switch Defaults.gitAuthenticationMethod {
|
||||
case .password:
|
||||
return GitCredential(credential: .http(userName: Defaults.gitUsername))
|
||||
case .key:
|
||||
let privateKey: String = AppKeychain.shared.get(for: SshKey.PRIVATE.getKeychainKey()) ?? ""
|
||||
return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey))
|
||||
}
|
||||
switch Defaults.gitAuthenticationMethod {
|
||||
case .password:
|
||||
return GitCredential(credential: .http(userName: Defaults.gitUsername))
|
||||
case .key:
|
||||
let privateKey: String = AppKeychain.shared.get(for: SshKey.PRIVATE.getKeychainKey()) ?? ""
|
||||
return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,9 +62,9 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
gitURLTextField.text = self.gitUrl.absoluteString
|
||||
usernameTextField.text = self.gitUsername
|
||||
branchNameTextField.text = self.gitBranchName
|
||||
gitURLTextField.text = gitUrl.absoluteString
|
||||
usernameTextField.text = gitUsername
|
||||
branchNameTextField.text = gitBranchName
|
||||
sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel
|
||||
authSSHKeyCell.accessoryType = .detailButton
|
||||
}
|
||||
|
|
@ -94,7 +95,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let cell = tableView.cellForRow(at: indexPath)
|
||||
if cell == authPasswordCell {
|
||||
self.gitAuthenticationMethod = .password
|
||||
gitAuthenticationMethod = .password
|
||||
} else if cell == authSSHKeyCell {
|
||||
if !AppKeychain.shared.contains(key: SshKey.PRIVATE.getKeychainKey()) {
|
||||
Utils.alert(title: "CannotSelectSshKey".localize(), message: "PleaseSetupSshKeyFirst.".localize(), controller: self)
|
||||
|
|
@ -108,7 +109,8 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
|
||||
// MARK: - Segue Handlers
|
||||
|
||||
@IBAction func save(_ sender: Any) {
|
||||
@IBAction
|
||||
func save(_: Any) {
|
||||
guard let gitURLTextFieldText = gitURLTextField.text, let gitURL = URL(string: gitURLTextFieldText.trimmed) else {
|
||||
Utils.alert(title: "CannotSave".localize(), message: "SetGitRepositoryUrl".localize(), controller: self)
|
||||
return
|
||||
|
|
@ -137,9 +139,9 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
}
|
||||
}
|
||||
|
||||
self.gitUrl = gitURL
|
||||
self.gitBranchName = branchName.trimmed
|
||||
self.gitUsername = (gitURL.user ?? usernameTextField.text ?? "git").trimmed
|
||||
gitUrl = gitURL
|
||||
gitBranchName = branchName.trimmed
|
||||
gitUsername = (gitURL.user ?? usernameTextField.text ?? "git").trimmed
|
||||
|
||||
if passwordStore.repositoryExists() {
|
||||
let overwriteAlert: UIAlertController = {
|
||||
|
|
@ -150,7 +152,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
alert.addAction(UIAlertAction.cancel())
|
||||
return alert
|
||||
}()
|
||||
self.present(overwriteAlert, animated: true)
|
||||
present(overwriteAlert, animated: true)
|
||||
} else {
|
||||
cloneAndSegueIfSuccess()
|
||||
}
|
||||
|
|
@ -159,15 +161,15 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
private func cloneAndSegueIfSuccess() {
|
||||
// Remember git credential password/passphrase temporarily, ask whether users want this after a successful clone.
|
||||
Defaults.isRememberGitCredentialPassphraseOn = true
|
||||
DispatchQueue.global(qos: .userInitiated).async() {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
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 progress = Float(gitTransferProgress.received_objects) / Float(gitTransferProgress.total_objects)
|
||||
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)
|
||||
SVProgressHUD.showProgress(progress, status: "CheckingOutBranch".localize(self.gitBranchName))
|
||||
}
|
||||
|
|
@ -179,7 +181,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
transferProgressBlock: transferProgressBlock,
|
||||
checkoutProgressBlock: checkoutProgressBlock)
|
||||
|
||||
SVProgressHUD.dismiss() {
|
||||
SVProgressHUD.dismiss {
|
||||
let savePassphraseAlert: UIAlertController = {
|
||||
let alert = UIAlertController(title: "Done".localize(), message: "WantToSaveGitCredential?".localize(), preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "No".localize(), style: .default) { _ in
|
||||
|
|
@ -188,7 +190,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
self.passwordStore.gitSSHPrivateKeyPassphrase = nil
|
||||
self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "Yes".localize(), style: .destructive) {_ in
|
||||
alert.addAction(UIAlertAction(title: "Yes".localize(), style: .destructive) { _ in
|
||||
Defaults.isRememberGitCredentialPassphraseOn = true
|
||||
self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
|
||||
})
|
||||
|
|
@ -199,7 +201,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
SVProgressHUD.dismiss() {
|
||||
SVProgressHUD.dismiss {
|
||||
let error = error as NSError
|
||||
var message = error.localizedDescription
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
|
@ -278,7 +281,7 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -297,21 +300,20 @@ class GitRepositorySettingsTableViewController: UITableViewController {
|
|||
|
||||
private func showGitURLFormatHelp() {
|
||||
let message = """
|
||||
https://example.com[:port]/project.git
|
||||
ssh://[user@]server[:port]/project.git
|
||||
[user@]server:project.git (no scheme)
|
||||
"""
|
||||
https://example.com[:port]/project.git
|
||||
ssh://[user@]server[:port]/project.git
|
||||
[user@]server:project.git (no scheme)
|
||||
"""
|
||||
Utils.alert(title: "Git URL Format", message: message, controller: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension GitRepositorySettingsTableViewController: KeyImporter {
|
||||
|
||||
static let keySource = KeySource.itunes
|
||||
static let label = "ITunesFileSharing".localize()
|
||||
|
||||
func isReadyToUse() -> Bool {
|
||||
return KeyFileManager.PrivateSsh.doesKeyFileExist()
|
||||
KeyFileManager.PrivateSsh.doesKeyFileExist()
|
||||
}
|
||||
|
||||
func importKeys() throws {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
import passKit
|
||||
|
||||
protocol KeyImporter {
|
||||
|
||||
static var keySource: KeySource { get }
|
||||
|
||||
static var label: String { get }
|
||||
|
|
@ -24,9 +23,8 @@ protocol KeyImporter {
|
|||
}
|
||||
|
||||
extension KeyImporter {
|
||||
|
||||
static var isCurrentKeySource: Bool {
|
||||
return Defaults.gitSSHKeySource == Self.keySource
|
||||
Defaults.gitSSHKeySource == Self.keySource
|
||||
}
|
||||
|
||||
static var menuLabel: String {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SafariServices
|
||||
import UIKit
|
||||
|
||||
class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
|
||||
|
||||
private static let openSourceComponents = [
|
||||
["FavIcon",
|
||||
"https://github.com/bitserf/FavIcon",
|
||||
|
|
@ -48,12 +47,13 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
|
|||
.link: item[1],
|
||||
.accessoryType: UITableViewCell.AccessoryType.detailDisclosureButton,
|
||||
.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) {
|
||||
let svc = SFSafariViewController(url: url, entersReaderIfAvailable: false)
|
||||
present(svc, animated: true)
|
||||
|
|
|
|||
|
|
@ -6,15 +6,14 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
|
||||
|
||||
@IBOutlet weak var armorPublicKeyTextView: UITextView!
|
||||
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
||||
@IBOutlet weak var scanPublicKeyCell: UITableViewCell!
|
||||
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
|
||||
@IBOutlet var armorPublicKeyTextView: UITextView!
|
||||
@IBOutlet var armorPrivateKeyTextView: UITextView!
|
||||
@IBOutlet var scanPublicKeyCell: UITableViewCell!
|
||||
@IBOutlet var scanPrivateKeyCell: UITableViewCell!
|
||||
|
||||
var armorPublicKey: String?
|
||||
var armorPrivateKey: String?
|
||||
|
|
@ -23,45 +22,47 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
|
|||
enum KeyType {
|
||||
case publicKey, privateKey
|
||||
}
|
||||
|
||||
var keyType = KeyType.publicKey
|
||||
var segments = [String]()
|
||||
var message = ""
|
||||
|
||||
func reset(keytype: KeyType) {
|
||||
self.keyType = keytype
|
||||
self.segments.removeAll()
|
||||
keyType = keytype
|
||||
segments.removeAll()
|
||||
message = "LookingForStartingFrame.".localize()
|
||||
}
|
||||
|
||||
func addSegment(segment: String) -> (accept: Bool, message: String) {
|
||||
let keyTypeStr = self.keyType == .publicKey ? "Public" : "Private"
|
||||
let theOtherKeyTypeStr = self.keyType == .publicKey ? "Private" : "Public"
|
||||
|
||||
let keyTypeStr = keyType == .publicKey ? "Public" : "Private"
|
||||
let theOtherKeyTypeStr = keyType == .publicKey ? "Private" : "Public"
|
||||
|
||||
// Skip duplicated segments.
|
||||
guard segment != self.segments.last else {
|
||||
return (accept: false, message: self.message)
|
||||
guard segment != segments.last else {
|
||||
return (accept: false, message: message)
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.
|
||||
self.segments.append(segment)
|
||||
segments.append(segment)
|
||||
if segment.contains("-----END PGP \(keyTypeStr.uppercased()) KEY BLOCK-----") {
|
||||
self.message = "Done".localize()
|
||||
return (accept: true, message: self.message)
|
||||
message = "Done".localize()
|
||||
return (accept: true, message: message)
|
||||
} else {
|
||||
self.message = "ScannedQrCodes(%d)".localize(self.segments.count)
|
||||
return (accept: false, message: self.message)
|
||||
message = "ScannedQrCodes(%d)".localize(segments.count)
|
||||
return (accept: false, message: message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scanned = ScannedPGPKey()
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -76,13 +77,14 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
|
|||
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
|
||||
}
|
||||
|
||||
@IBAction func save(_ sender: Any) {
|
||||
@IBAction
|
||||
func save(_: Any) {
|
||||
armorPublicKey = armorPublicKeyTextView.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 {
|
||||
// user pastes something, do the copy here again and clear the pasteboard in 45s
|
||||
SecurePasteboard.shared.copy(textToCopy: text)
|
||||
|
|
@ -94,21 +96,23 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
|
|||
let selectedCell = tableView.cellForRow(at: indexPath)
|
||||
if selectedCell == scanPublicKeyCell {
|
||||
scanned.reset(keytype: ScannedPGPKey.KeyType.publicKey)
|
||||
self.performSegue(withIdentifier: "showPGPScannerSegue", sender: self)
|
||||
performSegue(withIdentifier: "showPGPScannerSegue", sender: self)
|
||||
} else if selectedCell == scanPrivateKeyCell {
|
||||
scanned.reset(keytype: ScannedPGPKey.KeyType.privateKey)
|
||||
self.performSegue(withIdentifier: "showPGPScannerSegue", sender: self)
|
||||
performSegue(withIdentifier: "showPGPScannerSegue", sender: self)
|
||||
}
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - QRScannerControllerDelegate Methods
|
||||
|
||||
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
||||
return scanned.addSegment(segment: line)
|
||||
}
|
||||
|
||||
// MARK: - QRScannerControllerDelegate Methods
|
||||
func handleScannedOutput(line: String) {
|
||||
|
||||
func handleScannedOutput(line _: String) {
|
||||
let key = scanned.segments.joined(separator: "")
|
||||
switch scanned.keyType {
|
||||
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 let navController = segue.destination as? UINavigationController {
|
||||
if let viewController = navController.topViewController as? QRScannerController {
|
||||
|
|
@ -132,7 +136,6 @@ class PGPKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
|
|||
}
|
||||
|
||||
extension PGPKeyArmorImportTableViewController: PGPKeyImporter {
|
||||
|
||||
static let keySource = KeySource.armor
|
||||
static let label = "AsciiArmorEncryptedKey".localize()
|
||||
|
||||
|
|
|
|||
|
|
@ -9,18 +9,18 @@
|
|||
import passKit
|
||||
|
||||
class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController {
|
||||
@IBOutlet var pgpPublicKeyFile: UITableViewCell!
|
||||
@IBOutlet var pgpPrivateKeyFile: UITableViewCell!
|
||||
|
||||
@IBOutlet weak var pgpPublicKeyFile: UITableViewCell!
|
||||
@IBOutlet weak var pgpPrivateKeyFile: UITableViewCell!
|
||||
|
||||
private var publicKey: String? = nil
|
||||
private var privateKey: String? = nil
|
||||
private var publicKey: String?
|
||||
private var privateKey: String?
|
||||
|
||||
private enum KeyType { case none, `private`, `public` }
|
||||
private var currentlyPicking = KeyType.none
|
||||
|
||||
@IBAction func save(_ sender: Any) {
|
||||
self.saveImportedKeys()
|
||||
@IBAction
|
||||
func save(_: Any) {
|
||||
saveImportedKeys()
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
|
@ -43,7 +43,6 @@ class PGPKeyFileImportTableViewController: AutoCellHeightUITableViewController {
|
|||
}
|
||||
|
||||
extension PGPKeyFileImportTableViewController: UIDocumentPickerDelegate {
|
||||
|
||||
func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt url: [URL]) {
|
||||
guard let url = url.first else {
|
||||
return
|
||||
|
|
@ -78,12 +77,11 @@ extension PGPKeyFileImportTableViewController: UIDocumentPickerDelegate {
|
|||
}
|
||||
|
||||
extension PGPKeyFileImportTableViewController: PGPKeyImporter {
|
||||
|
||||
static let keySource = KeySource.file
|
||||
static let label = "LoadFromFiles".localize()
|
||||
|
||||
func isReadyToUse() -> Bool {
|
||||
return validate(key: publicKey) && validate(key: privateKey)
|
||||
validate(key: publicKey) && validate(key: privateKey)
|
||||
}
|
||||
|
||||
func importKeys() throws {
|
||||
|
|
|
|||
|
|
@ -9,19 +9,15 @@
|
|||
import passKit
|
||||
|
||||
protocol PGPKeyImporter: KeyImporter {
|
||||
|
||||
func doAfterImport()
|
||||
|
||||
func saveImportedKeys()
|
||||
}
|
||||
|
||||
extension PGPKeyImporter {
|
||||
|
||||
static var isCurrentKeySource: Bool {
|
||||
return Defaults.pgpKeySource == Self.keySource
|
||||
Defaults.pgpKeySource == Self.keySource
|
||||
}
|
||||
|
||||
func doAfterImport() {
|
||||
|
||||
}
|
||||
func doAfterImport() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class PGPKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
|
||||
|
||||
@IBOutlet weak var pgpPublicKeyURLTextField: UITextField!
|
||||
@IBOutlet weak var pgpPrivateKeyURLTextField: UITextField!
|
||||
@IBOutlet var pgpPublicKeyURLTextField: UITextField!
|
||||
@IBOutlet var pgpPrivateKeyURLTextField: UITextField!
|
||||
|
||||
var pgpPrivateKeyURL: URL?
|
||||
var pgpPublicKeyURL: URL?
|
||||
|
|
@ -22,31 +21,31 @@ class PGPKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
|
|||
pgpPublicKeyURLTextField.text = Defaults.pgpPublicKeyURL?.absoluteString
|
||||
pgpPrivateKeyURLTextField.text = Defaults.pgpPrivateKeyURL?.absoluteString
|
||||
}
|
||||
|
||||
@IBAction func save(_ sender: Any) {
|
||||
|
||||
@IBAction
|
||||
func save(_: Any) {
|
||||
guard let publicKeyURLText = pgpPublicKeyURLTextField.text,
|
||||
let publicKeyURL = URL(string: publicKeyURLText),
|
||||
let privateKeyURLText = pgpPrivateKeyURLTextField.text,
|
||||
let privateKeyURL = URL(string: privateKeyURLText) else {
|
||||
Utils.alert(title: "CannotSavePgpKey".localize(), message: "SetPgpKeyUrlsFirst.".localize(), controller: self)
|
||||
return
|
||||
Utils.alert(title: "CannotSavePgpKey".localize(), message: "SetPgpKeyUrlsFirst.".localize(), controller: self)
|
||||
return
|
||||
}
|
||||
if privateKeyURL.scheme?.lowercased() == "http" || publicKeyURL.scheme?.lowercased() == "http" {
|
||||
Utils.alert(title: "HttpNotSecure".localize(), message: "ReallyUseHttp.".localize(), controller: self)
|
||||
}
|
||||
pgpPrivateKeyURL = privateKeyURL
|
||||
pgpPublicKeyURL = publicKeyURL
|
||||
self.saveImportedKeys()
|
||||
saveImportedKeys()
|
||||
}
|
||||
}
|
||||
|
||||
extension PGPKeyUrlImportTableViewController: PGPKeyImporter {
|
||||
|
||||
static let keySource = KeySource.url
|
||||
static let label = "DownloadFromUrl".localize()
|
||||
|
||||
func isReadyToUse() -> Bool {
|
||||
return validate(pgpKeyUrl: pgpPublicKeyURLTextField.text ?? "")
|
||||
validate(pgpKeyUrl: pgpPublicKeyURLTextField.text ?? "")
|
||||
&& validate(pgpKeyUrl: pgpPrivateKeyURLTextField.text ?? "")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import FavIcon
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
|
||||
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
|
||||
var passwordEntity: PasswordEntity?
|
||||
private var password: Password?
|
||||
private var passwordImage: UIImage?
|
||||
private var oneTimePasswordIndexPath : IndexPath?
|
||||
private var oneTimePasswordIndexPath: IndexPath?
|
||||
private var shouldPopCurrentView = false
|
||||
private let passwordStore = PasswordStore.shared
|
||||
private let keychain = AppKeychain.shared
|
||||
|
|
@ -52,7 +52,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
tableView.addGestureRecognizer(tapGesture)
|
||||
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.estimatedRowHeight = 52
|
||||
|
||||
|
|
@ -66,8 +66,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
let image = UIImage(data: imageData as Data)
|
||||
passwordImage = image
|
||||
}
|
||||
self.decryptThenShowPassword()
|
||||
self.setupOneTimePasswordAutoRefresh()
|
||||
decryptThenShowPassword()
|
||||
setupOneTimePasswordAutoRefresh()
|
||||
|
||||
// pop the current view because this password might be "discarded"
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
|
||||
|
|
@ -78,16 +78,17 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
if self.shouldPopCurrentView {
|
||||
if shouldPopCurrentView {
|
||||
let alert = UIAlertController(title: "Notice".localize(), message: "PreviousChangesDiscarded.".localize(), preferredStyle: UIAlertController.Style.alert)
|
||||
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 {
|
||||
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)
|
||||
})
|
||||
return
|
||||
|
|
@ -98,7 +99,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: self)
|
||||
self.password = try self.passwordStore.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||
self.showPassword()
|
||||
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
||||
} catch let AppError.PgpPrivateKeyNotFound(key) {
|
||||
DispatchQueue.main.async {
|
||||
// alert: cancel or try again
|
||||
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||
|
|
@ -115,7 +116,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
// alert: cancel or try again
|
||||
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: error.localizedDescription, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||
alert.addAction(UIAlertAction(title: "TryAgain".localize(), style: .default) {_ in
|
||||
alert.addAction(UIAlertAction(title: "TryAgain".localize(), style: .default) { _ in
|
||||
self.decryptThenShowPassword()
|
||||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
|
|
@ -141,14 +142,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
|
||||
private func setupOneTimePasswordAutoRefresh() {
|
||||
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
|
||||
guard let strongSelf = self,
|
||||
let otpType = strongSelf.password?.otpType,
|
||||
otpType != .none,
|
||||
let indexPath = strongSelf.oneTimePasswordIndexPath,
|
||||
let cell = strongSelf.tableView.cellForRow(at: indexPath) as? LabelTableViewCell else {
|
||||
return
|
||||
return
|
||||
}
|
||||
switch otpType {
|
||||
case .totp:
|
||||
|
|
@ -163,64 +164,67 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func pressEdit(_ sender: Any?) {
|
||||
@objc
|
||||
private func pressEdit(_: Any?) {
|
||||
performSegue(withIdentifier: "editPasswordSegue", sender: self)
|
||||
}
|
||||
|
||||
@objc private func setShouldPopCurrentView() {
|
||||
self.shouldPopCurrentView = true
|
||||
@objc
|
||||
private func setShouldPopCurrentView() {
|
||||
shouldPopCurrentView = true
|
||||
}
|
||||
|
||||
@IBAction private func cancelEditPassword(segue: UIStoryboardSegue) {
|
||||
@IBAction
|
||||
private func cancelEditPassword(segue _: UIStoryboardSegue) {}
|
||||
|
||||
}
|
||||
|
||||
@IBAction private func saveEditPassword(segue: UIStoryboardSegue) {
|
||||
if self.password!.changed != 0 {
|
||||
self.saveEditPassword(password: self.password!)
|
||||
@IBAction
|
||||
private func saveEditPassword(segue _: UIStoryboardSegue) {
|
||||
if password!.changed != 0 {
|
||||
saveEditPassword(password: password!)
|
||||
}
|
||||
}
|
||||
|
||||
private func saveEditPassword(password: Password, keyID: String? = nil) {
|
||||
SVProgressHUD.show(withStatus: "Saving".localize())
|
||||
do {
|
||||
self.passwordEntity = try self.passwordStore.edit(passwordEntity: self.passwordEntity!, password: password, keyID: keyID)
|
||||
self.setTableData()
|
||||
self.tableView.reloadData()
|
||||
passwordEntity = try passwordStore.edit(passwordEntity: passwordEntity!, password: password, keyID: keyID)
|
||||
setTableData()
|
||||
tableView.reloadData()
|
||||
SVProgressHUD.showSuccess(withStatus: "Success".localize())
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
} catch AppError.PgpPublicKeyNotFound(let key) {
|
||||
DispatchQueue.main.async {
|
||||
// alert: cancel or select keys
|
||||
SVProgressHUD.dismiss()
|
||||
let alert = UIAlertController(title: "Cannot Edit Password", message: AppError.PgpPublicKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||
let selectKey = UIAlertAction.selectKey(controller: self) { action in
|
||||
self.saveEditPassword(password: password, keyID: action.title)
|
||||
}
|
||||
alert.addAction(selectKey)
|
||||
} catch let AppError.PgpPublicKeyNotFound(key) {
|
||||
DispatchQueue.main.async {
|
||||
// alert: cancel or select keys
|
||||
SVProgressHUD.dismiss()
|
||||
let alert = UIAlertController(title: "Cannot Edit Password", message: AppError.PgpPublicKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||
let selectKey = UIAlertAction.selectKey(controller: self) { action in
|
||||
self.saveEditPassword(password: password, keyID: action.title)
|
||||
}
|
||||
alert.addAction(selectKey)
|
||||
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
return
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
Utils.alert(title: "Error".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
}
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
return
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
Utils.alert(title: "Error".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction private func deletePassword(segue: UIStoryboardSegue) {
|
||||
@IBAction
|
||||
private func deletePassword(segue _: UIStoryboardSegue) {
|
||||
do {
|
||||
try passwordStore.delete(passwordEntity: passwordEntity!)
|
||||
} catch {
|
||||
Utils.alert(title: "Error".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
let _ = navigationController?.popViewController(animated: true)
|
||||
_ = navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
private func setTableData() {
|
||||
self.tableData = Array<TableSection>()
|
||||
tableData = [TableSection]()
|
||||
|
||||
// name section
|
||||
var section = TableSection(type: .name)
|
||||
|
|
@ -239,7 +243,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
section.item.append(Constants.PASSWORD_KEYWORD => password.password)
|
||||
tableData.append(section)
|
||||
|
||||
|
||||
// addition section
|
||||
|
||||
// show one time password
|
||||
|
|
@ -254,7 +257,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
|
||||
// show additional information
|
||||
let filteredAdditionKeys = password.getFilteredAdditions()
|
||||
if filteredAdditionKeys.count > 0 {
|
||||
if !filteredAdditionKeys.isEmpty {
|
||||
section = TableSection(type: .addition, header: "Additions".localize())
|
||||
section.item.append(contentsOf: filteredAdditionKeys)
|
||||
tableData.append(section)
|
||||
|
|
@ -264,10 +267,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
section = TableSection(type: .misc)
|
||||
section.item.append(AdditionField(title: "ShowRaw".localize()))
|
||||
tableData.append(section)
|
||||
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
override func prepare(for segue: UIStoryboardSegue, sender _: Any?) {
|
||||
if segue.identifier == "editPasswordSegue" {
|
||||
if let controller = segue.destination as? UINavigationController {
|
||||
if let editController = controller.viewControllers.first as? EditPasswordTableViewController {
|
||||
|
|
@ -288,9 +290,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
if urlString.lowercased().hasPrefix("http://") {
|
||||
// try to replace http url to https url
|
||||
newUrlString = urlString.replacingOccurrences(of: "http://",
|
||||
with: "https://",
|
||||
options: .caseInsensitive,
|
||||
range: urlString.range(of: "http://"))
|
||||
with: "https://",
|
||||
options: .caseInsensitive,
|
||||
range: urlString.range(of: "http://"))
|
||||
} else if urlString.lowercased().hasPrefix("https://") {
|
||||
// do nothing here
|
||||
} else {
|
||||
|
|
@ -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 {
|
||||
let tapLocation = recognizer.location(in: self.tableView)
|
||||
if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
|
||||
if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) as? LabelTableViewCell {
|
||||
let tapLocation = recognizer.location(in: tableView)
|
||||
if let tapIndexPath = tableView.indexPathForRow(at: tapLocation) {
|
||||
if let tappedCell = tableView.cellForRow(at: tapIndexPath) as? LabelTableViewCell {
|
||||
tappedCell.becomeFirstResponder()
|
||||
let menuController = UIMenuController.shared
|
||||
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()) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@IBAction func back(segue:UIStoryboardSegue) {
|
||||
}
|
||||
@IBAction
|
||||
func back(segue _: UIStoryboardSegue) {}
|
||||
|
||||
func getNextHOTP() {
|
||||
guard password != nil, passwordEntity != nil, password?.otpType == .hotp else {
|
||||
DispatchQueue.main.async {
|
||||
Utils.alert(title: "Error".localize(), message: "GetNextPasswordOfNonHotp.".localize(), controller: self, completion: nil)
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// copy HOTP to pasteboard (will update counter)
|
||||
|
|
@ -356,7 +359,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
// commit the change of HOTP counter
|
||||
if password!.changed != 0 {
|
||||
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.dismiss(withDelay: 1)
|
||||
} catch {
|
||||
|
|
@ -384,19 +387,19 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
return from
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return tableData.count
|
||||
override func numberOfSections(in _: UITableView) -> Int {
|
||||
tableData.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return tableData[section].item.count
|
||||
override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
tableData[section].item.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let sectionIndex = indexPath.section
|
||||
let rowIndex = indexPath.row
|
||||
let tableDataItem = tableData[sectionIndex].item[rowIndex]
|
||||
switch(tableData[sectionIndex].type) {
|
||||
switch tableData[sectionIndex].type {
|
||||
case .name:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordDetailTitleTableViewCell", for: indexPath) as! PasswordDetailTitleTableViewCell
|
||||
if !Defaults.isHidePasswordImagesOn {
|
||||
|
|
@ -453,8 +456,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
detailTextLabel.text = "HiddenFields(%d)".localize(numberOfHiddenFields)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
return tableData[section].header
|
||||
override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
tableData[section].header
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
|
|
@ -464,7 +467,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
footerLabel.numberOfLines = 0
|
||||
footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||
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)
|
||||
view.addSubview(footerLabel)
|
||||
return view
|
||||
|
|
@ -472,15 +475,15 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
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(_:)) {
|
||||
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]
|
||||
switch(section.type) {
|
||||
switch section.type {
|
||||
case .main, .addition:
|
||||
return action == #selector(UIResponderStandardEditActions.copy(_:))
|
||||
default:
|
||||
|
|
@ -488,8 +491,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
override func tableView(_: UITableView, shouldShowMenuForRowAt _: IndexPath) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SafariServices
|
||||
import OneTimePassword
|
||||
import passKit
|
||||
import SafariServices
|
||||
import UIKit
|
||||
|
||||
enum PasswordEditorCellType: Equatable {
|
||||
case nameCell
|
||||
|
|
@ -30,18 +30,16 @@ enum PasswordEditorCellKey {
|
|||
}
|
||||
|
||||
protocol PasswordSettingSliderTableViewCellDelegate {
|
||||
|
||||
func generateAndCopyPassword()
|
||||
}
|
||||
|
||||
class PasswordEditorTableViewController: UITableViewController {
|
||||
|
||||
var tableData = [[Dictionary<PasswordEditorCellKey, Any>]]()
|
||||
var tableData = [[[PasswordEditorCellKey: Any]]]()
|
||||
var password: Password?
|
||||
|
||||
private var navigationItemTitle: String?
|
||||
|
||||
private var sectionHeaderTitles = ["Name".localize(), "Password".localize(), "Additions".localize(),""].map {$0.uppercased()}
|
||||
private var sectionHeaderTitles = ["Name".localize(), "Password".localize(), "Additions".localize(), ""].map { $0.uppercased() }
|
||||
private var sectionFooterTitles = ["", "", "UseKeyValueFormat.".localize(), ""]
|
||||
private let nameSection = 0
|
||||
private let passwordSection = 1
|
||||
|
|
@ -86,7 +84,7 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
passwordFlavorCell?.textLabel?.text = "PasswordGeneratorFlavor".localize()
|
||||
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.addTarget(self, action: #selector(flavorChanged), for: .valueChanged)
|
||||
passwordFlavorCell?.accessoryView = passwordFlavorSelector
|
||||
|
|
@ -126,11 +124,11 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
],
|
||||
[
|
||||
[.type: PasswordEditorCellType.scanQRCodeCell],
|
||||
]
|
||||
],
|
||||
]
|
||||
|
||||
if self.password != nil {
|
||||
tableData[additionsSection+1].append([.type: PasswordEditorCellType.deletePasswordCell])
|
||||
|
||||
if password != nil {
|
||||
tableData[additionsSection + 1].append([.type: PasswordEditorCellType.deletePasswordCell])
|
||||
}
|
||||
updateTableData(withRespectTo: passwordGenerator.flavor)
|
||||
}
|
||||
|
|
@ -206,11 +204,11 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
return 44
|
||||
override func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat {
|
||||
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 {
|
||||
case .passwordLengthCell, .passwordGroupsCell:
|
||||
return 42
|
||||
|
|
@ -221,11 +219,11 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
}
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return tableData.count
|
||||
override func numberOfSections(in _: UITableView) -> Int {
|
||||
tableData.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if section == passwordSection, hidePasswordSettings {
|
||||
// hide the password section, only the password should be shown
|
||||
return 1
|
||||
|
|
@ -234,27 +232,27 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
return sectionHeaderTitles[section]
|
||||
override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
sectionHeaderTitles[section]
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
return sectionFooterTitles[section]
|
||||
override func tableView(_: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
sectionFooterTitles[section]
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let selectedCell = tableView.cellForRow(at: indexPath)
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
|
||||
if selectedCell == deletePasswordCell {
|
||||
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)
|
||||
}))
|
||||
alert.addAction(UIAlertAction.cancel())
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
present(alert, animated: true, completion: nil)
|
||||
} 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 {
|
||||
return (data[.type] as? PasswordEditorCellType) == .some(.passwordGroupsCell)
|
||||
private func isPasswordDelimiterCellData(data: [PasswordEditorCellKey: Any]) -> Bool {
|
||||
(data[.type] as? PasswordEditorCellType) == .some(.passwordGroupsCell)
|
||||
}
|
||||
|
||||
@objc func flavorChanged(_ sender: UISegmentedControl) {
|
||||
@objc
|
||||
func flavorChanged(_ sender: UISegmentedControl) {
|
||||
let flavor = PasswordGeneratorFlavor.allCases[sender.selectedSegmentIndex]
|
||||
guard passwordGenerator.flavor != flavor else {
|
||||
return
|
||||
|
|
@ -327,7 +326,7 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
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 let navController = segue.destination as? UINavigationController {
|
||||
if let viewController = navController.topViewController as? QRScannerController {
|
||||
|
|
@ -362,8 +361,8 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
// the name field should be a valid url
|
||||
guard let path = name.stringByAddingPercentEncodingForRFC3986(),
|
||||
var passwordURL = URL(string: path) else {
|
||||
Utils.alert(title: "CannotSave".localize(), message: "PasswordNameInvalid.".localize(), controller: self, completion: nil)
|
||||
return false
|
||||
Utils.alert(title: "CannotSave".localize(), message: "PasswordNameInvalid.".localize(), controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
|
||||
// check whether we can parse the filename (be consistent with PasswordStore::addPasswordEntities)
|
||||
|
|
@ -382,20 +381,20 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
// MARK: - FillPasswordTableViewCellDelegate
|
||||
extension PasswordEditorTableViewController: FillPasswordTableViewCellDelegate {
|
||||
|
||||
extension PasswordEditorTableViewController: FillPasswordTableViewCellDelegate {
|
||||
// generate password, copy to pasteboard, and set the cell
|
||||
// check whether the current password looks like an OTP field
|
||||
func generateAndCopyPassword() {
|
||||
if let currentPassword = fillPasswordCell?.getContent(), Constants.isOtpRelated(line: currentPassword) {
|
||||
let alert = UIAlertController(title: "Overwrite?".localize(), message: "OverwriteOtpConfiguration?".localize(), preferredStyle: UIAlertController.Style.alert)
|
||||
alert.addAction(UIAlertAction(title: "Yes".localize(), style: UIAlertAction.Style.destructive, handler: {_ in
|
||||
alert.addAction(UIAlertAction(title: "Yes".localize(), style: UIAlertAction.Style.destructive, handler: { _ in
|
||||
self.generateAndCopyPasswordNoOtpCheck()
|
||||
}))
|
||||
alert.addAction(UIAlertAction.cancel())
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
present(alert, animated: true, completion: nil)
|
||||
} else {
|
||||
self.generateAndCopyPasswordNoOtpCheck()
|
||||
generateAndCopyPasswordNoOtpCheck()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -407,11 +406,12 @@ extension PasswordEditorTableViewController: FillPasswordTableViewCellDelegate {
|
|||
}
|
||||
|
||||
// MARK: - PasswordSettingSliderTableViewCellDelegate
|
||||
|
||||
extension PasswordEditorTableViewController: PasswordSettingSliderTableViewCellDelegate {}
|
||||
|
||||
// MARK: - QRScannerControllerDelegate
|
||||
extension PasswordEditorTableViewController: QRScannerControllerDelegate {
|
||||
|
||||
extension PasswordEditorTableViewController: QRScannerControllerDelegate {
|
||||
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
||||
if let url = URL(string: line), let _ = Token(url: url) {
|
||||
return (accept: true, message: "ValidTokenUrl".localize())
|
||||
|
|
@ -426,31 +426,31 @@ extension PasswordEditorTableViewController: QRScannerControllerDelegate {
|
|||
}
|
||||
|
||||
// MARK: - SFSafariViewControllerDelegate
|
||||
extension PasswordEditorTableViewController: SFSafariViewControllerDelegate {
|
||||
|
||||
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
||||
let copiedLinesSplit = UIPasteboard.general.string?.components(separatedBy: CharacterSet.whitespacesAndNewlines).filter({ !$0.isEmpty })
|
||||
extension PasswordEditorTableViewController: SFSafariViewControllerDelegate {
|
||||
func safariViewControllerDidFinish(_: SFSafariViewController) {
|
||||
let copiedLinesSplit = UIPasteboard.general.string?.components(separatedBy: CharacterSet.whitespacesAndNewlines).filter { !$0.isEmpty }
|
||||
if copiedLinesSplit?.count ?? 0 > 0 {
|
||||
let generatedPassword = copiedLinesSplit![0]
|
||||
let alert = UIAlertController(title: "WannaUseIt?".localize(), message: "", preferredStyle: UIAlertController.Style.alert)
|
||||
let message = NSMutableAttributedString(string: "\("SeemsLikeYouHaveCopiedSomething.".localize()) \("FirstStringIs:".localize())\n")
|
||||
message.append(Utils.attributedPassword(plainPassword: generatedPassword))
|
||||
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
|
||||
self.tableData[self.passwordSection][0][PasswordEditorCellKey.content] = generatedPassword
|
||||
// update cell manually, no need to call reloadData()
|
||||
self.fillPasswordCell?.setContent(content: generatedPassword)
|
||||
}))
|
||||
alert.addAction(UIAlertAction.cancel())
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITextFieldDelegate
|
||||
extension PasswordEditorTableViewController: UITextFieldDelegate {
|
||||
|
||||
extension PasswordEditorTableViewController: UITextFieldDelegate {
|
||||
// update tableData so to make sure reloadData() works correctly
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
if textField == nameCell?.contentTextField {
|
||||
|
|
@ -471,8 +471,8 @@ extension PasswordEditorTableViewController: UITextFieldDelegate {
|
|||
}
|
||||
|
||||
// MARK: - UITextViewDelegate
|
||||
extension PasswordEditorTableViewController: UITextViewDelegate {
|
||||
|
||||
extension PasswordEditorTableViewController: UITextViewDelegate {
|
||||
// update tableData so to make sure reloadData() works correctly
|
||||
func textViewDidEndEditing(_ textView: UITextView) {
|
||||
if textView == additionsCell?.contentTextView {
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
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 {
|
||||
private var passwordsTableEntries: [PasswordTableEntry] = []
|
||||
private var passwordsTableAllEntries: [PasswordTableEntry] = []
|
||||
private var parentPasswordEntity: PasswordEntity? = nil
|
||||
private var parentPasswordEntity: PasswordEntity?
|
||||
private let passwordStore = PasswordStore.shared
|
||||
private let keychain = AppKeychain.shared
|
||||
|
||||
|
|
@ -30,14 +30,12 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
private var gitCredential: GitCredential {
|
||||
get {
|
||||
switch Defaults.gitAuthenticationMethod {
|
||||
case .password:
|
||||
return GitCredential(credential: .http(userName: Defaults.gitUsername))
|
||||
case .key:
|
||||
let privateKey: String = AppKeychain.shared.get(for: SshKey.PRIVATE.getKeychainKey()) ?? ""
|
||||
return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey))
|
||||
}
|
||||
switch Defaults.gitAuthenticationMethod {
|
||||
case .password:
|
||||
return GitCredential(credential: .http(userName: Defaults.gitUsername))
|
||||
case .key:
|
||||
let privateKey: String = AppKeychain.shared.get(for: SshKey.PRIVATE.getKeychainKey()) ?? ""
|
||||
return GitCredential(credential: .ssh(userName: Defaults.gitUsername, privateKey: privateKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -49,11 +47,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
uiSearchController.searchBar.sizeToFit()
|
||||
return uiSearchController
|
||||
}()
|
||||
|
||||
private lazy var syncControl: UIRefreshControl = {
|
||||
let syncControl = UIRefreshControl()
|
||||
syncControl.addTarget(self, action: #selector(handleRefresh(_:)), for: UIControl.Event.valueChanged)
|
||||
return syncControl
|
||||
}()
|
||||
|
||||
private lazy var searchBarView: UIView? = {
|
||||
guard #available(iOS 11, *) else {
|
||||
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
|
||||
}()
|
||||
|
||||
private lazy var backUIBarButtonItem: UIBarButtonItem = {
|
||||
let backUIButton = UIButton(type: .system)
|
||||
if #available(iOS 13.0, *) {
|
||||
|
|
@ -116,28 +117,29 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
return transition
|
||||
}()
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet var tableView: UITableView!
|
||||
|
||||
private func initPasswordsTableEntries(parent: PasswordEntity?) {
|
||||
let passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||
let passwordAllEntities = passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||
passwordsTableAllEntries = passwordAllEntities.compactMap {
|
||||
PasswordTableEntry($0)
|
||||
}
|
||||
|
||||
|
||||
let passwordEntities = Defaults.isShowFolderOn ?
|
||||
self.passwordStore.fetchPasswordEntityCoreData(parent: parent) :
|
||||
passwordStore.fetchPasswordEntityCoreData(parent: parent) :
|
||||
passwordAllEntities
|
||||
passwordsTableEntries = passwordEntities.compactMap {
|
||||
PasswordTableEntry($0)
|
||||
}
|
||||
|
||||
|
||||
parentPasswordEntity = parent
|
||||
}
|
||||
|
||||
@IBAction func cancelAddPassword(segue: UIStoryboardSegue) {
|
||||
@IBAction
|
||||
func cancelAddPassword(segue _: UIStoryboardSegue) {}
|
||||
|
||||
}
|
||||
@IBAction func saveAddPassword(segue: UIStoryboardSegue) {
|
||||
@IBAction
|
||||
func saveAddPassword(segue: UIStoryboardSegue) {
|
||||
if let controller = segue.source as? AddPasswordTableViewController {
|
||||
addPassword(password: controller.password!)
|
||||
}
|
||||
|
|
@ -149,13 +151,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
SVProgressHUD.show(withStatus: "Saving".localize())
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
let _ = try self.passwordStore.add(password: password, keyID: keyID)
|
||||
_ = try self.passwordStore.add(password: password, keyID: keyID)
|
||||
DispatchQueue.main.async {
|
||||
// will trigger reloadTableView() by a notification
|
||||
SVProgressHUD.showSuccess(withStatus: "Done".localize())
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
} catch AppError.PgpPublicKeyNotFound(let key) {
|
||||
} catch let AppError.PgpPublicKeyNotFound(key) {
|
||||
DispatchQueue.main.async {
|
||||
// alert: cancel or select keys
|
||||
SVProgressHUD.dismiss()
|
||||
|
|
@ -190,15 +192,15 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
|
||||
if Defaults.isShowFolderOn {
|
||||
searchController.searchBar.scopeButtonTitles = SearchBarScope.allCases.map { $0.localizedName }
|
||||
searchController.searchBar.scopeButtonTitles = SearchBarScope.allCases.map(\.localizedName)
|
||||
} else {
|
||||
searchController.searchBar.scopeButtonTitles = nil
|
||||
}
|
||||
|
|
@ -251,7 +253,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
navigationItem.hidesSearchBarWhenScrolling = false
|
||||
} else {
|
||||
// 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!)
|
||||
}
|
||||
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)
|
||||
|
||||
// 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
|
||||
self.view.addGestureRecognizer(swipeRight)
|
||||
view.addGestureRecognizer(swipeRight)
|
||||
}
|
||||
|
||||
@objc func didTapNavigationBar(_ sender: UITapGestureRecognizer) {
|
||||
let location = sender.location(in: self.navigationController?.navigationBar)
|
||||
let hitView = self.navigationController?.navigationBar.hitTest(location, with: nil)
|
||||
@objc
|
||||
func didTapNavigationBar(_ sender: UITapGestureRecognizer) {
|
||||
let location = sender.location(in: navigationController?.navigationBar)
|
||||
let hitView = navigationController?.navigationBar.hitTest(location, with: nil)
|
||||
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 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 filteredPasswordsTableEntries = self.passwordsTableEntries.filter { entry in
|
||||
return !entry.synced
|
||||
!entry.synced
|
||||
}
|
||||
self.reloadTableView(data: filteredPasswordsTableEntries, label: .unsynced)
|
||||
}
|
||||
|
|
@ -299,7 +302,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
ac.addAction(unsyncedAction)
|
||||
ac.addAction(cancelAction)
|
||||
|
||||
self.present(ac, animated: true, completion: nil)
|
||||
present(ac, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
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
|
||||
self.navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer)
|
||||
navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer)
|
||||
|
||||
// This allows controlls in the navigation bar to continue receiving touches
|
||||
tapNavigationBarGestureRecognizer.cancelsTouchesInView = false
|
||||
|
|
@ -318,9 +321,9 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
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
|
||||
self.navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer)
|
||||
navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer)
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
|
|
@ -332,12 +335,12 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return sections.count
|
||||
func numberOfSections(in _: UITableView) -> Int {
|
||||
sections.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return sections[section].entries.count
|
||||
func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
sections[section].entries.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
|
@ -372,7 +375,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -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 {
|
||||
// swipe right -> swipe back
|
||||
if swipeGesture.direction == .right && parentPasswordEntity != nil {
|
||||
self.backAction(nil)
|
||||
if swipeGesture.direction == .right, parentPasswordEntity != nil {
|
||||
backAction(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func backAction(_ sender: Any?) {
|
||||
@objc
|
||||
func backAction(_: Any?) {
|
||||
guard Defaults.isShowFolderOn else { return }
|
||||
var anim: CATransition? = transitionFromLeft
|
||||
if parentPasswordEntity == nil {
|
||||
|
|
@ -408,13 +413,15 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
reloadTableView(parent: parentPasswordEntity?.parent, anim: anim)
|
||||
}
|
||||
|
||||
@objc func addPasswordAction(_ sender: Any?) {
|
||||
@objc
|
||||
func addPasswordAction(_: Any?) {
|
||||
if shouldPerformSegue(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 {
|
||||
let touchPoint = gesture.location(in: tableView)
|
||||
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
|
||||
|
|
@ -424,31 +431,31 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
private func hideSectionHeader() -> Bool {
|
||||
if passwordsTableEntries.count < hideSectionHeaderThreshold || self.searchController.isActive {
|
||||
if passwordsTableEntries.count < hideSectionHeaderThreshold || searchController.isActive {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
if hideSectionHeader() {
|
||||
return nil
|
||||
}
|
||||
return sections[section].title
|
||||
}
|
||||
|
||||
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
|
||||
func sectionIndexTitles(for _: UITableView) -> [String]? {
|
||||
if hideSectionHeader() {
|
||||
return nil
|
||||
}
|
||||
return sections.map { $0.title }
|
||||
return sections.map(\.title)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
|
||||
return index
|
||||
func tableView(_: UITableView, sectionForSectionIndexTitle _: String, at index: Int) -> Int {
|
||||
index
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||
func tableView(_: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||
decryptThenCopyPassword(from: indexPath)
|
||||
}
|
||||
|
||||
|
|
@ -460,7 +467,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
let passwordEntity = getPasswordEntry(by: indexPath).passwordEntity
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
SVProgressHUD.dismiss()
|
||||
self.decryptPassword(passwordEntity: passwordEntity)
|
||||
decryptPassword(passwordEntity: passwordEntity)
|
||||
}
|
||||
|
||||
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
|
||||
|
|
@ -476,7 +483,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
SVProgressHUD.showSuccess(withStatus: "PasswordCopiedToPasteboard.".localize())
|
||||
SVProgressHUD.dismiss(withDelay: 0.6)
|
||||
}
|
||||
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
||||
} catch let AppError.PgpPrivateKeyNotFound(key) {
|
||||
DispatchQueue.main.async {
|
||||
// alert: cancel or try again
|
||||
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||
|
|
@ -488,7 +495,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
self.present(alert, animated: true)
|
||||
}
|
||||
} catch {
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self)
|
||||
}
|
||||
|
|
@ -496,14 +503,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private func generateSections(item: [PasswordTableEntry]) {
|
||||
let collation = UILocalizedIndexedCollation.current()
|
||||
let sectionTitles = collation.sectionIndexTitles
|
||||
var newSections = [(title: String, entries: [PasswordTableEntry])]()
|
||||
|
||||
// initialize all sections
|
||||
for i in 0..<sectionTitles.count {
|
||||
for i in 0 ..< sectionTitles.count {
|
||||
newSections.append((title: sectionTitles[i], entries: [PasswordTableEntry]()))
|
||||
}
|
||||
|
||||
|
|
@ -514,21 +520,21 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
// sort each list and set sectionTitles
|
||||
for i in 0..<sectionTitles.count {
|
||||
for i in 0 ..< sectionTitles.count {
|
||||
let entriesToSort = newSections[i].entries
|
||||
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordTableEntry.title))
|
||||
newSections[i].entries = sortedEntries as! [PasswordTableEntry]
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if identifier == "showPasswordDetail" {
|
||||
guard PGPAgent.shared.isPrepared else {
|
||||
|
|
@ -540,7 +546,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
return false
|
||||
}
|
||||
} 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)
|
||||
return false
|
||||
}
|
||||
|
|
@ -551,7 +557,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "showPasswordDetail" {
|
||||
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
|
||||
viewController.passwordEntity = passwordEntity
|
||||
}
|
||||
|
|
@ -568,8 +574,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
func filterContentForSearchText(searchText: String, scope: SearchBarScope = .all) {
|
||||
var entries: [PasswordTableEntry] = scope == .all ? passwordsTableAllEntries : passwordsTableEntries
|
||||
if searchController.isActive && searchController.searchBar.text != "" {
|
||||
entries = entries.filter {$0.match(searchText)}
|
||||
if searchController.isActive, searchController.searchBar.text != "" {
|
||||
entries = entries.filter { $0.match(searchText) }
|
||||
}
|
||||
reloadTableView(data: entries)
|
||||
}
|
||||
|
|
@ -587,7 +593,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
if #available(iOS 11, *) {
|
||||
navigationController?.navigationBar.prefersLargeTitles = false
|
||||
}
|
||||
self.navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer)
|
||||
navigationController?.navigationBar.removeGestureRecognizer(tapNavigationBarGestureRecognizer)
|
||||
} else {
|
||||
navigationItem.leftBarButtonItem = nil
|
||||
switch label {
|
||||
|
|
@ -599,17 +605,17 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
if #available(iOS 11, *) {
|
||||
navigationController?.navigationBar.prefersLargeTitles = true
|
||||
}
|
||||
self.navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer)
|
||||
navigationController?.navigationBar.addGestureRecognizer(tapNavigationBarGestureRecognizer)
|
||||
}
|
||||
navigationItem.rightBarButtonItem = addPasswordUIBarButtonItem
|
||||
|
||||
// set the password table
|
||||
generateSections(item: data)
|
||||
if anim != nil {
|
||||
self.tableView.layer.add(anim!, forKey: "UITableViewReloadDataAnimationKey")
|
||||
tableView.layer.add(anim!, forKey: "UITableViewReloadDataAnimationKey")
|
||||
}
|
||||
tableView.reloadData()
|
||||
self.tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey")
|
||||
tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey")
|
||||
|
||||
// set the sync control title
|
||||
let atribbutedTitle = "LastSynced".localize() + ": \(lastSyncedTimeString())"
|
||||
|
|
@ -617,7 +623,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
private func lastSyncedTimeString() -> String {
|
||||
guard let date = self.passwordStore.lastSyncedTime else {
|
||||
guard let date = passwordStore.lastSyncedTime else {
|
||||
return "SyncAgain?".localize()
|
||||
}
|
||||
let formatter = DateFormatter()
|
||||
|
|
@ -631,7 +637,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
reloadTableView(data: passwordsTableEntries, label: label, anim: anim)
|
||||
}
|
||||
|
||||
@objc func actOnReloadTableViewRelatedNotification() {
|
||||
@objc
|
||||
func actOnReloadTableViewRelatedNotification() {
|
||||
DispatchQueue.main.async { [weak weakSelf = self] in
|
||||
guard let strongSelf = weakSelf else { return }
|
||||
// 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()
|
||||
}
|
||||
|
||||
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
||||
if viewController == self.navigationController {
|
||||
func tabBarController(_: UITabBarController, didSelect viewController: UIViewController) {
|
||||
if viewController == navigationController {
|
||||
let currentTime = Date().timeIntervalSince1970
|
||||
let duration = currentTime - self.tapTabBarTime
|
||||
self.tapTabBarTime = currentTime
|
||||
let duration = currentTime - tapTabBarTime
|
||||
tapTabBarTime = currentTime
|
||||
if duration < 0.35 {
|
||||
let topIndexPath = IndexPath(row: 0, section: 0)
|
||||
if tableView.numberOfSections > 0 {
|
||||
tableView.scrollToRow(at: topIndexPath, at: .bottom, animated: true)
|
||||
}
|
||||
self.tapTabBarTime = 0
|
||||
if tableView.numberOfSections > 0 {
|
||||
tableView.scrollToRow(at: topIndexPath, at: .bottom, animated: true)
|
||||
}
|
||||
tapTabBarTime = 0
|
||||
return
|
||||
}
|
||||
backAction(self)
|
||||
}
|
||||
}
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
|
||||
func searchBar(_: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
|
||||
// update the default search scope
|
||||
Defaults.searchDefault = SearchBarScope(rawValue: selectedScope)
|
||||
updateSearchResults(for: searchController)
|
||||
}
|
||||
|
||||
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
|
||||
func searchBarShouldBeginEditing(_: UISearchBar) -> Bool {
|
||||
// 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
|
||||
} else {
|
||||
searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.current.rawValue
|
||||
|
|
@ -678,7 +686,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
return true
|
||||
}
|
||||
|
||||
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
|
||||
func searchBarShouldEndEditing(_: UISearchBar) -> Bool {
|
||||
// set the default search scope to "current"
|
||||
searchController.searchBar.selectedScopeButtonIndex = SearchBarScope.current.rawValue
|
||||
updateSearchResults(for: searchController)
|
||||
|
|
@ -686,13 +694,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
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 {
|
||||
|
||||
func updateSearchResults(for searchController: UISearchController) {
|
||||
let scope = SearchBarScope(rawValue: searchController.searchBar.selectedScopeButtonIndex) ?? .all
|
||||
filterContentForSearchText(searchText: searchController.searchBar.text!, scope: scope)
|
||||
|
|
@ -700,8 +706,7 @@ extension PasswordsViewController: UISearchResultsUpdating {
|
|||
}
|
||||
|
||||
extension PasswordsViewController: CAAnimationDelegate {
|
||||
|
||||
func animationDidStart(_ anim: CAAnimation) {
|
||||
func animationDidStart(_: CAAnimation) {
|
||||
view.window?.backgroundColor = Colors.systemBackground
|
||||
view.layer.backgroundColor = Colors.systemBackground.cgColor
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
// Copyright © 2017 Yishi Lin. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import OneTimePassword
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
|
||||
protocol QRScannerControllerDelegate {
|
||||
func checkScannedOutput(line: String) -> (accept: Bool, message: String)
|
||||
|
|
@ -18,8 +18,7 @@ protocol QRScannerControllerDelegate {
|
|||
}
|
||||
|
||||
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
|
||||
|
||||
@IBOutlet weak var scannerOutput: UILabel!
|
||||
@IBOutlet var scannerOutput: UILabel!
|
||||
|
||||
var captureSession: AVCaptureSession?
|
||||
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
|
||||
|
|
@ -93,15 +92,12 @@ class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDeleg
|
|||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
|
||||
// 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,
|
||||
supportedCodeTypes.contains(metadataObj.type),
|
||||
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj) {
|
||||
|
||||
// draw a bounds on the found QR code
|
||||
qrCodeFrameView?.frame = barCodeObject.bounds
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class RawPasswordViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var rawPasswordTextView: UITextView!
|
||||
@IBOutlet var rawPasswordTextView: UITextView!
|
||||
var password: Password?
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
|
||||
|
||||
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
||||
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
|
||||
@IBOutlet var armorPrivateKeyTextView: UITextView!
|
||||
@IBOutlet var scanPrivateKeyCell: UITableViewCell!
|
||||
|
||||
var gitSSHPrivateKeyPassphrase: String?
|
||||
var armorPrivateKey: String?
|
||||
|
|
@ -22,32 +21,33 @@ class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
|
|||
var message = ""
|
||||
|
||||
func reset() {
|
||||
self.segments.removeAll()
|
||||
segments.removeAll()
|
||||
message = "LookingForStartingFrame.".localize()
|
||||
}
|
||||
|
||||
func addSegment(segment: String) -> (accept: Bool, message: String) {
|
||||
// Skip duplicated segments.
|
||||
guard segment != self.segments.last else {
|
||||
return (accept: false, message: self.message)
|
||||
guard segment != segments.last else {
|
||||
return (accept: false, message: message)
|
||||
}
|
||||
|
||||
|
||||
// Check whether we have found the first block.
|
||||
guard !self.segments.isEmpty || segment.contains("-----BEGIN") else {
|
||||
return (accept: false, message: self.message)
|
||||
guard !segments.isEmpty || segment.contains("-----BEGIN") else {
|
||||
return (accept: false, message: message)
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
self.message = "Done".localize()
|
||||
return (accept: true, message: self.message)
|
||||
message = "Done".localize()
|
||||
return (accept: true, message: message)
|
||||
} else {
|
||||
self.message = "ScannedQrCodes(%d)".localize(self.segments.count)
|
||||
return (accept: false, message: self.message)
|
||||
message = "ScannedQrCodes(%d)".localize(segments.count)
|
||||
return (accept: false, message: message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scanned = ScannedSSHKey()
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -59,12 +59,13 @@ class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
|
|||
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
|
||||
}
|
||||
|
||||
@IBAction func doneButtonTapped(_ sender: Any) {
|
||||
@IBAction
|
||||
func doneButtonTapped(_: Any) {
|
||||
armorPrivateKey = armorPrivateKeyTextView.text
|
||||
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 {
|
||||
// user pastes something, do the copy here again and clear the pasteboard in 45s
|
||||
SecurePasteboard.shared.copy(textToCopy: text)
|
||||
|
|
@ -76,23 +77,24 @@ class SSHKeyArmorImportTableViewController: AutoCellHeightUITableViewController,
|
|||
let selectedCell = tableView.cellForRow(at: indexPath)
|
||||
if selectedCell == scanPrivateKeyCell {
|
||||
scanned.reset()
|
||||
self.performSegue(withIdentifier: "showSSHScannerSegue", sender: self)
|
||||
performSegue(withIdentifier: "showSSHScannerSegue", sender: self)
|
||||
}
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - QRScannerControllerDelegate Methods
|
||||
|
||||
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
||||
return scanned.addSegment(segment: line)
|
||||
}
|
||||
|
||||
// MARK: - QRScannerControllerDelegate Methods
|
||||
func handleScannedOutput(line: String) {
|
||||
|
||||
func handleScannedOutput(line _: String) {
|
||||
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 let navController = segue.destination as? UINavigationController {
|
||||
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 {
|
||||
|
||||
static let keySource = KeySource.armor
|
||||
static let label = "AsciiArmorEncryptedKey".localize()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ import passKit
|
|||
import SVProgressHUD
|
||||
|
||||
class SSHKeyFileImportTableViewController: AutoCellHeightUITableViewController {
|
||||
@IBOutlet var sshPrivateKeyFile: UITableViewCell!
|
||||
|
||||
@IBOutlet weak var sshPrivateKeyFile: UITableViewCell!
|
||||
private var privateKey: String?
|
||||
|
||||
private var privateKey: String? = nil
|
||||
|
||||
@IBAction func doneButtonTapped(_ sender: Any) {
|
||||
@IBAction
|
||||
func doneButtonTapped(_: Any) {
|
||||
performSegue(withIdentifier: "importSSHKeySegue", sender: self)
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +35,6 @@ class SSHKeyFileImportTableViewController: AutoCellHeightUITableViewController {
|
|||
}
|
||||
|
||||
extension SSHKeyFileImportTableViewController: UIDocumentPickerDelegate {
|
||||
|
||||
func documentPicker(_: UIDocumentPickerViewController, didPickDocumentsAt url: [URL]) {
|
||||
guard let url = url.first else {
|
||||
return
|
||||
|
|
@ -61,7 +60,6 @@ extension SSHKeyFileImportTableViewController: UIDocumentPickerDelegate {
|
|||
}
|
||||
|
||||
extension SSHKeyFileImportTableViewController: KeyImporter {
|
||||
|
||||
static let keySource = KeySource.file
|
||||
static let label = "LoadFromFiles".localize()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
|
||||
class SSHKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
|
||||
|
||||
@IBOutlet weak var privateKeyURLTextField: UITextField!
|
||||
@IBOutlet var privateKeyURLTextField: UITextField!
|
||||
|
||||
var sshPrivateKeyURL: URL?
|
||||
|
||||
|
|
@ -20,11 +19,12 @@ class SSHKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
|
|||
privateKeyURLTextField.text = Defaults.gitSSHPrivateKeyURL?.absoluteString
|
||||
}
|
||||
|
||||
@IBAction func doneButtonTapped(_ sender: UIButton) {
|
||||
@IBAction
|
||||
func doneButtonTapped(_: UIButton) {
|
||||
guard let text = privateKeyURLTextField.text,
|
||||
let privateKeyURL = URL(string: text) else {
|
||||
Utils.alert(title: "CannotSave".localize(), message: "SetPrivateKeyUrl.".localize(), controller: self)
|
||||
return
|
||||
Utils.alert(title: "CannotSave".localize(), message: "SetPrivateKeyUrl.".localize(), controller: self)
|
||||
return
|
||||
}
|
||||
|
||||
if privateKeyURL.scheme?.lowercased() == "http" {
|
||||
|
|
@ -41,7 +41,6 @@ class SSHKeyUrlImportTableViewController: AutoCellHeightUITableViewController {
|
|||
}
|
||||
|
||||
extension SSHKeyUrlImportTableViewController: KeyImporter {
|
||||
|
||||
static let keySource = KeySource.url
|
||||
static let label = "DownloadFromUrl".localize()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,15 +10,16 @@ import UIKit
|
|||
|
||||
class SettingsSplitViewController: UISplitViewController, UISplitViewControllerDelegate {
|
||||
override func viewDidLoad() {
|
||||
self.delegate = self
|
||||
self.preferredDisplayMode = .allVisible
|
||||
delegate = self
|
||||
preferredDisplayMode = .allVisible
|
||||
}
|
||||
|
||||
func splitViewController(
|
||||
_ splitViewController: UISplitViewController,
|
||||
collapseSecondary secondaryViewController: UIViewController,
|
||||
onto primaryViewController: UIViewController) -> Bool {
|
||||
_: UISplitViewController,
|
||||
collapseSecondary _: UIViewController,
|
||||
onto _: UIViewController
|
||||
) -> Bool {
|
||||
// Return true to prevent UIKit from applying its default behavior
|
||||
return true
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,26 +6,27 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
import CoreData
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
|
||||
class SettingsTableViewController: UITableViewController, UITabBarControllerDelegate {
|
||||
@IBOutlet weak var pgpKeyTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var passcodeTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell!
|
||||
@IBOutlet var pgpKeyTableViewCell: UITableViewCell!
|
||||
@IBOutlet var passcodeTableViewCell: UITableViewCell!
|
||||
@IBOutlet var passwordRepositoryTableViewCell: UITableViewCell!
|
||||
var setPasscodeLockAlert: UIAlertController?
|
||||
|
||||
let passwordStore = PasswordStore.shared
|
||||
let keychain = AppKeychain.shared
|
||||
var passcodeLock = PasscodeLock.shared
|
||||
|
||||
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
||||
func tabBarController(_: UITabBarController, didSelect _: UIViewController) {
|
||||
navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
||||
@IBAction func savePGPKey(segue: UIStoryboardSegue) {
|
||||
@IBAction
|
||||
func savePGPKey(segue: UIStoryboardSegue) {
|
||||
guard let sourceController = segue.source as? PGPKeyImporter, sourceController.isReadyToUse() else {
|
||||
return
|
||||
}
|
||||
|
|
@ -58,19 +59,20 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
}
|
||||
}
|
||||
|
||||
@IBAction func saveGitServerSetting(segue: UIStoryboardSegue) {
|
||||
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults.gitURL.host
|
||||
@IBAction
|
||||
func saveGitServerSetting(segue _: UIStoryboardSegue) {
|
||||
passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults.gitURL.host
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
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()
|
||||
setPasscodeLockCell()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
override func viewWillAppear(_: Bool) {
|
||||
super.viewWillAppear(true)
|
||||
tabBarController!.delegate = self
|
||||
setPasswordRepositoryTableViewCellDetailText()
|
||||
|
|
@ -78,9 +80,9 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
|
||||
private func setPasscodeLockCell() {
|
||||
if passcodeLock.hasPasscode {
|
||||
self.passcodeTableViewCell.detailTextLabel?.text = "On".localize()
|
||||
passcodeTableViewCell.detailTextLabel?.text = "On".localize()
|
||||
} else {
|
||||
self.passcodeTableViewCell.detailTextLabel?.text = "Off".localize()
|
||||
passcodeTableViewCell.detailTextLabel?.text = "Off".localize()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +109,8 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
passwordRepositoryTableViewCell.detailTextLabel?.text = host
|
||||
}
|
||||
|
||||
@objc func actOnPasswordStoreErasedNotification() {
|
||||
@objc
|
||||
func actOnPasswordStoreErasedNotification() {
|
||||
setPGPKeyTableViewCellDetailText()
|
||||
setPasswordRepositoryTableViewCellDetailText()
|
||||
setPasscodeLockCell()
|
||||
|
|
@ -180,9 +183,8 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
let passcodeRemoveViewController = PasscodeLockViewController()
|
||||
|
||||
|
||||
let removePasscodeAction = UIAlertAction(title: "RemovePasscode".localize(), style: .destructive) { [weak self] _ in
|
||||
passcodeRemoveViewController.successCallback = {
|
||||
passcodeRemoveViewController.successCallback = {
|
||||
self?.passcodeLock.delete()
|
||||
self?.setPasscodeLockCell()
|
||||
}
|
||||
|
|
@ -198,10 +200,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
optionMenu.addAction(UIAlertAction.cancel())
|
||||
optionMenu.popoverPresentationController?.sourceView = passcodeTableViewCell
|
||||
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
|
||||
if let setPasscodeLockAlert = self.setPasscodeLockAlert,
|
||||
let setPasscodeLockAlertTextFields0 = setPasscodeLockAlert.textFields?[0],
|
||||
|
|
@ -218,25 +221,25 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
func setPasscodeLock() {
|
||||
// prepare the alert for setting the passcode
|
||||
setPasscodeLockAlert = UIAlertController(title: "SetPasscode".localize(), message: "FillInAppPasscode.".localize(), preferredStyle: .alert)
|
||||
setPasscodeLockAlert?.addTextField(configurationHandler: {(_ textField: UITextField) -> Void in
|
||||
setPasscodeLockAlert?.addTextField(configurationHandler: { (_ textField: UITextField) -> Void in
|
||||
textField.placeholder = "Passcode".localize()
|
||||
textField.isSecureTextEntry = true
|
||||
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange(_:)), for: UIControl.Event.editingChanged)
|
||||
})
|
||||
setPasscodeLockAlert?.addTextField(configurationHandler: {(_ textField: UITextField) -> Void in
|
||||
setPasscodeLockAlert?.addTextField(configurationHandler: { (_ textField: UITextField) -> Void in
|
||||
textField.placeholder = "PasswordConfirmation".localize()
|
||||
textField.isSecureTextEntry = true
|
||||
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange(_:)), for: UIControl.Event.editingChanged)
|
||||
})
|
||||
|
||||
// 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!
|
||||
self.passcodeLock.save(passcode: passcode)
|
||||
// refresh the passcode lock cell ("On")
|
||||
self.setPasscodeLockCell()
|
||||
}
|
||||
saveAction.isEnabled = false // disable the Save button by default
|
||||
saveAction.isEnabled = false // disable the Save button by default
|
||||
|
||||
// cancel action
|
||||
let cancelAction = UIAlertAction.cancel()
|
||||
|
|
@ -244,17 +247,16 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
|||
// present
|
||||
setPasscodeLockAlert?.addAction(saveAction)
|
||||
setPasscodeLockAlert?.addAction(cancelAction)
|
||||
self.present(setPasscodeLockAlert!, animated: true, completion: nil)
|
||||
present(setPasscodeLockAlert!, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsTableViewController: PGPKeyImporter {
|
||||
|
||||
static let keySource = KeySource.itunes
|
||||
static let label = "ITunesFileSharing".localize()
|
||||
|
||||
func isReadyToUse() -> Bool {
|
||||
return KeyFileManager.PublicPgp.doesKeyFileExist() && KeyFileManager.PrivatePgp.doesKeyFileExist()
|
||||
KeyFileManager.PublicPgp.doesKeyFileExist() && KeyFileManager.PrivatePgp.doesKeyFileExist()
|
||||
}
|
||||
|
||||
func importKeys() throws {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class SpecialThanksTableViewController: BasicStaticTableViewController {
|
|||
"https://icons8.com"],
|
||||
["FlatIcon",
|
||||
"https://www.flaticon.com"],
|
||||
]
|
||||
]
|
||||
|
||||
override func viewDidLoad() {
|
||||
tableData.append([])
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
|
||||
public func requestGitCredentialPassword(credential: GitCredential.Credential,
|
||||
lastPassword: String?,
|
||||
controller: UIViewController) -> String? {
|
||||
lastPassword: String?,
|
||||
controller: UIViewController) -> String? {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var password: String?
|
||||
let message: String = {
|
||||
|
|
@ -27,21 +27,21 @@ public func requestGitCredentialPassword(credential: GitCredential.Credential,
|
|||
DispatchQueue.main.async {
|
||||
SVProgressHUD.dismiss()
|
||||
let alert = UIAlertController(title: "Password".localize(), message: message, preferredStyle: .alert)
|
||||
alert.addTextField() {
|
||||
alert.addTextField {
|
||||
$0.text = lastPassword ?? ""
|
||||
$0.isSecureTextEntry = true
|
||||
}
|
||||
alert.addAction(UIAlertAction.ok() { _ in
|
||||
alert.addAction(UIAlertAction.ok { _ in
|
||||
password = alert.textFields?.first?.text
|
||||
sem.signal()
|
||||
})
|
||||
alert.addAction(UIAlertAction.cancel() { _ in
|
||||
alert.addAction(UIAlertAction.cancel { _ in
|
||||
password = nil
|
||||
sem.signal()
|
||||
})
|
||||
controller.present(alert, animated: true)
|
||||
}
|
||||
|
||||
let _ = sem.wait(timeout: .distantFuture)
|
||||
_ = sem.wait(timeout: .distantFuture)
|
||||
return password
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class SecurePasteboard {
|
|||
// exit the existing background task, if any
|
||||
if backgroundTaskID != UIBackgroundTaskIdentifier.invalid {
|
||||
UIApplication.shared.endBackgroundTask(UIBackgroundTaskIdentifier.invalid)
|
||||
self.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
|
||||
backgroundTaskID = UIBackgroundTaskIdentifier.invalid
|
||||
}
|
||||
|
||||
backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in
|
||||
|
|
@ -40,5 +40,4 @@ class SecurePasteboard {
|
|||
self?.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
|
||||
extension Utils {
|
||||
static func alert(title: String, message: String, controller: UIViewController, handler: ((UIAlertAction) -> Void)? = nil, completion: (() -> Void)? = nil) {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
protocol FillPasswordTableViewCellDelegate {
|
||||
func generateAndCopyPassword()
|
||||
|
|
@ -15,12 +15,11 @@ protocol FillPasswordTableViewCellDelegate {
|
|||
}
|
||||
|
||||
class FillPasswordTableViewCell: UITableViewCell, ContentProvider {
|
||||
|
||||
@IBOutlet weak var contentTextField: UITextField!
|
||||
@IBOutlet var contentTextField: UITextField!
|
||||
var delegate: FillPasswordTableViewCellDelegate?
|
||||
|
||||
@IBOutlet weak var settingButton: UIButton!
|
||||
@IBOutlet weak var generateButton: UIButton!
|
||||
@IBOutlet var settingButton: UIButton!
|
||||
@IBOutlet var generateButton: UIButton!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
|
@ -32,21 +31,24 @@ class FillPasswordTableViewCell: UITableViewCell, ContentProvider {
|
|||
generateButton.imageView?.contentMode = .scaleAspectFit
|
||||
}
|
||||
|
||||
@IBAction func generatePassword(_ sender: UIButton) {
|
||||
self.delegate?.generateAndCopyPassword()
|
||||
@IBAction
|
||||
func generatePassword(_: UIButton) {
|
||||
delegate?.generateAndCopyPassword()
|
||||
}
|
||||
|
||||
@IBAction func showHidePasswordSettings() {
|
||||
self.delegate?.showHidePasswordSettings()
|
||||
@IBAction
|
||||
func showHidePasswordSettings() {
|
||||
delegate?.showHidePasswordSettings()
|
||||
}
|
||||
|
||||
// re-color
|
||||
@IBAction func textFieldDidChange(_ sender: UITextField) {
|
||||
@IBAction
|
||||
func textFieldDidChange(_ sender: UITextField) {
|
||||
contentTextField.attributedText = Utils.attributedPassword(plainPassword: sender.text ?? "")
|
||||
}
|
||||
|
||||
func getContent() -> String? {
|
||||
return contentTextField.attributedText?.string
|
||||
contentTextField.attributedText?.string
|
||||
}
|
||||
|
||||
func setContent(content: String?) {
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
import SVProgressHUD
|
||||
import UIKit
|
||||
|
||||
struct LabelTableViewCellData {
|
||||
var title: String
|
||||
|
|
@ -16,9 +16,8 @@ struct LabelTableViewCellData {
|
|||
}
|
||||
|
||||
class LabelTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var contentLabel: UILabel!
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet var contentLabel: UILabel!
|
||||
@IBOutlet var titleLabel: UILabel!
|
||||
|
||||
private enum CellType {
|
||||
case password, URL, HOTP, other
|
||||
|
|
@ -27,7 +26,7 @@ class LabelTableViewCell: UITableViewCell {
|
|||
private var type = CellType.other
|
||||
private var isReveal = false
|
||||
|
||||
weak var delegatePasswordTableView : PasswordDetailTableViewController?
|
||||
weak var delegatePasswordTableView: PasswordDetailTableViewController?
|
||||
|
||||
private var passwordDisplayButton: UIButton?
|
||||
private var buttons: UIView?
|
||||
|
|
@ -74,12 +73,10 @@ class LabelTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
get {
|
||||
return true
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
override func canPerformAction(_ action: Selector, withSender _: Any?) -> Bool {
|
||||
switch type {
|
||||
case .password:
|
||||
if isReveal {
|
||||
|
|
@ -100,11 +97,12 @@ class LabelTableViewCell: UITableViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
override func copy(_ sender: Any?) {
|
||||
override func copy(_: Any?) {
|
||||
SecurePasteboard.shared.copy(textToCopy: cellData?.content)
|
||||
}
|
||||
|
||||
@objc func revealPassword(_ sender: Any?) {
|
||||
@objc
|
||||
func revealPassword(_: Any?) {
|
||||
let plainPassword = cellData?.content ?? ""
|
||||
if type == .password {
|
||||
contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
|
||||
|
|
@ -115,7 +113,8 @@ class LabelTableViewCell: UITableViewCell {
|
|||
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal)
|
||||
}
|
||||
|
||||
@objc func concealPassword(_ sender: Any?) {
|
||||
@objc
|
||||
func concealPassword(_: Any?) {
|
||||
if type == .password {
|
||||
if cellData?.content.isEmpty == false {
|
||||
contentLabel.text = Globals.passwordDots
|
||||
|
|
@ -130,7 +129,8 @@ class LabelTableViewCell: UITableViewCell {
|
|||
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
|
||||
}
|
||||
|
||||
@objc func reversePasswordDisplay(_ sender: Any?) {
|
||||
@objc
|
||||
func reversePasswordDisplay(_ sender: Any?) {
|
||||
if isReveal {
|
||||
// conceal
|
||||
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
|
||||
delegatePasswordTableView!.openLink(to: cellData?.content)
|
||||
}
|
||||
|
||||
@objc func getNextHOTP(_ sender: Any?) {
|
||||
@objc
|
||||
func getNextHOTP(_: Any?) {
|
||||
// if isHOTPCell, passwordTableView should not be nil
|
||||
delegatePasswordTableView!.getNextHOTP()
|
||||
}
|
||||
|
||||
private func updateButtons() {
|
||||
// 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)
|
||||
|
||||
// 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!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
|
||||
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)
|
||||
buttons = passwordDisplayButton
|
||||
}
|
||||
|
|
@ -177,7 +179,7 @@ class LabelTableViewCell: UITableViewCell {
|
|||
nextButton.frame = CGRect(x: 0, y: 0, width: width, height: height)
|
||||
nextButton.setImage(#imageLiteral(resourceName: "Refresh"), for: .normal)
|
||||
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)
|
||||
|
||||
// password button
|
||||
|
|
@ -186,7 +188,7 @@ class LabelTableViewCell: UITableViewCell {
|
|||
|
||||
passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
|
||||
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)
|
||||
|
||||
buttons = UIView()
|
||||
|
|
@ -197,6 +199,6 @@ class LabelTableViewCell: UITableViewCell {
|
|||
passwordDisplayButton = nil
|
||||
buttons = nil
|
||||
}
|
||||
self.accessoryView = buttons
|
||||
accessoryView = buttons
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@
|
|||
import UIKit
|
||||
|
||||
class PasswordDetailTitleTableViewCell: UITableViewCell {
|
||||
@IBOutlet weak var categoryLabel: UILabel!
|
||||
@IBOutlet weak var nameLabel: UILabel!
|
||||
@IBOutlet weak var passwordImageImageView: UIImageView!
|
||||
@IBOutlet var categoryLabel: UILabel!
|
||||
@IBOutlet var nameLabel: UILabel!
|
||||
@IBOutlet var passwordImageImageView: UIImageView!
|
||||
@IBOutlet var labelImageConstraint: NSLayoutConstraint!
|
||||
@IBOutlet var labelCellConstraint: NSLayoutConstraint!
|
||||
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
}
|
||||
|
|
@ -25,5 +24,4 @@ class PasswordDetailTitleTableViewCell: UITableViewCell {
|
|||
|
||||
// Configure the view for the selected state
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import passKit
|
|||
import UIKit
|
||||
|
||||
class SliderTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet var titleLabel: UILabel!
|
||||
@IBOutlet var valueLabel: UILabel!
|
||||
@IBOutlet var slider: UISlider!
|
||||
|
|
@ -20,7 +19,8 @@ class SliderTableViewCell: UITableViewCell {
|
|||
|
||||
private var delegate: PasswordSettingSliderTableViewCellDelegate!
|
||||
|
||||
@IBAction func handleSliderValueChange(_ sender: UISlider) {
|
||||
@IBAction
|
||||
func handleSliderValueChange(_ sender: UISlider) {
|
||||
let newRoundedValue = Int(sender.value)
|
||||
// Proceed only if the rounded value gets updated.
|
||||
guard checker(newRoundedValue) else {
|
||||
|
|
@ -67,9 +67,8 @@ class SliderTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
extension SliderTableViewCell: ContentProvider {
|
||||
|
||||
func getContent() -> String? {
|
||||
return nil
|
||||
nil
|
||||
}
|
||||
|
||||
func setContent(content _: String?) {}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import passKit
|
|||
import UIKit
|
||||
|
||||
class SwitchTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet var titleLabel: UILabel!
|
||||
@IBOutlet var controlSwitch: UISwitch!
|
||||
|
||||
|
|
@ -18,7 +17,8 @@ class SwitchTableViewCell: UITableViewCell {
|
|||
|
||||
private var delegate: PasswordSettingSliderTableViewCellDelegate!
|
||||
|
||||
@IBAction func switchValueChanged(_: Any) {
|
||||
@IBAction
|
||||
func switchValueChanged(_: Any) {
|
||||
updater(controlSwitch.isOn)
|
||||
delegate.generateAndCopyPassword()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@
|
|||
import UIKit
|
||||
|
||||
class TextFieldTableViewCell: UITableViewCell, ContentProvider {
|
||||
|
||||
@IBOutlet weak var contentTextField: UITextField!
|
||||
@IBOutlet var contentTextField: UITextField!
|
||||
|
||||
func getContent() -> String? {
|
||||
return contentTextField.text
|
||||
contentTextField.text
|
||||
}
|
||||
|
||||
func setContent(content: String?) {
|
||||
|
|
|
|||
|
|
@ -9,17 +9,16 @@
|
|||
import UIKit
|
||||
|
||||
class TextViewTableViewCell: UITableViewCell, ContentProvider {
|
||||
@IBOutlet var contentTextView: UITextView!
|
||||
|
||||
@IBOutlet weak var contentTextView: UITextView!
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
self.contentTextView.textContainer.lineFragmentPadding = 0
|
||||
self.contentTextView.textContainerInset = .zero
|
||||
contentTextView.textContainer.lineFragmentPadding = 0
|
||||
contentTextView.textContainerInset = .zero
|
||||
}
|
||||
|
||||
func getContent() -> String? {
|
||||
return contentTextView.text
|
||||
contentTextView.text
|
||||
}
|
||||
|
||||
func setContent(content: String?) {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
Copyright © 2019 Bob Sun. All rights reserved.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class UICodeHighlightingLabel: UILocalizedLabel {
|
||||
|
||||
private static let CODE_ATTRIBUTES: [NSAttributedString.Key: Any] = [.font: UIFont(name: "Menlo-Regular", size: 12)!]
|
||||
private static let ATTRIBUTED_NEWLINE = NSAttributedString(string: "\n")
|
||||
|
||||
|
|
@ -43,4 +42,3 @@ class UICodeHighlightingLabel: UILocalizedLabel {
|
|||
return formattedText
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
Copyright © 2019 Bob Sun. All rights reserved.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class UILocalizedLabel: UILabel {
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
text = text?.localize()
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import AuthenticationServices
|
|||
import passKit
|
||||
|
||||
class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
|
||||
@IBOutlet weak var searchBar: UISearchBar!
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet var searchBar: UISearchBar!
|
||||
@IBOutlet var tableView: UITableView!
|
||||
|
||||
private let passwordStore = PasswordStore.shared
|
||||
private let keychain = AppKeychain.shared
|
||||
|
|
@ -26,13 +26,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
|||
}()
|
||||
|
||||
/*
|
||||
Prepare your UI to list available credentials for the user to choose from. The items in
|
||||
'serviceIdentifiers' describe the service the user is logging in to, so your extension can
|
||||
prioritize the most relevant credentials in the list.
|
||||
*/
|
||||
Prepare your UI to list available credentials for the user to choose from. The items in
|
||||
'serviceIdentifiers' describe the service the user is logging in to, so your extension can
|
||||
prioritize the most relevant credentials in the list.
|
||||
*/
|
||||
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
|
||||
// clean up the search bar
|
||||
guard serviceIdentifiers.count > 0 else {
|
||||
guard !serviceIdentifiers.isEmpty else {
|
||||
searchBar.text = ""
|
||||
searchBar.becomeFirstResponder()
|
||||
searchBarSearchButtonClicked(searchBar)
|
||||
|
|
@ -41,7 +41,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
|||
|
||||
// get the domain
|
||||
var identifier = serviceIdentifiers[0].identifier
|
||||
if !identifier.hasPrefix("http://") && !identifier.hasPrefix("https://") {
|
||||
if !identifier.hasPrefix("http://"), !identifier.hasPrefix("https://") {
|
||||
identifier = "http://" + identifier
|
||||
}
|
||||
let url = URL(string: identifier)?.host ?? ""
|
||||
|
|
@ -53,37 +53,38 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
|||
}
|
||||
|
||||
/*
|
||||
Implement this method if your extension support
|
||||
s showing credentials in the QuickType bar.
|
||||
When the user selects a credential from your app, this method will be called with the
|
||||
ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore.
|
||||
Provide the password by completing the extension request with the associated ASPasswordCredential.
|
||||
If using the credential would require showing custom UI for authenticating the user, cancel
|
||||
the request with error code ASExtensionError.userInteractionRequired.
|
||||
Implement this method if your extension support
|
||||
s showing credentials in the QuickType bar.
|
||||
When the user selects a credential from your app, this method will be called with the
|
||||
ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore.
|
||||
Provide the password by completing the extension request with the associated ASPasswordCredential.
|
||||
If using the credential would require showing custom UI for authenticating the user, cancel
|
||||
the request with error code ASExtensionError.userInteractionRequired.
|
||||
|
||||
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
|
||||
let databaseIsUnlocked = true
|
||||
if (databaseIsUnlocked) {
|
||||
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
|
||||
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
|
||||
} else {
|
||||
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
|
||||
}
|
||||
}
|
||||
*/
|
||||
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
|
||||
let databaseIsUnlocked = true
|
||||
if (databaseIsUnlocked) {
|
||||
let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234")
|
||||
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil)
|
||||
} else {
|
||||
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with
|
||||
ASExtensionError.userInteractionRequired. In this case, the system may present your extension's
|
||||
UI and call this method. Show appropriate UI for authenticating the user then provide the password
|
||||
by completing the extension request with the associated ASPasswordCredential.
|
||||
Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with
|
||||
ASExtensionError.userInteractionRequired. In this case, the system may present your extension's
|
||||
UI and call this method. Show appropriate UI for authenticating the user then provide the password
|
||||
by completing the extension request with the associated ASPasswordCredential.
|
||||
|
||||
override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
|
||||
}
|
||||
*/
|
||||
override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
|
||||
}
|
||||
*/
|
||||
|
||||
@IBAction func cancel(_ sender: AnyObject?) {
|
||||
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue))
|
||||
@IBAction
|
||||
func cancel(_: AnyObject?) {
|
||||
extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue))
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
|
@ -106,8 +107,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
|||
|
||||
private func initPasswordsTableEntries() {
|
||||
filteredPasswordsTableEntries.removeAll()
|
||||
|
||||
let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||
|
||||
let passwordEntities = passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||
passwordsTableEntries = passwordEntities.compactMap {
|
||||
PasswordTableEntry($0)
|
||||
}
|
||||
|
|
@ -129,7 +130,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
||||
guard PGPAgent.shared.isPrepared else {
|
||||
|
|
@ -139,7 +140,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
|||
|
||||
let passwordEntity = entry.passwordEntity
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
self.decryptPassword(passwordEntity: passwordEntity)
|
||||
decryptPassword(passwordEntity: passwordEntity)
|
||||
}
|
||||
|
||||
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
|
||||
|
|
@ -154,7 +155,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
|||
let passwordCredential = ASPasswordCredential(user: username, password: password)
|
||||
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential)
|
||||
}
|
||||
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
||||
} catch let AppError.PgpPrivateKeyNotFound(key) {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.cancelAndPopView(controller: self))
|
||||
|
|
@ -173,34 +174,34 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
|||
}
|
||||
}
|
||||
|
||||
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
return 1
|
||||
func numberOfSectionsInTableView(tableView _: UITableView) -> Int {
|
||||
1
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
|
||||
if searchActive {
|
||||
return filteredPasswordsTableEntries.count
|
||||
}
|
||||
return passwordsTableEntries.count;
|
||||
return passwordsTableEntries.count
|
||||
}
|
||||
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
searchBar.text = ""
|
||||
searchActive = false
|
||||
self.tableView.reloadData()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
||||
if let searchText = searchBar.text, searchText.isEmpty == false {
|
||||
filteredPasswordsTableEntries = passwordsTableEntries.filter {$0.match(searchText)}
|
||||
filteredPasswordsTableEntries = passwordsTableEntries.filter { $0.match(searchText) }
|
||||
searchActive = true
|
||||
} else {
|
||||
searchActive = false
|
||||
}
|
||||
self.tableView.reloadData()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange _: String) {
|
||||
searchBarSearchButtonClicked(searchBar)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,23 +6,26 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import AuthenticationServices
|
||||
import Foundation
|
||||
import passKit
|
||||
import AuthenticationServices
|
||||
|
||||
// cancel means cancel the extension
|
||||
class PasscodeLockViewControllerForExtension: PasscodeLockViewController {
|
||||
var originalExtensionContest: ASCredentialProviderExtensionContext?
|
||||
public convenience init(extensionContext: ASCredentialProviderExtensionContext?) {
|
||||
self.init()
|
||||
originalExtensionContest = extensionContext
|
||||
self.originalExtensionContest = extensionContext
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
cancelButton?.removeTarget(nil, action: nil, for: .allEvents)
|
||||
cancelButton?.addTarget(self, action: #selector(cancelExtension), for: .touchUpInside)
|
||||
}
|
||||
@objc func cancelExtension() {
|
||||
|
||||
@objc
|
||||
func cancelExtension() {
|
||||
originalExtensionContest?.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue))
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +37,7 @@ class PasscodeExtensionDisplay {
|
|||
|
||||
public init(extensionContext: ASCredentialProviderExtensionContext?) {
|
||||
self.extensionContext = extensionContext
|
||||
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
||||
self.passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
||||
passcodeLockVC.dismissCompletionCallback = { [weak self] in
|
||||
self?.dismiss()
|
||||
}
|
||||
|
|
@ -43,14 +46,14 @@ class PasscodeExtensionDisplay {
|
|||
|
||||
// present the passcode lock view if passcode is set and the view controller is not presented
|
||||
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
|
||||
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
|
||||
guard PasscodeLock.shared.hasPasscode, !isPasscodePresented == true else {
|
||||
return
|
||||
}
|
||||
isPasscodePresented = true
|
||||
extensionVC.present(passcodeLockVC, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public func dismiss(animated: Bool = true) {
|
||||
public func dismiss(animated _: Bool = true) {
|
||||
isPasscodePresented = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import MobileCoreServices
|
|||
import passKit
|
||||
|
||||
class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate {
|
||||
@IBOutlet weak var searchBar: UISearchBar!
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet var searchBar: UISearchBar!
|
||||
@IBOutlet var tableView: UITableView!
|
||||
|
||||
private let passwordStore = PasswordStore.shared
|
||||
private let keychain = AppKeychain.shared
|
||||
|
|
@ -34,8 +34,8 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
private func initPasswordsTableEntries() {
|
||||
filteredPasswordsTableEntries.removeAll()
|
||||
|
||||
let passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||
|
||||
let passwordEntities = passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||
passwordsTableEntries = passwordEntities.map {
|
||||
PasswordTableEntry($0)
|
||||
}
|
||||
|
|
@ -67,11 +67,11 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
for provider in itemProviders {
|
||||
// search using the extensionContext inputs
|
||||
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
|
||||
var url: 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
|
||||
}
|
||||
url = URL(string: urlString)?.host
|
||||
|
|
@ -84,14 +84,13 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
self?.searchBarSearchButtonClicked((self?.searchBar)!)
|
||||
}
|
||||
})
|
||||
}
|
||||
else if provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) {
|
||||
provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil, completionHandler: { (item, error) -> Void in
|
||||
} else if provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) {
|
||||
provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil, completionHandler: { (item, _) -> Void in
|
||||
var url: String?
|
||||
if let dictionary = item as? NSDictionary,
|
||||
let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary,
|
||||
var urlString = results[OnePasswordExtensionKey.URLStringKey] as? String {
|
||||
if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") {
|
||||
if !urlString.hasPrefix("http://"), !urlString.hasPrefix("https://") {
|
||||
urlString = "http://" + urlString
|
||||
}
|
||||
url = URL(string: urlString)?.host
|
||||
|
|
@ -105,7 +104,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
})
|
||||
} 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
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.extensionAction = .fillBrowser
|
||||
|
|
@ -137,7 +136,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
||||
guard PGPAgent.shared.isPrepared else {
|
||||
|
|
@ -147,7 +146,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
let passwordEntity = entry.passwordEntity
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
self.decryptPassword(passwordEntity: passwordEntity)
|
||||
decryptPassword(passwordEntity: passwordEntity)
|
||||
}
|
||||
|
||||
private func decryptPassword(passwordEntity: PasswordEntity, keyID: String? = nil) {
|
||||
|
|
@ -158,7 +157,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
let username = decryptedPassword.getUsernameForCompletion()
|
||||
let password = decryptedPassword.password
|
||||
DispatchQueue.main.async {// prepare a dictionary to return
|
||||
DispatchQueue.main.async { // prepare a dictionary to return
|
||||
switch self.extensionAction {
|
||||
case .findLogin:
|
||||
let extensionItem = NSExtensionItem()
|
||||
|
|
@ -173,14 +172,14 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
Utils.copyToPasteboard(textToCopy: decryptedPassword.password)
|
||||
// return a dictionary for JavaScript for best-effor fill in
|
||||
let extensionItem = NSExtensionItem()
|
||||
let returnDictionary = [NSExtensionJavaScriptFinalizeArgumentKey : ["username": username, "password": password]]
|
||||
let returnDictionary = [NSExtensionJavaScriptFinalizeArgumentKey: ["username": username, "password": password]]
|
||||
extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))]
|
||||
self.extensionContext?.completeRequest(returningItems: [extensionItem], completionHandler: nil)
|
||||
default:
|
||||
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
} catch AppError.PgpPrivateKeyNotFound(let key) {
|
||||
} catch let AppError.PgpPrivateKeyNotFound(key) {
|
||||
DispatchQueue.main.async {
|
||||
// alert: cancel or try again
|
||||
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.PgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
|
||||
|
|
@ -192,7 +191,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
} catch {
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
|
|
@ -200,39 +199,39 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
return 1
|
||||
func numberOfSectionsInTableView(tableView _: UITableView) -> Int {
|
||||
1
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if searchActive{
|
||||
func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
|
||||
if searchActive {
|
||||
return filteredPasswordsTableEntries.count
|
||||
}
|
||||
return passwordsTableEntries.count;
|
||||
return passwordsTableEntries.count
|
||||
}
|
||||
|
||||
@IBAction func cancelExtension(_ sender: Any) {
|
||||
@IBAction
|
||||
func cancelExtension(_: Any) {
|
||||
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
searchBar.text = ""
|
||||
searchActive = false
|
||||
self.tableView.reloadData()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
||||
if let searchText = searchBar.text, searchText.isEmpty == false {
|
||||
filteredPasswordsTableEntries = passwordsTableEntries.filter {$0.match(searchText)}
|
||||
filteredPasswordsTableEntries = passwordsTableEntries.filter { $0.match(searchText) }
|
||||
searchActive = true
|
||||
} else {
|
||||
searchActive = false
|
||||
}
|
||||
self.tableView.reloadData()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange _: String) {
|
||||
searchBarSearchButtonClicked(searchBar)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,14 +14,17 @@ class PasscodeLockViewControllerForExtension: PasscodeLockViewController {
|
|||
var originalExtensionContest: NSExtensionContext?
|
||||
public convenience init(extensionContext: NSExtensionContext?) {
|
||||
self.init()
|
||||
originalExtensionContest = extensionContext
|
||||
self.originalExtensionContest = extensionContext
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
cancelButton?.removeTarget(nil, action: nil, for: .allEvents)
|
||||
cancelButton?.addTarget(self, action: #selector(cancelExtension), for: .touchUpInside)
|
||||
}
|
||||
@objc func cancelExtension() {
|
||||
|
||||
@objc
|
||||
func cancelExtension() {
|
||||
originalExtensionContest?.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +36,7 @@ class PasscodeExtensionDisplay {
|
|||
|
||||
public init(extensionContext: NSExtensionContext?) {
|
||||
self.extensionContext = extensionContext
|
||||
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
||||
self.passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
||||
passcodeLockVC.dismissCompletionCallback = { [weak self] in
|
||||
self?.dismiss()
|
||||
}
|
||||
|
|
@ -42,14 +45,14 @@ class PasscodeExtensionDisplay {
|
|||
|
||||
// present the passcode lock view if passcode is set and the view controller is not presented
|
||||
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
|
||||
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
|
||||
guard PasscodeLock.shared.hasPasscode, !isPasscodePresented == true else {
|
||||
return
|
||||
}
|
||||
isPasscodePresented = true
|
||||
extensionVC.present(passcodeLockVC, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public func dismiss(animated: Bool = true) {
|
||||
public func dismiss(animated _: Bool = true) {
|
||||
isPasscodePresented = false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,9 @@
|
|||
import Crypto
|
||||
|
||||
struct GopenPGPInterface: PGPInterface {
|
||||
|
||||
private static let errorMapping: [String: Error] = [
|
||||
"gopenpgp: error in unlocking key: openpgp: invalid data: private key checksum failure": AppError.WrongPassphrase,
|
||||
"openpgp: incorrect key": AppError.KeyExpiredOrIncompatible,
|
||||
"gopenpgp: error in unlocking key: openpgp: invalid data: private key checksum failure": AppError.WrongPassphrase,
|
||||
"openpgp: incorrect key": AppError.KeyExpiredOrIncompatible,
|
||||
]
|
||||
|
||||
private var publicKeys: [String: CryptoKey] = [:]
|
||||
|
|
@ -43,26 +42,25 @@ struct GopenPGPInterface: PGPInterface {
|
|||
}
|
||||
privateKeys[k.getFingerprint().lowercased()] = k
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func extractKeysFromArmored(str: String) -> [String] {
|
||||
var keys: [String] = []
|
||||
var key: String = ""
|
||||
for line in str.splitByNewline() {
|
||||
if line.trimmed.uppercased().hasPrefix("-----BEGIN PGP") {
|
||||
key = ""
|
||||
key += line
|
||||
} else if line.trimmed.uppercased().hasPrefix("-----END PGP") {
|
||||
key += line
|
||||
keys.append(key)
|
||||
} else {
|
||||
key += line
|
||||
}
|
||||
var keys: [String] = []
|
||||
var key: String = ""
|
||||
for line in str.splitByNewline() {
|
||||
if line.trimmed.uppercased().hasPrefix("-----BEGIN PGP") {
|
||||
key = ""
|
||||
key += line
|
||||
} else if line.trimmed.uppercased().hasPrefix("-----END PGP") {
|
||||
key += line
|
||||
keys.append(key)
|
||||
} else {
|
||||
key += line
|
||||
}
|
||||
key += "\n"
|
||||
}
|
||||
return keys
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func containsPublicKey(with keyID: String) -> Bool {
|
||||
publicKeys.keys.contains(where: { key in key.hasSuffix(keyID.lowercased()) })
|
||||
|
|
@ -73,7 +71,7 @@ struct GopenPGPInterface: PGPInterface {
|
|||
}
|
||||
|
||||
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 {
|
||||
throw AppError.Decryption
|
||||
}
|
||||
|
|
@ -97,7 +95,7 @@ struct GopenPGPInterface: PGPInterface {
|
|||
}
|
||||
|
||||
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 {
|
||||
throw AppError.Encryption
|
||||
}
|
||||
|
|
@ -124,11 +122,11 @@ struct GopenPGPInterface: PGPInterface {
|
|||
}
|
||||
|
||||
var keyID: [String] {
|
||||
return publicKeys.keys.map({ $0.uppercased() })
|
||||
publicKeys.keys.map { $0.uppercased() }
|
||||
}
|
||||
|
||||
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? {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
import ObjectivePGP
|
||||
|
||||
struct ObjectivePGPInterface: PGPInterface {
|
||||
|
||||
private let publicKey: Key
|
||||
private let privateKey: Key
|
||||
|
||||
|
|
@ -30,11 +29,11 @@ struct ObjectivePGPInterface: PGPInterface {
|
|||
self.privateKey = privateKey
|
||||
}
|
||||
|
||||
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data? {
|
||||
return try ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys) { _ in passphrase }
|
||||
func decrypt(encryptedData: Data, keyID _: String, passphrase: String) throws -> Data? {
|
||||
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)
|
||||
if Defaults.encryptInArmored {
|
||||
return Armor.armored(encryptedData, as: .message).data(using: .ascii)!
|
||||
|
|
@ -51,10 +50,10 @@ struct ObjectivePGPInterface: PGPInterface {
|
|||
}
|
||||
|
||||
var keyID: [String] {
|
||||
return keyring.keys.map({ $0.keyID.longIdentifier })
|
||||
keyring.keys.map(\.keyID.longIdentifier)
|
||||
}
|
||||
|
||||
var shortKeyID: [String] {
|
||||
return keyring.keys.map({ $0.keyID.shortIdentifier })
|
||||
keyring.keys.map(\.keyID.shortIdentifier)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public class PGPAgent {
|
||||
|
||||
public static let shared: PGPAgent = PGPAgent()
|
||||
|
||||
private let keyStore: KeyStore
|
||||
|
|
@ -20,7 +19,7 @@ public class PGPAgent {
|
|||
|
||||
public func initKeys() throws {
|
||||
guard let publicKey: String = keyStore.get(for: PgpKey.PUBLIC.getKeychainKey()),
|
||||
let privateKey: String = keyStore.get(for: PgpKey.PRIVATE.getKeychainKey()) else {
|
||||
let privateKey: String = keyStore.get(for: PgpKey.PRIVATE.getKeychainKey()) else {
|
||||
pgpInterface = nil
|
||||
throw AppError.KeyImport
|
||||
}
|
||||
|
|
@ -52,7 +51,7 @@ public class PGPAgent {
|
|||
throw AppError.Decryption
|
||||
}
|
||||
|
||||
var keyID = keyID;
|
||||
var keyID = keyID
|
||||
if !pgpInterface.containsPrivateKey(with: keyID) {
|
||||
if pgpInterface.keyID.count == 1 {
|
||||
keyID = pgpInterface.keyID.first!
|
||||
|
|
@ -62,8 +61,8 @@ public class PGPAgent {
|
|||
}
|
||||
|
||||
// Remember the previous status and set the current status
|
||||
let previousDecryptStatus = self.latestDecryptStatus
|
||||
self.latestDecryptStatus = false
|
||||
let previousDecryptStatus = latestDecryptStatus
|
||||
latestDecryptStatus = false
|
||||
|
||||
// Get the PGP key passphrase.
|
||||
var passphrase = ""
|
||||
|
|
@ -77,7 +76,7 @@ public class PGPAgent {
|
|||
return nil
|
||||
}
|
||||
// The decryption step has succeed.
|
||||
self.latestDecryptStatus = true
|
||||
latestDecryptStatus = true
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +97,7 @@ public class PGPAgent {
|
|||
}
|
||||
|
||||
public var isPrepared: Bool {
|
||||
return keyStore.contains(key: PgpKey.PUBLIC.getKeychainKey())
|
||||
keyStore.contains(key: PgpKey.PUBLIC.getKeychainKey())
|
||||
&& keyStore.contains(key: PgpKey.PRIVATE.getKeychainKey())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
protocol PGPInterface {
|
||||
|
||||
func decrypt(encryptedData: Data, keyID: String, passphrase: String) throws -> Data?
|
||||
|
||||
func encrypt(plainData: Data, keyID: String) throws -> Data
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@
|
|||
import UIKit
|
||||
|
||||
open class PasscodeLockPresenter {
|
||||
|
||||
fileprivate var mainWindow: UIWindow?
|
||||
fileprivate var passcodeLockWindow: UIWindow?
|
||||
private var mainWindow: UIWindow?
|
||||
private var passcodeLockWindow: UIWindow?
|
||||
|
||||
public init(mainWindow window: UIWindow?) {
|
||||
self.mainWindow = window
|
||||
|
|
@ -27,7 +26,7 @@ open class PasscodeLockPresenter {
|
|||
|
||||
// new window
|
||||
mainWindow?.endEditing(true)
|
||||
passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame)
|
||||
passcodeLockWindow = UIWindow(frame: mainWindow!.frame)
|
||||
moveWindowsToFront(windowLevel: windowLevel)
|
||||
passcodeLockWindow?.isHidden = false
|
||||
|
||||
|
|
@ -46,9 +45,9 @@ open class PasscodeLockPresenter {
|
|||
passcodeLockWindow?.rootViewController = nil
|
||||
}
|
||||
|
||||
fileprivate func moveWindowsToFront(windowLevel: CGFloat?) {
|
||||
private func moveWindowsToFront(windowLevel: CGFloat?) {
|
||||
let windowLevel = 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@
|
|||
// Inspired by SwiftPasscodeLock created by Yanko Dimitrov.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import LocalAuthentication
|
||||
import UIKit
|
||||
|
||||
open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||
|
||||
open var dismissCompletionCallback: (()->Void)?
|
||||
open var successCallback: (()->Void)?
|
||||
open var cancelCallback: (()->Void)?
|
||||
open var dismissCompletionCallback: (() -> Void)?
|
||||
open var successCallback: (() -> Void)?
|
||||
open var cancelCallback: (() -> Void)?
|
||||
|
||||
weak var passcodeTextField: UITextField?
|
||||
weak var biometryAuthButton: UIButton?
|
||||
|
|
@ -23,21 +22,21 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
open weak var cancelButton: UIButton?
|
||||
|
||||
var isCancellable: Bool = false
|
||||
|
||||
|
||||
private let passwordStore = PasswordStore.shared
|
||||
|
||||
open override func loadView() {
|
||||
override open func loadView() {
|
||||
super.loadView()
|
||||
|
||||
let passcodeTextField = UITextField()
|
||||
let passcodeTextField = UITextField()
|
||||
passcodeTextField.borderStyle = UITextField.BorderStyle.roundedRect
|
||||
passcodeTextField.placeholder = "EnterPasscode".localize()
|
||||
passcodeTextField.isSecureTextEntry = true
|
||||
passcodeTextField.clearButtonMode = UITextField.ViewMode.whileEditing
|
||||
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
|
||||
self.view.addSubview(passcodeTextField)
|
||||
view.addSubview(passcodeTextField)
|
||||
self.passcodeTextField = passcodeTextField
|
||||
|
||||
view.backgroundColor = Colors.systemBackground
|
||||
|
|
@ -50,7 +49,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
biometryAuthButton.addTarget(self, action: #selector(bioButtonPressedAction(_:)), for: .touchUpInside)
|
||||
biometryAuthButton.isHidden = true
|
||||
biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.view.addSubview(biometryAuthButton)
|
||||
view.addSubview(biometryAuthButton)
|
||||
self.biometryAuthButton = biometryAuthButton
|
||||
|
||||
let myContext = LAContext()
|
||||
|
|
@ -71,21 +70,21 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
forgotPasscodeButton.setTitleColor(Colors.systemBlue, for: .normal)
|
||||
forgotPasscodeButton.addTarget(self, action: #selector(forgotPasscodeButtonPressedAction(_:)), for: .touchUpInside)
|
||||
// hide the forgotPasscodeButton if the native app is running
|
||||
forgotPasscodeButton.isHidden = self.isCancellable
|
||||
forgotPasscodeButton.isHidden = isCancellable
|
||||
forgotPasscodeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.view.addSubview(forgotPasscodeButton)
|
||||
view.addSubview(forgotPasscodeButton)
|
||||
self.forgotPasscodeButton = forgotPasscodeButton
|
||||
|
||||
let cancelButton = UIButton(type: .custom)
|
||||
cancelButton.setTitle("Cancel".localize(), for: .normal)
|
||||
cancelButton.setTitleColor(Colors.systemBlue, for: .normal)
|
||||
cancelButton.addTarget(self, action: #selector(passcodeLockDidCancel), for: .touchUpInside)
|
||||
cancelButton.isHidden = !self.isCancellable
|
||||
cancelButton.isHidden = !isCancellable
|
||||
cancelButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
cancelButton.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.left
|
||||
self.view.addSubview(cancelButton)
|
||||
view.addSubview(cancelButton)
|
||||
self.cancelButton = cancelButton
|
||||
|
||||
|
||||
// Display the Pass icon in the middle of the screen
|
||||
let bundle = Bundle(for: PasscodeLockViewController.self)
|
||||
let appIcon = UIImage(named: "PasscodeLockViewIcon", in: bundle, compatibleWith: nil)
|
||||
|
|
@ -94,49 +93,48 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
appIconView.translatesAutoresizingMaskIntoConstraints = false
|
||||
appIconView.layer.cornerRadius = appIconSize / 5
|
||||
appIconView.layer.masksToBounds = true
|
||||
self.view?.addSubview(appIconView)
|
||||
view?.addSubview(appIconView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
passcodeTextField.widthAnchor.constraint(equalToConstant: 250),
|
||||
passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
|
||||
passcodeTextField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||
passcodeTextField.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -20),
|
||||
passcodeTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
passcodeTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20),
|
||||
// above passocde
|
||||
appIconView.widthAnchor.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),
|
||||
// below passcode
|
||||
biometryAuthButton.widthAnchor.constraint(equalToConstant: 250),
|
||||
biometryAuthButton.heightAnchor.constraint(equalToConstant: 40),
|
||||
biometryAuthButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||
biometryAuthButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
biometryAuthButton.topAnchor.constraint(equalTo: passcodeTextField.bottomAnchor),
|
||||
// cancel (top-left of the screen)
|
||||
cancelButton.widthAnchor.constraint(equalToConstant: 150),
|
||||
cancelButton.heightAnchor.constraint(equalToConstant: 40),
|
||||
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor),
|
||||
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20),
|
||||
cancelButton.topAnchor.constraint(equalTo: view.safeTopAnchor),
|
||||
cancelButton.leftAnchor.constraint(equalTo: view.safeLeftAnchor, constant: 20),
|
||||
// bottom of the screen
|
||||
forgotPasscodeButton.widthAnchor.constraint(equalToConstant: 250),
|
||||
forgotPasscodeButton.heightAnchor.constraint(equalToConstant: 40),
|
||||
forgotPasscodeButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
|
||||
forgotPasscodeButton.bottomAnchor.constraint(equalTo: self.view.safeBottomAnchor, constant: -40)
|
||||
forgotPasscodeButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
forgotPasscodeButton.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: -40),
|
||||
])
|
||||
|
||||
// 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)
|
||||
|
||||
}
|
||||
|
||||
open override func viewDidLoad() {
|
||||
override open func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
override open func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
if let biometryAuthButton = biometryAuthButton {
|
||||
self.bioButtonPressedAction(biometryAuthButton)
|
||||
bioButtonPressedAction(biometryAuthButton)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,16 +165,18 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
dismissPasscodeLock(completionHandler: successCallback)
|
||||
}
|
||||
|
||||
@objc func passcodeLockDidCancel() {
|
||||
@objc
|
||||
func passcodeLockDidCancel() {
|
||||
dismissPasscodeLock(completionHandler: cancelCallback)
|
||||
}
|
||||
|
||||
@objc func bioButtonPressedAction(_ uiButton: UIButton) {
|
||||
@objc
|
||||
func bioButtonPressedAction(_: UIButton) {
|
||||
let myContext = LAContext()
|
||||
let myLocalizedReasonString = "AuthenticationNeeded.".localize()
|
||||
var authError: NSError?
|
||||
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
|
||||
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
|
||||
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, _ in
|
||||
if success {
|
||||
DispatchQueue.main.async {
|
||||
// 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)
|
||||
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()
|
||||
var error: NSError?
|
||||
// If the device passcode is not set, reset the app.
|
||||
|
|
@ -199,7 +200,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
return
|
||||
}
|
||||
// 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 {
|
||||
DispatchQueue.main.async {
|
||||
// User authenticated successfully, take appropriate action
|
||||
|
|
@ -214,25 +215,26 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
|||
}
|
||||
}))
|
||||
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 !PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
||||
self.passcodeTextField?.placeholder =
|
||||
passcodeTextField?.placeholder =
|
||||
"TryAgain".localize()
|
||||
self.passcodeTextField?.text = ""
|
||||
self.passcodeTextField?.shake()
|
||||
passcodeTextField?.text = ""
|
||||
passcodeTextField?.shake()
|
||||
}
|
||||
}
|
||||
textField.resignFirstResponder()
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func passcodeTextFieldDidChange(_ textField: UITextField) {
|
||||
@objc
|
||||
func passcodeTextFieldDidChange(_ textField: UITextField) {
|
||||
if PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
||||
self.passcodeLockDidSucceed()
|
||||
passcodeLockDidSucceed()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
extension Array {
|
||||
|
||||
func slices(count: UInt) -> [ArraySlice<Element>] {
|
||||
guard count != 0 else {
|
||||
return []
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@
|
|||
|
||||
extension String {
|
||||
public func localize() -> String {
|
||||
return NSLocalizedString(self, value: "#\(self)#", comment: "")
|
||||
NSLocalizedString(self, value: "#\(self)#", comment: "")
|
||||
}
|
||||
|
||||
public func localize(_ firstValue: CVarArg) -> String {
|
||||
return String(format: localize(), firstValue)
|
||||
String(format: localize(), firstValue)
|
||||
}
|
||||
|
||||
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 {
|
||||
return localize(error.localizedDescription)
|
||||
localize(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
extension String {
|
||||
public var trimmed: String {
|
||||
return trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
public func stringByAddingPercentEncodingForRFC3986() -> String? {
|
||||
|
|
@ -19,12 +19,12 @@ extension 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 {
|
||||
public static func | (left: String, right: String) -> String {
|
||||
return right.isEmpty ? left : left + "\n" + right
|
||||
right.isEmpty ? left : left + "\n" + right
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension UIAlertAction {
|
||||
public static func cancelAndPopView(controller: UIViewController) -> UIAlertAction {
|
||||
return cancel() { _ in
|
||||
cancel { _ in
|
||||
controller.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ extension UIAlertAction {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ extension UIAlertAction {
|
|||
}
|
||||
|
||||
public static func okAndPopView(controller: UIViewController) -> UIAlertAction {
|
||||
return ok() { _ in
|
||||
ok { _ in
|
||||
controller.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -41,13 +41,12 @@ extension UIAlertAction {
|
|||
public static func selectKey(controller: UIViewController, handler: ((UIAlertAction) -> Void)?) -> UIAlertAction {
|
||||
UIAlertAction(title: "Select Key", style: .default) { _ in
|
||||
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)
|
||||
selectKeyAlert.addAction(action)
|
||||
})
|
||||
}
|
||||
selectKeyAlert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
|
||||
controller.present(selectKeyAlert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,18 +14,18 @@ private var kAssociationKeyNextField: UInt8 = 0
|
|||
extension UITextField {
|
||||
@IBOutlet var nextField: UITextField? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
|
||||
objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
|
||||
}
|
||||
set(newField) {
|
||||
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func shake() {
|
||||
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
animation.repeatCount = 3
|
||||
animation.duration = 0.2/TimeInterval(animation.repeatCount)
|
||||
animation.duration = 0.2 / TimeInterval(animation.repeatCount)
|
||||
animation.autoreverses = true
|
||||
animation.values = [3, -3]
|
||||
layer.add(animation, forKey: "shake")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
//
|
||||
|
||||
extension UIViewController {
|
||||
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
@objc
|
||||
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
if textField.nextField != nil {
|
||||
textField.nextField?.becomeFirstResponder()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -9,29 +9,28 @@
|
|||
import Foundation
|
||||
|
||||
extension UIView {
|
||||
|
||||
// Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically
|
||||
var safeTopAnchor: NSLayoutYAxisAnchor {
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.safeAreaLayoutGuide.topAnchor
|
||||
} else {
|
||||
return self.topAnchor
|
||||
return topAnchor
|
||||
}
|
||||
}
|
||||
|
||||
var safeLeftAnchor: NSLayoutXAxisAnchor {
|
||||
if #available(iOS 11.0, *){
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.safeAreaLayoutGuide.leftAnchor
|
||||
} else {
|
||||
return self.leftAnchor
|
||||
return leftAnchor
|
||||
}
|
||||
}
|
||||
|
||||
var safeRightAnchor: NSLayoutXAxisAnchor {
|
||||
if #available(iOS 11.0, *){
|
||||
if #available(iOS 11.0, *) {
|
||||
return self.safeAreaLayoutGuide.rightAnchor
|
||||
} else {
|
||||
return self.rightAnchor
|
||||
return rightAnchor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +38,7 @@ extension UIView {
|
|||
if #available(iOS 11.0, *) {
|
||||
return self.safeAreaLayoutGuide.bottomAnchor
|
||||
} else {
|
||||
return self.bottomAnchor
|
||||
return bottomAnchor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@
|
|||
import KeychainAccess
|
||||
|
||||
public class AppKeychain: KeyStore {
|
||||
|
||||
public static let shared = AppKeychain()
|
||||
|
||||
|
||||
private let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
||||
.accessibility(.whenUnlockedThisDeviceOnly)
|
||||
.synchronizable(false)
|
||||
|
|
@ -25,15 +24,15 @@ public class AppKeychain: KeyStore {
|
|||
}
|
||||
|
||||
public func contains(key: String) -> Bool {
|
||||
return (try? keychain.contains(key)) ?? false
|
||||
(try? keychain.contains(key)) ?? false
|
||||
}
|
||||
|
||||
public func get(for key: String) -> Data? {
|
||||
return try? keychain.getData(key)
|
||||
try? keychain.getData(key)
|
||||
}
|
||||
|
||||
public func get(for key: String) -> String? {
|
||||
return try? keychain.getString(key)
|
||||
try? keychain.getString(key)
|
||||
}
|
||||
|
||||
public func removeContent(for key: String) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Foundation
|
|||
|
||||
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
|
||||
public extension FileManager {
|
||||
|
||||
/// 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,
|
||||
|
|
@ -19,8 +18,7 @@ public extension FileManager {
|
|||
///
|
||||
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
|
||||
/// directories, hard links, ...).
|
||||
func allocatedSizeOfDirectoryAtURL(directoryURL : URL) throws -> UInt64 {
|
||||
|
||||
func allocatedSizeOfDirectoryAtURL(directoryURL: URL) throws -> UInt64 {
|
||||
// We'll sum up content size here:
|
||||
var accumulatedSize = UInt64(0)
|
||||
|
||||
|
|
@ -29,7 +27,7 @@ public extension FileManager {
|
|||
URLResourceKey.isRegularFileKey,
|
||||
URLResourceKey.fileAllocatedSizeKey,
|
||||
URLResourceKey.totalFileAllocatedSizeKey,
|
||||
]
|
||||
]
|
||||
|
||||
// The error handler simply signals errors to outside code.
|
||||
var errorDidOccur: Error?
|
||||
|
|
@ -38,7 +36,6 @@ public extension FileManager {
|
|||
return false
|
||||
}
|
||||
|
||||
|
||||
// We have to enumerate all directory contents, including subdirectories.
|
||||
let enumerator = self.enumerator(at: directoryURL,
|
||||
includingPropertiesForKeys: prefetchedProperties,
|
||||
|
|
@ -91,4 +88,3 @@ public extension FileManager {
|
|||
return accumulatedSize
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public final class Globals {
|
|||
public static let gitPassword = "gitPassword"
|
||||
public static let gitSSHPrivateKeyPassphrase = "gitSSHPrivateKeyPassphrase"
|
||||
public static let pgpKeyPassphrase = "pgpKeyPassphrase"
|
||||
|
||||
|
||||
public static let gitSignatureDefaultName = "Pass for iOS"
|
||||
public static let gitSignatureDefaultEmail = "user@passforios"
|
||||
|
||||
|
|
@ -43,14 +43,15 @@ public final class Globals {
|
|||
// UI related
|
||||
public static let tableCellButtonSize = CGFloat(20.0)
|
||||
|
||||
private init() { }
|
||||
private init() {}
|
||||
}
|
||||
|
||||
public extension Bundle {
|
||||
var releaseVersionNumber: String? {
|
||||
return infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
}
|
||||
|
||||
var buildVersionNumber: String? {
|
||||
return infoDictionary?["CFBundleVersion"] as? String
|
||||
infoDictionary?["CFBundleVersion"] as? String
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,6 @@ public class KeyFileManager {
|
|||
}
|
||||
|
||||
public func doesKeyFileExist() -> Bool {
|
||||
return FileManager.default.fileExists(atPath: keyPath)
|
||||
FileManager.default.fileExists(atPath: keyPath)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static func copyToPasteboard(textToCopy: String?) {
|
||||
guard textToCopy != nil else {
|
||||
return
|
||||
|
|
@ -15,8 +14,8 @@ public class Utils {
|
|||
UIPasteboard.general.string = textToCopy
|
||||
}
|
||||
|
||||
public static func attributedPassword(plainPassword: String) -> NSAttributedString{
|
||||
let attributedPassword = NSMutableAttributedString.init(string: plainPassword)
|
||||
public static func attributedPassword(plainPassword: String) -> NSAttributedString {
|
||||
let attributedPassword = NSMutableAttributedString(string: plainPassword)
|
||||
// draw all digits in the password into red
|
||||
// draw all punctuation characters in the password into blue
|
||||
for (index, element) in plainPassword.unicodeScalars.enumerated() {
|
||||
|
|
@ -40,24 +39,24 @@ public class Utils {
|
|||
}
|
||||
|
||||
public static func createRequestPGPKeyPassphraseHandler(controller: UIViewController) -> (String) -> String {
|
||||
return { keyID in
|
||||
{ keyID in
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var passphrase = ""
|
||||
DispatchQueue.main.async {
|
||||
let title = "Passphrase".localize() + " (\(keyID.suffix(8)))"
|
||||
let message = "FillInPgpPassphrase.".localize()
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction.ok() { _ in
|
||||
alert.addAction(UIAlertAction.ok { _ in
|
||||
passphrase = alert.textFields?.first?.text ?? ""
|
||||
sem.signal()
|
||||
})
|
||||
alert.addTextField() { textField in
|
||||
alert.addTextField { textField in
|
||||
textField.text = AppKeychain.shared.get(for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID)) ?? ""
|
||||
textField.isSecureTextEntry = true
|
||||
}
|
||||
controller.present(alert, animated: true)
|
||||
}
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
_ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
if Defaults.isRememberPGPPassphraseOn {
|
||||
AppKeychain.shared.add(string: passphrase, for: AppKeychain.getPGPKeyPassphraseKey(keyID: keyID))
|
||||
}
|
||||
|
|
@ -65,4 +64,3 @@ public class Utils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public struct GitCredential {
|
|||
public func credentialProvider(requestCredentialPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider {
|
||||
var attempts = 0
|
||||
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
||||
var credential: GTCredential? = nil
|
||||
var credential: GTCredential?
|
||||
|
||||
switch self.credential {
|
||||
case let .http(userName):
|
||||
|
|
@ -52,7 +52,7 @@ public struct GitCredential {
|
|||
return nil
|
||||
}
|
||||
var lastPassword = self.passwordStore.gitSSHPrivateKeyPassphrase
|
||||
if lastPassword == nil || attempts != 0 {
|
||||
if lastPassword == nil || attempts != 0 {
|
||||
if let requestedPassword = requestCredentialPassword(self.credential, lastPassword) {
|
||||
if Defaults.isRememberGitCredentialPassphraseOn {
|
||||
self.passwordStore.gitSSHPrivateKeyPassphrase = requestedPassword
|
||||
|
|
@ -72,10 +72,9 @@ public struct GitCredential {
|
|||
public func delete() {
|
||||
switch credential {
|
||||
case .http:
|
||||
self.passwordStore.gitPassword = nil
|
||||
passwordStore.gitPassword = nil
|
||||
case .ssh:
|
||||
self.passwordStore.gitSSHPrivateKeyPassphrase = nil
|
||||
passwordStore.gitSSHPrivateKeyPassphrase = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public class PasscodeLock {
|
|||
}
|
||||
|
||||
public var hasPasscode: Bool {
|
||||
return passcode != nil
|
||||
passcode != nil
|
||||
}
|
||||
|
||||
public func save(passcode: String) {
|
||||
|
|
@ -32,7 +32,7 @@ public class PasscodeLock {
|
|||
}
|
||||
|
||||
public func check(passcode: String) -> Bool {
|
||||
return self.passcode == passcode
|
||||
self.passcode == passcode
|
||||
}
|
||||
|
||||
public func delete() {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import OneTimePassword
|
||||
import Base32
|
||||
import OneTimePassword
|
||||
|
||||
public class Password {
|
||||
|
||||
public var name: String
|
||||
public var url: URL
|
||||
public var plainText: String
|
||||
|
|
@ -29,47 +28,47 @@ public class Password {
|
|||
}
|
||||
|
||||
public var namePath: String {
|
||||
return url.deletingPathExtension().path
|
||||
url.deletingPathExtension().path
|
||||
}
|
||||
|
||||
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 {
|
||||
return parser.firstLine
|
||||
parser.firstLine
|
||||
}
|
||||
|
||||
public var plainData: Data {
|
||||
return plainText.data(using: .utf8)!
|
||||
plainText.data(using: .utf8)!
|
||||
}
|
||||
|
||||
public var additionsPlainText: String {
|
||||
return parser.additionsSection
|
||||
parser.additionsSection
|
||||
}
|
||||
|
||||
public var username: String? {
|
||||
return getAdditionValue(withKey: Constants.USERNAME_KEYWORD)
|
||||
getAdditionValue(withKey: Constants.USERNAME_KEYWORD)
|
||||
}
|
||||
|
||||
public var login: String? {
|
||||
return getAdditionValue(withKey: Constants.LOGIN_KEYWORD)
|
||||
getAdditionValue(withKey: Constants.LOGIN_KEYWORD)
|
||||
}
|
||||
|
||||
public var urlString: String? {
|
||||
return getAdditionValue(withKey: Constants.URL_KEYWORD)
|
||||
getAdditionValue(withKey: Constants.URL_KEYWORD)
|
||||
}
|
||||
|
||||
public var currentOtp: String? {
|
||||
return otpToken?.currentPassword
|
||||
otpToken?.currentPassword
|
||||
}
|
||||
|
||||
public var numberOfUnknowns: Int {
|
||||
return additions.map { $0.title }.filter(Constants.isUnknown).count
|
||||
additions.map(\.title).filter(Constants.isUnknown).count
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -119,7 +118,7 @@ public class Password {
|
|||
}
|
||||
|
||||
public func getFilteredAdditions() -> [AdditionField] {
|
||||
return additions.filter { field in
|
||||
additions.filter { field in
|
||||
let title = field.title.lowercased()
|
||||
return title != Constants.USERNAME_KEYWORD
|
||||
&& title != Constants.LOGIN_KEYWORD
|
||||
|
|
@ -194,12 +193,12 @@ public class Password {
|
|||
newOtpauth?.append("&secret=")
|
||||
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
|
||||
|
||||
var lines : [String] = []
|
||||
self.plainText.enumerateLines() { line, _ in
|
||||
var lines: [String] = []
|
||||
plainText.enumerateLines { line, _ in
|
||||
let (key, _) = Parser.getKeyValuePair(from: line)
|
||||
if !Constants.OTP_KEYWORDS.contains(key ?? "") {
|
||||
lines.append(line)
|
||||
} else if key == Constants.OTPAUTH && newOtpauth != nil {
|
||||
} else if key == Constants.OTPAUTH, newOtpauth != nil {
|
||||
lines.append(newOtpauth!)
|
||||
// set to nil to prevent duplication
|
||||
newOtpauth = nil
|
||||
|
|
@ -208,10 +207,10 @@ public class Password {
|
|||
if newOtpauth != nil {
|
||||
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
|
||||
return self.otpToken?.currentPassword
|
||||
return otpToken?.currentPassword
|
||||
}
|
||||
|
||||
public func getUsernameForCompletion() -> String {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Foundation
|
|||
import SwiftyUserDefaults
|
||||
|
||||
extension PasswordEntity {
|
||||
|
||||
public var nameWithCategory: String {
|
||||
if let p = path, p.hasSuffix(".gpg") {
|
||||
return String(p.prefix(upTo: p.index(p.endIndex, offsetBy: -4)))
|
||||
|
|
@ -19,7 +18,7 @@ extension PasswordEntity {
|
|||
}
|
||||
|
||||
public func getCategoryText() -> String {
|
||||
return getCategoryArray().joined(separator: " > ")
|
||||
getCategoryArray().joined(separator: " > ")
|
||||
}
|
||||
|
||||
public func getCategoryArray() -> [String] {
|
||||
|
|
@ -44,17 +43,16 @@ extension PasswordEntity {
|
|||
// manually write models instead auto generation.
|
||||
|
||||
public func getImage() -> Data? {
|
||||
return image
|
||||
image
|
||||
}
|
||||
|
||||
public func getName() -> String {
|
||||
// unwrap non-optional core data
|
||||
return name ?? ""
|
||||
name ?? ""
|
||||
}
|
||||
|
||||
public func getPath() -> String {
|
||||
// unwrap non-optional core data
|
||||
return path ?? ""
|
||||
path ?? ""
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import ObjectiveGit
|
||||
import Foundation
|
||||
import KeychainAccess
|
||||
import ObjectiveGit
|
||||
import SwiftyUserDefaults
|
||||
import UIKit
|
||||
|
||||
public class PasswordStore {
|
||||
public static let shared = PasswordStore()
|
||||
|
|
@ -21,43 +21,40 @@ public class PasswordStore {
|
|||
dateFormatter.timeStyle = .short
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
public var storeURL: URL
|
||||
public var tempStoreURL: URL {
|
||||
get {
|
||||
URL(fileURLWithPath: "\(storeURL.path)-temp")
|
||||
}
|
||||
URL(fileURLWithPath: "\(storeURL.path)-temp")
|
||||
}
|
||||
|
||||
public var storeRepository: GTRepository?
|
||||
|
||||
|
||||
public var gitSignatureForNow: GTSignature? {
|
||||
get {
|
||||
let gitSignatureName = Defaults.gitSignatureName ?? Globals.gitSignatureDefaultName
|
||||
let gitSignatureEmail = Defaults.gitSignatureEmail ?? Globals.gitSignatureDefaultEmail
|
||||
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())
|
||||
}
|
||||
let gitSignatureName = Defaults.gitSignatureName ?? Globals.gitSignatureDefaultName
|
||||
let gitSignatureEmail = Defaults.gitSignatureEmail ?? Globals.gitSignatureDefaultEmail
|
||||
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())
|
||||
}
|
||||
|
||||
|
||||
public var gitPassword: String? {
|
||||
set {
|
||||
AppKeychain.shared.add(string: newValue, for: Globals.gitPassword)
|
||||
}
|
||||
get {
|
||||
return AppKeychain.shared.get(for: Globals.gitPassword)
|
||||
AppKeychain.shared.get(for: Globals.gitPassword)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var gitSSHPrivateKeyPassphrase: String? {
|
||||
set {
|
||||
AppKeychain.shared.add(string: newValue, for: Globals.gitSSHPrivateKeyPassphrase)
|
||||
}
|
||||
get {
|
||||
return AppKeychain.shared.get(for: Globals.gitSSHPrivateKeyPassphrase)
|
||||
AppKeychain.shared.get(for: Globals.gitSSHPrivateKeyPassphrase)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
|
||||
let container = NSPersistentContainer(name: "pass", managedObjectModel: managedObjectModel!)
|
||||
|
|
@ -65,11 +62,11 @@ public class PasswordStore {
|
|||
try! FileManager.default.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
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? {
|
||||
// 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.
|
||||
|
||||
|
||||
/*
|
||||
Typical reasons for an error here include:
|
||||
* The parent directory does not exist, cannot be created, or disallows writing.
|
||||
|
|
@ -83,36 +80,36 @@ public class PasswordStore {
|
|||
})
|
||||
return container.viewContext
|
||||
}()
|
||||
|
||||
public var numberOfPasswords : Int {
|
||||
return self.fetchPasswordEntityCoreData(withDir: false).count
|
||||
|
||||
public var numberOfPasswords: Int {
|
||||
fetchPasswordEntityCoreData(withDir: false).count
|
||||
}
|
||||
|
||||
public var sizeOfRepositoryByteCount : UInt64 {
|
||||
return (try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)) ?? 0
|
||||
|
||||
public var sizeOfRepositoryByteCount: UInt64 {
|
||||
(try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: storeURL)) ?? 0
|
||||
}
|
||||
|
||||
|
||||
public var numberOfLocalCommits: Int {
|
||||
return (try? getLocalCommits())?.flatMap { $0.count } ?? 0
|
||||
(try? getLocalCommits()).map(\.count) ?? 0
|
||||
}
|
||||
|
||||
|
||||
public var lastSyncedTime: Date? {
|
||||
return Defaults.lastSyncedTime
|
||||
Defaults.lastSyncedTime
|
||||
}
|
||||
|
||||
|
||||
public var numberOfCommits: UInt? {
|
||||
return storeRepository?.numberOfCommits(inCurrentBranch: nil)
|
||||
storeRepository?.numberOfCommits(inCurrentBranch: nil)
|
||||
}
|
||||
|
||||
init(url: URL = URL(fileURLWithPath: "\(Globals.repositoryPath)")) {
|
||||
storeURL = url
|
||||
self.storeURL = url
|
||||
|
||||
// Migration
|
||||
importExistingKeysIntoKeychain()
|
||||
|
||||
do {
|
||||
if fm.fileExists(atPath: storeURL.path) {
|
||||
try storeRepository = GTRepository.init(url: storeURL)
|
||||
try self.storeRepository = GTRepository(url: storeURL)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
|
|
@ -128,12 +125,12 @@ public class PasswordStore {
|
|||
Defaults.remove(\.pgpPrivateKeyArmor)
|
||||
Defaults.remove(\.gitSSHPrivateKeyArmor)
|
||||
}
|
||||
|
||||
|
||||
public func repositoryExists() -> Bool {
|
||||
let fm = FileManager()
|
||||
return fm.fileExists(atPath: Globals.repositoryPath)
|
||||
}
|
||||
|
||||
|
||||
public func passwordExisted(password: Password) -> Bool {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -148,7 +145,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswordEntities".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func passwordEntityExisted(path: String) -> Bool {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -163,7 +160,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswordEntities".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func getPasswordEntity(by path: String, isDir: Bool) -> PasswordEntity? {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -173,7 +170,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswordEntities".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func cloneRepository(remoteRepoURL: URL,
|
||||
credential: GitCredential,
|
||||
branchName: String,
|
||||
|
|
@ -183,23 +180,23 @@ public class PasswordStore {
|
|||
do {
|
||||
let credentialProvider = try credential.credentialProvider(requestCredentialPassword: requestCredentialPassword)
|
||||
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 {
|
||||
credential.delete()
|
||||
throw(error)
|
||||
throw (error)
|
||||
}
|
||||
}
|
||||
|
||||
public func cloneRepository(remoteRepoURL: URL,
|
||||
options: [AnyHashable : Any]? = nil,
|
||||
options: [AnyHashable: Any]? = nil,
|
||||
branchName: String,
|
||||
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
||||
checkoutProgressBlock: @escaping (String, UInt, UInt) -> Void,
|
||||
completion: @escaping () -> Void = {}) throws {
|
||||
try? fm.removeItem(at: storeURL)
|
||||
try? fm.removeItem(at: tempStoreURL)
|
||||
self.gitPassword = nil
|
||||
self.gitSSHPrivateKeyPassphrase = nil
|
||||
gitPassword = nil
|
||||
gitSSHPrivateKeyPassphrase = nil
|
||||
do {
|
||||
storeRepository = try GTRepository.clone(from: remoteRepoURL,
|
||||
toWorkingDirectory: tempStoreURL,
|
||||
|
|
@ -216,7 +213,7 @@ public class PasswordStore {
|
|||
self.deleteCoreData(entityName: "PasswordEntity")
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
throw(error)
|
||||
throw (error)
|
||||
}
|
||||
Defaults.lastSyncedTime = Date()
|
||||
DispatchQueue.main.async {
|
||||
|
|
@ -225,7 +222,7 @@ public class PasswordStore {
|
|||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func checkoutAndChangeBranch(withName localBranchName: String, progressBlock: @escaping (String, UInt, UInt) -> Void) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -241,7 +238,7 @@ public class PasswordStore {
|
|||
try storeRepository.checkoutReference(localBranch.reference, options: checkoutOptions)
|
||||
try storeRepository.moveHEAD(to: localBranch.reference)
|
||||
}
|
||||
|
||||
|
||||
public func pullRepository(credential: GitCredential,
|
||||
requestCredentialPassword: @escaping (GitCredential.Credential, String?) -> String?,
|
||||
progressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
|
|
@ -253,30 +250,30 @@ public class PasswordStore {
|
|||
let remote = try GTRemote(name: "origin", in: storeRepository)
|
||||
try storeRepository.pull(storeRepository.currentBranch(), from: remote, withOptions: options, progress: progressBlock)
|
||||
Defaults.lastSyncedTime = Date()
|
||||
self.setAllSynced()
|
||||
setAllSynced()
|
||||
DispatchQueue.main.async {
|
||||
self.updatePasswordEntityCoreData()
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func updatePasswordEntityCoreData() {
|
||||
deleteCoreData(entityName: "PasswordEntity")
|
||||
do {
|
||||
var q = try fm.contentsOfDirectory(atPath: self.storeURL.path).filter {
|
||||
var q = try fm.contentsOfDirectory(atPath: storeURL.path).filter {
|
||||
!$0.hasPrefix(".")
|
||||
}.map { (filename) -> PasswordEntity in
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
if filename.hasSuffix(".gpg") {
|
||||
passwordEntity.name = String(filename.prefix(upTo: filename.index(filename.endIndex, offsetBy: -4)))
|
||||
} else {
|
||||
passwordEntity.name = filename
|
||||
}
|
||||
passwordEntity.path = filename
|
||||
passwordEntity.parent = nil
|
||||
return passwordEntity
|
||||
}.map { (filename) -> PasswordEntity in
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
if filename.hasSuffix(".gpg") {
|
||||
passwordEntity.name = String(filename.prefix(upTo: filename.index(filename.endIndex, offsetBy: -4)))
|
||||
} else {
|
||||
passwordEntity.name = filename
|
||||
}
|
||||
passwordEntity.path = filename
|
||||
passwordEntity.parent = nil
|
||||
return passwordEntity
|
||||
}
|
||||
while q.count > 0 {
|
||||
while !q.isEmpty {
|
||||
let e = q.first!
|
||||
q.remove(at: 0)
|
||||
guard !e.name!.hasPrefix(".") else {
|
||||
|
|
@ -309,9 +306,9 @@ public class PasswordStore {
|
|||
} catch {
|
||||
print(error)
|
||||
}
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
}
|
||||
|
||||
|
||||
public func getRecentCommits(count: Int) throws -> [GTCommit] {
|
||||
guard let storeRepository = storeRepository else {
|
||||
return []
|
||||
|
|
@ -328,7 +325,7 @@ public class PasswordStore {
|
|||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
|
||||
public func fetchPasswordEntityCoreData(parent: PasswordEntity?) -> [PasswordEntity] {
|
||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -339,7 +336,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswords".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func fetchPasswordEntityCoreData(withDir: Bool) -> [PasswordEntity] {
|
||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
|
|
@ -352,8 +349,7 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswords".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func fetchUnsyncedPasswords() -> [PasswordEntity] {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
|
||||
|
|
@ -364,17 +360,17 @@ public class PasswordStore {
|
|||
fatalError("FailedToFetchPasswords".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func setAllSynced() {
|
||||
let passwordEntities = fetchUnsyncedPasswords()
|
||||
if passwordEntities.count > 0 {
|
||||
if !passwordEntities.isEmpty {
|
||||
for passwordEntity in passwordEntities {
|
||||
passwordEntity.synced = true
|
||||
}
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func getLatestUpdateInfo(filename: String) -> String {
|
||||
guard let storeRepository = storeRepository else {
|
||||
return "Unknown".localize()
|
||||
|
|
@ -383,7 +379,7 @@ public class PasswordStore {
|
|||
let latestCommitTime = blameHunks.map({
|
||||
$0.finalSignature?.time?.timeIntervalSince1970 ?? 0
|
||||
}).max() else {
|
||||
return "Unknown".localize()
|
||||
return "Unknown".localize()
|
||||
}
|
||||
let lastCommitDate = Date(timeIntervalSince1970: latestCommitTime)
|
||||
if Date().timeIntervalSince(lastCommitDate) <= 60 {
|
||||
|
|
@ -391,10 +387,9 @@ public class PasswordStore {
|
|||
}
|
||||
return PasswordStore.dateFormatter.string(from: lastCommitDate)
|
||||
}
|
||||
|
||||
public func updateRemoteRepo() {
|
||||
}
|
||||
|
||||
|
||||
public func updateRemoteRepo() {}
|
||||
|
||||
private func gitAdd(path: String) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -402,7 +397,7 @@ public class PasswordStore {
|
|||
try storeRepository.index().addFile(path)
|
||||
try storeRepository.index().write()
|
||||
}
|
||||
|
||||
|
||||
private func gitRm(path: String) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -414,7 +409,7 @@ public class PasswordStore {
|
|||
try storeRepository.index().removeFile(path)
|
||||
try storeRepository.index().write()
|
||||
}
|
||||
|
||||
|
||||
private func deleteDirectoryTree(at url: URL) throws {
|
||||
var tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||
var count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||
|
|
@ -424,12 +419,12 @@ public class PasswordStore {
|
|||
count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func createDirectoryTree(at url: URL) throws {
|
||||
let tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||
try fm.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
|
||||
|
||||
private func gitMv(from: String, to: String) throws {
|
||||
let fromURL = storeURL.appendingPathComponent(from)
|
||||
let toURL = storeURL.appendingPathComponent(to)
|
||||
|
|
@ -437,7 +432,7 @@ public class PasswordStore {
|
|||
try gitAdd(path: to)
|
||||
try gitRm(path: from)
|
||||
}
|
||||
|
||||
|
||||
private func gitCommit(message: String) throws -> GTCommit? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -453,7 +448,7 @@ public class PasswordStore {
|
|||
let commit = try storeRepository.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
||||
return commit
|
||||
}
|
||||
|
||||
|
||||
private func getLocalBranch(withName branchName: String) throws -> GTBranch? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -462,7 +457,7 @@ public class PasswordStore {
|
|||
let branches = try storeRepository.branches(withPrefix: reference)
|
||||
return branches.first
|
||||
}
|
||||
|
||||
|
||||
public func pushRepository(credential: GitCredential, requestCredentialPassword: @escaping (GitCredential.Credential, String?) -> String?, transferProgressBlock: @escaping (UInt32, UInt32, Int, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -477,12 +472,12 @@ public class PasswordStore {
|
|||
throw AppError.GitPushNotSuccessful
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func addPasswordEntities(password: Password) throws -> PasswordEntity? {
|
||||
guard !passwordExisted(password: password) else {
|
||||
throw AppError.PasswordDuplicated
|
||||
}
|
||||
|
||||
|
||||
var passwordURL = password.url
|
||||
var previousPathLength = Int.max
|
||||
var paths: [String] = []
|
||||
|
|
@ -490,20 +485,20 @@ public class PasswordStore {
|
|||
paths.append(passwordURL.path)
|
||||
passwordURL = passwordURL.deletingLastPathComponent()
|
||||
// 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
|
||||
}
|
||||
previousPathLength = passwordURL.path.count
|
||||
}
|
||||
paths.reverse()
|
||||
var parentPasswordEntity: PasswordEntity? = nil
|
||||
var parentPasswordEntity: PasswordEntity?
|
||||
for path in paths {
|
||||
let isDir = !path.hasSuffix(".gpg")
|
||||
if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) {
|
||||
passwordEntity.synced = false
|
||||
parentPasswordEntity = passwordEntity
|
||||
} 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()!)!
|
||||
if isDir {
|
||||
passwordEntity.name = pathURL.lastPathComponent
|
||||
|
|
@ -518,72 +513,73 @@ public class PasswordStore {
|
|||
}
|
||||
}
|
||||
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
return parentPasswordEntity
|
||||
}
|
||||
|
||||
|
||||
public func add(password: Password, keyID: String? = nil) throws -> PasswordEntity? {
|
||||
try createDirectoryTree(at: password.url)
|
||||
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)
|
||||
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)
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
|
||||
public func delete(passwordEntity: PasswordEntity) throws {
|
||||
let deletedFileURL = try passwordEntity.getURL()
|
||||
try gitRm(path: deletedFileURL.path)
|
||||
try deletePasswordEntities(passwordEntity: passwordEntity)
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
public func edit(passwordEntity: PasswordEntity, password: Password, keyID: String? = nil) throws -> PasswordEntity? {
|
||||
var newPasswordEntity: PasswordEntity? = passwordEntity
|
||||
let url = try passwordEntity.getURL()
|
||||
|
||||
if password.changed&PasswordChange.content.rawValue != 0 {
|
||||
|
||||
if password.changed & PasswordChange.content.rawValue != 0 {
|
||||
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)
|
||||
let _ = try gitCommit(message: "EditPassword.".localize(url.deletingPathExtension().path.removingPercentEncoding!))
|
||||
_ = try gitCommit(message: "EditPassword.".localize(url.deletingPathExtension().path.removingPercentEncoding!))
|
||||
newPasswordEntity = passwordEntity
|
||||
newPasswordEntity?.synced = false
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
}
|
||||
|
||||
if password.changed&PasswordChange.path.rawValue != 0 {
|
||||
|
||||
if password.changed & PasswordChange.path.rawValue != 0 {
|
||||
let deletedFileURL = url
|
||||
// add
|
||||
try createDirectoryTree(at: password.url)
|
||||
newPasswordEntity = try addPasswordEntities(password: password)
|
||||
|
||||
|
||||
// mv
|
||||
try gitMv(from: deletedFileURL.path, to: password.url.path)
|
||||
|
||||
|
||||
// delete
|
||||
try deleteDirectoryTree(at: deletedFileURL)
|
||||
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)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
|
||||
private func deletePasswordEntities(passwordEntity: PasswordEntity) throws {
|
||||
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
|
||||
self.context.delete(current!)
|
||||
context.delete(current!)
|
||||
current = parent
|
||||
}
|
||||
self.saveUpdatedContext()
|
||||
saveUpdatedContext()
|
||||
}
|
||||
|
||||
|
||||
public func saveUpdatedContext() {
|
||||
do {
|
||||
if context.hasChanges {
|
||||
|
|
@ -593,11 +589,11 @@ public class PasswordStore {
|
|||
fatalError("FailureToSaveContext".localize(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func deleteCoreData(entityName: String) {
|
||||
let deleteFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
|
||||
|
||||
|
||||
do {
|
||||
try context.execute(deleteRequest)
|
||||
try context.save()
|
||||
|
|
@ -606,7 +602,7 @@ public class PasswordStore {
|
|||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func updateImage(passwordEntity: PasswordEntity, image: Data?) {
|
||||
guard let image = image else {
|
||||
return
|
||||
|
|
@ -625,33 +621,33 @@ public class PasswordStore {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func erase() {
|
||||
// Delete files.
|
||||
try? fm.removeItem(at: storeURL)
|
||||
try? fm.removeItem(at: tempStoreURL)
|
||||
|
||||
|
||||
// Delete PGP key, SSH key and other secrets from the keychain.
|
||||
AppKeychain.shared.removeAllContent()
|
||||
|
||||
// Delete core data.
|
||||
deleteCoreData(entityName: "PasswordEntity")
|
||||
|
||||
|
||||
// Delete default settings.
|
||||
Defaults.removeAll()
|
||||
|
||||
|
||||
// Clean up variables inside PasswordStore.
|
||||
storeRepository = nil
|
||||
|
||||
|
||||
// Delete cache explicitly.
|
||||
PasscodeLock.shared.delete()
|
||||
PGPAgent.shared.uninitKeys()
|
||||
|
||||
|
||||
// Broadcast.
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .passwordStoreErased, object: nil)
|
||||
}
|
||||
|
||||
|
||||
// return the number of discarded commits
|
||||
public func reset() throws -> Int {
|
||||
guard let storeRepository = storeRepository else {
|
||||
|
|
@ -659,26 +655,25 @@ public class PasswordStore {
|
|||
}
|
||||
// get a list of local commits
|
||||
if let localCommits = try getLocalCommits(),
|
||||
localCommits.count > 0 {
|
||||
!localCommits.isEmpty {
|
||||
// get the oldest local commit
|
||||
guard let firstLocalCommit = localCommits.last,
|
||||
firstLocalCommit.parents.count == 1,
|
||||
let newHead = firstLocalCommit.parents.first else {
|
||||
throw AppError.GitReset
|
||||
throw AppError.GitReset
|
||||
}
|
||||
try storeRepository.reset(to: newHead, resetType: .hard)
|
||||
self.setAllSynced()
|
||||
self.updatePasswordEntityCoreData()
|
||||
|
||||
setAllSynced()
|
||||
updatePasswordEntityCoreData()
|
||||
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
|
||||
return localCommits.count
|
||||
} else {
|
||||
return 0 // no new commit
|
||||
return 0 // no new commit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func getLocalCommits() throws -> [GTCommit]? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSet
|
||||
|
|
@ -692,7 +687,7 @@ public class PasswordStore {
|
|||
guard remoteBranch.oid != nil else {
|
||||
throw AppError.RepositoryRemoteBranchNotFound(remoteBranchName)
|
||||
}
|
||||
|
||||
|
||||
// get a list of local commits
|
||||
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteBranch)
|
||||
}
|
||||
|
|
@ -708,13 +703,13 @@ public class PasswordStore {
|
|||
let url = try passwordEntity.getURL()
|
||||
return Password(name: passwordEntity.getName(), url: url, plainText: plainText)
|
||||
}
|
||||
|
||||
|
||||
public func encrypt(password: Password, keyID: String? = nil) throws -> Data {
|
||||
let encryptedDataPath = storeURL.appendingPathComponent(password.url.path)
|
||||
let keyID = keyID ?? findGPGID(from: encryptedDataPath)
|
||||
return try PGPAgent.shared.encrypt(plainData: password.plainData, keyID: keyID)
|
||||
}
|
||||
|
||||
|
||||
public func removeGitSSHKeys() {
|
||||
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
|
||||
Defaults.remove(\.gitSSHKeySource)
|
||||
|
|
@ -727,8 +722,8 @@ public class PasswordStore {
|
|||
|
||||
public func findGPGID(from url: URL) -> String {
|
||||
var path = url
|
||||
while !FileManager.default.fileExists(atPath: path.appendingPathComponent(".gpg-id").path)
|
||||
&& path.path != "file:///" {
|
||||
while !FileManager.default.fileExists(atPath: path.appendingPathComponent(".gpg-id").path),
|
||||
path.path != "file:///" {
|
||||
path = path.deletingLastPathComponent()
|
||||
}
|
||||
path = path.appendingPathComponent(".gpg-id")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class PasswordTableEntry: NSObject {
|
|||
public let isDir: Bool
|
||||
public let synced: Bool
|
||||
public let categoryText: String
|
||||
|
||||
|
||||
public init(_ entity: PasswordEntity) {
|
||||
self.passwordEntity = entity
|
||||
self.title = entity.name!
|
||||
|
|
@ -22,23 +22,22 @@ public class PasswordTableEntry: NSObject {
|
|||
self.synced = entity.synced
|
||||
self.categoryText = entity.getCategoryText()
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
let titleSplit = nameWithCategory.split{ !($0.isLetter || $0.isNumber || $0 == ".") }
|
||||
let titleSplit = nameWithCategory.split { !($0.isLetter || $0.isNumber || $0 == ".") }
|
||||
for str in titleSplit {
|
||||
if (str.localizedCaseInsensitiveContains(searchText)) {
|
||||
if str.localizedCaseInsensitiveContains(searchText) {
|
||||
return true
|
||||
}
|
||||
if (searchText.localizedCaseInsensitiveContains(str)) {
|
||||
if searchText.localizedCaseInsensitiveContains(str) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public struct AdditionField: Hashable {
|
||||
|
||||
public let title: String, content: String
|
||||
|
||||
public init(title: String = "", content: String = "") {
|
||||
|
|
@ -16,7 +15,7 @@ public struct AdditionField: Hashable {
|
|||
}
|
||||
|
||||
var asString: String {
|
||||
return title.isEmpty ? content : title + ": " + content
|
||||
title.isEmpty ? content : title + ": " + content
|
||||
}
|
||||
|
||||
var asTuple: (String, String) {
|
||||
|
|
@ -25,21 +24,20 @@ public struct AdditionField: Hashable {
|
|||
}
|
||||
|
||||
extension AdditionField {
|
||||
|
||||
static func | (left: String, right: AdditionField) -> String {
|
||||
return left | right.asString
|
||||
left | right.asString
|
||||
}
|
||||
|
||||
static func | (left: AdditionField, right: String) -> String {
|
||||
return left.asString | right
|
||||
left.asString | right
|
||||
}
|
||||
|
||||
static func | (left: AdditionField, right: AdditionField) -> String {
|
||||
return left.asString | right
|
||||
left.asString | right
|
||||
}
|
||||
}
|
||||
|
||||
infix operator =>: MultiplicationPrecedence
|
||||
public func => (key: String, value: String) -> AdditionField {
|
||||
return AdditionField(title: key, content: value)
|
||||
AdditionField(title: key, content: value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public struct Constants {
|
||||
|
||||
static let OTP_SECRET = "otp_secret"
|
||||
static let OTP_TYPE = "otp_type"
|
||||
static let OTP_ALGORITHM = "otp_algorithm"
|
||||
|
|
@ -54,7 +53,7 @@ public struct Constants {
|
|||
}
|
||||
|
||||
static func isOtpKeyword(_ keyword: String) -> Bool {
|
||||
return OTP_KEYWORDS.contains(keyword.lowercased())
|
||||
OTP_KEYWORDS.contains(keyword.lowercased())
|
||||
}
|
||||
|
||||
static func isUnknown(_ string: String) -> Bool {
|
||||
|
|
@ -63,10 +62,10 @@ public struct Constants {
|
|||
}
|
||||
|
||||
static func unknown(_ number: UInt) -> String {
|
||||
return "\(UNKNOWN) \(number)"
|
||||
"\(UNKNOWN) \(number)"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public enum OTPType: String {
|
|||
case none = "None"
|
||||
|
||||
var description: String {
|
||||
return rawValue.localize()
|
||||
rawValue.localize()
|
||||
}
|
||||
|
||||
init(token: Token?) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
class Parser {
|
||||
|
||||
let firstLine: String
|
||||
let additionsSection: String
|
||||
let purgedAdditionalLines: [String]
|
||||
|
|
@ -19,8 +18,8 @@ class Parser {
|
|||
let splittedPlainText = plainText.splitByNewline()
|
||||
|
||||
firstLine = splittedPlainText.first!
|
||||
additionsSection = splittedPlainText[1...].joined(separator: "\n")
|
||||
purgedAdditionalLines = splittedPlainText[1...].filter { !$0.isEmpty }
|
||||
self.additionsSection = splittedPlainText[1...].joined(separator: "\n")
|
||||
self.purgedAdditionalLines = splittedPlainText[1...].filter { !$0.isEmpty }
|
||||
}
|
||||
|
||||
private func getAdditionFields() -> [AdditionField] {
|
||||
|
|
@ -57,7 +56,7 @@ class Parser {
|
|||
}
|
||||
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(Constants.getSeparator(breakingLines: !removingLineBreaks))
|
||||
i += 1
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import OneTimePassword
|
|||
/// * digits: `6` (default: `6`, optional)
|
||||
///
|
||||
class TokenBuilder {
|
||||
|
||||
private var name: String = ""
|
||||
private var secret: Data?
|
||||
private var type: OTPType = .totp
|
||||
|
|
@ -80,7 +79,6 @@ class TokenBuilder {
|
|||
return self
|
||||
}
|
||||
|
||||
|
||||
func build() -> Token? {
|
||||
guard secret != nil, digits != nil else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
public struct PasswordGenerator: Codable {
|
||||
|
||||
private static let digits = "0123456789"
|
||||
private static let letters = "abcdefghijklmnopqrstuvwxyz"
|
||||
private static let capitalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
|
@ -114,7 +113,7 @@ public struct PasswordGenerator: Codable {
|
|||
}
|
||||
|
||||
private func selectRandomly(count: Int, from string: String) -> [Character] {
|
||||
return (0 ..< count).map { _ in string.randomElement()! }
|
||||
(0 ..< count).map { _ in string.randomElement()! }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public enum PasswordGeneratorFlavor: String {
|
|||
case xkcd = "XKCD"
|
||||
|
||||
public var localized: String {
|
||||
return rawValue.localize()
|
||||
rawValue.localize()
|
||||
}
|
||||
|
||||
public var longNameLocalized: String {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
|
||||
import XCTest
|
||||
|
||||
// swiftformat:disable:next sortedImports
|
||||
@testable import passKit
|
||||
@testable import Crypto
|
||||
|
||||
class CryptoFrameworkTest: XCTestCase {
|
||||
|
||||
typealias MessageConverter = (CryptoPGPMessage, NSErrorPointer) -> CryptoPGPMessage?
|
||||
|
||||
private let testText = "Hello World!"
|
||||
|
|
@ -47,7 +47,7 @@ class CryptoFrameworkTest: XCTestCase {
|
|||
].forEach { testKeyInfo in
|
||||
var error: NSError?
|
||||
guard let publicKey = CryptoNewKeyFromArmored(testKeyInfo.publicKey, &error),
|
||||
let privateKey = CryptoNewKeyFromArmored(testKeyInfo.privateKey, &error) else {
|
||||
let privateKey = CryptoNewKeyFromArmored(testKeyInfo.privateKey, &error) else {
|
||||
XCTFail("Keys cannot be initialized.")
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
// Copyright © 2019 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SwiftyUserDefaults
|
||||
import XCTest
|
||||
|
||||
@testable import passKit
|
||||
|
||||
|
|
@ -144,17 +144,17 @@ class PGPAgentTest: XCTestCase {
|
|||
passphraseRequestCalledCount += 1
|
||||
return "incorrect passphrase"
|
||||
}
|
||||
|
||||
|
||||
// Provide the correct passphrase.
|
||||
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint, requestPassphrase: provideCorrectPassphrase), testData)
|
||||
XCTAssertEqual(passphraseRequestCalledCount, 1)
|
||||
|
||||
|
||||
// Provide the wrong passphrase.
|
||||
XCTAssertThrowsError(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint, requestPassphrase: provideIncorrectPassphrase)) {
|
||||
XCTAssertEqual($0 as! AppError, AppError.WrongPassphrase)
|
||||
}
|
||||
XCTAssertEqual(passphraseRequestCalledCount, 2)
|
||||
|
||||
|
||||
// Ask for the passphrase because the previous decryption has failed.
|
||||
XCTAssertEqual(try basicEncryptDecrypt(using: pgpAgent, keyID: RSA2048.fingerprint, requestPassphrase: provideCorrectPassphrase), testData)
|
||||
XCTAssertEqual(passphraseRequestCalledCount, 3)
|
||||
|
|
@ -165,4 +165,3 @@ class PGPAgentTest: XCTestCase {
|
|||
try KeyFileManager(keyType: PgpKey.PRIVATE, keyPath: "", keyHandler: keychain.add).importKey(from: privateKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class ArraySlicesTest: XCTestCase {
|
||||
|
||||
func testZeroCount() {
|
||||
XCTAssertEqual([1, 2, 3].slices(count: 0), [])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class StringUtilitiesTest: XCTestCase {
|
||||
|
||||
func testTrimmed() {
|
||||
[
|
||||
(" ", ""),
|
||||
|
|
|
|||
|
|
@ -6,20 +6,20 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
import ObjectiveGit
|
||||
import XCTest
|
||||
|
||||
@testable import passKit
|
||||
|
||||
class PasswordStoreTest: XCTestCase {
|
||||
let cloneOptions: [String : GTCredentialProvider] = {
|
||||
let credentialProvider = GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
||||
try? GTCredential(userName: "", password: "")
|
||||
}
|
||||
return [GTRepositoryCloneOptionsCredentialProvider: credentialProvider]
|
||||
}()
|
||||
let remoteRepoURL = URL(string: "https://github.com/mssun/passforios-password-store.git")!
|
||||
let cloneOptions: [String: GTCredentialProvider] = {
|
||||
let credentialProvider = GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
||||
try? GTCredential(userName: "", password: "")
|
||||
}
|
||||
return [GTRepositoryCloneOptionsCredentialProvider: credentialProvider]
|
||||
}()
|
||||
|
||||
let remoteRepoURL = URL(string: "https://github.com/mssun/passforios-password-store.git")!
|
||||
|
||||
func testCloneAndDecryptMultiKeys() throws {
|
||||
let url = URL(fileURLWithPath: "\(Globals.repositoryPath)-test")
|
||||
|
|
@ -38,8 +38,8 @@ class PasswordStoreTest: XCTestCase {
|
|||
|
||||
[
|
||||
("work/github.com", "4712286271220DB299883EA7062E678DA1024DAE"),
|
||||
("personal/github.com", "787EAE1A5FA3E749AA34CC6AA0645EBED862027E")
|
||||
].forEach {(path, id) in
|
||||
("personal/github.com", "787EAE1A5FA3E749AA34CC6AA0645EBED862027E"),
|
||||
].forEach { path, id in
|
||||
let keyID = findGPGID(from: url.appendingPathComponent(path))
|
||||
XCTAssertEqual(keyID, id)
|
||||
}
|
||||
|
|
@ -57,17 +57,14 @@ class PasswordStoreTest: XCTestCase {
|
|||
|
||||
let testPassword = Password(name: "test", url: URL(string: "test.gpg")!, plainText: "testpassword")
|
||||
let testPasswordEntity = try passwordStore.add(password: testPassword)!
|
||||
let testPasswordPlain = try passwordStore.decrypt(passwordEntity: testPasswordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase )
|
||||
let testPasswordPlain = try passwordStore.decrypt(passwordEntity: testPasswordEntity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||
XCTAssertEqual(testPasswordPlain.plainText, "testpassword")
|
||||
|
||||
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)!
|
||||
return try passwordStore.decrypt(passwordEntity: entity, requestPGPKeyPassphrase: requestPGPKeyPassphrase )
|
||||
|
||||
return try passwordStore.decrypt(passwordEntity: entity, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class PasswordTableEntryTest: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
let nameWithCategoryList = [
|
||||
"github",
|
||||
|
|
@ -27,13 +26,13 @@ class PasswordTableEntryTest: XCTestCase {
|
|||
]
|
||||
let searchTextList1 = [
|
||||
"github.com",
|
||||
"www.github.com"
|
||||
"www.github.com",
|
||||
]
|
||||
let searchTextList2 = [
|
||||
"xx.com",
|
||||
"www.xx.com"
|
||||
"www.xx.com",
|
||||
]
|
||||
|
||||
|
||||
for nameWithCategory in nameWithCategoryList {
|
||||
for searchText in searchTextList1 {
|
||||
XCTAssertTrue(PasswordTableEntry.match(nameWithCategory: nameWithCategory, searchText: searchText))
|
||||
|
|
@ -43,5 +42,4 @@ class PasswordTableEntryTest: XCTestCase {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class PasswordTest: XCTestCase {
|
||||
|
||||
func testUrl() {
|
||||
let password = getPasswordObjectWith(content: "")
|
||||
|
||||
|
|
@ -245,7 +244,6 @@ class PasswordTest: XCTestCase {
|
|||
XCTAssertEqual(password.nameFromPath, "exampleusername")
|
||||
}
|
||||
|
||||
|
||||
func testMultilineValues() {
|
||||
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!"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class AdditionFieldTest: XCTestCase {
|
||||
|
||||
func testAdditionField() {
|
||||
let field1 = AdditionField(title: "key", content: "value")
|
||||
let field2 = AdditionField(title: "no content")
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class ConstantsTest: XCTestCase {
|
||||
|
||||
func testIsOtpRelated() {
|
||||
XCTAssert(Constants.isOtpRelated(line: "otpauth://something"))
|
||||
XCTAssert(Constants.isOtpRelated(line: "otp_algorithm: algorithm"))
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class OTPTypeTest: XCTestCase {
|
||||
|
||||
func testInitFromToken() {
|
||||
let secret = "secret".data(using: .utf8)!
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
// Copyright © 2018 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import passKit
|
||||
import XCTest
|
||||
@testable import passKit
|
||||
|
||||
class ParserTest: XCTestCase {
|
||||
|
||||
func testInit() {
|
||||
[
|
||||
("", "", "", []),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class TokenBuilderTest: XCTestCase {
|
||||
|
||||
private let SECRET = "secret"
|
||||
private let DIGITS = Constants.DEFAULT_DIGITS
|
||||
private let TIMER = Generator.Factor.timer(period: Constants.DEFAULT_PERIOD)
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class PasswordGeneratorFlavorTest: XCTestCase {
|
||||
|
||||
func testLengthLimits() {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class PasswordGeneratorTest: XCTestCase {
|
||||
|
||||
func testLimitedLength() {
|
||||
[
|
||||
PasswordGenerator(length: 15),
|
||||
|
|
|
|||
|
|
@ -21,15 +21,15 @@ class DictBasedKeychain: KeyStore {
|
|||
}
|
||||
|
||||
public func contains(key: String) -> Bool {
|
||||
return store[key] != nil
|
||||
store[key] != nil
|
||||
}
|
||||
|
||||
public func get(for key: String) -> Data? {
|
||||
return store[key] as? Data
|
||||
store[key] as? Data
|
||||
}
|
||||
|
||||
public func get(for key: String) -> String? {
|
||||
return store[key] as? String
|
||||
store[key] as? String
|
||||
}
|
||||
|
||||
public func removeContent(for key: String) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ let MULTILINE_BLOCK_START = "multiline block" => "|"
|
|||
let MULTILINE_LINE_START = "multiline line" => ">"
|
||||
|
||||
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,
|
||||
|
|
@ -44,11 +44,11 @@ func assertDefaults(in password: Password, with passwordString: String, and addi
|
|||
}
|
||||
|
||||
infix operator ∈: AdditionPrecedence
|
||||
func ∈(field: AdditionField, password: Password) -> Bool {
|
||||
return password.getFilteredAdditions().contains(field)
|
||||
func ∈ (field: AdditionField, password: Password) -> Bool {
|
||||
password.getFilteredAdditions().contains(field)
|
||||
}
|
||||
|
||||
infix operator ∉: AdditionPrecedence
|
||||
func ∉(field: AdditionField, password: Password) -> Bool {
|
||||
return !(field ∈ password)
|
||||
func ∉ (field: AdditionField, password: Password) -> Bool {
|
||||
!(field ∈ password)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
struct PGPTestSet {
|
||||
|
||||
fileprivate static var ALL_TEST_SETS: [String: PGPTestSet] = [:]
|
||||
|
||||
let publicKey: String
|
||||
|
|
@ -81,14 +80,14 @@ let NISTP384 = PGPTestSet(
|
|||
passphrase: "soirofssap"
|
||||
).collect()
|
||||
|
||||
let RSA2048_RSA4096 = MultiKeyPGPTestSet(
|
||||
let RSA2048_RSA4096 = MultiKeyPGPTestSet(
|
||||
publicKeys: PGP_RSA2048_PUBLIC_KEY | PGP_RSA4096_PUBLIC_KEY,
|
||||
privateKeys: PGP_RSA2048_PRIVATE_KEY | PGP_RSA4096_PRIVATE_KEY,
|
||||
fingerprints: ["a1024dae", "d862027e"],
|
||||
passphrases: ["passforios", "passforios"]
|
||||
)
|
||||
|
||||
let ED25519_NISTP384 = MultiKeyPGPTestSet(
|
||||
let ED25519_NISTP384 = MultiKeyPGPTestSet(
|
||||
publicKeys: PGP_ED25519_PUBLIC_KEY | PGP_NISTP384_PUBLIC_KEY,
|
||||
privateKeys: PGP_ED25519_PRIVATE_KEY | PGP_NISTP384_PRIVATE_KEY,
|
||||
fingerprints: ["e9444483", "5af3c085"],
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import XCTest
|
|||
@testable import passKit
|
||||
|
||||
class passKitTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// 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() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Intents
|
|||
import passKit
|
||||
|
||||
class IntentHandler: INExtension {
|
||||
|
||||
override func handler(for intent: INIntent) -> Any {
|
||||
guard intent is SyncRepositoryIntent else {
|
||||
fatalError("Unhandled intent type \(intent).")
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Intents
|
|||
import passKit
|
||||
|
||||
public class SyncRepositoryIntentHandler: NSObject, SyncRepositoryIntentHandling {
|
||||
|
||||
private let passwordStore = PasswordStore.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 {
|
||||
completion(SyncRepositoryIntentResponse(code: .noRepository, userActivity: nil))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
import XCTest
|
||||
|
||||
class passTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// 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() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue