Update UI to support more customizable password generator
This commit is contained in:
parent
ff014a5699
commit
b84f2dce13
8 changed files with 252 additions and 71 deletions
|
|
@ -49,6 +49,8 @@
|
|||
30697C5321F63E0B0064FCAC /* PasscodeExtensionDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30697C5121F63E0B0064FCAC /* PasscodeExtensionDisplay.swift */; };
|
||||
30697C5421F63E0B0064FCAC /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30697C5221F63E0B0064FCAC /* CredentialProviderViewController.swift */; };
|
||||
30697C5F21F674800064FCAC /* String+UtilitiesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30697C5E21F674800064FCAC /* String+UtilitiesTest.swift */; };
|
||||
306D970E24091CDD006C0E2E /* SwitchTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306D970D24091CDD006C0E2E /* SwitchTableViewCell.swift */; };
|
||||
306D971224091EE7006C0E2E /* SwitchTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 306D971124091EE7006C0E2E /* SwitchTableViewCell.xib */; };
|
||||
3087574F2343E42A00B971A2 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3087574E2343E42A00B971A2 /* Colors.swift */; };
|
||||
308C273A2279F9CB0016D0E2 /* SearchBarScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302202EE222F14E400555236 /* SearchBarScope.swift */; };
|
||||
30A1D29C21AF451E00E2D1F7 /* PasswordGeneratorFlavorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavorTest.swift */; };
|
||||
|
|
@ -278,6 +280,8 @@
|
|||
30697C5121F63E0B0064FCAC /* PasscodeExtensionDisplay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeExtensionDisplay.swift; sourceTree = "<group>"; };
|
||||
30697C5221F63E0B0064FCAC /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = "<group>"; };
|
||||
30697C5E21F674800064FCAC /* String+UtilitiesTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+UtilitiesTest.swift"; sourceTree = "<group>"; };
|
||||
306D970D24091CDD006C0E2E /* SwitchTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchTableViewCell.swift; sourceTree = "<group>"; };
|
||||
306D971124091EE7006C0E2E /* SwitchTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SwitchTableViewCell.xib; sourceTree = "<group>"; };
|
||||
3087574E2343E42A00B971A2 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||
30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGeneratorFlavorTest.swift; sourceTree = "<group>"; };
|
||||
30A1D2A121B2BC6F00E2D1F7 /* TokenBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBuilder.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -786,6 +790,8 @@
|
|||
DCFB77A21E500D9C008DE471 /* PasswordDetailTitleTableViewCell.xib */,
|
||||
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */,
|
||||
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */,
|
||||
306D970D24091CDD006C0E2E /* SwitchTableViewCell.swift */,
|
||||
306D971124091EE7006C0E2E /* SwitchTableViewCell.xib */,
|
||||
DC037CB91E4DD47B00609409 /* TextFieldTableViewCell.swift */,
|
||||
DC037CBA1E4DD47B00609409 /* TextFieldTableViewCell.xib */,
|
||||
DC037CBD1E4ED4E100609409 /* TextViewTableViewCell.swift */,
|
||||
|
|
@ -1160,6 +1166,7 @@
|
|||
A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */,
|
||||
DCFB779F1E4F40C7008DE471 /* FillPasswordTableViewCell.xib in Resources */,
|
||||
DC037CC01E4ED4E100609409 /* TextViewTableViewCell.xib in Resources */,
|
||||
306D971224091EE7006C0E2E /* SwitchTableViewCell.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -1423,6 +1430,7 @@
|
|||
files = (
|
||||
DC037CBF1E4ED4E100609409 /* TextViewTableViewCell.swift in Sources */,
|
||||
DCC441541E916382008A90C4 /* SSHKeyArmorImportTableViewController.swift in Sources */,
|
||||
306D970E24091CDD006C0E2E /* SwitchTableViewCell.swift in Sources */,
|
||||
A2A61C201EEFABAD00CFE063 /* UtilsExtension.swift in Sources */,
|
||||
DC8963C01E38EEB900828B09 /* SSHKeyUrlImportTableViewController.swift in Sources */,
|
||||
3066AD6823EE0D6500F65535 /* PGPKeyImporter.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -11,14 +11,29 @@ import SafariServices
|
|||
import OneTimePassword
|
||||
import passKit
|
||||
|
||||
enum PasswordEditorCellType {
|
||||
case nameCell, fillPasswordCell, passwordLengthCell, additionsCell, deletePasswordCell, scanQRCodeCell, passwordFlavorCell
|
||||
enum PasswordEditorCellType: Equatable {
|
||||
case nameCell
|
||||
case fillPasswordCell
|
||||
case passwordLengthCell
|
||||
case passwordUseDigitsCell
|
||||
case passwordVaryCasesCell
|
||||
case passwordUseSpecialSymbols
|
||||
case passwordGroupsCell
|
||||
case additionsCell
|
||||
case deletePasswordCell
|
||||
case scanQRCodeCell
|
||||
case passwordFlavorCell
|
||||
}
|
||||
|
||||
enum PasswordEditorCellKey {
|
||||
case type, title, content, placeholders
|
||||
}
|
||||
|
||||
protocol PasswordSettingSliderTableViewCellDelegate {
|
||||
|
||||
func generateAndCopyPassword()
|
||||
}
|
||||
|
||||
class PasswordEditorTableViewController: UITableViewController {
|
||||
|
||||
var tableData = [[Dictionary<PasswordEditorCellKey, Any>]]()
|
||||
|
|
@ -33,9 +48,10 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
private let additionsSection = 2
|
||||
private var hidePasswordSettings = true
|
||||
|
||||
private var passwordGenerator: PasswordGenerator = Defaults.passwordGenerator
|
||||
|
||||
var nameCell: TextFieldTableViewCell?
|
||||
var fillPasswordCell: FillPasswordTableViewCell?
|
||||
private var passwordLengthCell: SliderTableViewCell?
|
||||
var additionsCell: TextViewTableViewCell?
|
||||
private var deletePasswordCell: UITableViewCell?
|
||||
private var scanQRCodeCell: UITableViewCell?
|
||||
|
|
@ -72,7 +88,7 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
passwordFlavorCell?.textLabel?.textColor = Colors.systemBlue
|
||||
passwordFlavorCell?.selectionStyle = .none
|
||||
passwordFlavorCell?.accessoryType = .disclosureIndicator
|
||||
passwordFlavorCell?.detailTextLabel?.text = Defaults.passwordGeneratorFlavor.localized
|
||||
passwordFlavorCell?.detailTextLabel?.text = passwordGenerator.flavor.localized
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -84,7 +100,8 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
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")
|
||||
tableView.register(UINib(nibName: "SliderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordLengthCell")
|
||||
tableView.register(UINib(nibName: "SliderTableViewCell", bundle: nil), forCellReuseIdentifier: "sliderCell")
|
||||
tableView.register(UINib(nibName: "SwitchTableViewCell", bundle: nil), forCellReuseIdentifier: "switchCell")
|
||||
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.estimatedRowHeight = 48
|
||||
|
|
@ -97,7 +114,10 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
],
|
||||
[
|
||||
[.type: PasswordEditorCellType.fillPasswordCell, .title: "Password".localize(), .content: password?.password ?? ""],
|
||||
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"],
|
||||
[.type: PasswordEditorCellType.passwordLengthCell],
|
||||
[.type: PasswordEditorCellType.passwordUseDigitsCell],
|
||||
[.type: PasswordEditorCellType.passwordVaryCasesCell],
|
||||
[.type: PasswordEditorCellType.passwordUseSpecialSymbols],
|
||||
[.type: PasswordEditorCellType.passwordFlavorCell],
|
||||
],
|
||||
[
|
||||
|
|
@ -108,6 +128,7 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
[.type: PasswordEditorCellType.deletePasswordCell],
|
||||
]
|
||||
]
|
||||
updateTableData(withRespectTo: passwordGenerator.flavor)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
|
|
@ -133,22 +154,39 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
}
|
||||
return fillPasswordCell!
|
||||
case .passwordLengthCell:
|
||||
passwordLengthCell = tableView.dequeueReusableCell(withIdentifier: "passwordLengthCell", for: indexPath) as? SliderTableViewCell
|
||||
let lengthSetting = Defaults.passwordGeneratorFlavor.defaultLength
|
||||
let minimumLength = lengthSetting.min
|
||||
let maximumLength = lengthSetting.max
|
||||
var defaultLength = lengthSetting.def
|
||||
if let currentPasswordLength = (tableData[passwordSection][0][PasswordEditorCellKey.content] as? String)?.count,
|
||||
currentPasswordLength >= minimumLength,
|
||||
currentPasswordLength <= maximumLength {
|
||||
defaultLength = currentPasswordLength
|
||||
}
|
||||
passwordLengthCell?.reset(title: "Length".localize(),
|
||||
minimumValue: minimumLength,
|
||||
maximumValue: maximumLength,
|
||||
defaultValue: defaultLength)
|
||||
passwordLengthCell?.delegate = self
|
||||
return passwordLengthCell!
|
||||
return (tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderTableViewCell)
|
||||
.set(title: "Length".localize())
|
||||
.configureSlider(with: passwordGenerator.flavor.lengthLimits)
|
||||
.set(initialValue: passwordGenerator.limitedLength)
|
||||
.checkNewValue { $0 != self.passwordGenerator.length }
|
||||
.updateNewValue { self.passwordGenerator.length = $0 }
|
||||
.delegate(to: self)
|
||||
case .passwordUseDigitsCell:
|
||||
return (tableView.dequeueReusableCell(withIdentifier: "switchCell", for: indexPath) as! SwitchTableViewCell)
|
||||
.set(title: "Digits".localize())
|
||||
.set(initialValue: passwordGenerator.useDigits)
|
||||
.updateNewValue { self.passwordGenerator.useDigits = $0 }
|
||||
.delegate(to: self)
|
||||
case .passwordVaryCasesCell:
|
||||
return (tableView.dequeueReusableCell(withIdentifier: "switchCell", for: indexPath) as! SwitchTableViewCell)
|
||||
.set(title: "VaryCases".localize())
|
||||
.set(initialValue: passwordGenerator.varyCases)
|
||||
.updateNewValue { self.passwordGenerator.varyCases = $0 }
|
||||
.delegate(to: self)
|
||||
case .passwordUseSpecialSymbols:
|
||||
return (tableView.dequeueReusableCell(withIdentifier: "switchCell", for: indexPath) as! SwitchTableViewCell)
|
||||
.set(title: "SpecialSymbols".localize())
|
||||
.set(initialValue: passwordGenerator.useSpecialSymbols)
|
||||
.updateNewValue { self.passwordGenerator.useSpecialSymbols = $0 }
|
||||
.delegate(to: self)
|
||||
case .passwordGroupsCell:
|
||||
return (tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderTableViewCell)
|
||||
.set(title: "Groups".localize())
|
||||
.configureSlider(with: (min: 0, max: 6))
|
||||
.set(initialValue: passwordGenerator.groups)
|
||||
.checkNewValue { $0 != self.passwordGenerator.groups && self.passwordGenerator.isAcceptable(groups: $0) }
|
||||
.updateNewValue { self.passwordGenerator.groups = $0 }
|
||||
.delegate(to: self)
|
||||
case .passwordFlavorCell:
|
||||
return passwordFlavorCell!
|
||||
case .additionsCell:
|
||||
|
|
@ -207,17 +245,26 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
Defaults.passwordGenerator = passwordGenerator
|
||||
}
|
||||
|
||||
private func showPasswordGeneratorFlavorActionSheet(sourceCell: UITableViewCell, tableView: UITableView) {
|
||||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
|
||||
PasswordGeneratorFlavor.allCases.forEach { flavor in
|
||||
var actionTitle = flavor.longNameLocalized
|
||||
if Defaults.passwordGeneratorFlavor == flavor {
|
||||
if passwordGenerator.flavor == flavor {
|
||||
actionTitle = "✓ " + actionTitle
|
||||
}
|
||||
let action = UIAlertAction(title: actionTitle, style: .default) { _ in
|
||||
Defaults.passwordGeneratorFlavor = flavor
|
||||
sourceCell.detailTextLabel?.text = Defaults.passwordGeneratorFlavor.localized
|
||||
guard self.passwordGenerator.flavor != flavor else {
|
||||
return
|
||||
}
|
||||
self.passwordGenerator.flavor = flavor
|
||||
sourceCell.detailTextLabel?.text = self.passwordGenerator.flavor.localized
|
||||
self.updateTableData(withRespectTo: flavor)
|
||||
tableView.reloadSections([self.passwordSection], with: .none)
|
||||
}
|
||||
optionMenu.addAction(action)
|
||||
|
|
@ -230,13 +277,29 @@ class PasswordEditorTableViewController: UITableViewController {
|
|||
self.present(optionMenu, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
private func updateTableData(withRespectTo flavor: PasswordGeneratorFlavor) {
|
||||
// Remove delimiter configuration for XKCD style passwords. Re-add it for random ones.
|
||||
switch flavor {
|
||||
case .random:
|
||||
guard tableData[1].first(where: isPasswordDelimiterCellData) == nil else {
|
||||
return
|
||||
}
|
||||
tableData[1].insert([.type: PasswordEditorCellType.passwordGroupsCell], at: tableData[1].endIndex - 1)
|
||||
case .xkcd:
|
||||
tableData[1].removeAll(where: isPasswordDelimiterCellData)
|
||||
}
|
||||
}
|
||||
|
||||
private func isPasswordDelimiterCellData(data: Dictionary<PasswordEditorCellKey, Any>) -> Bool {
|
||||
return (data[.type] as? PasswordEditorCellType) == .some(.passwordGroupsCell)
|
||||
}
|
||||
|
||||
// generate the password, don't care whether the original line is otp
|
||||
private func generateAndCopyPasswordNoOtpCheck() {
|
||||
// show password settings (e.g., the length slider)
|
||||
showPasswordSettings()
|
||||
|
||||
let length = passwordLengthCell?.roundedValue ?? 0
|
||||
let plainPassword = Defaults.passwordGeneratorFlavor.generate(length: length)
|
||||
let plainPassword = passwordGenerator.generate()
|
||||
|
||||
// update tableData so to make sure reloadData() works correctly
|
||||
tableData[passwordSection][0][PasswordEditorCellKey.content] = plainPassword
|
||||
|
|
|
|||
|
|
@ -6,59 +6,71 @@
|
|||
// Copyright © 2017 Yishi Lin. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
protocol PasswordSettingSliderTableViewCellDelegate {
|
||||
func generateAndCopyPassword()
|
||||
}
|
||||
class SliderTableViewCell: UITableViewCell {
|
||||
|
||||
class SliderTableViewCell: UITableViewCell, ContentProvider {
|
||||
@IBOutlet var titleLabel: UILabel!
|
||||
@IBOutlet var valueLabel: UILabel!
|
||||
@IBOutlet var slider: UISlider!
|
||||
|
||||
@IBOutlet weak var titleLabel: UILabel!
|
||||
@IBOutlet weak var valueLabel: UILabel!
|
||||
@IBOutlet weak var slider: UISlider!
|
||||
private var checker: ((Int) -> Bool)!
|
||||
private var updater: ((Int) -> Void)!
|
||||
|
||||
var delegate: UITableViewController?
|
||||
|
||||
var roundedValue: Int {
|
||||
get {
|
||||
return Int(valueLabel.text!)!
|
||||
}
|
||||
}
|
||||
private var delegate: PasswordSettingSliderTableViewCellDelegate!
|
||||
|
||||
@IBAction func handleSliderValueChange(_ sender: UISlider) {
|
||||
let oldRoundedValue = self.roundedValue
|
||||
let newRoundedValue = Int(sender.value)
|
||||
// proceed only when the rounded value gets updated
|
||||
guard newRoundedValue != oldRoundedValue else {
|
||||
return;
|
||||
// Proceed only if the rounded value gets updated.
|
||||
guard checker(newRoundedValue) else {
|
||||
return
|
||||
}
|
||||
sender.value = Float(newRoundedValue)
|
||||
valueLabel.text = "\(newRoundedValue)"
|
||||
if let delegate: PasswordSettingSliderTableViewCellDelegate = self.delegate as? PasswordSettingSliderTableViewCellDelegate {
|
||||
|
||||
updater(newRoundedValue)
|
||||
delegate.generateAndCopyPassword()
|
||||
}
|
||||
}
|
||||
|
||||
func reset(title: String, minimumValue: Int, maximumValue: Int, defaultValue: Int) {
|
||||
func set(title: String) -> SliderTableViewCell {
|
||||
titleLabel.text = title
|
||||
slider.minimumValue = Float(minimumValue)
|
||||
slider.maximumValue = Float(maximumValue)
|
||||
slider.value = Float(defaultValue)
|
||||
valueLabel.text = String(defaultValue)
|
||||
return self
|
||||
}
|
||||
|
||||
// "not editable"
|
||||
if minimumValue == maximumValue {
|
||||
titleLabel.textColor = UIColor.gray
|
||||
valueLabel.textColor = UIColor.gray
|
||||
slider.isUserInteractionEnabled = false
|
||||
func configureSlider(with configuration: LengthLimits) -> SliderTableViewCell {
|
||||
slider.minimumValue = Float(configuration.min)
|
||||
slider.maximumValue = Float(configuration.max)
|
||||
return self
|
||||
}
|
||||
|
||||
func set(initialValue: Int) -> SliderTableViewCell {
|
||||
slider.value = Float(initialValue)
|
||||
valueLabel.text = String(initialValue)
|
||||
return self
|
||||
}
|
||||
|
||||
func checkNewValue(with checker: @escaping (Int) -> Bool) -> SliderTableViewCell {
|
||||
self.checker = checker
|
||||
return self
|
||||
}
|
||||
|
||||
func updateNewValue(using updater: @escaping (Int) -> Void) -> SliderTableViewCell {
|
||||
self.updater = updater
|
||||
return self
|
||||
}
|
||||
|
||||
func delegate(to delegate: PasswordSettingSliderTableViewCellDelegate) -> SliderTableViewCell {
|
||||
self.delegate = delegate
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension SliderTableViewCell: ContentProvider {
|
||||
|
||||
func getContent() -> String? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setContent(content: String?) {}
|
||||
func setContent(content _: String?) {}
|
||||
}
|
||||
|
|
|
|||
45
pass/Views/SwitchTableViewCell.swift
Normal file
45
pass/Views/SwitchTableViewCell.swift
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// SwitchTableViewCell.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Danny Moesch on 28.02.20.
|
||||
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import passKit
|
||||
import UIKit
|
||||
|
||||
class SwitchTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet var titleLabel: UILabel!
|
||||
@IBOutlet var controlSwitch: UISwitch!
|
||||
|
||||
private var updater: ((Bool) -> Void)!
|
||||
|
||||
private var delegate: PasswordSettingSliderTableViewCellDelegate!
|
||||
|
||||
@IBAction func switchValueChanged(_: Any) {
|
||||
updater(controlSwitch.isOn)
|
||||
delegate.generateAndCopyPassword()
|
||||
}
|
||||
|
||||
func set(title: String) -> SwitchTableViewCell {
|
||||
titleLabel.text = title
|
||||
return self
|
||||
}
|
||||
|
||||
func set(initialValue: Bool) -> SwitchTableViewCell {
|
||||
controlSwitch.isOn = initialValue
|
||||
return self
|
||||
}
|
||||
|
||||
func updateNewValue(using updater: @escaping (Bool) -> Void) -> SwitchTableViewCell {
|
||||
self.updater = updater
|
||||
return self
|
||||
}
|
||||
|
||||
func delegate(to delegate: PasswordSettingSliderTableViewCellDelegate) -> SwitchTableViewCell {
|
||||
self.delegate = delegate
|
||||
return self
|
||||
}
|
||||
}
|
||||
51
pass/Views/SwitchTableViewCell.xib
Normal file
51
pass/Views/SwitchTableViewCell.xib
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<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" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="71" id="ZfB-GH-J5H" customClass="SwitchTableViewCell" 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" preservesSuperviewLayoutMargins="YES" tableViewCell="ZfB-GH-J5H" id="yOK-hg-p5T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="74"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZcB-yD-ZwW" userLabel="Title">
|
||||
<rect key="frame" x="15" y="11" width="26.5" height="52"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="KbB-1u-gvz"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uza-Tn-Tvb">
|
||||
<rect key="frame" x="263" y="20" width="51" height="34"/>
|
||||
<connections>
|
||||
<action selector="switchValueChanged:" destination="ZfB-GH-J5H" eventType="valueChanged" id="7M4-rU-jkI"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="uza-Tn-Tvb" firstAttribute="trailing" secondItem="yOK-hg-p5T" secondAttribute="trailingMargin" constant="7" id="AI4-cq-01M"/>
|
||||
<constraint firstAttribute="bottom" secondItem="uza-Tn-Tvb" secondAttribute="bottom" constant="20" symbolic="YES" id="JHm-gr-hAx"/>
|
||||
<constraint firstItem="uza-Tn-Tvb" firstAttribute="top" secondItem="yOK-hg-p5T" secondAttribute="top" constant="20" symbolic="YES" id="YPW-8k-up9"/>
|
||||
<constraint firstItem="ZcB-yD-ZwW" firstAttribute="leading" secondItem="yOK-hg-p5T" secondAttribute="leadingMargin" id="lFa-jJ-Jc7"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="ZcB-yD-ZwW" secondAttribute="bottom" id="nMt-uF-ECF"/>
|
||||
<constraint firstItem="ZcB-yD-ZwW" firstAttribute="top" secondItem="yOK-hg-p5T" secondAttribute="topMargin" id="wJh-hO-b3q"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="controlSwitch" destination="uza-Tn-Tvb" id="XcC-iD-dpH"/>
|
||||
<outlet property="titleLabel" destination="ZcB-yD-ZwW" id="be8-Ec-Iu5"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-106" y="64"/>
|
||||
</tableViewCell>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
@ -255,8 +255,11 @@
|
|||
"UseKeyValueFormat." = "Verwende das Format \"Name: Inhalt\" für zusätzliche Felder.";
|
||||
"DeletePassword" = "Passwort löschen";
|
||||
"AddOneTimePassword" = "Einmalkennwort hinzufügen";
|
||||
"GetMemorableOne" = "Einprägsames Passwort: xkpasswd";
|
||||
"Length" = "Länge";
|
||||
"VaryCases" = "Groß- und Kleinbuchstaben";
|
||||
"Digits" = "Ziffern";
|
||||
"SpecialSymbols" = "Sonderzeichen";
|
||||
"Groups" = "Bilde Gruppen";
|
||||
"DeletePassword?" = "Passwort löschen?";
|
||||
"OverwriteOtpConfiguration?" = "Konfiguration des Einmalkennwortes überschreiben?";
|
||||
"ValidTokenUrl" = "Gültiges URL-Token";
|
||||
|
|
|
|||
|
|
@ -255,8 +255,11 @@
|
|||
"UseKeyValueFormat." = "Use \"key: value\" format for additional fields.";
|
||||
"DeletePassword" = "Delete Password";
|
||||
"AddOneTimePassword" = "Add One-Time Password";
|
||||
"GetMemorableOne" = "Get a Memorable One: xkpasswd";
|
||||
"Length" = "Length";
|
||||
"VaryCases" = "Random Capitalization";
|
||||
"Digits" = "Digits";
|
||||
"SpecialSymbols" = "Special Symbols";
|
||||
"Groups" = "Arrange Into Groups";
|
||||
"DeletePassword?" = "Delete Password?";
|
||||
"OverwriteOtpConfiguration?" = "Overwrite the one-time password configuration?";
|
||||
"ValidTokenUrl" = "Valid token URL";
|
||||
|
|
|
|||
|
|
@ -35,11 +35,7 @@ public struct Colors {
|
|||
return .init(red: 242.0, green: 242.0, blue: 247.0, alpha: 1.0)
|
||||
}()
|
||||
|
||||
public static let systemRed: UIColor = {
|
||||
return .systemRed
|
||||
}()
|
||||
public static let systemRed = UIColor.systemRed
|
||||
|
||||
public static let systemBlue: UIColor = {
|
||||
return .systemBlue
|
||||
}()
|
||||
public static let systemBlue = UIColor.systemBlue
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue