Support folder in password view
- change core data - change data struct to store table view entry - delete unnecessary functions
This commit is contained in:
parent
98b01d16cf
commit
050a960167
7 changed files with 177 additions and 89 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16E163f" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="YoR-iB-XAd">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16E175b" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="YoR-iB-XAd">
|
||||||
<device id="retina5_5" orientation="portrait">
|
<device id="retina5_5" orientation="portrait">
|
||||||
<adaptation id="fullscreen"/>
|
<adaptation id="fullscreen"/>
|
||||||
</device>
|
</device>
|
||||||
|
|
@ -43,9 +43,6 @@
|
||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
<connections>
|
|
||||||
<segue destination="tW4-E9-CGv" kind="show" identifier="showPasswordDetail" id="26n-ZD-G0k"/>
|
|
||||||
</connections>
|
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
</prototypes>
|
</prototypes>
|
||||||
<sections/>
|
<sections/>
|
||||||
|
|
@ -63,6 +60,7 @@
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="tableView" destination="Tn1-q5-vaJ" id="UHc-AS-gXh"/>
|
<outlet property="tableView" destination="Tn1-q5-vaJ" id="UHc-AS-gXh"/>
|
||||||
|
<segue destination="tW4-E9-CGv" kind="show" identifier="showPasswordDetail" id="gXF-zd-527"/>
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="6ju-JT-yds" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="6ju-JT-yds" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
||||||
numberFormatter.numberStyle = NumberFormatter.Style.decimal
|
numberFormatter.numberStyle = NumberFormatter.Style.decimal
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
|
|
||||||
let passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
let passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData(withDir: false)
|
||||||
let numberOfPasswords = numberFormatter.string(from: NSNumber(value: passwordEntities.count))!
|
let numberOfPasswords = numberFormatter.string(from: NSNumber(value: passwordEntities.count))!
|
||||||
|
|
||||||
var size = UInt64(0)
|
var size = UInt64(0)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import SVProgressHUD
|
||||||
|
|
||||||
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
|
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
|
||||||
var passwordEntity: PasswordEntity?
|
var passwordEntity: PasswordEntity?
|
||||||
var passwordCategoryEntities: [PasswordCategoryEntity]?
|
|
||||||
var passwordCategoryText = ""
|
var passwordCategoryText = ""
|
||||||
var password: Password?
|
var password: Password?
|
||||||
var passwordImage: UIImage?
|
var passwordImage: UIImage?
|
||||||
|
|
@ -62,13 +61,23 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
|
|
||||||
var tableData = Array<TableSection>()
|
var tableData = Array<TableSection>()
|
||||||
|
|
||||||
|
private func generateCategoryText() -> String {
|
||||||
|
var passwordCategoryArray: [String] = []
|
||||||
|
var parent = passwordEntity?.parent
|
||||||
|
while parent != nil {
|
||||||
|
passwordCategoryArray.append(parent!.name!)
|
||||||
|
parent = parent!.parent
|
||||||
|
}
|
||||||
|
passwordCategoryArray.reverse()
|
||||||
|
return passwordCategoryArray.joined(separator: " > ")
|
||||||
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
|
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
|
||||||
tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell")
|
tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell")
|
||||||
|
|
||||||
let passwordCategoryArray = passwordCategoryEntities?.map { $0.category! }
|
passwordCategoryText = generateCategoryText()
|
||||||
passwordCategoryText = (passwordCategoryArray?.joined(separator: " > "))!
|
|
||||||
|
|
||||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:)))
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:)))
|
||||||
tableView.addGestureRecognizer(tapGesture)
|
tableView.addGestureRecognizer(tapGesture)
|
||||||
|
|
@ -300,7 +309,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
footerLabel.numberOfLines = 0
|
footerLabel.numberOfLines = 0
|
||||||
footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
|
footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||||
footerLabel.textColor = UIColor.gray
|
footerLabel.textColor = UIColor.gray
|
||||||
let dateString = PasswordStore.shared.getLatestCommitDate(filename: (passwordEntity?.rawPath)!)
|
let dateString = PasswordStore.shared.getLatestCommitDate(filename: (passwordEntity?.path)!)
|
||||||
footerLabel.text = "Last Updated: \(dateString ?? "Unknown")"
|
footerLabel.text = "Last Updated: \(dateString ?? "Unknown")"
|
||||||
view.addSubview(footerLabel)
|
view.addSubview(footerLabel)
|
||||||
return view
|
return view
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,29 @@ import SVProgressHUD
|
||||||
import SwiftyUserDefaults
|
import SwiftyUserDefaults
|
||||||
import PasscodeLock
|
import PasscodeLock
|
||||||
|
|
||||||
|
enum PasswordsTableEntryType {
|
||||||
|
case password, dir
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PasswordsTableEntry {
|
||||||
|
var title: String
|
||||||
|
var isDir: Bool
|
||||||
|
var passwordEntity: PasswordEntity?
|
||||||
|
}
|
||||||
|
|
||||||
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||||
private var passwordEntities: [PasswordEntity]?
|
private var passwordsTableEntries: [PasswordsTableEntry] = []
|
||||||
var filteredPasswordEntities = [PasswordEntity]()
|
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
||||||
|
private var parentPasswordEntity: PasswordEntity? = nil
|
||||||
|
|
||||||
|
private func initPasswordsTableEntries() {
|
||||||
|
passwordsTableEntries.removeAll()
|
||||||
|
filteredPasswordsTableEntries.removeAll()
|
||||||
|
passwordsTableEntries = PasswordStore.shared.fetchPasswordEntityCoreData(parent: parentPasswordEntity).map {
|
||||||
|
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sections : [(index: Int, length :Int, title: String)] = Array()
|
var sections : [(index: Int, length :Int, title: String)] = Array()
|
||||||
var searchActive : Bool = false
|
var searchActive : Bool = false
|
||||||
let searchController = UISearchController(searchResultsController: nil)
|
let searchController = UISearchController(searchResultsController: nil)
|
||||||
|
|
@ -23,6 +43,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
return refreshControl
|
return refreshControl
|
||||||
}()
|
}()
|
||||||
let searchBarView = UIView(frame: CGRect(x: 0, y: 64, width: UIScreen.main.bounds.width, height: 44))
|
let searchBarView = UIView(frame: CGRect(x: 0, y: 64, width: UIScreen.main.bounds.width, height: 44))
|
||||||
|
lazy var backUIBarButtonItem: UIBarButtonItem = {
|
||||||
|
let backUIBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(self.backAction(_:)))
|
||||||
|
return backUIBarButtonItem
|
||||||
|
}()
|
||||||
|
|
||||||
@IBOutlet weak var tableView: UITableView!
|
@IBOutlet weak var tableView: UITableView!
|
||||||
|
|
||||||
|
|
@ -48,6 +72,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncPasswords() {
|
func syncPasswords() {
|
||||||
SVProgressHUD.setDefaultMaskType(.black)
|
SVProgressHUD.setDefaultMaskType(.black)
|
||||||
SVProgressHUD.setDefaultStyle(.light)
|
SVProgressHUD.setDefaultStyle(.light)
|
||||||
|
|
@ -69,8 +94,9 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
PasswordStore.shared.updatePasswordEntityCoreData()
|
PasswordStore.shared.updatePasswordEntityCoreData()
|
||||||
self.passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
self.parentPasswordEntity = nil
|
||||||
self.reloadTableView(data: self.passwordEntities!)
|
self.initPasswordsTableEntries()
|
||||||
|
self.reloadTableView(data: self.passwordsTableEntries)
|
||||||
PasswordStore.shared.setAllSynced()
|
PasswordStore.shared.setAllSynced()
|
||||||
self.setNavigationItemTitle()
|
self.setNavigationItemTitle()
|
||||||
Defaults[.lastUpdatedTime] = Date()
|
Defaults[.lastUpdatedTime] = Date()
|
||||||
|
|
@ -90,12 +116,12 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
setNavigationItemTitle()
|
setNavigationItemTitle()
|
||||||
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
initPasswordsTableEntries()
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnPasswordUpdatedNotification), name: NSNotification.Name(rawValue: "passwordUpdated"), object: nil)
|
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.actOnPasswordStoreErasedNotification), name: NSNotification.Name(rawValue: "passwordStoreErased"), object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnSearchNotification), name: NSNotification.Name(rawValue: "search"), object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(PasswordsViewController.actOnSearchNotification), name: NSNotification.Name(rawValue: "search"), object: nil)
|
||||||
|
|
||||||
generateSections(item: passwordEntities!)
|
generateSections(item: passwordsTableEntries)
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
searchController.searchResultsUpdater = self
|
searchController.searchResultsUpdater = self
|
||||||
|
|
@ -134,17 +160,15 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||||
var password: PasswordEntity
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
let index = sections[indexPath.section].index + indexPath.row
|
if !entry.isDir {
|
||||||
if searchController.isActive && searchController.searchBar.text != "" {
|
if entry.passwordEntity!.synced {
|
||||||
password = filteredPasswordEntities[index]
|
cell.textLabel?.text = entry.title
|
||||||
|
} else {
|
||||||
|
cell.textLabel?.text = "↻ \(entry.title)"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
password = passwordEntities![index]
|
cell.textLabel?.text = "\(entry.title)/"
|
||||||
}
|
|
||||||
if password.synced {
|
|
||||||
cell.textLabel?.text = password.name!
|
|
||||||
} else {
|
|
||||||
cell.textLabel?.text = "↻ \(password.name!)"
|
|
||||||
}
|
}
|
||||||
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
|
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
|
||||||
longPressGestureRecognizer.minimumPressDuration = 0.6
|
longPressGestureRecognizer.minimumPressDuration = 0.6
|
||||||
|
|
@ -152,6 +176,35 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry{
|
||||||
|
var entry: PasswordsTableEntry
|
||||||
|
let index = sections[indexPath.section].index + indexPath.row
|
||||||
|
if searchController.isActive && searchController.searchBar.text != "" {
|
||||||
|
entry = filteredPasswordsTableEntries[index]
|
||||||
|
} else {
|
||||||
|
entry = passwordsTableEntries[index]
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
|
if !entry.isDir {
|
||||||
|
performSegue(withIdentifier: "showPasswordDetail", sender: tableView.cellForRow(at: indexPath))
|
||||||
|
} else {
|
||||||
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
parentPasswordEntity = entry.passwordEntity
|
||||||
|
initPasswordsTableEntries()
|
||||||
|
reloadTableView(data: passwordsTableEntries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func backAction(_ sender: Any?) {
|
||||||
|
parentPasswordEntity = parentPasswordEntity?.parent
|
||||||
|
initPasswordsTableEntries()
|
||||||
|
reloadTableView(data: passwordsTableEntries)
|
||||||
|
}
|
||||||
|
|
||||||
func longPressAction(_ gesture: UILongPressGestureRecognizer) {
|
func longPressAction(_ gesture: UILongPressGestureRecognizer) {
|
||||||
if gesture.state == UIGestureRecognizerState.began {
|
if gesture.state == UIGestureRecognizerState.began {
|
||||||
let touchPoint = gesture.location(in: tableView)
|
let touchPoint = gesture.location(in: tableView)
|
||||||
|
|
@ -185,9 +238,9 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
let index = sections[indexPath.section].index + indexPath.row
|
let index = sections[indexPath.section].index + indexPath.row
|
||||||
let password: PasswordEntity
|
let password: PasswordEntity
|
||||||
if searchController.isActive && searchController.searchBar.text != "" {
|
if searchController.isActive && searchController.searchBar.text != "" {
|
||||||
password = filteredPasswordEntities[index]
|
password = passwordsTableEntries[index].passwordEntity!
|
||||||
} else {
|
} else {
|
||||||
password = passwordEntities![index]
|
password = filteredPasswordsTableEntries[index].passwordEntity!
|
||||||
}
|
}
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
var passphrase = ""
|
var passphrase = ""
|
||||||
|
|
@ -232,33 +285,34 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateSections(item: [PasswordEntity]) {
|
func generateSections(item: [PasswordsTableEntry]) {
|
||||||
sections.removeAll()
|
sections.removeAll()
|
||||||
if item.count == 0 {
|
guard item.count != 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var index = 0
|
var index = 0
|
||||||
for i in 0 ..< item.count {
|
for i in 0 ..< item.count {
|
||||||
let name = item[index].name!.uppercased()
|
let title = item[index].title.uppercased()
|
||||||
let commonPrefix = item[i].name!.commonPrefix(with: name, options: .caseInsensitive)
|
let commonPrefix = item[i].title.commonPrefix(with: title, options: .caseInsensitive)
|
||||||
if commonPrefix.characters.count == 0 {
|
if commonPrefix.characters.count == 0 {
|
||||||
let firstCharacter = name[name.startIndex]
|
let firstCharacter = title[title.startIndex]
|
||||||
let newSection = (index: index, length: i - index, title: "\(firstCharacter)")
|
let newSection = (index: index, length: i - index, title: "\(firstCharacter)")
|
||||||
sections.append(newSection)
|
sections.append(newSection)
|
||||||
index = i
|
index = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let name = item[index].name!.uppercased()
|
let title = item[index].title.uppercased()
|
||||||
let firstCharacter = name[name.startIndex]
|
let firstCharacter = title[title.startIndex]
|
||||||
let newSection = (index: index, length: item.count - index, title: "\(firstCharacter)")
|
let newSection = (index: index, length: item.count - index, title: "\(firstCharacter)")
|
||||||
sections.append(newSection)
|
sections.append(newSection)
|
||||||
}
|
}
|
||||||
|
|
||||||
func actOnPasswordUpdatedNotification() {
|
func actOnPasswordUpdatedNotification() {
|
||||||
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
initPasswordsTableEntries()
|
||||||
reloadTableView(data: passwordEntities!)
|
reloadTableView(data: passwordsTableEntries)
|
||||||
setNavigationItemTitle()
|
setNavigationItemTitle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setNavigationItemTitle() {
|
private func setNavigationItemTitle() {
|
||||||
let numberOfUnsynced = PasswordStore.shared.getNumberOfUnsyncedPasswords()
|
let numberOfUnsynced = PasswordStore.shared.getNumberOfUnsyncedPasswords()
|
||||||
if numberOfUnsynced == 0 {
|
if numberOfUnsynced == 0 {
|
||||||
|
|
@ -269,8 +323,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
|
|
||||||
func actOnPasswordStoreErasedNotification() {
|
func actOnPasswordStoreErasedNotification() {
|
||||||
passwordEntities = PasswordStore.shared.fetchPasswordEntityCoreData()
|
initPasswordsTableEntries()
|
||||||
reloadTableView(data: passwordEntities!)
|
reloadTableView(data: passwordsTableEntries)
|
||||||
setNavigationItemTitle()
|
setNavigationItemTitle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,28 +351,20 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
if segue.identifier == "showPasswordDetail" {
|
if segue.identifier == "showPasswordDetail" {
|
||||||
if let viewController = segue.destination as? PasswordDetailTableViewController {
|
if let viewController = segue.destination as? PasswordDetailTableViewController {
|
||||||
let selectedIndexPath = self.tableView.indexPath(for: sender as! UITableViewCell)!
|
let selectedIndexPath = self.tableView.indexPath(for: sender as! UITableViewCell)!
|
||||||
let index = sections[selectedIndexPath.section].index + selectedIndexPath.row
|
let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity!
|
||||||
let passwordEntity: PasswordEntity
|
|
||||||
if searchController.isActive && searchController.searchBar.text != "" {
|
|
||||||
passwordEntity = filteredPasswordEntities[index]
|
|
||||||
} else {
|
|
||||||
passwordEntity = passwordEntities![index]
|
|
||||||
}
|
|
||||||
viewController.passwordEntity = passwordEntity
|
viewController.passwordEntity = passwordEntity
|
||||||
let passwordCategoryEntities = PasswordStore.shared.fetchPasswordCategoryEntityCoreData(password: passwordEntity)
|
|
||||||
viewController.passwordCategoryEntities = passwordCategoryEntities
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterContentForSearchText(searchText: String, scope: String = "All") {
|
func filterContentForSearchText(searchText: String, scope: String = "All") {
|
||||||
filteredPasswordEntities = passwordEntities!.filter { password in
|
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
|
||||||
return password.name!.lowercased().contains(searchText.lowercased())
|
return entry.title.lowercased().contains(searchText.lowercased())
|
||||||
}
|
}
|
||||||
if searchController.isActive && searchController.searchBar.text != "" {
|
if searchController.isActive && searchController.searchBar.text != "" {
|
||||||
reloadTableView(data: filteredPasswordEntities)
|
reloadTableView(data: filteredPasswordsTableEntries)
|
||||||
} else {
|
} else {
|
||||||
reloadTableView(data: passwordEntities!)
|
reloadTableView(data: passwordsTableEntries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,7 +374,12 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
refreshControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
|
refreshControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadTableView (data: [PasswordEntity]) {
|
func reloadTableView(data: [PasswordsTableEntry]) {
|
||||||
|
if parentPasswordEntity != nil {
|
||||||
|
navigationItem.leftBarButtonItem = backUIBarButtonItem
|
||||||
|
} else {
|
||||||
|
navigationItem.leftBarButtonItem = nil
|
||||||
|
}
|
||||||
generateSections(item: data)
|
generateSections(item: data)
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
updateRefreshControlTitle()
|
updateRefreshControlTitle()
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import SwiftyUserDefaults
|
||||||
extension PasswordEntity {
|
extension PasswordEntity {
|
||||||
func decrypt(passphrase: String) throws -> Password? {
|
func decrypt(passphrase: String) throws -> Password? {
|
||||||
var password: Password?
|
var password: Password?
|
||||||
let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(rawPath!)")
|
let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(path!)")
|
||||||
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
||||||
let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase)
|
let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase)
|
||||||
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
||||||
|
|
|
||||||
|
|
@ -237,27 +237,56 @@ class PasswordStore {
|
||||||
try storeRepository?.pull((storeRepository?.currentBranch())!, from: remote, withOptions: options, progress: transferProgressBlock)
|
try storeRepository?.pull((storeRepository?.currentBranch())!, from: remote, withOptions: options, progress: transferProgressBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func updatePasswordEntityCoreData() {
|
func updatePasswordEntityCoreData() {
|
||||||
deleteCoreData(entityName: "PasswordEntity")
|
deleteCoreData(entityName: "PasswordEntity")
|
||||||
deleteCoreData(entityName: "PasswordCategoryEntity")
|
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
fm.enumerator(atPath: self.storeURL.path)?.forEach({ (e) in
|
do {
|
||||||
if let e = e as? String, let url = URL(string: e) {
|
var q = try fm.contentsOfDirectory(atPath: self.storeURL.path).filter{
|
||||||
if url.pathExtension == "gpg" {
|
!$0.hasPrefix(".")
|
||||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
}.map { (filename) -> PasswordEntity in
|
||||||
let endIndex = url.lastPathComponent.index(url.lastPathComponent.endIndex, offsetBy: -4)
|
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||||
passwordEntity.name = url.lastPathComponent.substring(to: endIndex)
|
if filename.hasSuffix(".gpg") {
|
||||||
passwordEntity.rawPath = "\(url.path)"
|
passwordEntity.name = filename.substring(to: filename.index(filename.endIndex, offsetBy: -4))
|
||||||
let items = url.path.characters.split(separator: "/").map(String.init)
|
} else {
|
||||||
for i in 0 ..< items.count - 1 {
|
passwordEntity.name = filename
|
||||||
let passwordCategoryEntity = PasswordCategoryEntity(context: context)
|
}
|
||||||
passwordCategoryEntity.category = items[i]
|
passwordEntity.path = filename
|
||||||
passwordCategoryEntity.level = Int16(i)
|
passwordEntity.parent = nil
|
||||||
passwordCategoryEntity.password = passwordEntity
|
return passwordEntity
|
||||||
|
}
|
||||||
|
while q.count > 0 {
|
||||||
|
let e = q.first!
|
||||||
|
q.remove(at: 0)
|
||||||
|
guard !e.name!.hasPrefix(".") else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var isDirectory: ObjCBool = false
|
||||||
|
let filePath = storeURL.appendingPathComponent(e.path!).path
|
||||||
|
if fm.fileExists(atPath: filePath, isDirectory: &isDirectory) {
|
||||||
|
if isDirectory.boolValue {
|
||||||
|
e.isDir = true
|
||||||
|
let files = try fm.contentsOfDirectory(atPath: filePath).map { (filename) -> PasswordEntity in
|
||||||
|
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||||
|
if filename.hasSuffix(".gpg") {
|
||||||
|
passwordEntity.name = filename.substring(to: filename.index(filename.endIndex, offsetBy: -4))
|
||||||
|
} else {
|
||||||
|
passwordEntity.name = filename
|
||||||
|
}
|
||||||
|
passwordEntity.path = "\(e.path!)/\(filename)"
|
||||||
|
passwordEntity.parent = e
|
||||||
|
return passwordEntity
|
||||||
|
}
|
||||||
|
q += files
|
||||||
|
} else {
|
||||||
|
e.isDir = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
try context.save()
|
try context.save()
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -281,9 +310,10 @@ class PasswordStore {
|
||||||
return commits
|
return commits
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPasswordEntityCoreData() -> [PasswordEntity] {
|
func fetchPasswordEntityCoreData(parent: PasswordEntity?) -> [PasswordEntity] {
|
||||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
do {
|
do {
|
||||||
|
passwordEntityFetch.predicate = NSPredicate(format: "parent = %@", parent ?? 0)
|
||||||
let fetchedPasswordEntities = try context.fetch(passwordEntityFetch) as! [PasswordEntity]
|
let fetchedPasswordEntities = try context.fetch(passwordEntityFetch) as! [PasswordEntity]
|
||||||
return fetchedPasswordEntities.sorted { $0.name!.caseInsensitiveCompare($1.name!) == .orderedAscending }
|
return fetchedPasswordEntities.sorted { $0.name!.caseInsensitiveCompare($1.name!) == .orderedAscending }
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -291,18 +321,21 @@ class PasswordStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPasswordCategoryEntityCoreData(password: PasswordEntity) -> [PasswordCategoryEntity] {
|
func fetchPasswordEntityCoreData(withDir: Bool) -> [PasswordEntity] {
|
||||||
let passwordCategoryEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordCategoryEntity")
|
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
passwordCategoryEntityFetchRequest.predicate = NSPredicate(format: "password = %@", password)
|
|
||||||
passwordCategoryEntityFetchRequest.sortDescriptors = [NSSortDescriptor(key: "level", ascending: true)]
|
|
||||||
do {
|
do {
|
||||||
let passwordCategoryEntities = try context.fetch(passwordCategoryEntityFetchRequest) as! [PasswordCategoryEntity]
|
if !withDir {
|
||||||
return passwordCategoryEntities
|
passwordEntityFetch.predicate = NSPredicate(format: "isDir = false")
|
||||||
|
|
||||||
|
}
|
||||||
|
let fetchedPasswordEntities = try context.fetch(passwordEntityFetch) as! [PasswordEntity]
|
||||||
|
return fetchedPasswordEntities.sorted { $0.name!.caseInsensitiveCompare($1.name!) == .orderedAscending }
|
||||||
} catch {
|
} catch {
|
||||||
fatalError("Failed to fetch password categories: \(error)")
|
fatalError("Failed to fetch passwords: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func fetchUnsyncedPasswords() -> [PasswordEntity] {
|
func fetchUnsyncedPasswords() -> [PasswordEntity] {
|
||||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
|
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
|
||||||
|
|
@ -452,7 +485,9 @@ class PasswordStore {
|
||||||
progressBlock(0.3)
|
progressBlock(0.3)
|
||||||
let saveURL = storeURL.appendingPathComponent("\(password.name).gpg")
|
let saveURL = storeURL.appendingPathComponent("\(password.name).gpg")
|
||||||
try encryptedData.write(to: saveURL)
|
try encryptedData.write(to: saveURL)
|
||||||
passwordEntity.rawPath = "\(password.name).gpg"
|
passwordEntity.name = password.name
|
||||||
|
passwordEntity.path = "\(password.name).gpg"
|
||||||
|
passwordEntity.parent = nil
|
||||||
passwordEntity.synced = false
|
passwordEntity.synced = false
|
||||||
try context.save()
|
try context.save()
|
||||||
print(saveURL.path)
|
print(saveURL.path)
|
||||||
|
|
@ -466,7 +501,7 @@ class PasswordStore {
|
||||||
func update(passwordEntity: PasswordEntity, password: Password, progressBlock: (_ progress: Float) -> Void) {
|
func update(passwordEntity: PasswordEntity, password: Password, progressBlock: (_ progress: Float) -> Void) {
|
||||||
do {
|
do {
|
||||||
let encryptedData = try passwordEntity.encrypt(password: password)
|
let encryptedData = try passwordEntity.encrypt(password: password)
|
||||||
let saveURL = storeURL.appendingPathComponent(passwordEntity.rawPath!)
|
let saveURL = storeURL.appendingPathComponent(passwordEntity.path!)
|
||||||
try encryptedData.write(to: saveURL)
|
try encryptedData.write(to: saveURL)
|
||||||
progressBlock(0.3)
|
progressBlock(0.3)
|
||||||
let _ = createAddCommitInRepository(message: "Update password by pass for iOS", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock)
|
let _ = createAddCommitInRepository(message: "Update password by pass for iOS", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock)
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="11759" systemVersion="16D32" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="12124.1" systemVersion="16E175b" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="PasswordCategoryEntity" representedClassName="PasswordCategoryEntity" syncable="YES" codeGenerationType="class">
|
|
||||||
<attribute name="category" attributeType="String" syncable="YES"/>
|
|
||||||
<attribute name="level" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES" indexed="YES" syncable="YES"/>
|
|
||||||
<relationship name="password" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PasswordEntity" inverseName="categories" inverseEntity="PasswordEntity" syncable="YES"/>
|
|
||||||
</entity>
|
|
||||||
<entity name="PasswordEntity" representedClassName="PasswordEntity" syncable="YES" codeGenerationType="class">
|
<entity name="PasswordEntity" representedClassName="PasswordEntity" syncable="YES" codeGenerationType="class">
|
||||||
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
|
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
|
||||||
|
<attribute name="isDir" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
|
||||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="raw" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
|
<attribute name="path" attributeType="String" syncable="YES"/>
|
||||||
<attribute name="rawPath" attributeType="String" syncable="YES"/>
|
|
||||||
<attribute name="synced" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
<attribute name="synced" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||||
<relationship name="categories" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PasswordCategoryEntity" inverseName="password" inverseEntity="PasswordCategoryEntity" syncable="YES"/>
|
<relationship name="children" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PasswordEntity" inverseName="parent" inverseEntity="PasswordEntity" syncable="YES"/>
|
||||||
|
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PasswordEntity" inverseName="children" inverseEntity="PasswordEntity" syncable="YES"/>
|
||||||
</entity>
|
</entity>
|
||||||
<elements>
|
<elements>
|
||||||
<element name="PasswordCategoryEntity" positionX="115" positionY="-9" width="128" height="90"/>
|
<element name="PasswordEntity" positionX="36" positionY="81" width="128" height="150"/>
|
||||||
<element name="PasswordEntity" positionX="-63" positionY="-18" width="128" height="135"/>
|
|
||||||
</elements>
|
</elements>
|
||||||
</model>
|
</model>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue