Merge branch 'release/0.2.4'

This commit is contained in:
Bob Sun 2017-04-09 15:30:23 -07:00
commit 9f2aa37b8d
No known key found for this signature in database
GPG key ID: 1F86BA2052FED3B4
38 changed files with 1036 additions and 554 deletions

View file

@ -3,5 +3,5 @@ github "radex/SwiftyUserDefaults"
github "libgit2/objective-git" github "libgit2/objective-git"
github "zahlz/SwiftPasscodeLock" "master" github "zahlz/SwiftPasscodeLock" "master"
github "bitserf/FavIcon" github "bitserf/FavIcon"
github "kishikawakatsumi/KeychainAccess" "master" github "kishikawakatsumi/KeychainAccess"
github "mattrubin/OneTimePassword" github "mattrubin/OneTimePassword"

View file

@ -8,10 +8,13 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
94BA784B85E071D25EE89B59 /* libPods-pass.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADCE7A5C3CCC67D7D21BB3C4 /* libPods-pass.a */; }; 94BA784B85E071D25EE89B59 /* libPods-pass.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADCE7A5C3CCC67D7D21BB3C4 /* libPods-pass.a */; };
A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */; };
A262A58D1E68749C006B0890 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; }; A262A58D1E68749C006B0890 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; };
A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = A27424D81E7C35960093F436 /* NotificationNames.swift */; }; A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = A27424D81E7C35960093F436 /* NotificationNames.swift */; };
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; }; A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; };
A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2802BF81E70813A00879216 /* SliderTableViewCell.xib */; }; A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A2802BF81E70813A00879216 /* SliderTableViewCell.xib */; };
A2A7813F1E97DBD9001311F5 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */; };
A2A89D691E954698003FB2D3 /* UITextFieldExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2A89D681E954698003FB2D3 /* UITextFieldExtension.swift */; };
DC037CA61E4B883900609409 /* OpenSourceComponentsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA51E4B883900609409 /* OpenSourceComponentsTableViewController.swift */; }; DC037CA61E4B883900609409 /* OpenSourceComponentsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA51E4B883900609409 /* OpenSourceComponentsTableViewController.swift */; };
DC037CA81E4B898100609409 /* BasicStaticTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA71E4B898100609409 /* BasicStaticTableViewController.swift */; }; DC037CA81E4B898100609409 /* BasicStaticTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA71E4B898100609409 /* BasicStaticTableViewController.swift */; };
DC037CAA1E4B8EAE00609409 /* SpecialThanksTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */; }; DC037CAA1E4B8EAE00609409 /* SpecialThanksTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */; };
@ -80,10 +83,13 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
274CCFCF32444A2FF46BE7F4 /* Pods-pass.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.debug.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.debug.xcconfig"; sourceTree = "<group>"; }; 274CCFCF32444A2FF46BE7F4 /* Pods-pass.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.debug.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.debug.xcconfig"; sourceTree = "<group>"; };
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPScannerController.swift; sourceTree = "<group>"; };
A262A58C1E68749C006B0890 /* Base32.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Base32.framework; path = Carthage/Build/iOS/Base32.framework; sourceTree = "<group>"; }; A262A58C1E68749C006B0890 /* Base32.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Base32.framework; path = Carthage/Build/iOS/Base32.framework; sourceTree = "<group>"; };
A27424D81E7C35960093F436 /* NotificationNames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationNames.swift; sourceTree = "<group>"; }; A27424D81E7C35960093F436 /* NotificationNames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationNames.swift; sourceTree = "<group>"; };
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTableViewCell.swift; sourceTree = "<group>"; }; A2802BF71E70813A00879216 /* SliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTableViewCell.swift; sourceTree = "<group>"; };
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SliderTableViewCell.xib; sourceTree = "<group>"; }; A2802BF81E70813A00879216 /* SliderTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SliderTableViewCell.xib; sourceTree = "<group>"; };
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = "<group>"; };
A2A89D681E954698003FB2D3 /* UITextFieldExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextFieldExtension.swift; sourceTree = "<group>"; };
ADCE7A5C3CCC67D7D21BB3C4 /* libPods-pass.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-pass.a"; sourceTree = BUILT_PRODUCTS_DIR; }; ADCE7A5C3CCC67D7D21BB3C4 /* libPods-pass.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-pass.a"; sourceTree = BUILT_PRODUCTS_DIR; };
AEAD6B31EAF5D061447A68CC /* Pods-pass.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.release.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.release.xcconfig"; sourceTree = "<group>"; }; AEAD6B31EAF5D061447A68CC /* Pods-pass.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.release.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.release.xcconfig"; sourceTree = "<group>"; };
DC037CA51E4B883900609409 /* OpenSourceComponentsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSourceComponentsTableViewController.swift; sourceTree = "<group>"; }; DC037CA51E4B883900609409 /* OpenSourceComponentsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSourceComponentsTableViewController.swift; sourceTree = "<group>"; };
@ -215,6 +221,8 @@
DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */, DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */,
DC8963BF1E38EEB900828B09 /* SSHKeySettingTableViewController.swift */, DC8963BF1E38EEB900828B09 /* SSHKeySettingTableViewController.swift */,
DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */, DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */,
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */,
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */,
); );
path = Controllers; path = Controllers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -239,6 +247,7 @@
DCA049971E33586A00522E8F /* DefaultsKeys.swift */, DCA049971E33586A00522E8F /* DefaultsKeys.swift */,
DC19400A1E4B36B60077E0A3 /* Utils.swift */, DC19400A1E4B36B60077E0A3 /* Utils.swift */,
A27424D81E7C35960093F436 /* NotificationNames.swift */, A27424D81E7C35960093F436 /* NotificationNames.swift */,
A2A89D681E954698003FB2D3 /* UITextFieldExtension.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -524,9 +533,11 @@
DC5F385B1E56AADB00C69ACA /* PGPKeyArmorSettingTableViewController.swift in Sources */, DC5F385B1E56AADB00C69ACA /* PGPKeyArmorSettingTableViewController.swift in Sources */,
A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */, A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */,
DCAAF7451E2FA66800AB94BC /* SettingsTableViewController.swift in Sources */, DCAAF7451E2FA66800AB94BC /* SettingsTableViewController.swift in Sources */,
A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */,
DCFB77A71E502DF9008DE471 /* EditPasswordTableViewController.swift in Sources */, DCFB77A71E502DF9008DE471 /* EditPasswordTableViewController.swift in Sources */,
DCA0499A1E335CC800522E8F /* GitServerSettingTableViewController.swift in Sources */, DCA0499A1E335CC800522E8F /* GitServerSettingTableViewController.swift in Sources */,
DCDDEAB31E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift in Sources */, DCDDEAB31E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift in Sources */,
A2A7813F1E97DBD9001311F5 /* QRScannerController.swift in Sources */,
DCC277D21E30D6EA00402246 /* pass.xcdatamodeld in Sources */, DCC277D21E30D6EA00402246 /* pass.xcdatamodeld in Sources */,
DC4914991E434600007FF592 /* PasswordDetailTableViewController.swift in Sources */, DC4914991E434600007FF592 /* PasswordDetailTableViewController.swift in Sources */,
DC962CDF1E4B62C10033B5D8 /* AboutTableViewController.swift in Sources */, DC962CDF1E4B62C10033B5D8 /* AboutTableViewController.swift in Sources */,
@ -544,6 +555,7 @@
DC1940001E49E1A60077E0A3 /* PasscodeLockConfiguration.swift in Sources */, DC1940001E49E1A60077E0A3 /* PasscodeLockConfiguration.swift in Sources */,
DC917BD71E2E8231000FDF54 /* AppDelegate.swift in Sources */, DC917BD71E2E8231000FDF54 /* AppDelegate.swift in Sources */,
DCFB779A1E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift in Sources */, DCFB779A1E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift in Sources */,
A2A89D691E954698003FB2D3 /* UITextFieldExtension.swift in Sources */,
DC037CBB1E4DD47B00609409 /* TextFieldTableViewCell.swift in Sources */, DC037CBB1E4DD47B00609409 /* TextFieldTableViewCell.swift in Sources */,
DC193FFE1E49E0760077E0A3 /* PasscodeLockRepository.swift in Sources */, DC193FFE1E49E0760077E0A3 /* PasscodeLockRepository.swift in Sources */,
DCA049981E33586A00522E8F /* DefaultsKeys.swift in Sources */, DCA049981E33586A00522E8F /* DefaultsKeys.swift in Sources */,

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,6 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
override func viewDidLoad() { override func viewDidLoad() {
navigationItemTitle = "About Repository"
super.viewDidLoad() super.viewDidLoad()
indicator.center = CGPoint(x: view.bounds.midX, y: view.bounds.height * 0.382) indicator.center = CGPoint(x: view.bounds.midX, y: view.bounds.height * 0.382)

View file

@ -21,7 +21,6 @@ class AboutTableViewController: BasicStaticTableViewController {
[[.title: "Open Source Components", .action: "segue", .link: "showOpenSourceComponentsSegue"], [[.title: "Open Source Components", .action: "segue", .link: "showOpenSourceComponentsSegue"],
[.title: "Special Thanks", .action: "segue", .link: "showSpecialThanksSegue"],], [.title: "Special Thanks", .action: "segue", .link: "showSpecialThanksSegue"],],
] ]
navigationItemTitle = "About"
super.viewDidLoad() super.viewDidLoad()
} }

View file

@ -19,6 +19,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password"], [[.type: PasswordEditorCellType.fillPasswordCell, .title: "password"],
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]], [.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
[[.type: PasswordEditorCellType.textViewCell, .title: "additions"]], [[.type: PasswordEditorCellType.textViewCell, .title: "additions"]],
[[.type: PasswordEditorCellType.scanQRCodeCell]]
] ]
super.viewDidLoad() super.viewDidLoad()
} }
@ -54,13 +55,15 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
} }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "saveAddPasswordSegue" {let cells = tableView.visibleCells super.prepare(for: segue, sender: sender)
if segue.identifier == "saveAddPasswordSegue" {
let cells = tableView.visibleCells
var cellContents = [String: String]() var cellContents = [String: String]()
for cell in cells { for cell in cells {
let indexPath = tableView.indexPath(for: cell)! if let indexPath = tableView.indexPath(for: cell),
let contentCell = cell as! ContentTableViewCell let contentCell = cell as? ContentTableViewCell,
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as! String let cellTitle = tableData[indexPath.section][indexPath.row][.title] as? String,
if let cellContent = contentCell.getContent() { let cellContent = contentCell.getContent() {
cellContents[cellTitle] = cellContent cellContents[cellTitle] = cellContent
} }
} }

View file

@ -27,7 +27,6 @@ class AdvancedSettingsTableViewController: UITableViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
navigationItem.title = "Advanced"
encryptInASCIIArmoredSwitch.isOn = Defaults[.encryptInArmored] encryptInASCIIArmoredSwitch.isOn = Defaults[.encryptInArmored]
encryptInASCIIArmoredTableViewCell.accessoryView = encryptInASCIIArmoredSwitch encryptInASCIIArmoredTableViewCell.accessoryView = encryptInASCIIArmoredSwitch
encryptInASCIIArmoredTableViewCell.selectionStyle = .none encryptInASCIIArmoredTableViewCell.selectionStyle = .none

View file

@ -29,7 +29,9 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
navigationItem.title = navigationItemTitle if navigationItemTitle != nil {
navigationItem.title = navigationItemTitle
}
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
@ -54,6 +56,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
switch cellDataStyle ?? .defaultStyle { switch cellDataStyle ?? .defaultStyle {
case .value1: case .value1:
cell = UITableViewCell(style: .value1, reuseIdentifier: "value1 cell") cell = UITableViewCell(style: .value1, reuseIdentifier: "value1 cell")
cell?.selectionStyle = .none
default: default:
cell = UITableViewCell(style: .default, reuseIdentifier: "default cell") cell = UITableViewCell(style: .default, reuseIdentifier: "default cell")
} }
@ -65,6 +68,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
cell?.accessoryType = accessoryType cell?.accessoryType = accessoryType
} else { } else {
cell?.accessoryType = .disclosureIndicator cell?.accessoryType = .disclosureIndicator
cell?.selectionStyle = .default
} }
cell?.textLabel?.text = cellData[CellDataKey.title] as? String cell?.textLabel?.text = cellData[CellDataKey.title] as? String

View file

@ -16,8 +16,8 @@ class CommitLogsTableViewController: UITableViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
commits = passwordStore.getRecentCommits(count: 20) commits = passwordStore.getRecentCommits(count: 20)
navigationItem.title = "Recent Commit Logs" self.tableView.estimatedRowHeight = 50
navigationController!.navigationBar.topItem!.title = "About" self.tableView.rowHeight = UITableViewAutomaticDimension
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@ -27,13 +27,16 @@ class CommitLogsTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "commitLogCell", for: indexPath) let cell = tableView.dequeueReusableCell(withIdentifier: "commitLogCell", for: indexPath)
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateStyle = DateFormatter.Style.short formatter.dateStyle = DateFormatter.Style.medium
formatter.timeStyle = .none formatter.timeStyle = .medium
let dateString = formatter.string(from: commits[indexPath.row].commitDate) let dateString = formatter.string(from: commits[indexPath.row].commitDate)
let dateLabel = cell.viewWithTag(101) as! UILabel
let messageLabel = cell.viewWithTag(102) as! UILabel let author = cell.contentView.viewWithTag(100) as? UILabel
dateLabel.text = dateString let dateLabel = cell.contentView.viewWithTag(101) as? UILabel
messageLabel.text = commits[indexPath.row].message let messageLabel = cell.contentView.viewWithTag(102) as? UILabel
author?.text = commits[indexPath.row].author?.name
dateLabel?.text = dateString
messageLabel?.text = commits[indexPath.row].message?.trimmingCharacters(in: .whitespacesAndNewlines)
return cell return cell
} }
} }

View file

@ -15,7 +15,8 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password", .content: password!.password], [[.type: PasswordEditorCellType.fillPasswordCell, .title: "password", .content: password!.password],
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]], [.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
[[.type: PasswordEditorCellType.textViewCell, .title: "additions", .content: password!.getAdditionsPlainText()]], [[.type: PasswordEditorCellType.textViewCell, .title: "additions", .content: password!.getAdditionsPlainText()]],
[[.type: PasswordEditorCellType.deletePasswordCell]], [[.type: PasswordEditorCellType.scanQRCodeCell],
[.type: PasswordEditorCellType.deletePasswordCell]]
] ]
super.viewDidLoad() super.viewDidLoad()
} }
@ -37,16 +38,16 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
} }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "saveEditPasswordSegue" { if segue.identifier == "saveEditPasswordSegue" {
let cells = tableView.visibleCells let cells = tableView.visibleCells
var cellContents = [String: String]() var cellContents = [String: String]()
for cell in cells { for cell in cells {
let indexPath = tableView.indexPath(for: cell)! if let indexPath = tableView.indexPath(for: cell),
if let contentCell = cell as? ContentTableViewCell { let contentCell = cell as? ContentTableViewCell,
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as! String let cellTitle = tableData[indexPath.section][indexPath.row][.title] as? String,
if let cellContent = contentCell.getContent() { let cellContent = contentCell.getContent() {
cellContents[cellTitle] = cellContent cellContents[cellTitle] = cellContent
}
} }
} }
var plainText = "" var plainText = ""

View file

@ -47,7 +47,6 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
}() }()
override func viewDidLoad() { override func viewDidLoad() {
navigationItemTitle = "General"
tableData = [ tableData = [
// section 0 // section 0
[[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],], [[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],],

View file

@ -9,12 +9,13 @@
import UIKit import UIKit
import SwiftyUserDefaults import SwiftyUserDefaults
class GitSSHKeyArmorSettingTableViewController: UITableViewController { class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate {
@IBOutlet weak var armorPublicKeyTextView: UITextView! @IBOutlet weak var armorPublicKeyTextView: UITextView!
@IBOutlet weak var armorPrivateKeyTextView: UITextView! @IBOutlet weak var armorPrivateKeyTextView: UITextView!
var gitSSHPrivateKeyPassphrase: String? var gitSSHPrivateKeyPassphrase: String?
let passwordStore = PasswordStore.shared let passwordStore = PasswordStore.shared
var doneBarButtonItem: UIBarButtonItem?
private var recentPastedText = ""
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -22,12 +23,8 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
armorPrivateKeyTextView.text = Defaults[.gitSSHPrivateKeyArmor] armorPrivateKeyTextView.text = Defaults[.gitSSHPrivateKeyArmor]
gitSSHPrivateKeyPassphrase = passwordStore.gitSSHPrivateKeyPassphrase gitSSHPrivateKeyPassphrase = passwordStore.gitSSHPrivateKeyPassphrase
doneBarButtonItem = UIBarButtonItem(title: "Done", armorPublicKeyTextView.delegate = self
style: UIBarButtonItemStyle.done, armorPrivateKeyTextView.delegate = self
target: self,
action: #selector(doneButtonTapped(_:)))
navigationItem.rightBarButtonItem = doneBarButtonItem
navigationItem.title = "SSH Key"
} }
private func createSavePassphraseAlert() -> UIAlertController { private func createSavePassphraseAlert() -> UIAlertController {
@ -46,7 +43,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
return savePassphraseAlert return savePassphraseAlert
} }
func doneButtonTapped(_ sender: Any) { @IBAction func doneButtonTapped(_ sender: Any) {
Defaults[.gitSSHPublicKeyArmor] = armorPublicKeyTextView.text Defaults[.gitSSHPublicKeyArmor] = armorPublicKeyTextView.text
Defaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text Defaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
do { do {
@ -68,5 +65,17 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
} }
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == UIPasteboard.general.string {
// user pastes somethint, get ready to clear in 10s
recentPastedText = text
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + 10) { [weak weakSelf = self] in
if let pasteboardString = UIPasteboard.general.string,
pasteboardString == weakSelf?.recentPastedText {
UIPasteboard.general.string = ""
}
}
}
return true
}
} }

View file

@ -0,0 +1,83 @@
//
// QRScannerController.swift
// pass
//
// Created by Yishi Lin on 10/4/17.
// Copyright © 2017 Yishi Lin. All rights reserved.
//
import UIKit
import AVFoundation
class OTPScannerController: QRScannerController {
var tempPassword: Password?
var scannedOTP: String?
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
override func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: 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
// check whether it is a valid result
if let scannedString = metadataObj.stringValue {
if let (accept, message) = delegate?.checkScannedOutput(line: scannedString) {
if accept == true {
captureSession?.stopRunning()
scannedOTP = scannedString
tempPassword = Password(name: "empty", plainText: scannedString)
// set scannerOutput
setupOneTimePasswordMessage()
} else {
scannerOutput.text = message
}
} else {
// no delegate, show the scanned result
scannerOutput.text = scannedString
}
} else {
scannerOutput.text = "No string value"
}
} else {
qrCodeFrameView?.frame = CGRect.zero
scannerOutput.text = "No QR code detected"
}
}
private func setupOneTimePasswordMessage() {
if let password = tempPassword {
if password.otpType == .hotp {
// hotp, no need to refresh
let (title, content) = password.getOtpStrings()!
scannerOutput.text = "\(title):\(content)"
} else if password.otpType == .totp {
// totp, refresh
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak weakSelf = self] timer in
let (title, content) = password.getOtpStrings()!
weakSelf?.scannerOutput.text = "\(title):\(content)"
}
}
}
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveAddScannedOTPSegue" {
return tempPassword != nil
}
return true
}
// override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// super.prepare(for: segue, sender: sender)
// if segue.identifier == "saveAddScannedOTPSegue" {
// delegate?.handleScannedOutput(line: scannedOTP)
// }
// }
}

View file

@ -44,7 +44,6 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
[CellDataKey.title: item[0], CellDataKey.action: "link", CellDataKey.link: item[1], CellDataKey.accessoryType: UITableViewCellAccessoryType.detailDisclosureButton, CellDataKey.detailDisclosureAction: #selector(actOnDetailDisclosureButton(_:)), CellDataKey.detailDisclosureData: item[2]] [CellDataKey.title: item[0], CellDataKey.action: "link", CellDataKey.link: item[1], CellDataKey.accessoryType: UITableViewCellAccessoryType.detailDisclosureButton, CellDataKey.detailDisclosureAction: #selector(actOnDetailDisclosureButton(_:)), CellDataKey.detailDisclosureData: item[2]]
) )
} }
navigationItemTitle = "Open Source Components"
super.viewDidLoad() super.viewDidLoad()
} }

View file

@ -9,12 +9,14 @@
import UIKit import UIKit
import SwiftyUserDefaults import SwiftyUserDefaults
class PGPKeyArmorSettingTableViewController: UITableViewController { class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate {
@IBOutlet weak var armorPublicKeyTextView: UITextView! @IBOutlet weak var armorPublicKeyTextView: UITextView!
@IBOutlet weak var armorPrivateKeyTextView: UITextView! @IBOutlet weak var armorPrivateKeyTextView: UITextView!
var pgpPassphrase: String? var pgpPassphrase: String?
let passwordStore = PasswordStore.shared let passwordStore = PasswordStore.shared
private var recentPastedText = ""
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
armorPublicKeyTextView.text = Defaults[.pgpPublicKeyArmor] armorPublicKeyTextView.text = Defaults[.pgpPublicKeyArmor]
@ -22,7 +24,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
pgpPassphrase = passwordStore.pgpKeyPassphrase pgpPassphrase = passwordStore.pgpKeyPassphrase
} }
private func createSavePassphraseAlert() -> UIAlertController { private func createSavePassphraseAndSegueAlert() -> UIAlertController {
let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later decryption?", preferredStyle: UIAlertControllerStyle.alert) let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later decryption?", preferredStyle: UIAlertControllerStyle.alert)
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
Defaults[.isRememberPassphraseOn] = false Defaults[.isRememberPassphraseOn] = false
@ -35,12 +37,26 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
return savePassphraseAlert return savePassphraseAlert
} }
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "savePGPKeySegue" {
if armorPublicKeyTextView.text.isEmpty {
Utils.alert(title: "Cannot Save", message: "Please set public key first.", controller: self, completion: nil)
return false
}
if armorPrivateKeyTextView.text.isEmpty {
Utils.alert(title: "Cannot Save", message: "Please set private key first.", controller: self, completion: nil)
return false
}
}
return true
}
@IBAction func save(_ sender: Any) { @IBAction func save(_ sender: Any) {
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert) let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text self.pgpPassphrase = alert.textFields?.first?.text
let savePassphraseAlert = self.createSavePassphraseAlert() let savePassphraseAndSegueAlert = self.createSavePassphraseAndSegueAlert()
self.present(savePassphraseAlert, animated: true, completion: nil) self.present(savePassphraseAndSegueAlert, animated: true, completion: nil)
})) }))
alert.addTextField(configurationHandler: {(textField: UITextField!) in alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = self.pgpPassphrase textField.text = self.pgpPassphrase
@ -48,5 +64,18 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
}) })
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
} }
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == UIPasteboard.general.string {
// user pastes something, get ready to clear in 10s
recentPastedText = text
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + 10) { [weak weakSelf = self] in
if let pasteboardString = UIPasteboard.general.string,
pasteboardString == weakSelf?.recentPastedText {
UIPasteboard.general.string = ""
}
}
}
return true
}
} }

View file

@ -24,6 +24,19 @@ class PGPKeySettingTableViewController: UITableViewController {
pgpPassphrase = passwordStore.pgpKeyPassphrase pgpPassphrase = passwordStore.pgpKeyPassphrase
} }
private func createSavePassphraseAndSegueAlert() -> UIAlertController {
let savePassphraseAlert = UIAlertController(title: "Passphrase", message: "Do you want to save the passphrase for later decryption?", preferredStyle: UIAlertControllerStyle.alert)
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
Defaults[.isRememberPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
savePassphraseAlert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.destructive) {_ in
Defaults[.isRememberPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
return savePassphraseAlert
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "savePGPKeySegue" { if identifier == "savePGPKeySegue" {
guard let pgpPublicKeyURL = URL(string: pgpPublicKeyURLTextField.text!) else { guard let pgpPublicKeyURL = URL(string: pgpPublicKeyURLTextField.text!) else {
@ -46,9 +59,8 @@ class PGPKeySettingTableViewController: UITableViewController {
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert) let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text self.pgpPassphrase = alert.textFields?.first?.text
if self.shouldPerformSegue(withIdentifier: "savePGPKeySegue", sender: self) { let savePassphraseAndSegueAlert = self.createSavePassphraseAndSegueAlert()
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self) self.present(savePassphraseAndSegueAlert, animated: true, completion: nil)
}
})) }))
alert.addTextField(configurationHandler: {(textField: UITextField!) in alert.addTextField(configurationHandler: {(textField: UITextField!) in
textField.text = self.pgpPassphrase textField.text = self.pgpPassphrase

View file

@ -386,11 +386,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
return; return;
} }
// increase HOTP counter // copy HOTP to pasteboard (will update counter)
password!.increaseHotpCounter() if let plainPassword = password!.getNextHotp() {
// copy HOTP to pasteboard
if let plainPassword = password!.getOtp() {
Utils.copyToPasteboard(textToCopy: plainPassword) Utils.copyToPasteboard(textToCopy: plainPassword)
} }
@ -448,9 +445,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
let titleData = tableDataItem.title let titleData = tableDataItem.title
let contentData = tableDataItem.content let contentData = tableDataItem.content
cell.delegatePasswordTableView = self cell.delegatePasswordTableView = self
cell.isPasswordCell = (titleData.lowercased() == "password" ? true : false)
cell.isURLCell = (titleData.lowercased() == "url" ? true : false)
cell.isHOTPCell = (titleData == "HMAC-based" ? true : false)
cell.cellData = LabelTableViewCellData(title: titleData, content: contentData) cell.cellData = LabelTableViewCellData(title: titleData, content: contentData)
cell.selectionStyle = .none cell.selectionStyle = .none
return cell return cell

View file

@ -8,16 +8,17 @@
import UIKit import UIKit
import SwiftyUserDefaults import SwiftyUserDefaults
import OneTimePassword
enum PasswordEditorCellType { enum PasswordEditorCellType {
case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell, deletePasswordCell case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell, deletePasswordCell, scanQRCodeCell
} }
enum PasswordEditorCellKey { enum PasswordEditorCellKey {
case type, title, content, placeholders case type, title, content, placeholders
} }
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, UIGestureRecognizerDelegate { class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, QRScannerControllerDelegate {
var tableData = [ var tableData = [
[Dictionary<PasswordEditorCellKey, Any>] [Dictionary<PasswordEditorCellKey, Any>]
@ -29,11 +30,13 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()} private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""] private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
private let passwordSection = 1 private let passwordSection = 1
private let additionsSection = 2
private var hidePasswordSettings = true private var hidePasswordSettings = true
private var fillPasswordCell: FillPasswordTableViewCell? private var fillPasswordCell: FillPasswordTableViewCell?
private var passwordLengthCell: SliderTableViewCell? private var passwordLengthCell: SliderTableViewCell?
private var deletePasswordCell: UITableViewCell? private var deletePasswordCell: UITableViewCell?
private var scanQRCodeCell: UITableViewCell?
override func loadView() { override func loadView() {
super.loadView() super.loadView()
@ -42,11 +45,22 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
deletePasswordCell!.textLabel?.text = "Delete Password" deletePasswordCell!.textLabel?.text = "Delete Password"
deletePasswordCell!.textLabel?.textColor = Globals.red deletePasswordCell!.textLabel?.textColor = Globals.red
deletePasswordCell?.selectionStyle = .default deletePasswordCell?.selectionStyle = .default
scanQRCodeCell = UITableViewCell(style: .default, reuseIdentifier: "default")
scanQRCodeCell?.textLabel?.text = "Add One-Time Password"
scanQRCodeCell?.textLabel?.textColor = Globals.blue
scanQRCodeCell?.selectionStyle = .default
// scanQRCodeCell?.imageView?.image = #imageLiteral(resourceName: "Camera").withRenderingMode(.alwaysTemplate)
// scanQRCodeCell?.imageView?.tintColor = Globals.blue
// scanQRCodeCell?.imageView?.contentMode = .scaleAspectFit
} }
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
navigationItem.title = navigationItemTitle if navigationItemTitle != nil {
navigationItem.title = navigationItemTitle
}
tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell") tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell")
tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell") tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell")
tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell") tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell")
@ -83,6 +97,8 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
return passwordLengthCell! return passwordLengthCell!
case .deletePasswordCell: case .deletePasswordCell:
return deletePasswordCell! return deletePasswordCell!
case .scanQRCodeCell:
return scanQRCodeCell!
default: default:
let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! ContentTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! ContentTableViewCell
cell.setContent(content: cellData[PasswordEditorCellKey.content] as? String) cell.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
@ -116,13 +132,16 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath) == deletePasswordCell { let selectedCell = tableView.cellForRow(at: indexPath)
if selectedCell == deletePasswordCell {
let alert = UIAlertController(title: "Delete Password?", message: nil, preferredStyle: UIAlertControllerStyle.alert) let alert = UIAlertController(title: "Delete Password?", message: nil, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Delete", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in alert.addAction(UIAlertAction(title: "Delete", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in
self.performSegue(withIdentifier: "deletePasswordSegue", sender: self) self.performSegue(withIdentifier: "deletePasswordSegue", sender: self)
})) }))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler:nil)) alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler:nil))
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
} else if selectedCell == scanQRCodeCell {
self.performSegue(withIdentifier: "showQRScannerSegue", sender: self)
} }
tableView.deselectRow(at: indexPath, animated: true) tableView.deselectRow(at: indexPath, animated: true)
} }
@ -165,4 +184,53 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
hidePasswordSettings = !hidePasswordSettings hidePasswordSettings = !hidePasswordSettings
tableView.reloadSections([passwordSection], with: .fade) tableView.reloadSections([passwordSection], with: .fade)
} }
func insertScannedOTPFields(_ otpauth: String) {
// update tableData
if let additionsPlainText = (tableData[additionsSection][0][PasswordEditorCellKey.content] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines), additionsPlainText != "" {
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsPlainText + "\n" + otpauth
} else {
tableData[additionsSection][0][PasswordEditorCellKey.content] = otpauth
}
// reload
tableView.reloadSections([additionsSection], with: .none)
}
// MARK: - QRScannerControllerDelegate Methods
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
if let url = URL(string: line), let _ = Token(url: url) {
return (accept: true, message: "Valid token URL")
} else {
return (accept: false, message: "Invalid token URL")
}
}
// MARK: - QRScannerControllerDelegate Methods
func handleScannedOutput(line: String) {
insertScannedOTPFields(line)
}
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 {
viewController.delegate = self
}
} else if let viewController = segue.destination as? QRScannerController {
viewController.delegate = self
}
}
}
@IBAction private func cancelOTPScanner(segue: UIStoryboardSegue) {
}
@IBAction func saveScannedOTP(segue: UIStoryboardSegue) {
if let controller = segue.source as? OTPScannerController {
if let scannedOTP = controller.scannedOTP {
insertScannedOTPFields(scannedOTP)
}
}
}
} }

View file

@ -11,25 +11,28 @@ import SVProgressHUD
import SwiftyUserDefaults import SwiftyUserDefaults
import PasscodeLock import PasscodeLock
enum PasswordsTableEntryType { fileprivate class PasswordsTableEntry : NSObject {
case password, dir
}
struct PasswordsTableEntry {
var title: String var title: String
var isDir: Bool var isDir: Bool
var passwordEntity: PasswordEntity? var passwordEntity: PasswordEntity?
init(title: String, isDir: Bool, passwordEntity: PasswordEntity?) {
self.title = title
self.isDir = isDir
self.passwordEntity = passwordEntity
}
} }
class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate { class PasswordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITabBarControllerDelegate, UISearchBarDelegate {
private var passwordsTableEntries: [PasswordsTableEntry] = [] private var passwordsTableEntries: [PasswordsTableEntry] = []
private var passwordsTableAllEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = [] private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private var parentPasswordEntity: PasswordEntity? = nil private var parentPasswordEntity: PasswordEntity? = nil
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
private var tapTabBarTime: TimeInterval = 0 private var tapTabBarTime: TimeInterval = 0
private var sections : [(index: Int, length :Int, title: String)] = Array() private var sections = [(title: String, entries: [PasswordsTableEntry])]()
private var searchActive : Bool = false private var searchActive : Bool = false
private lazy var searchController: UISearchController = { private lazy var searchController: UISearchController = {
@ -80,8 +83,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
private func initPasswordsTableEntries(parent: PasswordEntity?) { private func initPasswordsTableEntries(parent: PasswordEntity?) {
passwordsTableEntries.removeAll() passwordsTableEntries.removeAll()
passwordsTableAllEntries.removeAll()
filteredPasswordsTableEntries.removeAll() filteredPasswordsTableEntries.removeAll()
var passwordEntities = [PasswordEntity]() var passwordEntities = [PasswordEntity]()
var passwordAllEntities = [PasswordEntity]()
if Defaults[.isShowFolderOn] { if Defaults[.isShowFolderOn] {
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(parent: parent) passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(parent: parent)
} else { } else {
@ -90,6 +95,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
passwordsTableEntries = passwordEntities.map { passwordsTableEntries = passwordEntities.map {
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0) PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
} }
passwordAllEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableAllEntries = passwordAllEntities.map {
PasswordsTableEntry(title: $0.name!, isDir: $0.isDir, passwordEntity: $0)
}
parentPasswordEntity = parent parentPasswordEntity = parent
} }
@ -149,17 +158,31 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
} catch { } catch {
DispatchQueue.main.async { DispatchQueue.main.async {
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) SVProgressHUD.dismiss()
self.syncControl.endRefreshing()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
}
} }
} }
} }
} }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if Defaults[.isShowFolderOn] {
searchController.searchBar.scopeButtonTitles = ["Current", "All"]
} else {
searchController.searchBar.scopeButtonTitles = nil
}
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
tabBarController!.delegate = self tabBarController!.delegate = self
searchController.searchBar.delegate = self
tableView.delegate = self tableView.delegate = self
tableView.dataSource = self tableView.dataSource = self
definesPresentationContext = true definesPresentationContext = true
@ -196,13 +219,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].length return sections[section].entries.count
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:))) let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
longPressGestureRecognizer.minimumPressDuration = 0.6 longPressGestureRecognizer.minimumPressDuration = 0.6
if Defaults[.isShowFolderOn] { if Defaults[.isShowFolderOn] && searchController.searchBar.selectedScopeButtonIndex == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath) let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
let entry = getPasswordEntry(by: indexPath) let entry = getPasswordEntry(by: indexPath)
@ -241,14 +264,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry { private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
var entry: PasswordsTableEntry return sections[indexPath.section].entries[indexPath.row]
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) { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -268,7 +284,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
func backAction(_ sender: Any?) { func backAction(_ sender: Any?) {
guard Defaults[.isShowFolderOn] else { return } guard Defaults[.isShowFolderOn] else { return }
reloadTableView(parent: parentPasswordEntity?.parent, anim: transitionFromLeft) var anim: CATransition? = transitionFromLeft
if parentPasswordEntity == nil {
anim = nil
}
reloadTableView(parent: parentPasswordEntity?.parent, anim: anim)
} }
func longPressAction(_ gesture: UILongPressGestureRecognizer) { func longPressAction(_ gesture: UILongPressGestureRecognizer) {
@ -345,25 +365,30 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
private func generateSections(item: [PasswordsTableEntry]) { private func generateSections(item: [PasswordsTableEntry]) {
sections.removeAll() let collation = UILocalizedIndexedCollation.current()
guard item.count != 0 else { let sectionTitles = collation.sectionIndexTitles
return var newSections = [(title: String, entries: [PasswordsTableEntry])]()
// initialize all sections
for i in 0..<sectionTitles.count {
newSections.append((title: sectionTitles[i], entries: [PasswordsTableEntry]()))
} }
var index = 0
for i in 0 ..< item.count { // put entries into sections
let title = item[index].title.uppercased() for entry in item {
let commonPrefix = item[i].title.commonPrefix(with: title, options: .caseInsensitive) let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
if commonPrefix.characters.count == 0 { newSections[sectionNumber].entries.append(entry)
let firstCharacter = title[title.startIndex]
let newSection = (index: index, length: i - index, title: "\(firstCharacter)")
sections.append(newSection)
index = i
}
} }
let title = item[index].title.uppercased()
let firstCharacter = title[title.startIndex] // sort each list and set sectionTitles
let newSection = (index: index, length: item.count - index, title: "\(firstCharacter)") for i in 0..<sectionTitles.count {
sections.append(newSection) let entriesToSort = newSections[i].entries
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
newSections[i].entries = sortedEntries as! [PasswordsTableEntry]
}
// only keep non-empty sections
sections = newSections.filter {$0.entries.count > 0}
} }
func actOnSearchNotification() { func actOnSearchNotification() {
@ -381,6 +406,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
return false return false
} }
} else if identifier == "addPasswordSegue" {
guard self.passwordStore.publicKey != nil, self.passwordStore.storeRepository != nil else {
Utils.alert(title: "Cannot Add Password", message: "Please make sure PGP Key and Git Server are properly set.", controller: self, completion: nil)
return false
}
} }
return true return true
} }
@ -396,14 +426,30 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} }
func filterContentForSearchText(searchText: String, scope: String = "All") { func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in switch scope {
return entry.title.lowercased().contains(searchText.lowercased()) case "All":
} filteredPasswordsTableEntries = passwordsTableAllEntries.filter { entry in
if searchController.isActive && searchController.searchBar.text != "" { return entry.title.lowercased().contains(searchText.lowercased())
reloadTableView(data: filteredPasswordsTableEntries) }
} else { if searchController.isActive && searchController.searchBar.text != "" {
reloadTableView(data: passwordsTableEntries) reloadTableView(data: filteredPasswordsTableEntries)
} else {
reloadTableView(data: passwordsTableAllEntries)
}
case "Current":
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
return entry.title.lowercased().contains(searchText.lowercased())
}
if searchController.isActive && searchController.searchBar.text != "" {
reloadTableView(data: filteredPasswordsTableEntries)
} else {
reloadTableView(data: passwordsTableEntries)
}
default:
break
} }
} }
private func reloadTableView(data: [PasswordsTableEntry], anim: CAAnimation? = nil) { private func reloadTableView(data: [PasswordsTableEntry], anim: CAAnimation? = nil) {
@ -466,10 +512,22 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
backAction(self) backAction(self)
} }
} }
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
updateSearchResults(for: searchController)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchController.searchBar.selectedScopeButtonIndex = 0
updateSearchResults(for: searchController)
}
} }
extension PasswordsViewController: UISearchResultsUpdating { extension PasswordsViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) { func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchText: searchController.searchBar.text!) var scope = "All"
if let scopeButtonTitles = searchController.searchBar.scopeButtonTitles {
scope = scopeButtonTitles[searchController.searchBar.selectedScopeButtonIndex]
}
filterContentForSearchText(searchText: searchController.searchBar.text!, scope: scope)
} }
} }

View file

@ -0,0 +1,128 @@
//
// QRScannerController.swift
// pass
//
// Created by Yishi Lin on 7/4/17.
// Copyright © 2017 Yishi Lin. All rights reserved.
//
import UIKit
import AVFoundation
import OneTimePassword
import SVProgressHUD
protocol QRScannerControllerDelegate {
func checkScannedOutput(line: String) -> (accept: Bool, message: String)
func handleScannedOutput(line: String)
}
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
@IBOutlet weak var scannerOutput: UILabel!
var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var qrCodeFrameView: UIView?
let supportedCodeTypes = [AVMetadataObjectTypeQRCode]
var delegate: QRScannerControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video as the media type parameter.
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
let input = try AVCaptureDeviceInput(device: captureDevice)
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input)
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
// Start video capture.
captureSession?.startRunning()
// Move the message label to the front
scannerOutput.layer.cornerRadius = 10
scannerOutput.text = "No QR code detected"
view.bringSubview(toFront: scannerOutput)
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView {
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
view.bringSubview(toFront: qrCodeFrameView)
}
} catch {
print(error)
return
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: 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
// check whether it is a valid result
if let scanned = metadataObj.stringValue {
if let (accept, message) = delegate?.checkScannedOutput(line: scanned) {
scannerOutput.text = message
if accept == true {
captureSession?.stopRunning()
delegate?.handleScannedOutput(line: scanned)
DispatchQueue.main.async {
SVProgressHUD.showSuccess(withStatus: "Done")
SVProgressHUD.dismiss(withDelay: 1)
self.navigationController?.popViewController(animated: true)
}
}
} else {
// no delegate, show the scanned result
scannerOutput.text = scanned
}
} else {
scannerOutput.text = "No string value"
}
} else {
qrCodeFrameView?.frame = CGRect.zero
scannerOutput.text = "No QR code detected"
}
}
}

View file

@ -15,7 +15,9 @@ class RawPasswordViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
navigationItem.title = "Raw" navigationItem.title = password?.name
rawPasswordTextView.textContainer.lineFragmentPadding = 0
rawPasswordTextView.textContainerInset = .zero
rawPasswordTextView.text = password?.plainText rawPasswordTextView.text = password?.plainText
} }
} }

View file

@ -20,17 +20,10 @@ class SSHKeySettingTableViewController: UITableViewController {
super.viewDidLoad() super.viewDidLoad()
privateKeyURLTextField.text = Defaults[.gitSSHPrivateKeyURL]?.absoluteString privateKeyURLTextField.text = Defaults[.gitSSHPrivateKeyURL]?.absoluteString
publicKeyURLTextField.text = Defaults[.gitSSHPublicKeyURL]?.absoluteString publicKeyURLTextField.text = Defaults[.gitSSHPublicKeyURL]?.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) {
@IBAction func doneButtonTapped(_ sender: UIButton) {
guard let publicKeyURL = URL(string: publicKeyURLTextField.text!) else { guard let publicKeyURL = URL(string: publicKeyURLTextField.text!) else {
Utils.alert(title: "Cannot Save", message: "Please set Public Key URL first.", controller: self, completion: nil) Utils.alert(title: "Cannot Save", message: "Please set Public Key URL first.", controller: self, completion: nil)
return return

View file

@ -27,7 +27,6 @@ class SpecialThanksTableViewController: BasicStaticTableViewController {
[CellDataKey.action: "link", CellDataKey.title: item[0], CellDataKey.link: item[1]] [CellDataKey.action: "link", CellDataKey.title: item[0], CellDataKey.link: item[1]]
) )
} }
navigationItemTitle = "Special Thanks"
super.viewDidLoad() super.viewDidLoad()
} }
} }

View file

@ -27,6 +27,7 @@ class Globals {
"Apple": (min: 15, max: 15, def: 15)] "Apple": (min: 15, max: 15, def: 15)]
static let passwordDots = "••••••••••••" static let passwordDots = "••••••••••••"
static let oneTimePasswordDots = "••••••"
static let passwordFonts = "Menlo" static let passwordFonts = "Menlo"
// UI related // UI related

View file

