restructure file organization
This commit is contained in:
parent
7effaa841a
commit
910660ede3
19 changed files with 52 additions and 20 deletions
41
pass/Controllers/AdvancedSettingsTableViewController.swift
Normal file
41
pass/Controllers/AdvancedSettingsTableViewController.swift
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// AdvancedSettingsTableViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 7/2/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
|
||||
class AdvancedSettingsTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var eraseDataTableViewCell: UITableViewCell!
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if tableView.cellForRow(at: indexPath) == eraseDataTableViewCell {
|
||||
print("erase data")
|
||||
let alert = UIAlertController(title: "Erase Password Store Data?", message: "This will delete all local data and settings. Password store data on your remote server will not be affected.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "Erase Password Data", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in
|
||||
SVProgressHUD.show(withStatus: "Erasing ...")
|
||||
DispatchQueue.global(qos: .userInitiated).sync {
|
||||
PasswordStore.shared.erase()
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(Notification(name: Notification.Name("passwordStoreErased")))
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
}
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.cancel, handler:nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// GitRepositoryAuthenticationSettingTableViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 25/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class GitRepositoryAuthenticationSettingTableViewController: UITableViewController {
|
||||
|
||||
var selectedMethod: String?
|
||||
|
||||
@IBOutlet weak var sshKeyCell: UITableViewCell!
|
||||
@IBOutlet weak var passwordCell: UITableViewCell!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
navigationItem.title = "Auth Method"
|
||||
switch selectedMethod! {
|
||||
case "Password":
|
||||
passwordCell.accessoryType = UITableViewCellAccessoryType.checkmark
|
||||
case "SSH Key":
|
||||
sshKeyCell.accessoryType = UITableViewCellAccessoryType.checkmark
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
94
pass/Controllers/GitServerSettingTableViewController.swift
Normal file
94
pass/Controllers/GitServerSettingTableViewController.swift
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// GitServerSettingTableViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 21/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
|
||||
class GitServerSettingTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var gitRepositoryURLTextField: UITextField!
|
||||
@IBOutlet weak var usernameTextField: UITextField!
|
||||
@IBOutlet weak var passwordTextField: UITextField!
|
||||
@IBOutlet weak var authenticationTableViewCell: UITableViewCell!
|
||||
|
||||
var authenticationMethod = Defaults[.gitRepositoryAuthenticationMethod]
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
if let url = Defaults[.gitRepositoryURL] {
|
||||
gitRepositoryURLTextField.text = url.absoluteString
|
||||
}
|
||||
usernameTextField.text = Defaults[.gitRepositoryUsername]
|
||||
passwordTextField.text = Defaults[.gitRepositoryPassword]
|
||||
authenticationTableViewCell.detailTextLabel?.text = authenticationMethod
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
if authenticationMethod == "SSH Key" {
|
||||
if Defaults[.gitRepositorySSHPublicKeyURL] == nil && Defaults[.gitRepositorySSHPrivateKeyURL] == nil {
|
||||
authenticationMethod = "Password"
|
||||
|
||||
let alertController = UIAlertController(title: "Attention", message: "Please setup SSH key first.", preferredStyle: .alert)
|
||||
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
|
||||
alertController.addAction(defaultAction)
|
||||
present(alertController, animated: true, completion: nil)
|
||||
} else {
|
||||
authenticationMethod = "SSH Key"
|
||||
}
|
||||
}
|
||||
authenticationTableViewCell.detailTextLabel?.text = authenticationMethod
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
view.endEditing(true)
|
||||
}
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if identifier == "saveGitServerSettingSegue" {
|
||||
if gitRepositoryURLTextField.text == "" || authenticationMethod == "" {
|
||||
var alertMessage = ""
|
||||
if gitRepositoryURLTextField.text == "" {
|
||||
alertMessage = "Git Server is not set. Please set the Git server first."
|
||||
} else if authenticationMethod == "" {
|
||||
alertMessage = "Authentication method is not set. Please set your authentication method first."
|
||||
}
|
||||
let alert = UIAlertController(title: "Cannot Save Settings", message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@IBAction func save(segue: UIStoryboardSegue) {
|
||||
if let controller = segue.source as? UITableViewController {
|
||||
if controller.tableView.indexPathForSelectedRow == IndexPath(row: 0, section:0) {
|
||||
authenticationMethod = "Password"
|
||||
} else {
|
||||
authenticationMethod = "SSH Key"
|
||||
}
|
||||
}
|
||||
authenticationTableViewCell.detailTextLabel?.text = authenticationMethod
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
if segue.identifier == "selectAuthenticationMethod" {
|
||||
if let controller = segue.destination as? GitRepositoryAuthenticationSettingTableViewController {
|
||||
controller.selectedMethod = authenticationTableViewCell.detailTextLabel!.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
pass/Controllers/PGPKeySettingTableViewController.swift
Normal file
35
pass/Controllers/PGPKeySettingTableViewController.swift
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// PGPKeySettingTableViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 21/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
|
||||
class PGPKeySettingTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var pgpKeyURLTextField: UITextField!
|
||||
@IBOutlet weak var pgpKeyPassphraseTextField: UITextField!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
pgpKeyURLTextField.text = Defaults[.pgpKeyURL]?.absoluteString
|
||||
pgpKeyPassphraseTextField.text = Defaults[.pgpKeyPassphrase]
|
||||
}
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if identifier == "savePGPKeySegue" {
|
||||
if URL(string: pgpKeyURLTextField.text!)!.scheme! == "http" {
|
||||
let alertMessage = "HTTP connection is not supported."
|
||||
let alert = UIAlertController(title: "Cannot Save Settings", message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
166
pass/Controllers/PasswordDetailTableViewController.swift
Normal file
166
pass/Controllers/PasswordDetailTableViewController.swift
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// PasswordDetailTableViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 2/2/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
|
||||
var passwordEntity: PasswordEntity?
|
||||
var passwordCategoryEntities: [PasswordCategoryEntity]?
|
||||
var passwordCategoryText = ""
|
||||
var password = Password()
|
||||
|
||||
struct TableCell {
|
||||
var title: String
|
||||
var content: String
|
||||
}
|
||||
|
||||
struct TableSection {
|
||||
var title: String
|
||||
var item: Array<TableCell>
|
||||
}
|
||||
|
||||
var tableData = Array<TableSection>()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
|
||||
tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell")
|
||||
|
||||
let passwordCategoryArray = passwordCategoryEntities?.map({ (value: PasswordCategoryEntity) -> String in
|
||||
value.category!
|
||||
})
|
||||
passwordCategoryText = (passwordCategoryArray?.joined(separator: " > "))!
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:)))
|
||||
tableView.addGestureRecognizer(tapGesture)
|
||||
tapGesture.delegate = self
|
||||
|
||||
tableView.contentInset = UIEdgeInsetsMake(-36, 0, 0, 0);
|
||||
tableView.rowHeight = UITableViewAutomaticDimension
|
||||
tableView.estimatedRowHeight = 52
|
||||
let indicatorLable = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 21))
|
||||
indicatorLable.center = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height * 0.382 + 22)
|
||||
indicatorLable.backgroundColor = UIColor.clear
|
||||
indicatorLable.textColor = UIColor.gray
|
||||
indicatorLable.text = "decrypting password"
|
||||
indicatorLable.textAlignment = .center
|
||||
indicatorLable.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
|
||||
indicator.center = CGPoint(x: view.frame.size.width / 2, y: view.frame.size.height * 0.382)
|
||||
indicator.startAnimating()
|
||||
tableView.addSubview(indicator)
|
||||
tableView.addSubview(indicatorLable)
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
self.password = try self.passwordEntity!.decrypt()!
|
||||
} catch {
|
||||
let alert = UIAlertController(title: "Cannot Show Password", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(UIAlertAction) -> Void in
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
var tableDataIndex = 0
|
||||
self.tableData.append(TableSection(title: "", item: []))
|
||||
if self.password.username != "" {
|
||||
self.tableData[tableDataIndex].item.append(TableCell(title: "username", content: self.password.username))
|
||||
}
|
||||
self.tableData[tableDataIndex].item.append(TableCell(title: "password", content: self.password.password))
|
||||
|
||||
if self.password.additions.count > 0 {
|
||||
self.tableData.append(TableSection(title: "additions", item: []))
|
||||
tableDataIndex += 1
|
||||
for addition in self.password.additions {
|
||||
self.tableData[tableDataIndex].item.append(TableCell(title: addition.title, content: addition.content))
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.tableView.reloadData()
|
||||
indicator.stopAnimating()
|
||||
indicatorLable.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tapMenu(recognizer: UITapGestureRecognizer) {
|
||||
if recognizer.state == UIGestureRecognizerState.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 {
|
||||
tappedCell.becomeFirstResponder()
|
||||
let menuController = UIMenuController.shared
|
||||
let revealItem = UIMenuItem(title: "Reveal", action: #selector(LabelTableViewCell.revealPassword(_:)))
|
||||
let concealItem = UIMenuItem(title: "Conceal", action: #selector(LabelTableViewCell.concealPassword(_:)))
|
||||
let openURLItem = UIMenuItem(title: "Copy Password & Open Link", action: #selector(LabelTableViewCell.openLink(_:)))
|
||||
menuController.menuItems = [revealItem, concealItem, openURLItem]
|
||||
menuController.setTargetRect(tappedCell.contentLabel.frame, in: tappedCell.contentLabel.superview!)
|
||||
menuController.setMenuVisible(true, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return tableData.count + 1
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
if section == 0 {
|
||||
return 1
|
||||
}
|
||||
return tableData[section - 1].item.count
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let sectionIndex = indexPath.section
|
||||
let rowIndex = indexPath.row
|
||||
|
||||
if sectionIndex == 0 && rowIndex == 0 {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordDetailTitleTableViewCell", for: indexPath) as! PasswordDetailTitleTableViewCell
|
||||
cell.passwordImageImageView.image = #imageLiteral(resourceName: "PasswordImagePlaceHolder")
|
||||
cell.nameLabel.text = passwordEntity?.name
|
||||
cell.categoryLabel.text = passwordCategoryText
|
||||
return cell
|
||||
|
||||
} else {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "labelCell", for: indexPath) as! LabelTableViewCell
|
||||
let titleData = tableData[sectionIndex - 1].item[rowIndex].title
|
||||
let contentData = tableData[sectionIndex - 1].item[rowIndex].content
|
||||
cell.password = password
|
||||
cell.isPasswordCell = (titleData.lowercased() == "password" ? true : false)
|
||||
cell.isURLCell = (titleData.lowercased() == "url" ? true : false)
|
||||
cell.cellData = LabelTableViewCellData(title: titleData, content: contentData)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
if section == 0 {
|
||||
return nil
|
||||
}
|
||||
return tableData[section - 1].title
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
|
||||
if action == #selector(copy(_:)) {
|
||||
UIPasteboard.general.string = tableData[indexPath.section].item[indexPath.row].content
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
|
||||
return action == #selector(UIResponderStandardEditActions.copy(_:))
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
281
pass/Controllers/PasswordsViewController.swift
Normal file
281
pass/Controllers/PasswordsViewController.swift
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
//
|
||||
// PasswordsViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 3/2/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Result
|
||||
import SVProgressHUD
|
||||
import SwiftyUserDefaults
|
||||
import PasscodeLock
|
||||
|
||||
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
private var passwordEntities: [PasswordEntity]?
|
||||
var filteredPasswordEntities = [PasswordEntity]()
|
||||
var sections : [(index: Int, length :Int, title: String)] = Array()
|
||||
var searchActive : Bool = false
|
||||
let searchController = UISearchController(searchResultsController: nil)
|
||||
lazy var refreshControl: UIRefreshControl = {
|
||||
let refreshControl = UIRefreshControl()
|
||||
refreshControl.addTarget(self, action: #selector(PasswordsViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
|
||||
return refreshControl
|
||||
}()
|
||||
let searchBarView = UIView(frame: CGRect(x: 0, y: 64, width: UIScreen.main.bounds.width, height: 44))
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
|
||||
func syncPasswords() {
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
SVProgressHUD.show(withStatus: "Sync Password Store")
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
do {
|
||||
try PasswordStore.shared.pullRepository(transferProgressBlock: {(git_transfer_progress, stop) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "Pull Remote Repository")
|
||||
}
|
||||
})
|
||||
DispatchQueue.main.async {
|
||||
self.passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
||||
self.reloadTableView(data: self.passwordEntities!)
|
||||
Defaults[.lastUpdatedTime] = Date()
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
||||
SVProgressHUD.dismiss(withDelay: 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnPasswordUpdatedNotification), name: NSNotification.Name(rawValue: "passwordUpdated"), object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnPasswordStoreErasedNotification), name: NSNotification.Name(rawValue: "passwordStoreErased"), object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnSearchNotification), name: NSNotification.Name(rawValue: "search"), object: nil)
|
||||
|
||||
generateSections(item: passwordEntities!)
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
searchController.searchResultsUpdater = self
|
||||
searchController.dimsBackgroundDuringPresentation = false
|
||||
searchController.searchBar.isTranslucent = false
|
||||
searchController.searchBar.backgroundColor = UIColor.gray
|
||||
searchController.searchBar.sizeToFit()
|
||||
definesPresentationContext = true
|
||||
searchBarView.addSubview(searchController.searchBar)
|
||||
view.addSubview(searchBarView)
|
||||
tableView.insertSubview(refreshControl, at: 0)
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
updateRefreshControlTitle()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
if let path = tableView.indexPathForSelectedRow {
|
||||
tableView.deselectRow(at: path, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
searchBarView.frame = CGRect(x: 0, y: navigationController!.navigationBar.bounds.size.height + UIApplication.shared.statusBarFrame.height, width: UIScreen.main.bounds.width, height: 44)
|
||||
searchController.searchBar.sizeToFit()
|
||||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return sections.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return sections[section].length
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||
var password: PasswordEntity
|
||||
let index = sections[indexPath.section].index + indexPath.row
|
||||
if searchController.isActive && searchController.searchBar.text != "" {
|
||||
password = filteredPasswordEntities[index]
|
||||
} else {
|
||||
password = passwordEntities![index]
|
||||
}
|
||||
cell.textLabel?.text = password.name
|
||||
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
|
||||
longPressGestureRecognizer.minimumPressDuration = 0.6
|
||||
cell.addGestureRecognizer(longPressGestureRecognizer)
|
||||
return cell
|
||||
}
|
||||
|
||||
func longPressAction(_ gesture: UILongPressGestureRecognizer) {
|
||||
if gesture.state == UIGestureRecognizerState.began {
|
||||
let touchPoint = gesture.location(in: tableView)
|
||||
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
|
||||
copyToPasteboard(from: indexPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
return sections[section].title
|
||||
}
|
||||
|
||||
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
|
||||
return sections.map { $0.title }
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
|
||||
return index
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||
copyToPasteboard(from: indexPath)
|
||||
}
|
||||
|
||||
func copyToPasteboard(from indexPath: IndexPath) {
|
||||
let index = sections[indexPath.section].index + indexPath.row
|
||||
let password: PasswordEntity
|
||||
if searchController.isActive && searchController.searchBar.text != "" {
|
||||
password = filteredPasswordEntities[index]
|
||||
} else {
|
||||
password = passwordEntities![index]
|
||||
}
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
SVProgressHUD.setDefaultMaskType(.clear)
|
||||
SVProgressHUD.setDefaultStyle(.dark)
|
||||
SVProgressHUD.show(withStatus: "Decrypting")
|
||||
DispatchQueue.global(qos: .userInteractive).async {
|
||||
var decryptedPassword: Password?
|
||||
do {
|
||||
decryptedPassword = try password.decrypt()!
|
||||
} catch {
|
||||
print(error)
|
||||
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
UIPasteboard.general.string = decryptedPassword?.password
|
||||
SVProgressHUD.showSuccess(withStatus: "Password Copied")
|
||||
SVProgressHUD.dismiss(withDelay: 0.6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateSections(item: [PasswordEntity]) {
|
||||
sections.removeAll()
|
||||
if item.count == 0 {
|
||||
return
|
||||
}
|
||||
var index = 0
|
||||
for i in 0 ..< item.count {
|
||||
let name = item[index].name!.uppercased()
|
||||
let commonPrefix = item[i].name!.commonPrefix(with: name, options: .caseInsensitive)
|
||||
if commonPrefix.characters.count == 0 {
|
||||
let firstCharacter = name[name.startIndex]
|
||||
let newSection = (index: index, length: i - index, title: "\(firstCharacter)")
|
||||
sections.append(newSection)
|
||||
index = i
|
||||
}
|
||||
}
|
||||
let name = item[index].name!.uppercased()
|
||||
let firstCharacter = name[name.startIndex]
|
||||
let newSection = (index: index, length: item.count - index, title: "\(firstCharacter)")
|
||||
sections.append(newSection)
|
||||
}
|
||||
|
||||
func actOnPasswordUpdatedNotification() {
|
||||
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
||||
reloadTableView(data: passwordEntities!)
|
||||
}
|
||||
|
||||
func actOnPasswordStoreErasedNotification() {
|
||||
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
||||
reloadTableView(data: passwordEntities!)
|
||||
}
|
||||
|
||||
func actOnSearchNotification() {
|
||||
searchController.searchBar.becomeFirstResponder()
|
||||
}
|
||||
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if identifier == "showPasswordDetail" {
|
||||
if Defaults[.pgpKeyID] == "" {
|
||||
let alert = UIAlertController(title: "Cannot Show Password", message: "PGP Key is not set. Please set your PGP Key first.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
if let s = sender as? UITableViewCell {
|
||||
let selectedIndexPath = tableView.indexPath(for: s)!
|
||||
tableView.deselectRow(at: selectedIndexPath, animated: true)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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 index = sections[selectedIndexPath.section].index + selectedIndexPath.row
|
||||
let passwordEntity: PasswordEntity
|
||||
if searchController.isActive && searchController.searchBar.text != "" {
|
||||
passwordEntity = filteredPasswordEntities[index]
|
||||
} else {
|
||||
passwordEntity = passwordEntities![index]
|
||||
}
|
||||
viewController.passwordEntity = passwordEntity
|
||||
let passwordCategoryEntities = PasswordStore.shared.fetchPasswordCategoryEntityCoreData(password: passwordEntity)
|
||||
viewController.passwordCategoryEntities = passwordCategoryEntities
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterContentForSearchText(searchText: String, scope: String = "All") {
|
||||
filteredPasswordEntities = passwordEntities!.filter { password in
|
||||
return password.name!.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
if searchController.isActive && searchController.searchBar.text != "" {
|
||||
reloadTableView(data: filteredPasswordEntities)
|
||||
} else {
|
||||
reloadTableView(data: passwordEntities!)
|
||||
}
|
||||
}
|
||||
|
||||
func updateRefreshControlTitle() {
|
||||
var atribbutedTitle = "Pull to Sync Password Store"
|
||||
if let lastUpdatedTime = Defaults[.lastUpdatedTime] {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .long
|
||||
formatter.timeStyle = .short
|
||||
let dateString = formatter.string(from: lastUpdatedTime)
|
||||
atribbutedTitle = "Last Sync: \(dateString)"
|
||||
}
|
||||
refreshControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
|
||||
}
|
||||
|
||||
func reloadTableView (data: [PasswordEntity]) {
|
||||
generateSections(item: data)
|
||||
tableView.reloadData()
|
||||
updateRefreshControlTitle()
|
||||
}
|
||||
|
||||
func handleRefresh(_ refreshControl: UIRefreshControl) {
|
||||
syncPasswords()
|
||||
refreshControl.endRefreshing()
|
||||
}
|
||||
}
|
||||
|
||||
extension PasswordsViewController: UISearchResultsUpdating {
|
||||
func updateSearchResults(for searchController: UISearchController) {
|
||||
filterContentForSearchText(searchText: searchController.searchBar.text!)
|
||||
}
|
||||
}
|
||||
48
pass/Controllers/SSHKeySettingTableViewController.swift
Normal file
48
pass/Controllers/SSHKeySettingTableViewController.swift
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// SSHKeySettingTableViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 25/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
|
||||
class SSHKeySettingTableViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var passphraseTextField: UITextField!
|
||||
@IBOutlet weak var privateKeyURLTextField: UITextField!
|
||||
@IBOutlet weak var publicKeyURLTextField: UITextField!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
passphraseTextField.text = Defaults[.gitRepositorySSHPrivateKeyPassphrase]
|
||||
privateKeyURLTextField.text = Defaults[.gitRepositorySSHPrivateKeyURL]?.absoluteString
|
||||
publicKeyURLTextField.text = Defaults[.gitRepositorySSHPublicKeyURL]?.absoluteString
|
||||
var doneBarButtonItem: UIBarButtonItem?
|
||||
|
||||
doneBarButtonItem = UIBarButtonItem(title: "Done",
|
||||
style: UIBarButtonItemStyle.done,
|
||||
target: self,
|
||||
action: #selector(doneButtonTapped(_:)))
|
||||
navigationItem.rightBarButtonItem = doneBarButtonItem
|
||||
navigationItem.title = "SSH Key"
|
||||
}
|
||||
|
||||
func doneButtonTapped(_ sender: UIButton) {
|
||||
Defaults[.gitRepositorySSHPublicKeyURL] = URL(string: publicKeyURLTextField.text!)
|
||||
Defaults[.gitRepositorySSHPrivateKeyURL] = URL(string: privateKeyURLTextField.text!)
|
||||
Defaults[.gitRepositorySSHPrivateKeyPassphrase] = passphraseTextField.text!
|
||||
|
||||
do {
|
||||
try Data(contentsOf: Defaults[.gitRepositorySSHPublicKeyURL]!).write(to: Globals.sshPublicKeyURL, options: .atomic)
|
||||
try Data(contentsOf: Defaults[.gitRepositorySSHPrivateKeyURL]!).write(to: Globals.sshPrivateKeyURL, options: .atomic)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
navigationController!.popViewController(animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
196
pass/Controllers/SettingsTableViewController.swift
Normal file
196
pass/Controllers/SettingsTableViewController.swift
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
//
|
||||
// SettingsTableViewController.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 18/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
import CoreData
|
||||
import SwiftyUserDefaults
|
||||
import PasscodeLock
|
||||
|
||||
class SettingsTableViewController: UITableViewController {
|
||||
|
||||
let touchIDSwitch = UISwitch(frame: CGRect.zero)
|
||||
|
||||
@IBOutlet weak var pgpKeyTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var touchIDTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var passcodeTableViewCell: UITableViewCell!
|
||||
@IBAction func cancel(segue: UIStoryboardSegue) {
|
||||
}
|
||||
|
||||
@IBAction func save(segue: UIStoryboardSegue) {
|
||||
if let controller = segue.source as? GitServerSettingTableViewController {
|
||||
let gitRepostiroyURL = controller.gitRepositoryURLTextField.text!
|
||||
let username = controller.usernameTextField.text!
|
||||
let password = controller.passwordTextField.text!
|
||||
let auth = controller.authenticationMethod
|
||||
|
||||
if Defaults[.gitRepositoryURL] == nil || gitRepostiroyURL != Defaults[.gitRepositoryURL]!.absoluteString {
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
SVProgressHUD.show(withStatus: "Prepare Repository")
|
||||
var gitCredential: GitCredential
|
||||
if auth == "Password" {
|
||||
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: username, password: password))
|
||||
} else {
|
||||
gitCredential = GitCredential(credential: GitCredential.Credential.ssh(userName: username, password: Defaults[.gitRepositorySSHPrivateKeyPassphrase]!, publicKeyFile: Globals.sshPublicKeyURL, privateKeyFile: Globals.sshPrivateKeyURL))
|
||||
}
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
try PasswordStore.shared.cloneRepository(remoteRepoURL: URL(string: gitRepostiroyURL)!,
|
||||
credential: gitCredential,
|
||||
transferProgressBlock:{ (git_transfer_progress, stop) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "Clone Remote Repository")
|
||||
}
|
||||
},
|
||||
checkoutProgressBlock: { (path, completedSteps, totalSteps) in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(Float(completedSteps)/Float(totalSteps), status: "Checkout Master Branch")
|
||||
}
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
|
||||
Defaults[.lastUpdatedTime] = Date()
|
||||
|
||||
NotificationCenter.default.post(Notification(name: Notification.Name("passwordUpdated")))
|
||||
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
print(error)
|
||||
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
||||
SVProgressHUD.dismiss(withDelay: 3)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Defaults[.gitRepositoryURL] = URL(string: gitRepostiroyURL)
|
||||
Defaults[.gitRepositoryUsername] = username
|
||||
Defaults[.gitRepositoryPassword] = password
|
||||
Defaults[.gitRepositoryAuthenticationMethod] = auth
|
||||
} else if let controller = segue.source as? PGPKeySettingTableViewController {
|
||||
|
||||
if Defaults[.pgpKeyURL] != URL(string: controller.pgpKeyURLTextField.text!) ||
|
||||
Defaults[.pgpKeyPassphrase] != controller.pgpKeyPassphraseTextField.text! {
|
||||
Defaults[.pgpKeyURL] = URL(string: controller.pgpKeyURLTextField.text!)
|
||||
Defaults[.pgpKeyPassphrase] = controller.pgpKeyPassphraseTextField.text!
|
||||
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
SVProgressHUD.show(withStatus: "Fetching PGP Key")
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
do {
|
||||
try PasswordStore.shared.initPGP(pgpKeyURL: Defaults[.pgpKeyURL]!, pgpKeyLocalPath: Globals.secringPath)
|
||||
DispatchQueue.main.async {
|
||||
self.pgpKeyTableViewCell.detailTextLabel?.text = Defaults[.pgpKeyID]
|
||||
SVProgressHUD.showSuccess(withStatus: "Success. Remember to remove the key from the server.")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
||||
SVProgressHUD.dismiss(withDelay: 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
touchIDSwitch.onTintColor = UIColor(displayP3Red: 0, green: 122.0/255, blue: 1, alpha: 1)
|
||||
touchIDTableViewCell.accessoryView = touchIDSwitch
|
||||
touchIDSwitch.addTarget(self, action: #selector(touchIDSwitchAction), for: UIControlEvents.valueChanged)
|
||||
if Defaults[.isTouchIDOn] {
|
||||
touchIDSwitch.isOn = true
|
||||
} else {
|
||||
touchIDSwitch.isOn = false
|
||||
}
|
||||
if PasscodeLockRepository().hasPasscode {
|
||||
self.passcodeTableViewCell.detailTextLabel?.text = "On"
|
||||
} else {
|
||||
self.passcodeTableViewCell.detailTextLabel?.text = "Off"
|
||||
touchIDSwitch.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if tableView.cellForRow(at: indexPath) == passcodeTableViewCell {
|
||||
if Defaults[.passcodeKey] != nil{
|
||||
showPasscodeActionSheet()
|
||||
} else {
|
||||
setPasscodeLock()
|
||||
}
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
if Defaults[.pgpKeyID] == "" {
|
||||
pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
|
||||
} else {
|
||||
pgpKeyTableViewCell.detailTextLabel?.text = Defaults[.pgpKeyID]
|
||||
}
|
||||
}
|
||||
|
||||
func touchIDSwitchAction(uiSwitch: UISwitch) {
|
||||
if uiSwitch.isOn {
|
||||
Defaults[.isTouchIDOn] = true
|
||||
Globals.passcodeConfiguration.isTouchIDAllowed = true
|
||||
} else {
|
||||
Defaults[.isTouchIDOn] = false
|
||||
Globals.passcodeConfiguration.isTouchIDAllowed = false
|
||||
}
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: Globals.passcodeConfiguration)
|
||||
}
|
||||
|
||||
func showPasscodeActionSheet() {
|
||||
let passcodeChangeViewController = PasscodeLockViewController(state: .change, configuration: Globals.passcodeConfiguration)
|
||||
let passcodeRemoveViewController = PasscodeLockViewController(state: .remove, configuration: Globals.passcodeConfiguration)
|
||||
|
||||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
let removePasscodeAction = UIAlertAction(title: "Remove Passcode", style: .destructive) { [unowned self] _ in
|
||||
passcodeRemoveViewController.successCallback = { _ in
|
||||
self.passcodeTableViewCell.detailTextLabel?.text = "Off"
|
||||
self.touchIDSwitch.isEnabled = false
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: Globals.passcodeConfiguration)
|
||||
}
|
||||
self.present(passcodeRemoveViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
let changePasscodeAction = UIAlertAction(title: "Change Passcode", style: .default) { [unowned self] _ in
|
||||
self.present(passcodeChangeViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||
optionMenu.addAction(removePasscodeAction)
|
||||
optionMenu.addAction(changePasscodeAction)
|
||||
optionMenu.addAction(cancelAction)
|
||||
self.present(optionMenu, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func setPasscodeLock() {
|
||||
let passcodeSetViewController = PasscodeLockViewController(state: .set, configuration: Globals.passcodeConfiguration)
|
||||
passcodeSetViewController.successCallback = { _ in
|
||||
self.passcodeTableViewCell.detailTextLabel?.text = "On"
|
||||
self.touchIDSwitch.isEnabled = true
|
||||
}
|
||||
present(passcodeSetViewController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue