Rewrite PasswordViewController

This commit is contained in:
Mingshen Sun 2021-01-17 19:49:05 -08:00
parent 372e897350
commit 68077bf04c
No known key found for this signature in database
GPG key ID: 1F86BA2052FED3B4
10 changed files with 676 additions and 729 deletions

View file

@ -0,0 +1,39 @@
//
// PasswordDecryptor.swift
// pass
//
// Created by Sun, Mingshen on 1/17/21.
// Copyright © 2021 Bob Sun. All rights reserved.
//
import passKit
import SVProgressHUD
import UIKit
func decryptPassword(in controller: UIViewController, with passwordPath: String, using keyID: String? = nil, completion: @escaping ((Password) -> Void)) {
DispatchQueue.global(qos: .userInteractive).async {
do {
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: controller)
let decryptedPassword = try PasswordStore.shared.decrypt(path: passwordPath, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
DispatchQueue.main.async {
completion(decryptedPassword)
}
} catch let AppError.pgpPrivateKeyNotFound(keyID: key) {
DispatchQueue.main.async {
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.pgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
let selectKey = UIAlertAction.selectKey(controller: controller) { action in
decryptPassword(in: controller, with: passwordPath, using: action.title, completion: completion)
}
alert.addAction(selectKey)
controller.present(alert, animated: true)
}
} catch {
DispatchQueue.main.async {
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: controller)
}
}
}
}

View file

@ -0,0 +1,28 @@
import passKit
func encryptPassword(in controller: UIViewController, with password: Password, keyID: String? = nil, completion: @escaping (() -> Void)) {
DispatchQueue.global(qos: .userInitiated).async {
do {
_ = try PasswordStore.shared.add(password: password, keyID: keyID)
DispatchQueue.main.async {
completion()
}
} catch let AppError.pgpPublicKeyNotFound(keyID: key) {
DispatchQueue.main.async {
let alert = UIAlertController(title: "Cannot Encrypt Password", message: AppError.pgpPublicKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
let selectKey = UIAlertAction.selectKey(controller: controller) { action in
encryptPassword(in: controller, with: password, keyID: action.title, completion: completion)
}
alert.addAction(selectKey)
controller.present(alert, animated: true)
}
return
} catch {
DispatchQueue.main.async {
Utils.alert(title: "Error".localize(), message: error.localizedDescription, controller: controller, completion: nil)
}
}
}
}

View file

@ -0,0 +1,37 @@
import UIKit
import passKit
import SVProgressHUD
class PasswordManager {
weak var viewController: UIViewController?
init(viewController: UIViewController) {
self.viewController = viewController
}
func providePasswordPasteboard(with passwordPath: String) {
guard let viewController = viewController else {
return
}
decryptPassword(in: viewController, with: passwordPath) { password in
SecurePasteboard.shared.copy(textToCopy: password.password)
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.dark)
SVProgressHUD.showSuccess(withStatus: "PasswordCopiedToPasteboard.".localize())
SVProgressHUD.dismiss(withDelay: 1)
}
}
func addPassword(with password: Password) {
guard let viewController = viewController else {
return
}
encryptPassword(in: viewController, with: password) {
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.showSuccess(withStatus: "Done".localize())
SVProgressHUD.dismiss(withDelay: 1)
}
}
}

View file

@ -0,0 +1,117 @@
//
// PasswordNavigationDataSource.swift
// pass
//
// Created by Sun, Mingshen on 1/16/21.
// Copyright © 2021 Bob Sun. All rights reserved.
//
import UIKit
import passKit
struct Section {
var title: String
var entries: [PasswordTableEntry]
}
class PasswordNavigationDataSource: NSObject, UITableViewDataSource {
var sections: [Section]
var filteredSections: [Section]
var isSearchActive = false
var cellGestureRecognizer: UIGestureRecognizer?
let hideSectionHeaderThreshold = 6
init(entries: [PasswordTableEntry] = []) {
sections = buildSections(from: entries)
filteredSections = sections
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
filteredSections[section].entries.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
showSectionTitles() ? filteredSections[section].title : nil
}
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
index
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath) as! PasswordTableViewCell
let entry = getPasswordTableEntry(at: indexPath)
cell.configure(with: entry)
if let gestureRecognizer = cellGestureRecognizer, !entry.isDir {
cell.addGestureRecognizer(gestureRecognizer)
}
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
filteredSections.count
}
func sectionIndexTitles(for _: UITableView) -> [String]? {
showSectionTitles() ? filteredSections.map(\.title) : nil
}
func showSectionTitles() -> Bool {
!isSearchActive && filteredSections.count > hideSectionHeaderThreshold
}
func getPasswordTableEntry(at indexPath: IndexPath) -> PasswordTableEntry {
filteredSections[indexPath.section].entries[indexPath.row]
}
func showTableEntries(matching text: String) {
guard !text.isEmpty else {
filteredSections = sections
return
}
filteredSections = sections.map { section in
let entries = section.entries.filter { $0.match(text) }
return Section(title: section.title, entries: entries)
}
.filter { !$0.entries.isEmpty }
}
func showUnsyncedTableEntries() {
filteredSections = sections.map { section in
let entries = section.entries.filter { !$0.synced }
return Section(title: section.title, entries: entries)
}
.filter { !$0.entries.isEmpty }
}
}
private func buildSections(from entries: [PasswordTableEntry]) -> [Section] {
let collation = UILocalizedIndexedCollation.current()
let sectionTitles = collation.sectionIndexTitles
var sections = [Section]()
// initialize all sections
for titleNumber in 0 ..< sectionTitles.count {
sections.append(Section(title: sectionTitles[titleNumber], entries: [PasswordTableEntry]()))
}
// put entries into sections
for entry in entries {
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordTableEntry.title))
sections[sectionNumber].entries.append(entry)
}
// sort each list and set sectionTitles
for titleNumber in 0 ..< sectionTitles.count {
let entriesToSort = sections[titleNumber].entries
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordTableEntry.title))
sections[titleNumber].entries = sortedEntries as! [PasswordTableEntry]
}
// only keep non-empty sections
return sections.filter { !$0.entries.isEmpty }
}