Merge branch 'release/0.2.4'
This commit is contained in:
commit
9f2aa37b8d
38 changed files with 1036 additions and 554 deletions
2
Cartfile
2
Cartfile
|
|
@ -3,5 +3,5 @@ github "radex/SwiftyUserDefaults"
|
|||
github "libgit2/objective-git"
|
||||
github "zahlz/SwiftPasscodeLock" "master"
|
||||
github "bitserf/FavIcon"
|
||||
github "kishikawakatsumi/KeychainAccess" "master"
|
||||
github "kishikawakatsumi/KeychainAccess"
|
||||
github "mattrubin/OneTimePassword"
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
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 */; };
|
||||
A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = A27424D81E7C35960093F436 /* NotificationNames.swift */; };
|
||||
A2802BF91E70813A00879216 /* SliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2802BF71E70813A00879216 /* SliderTableViewCell.swift */; };
|
||||
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 */; };
|
||||
DC037CA81E4B898100609409 /* BasicStaticTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA71E4B898100609409 /* BasicStaticTableViewController.swift */; };
|
||||
DC037CAA1E4B8EAE00609409 /* SpecialThanksTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */; };
|
||||
|
|
@ -80,10 +83,13 @@
|
|||
|
||||
/* 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
|
|
@ -215,6 +221,8 @@
|
|||
DC037CA91E4B8EAE00609409 /* SpecialThanksTableViewController.swift */,
|
||||
DC8963BF1E38EEB900828B09 /* SSHKeySettingTableViewController.swift */,
|
||||
DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */,
|
||||
A2A7813E1E97DBD9001311F5 /* QRScannerController.swift */,
|
||||
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */,
|
||||
);
|
||||
path = Controllers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -239,6 +247,7 @@
|
|||
DCA049971E33586A00522E8F /* DefaultsKeys.swift */,
|
||||
DC19400A1E4B36B60077E0A3 /* Utils.swift */,
|
||||
A27424D81E7C35960093F436 /* NotificationNames.swift */,
|
||||
A2A89D681E954698003FB2D3 /* UITextFieldExtension.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -524,9 +533,11 @@
|
|||
DC5F385B1E56AADB00C69ACA /* PGPKeyArmorSettingTableViewController.swift in Sources */,
|
||||
A27424D91E7C35960093F436 /* NotificationNames.swift in Sources */,
|
||||
DCAAF7451E2FA66800AB94BC /* SettingsTableViewController.swift in Sources */,
|
||||
A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */,
|
||||
DCFB77A71E502DF9008DE471 /* EditPasswordTableViewController.swift in Sources */,
|
||||
DCA0499A1E335CC800522E8F /* GitServerSettingTableViewController.swift in Sources */,
|
||||
DCDDEAB31E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift in Sources */,
|
||||
A2A7813F1E97DBD9001311F5 /* QRScannerController.swift in Sources */,
|
||||
DCC277D21E30D6EA00402246 /* pass.xcdatamodeld in Sources */,
|
||||
DC4914991E434600007FF592 /* PasswordDetailTableViewController.swift in Sources */,
|
||||
DC962CDF1E4B62C10033B5D8 /* AboutTableViewController.swift in Sources */,
|
||||
|
|
@ -544,6 +555,7 @@
|
|||
DC1940001E49E1A60077E0A3 /* PasscodeLockConfiguration.swift in Sources */,
|
||||
DC917BD71E2E8231000FDF54 /* AppDelegate.swift in Sources */,
|
||||
DCFB779A1E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift in Sources */,
|
||||
A2A89D691E954698003FB2D3 /* UITextFieldExtension.swift in Sources */,
|
||||
DC037CBB1E4DD47B00609409 /* TextFieldTableViewCell.swift in Sources */,
|
||||
DC193FFE1E49E0760077E0A3 /* PasscodeLockRepository.swift in Sources */,
|
||||
DCA049981E33586A00522E8F /* DefaultsKeys.swift in Sources */,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,7 +18,6 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
|||
private let passwordStore = PasswordStore.shared
|
||||
|
||||
override func viewDidLoad() {
|
||||
navigationItemTitle = "About Repository"
|
||||
super.viewDidLoad()
|
||||
|
||||
indicator.center = CGPoint(x: view.bounds.midX, y: view.bounds.height * 0.382)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ class AboutTableViewController: BasicStaticTableViewController {
|
|||
[[.title: "Open Source Components", .action: "segue", .link: "showOpenSourceComponentsSegue"],
|
||||
[.title: "Special Thanks", .action: "segue", .link: "showSpecialThanksSegue"],],
|
||||
]
|
||||
navigationItemTitle = "About"
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password"],
|
||||
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
|
||||
[[.type: PasswordEditorCellType.textViewCell, .title: "additions"]],
|
||||
[[.type: PasswordEditorCellType.scanQRCodeCell]]
|
||||
]
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
|
@ -54,13 +55,15 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
}
|
||||
|
||||
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]()
|
||||
for cell in cells {
|
||||
let indexPath = tableView.indexPath(for: cell)!
|
||||
let contentCell = cell as! ContentTableViewCell
|
||||
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as! String
|
||||
if let cellContent = contentCell.getContent() {
|
||||
if let indexPath = tableView.indexPath(for: cell),
|
||||
let contentCell = cell as? ContentTableViewCell,
|
||||
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as? String,
|
||||
let cellContent = contentCell.getContent() {
|
||||
cellContents[cellTitle] = cellContent
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
navigationItem.title = "Advanced"
|
||||
encryptInASCIIArmoredSwitch.isOn = Defaults[.encryptInArmored]
|
||||
encryptInASCIIArmoredTableViewCell.accessoryView = encryptInASCIIArmoredSwitch
|
||||
encryptInASCIIArmoredTableViewCell.selectionStyle = .none
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
navigationItem.title = navigationItemTitle
|
||||
if navigationItemTitle != nil {
|
||||
navigationItem.title = navigationItemTitle
|
||||
}
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
|
@ -54,6 +56,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
|||
switch cellDataStyle ?? .defaultStyle {
|
||||
case .value1:
|
||||
cell = UITableViewCell(style: .value1, reuseIdentifier: "value1 cell")
|
||||
cell?.selectionStyle = .none
|
||||
default:
|
||||
cell = UITableViewCell(style: .default, reuseIdentifier: "default cell")
|
||||
}
|
||||
|
|
@ -65,6 +68,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
|||
cell?.accessoryType = accessoryType
|
||||
} else {
|
||||
cell?.accessoryType = .disclosureIndicator
|
||||
cell?.selectionStyle = .default
|
||||
}
|
||||
|
||||
cell?.textLabel?.text = cellData[CellDataKey.title] as? String
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ class CommitLogsTableViewController: UITableViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
commits = passwordStore.getRecentCommits(count: 20)
|
||||
navigationItem.title = "Recent Commit Logs"
|
||||
navigationController!.navigationBar.topItem!.title = "About"
|
||||
self.tableView.estimatedRowHeight = 50
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension
|
||||
}
|
||||
|
||||
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 {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "commitLogCell", for: indexPath)
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = DateFormatter.Style.short
|
||||
formatter.timeStyle = .none
|
||||
formatter.dateStyle = DateFormatter.Style.medium
|
||||
formatter.timeStyle = .medium
|
||||
let dateString = formatter.string(from: commits[indexPath.row].commitDate)
|
||||
let dateLabel = cell.viewWithTag(101) as! UILabel
|
||||
let messageLabel = cell.viewWithTag(102) as! UILabel
|
||||
dateLabel.text = dateString
|
||||
messageLabel.text = commits[indexPath.row].message
|
||||
|
||||
let author = cell.contentView.viewWithTag(100) as? UILabel
|
||||
let dateLabel = cell.contentView.viewWithTag(101) as? UILabel
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
|||
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password", .content: password!.password],
|
||||
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
|
||||
[[.type: PasswordEditorCellType.textViewCell, .title: "additions", .content: password!.getAdditionsPlainText()]],
|
||||
[[.type: PasswordEditorCellType.deletePasswordCell]],
|
||||
[[.type: PasswordEditorCellType.scanQRCodeCell],
|
||||
[.type: PasswordEditorCellType.deletePasswordCell]]
|
||||
]
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
|
@ -37,16 +38,16 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
|||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
super.prepare(for: segue, sender: sender)
|
||||
if segue.identifier == "saveEditPasswordSegue" {
|
||||
let cells = tableView.visibleCells
|
||||
var cellContents = [String: String]()
|
||||
for cell in cells {
|
||||
let indexPath = tableView.indexPath(for: cell)!
|
||||
if let contentCell = cell as? ContentTableViewCell {
|
||||
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as! String
|
||||
if let cellContent = contentCell.getContent() {
|
||||
cellContents[cellTitle] = cellContent
|
||||
}
|
||||
if let indexPath = tableView.indexPath(for: cell),
|
||||
let contentCell = cell as? ContentTableViewCell,
|
||||
let cellTitle = tableData[indexPath.section][indexPath.row][.title] as? String,
|
||||
let cellContent = contentCell.getContent() {
|
||||
cellContents[cellTitle] = cellContent
|
||||
}
|
||||
}
|
||||
var plainText = ""
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
navigationItemTitle = "General"
|
||||
tableData = [
|
||||
// section 0
|
||||
[[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],],
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@
|
|||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
|
||||
class GitSSHKeyArmorSettingTableViewController: UITableViewController {
|
||||
class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate {
|
||||
@IBOutlet weak var armorPublicKeyTextView: UITextView!
|
||||
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
||||
var gitSSHPrivateKeyPassphrase: String?
|
||||
let passwordStore = PasswordStore.shared
|
||||
var doneBarButtonItem: UIBarButtonItem?
|
||||
|
||||
private var recentPastedText = ""
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
|
@ -22,12 +23,8 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
|
|||
armorPrivateKeyTextView.text = Defaults[.gitSSHPrivateKeyArmor]
|
||||
gitSSHPrivateKeyPassphrase = passwordStore.gitSSHPrivateKeyPassphrase
|
||||
|
||||
doneBarButtonItem = UIBarButtonItem(title: "Done",
|
||||
style: UIBarButtonItemStyle.done,
|
||||
target: self,
|
||||
action: #selector(doneButtonTapped(_:)))
|
||||
navigationItem.rightBarButtonItem = doneBarButtonItem
|
||||
navigationItem.title = "SSH Key"
|
||||
armorPublicKeyTextView.delegate = self
|
||||
armorPrivateKeyTextView.delegate = self
|
||||
}
|
||||
|
||||
private func createSavePassphraseAlert() -> UIAlertController {
|
||||
|
|
@ -46,7 +43,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
|
|||
return savePassphraseAlert
|
||||
}
|
||||
|
||||
func doneButtonTapped(_ sender: Any) {
|
||||
@IBAction func doneButtonTapped(_ sender: Any) {
|
||||
Defaults[.gitSSHPublicKeyArmor] = armorPublicKeyTextView.text
|
||||
Defaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
|
||||
do {
|
||||
|
|
@ -68,5 +65,17 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
83
pass/Controllers/OTPScannerController.swift
Normal file
83
pass/Controllers/OTPScannerController.swift
Normal 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)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
@ -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]]
|
||||
)
|
||||
}
|
||||
navigationItemTitle = "Open Source Components"
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@
|
|||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
|
||||
class PGPKeyArmorSettingTableViewController: UITableViewController {
|
||||
class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate {
|
||||
@IBOutlet weak var armorPublicKeyTextView: UITextView!
|
||||
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
||||
var pgpPassphrase: String?
|
||||
let passwordStore = PasswordStore.shared
|
||||
|
||||
private var recentPastedText = ""
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
armorPublicKeyTextView.text = Defaults[.pgpPublicKeyArmor]
|
||||
|
|
@ -22,7 +24,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
|
|||
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)
|
||||
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
|
||||
Defaults[.isRememberPassphraseOn] = false
|
||||
|
|
@ -35,12 +37,26 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
|
|||
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) {
|
||||
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
|
||||
self.pgpPassphrase = alert.textFields?.first?.text
|
||||
let savePassphraseAlert = self.createSavePassphraseAlert()
|
||||
self.present(savePassphraseAlert, animated: true, completion: nil)
|
||||
let savePassphraseAndSegueAlert = self.createSavePassphraseAndSegueAlert()
|
||||
self.present(savePassphraseAndSegueAlert, animated: true, completion: nil)
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = self.pgpPassphrase
|
||||
|
|
@ -48,5 +64,18 @@ class PGPKeyArmorSettingTableViewController: UITableViewController {
|
|||
})
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,19 @@ class PGPKeySettingTableViewController: UITableViewController {
|
|||
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 {
|
||||
if identifier == "savePGPKeySegue" {
|
||||
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)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
self.pgpPassphrase = alert.textFields?.first?.text
|
||||
if self.shouldPerformSegue(withIdentifier: "savePGPKeySegue", sender: self) {
|
||||
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
|
||||
}
|
||||
let savePassphraseAndSegueAlert = self.createSavePassphraseAndSegueAlert()
|
||||
self.present(savePassphraseAndSegueAlert, animated: true, completion: nil)
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = self.pgpPassphrase
|
||||
|
|
|
|||
|
|
@ -386,11 +386,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
return;
|
||||
}
|
||||
|
||||
// increase HOTP counter
|
||||
password!.increaseHotpCounter()
|
||||
|
||||
// copy HOTP to pasteboard
|
||||
if let plainPassword = password!.getOtp() {
|
||||
// copy HOTP to pasteboard (will update counter)
|
||||
if let plainPassword = password!.getNextHotp() {
|
||||
Utils.copyToPasteboard(textToCopy: plainPassword)
|
||||
}
|
||||
|
||||
|
|
@ -448,9 +445,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
let titleData = tableDataItem.title
|
||||
let contentData = tableDataItem.content
|
||||
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.selectionStyle = .none
|
||||
return cell
|
||||
|
|
|
|||
|
|
@ -8,16 +8,17 @@
|
|||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import OneTimePassword
|
||||
|
||||
enum PasswordEditorCellType {
|
||||
case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell, deletePasswordCell
|
||||
case textFieldCell, textViewCell, fillPasswordCell, passwordLengthCell, deletePasswordCell, scanQRCodeCell
|
||||
}
|
||||
|
||||
enum PasswordEditorCellKey {
|
||||
case type, title, content, placeholders
|
||||
}
|
||||
|
||||
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, UIGestureRecognizerDelegate {
|
||||
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, QRScannerControllerDelegate {
|
||||
|
||||
var tableData = [
|
||||
[Dictionary<PasswordEditorCellKey, Any>]
|
||||
|
|
@ -29,11 +30,13 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
|
||||
private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
|
||||
private let passwordSection = 1
|
||||
private let additionsSection = 2
|
||||
private var hidePasswordSettings = true
|
||||
|
||||
private var fillPasswordCell: FillPasswordTableViewCell?
|
||||
private var passwordLengthCell: SliderTableViewCell?
|
||||
private var deletePasswordCell: UITableViewCell?
|
||||
private var scanQRCodeCell: UITableViewCell?
|
||||
|
||||
override func loadView() {
|
||||
super.loadView()
|
||||
|
|
@ -42,11 +45,22 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
deletePasswordCell!.textLabel?.text = "Delete Password"
|
||||
deletePasswordCell!.textLabel?.textColor = Globals.red
|
||||
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() {
|
||||
super.viewDidLoad()
|
||||
navigationItem.title = navigationItemTitle
|
||||
if navigationItemTitle != nil {
|
||||
navigationItem.title = navigationItemTitle
|
||||
}
|
||||
|
||||
tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell")
|
||||
tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell")
|
||||
tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell")
|
||||
|
|
@ -83,6 +97,8 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
return passwordLengthCell!
|
||||
case .deletePasswordCell:
|
||||
return deletePasswordCell!
|
||||
case .scanQRCodeCell:
|
||||
return scanQRCodeCell!
|
||||
default:
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! ContentTableViewCell
|
||||
cell.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
|
||||
|
|
@ -116,13 +132,16 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
}
|
||||
|
||||
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)
|
||||
alert.addAction(UIAlertAction(title: "Delete", style: UIAlertActionStyle.destructive, handler: {[unowned self] (action) -> Void in
|
||||
self.performSegue(withIdentifier: "deletePasswordSegue", sender: self)
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler:nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
} else if selectedCell == scanQRCodeCell {
|
||||
self.performSegue(withIdentifier: "showQRScannerSegue", sender: self)
|
||||
}
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
}
|
||||
|
|
@ -165,4 +184,53 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
hidePasswordSettings = !hidePasswordSettings
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,25 +11,28 @@ import SVProgressHUD
|
|||
import SwiftyUserDefaults
|
||||
import PasscodeLock
|
||||
|
||||
enum PasswordsTableEntryType {
|
||||
case password, dir
|
||||
}
|
||||
|
||||
struct PasswordsTableEntry {
|
||||
fileprivate class PasswordsTableEntry : NSObject {
|
||||
var title: String
|
||||
var isDir: Bool
|
||||
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 passwordsTableAllEntries: [PasswordsTableEntry] = []
|
||||
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
||||
private var parentPasswordEntity: PasswordEntity? = nil
|
||||
private let passwordStore = PasswordStore.shared
|
||||
|
||||
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 lazy var searchController: UISearchController = {
|
||||
|
|
@ -80,8 +83,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
private func initPasswordsTableEntries(parent: PasswordEntity?) {
|
||||
passwordsTableEntries.removeAll()
|
||||
passwordsTableAllEntries.removeAll()
|
||||
filteredPasswordsTableEntries.removeAll()
|
||||
var passwordEntities = [PasswordEntity]()
|
||||
var passwordAllEntities = [PasswordEntity]()
|
||||
if Defaults[.isShowFolderOn] {
|
||||
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(parent: parent)
|
||||
} else {
|
||||
|
|
@ -90,6 +95,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
passwordsTableEntries = passwordEntities.map {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -149,17 +158,31 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
} catch {
|
||||
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() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tabBarController!.delegate = self
|
||||
searchController.searchBar.delegate = self
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
definesPresentationContext = true
|
||||
|
|
@ -196,13 +219,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
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 {
|
||||
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
|
||||
longPressGestureRecognizer.minimumPressDuration = 0.6
|
||||
if Defaults[.isShowFolderOn] {
|
||||
if Defaults[.isShowFolderOn] && searchController.searchBar.selectedScopeButtonIndex == 0{
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||
|
||||
let entry = getPasswordEntry(by: indexPath)
|
||||
|
|
@ -241,14 +264,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
||||
var entry: PasswordsTableEntry
|
||||
let index = sections[indexPath.section].index + indexPath.row
|
||||
if searchController.isActive && searchController.searchBar.text != "" {
|
||||
entry = filteredPasswordsTableEntries[index]
|
||||
} else {
|
||||
entry = passwordsTableEntries[index]
|
||||
}
|
||||
return entry
|
||||
return sections[indexPath.section].entries[indexPath.row]
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
|
@ -268,7 +284,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
|
||||
func backAction(_ sender: Any?) {
|
||||
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) {
|
||||
|
|
@ -345,25 +365,30 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
private func generateSections(item: [PasswordsTableEntry]) {
|
||||
sections.removeAll()
|
||||
guard item.count != 0 else {
|
||||
return
|
||||
let collation = UILocalizedIndexedCollation.current()
|
||||
let sectionTitles = collation.sectionIndexTitles
|
||||
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 {
|
||||
let title = item[index].title.uppercased()
|
||||
let commonPrefix = item[i].title.commonPrefix(with: title, options: .caseInsensitive)
|
||||
if commonPrefix.characters.count == 0 {
|
||||
let firstCharacter = title[title.startIndex]
|
||||
let newSection = (index: index, length: i - index, title: "\(firstCharacter)")
|
||||
sections.append(newSection)
|
||||
index = i
|
||||
}
|
||||
|
||||
// put entries into sections
|
||||
for entry in item {
|
||||
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
|
||||
newSections[sectionNumber].entries.append(entry)
|
||||
}
|
||||
let title = item[index].title.uppercased()
|
||||
let firstCharacter = title[title.startIndex]
|
||||
let newSection = (index: index, length: item.count - index, title: "\(firstCharacter)")
|
||||
sections.append(newSection)
|
||||
|
||||
// sort each list and set sectionTitles
|
||||
for i in 0..<sectionTitles.count {
|
||||
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() {
|
||||
|
|
@ -381,6 +406,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -396,14 +426,30 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
func filterContentForSearchText(searchText: String, scope: String = "All") {
|
||||
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)
|
||||
switch scope {
|
||||
case "All":
|
||||
filteredPasswordsTableEntries = passwordsTableAllEntries.filter { entry in
|
||||
return entry.title.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
if searchController.isActive && searchController.searchBar.text != "" {
|
||||
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) {
|
||||
|
|
@ -466,10 +512,22 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
128
pass/Controllers/QRScannerController.swift
Normal file
128
pass/Controllers/QRScannerController.swift
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,9 @@ class RawPasswordViewController: UIViewController {
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
navigationItem.title = "Raw"
|
||||
navigationItem.title = password?.name
|
||||
rawPasswordTextView.textContainer.lineFragmentPadding = 0
|
||||
rawPasswordTextView.textContainerInset = .zero
|
||||
rawPasswordTextView.text = password?.plainText
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,17 +20,10 @@ class SSHKeySettingTableViewController: UITableViewController {
|
|||
super.viewDidLoad()
|
||||
privateKeyURLTextField.text = Defaults[.gitSSHPrivateKeyURL]?.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 {
|
||||
Utils.alert(title: "Cannot Save", message: "Please set Public Key URL first.", controller: self, completion: nil)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class SpecialThanksTableViewController: BasicStaticTableViewController {
|
|||
[CellDataKey.action: "link", CellDataKey.title: item[0], CellDataKey.link: item[1]]
|
||||
)
|
||||
}
|
||||
navigationItemTitle = "Special Thanks"
|
||||
super.viewDidLoad()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class Globals {
|
|||
"Apple": (min: 15, max: 15, def: 15)]
|
||||
|
||||
static let passwordDots = "••••••••••••"
|
||||
static let oneTimePasswordDots = "••••••"
|
||||
static let passwordFonts = "Menlo"
|
||||
|
||||
// UI related
|
||||
|
|
|
|||
34
pass/Helpers/UITextFieldExtension.swift
Normal file
34
pass/Helpers/UITextFieldExtension.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ class Utils {
|
|||
|
||||
static func randomString(length: Int) -> String {
|
||||
|
||||
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_+-="
|
||||
let len = UInt32(letters.length)
|
||||
|
||||
var randomString = ""
|
||||
|
|
|
|||
|
|
@ -17,13 +17,15 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2.3</string>
|
||||
<string>0.2.4</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>We need to access your camera for scanning QR code.</string>
|
||||
<key>UIApplicationShortcutItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
|
|
|||
|
|
@ -61,21 +61,20 @@ class Password {
|
|||
private func initEverything(name: String, plainText: String) {
|
||||
self.name = name
|
||||
self.plainText = plainText
|
||||
self.additions.removeAll()
|
||||
self.additionKeys.removeAll()
|
||||
|
||||
// get password and additional fields
|
||||
let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) {
|
||||
$0 == "\n" || $0 == "\r\n"
|
||||
}.map(String.init)
|
||||
guard plainTextSplit.count > 0 else {
|
||||
return;
|
||||
}
|
||||
self.password = plainTextSplit[0]
|
||||
self.password = plainTextSplit.first ?? ""
|
||||
if plainTextSplit.count == 2 {
|
||||
(self.additions, self.additionKeys) = Password.getAdditionFields(from: plainTextSplit[1])
|
||||
}
|
||||
|
||||
// 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 ?? "") {
|
||||
firstLineIsOTPField = true
|
||||
self.additions[key!] = value
|
||||
|
|
@ -99,12 +98,16 @@ class Password {
|
|||
// return a key-value pair from the line
|
||||
// key might be nil, if there is no ":" in the line
|
||||
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 value = ""
|
||||
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
|
||||
// otpauth special case
|
||||
if value.hasPrefix("otpauth://") {
|
||||
key = "otpauth"
|
||||
}
|
||||
} else {
|
||||
if !items[0].isEmpty {
|
||||
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
|
||||
func getOtpStrings() -> (description: String, otp: String)? {
|
||||
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 {
|
||||
let (key, _) = getKeyValuePair(from: line)
|
||||
return Password.otpKeywords.contains(key ?? "")
|
||||
|
|
|
|||
|
|
@ -220,27 +220,21 @@ class PasswordStore {
|
|||
}
|
||||
|
||||
public func initPGPKey(_ keyType: PGPKeyType) throws {
|
||||
var keyPath = ""
|
||||
switch keyType {
|
||||
case .public:
|
||||
keyPath = Globals.pgpPublicKeyPath
|
||||
case .secret:
|
||||
keyPath = Globals.pgpPrivateKeyPath
|
||||
default:
|
||||
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."])
|
||||
let keyPath = Globals.pgpPublicKeyPath
|
||||
self.publicKey = importKey(from: keyPath)
|
||||
if self.publicKey == nil {
|
||||
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import the public PGP key."])
|
||||
}
|
||||
} else {
|
||||
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import key."])
|
||||
case .secret:
|
||||
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
|
||||
if fm.fileExists(atPath: keyPath) {
|
||||
if let keys = pgp.importKeys(fromFile: keyPath, allowDuplicates: false) as? [PGPKey] {
|
||||
if keys.count > 0 {
|
||||
return keys[0]
|
||||
}
|
||||
return keys.first
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
<?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">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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">
|
||||
<rect key="frame" x="16" y="8" width="231" height="74"/>
|
||||
<rect key="frame" x="15" y="11" width="225" height="68"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="9gX-VT-F9P"/>
|
||||
</constraints>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
</connections>
|
||||
</textField>
|
||||
<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>
|
||||
<constraint firstAttribute="width" constant="30" id="D9D-FC-ANz"/>
|
||||
</constraints>
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<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>
|
||||
<constraint firstAttribute="width" constant="30" id="l0l-7B-Tws"/>
|
||||
</constraints>
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
</subviews>
|
||||
<constraints>
|
||||
<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 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"/>
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
<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 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>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
import UIKit
|
||||
import SVProgressHUD
|
||||
|
||||
|
||||
struct LabelTableViewCellData {
|
||||
var title: String
|
||||
var content: String
|
||||
|
|
@ -19,36 +18,53 @@ class LabelTableViewCell: UITableViewCell {
|
|||
|
||||
@IBOutlet weak var contentLabel: UILabel!
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
|
||||
private enum CellType {
|
||||
case password, URL, HOTP, other
|
||||
}
|
||||
|
||||
var isPasswordCell = false
|
||||
var isURLCell = false
|
||||
var isReveal = false
|
||||
var isHOTPCell = false
|
||||
private var type = CellType.other
|
||||
private var isReveal = false
|
||||
|
||||
weak var delegatePasswordTableView : PasswordDetailTableViewController?
|
||||
|
||||
var passwordDisplayButton: UIButton?
|
||||
var buttons: UIView?
|
||||
private var passwordDisplayButton: UIButton?
|
||||
private var buttons: UIView?
|
||||
|
||||
var cellData: LabelTableViewCellData? {
|
||||
didSet {
|
||||
titleLabel.text = cellData?.title ?? ""
|
||||
if isPasswordCell {
|
||||
guard let title = cellData?.title, let content = cellData?.content else {
|
||||
type = .other
|
||||
return
|
||||
}
|
||||
titleLabel.text = title
|
||||
switch title.lowercased() {
|
||||
case "password":
|
||||
type = .password
|
||||
if isReveal {
|
||||
contentLabel.attributedText = Utils.attributedPassword(plainPassword: cellData?.content ?? "")
|
||||
contentLabel.attributedText = Utils.attributedPassword(plainPassword: content)
|
||||
} else {
|
||||
contentLabel.text = Globals.passwordDots
|
||||
if content == "" {
|
||||
contentLabel.text = ""
|
||||
} else {
|
||||
contentLabel.text = Globals.passwordDots
|
||||
}
|
||||
}
|
||||
contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
|
||||
} else if isHOTPCell {
|
||||
case "hmac-based":
|
||||
type = .HOTP
|
||||
if isReveal {
|
||||
contentLabel.text = cellData?.content ?? ""
|
||||
contentLabel.text = content
|
||||
} else {
|
||||
contentLabel.text = Globals.passwordDots
|
||||
contentLabel.text = Globals.oneTimePasswordDots
|
||||
}
|
||||
contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
|
||||
} else {
|
||||
contentLabel.text = cellData?.content
|
||||
case "url":
|
||||
type = .URL
|
||||
contentLabel.text = content
|
||||
default:
|
||||
type = .other
|
||||
contentLabel.text = content
|
||||
}
|
||||
updateButtons()
|
||||
}
|
||||
|
|
@ -76,46 +92,51 @@ class LabelTableViewCell: UITableViewCell {
|
|||
}
|
||||
|
||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||
if isPasswordCell {
|
||||
switch type {
|
||||
case .password:
|
||||
if isReveal {
|
||||
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.concealPassword(_:))
|
||||
return action == #selector(copy(_:)) || action == #selector(concealPassword(_:))
|
||||
} else {
|
||||
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.revealPassword(_:))
|
||||
return action == #selector(copy(_:)) || action == #selector(revealPassword(_:))
|
||||
}
|
||||
}
|
||||
if isURLCell {
|
||||
return action == #selector(copy(_:)) || action == #selector(LabelTableViewCell.openLink(_:))
|
||||
}
|
||||
if isHOTPCell {
|
||||
case .URL:
|
||||
return action == #selector(copy(_:)) || action == #selector(openLink(_:))
|
||||
case .HOTP:
|
||||
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 {
|
||||
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?) {
|
||||
Utils.copyToPasteboard(textToCopy: cellData?.content)
|
||||
}
|
||||
|
||||
func revealPassword(_ sender: Any?) {
|
||||
if let plainPassword = cellData?.content {
|
||||
if isHOTPCell {
|
||||
contentLabel.text = plainPassword
|
||||
} else {
|
||||
contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
|
||||
}
|
||||
let plainPassword = cellData?.content ?? ""
|
||||
if type == .password {
|
||||
contentLabel.attributedText = Utils.attributedPassword(plainPassword: plainPassword)
|
||||
} else {
|
||||
contentLabel.text = ""
|
||||
contentLabel.text = plainPassword
|
||||
}
|
||||
isReveal = true
|
||||
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal)
|
||||
}
|
||||
|
||||
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
|
||||
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
|
||||
}
|
||||
|
|
@ -130,7 +151,6 @@ class LabelTableViewCell: UITableViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
func openLink(_ sender: Any?) {
|
||||
// if isURLCell, passwordTableView should not be nil
|
||||
delegatePasswordTableView!.openLink()
|
||||
|
|
@ -141,10 +161,7 @@ class LabelTableViewCell: UITableViewCell {
|
|||
delegatePasswordTableView!.getNextHOTP()
|
||||
}
|
||||
|
||||
func updateButtons() {
|
||||
passwordDisplayButton = nil
|
||||
buttons = nil
|
||||
|
||||
private func updateButtons() {
|
||||
// total width and height of a button
|
||||
let height = min(self.bounds.height, 36.0)
|
||||
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 marginX = max((width - Globals.tableCellButtonSize) / 2, 0.0)
|
||||
|
||||
if isPasswordCell {
|
||||
// password button
|
||||
passwordDisplayButton = UIButton(type: .system)
|
||||
passwordDisplayButton!.frame = CGRect(x: 0, y: 0, width: width, height: height)
|
||||
passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
|
||||
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
|
||||
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
|
||||
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside)
|
||||
buttons = passwordDisplayButton
|
||||
} else if isHOTPCell {
|
||||
switch type {
|
||||
case .password:
|
||||
if let content = cellData?.content, content != "" {
|
||||
// password button
|
||||
passwordDisplayButton = UIButton(type: .system)
|
||||
passwordDisplayButton!.frame = CGRect(x: 0, y: 0, width: width, height: height)
|
||||
passwordDisplayButton!.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
|
||||
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
|
||||
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
|
||||
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside)
|
||||
buttons = passwordDisplayButton
|
||||
}
|
||||
case .HOTP:
|
||||
// hotp button
|
||||
let nextButton = UIButton(type: .system)
|
||||
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!.addSubview(nextButton)
|
||||
buttons!.addSubview(passwordDisplayButton!)
|
||||
default:
|
||||
passwordDisplayButton = nil
|
||||
buttons = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
<?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">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="333" height="52"/>
|
||||
<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="59"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="333" height="51.5"/>
|
||||
<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="58.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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">
|
||||
<rect key="frame" x="16" y="22.5" width="309" height="21"/>
|
||||
<rect key="frame" x="15" y="27" width="303" height="21"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="21" id="pgw-DF-LQa"/>
|
||||
</constraints>
|
||||
|
|
@ -29,15 +29,15 @@
|
|||
<nil key="highlightedColor"/>
|
||||
</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">
|
||||
<rect key="frame" x="16" y="8" width="309" height="11.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<rect key="frame" x="15" y="11" width="303" height="13"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
|
||||
<color key="textColor" red="0.0" green="0.47843137254901957" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<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 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"/>
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
<outlet property="contentLabel" destination="yyr-cF-QN0" id="lhr-oA-ofC"/>
|
||||
<outlet property="titleLabel" destination="Dqz-7n-uEZ" id="Hac-od-A7K"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="27.5" y="58"/>
|
||||
<point key="canvasLocation" x="15.5" y="89.5"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<?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">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<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="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
|
@ -13,28 +13,28 @@
|
|||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<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>
|
||||
<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"/>
|
||||
</constraints>
|
||||
</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">
|
||||
<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"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="category1 > 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"/>
|
||||
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<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="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 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 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 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>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
<?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">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<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>
|
||||
<action selector="handleSliderValueChange:" destination="KGk-i7-Jjw" eventType="valueChanged" id="WwM-ZE-yIB"/>
|
||||
</connections>
|
||||
</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">
|
||||
<rect key="frame" x="281" y="8" width="24" height="57.5"/>
|
||||
<rect key="frame" x="274" y="11" width="24" height="52"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="24" id="tOG-yp-eFw"/>
|
||||
</constraints>
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
<nil key="highlightedColor"/>
|
||||
</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">
|
||||
<rect key="frame" x="16" y="8" width="30" height="57.5"/>
|
||||
<rect key="frame" x="15" y="11" width="30" height="52"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="8Tz-Qo-Mkg"/>
|
||||
</constraints>
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
</subviews>
|
||||
<constraints>
|
||||
<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 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"/>
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
<?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">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="56"/>
|
||||
<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="69"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="55.5"/>
|
||||
<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="68.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<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>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="R1z-fU-Xr2"/>
|
||||
</constraints>
|
||||
<nil key="textColor"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" returnKeyType="next"/>
|
||||
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no"/>
|
||||
</textField>
|
||||
</subviews>
|
||||
<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 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"/>
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<connections>
|
||||
<outlet property="contentTextField" destination="8Ky-UZ-sLu" id="pdS-Nl-e9s"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="34" y="60"/>
|
||||
<point key="canvasLocation" x="34" y="66.5"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ class TextViewTableViewCell: ContentTableViewCell {
|
|||
@IBOutlet weak var contentTextView: UITextView!
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
self.contentTextView.textContainer.lineFragmentPadding = 0
|
||||
self.contentTextView.textContainerInset = .zero
|
||||
}
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
<?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">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<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"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="120" id="Tvq-j8-Nvh"/>
|
||||
|
|
@ -31,9 +31,9 @@
|
|||
</subviews>
|
||||
<constraints>
|
||||
<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 firstAttribute="bottomMargin" secondItem="xHX-Sh-1pR" secondAttribute="bottom" constant="8" 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="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="SRq-7t-Gyr"/>
|
||||
<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" id="gwb-2C-4wp"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
<?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">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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">
|
||||
<rect key="frame" x="16" y="8" width="296" height="15"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<rect key="frame" x="15" y="11" width="290" height="10"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
|
||||
<color key="textColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="21" id="dHY-dw-mFe"/>
|
||||
</constraints>
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<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="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 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"/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue