Update UI to support more customizable password generator

This commit is contained in:
Danny Moesch 2020-02-28 19:05:23 +01:00 committed by Mingshen Sun
parent ff014a5699
commit b84f2dce13
8 changed files with 252 additions and 71 deletions

View file

@ -49,6 +49,8 @@
30697C5321F63E0B0064FCAC /* PasscodeExtensionDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30697C5121F63E0B0064FCAC /* PasscodeExtensionDisplay.swift */; }; 30697C5321F63E0B0064FCAC /* PasscodeExtensionDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30697C5121F63E0B0064FCAC /* PasscodeExtensionDisplay.swift */; };
30697C5421F63E0B0064FCAC /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30697C5221F63E0B0064FCAC /* CredentialProviderViewController.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 */; }; 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 */; }; 3087574F2343E42A00B971A2 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3087574E2343E42A00B971A2 /* Colors.swift */; };
308C273A2279F9CB0016D0E2 /* SearchBarScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302202EE222F14E400555236 /* SearchBarScope.swift */; }; 308C273A2279F9CB0016D0E2 /* SearchBarScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302202EE222F14E400555236 /* SearchBarScope.swift */; };
30A1D29C21AF451E00E2D1F7 /* PasswordGeneratorFlavorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavorTest.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 30A1D2A121B2BC6F00E2D1F7 /* TokenBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBuilder.swift; sourceTree = "<group>"; };
@ -786,6 +790,8 @@
DCFB77A21E500D9C008DE471 /* PasswordDetailTitleTableViewCell.xib */, DCFB77A21E500D9C008DE471 /* PasswordDetailTitleTableViewCell.xib */,
A2802BF71E70813A00879216 /* SliderTableViewCell.swift */, A2802BF71E70813A00879216 /* SliderTableViewCell.swift */,
A2802BF81E70813A00879216 /* SliderTableViewCell.xib */, A2802BF81E70813A00879216 /* SliderTableViewCell.xib */,
306D970D24091CDD006C0E2E /* SwitchTableViewCell.swift */,
306D971124091EE7006C0E2E /* SwitchTableViewCell.xib */,
DC037CB91E4DD47B00609409 /* TextFieldTableViewCell.swift */, DC037CB91E4DD47B00609409 /* TextFieldTableViewCell.swift */,
DC037CBA1E4DD47B00609409 /* TextFieldTableViewCell.xib */, DC037CBA1E4DD47B00609409 /* TextFieldTableViewCell.xib */,
DC037CBD1E4ED4E100609409 /* TextViewTableViewCell.swift */, DC037CBD1E4ED4E100609409 /* TextViewTableViewCell.swift */,
@ -1160,6 +1166,7 @@
A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */, A2802BFA1E70813A00879216 /* SliderTableViewCell.xib in Resources */,
DCFB779F1E4F40C7008DE471 /* FillPasswordTableViewCell.xib in Resources */, DCFB779F1E4F40C7008DE471 /* FillPasswordTableViewCell.xib in Resources */,
DC037CC01E4ED4E100609409 /* TextViewTableViewCell.xib in Resources */, DC037CC01E4ED4E100609409 /* TextViewTableViewCell.xib in Resources */,
306D971224091EE7006C0E2E /* SwitchTableViewCell.xib in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -1423,6 +1430,7 @@
files = ( files = (
DC037CBF1E4ED4E100609409 /* TextViewTableViewCell.swift in Sources */, DC037CBF1E4ED4E100609409 /* TextViewTableViewCell.swift in Sources */,
DCC441541E916382008A90C4 /* SSHKeyArmorImportTableViewController.swift in Sources */, DCC441541E916382008A90C4 /* SSHKeyArmorImportTableViewController.swift in Sources */,
306D970E24091CDD006C0E2E /* SwitchTableViewCell.swift in Sources */,
A2A61C201EEFABAD00CFE063 /* UtilsExtension.swift in Sources */, A2A61C201EEFABAD00CFE063 /* UtilsExtension.swift in Sources */,
DC8963C01E38EEB900828B09 /* SSHKeyUrlImportTableViewController.swift in Sources */, DC8963C01E38EEB900828B09 /* SSHKeyUrlImportTableViewController.swift in Sources */,
3066AD6823EE0D6500F65535 /* PGPKeyImporter.swift in Sources */, 3066AD6823EE0D6500F65535 /* PGPKeyImporter.swift in Sources */,

View file

@ -11,14 +11,29 @@ import SafariServices
import OneTimePassword import OneTimePassword
import passKit import passKit
enum PasswordEditorCellType { enum PasswordEditorCellType: Equatable {
case nameCell, fillPasswordCell, passwordLengthCell, additionsCell, deletePasswordCell, scanQRCodeCell, passwordFlavorCell case nameCell
case fillPasswordCell
case passwordLengthCell
case passwordUseDigitsCell
case passwordVaryCasesCell
case passwordUseSpecialSymbols
case passwordGroupsCell
case additionsCell
case deletePasswordCell
case scanQRCodeCell
case passwordFlavorCell
} }
enum PasswordEditorCellKey { enum PasswordEditorCellKey {
case type, title, content, placeholders case type, title, content, placeholders
} }
protocol PasswordSettingSliderTableViewCellDelegate {
func generateAndCopyPassword()
}
class PasswordEditorTableViewController: UITableViewController { class PasswordEditorTableViewController: UITableViewController {
var tableData = [[Dictionary<PasswordEditorCellKey, Any>]]() var tableData = [[Dictionary<PasswordEditorCellKey, Any>]]()
@ -33,9 +48,10 @@ class PasswordEditorTableViewController: UITableViewController {
private let additionsSection = 2 private let additionsSection = 2
private var hidePasswordSettings = true private var hidePasswordSettings = true
private var passwordGenerator: PasswordGenerator = Defaults.passwordGenerator
var nameCell: TextFieldTableViewCell? var nameCell: TextFieldTableViewCell?
var fillPasswordCell: FillPasswordTableViewCell? var fillPasswordCell: FillPasswordTableViewCell?
private var passwordLengthCell: SliderTableViewCell?
var additionsCell: TextViewTableViewCell? var additionsCell: TextViewTableViewCell?
private var deletePasswordCell: UITableViewCell? private var deletePasswordCell: UITableViewCell?
private var scanQRCodeCell: UITableViewCell? private var scanQRCodeCell: UITableViewCell?
@ -72,7 +88,7 @@ class PasswordEditorTableViewController: UITableViewController {
passwordFlavorCell?.textLabel?.textColor = Colors.systemBlue passwordFlavorCell?.textLabel?.textColor = Colors.systemBlue
passwordFlavorCell?.selectionStyle = .none passwordFlavorCell?.selectionStyle = .none
passwordFlavorCell?.accessoryType = .disclosureIndicator passwordFlavorCell?.accessoryType = .disclosureIndicator
passwordFlavorCell?.detailTextLabel?.text = Defaults.passwordGeneratorFlavor.localized passwordFlavorCell?.detailTextLabel?.text = passwordGenerator.flavor.localized
} }
override func viewDidLoad() { override func viewDidLoad() {
@ -84,7 +100,8 @@ class PasswordEditorTableViewController: UITableViewController {
tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell") tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell")
tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell") tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell")
tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell") tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell")
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.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 48 tableView.estimatedRowHeight = 48
@ -97,7 +114,10 @@ class PasswordEditorTableViewController: UITableViewController {
], ],
[ [
[.type: PasswordEditorCellType.fillPasswordCell, .title: "Password".localize(), .content: password?.password ?? ""], [.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], [.type: PasswordEditorCellType.passwordFlavorCell],
], ],
[ [
@ -108,6 +128,7 @@ class PasswordEditorTableViewController: UITableViewController {
[.type: PasswordEditorCellType.deletePasswordCell], [.type: PasswordEditorCellType.deletePasswordCell],
] ]
] ]
updateTableData(withRespectTo: passwordGenerator.flavor)
} }
override func viewDidLayoutSubviews() { override func viewDidLayoutSubviews() {
@ -133,26 +154,43 @@ class PasswordEditorTableViewController: UITableViewController {
} }
return fillPasswordCell! return fillPasswordCell!
case .passwordLengthCell: case .passwordLengthCell:
passwordLengthCell = tableView.dequeueReusableCell(withIdentifier: "passwordLengthCell", for: indexPath) as? SliderTableViewCell return (tableView.dequeueReusableCell(withIdentifier: "sliderCell", for: indexPath) as! SliderTableViewCell)
let lengthSetting = Defaults.passwordGeneratorFlavor.defaultLength .set(title: "Length".localize())
let minimumLength = lengthSetting.min .configureSlider(with: passwordGenerator.flavor.lengthLimits)
let maximumLength = lengthSetting.max .set(initialValue: passwordGenerator.limitedLength)
var defaultLength = lengthSetting.def .checkNewValue { $0 != self.passwordGenerator.length }
if let currentPasswordLength = (tableData[passwordSection][0][PasswordEditorCellKey.content] as? String)?.count, .updateNewValue { self.passwordGenerator.length = $0 }
currentPasswordLength >= minimumLength, .delegate(to: self)
currentPasswordLength <= maximumLength { case .passwordUseDigitsCell:
defaultLength = currentPasswordLength return (tableView.dequeueReusableCell(withIdentifier: "switchCell", for: indexPath) as! SwitchTableViewCell)
} .set(title: "Digits".localize())
passwordLengthCell?.reset(title: "Length".localize(), .set(initialValue: passwordGenerator.useDigits)
minimumValue: minimumLength, .updateNewValue { self.passwordGenerator.useDigits = $0 }
maximumValue: maximumLength, .delegate(to: self)
defaultValue: defaultLength) case .passwordVaryCasesCell:
passwordLengthCell?.delegate = self return (tableView.dequeueReusableCell(withIdentifier: "switchCell", for: indexPath) as! SwitchTableViewCell)
return passwordLengthCell! .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: case .passwordFlavorCell:
return passwordFlavorCell! return passwordFlavorCell!
case .additionsCell: case .additionsCell:
additionsCell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as?TextViewTableViewCell additionsCell = tableView.dequeueReusableCell(withIdentifier: "textViewCell", for: indexPath) as? TextViewTableViewCell
additionsCell?.contentTextView.delegate = self additionsCell?.contentTextView.delegate = self
additionsCell?.setContent(content: cellData[PasswordEditorCellKey.content] as? String) additionsCell?.setContent(content: cellData[PasswordEditorCellKey.content] as? String)
additionsCell?.contentTextView.textColor = Colors.label additionsCell?.contentTextView.textColor = Colors.label
@ -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) { private func showPasswordGeneratorFlavorActionSheet(sourceCell: UITableViewCell, tableView: UITableView) {
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
PasswordGeneratorFlavor.allCases.forEach { flavor in PasswordGeneratorFlavor.allCases.forEach { flavor in
var actionTitle = flavor.longNameLocalized var actionTitle = flavor.longNameLocalized
if Defaults.passwordGeneratorFlavor == flavor { if passwordGenerator.flavor == flavor {
actionTitle = "" + actionTitle actionTitle = "" + actionTitle
} }
let action = UIAlertAction(title: actionTitle, style: .default) { _ in let action = UIAlertAction(title: actionTitle, style: .default) { _ in
Defaults.passwordGeneratorFlavor = flavor guard self.passwordGenerator.flavor != flavor else {
sourceCell.detailTextLabel?.text = Defaults.passwordGeneratorFlavor.localized return
}
self.passwordGenerator.flavor = flavor
sourceCell.detailTextLabel?.text = self.passwordGenerator.flavor.localized
self.updateTableData(withRespectTo: flavor)
tableView.reloadSections([self.passwordSection], with: .none) tableView.reloadSections([self.passwordSection], with: .none)
} }
optionMenu.addAction(action) optionMenu.addAction(action)
@ -230,13 +277,29 @@ class PasswordEditorTableViewController: UITableViewController {
self.present(optionMenu, animated: true, completion: nil) 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 // generate the password, don't care whether the original line is otp
private func generateAndCopyPasswordNoOtpCheck() { private func generateAndCopyPasswordNoOtpCheck() {
// show password settings (e.g., the length slider) // show password settings (e.g., the length slider)
showPasswordSettings() showPasswordSettings()
let length = passwordLengthCell?.roundedValue ?? 0 let plainPassword = passwordGenerator.generate()
let plainPassword = Defaults.passwordGeneratorFlavor.generate(length: length)
// update tableData so to make sure reloadData() works correctly // update tableData so to make sure reloadData() works correctly
tableData[passwordSection][0][PasswordEditorCellKey.content] = plainPassword tableData[passwordSection][0][PasswordEditorCellKey.content] = plainPassword

View file

@ -6,59 +6,71 @@
// Copyright © 2017 Yishi Lin. All rights reserved. // Copyright © 2017 Yishi Lin. All rights reserved.
// //
import passKit
import UIKit import UIKit
protocol PasswordSettingSliderTableViewCellDelegate { class SliderTableViewCell: UITableViewCell {
func generateAndCopyPassword()
}
class SliderTableViewCell: UITableViewCell, ContentProvider { @IBOutlet var titleLabel: UILabel!
@IBOutlet var valueLabel: UILabel!
@IBOutlet var slider: UISlider!
@IBOutlet weak var titleLabel: UILabel! private var checker: ((Int) -> Bool)!
@IBOutlet weak var valueLabel: UILabel! private var updater: ((Int) -> Void)!
@IBOutlet weak var slider: UISlider!
var delegate: UITableViewController? private var delegate: PasswordSettingSliderTableViewCellDelegate!
var roundedValue: Int {
get {
return Int(valueLabel.text!)!
}
}
@IBAction func handleSliderValueChange(_ sender: UISlider) { @IBAction func handleSliderValueChange(_ sender: UISlider) {
let oldRoundedValue = self.roundedValue
let newRoundedValue = Int(sender.value) let newRoundedValue = Int(sender.value)
// proceed only when the rounded value gets updated // Proceed only if the rounded value gets updated.
guard newRoundedValue != oldRoundedValue else { guard checker(newRoundedValue) else {
return; return
} }
sender.value = Float(newRoundedValue) sender.value = Float(newRoundedValue)
valueLabel.text = "\(newRoundedValue)" valueLabel.text = "\(newRoundedValue)"
if let delegate: PasswordSettingSliderTableViewCellDelegate = self.delegate as? PasswordSettingSliderTableViewCellDelegate {
delegate.generateAndCopyPassword() updater(newRoundedValue)
} delegate.generateAndCopyPassword()
} }
func reset(title: String, minimumValue: Int, maximumValue: Int, defaultValue: Int) { func set(title: String) -> SliderTableViewCell {
titleLabel.text = title titleLabel.text = title
slider.minimumValue = Float(minimumValue) return self
slider.maximumValue = Float(maximumValue)
slider.value = Float(defaultValue)
valueLabel.text = String(defaultValue)
// "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? { func getContent() -> String? {
return nil return nil
} }
func setContent(content: String?) {} func setContent(content _: String?) {}
} }

View 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
}
}

View 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>

View file

@ -255,8 +255,11 @@
"UseKeyValueFormat." = "Verwende das Format \"Name: Inhalt\" für zusätzliche Felder."; "UseKeyValueFormat." = "Verwende das Format \"Name: Inhalt\" für zusätzliche Felder.";
"DeletePassword" = "Passwort löschen"; "DeletePassword" = "Passwort löschen";
"AddOneTimePassword" = "Einmalkennwort hinzufügen"; "AddOneTimePassword" = "Einmalkennwort hinzufügen";
"GetMemorableOne" = "Einprägsames Passwort: xkpasswd";
"Length" = "Länge"; "Length" = "Länge";
"VaryCases" = "Groß- und Kleinbuchstaben";
"Digits" = "Ziffern";
"SpecialSymbols" = "Sonderzeichen";
"Groups" = "Bilde Gruppen";
"DeletePassword?" = "Passwort löschen?"; "DeletePassword?" = "Passwort löschen?";
"OverwriteOtpConfiguration?" = "Konfiguration des Einmalkennwortes überschreiben?"; "OverwriteOtpConfiguration?" = "Konfiguration des Einmalkennwortes überschreiben?";
"ValidTokenUrl" = "Gültiges URL-Token"; "ValidTokenUrl" = "Gültiges URL-Token";

View file

@ -255,8 +255,11 @@
"UseKeyValueFormat." = "Use \"key: value\" format for additional fields."; "UseKeyValueFormat." = "Use \"key: value\" format for additional fields.";
"DeletePassword" = "Delete Password"; "DeletePassword" = "Delete Password";
"AddOneTimePassword" = "Add One-Time Password"; "AddOneTimePassword" = "Add One-Time Password";
"GetMemorableOne" = "Get a Memorable One: xkpasswd";
"Length" = "Length"; "Length" = "Length";
"VaryCases" = "Random Capitalization";
"Digits" = "Digits";
"SpecialSymbols" = "Special Symbols";
"Groups" = "Arrange Into Groups";
"DeletePassword?" = "Delete Password?"; "DeletePassword?" = "Delete Password?";
"OverwriteOtpConfiguration?" = "Overwrite the one-time password configuration?"; "OverwriteOtpConfiguration?" = "Overwrite the one-time password configuration?";
"ValidTokenUrl" = "Valid token URL"; "ValidTokenUrl" = "Valid token URL";

View file

@ -35,11 +35,7 @@ public struct Colors {
return .init(red: 242.0, green: 242.0, blue: 247.0, alpha: 1.0) return .init(red: 242.0, green: 242.0, blue: 247.0, alpha: 1.0)
}() }()
public static let systemRed: UIColor = { public static let systemRed = UIColor.systemRed
return .systemRed
}()
public static let systemBlue: UIColor = { public static let systemBlue = UIColor.systemBlue
return .systemBlue
}()
} }