@ -0,0 +1,34 @@
//
// UIViewControllerExtionsion.swift
// pass
//
// Created by Yishi Lin on 5/4/17.
// Copyright © 2017 Yishi Lin. All rights reserved.
//
import Foundation
import UIKit
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
@IBOutlet var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
extension UIViewController {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField.nextField != nil {
textField.nextField?.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
}

View file

@ -50,7 +50,7 @@ class Utils {
static func randomString(length: Int) -> String { static func randomString(length: Int) -> String {
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_+-="
let len = UInt32(letters.length) let len = UInt32(letters.length)
var randomString = "" var randomString = ""

View file

@ -17,13 +17,15 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.2.3</string> <string>0.2.4</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSCameraUsageDescription</key>
<string>We need to access your camera for scanning QR code.</string>
<key>UIApplicationShortcutItems</key> <key>UIApplicationShortcutItems</key>
<array> <array>
<dict> <dict>

View file

@ -61,21 +61,20 @@ class Password {
private func initEverything(name: String, plainText: String) { private func initEverything(name: String, plainText: String) {
self.name = name self.name = name
self.plainText = plainText self.plainText = plainText
self.additions.removeAll()
self.additionKeys.removeAll()
// get password and additional fields // get password and additional fields
let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) { let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) {
$0 == "\n" || $0 == "\r\n" $0 == "\n" || $0 == "\r\n"
}.map(String.init) }.map(String.init)
guard plainTextSplit.count > 0 else { self.password = plainTextSplit.first ?? ""
return;
}
self.password = plainTextSplit[0]
if plainTextSplit.count == 2 { if plainTextSplit.count == 2 {
(self.additions, self.additionKeys) = Password.getAdditionFields(from: plainTextSplit[1]) (self.additions, self.additionKeys) = Password.getAdditionFields(from: plainTextSplit[1])
} }
// check whether the first line of the plainText looks like an otp entry // check whether the first line of the plainText looks like an otp entry
let (key, value) = Password.getKeyValuePair(from: plainTextSplit[0]) let (key, value) = Password.getKeyValuePair(from: self.password)
if Password.otpKeywords.contains(key ?? "") { if Password.otpKeywords.contains(key ?? "") {
firstLineIsOTPField = true firstLineIsOTPField = true
self.additions[key!] = value self.additions[key!] = value
@ -99,12 +98,16 @@ class Password {
// return a key-value pair from the line // return a key-value pair from the line
// key might be nil, if there is no ":" in the line // key might be nil, if there is no ":" in the line
static private func getKeyValuePair(from line: String) -> (key: String?, value: String) { static private func getKeyValuePair(from line: String) -> (key: String?, value: String) {
let items = line.characters.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false).map{String($0).trimmingCharacters(in: .whitespaces)} let items = line.components(separatedBy: ": ").map{String($0).trimmingCharacters(in: .whitespaces)}
var key : String? = nil var key : String? = nil
var value = "" var value = ""
if items.count == 1 || (items[0].isEmpty && items[1].isEmpty) { if items.count == 1 || (items[0].isEmpty && items[1].isEmpty) {
// no ":" found, or empty on both sides of ":" (e.g., " : ") // no ": " found, or empty on both sides of ": "
value = line value = line
// otpauth special case
if value.hasPrefix("otpauth://") {
key = "otpauth"
}
} else { } else {
if !items[0].isEmpty { if !items[0].isEmpty {
key = items[0] key = items[0]
@ -265,21 +268,6 @@ class Password {
} }
} }
// it is guaranteed that it is a HOTP password when we call this
func increaseHotpCounter() {
var lines : [String] = []
self.plainText.enumerateLines() { line, _ in
let (key, value) = Password.getKeyValuePair(from: line)
if key == "otp_counter", let newValue = UInt64(value)?.advanced(by: 1) {
let newLine = "\(key!): \(newValue)"
lines.append(newLine)
} else {
lines.append(line)
}
}
self.updatePassword(name: self.name, plainText: lines.joined(separator: "\n"))
}
// return the description and the password strings // return the description and the password strings
func getOtpStrings() -> (description: String, otp: String)? { func getOtpStrings() -> (description: String, otp: String)? {
guard let token = self.otpToken else { guard let token = self.otpToken else {
@ -309,6 +297,37 @@ class Password {
} }
} }
// return the password strings
// it is guaranteed that it is a HOTP password when we call this
func getNextHotp() -> String? {
// increase the counter
otpToken = otpToken?.updatedToken()
// replace old HOTP settings with the new otpauth
var newOtpauth = try! otpToken?.toURL().absoluteString
newOtpauth?.append("&secret=")
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
var lines : [String] = []
self.plainText.enumerateLines() { line, _ in
let (key, _) = Password.getKeyValuePair(from: line)
if !Password.otpKeywords.contains(key ?? "") {
lines.append(line)
} else if key == "otpauth" && newOtpauth != nil {
lines.append(newOtpauth!)
// set to nil to prevent duplication
newOtpauth = nil
}
}
if newOtpauth != nil {
lines.append(newOtpauth!)
}
self.updatePassword(name: self.name, plainText: lines.joined(separator: "\n"))
// get and return the password
return self.otpToken?.currentPassword
}
static func LooksLikeOTP(line: String) -> Bool { static func LooksLikeOTP(line: String) -> Bool {
let (key, _) = getKeyValuePair(from: line) let (key, _) = getKeyValuePair(from: line)
return Password.otpKeywords.contains(key ?? "") return Password.otpKeywords.contains(key ?? "")

View file

@ -220,27 +220,21 @@ class PasswordStore {
} }
public func initPGPKey(_ keyType: PGPKeyType) throws { public func initPGPKey(_ keyType: PGPKeyType) throws {
var keyPath = ""
switch keyType { switch keyType {
case .public: case .public:
keyPath = Globals.pgpPublicKeyPath let keyPath = Globals.pgpPublicKeyPath
case .secret: self.publicKey = importKey(from: keyPath)
keyPath = Globals.pgpPrivateKeyPath if self.publicKey == nil {
default: throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import the public PGP key."])
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key."])
}
if let key = importKey(from: keyPath) {
switch keyType {
case .public:
self.publicKey = key
case .secret:
self.privateKey = key
default:
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key."])
} }
} else { case .secret:
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key."]) let keyPath = Globals.pgpPrivateKeyPath
self.privateKey = importKey(from: keyPath)
if self.privateKey == nil {
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import the private PGP key."])
}
default:
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key: unknown PGP key type."])
} }
} }
@ -272,9 +266,7 @@ class PasswordStore {
let fm = FileManager.default let fm = FileManager.default
if fm.fileExists(atPath: keyPath) { if fm.fileExists(atPath: keyPath) {
if let keys = pgp.importKeys(fromFile: keyPath, allowDuplicates: false) as? [PGPKey] { if let keys = pgp.importKeys(fromFile: keyPath, allowDuplicates: false) as? [PGPKey] {
if keys.count > 0 { return keys.first
return keys[0]
}
} }
} }
return nil return nil

View file

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="90" id="KGk-i7-Jjw" customClass="FillPasswordTableViewCell" customModule="pass" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="90" id="KGk-i7-Jjw" customClass="FillPasswordTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="90"/> <rect key="frame" x="0.0" y="0.0" width="320" height="90"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="89.5"/> <rect key="frame" x="0.0" y="0.0" width="320" height="89.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="password" textAlignment="natural" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="k0U-2N-YaX" userLabel="Password"> <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="password" textAlignment="natural" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="k0U-2N-YaX" userLabel="Password">
<rect key="frame" x="16" y="8" width="231" height="74"/> <rect key="frame" x="15" y="11" width="225" height="68"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="9gX-VT-F9P"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="9gX-VT-F9P"/>
</constraints> </constraints>
@ -32,7 +32,7 @@
</connections> </connections>
</textField> </textField>
<button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SZJ-aY-45Y" userLabel="Setting"> <button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SZJ-aY-45Y" userLabel="Setting">
<rect key="frame" x="275" y="8" width="30" height="73.5"/> <rect key="frame" x="275" y="11" width="30" height="68"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="30" id="D9D-FC-ANz"/> <constraint firstAttribute="width" constant="30" id="D9D-FC-ANz"/>
</constraints> </constraints>
@ -43,7 +43,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hTh-ek-Xam" userLabel="Generate"> <button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hTh-ek-Xam" userLabel="Generate">
<rect key="frame" x="245" y="8" width="30" height="73.5"/> <rect key="frame" x="245" y="11" width="30" height="68"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="30" id="l0l-7B-Tws"/> <constraint firstAttribute="width" constant="30" id="l0l-7B-Tws"/>
</constraints> </constraints>
@ -56,7 +56,7 @@
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="SZJ-aY-45Y" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="5XP-xZ-YWY"/> <constraint firstItem="SZJ-aY-45Y" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="5XP-xZ-YWY"/>
<constraint firstAttribute="trailingMargin" secondItem="SZJ-aY-45Y" secondAttribute="trailing" constant="7" id="5bp-bm-9vv"/> <constraint firstAttribute="trailingMargin" secondItem="SZJ-aY-45Y" secondAttribute="trailing" id="5bp-bm-9vv"/>
<constraint firstAttribute="bottomMargin" secondItem="SZJ-aY-45Y" secondAttribute="bottom" id="CMF-1f-MdG"/> <constraint firstAttribute="bottomMargin" secondItem="SZJ-aY-45Y" secondAttribute="bottom" id="CMF-1f-MdG"/>
<constraint firstItem="hTh-ek-Xam" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="OjQ-mD-CWV"/> <constraint firstItem="hTh-ek-Xam" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="OjQ-mD-CWV"/>
<constraint firstItem="SZJ-aY-45Y" firstAttribute="leading" secondItem="hTh-ek-Xam" secondAttribute="trailing" id="UlR-NB-9So"/> <constraint firstItem="SZJ-aY-45Y" firstAttribute="leading" secondItem="hTh-ek-Xam" secondAttribute="trailing" id="UlR-NB-9So"/>
@ -64,7 +64,7 @@
<constraint firstAttribute="trailingMargin" secondItem="k0U-2N-YaX" secondAttribute="trailing" constant="65" id="fz0-HW-sCe"/> <constraint firstAttribute="trailingMargin" secondItem="k0U-2N-YaX" secondAttribute="trailing" constant="65" id="fz0-HW-sCe"/>
<constraint firstAttribute="bottomMargin" secondItem="k0U-2N-YaX" secondAttribute="bottom" id="lRv-MC-Cru"/> <constraint firstAttribute="bottomMargin" secondItem="k0U-2N-YaX" secondAttribute="bottom" id="lRv-MC-Cru"/>
<constraint firstItem="k0U-2N-YaX" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="sUk-sk-JLT"/> <constraint firstItem="k0U-2N-YaX" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="sUk-sk-JLT"/>
<constraint firstItem="k0U-2N-YaX" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="zGn-an-SlK"/> <constraint firstItem="k0U-2N-YaX" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="zGn-an-SlK"/>
</constraints> </constraints>
</tableViewCellContentView> </tableViewCellContentView>
<connections> <connections>

View file

@ -9,7 +9,6 @@
import UIKit import UIKit
import SVProgressHUD import SVProgressHUD
struct LabelTableViewCellData { struct LabelTableViewCellData {
var title: String var title: String
var content: String var content: String
@ -19,36 +18,53 @@ class LabelTableViewCell: UITableViewCell {
@IBOutlet weak var contentLabel: UILabel! @IBOutlet weak var contentLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var titleLabel: UILabel!
private enum CellType {
case password, URL, HOTP, other
}
var isPasswordCell = false private var type = CellType.other
var isURLCell = false private var isReveal = false
var isReveal = false
var isHOTPCell = false
weak var delegatePasswordTableView : PasswordDetailTableViewController? weak var delegatePasswordTableView : PasswordDetailTableViewController?
var passwordDisplayButton: UIButton? private var passwordDisplayButton: UIButton?
var buttons: UIView? private var buttons: UIView?
var cellData: LabelTableViewCellData? { var cellData: LabelTableViewCellData? {
didSet { didSet {
titleLabel.text = cellData?.title ?? "" guard let title = cellData?.title, let content = cellData?.content else {
if isPasswordCell { type = .other
return
}
titleLabel.text = title
switch title.lowercased() {
case "password":
type = .password
if isReveal { if isReveal {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: cellData?.content ?? "") contentLabel.attributedText = Utils.attributedPassword(plainPassword: content)
} else { } else {
contentLabel.text = Globals.passwordDots if content == "" {
contentLabel.text = ""
} else {
contentLabel.text = Globals.passwordDots
}
} }
contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize) contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
} else if isHOTPCell { case "hmac-based":
type = .HOTP
if isReveal { if isReveal {
contentLabel.text = cellData?.content ?? "" contentLabel.text = content
} else { } else {
contentLabel.text = Globals.passwordDots contentLabel.text = Globals.oneTimePasswordDots
} }
contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize) contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
} else { case "url":
contentLabel.text = cellData?.content type = .URL
contentLabel.text = content
default:
type = .other
contentLabel.text = content
} }
updateButtons() updateButtons()
} }
@ -76,46 +92,51 @@ class LabelTableViewCell: UITableViewCell {
} }
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if isPasswordCell { switch type {
case .password:
if isReveal { if isReveal {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:)) return action == #selector(copy(_:)) || action == #selector(concealPassword(_:))
} else { } else {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:)) return action == #selector(copy(_:)) || action == #selector(revealPassword(_:))
} }
} case .URL:
if isURLCell { return action == #selector(copy(_:)) || action == #selector(openLink(_:))
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.openLink(_:)) case .HOTP:
}
if isHOTPCell {
if isReveal { if isReveal {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:)) || action == #selector(LabelTableViewCell.getNextHOTP(_:)) return action == #selector(copy(_:)) || action == #selector(concealPassword(_:)) || action == #selector(getNextHOTP(_:))
} else { } else {
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:)) || action == #selector(LabelTableViewCell.getNextHOTP(_:)) return action == #selector(copy(_:)) || action == #selector(revealPassword(_:)) || action == #selector(getNextHOTP(_:))
} }
default:
return action == #selector(copy(_:))
} }
return action == #selector(copy(_:))
} }
override func copy(_ sender: Any?) { override func copy(_ sender: Any?) {
Utils.copyToPasteboard(textToCopy: cellData?.content) Utils.copyToPasteboard(textToCopy: cellData?.content)
} }
func revealPassword(_ sender: Any?) { func revealPassword(_ sender: Any?) {
if let plainPassword = cellData?.content { let plainPassword = cellData?.content ?? ""
if isHOTPCell { if type == .password {
contentLabel.text = plainPassword contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
} else {
contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
}
} else { } else {
contentLabel.text = "" contentLabel.text = plainPassword
} }
isReveal = true isReveal = true
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal) passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal)
} }
func concealPassword(_ sender: Any?) { func concealPassword(_ sender: Any?) {
contentLabel.text = Globals.passwordDots if type == .password {
if cellData?.content.isEmpty == false {
contentLabel.text = Globals.passwordDots
} else {
contentLabel.text = ""
}
} else {
contentLabel.text = Globals.oneTimePasswordDots
}
isReveal = false isReveal = false
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal) passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
} }
@ -130,7 +151,6 @@ class LabelTableViewCell: UITableViewCell {
} }
} }
func openLink(_ sender: Any?) { func openLink(_ sender: Any?) {
// if isURLCell, passwordTableView should not be nil // if isURLCell, passwordTableView should not be nil
delegatePasswordTableView!.openLink() delegatePasswordTableView!.openLink()
@ -141,10 +161,7 @@ class LabelTableViewCell: UITableViewCell {
delegatePasswordTableView!.getNextHOTP() delegatePasswordTableView!.getNextHOTP()
} }
func updateButtons() { private func updateButtons() {
passwordDisplayButton = nil
buttons = nil
// total width and height of a button // total width and height of a button
let height = min(self.bounds.height, 36.0) let height = min(self.bounds.height, 36.0)
let width = max(height * 0.8, Globals.tableCellButtonSize) let width = max(height * 0.8, Globals.tableCellButtonSize)
@ -153,16 +170,19 @@ class LabelTableViewCell: UITableViewCell {
let marginY = max((height - Globals.tableCellButtonSize) / 2, 0.0) let marginY = max((height - Globals.tableCellButtonSize) / 2, 0.0)
let marginX = max((width - Globals.tableCellButtonSize) / 2, 0.0) let marginX = max((width - Globals.tableCellButtonSize) / 2, 0.0)
if isPasswordCell { switch type {
// password button case .password:
passwordDisplayButton = UIButton(type: .system) if let content = cellData?.content, content != "" {
passwordDisplayButton!.frame = CGRect(x: 0, y: 0, width: width, height: height) // password button
passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal) passwordDisplayButton = UIButton(type: .system)
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit passwordDisplayButton!.frame = CGRect(x: 0, y: 0, width: width, height: height)
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX) passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside) passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
buttons = passwordDisplayButton passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
} else if isHOTPCell { passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside)
buttons = passwordDisplayButton
}
case .HOTP:
// hotp button // hotp button
let nextButton = UIButton(type: .system) let nextButton = UIButton(type: .system)
nextButton.frame = CGRect(x: 0, y: 0, width: width, height: height) nextButton.frame = CGRect(x: 0, y: 0, width: width, height: height)
@ -184,6 +204,9 @@ class LabelTableViewCell: UITableViewCell {
buttons!.frame = CGRect(x: 0, y: 0, width: width * 2, height: height) buttons!.frame = CGRect(x: 0, y: 0, width: width * 2, height: height)
buttons!.addSubview(nextButton) buttons!.addSubview(nextButton)
buttons!.addSubview(passwordDisplayButton!) buttons!.addSubview(passwordDisplayButton!)
default:
passwordDisplayButton = nil
buttons = nil
} }
} }
} }

View file

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="labelCell" id="KGk-i7-Jjw" customClass="LabelTableViewCell" customModule="pass" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="labelCell" rowHeight="59" id="KGk-i7-Jjw" customClass="LabelTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="333" height="52"/> <rect key="frame" x="0.0" y="0.0" width="333" height="59"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="333" height="51.5"/> <rect key="frame" x="0.0" y="0.0" width="333" height="58.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yyr-cF-QN0"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yyr-cF-QN0">
<rect key="frame" x="16" y="22.5" width="309" height="21"/> <rect key="frame" x="15" y="27" width="303" height="21"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="21" id="pgw-DF-LQa"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="21" id="pgw-DF-LQa"/>
</constraints> </constraints>
@ -29,15 +29,15 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dqz-7n-uEZ"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dqz-7n-uEZ">
<rect key="frame" x="16" y="8" width="309" height="11.5"/> <rect key="frame" x="15" y="11" width="303" height="13"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/> <fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
<color key="textColor" red="0.0" green="0.47843137254901957" blue="1" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.0" green="0.47843137254901957" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="trailingMargin" secondItem="Dqz-7n-uEZ" secondAttribute="trailing" id="228-VM-Z8Y"/> <constraint firstAttribute="trailingMargin" secondItem="Dqz-7n-uEZ" secondAttribute="trailing" id="228-VM-Z8Y"/>
<constraint firstItem="Dqz-7n-uEZ" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="CVm-uc-iVo"/> <constraint firstItem="Dqz-7n-uEZ" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="CVm-uc-iVo"/>
<constraint firstAttribute="topMargin" secondItem="Dqz-7n-uEZ" secondAttribute="top" id="N4y-iT-CiY"/> <constraint firstAttribute="topMargin" secondItem="Dqz-7n-uEZ" secondAttribute="top" id="N4y-iT-CiY"/>
<constraint firstItem="yyr-cF-QN0" firstAttribute="leading" secondItem="Dqz-7n-uEZ" secondAttribute="leading" id="TpW-bu-QIx"/> <constraint firstItem="yyr-cF-QN0" firstAttribute="leading" secondItem="Dqz-7n-uEZ" secondAttribute="leading" id="TpW-bu-QIx"/>
<constraint firstItem="yyr-cF-QN0" firstAttribute="width" secondItem="Dqz-7n-uEZ" secondAttribute="width" id="ZXa-fK-0Bg"/> <constraint firstItem="yyr-cF-QN0" firstAttribute="width" secondItem="Dqz-7n-uEZ" secondAttribute="width" id="ZXa-fK-0Bg"/>
@ -49,7 +49,7 @@
<outlet property="contentLabel" destination="yyr-cF-QN0" id="lhr-oA-ofC"/> <outlet property="contentLabel" destination="yyr-cF-QN0" id="lhr-oA-ofC"/>
<outlet property="titleLabel" destination="Dqz-7n-uEZ" id="Hac-od-A7K"/> <outlet property="titleLabel" destination="Dqz-7n-uEZ" id="Hac-od-A7K"/>
</connections> </connections>
<point key="canvasLocation" x="27.5" y="58"/> <point key="canvasLocation" x="15.5" y="89.5"/>
</tableViewCell> </tableViewCell>
</objects> </objects>
</document> </document>

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/> <capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -13,28 +13,28 @@
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="passwordDetailTitleTableViewCell" rowHeight="84" id="KGk-i7-Jjw" customClass="PasswordDetailTitleTableViewCell" customModule="pass" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="passwordDetailTitleTableViewCell" rowHeight="84" id="KGk-i7-Jjw" customClass="PasswordDetailTitleTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="84"/> <rect key="frame" x="0.0" y="0.0" width="320" height="84"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="83.5"/> <rect key="frame" x="0.0" y="0.0" width="320" height="83.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" placeholderIntrinsicWidth="52" placeholderIntrinsicHeight="52" translatesAutoresizingMaskIntoConstraints="NO" id="gKV-Cd-wIk"> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" placeholderIntrinsicWidth="52" placeholderIntrinsicHeight="52" translatesAutoresizingMaskIntoConstraints="NO" id="gKV-Cd-wIk">
<rect key="frame" x="16" y="16" width="52" height="52"/> <rect key="frame" x="15" y="15" width="54" height="54"/>
<constraints> <constraints>
<constraint firstAttribute="height" priority="999" constant="52" id="Liw-y2-PDc"/> <constraint firstAttribute="height" priority="999" constant="52" id="Liw-y2-PDc"/>
<constraint firstAttribute="width" secondItem="gKV-Cd-wIk" secondAttribute="height" multiplier="1:1" id="rsw-IM-Eh1"/> <constraint firstAttribute="width" secondItem="gKV-Cd-wIk" secondAttribute="height" multiplier="1:1" id="rsw-IM-Eh1"/>
</constraints> </constraints>
</imageView> </imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KvF-3d-EbG"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KvF-3d-EbG">
<rect key="frame" x="84" y="18" width="228" height="21"/> <rect key="frame" x="85" y="21" width="220" height="21"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/> <fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="category1 &gt; category2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ctk-b6-pjw"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="category1 &gt; category2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ctk-b6-pjw">
<rect key="frame" x="84" y="45" width="228" height="16"/> <rect key="frame" x="85" y="48" width="220" height="16"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/> <fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@ -44,12 +44,12 @@
<constraint firstItem="KvF-3d-EbG" firstAttribute="leading" secondItem="gKV-Cd-wIk" secondAttribute="trailing" constant="16" id="B4p-uV-LTm"/> <constraint firstItem="KvF-3d-EbG" firstAttribute="leading" secondItem="gKV-Cd-wIk" secondAttribute="trailing" constant="16" id="B4p-uV-LTm"/>
<constraint firstItem="Ctk-b6-pjw" firstAttribute="leading" secondItem="KvF-3d-EbG" secondAttribute="leading" id="CfQ-pR-HTm"/> <constraint firstItem="Ctk-b6-pjw" firstAttribute="leading" secondItem="KvF-3d-EbG" secondAttribute="leading" id="CfQ-pR-HTm"/>
<constraint firstAttribute="trailingMargin" secondItem="Ctk-b6-pjw" secondAttribute="trailing" id="GbW-iJ-02i"/> <constraint firstAttribute="trailingMargin" secondItem="Ctk-b6-pjw" secondAttribute="trailing" id="GbW-iJ-02i"/>
<constraint firstItem="gKV-Cd-wIk" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="8" id="JX5-qI-8f8"/> <constraint firstItem="gKV-Cd-wIk" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="4" id="JX5-qI-8f8"/>
<constraint firstItem="KvF-3d-EbG" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="10" id="OFd-QE-rRm"/> <constraint firstItem="KvF-3d-EbG" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="10" id="OFd-QE-rRm"/>
<constraint firstAttribute="bottomMargin" secondItem="gKV-Cd-wIk" secondAttribute="bottom" constant="8" id="fyJ-BI-Jls"/> <constraint firstAttribute="bottomMargin" secondItem="gKV-Cd-wIk" secondAttribute="bottom" constant="4" id="fyJ-BI-Jls"/>
<constraint firstAttribute="trailingMargin" secondItem="KvF-3d-EbG" secondAttribute="trailing" id="jPo-Fz-5h5"/> <constraint firstAttribute="trailingMargin" secondItem="KvF-3d-EbG" secondAttribute="trailing" id="jPo-Fz-5h5"/>
<constraint firstItem="Ctk-b6-pjw" firstAttribute="top" secondItem="KvF-3d-EbG" secondAttribute="bottom" constant="6" id="wDX-Mb-bsB"/> <constraint firstItem="Ctk-b6-pjw" firstAttribute="top" secondItem="KvF-3d-EbG" secondAttribute="bottom" constant="6" id="wDX-Mb-bsB"/>
<constraint firstItem="gKV-Cd-wIk" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="wjc-N6-2b9"/> <constraint firstItem="gKV-Cd-wIk" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="wjc-N6-2b9"/>
</constraints> </constraints>
</tableViewCellContentView> </tableViewCellContentView>
<connections> <connections>

View file

@ -1,32 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="71" id="KGk-i7-Jjw" customClass="SliderTableViewCell" customModule="pass" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="71" id="KGk-i7-Jjw" customClass="SliderTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="74"/> <rect key="frame" x="0.0" y="0.0" width="320" height="74"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="73.5"/> <rect key="frame" x="0.0" y="0.0" width="320" height="73.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="MwT-Jl-hhE"> <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="MwT-Jl-hhE">
<rect key="frame" x="61.5" y="22" width="204.5" height="31"/> <rect key="frame" x="60.5" y="22.5" width="198.5" height="31"/>
<connections> <connections>
<action selector="handleSliderValueChange:" destination="KGk-i7-Jjw" eventType="valueChanged" id="WwM-ZE-yIB"/> <action selector="handleSliderValueChange:" destination="KGk-i7-Jjw" eventType="valueChanged" id="WwM-ZE-yIB"/>
</connections> </connections>
</slider> </slider>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="88" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GJP-Fj-VZt" userLabel="Value"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="88" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GJP-Fj-VZt" userLabel="Value">
<rect key="frame" x="281" y="8" width="24" height="57.5"/> <rect key="frame" x="274" y="11" width="24" height="52"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="24" id="tOG-yp-eFw"/> <constraint firstAttribute="width" constant="24" id="tOG-yp-eFw"/>
</constraints> </constraints>
@ -35,7 +35,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="t7T-HC-hUd" userLabel="Title"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="t7T-HC-hUd" userLabel="Title">
<rect key="frame" x="16" y="8" width="30" height="57.5"/> <rect key="frame" x="15" y="11" width="30" height="52"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="8Tz-Qo-Mkg"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="8Tz-Qo-Mkg"/>
</constraints> </constraints>
@ -46,7 +46,7 @@
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="MwT-Jl-hhE" firstAttribute="leading" secondItem="t7T-HC-hUd" secondAttribute="trailing" constant="17" id="Cc9-kV-nZf"/> <constraint firstItem="MwT-Jl-hhE" firstAttribute="leading" secondItem="t7T-HC-hUd" secondAttribute="trailing" constant="17" id="Cc9-kV-nZf"/>
<constraint firstItem="t7T-HC-hUd" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="Kje-Zj-RaX"/> <constraint firstItem="t7T-HC-hUd" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="Kje-Zj-RaX"/>
<constraint firstAttribute="trailingMargin" secondItem="GJP-Fj-VZt" secondAttribute="trailing" constant="7" id="LjD-JH-dRT"/> <constraint firstAttribute="trailingMargin" secondItem="GJP-Fj-VZt" secondAttribute="trailing" constant="7" id="LjD-JH-dRT"/>
<constraint firstItem="MwT-Jl-hhE" firstAttribute="centerY" secondItem="t7T-HC-hUd" secondAttribute="centerY" id="MHN-Vl-1le"/> <constraint firstItem="MwT-Jl-hhE" firstAttribute="centerY" secondItem="t7T-HC-hUd" secondAttribute="centerY" id="MHN-Vl-1le"/>
<constraint firstAttribute="bottomMargin" secondItem="GJP-Fj-VZt" secondAttribute="bottom" id="NmC-sS-9Yt"/> <constraint firstAttribute="bottomMargin" secondItem="GJP-Fj-VZt" secondAttribute="bottom" id="NmC-sS-9Yt"/>

View file

@ -1,36 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="56" id="KGk-i7-Jjw" customClass="TextFieldTableViewCell" customModule="pass" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="69" id="KGk-i7-Jjw" customClass="TextFieldTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="56"/> <rect key="frame" x="0.0" y="0.0" width="320" height="69"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="55.5"/> <rect key="frame" x="0.0" y="0.0" width="320" height="68.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="8Ky-UZ-sLu"> <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="8Ky-UZ-sLu">
<rect key="frame" x="16" y="8" width="296" height="39.5"/> <rect key="frame" x="15" y="11" width="290" height="47"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="R1z-fU-Xr2"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="R1z-fU-Xr2"/>
</constraints> </constraints>
<nil key="textColor"/> <nil key="textColor"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/> <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" returnKeyType="next"/> <textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no"/>
</textField> </textField>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="3h4-9E-oDJ"/> <constraint firstItem="8Ky-UZ-sLu" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="3h4-9E-oDJ"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="FH4-4T-aaK"/> <constraint firstItem="8Ky-UZ-sLu" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="FH4-4T-aaK"/>
<constraint firstAttribute="trailingMargin" secondItem="8Ky-UZ-sLu" secondAttribute="trailing" id="Nii-Cg-gC1"/> <constraint firstAttribute="trailingMargin" secondItem="8Ky-UZ-sLu" secondAttribute="trailing" id="Nii-Cg-gC1"/>
<constraint firstAttribute="bottomMargin" secondItem="8Ky-UZ-sLu" secondAttribute="bottom" id="h72-l3-Sb3"/> <constraint firstAttribute="bottomMargin" secondItem="8Ky-UZ-sLu" secondAttribute="bottom" id="h72-l3-Sb3"/>
@ -39,7 +39,7 @@
<connections> <connections>
<outlet property="contentTextField" destination="8Ky-UZ-sLu" id="pdS-Nl-e9s"/> <outlet property="contentTextField" destination="8Ky-UZ-sLu" id="pdS-Nl-e9s"/>
</connections> </connections>
<point key="canvasLocation" x="34" y="60"/> <point key="canvasLocation" x="34" y="66.5"/>
</tableViewCell> </tableViewCell>
</objects> </objects>
</document> </document>

View file

@ -13,6 +13,8 @@ class TextViewTableViewCell: ContentTableViewCell {
@IBOutlet weak var contentTextView: UITextView! @IBOutlet weak var contentTextView: UITextView!
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
self.contentTextView.textContainer.lineFragmentPadding = 0
self.contentTextView.textContainerInset = .zero
} }
override func setSelected(_ selected: Bool, animated: Bool) { override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated) super.setSelected(selected, animated: animated)

View file

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="217" id="KGk-i7-Jjw" customClass="TextViewTableViewCell" customModule="pass" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="217" id="KGk-i7-Jjw" customClass="TextViewTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="217"/> <rect key="frame" x="0.0" y="0.0" width="320" height="217"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="216.5"/> <rect key="frame" x="0.0" y="0.0" width="320" height="216.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xHX-Sh-1pR"> <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xHX-Sh-1pR">
<rect key="frame" x="12" y="16" width="300" height="184.5"/> <rect key="frame" x="15" y="11" width="290" height="195"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="120" id="Tvq-j8-Nvh"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="120" id="Tvq-j8-Nvh"/>
@ -31,9 +31,9 @@
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="trailingMargin" secondItem="xHX-Sh-1pR" secondAttribute="trailing" id="LWS-JW-9dS"/> <constraint firstAttribute="trailingMargin" secondItem="xHX-Sh-1pR" secondAttribute="trailing" id="LWS-JW-9dS"/>
<constraint firstItem="xHX-Sh-1pR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="4" id="SRq-7t-Gyr"/> <constraint firstItem="xHX-Sh-1pR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="SRq-7t-Gyr"/>
<constraint firstAttribute="bottomMargin" secondItem="xHX-Sh-1pR" secondAttribute="bottom" constant="8" id="UPQ-jk-QJR"/> <constraint firstAttribute="bottomMargin" secondItem="xHX-Sh-1pR" secondAttribute="bottom" id="UPQ-jk-QJR"/>
<constraint firstItem="xHX-Sh-1pR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" constant="8" id="gwb-2C-4wp"/> <constraint firstItem="xHX-Sh-1pR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="gwb-2C-4wp"/>
</constraints> </constraints>
</tableViewCellContentView> </tableViewCellContentView>
<connections> <connections>

View file

@ -1,32 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12106.1" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12118" systemVersion="16F43c" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12074.1"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="56" id="KGk-i7-Jjw" customClass="TitleTextFieldTableViewCell" customModule="pass" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="56" id="KGk-i7-Jjw" customClass="TitleTextFieldTableViewCell" customModule="pass" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="56"/> <rect key="frame" x="0.0" y="0.0" width="320" height="56"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="55.5"/> <rect key="frame" x="0.0" y="0.0" width="320" height="55.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HPC-rQ-39b"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HPC-rQ-39b">
<rect key="frame" x="16" y="8" width="296" height="15"/> <rect key="frame" x="15" y="11" width="290" height="10"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/> <fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
<color key="textColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="123" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="8Ky-UZ-sLu"> <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="123" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="8Ky-UZ-sLu">
<rect key="frame" x="16" y="26" width="296" height="21"/> <rect key="frame" x="15" y="24" width="290" height="21"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="21" id="dHY-dw-mFe"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="21" id="dHY-dw-mFe"/>
</constraints> </constraints>
@ -39,7 +39,7 @@
<constraint firstAttribute="bottomMargin" secondItem="8Ky-UZ-sLu" secondAttribute="bottom" id="11n-KG-zy6"/> <constraint firstAttribute="bottomMargin" secondItem="8Ky-UZ-sLu" secondAttribute="bottom" id="11n-KG-zy6"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="trailing" secondItem="HPC-rQ-39b" secondAttribute="trailing" id="42z-vE-AsJ"/> <constraint firstItem="8Ky-UZ-sLu" firstAttribute="trailing" secondItem="HPC-rQ-39b" secondAttribute="trailing" id="42z-vE-AsJ"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="leading" secondItem="HPC-rQ-39b" secondAttribute="leading" id="8F6-w6-BDY"/> <constraint firstItem="8Ky-UZ-sLu" firstAttribute="leading" secondItem="HPC-rQ-39b" secondAttribute="leading" id="8F6-w6-BDY"/>
<constraint firstItem="HPC-rQ-39b" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" constant="8" id="8ps-9g-9zG"/> <constraint firstItem="HPC-rQ-39b" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="8ps-9g-9zG"/>
<constraint firstAttribute="trailingMargin" secondItem="HPC-rQ-39b" secondAttribute="trailing" id="Ayv-Bg-JxT"/> <constraint firstAttribute="trailingMargin" secondItem="HPC-rQ-39b" secondAttribute="trailing" id="Ayv-Bg-JxT"/>
<constraint firstItem="HPC-rQ-39b" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="JN2-lI-0ZY"/> <constraint firstItem="HPC-rQ-39b" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="JN2-lI-0ZY"/>
<constraint firstItem="8Ky-UZ-sLu" firstAttribute="top" secondItem="HPC-rQ-39b" secondAttribute="bottom" constant="3" id="Pqs-gm-93j"/> <constraint firstItem="8Ky-UZ-sLu" firstAttribute="top" secondItem="HPC-rQ-39b" secondAttribute="bottom" constant="3" id="Pqs-gm-93j"/>