Add "Edit" item to rename host profile
Disabled in network profiles. Reuse same title style/constraints and message strings in host wizard. For consistency, rename activate() to activateProfile(). And it's not even an IBAction.
This commit is contained in:
parent
56c0a1a15e
commit
b051f8118f
|
@ -36,15 +36,16 @@ class Macros {
|
|||
}
|
||||
|
||||
extension UIAlertController {
|
||||
func addDefaultAction(_ title: String, handler: @escaping () -> Void) {
|
||||
@discardableResult func addDefaultAction(_ title: String, handler: @escaping () -> Void) -> UIAlertAction {
|
||||
let action = UIAlertAction(title: title, style: .default) { (action) in
|
||||
handler()
|
||||
}
|
||||
addAction(action)
|
||||
preferredAction = action
|
||||
return action
|
||||
}
|
||||
|
||||
func addCancelAction(_ title: String, handler: (() -> Void)? = nil) {
|
||||
@discardableResult func addCancelAction(_ title: String, handler: (() -> Void)? = nil) -> UIAlertAction {
|
||||
let action = UIAlertAction(title: title, style: .cancel) { (action) in
|
||||
handler?()
|
||||
}
|
||||
|
@ -52,20 +53,23 @@ extension UIAlertController {
|
|||
if actions.count == 1 {
|
||||
preferredAction = action
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func addAction(_ title: String, handler: @escaping () -> Void) {
|
||||
@discardableResult func addAction(_ title: String, handler: @escaping () -> Void) -> UIAlertAction {
|
||||
let action = UIAlertAction(title: title, style: .default) { (action) in
|
||||
handler()
|
||||
}
|
||||
addAction(action)
|
||||
return action
|
||||
}
|
||||
|
||||
func addDestructiveAction(_ title: String, handler: @escaping () -> Void) {
|
||||
@discardableResult func addDestructiveAction(_ title: String, handler: @escaping () -> Void) -> UIAlertAction {
|
||||
let action = UIAlertAction(title: title, style: .destructive) { (action) in
|
||||
handler()
|
||||
}
|
||||
addAction(action)
|
||||
preferredAction = action
|
||||
return action
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,17 @@ extension UIButton {
|
|||
}
|
||||
}
|
||||
|
||||
extension UITextField {
|
||||
func applyProfileId(_ theme: Theme) {
|
||||
placeholder = L10n.Global.Host.TitleInput.placeholder
|
||||
clearButtonMode = .always
|
||||
keyboardType = .asciiCapable
|
||||
returnKeyType = .done
|
||||
autocapitalizationType = .none
|
||||
autocorrectionType = .no
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: status bar is broken
|
||||
extension MFMailComposeViewController {
|
||||
func apply(_ theme: Theme) {
|
||||
|
|
|
@ -445,7 +445,7 @@ extension OrganizerViewController: ConnectionServiceDelegate {
|
|||
perform(segue: StoryboardSegue.Organizer.selectProfileSegueIdentifier, sender: profile)
|
||||
}
|
||||
|
||||
func connectionService(didRename profile: ConnectionProfile) {
|
||||
func connectionService(didRename oldProfile: ConnectionProfile, to newProfile: ConnectionProfile) {
|
||||
TransientStore.shared.serialize() // rename
|
||||
|
||||
reloadModel()
|
||||
|
|
|
@ -51,6 +51,7 @@ class WizardHostViewController: UITableViewController, TableModelHost {
|
|||
lazy var model: TableModel<SectionType, RowType> = {
|
||||
let model: TableModel<SectionType, RowType> = TableModel()
|
||||
model.add(.meta)
|
||||
model.setFooter(L10n.Global.Host.TitleInput.message, for: .meta)
|
||||
if !existingHosts.isEmpty {
|
||||
model.add(.existing)
|
||||
model.setHeader(L10n.Wizards.Host.Sections.Existing.header, for: .existing)
|
||||
|
@ -185,6 +186,10 @@ extension WizardHostViewController {
|
|||
return model.header(for: section)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
return model.footer(for: section)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return model.count(for: section)
|
||||
}
|
||||
|
@ -196,9 +201,7 @@ extension WizardHostViewController {
|
|||
cell.caption = L10n.Wizards.Host.Cells.TitleInput.caption
|
||||
cell.captionWidth = 100.0
|
||||
cell.allowedCharset = .filename
|
||||
cell.field.placeholder = L10n.Wizards.Host.Cells.TitleInput.placeholder
|
||||
cell.field.clearButtonMode = .always
|
||||
cell.field.returnKeyType = .done
|
||||
cell.field.applyProfileId(Theme.current)
|
||||
cell.delegate = self
|
||||
return cell
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ class ServiceViewController: UIViewController, TableModelHost {
|
|||
var profile: ConnectionProfile? {
|
||||
didSet {
|
||||
title = profile?.id
|
||||
navigationItem.rightBarButtonItem?.isEnabled = (profile?.context == .host)
|
||||
reloadModel()
|
||||
updateViewsIfNeeded()
|
||||
}
|
||||
|
@ -47,6 +48,8 @@ class ServiceViewController: UIViewController, TableModelHost {
|
|||
|
||||
private lazy var vpn = GracefulVPN(service: service)
|
||||
|
||||
private weak var pendingRenameAction: UIAlertAction?
|
||||
|
||||
private var lastInfrastructureUpdate: Date?
|
||||
|
||||
// MARK: Table
|
||||
|
@ -179,7 +182,7 @@ class ServiceViewController: UIViewController, TableModelHost {
|
|||
viewWelcome?.isHidden = (profile != nil)
|
||||
}
|
||||
|
||||
@IBAction private func activate() {
|
||||
private func activateProfile() {
|
||||
service.activateProfile(uncheckedProfile)
|
||||
TransientStore.shared.serialize() // activate
|
||||
|
||||
|
@ -189,6 +192,28 @@ class ServiceViewController: UIViewController, TableModelHost {
|
|||
vpn.disconnect(completionHandler: nil)
|
||||
}
|
||||
|
||||
@IBAction private func renameProfile() {
|
||||
let alert = Macros.alert(L10n.Service.Alerts.Rename.title, L10n.Global.Host.TitleInput.message)
|
||||
alert.addTextField { (field) in
|
||||
field.text = self.profile?.id
|
||||
field.applyProfileId(Theme.current)
|
||||
field.delegate = self
|
||||
}
|
||||
pendingRenameAction = alert.addDefaultAction(L10n.Global.ok) {
|
||||
guard let newId = alert.textFields?.first?.text else {
|
||||
return
|
||||
}
|
||||
self.doRenameCurrentProfile(to: newId)
|
||||
}
|
||||
alert.addCancelAction(L10n.Global.cancel)
|
||||
pendingRenameAction?.isEnabled = false
|
||||
present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
private func doRenameCurrentProfile(to newId: String) {
|
||||
profile = service.renameProfile(uncheckedHostProfile, to: newId)
|
||||
}
|
||||
|
||||
private func toggleVpnService(cell: ToggleTableViewCell) {
|
||||
if cell.isOn {
|
||||
guard !service.needsCredentials(for: uncheckedProfile) else {
|
||||
|
@ -713,7 +738,7 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
|
|||
private func handle(row: RowType, cell: UITableViewCell) -> Bool {
|
||||
switch row {
|
||||
case .useProfile:
|
||||
activate()
|
||||
activateProfile()
|
||||
|
||||
case .reconnect:
|
||||
confirmVpnReconnection()
|
||||
|
@ -931,6 +956,21 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
|
|||
|
||||
// MARK: -
|
||||
|
||||
extension ServiceViewController: UITextFieldDelegate {
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
guard string.rangeOfCharacter(from: CharacterSet.filename.inverted) == nil else {
|
||||
return false
|
||||
}
|
||||
if let text = textField.text {
|
||||
let replacement = (text as NSString).replacingCharacters(in: range, with: string)
|
||||
pendingRenameAction?.isEnabled = (replacement != uncheckedProfile.id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension ServiceViewController: TrustedNetworksModelDelegate {
|
||||
func trustedNetworksCouldDisconnect(_: TrustedNetworksModel) -> Bool {
|
||||
return (service.preferences.trustPolicy == .disconnect) && (vpn.status != .disconnected)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="AAm-3V-G5F">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="AAm-3V-G5F">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -215,7 +215,13 @@
|
|||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="Vwa-AG-OnN"/>
|
||||
<navigationItem key="navigationItem" id="Vwa-AG-OnN">
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="edit" id="gbN-fY-AoW">
|
||||
<connections>
|
||||
<action selector="renameProfile" destination="BYZ-38-t0r" id="xxl-Nu-VS6"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="labelWelcome" destination="jEt-mV-gjN" id="kaN-fX-eRE"/>
|
||||
<outlet property="tableView" destination="14D-an-pBY" id="qzB-YR-Pss"/>
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
"global.ok" = "OK";
|
||||
"global.cancel" = "Cancel";
|
||||
"global.next" = "Next";
|
||||
"global.host.title_input.message" = "Legal characters are alphanumerics plus dash (-), underscore (_) and dot (.).";
|
||||
"global.host.title_input.placeholder" = "My Profile";
|
||||
|
||||
"reddit.title" = "Reddit";
|
||||
"reddit.message" = "Did you know that Passepartout has a subreddit? Subscribe for updates or to discuss issues, features, new platforms or whatever you like.\n\nIt's also a great way to show you care about this project.";
|
||||
|
@ -49,7 +51,6 @@
|
|||
"account.suggestion_footer.infrastructure.pia" = "Use your website credentials. Your username is usually numeric with a \"p\" prefix.";
|
||||
|
||||
"wizards.host.cells.title_input.caption" = "Title";
|
||||
"wizards.host.cells.title_input.placeholder" = "My Profile";
|
||||
"wizards.host.sections.existing.header" = "Existing profiles";
|
||||
"wizards.host.alerts.existing.message" = "A host profile with the same title already exists. Replace it?";
|
||||
|
||||
|
@ -105,6 +106,7 @@
|
|||
"service.cells.debug_log.caption" = "Debug log";
|
||||
"service.cells.report_issue.caption" = "Report connectivity issue";
|
||||
|
||||
"service.alerts.rename.title" = "Rename profile";
|
||||
"service.alerts.credentials_needed.message" = "You need to enter account credentials first.";
|
||||
"service.alerts.reconnect_vpn.message" = "Do you want to reconnect to the VPN?";
|
||||
"service.alerts.trusted.no_network.message" = "You are not connected to any Wi-Fi network.";
|
||||
|
|
|
@ -243,6 +243,14 @@ internal enum L10n {
|
|||
internal static let next = L10n.tr("Localizable", "global.next")
|
||||
/// OK
|
||||
internal static let ok = L10n.tr("Localizable", "global.ok")
|
||||
internal enum Host {
|
||||
internal enum TitleInput {
|
||||
/// Legal characters are alphanumerics plus dash (-), underscore (_) and dot (.).
|
||||
internal static let message = L10n.tr("Localizable", "global.host.title_input.message")
|
||||
/// My Profile
|
||||
internal static let placeholder = L10n.tr("Localizable", "global.host.title_input.placeholder")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal enum ImportedHosts {
|
||||
|
@ -415,6 +423,10 @@ internal enum L10n {
|
|||
/// Do you want to reconnect to the VPN?
|
||||
internal static let message = L10n.tr("Localizable", "service.alerts.reconnect_vpn.message")
|
||||
}
|
||||
internal enum Rename {
|
||||
/// Rename profile
|
||||
internal static let title = L10n.tr("Localizable", "service.alerts.rename.title")
|
||||
}
|
||||
internal enum TestConnectivity {
|
||||
/// Connectivity
|
||||
internal static let title = L10n.tr("Localizable", "service.alerts.test_connectivity.title")
|
||||
|
@ -655,8 +667,6 @@ internal enum L10n {
|
|||
internal enum TitleInput {
|
||||
/// Title
|
||||
internal static let caption = L10n.tr("Localizable", "wizards.host.cells.title_input.caption")
|
||||
/// My Profile
|
||||
internal static let placeholder = L10n.tr("Localizable", "wizards.host.cells.title_input.placeholder")
|
||||
}
|
||||
}
|
||||
internal enum Sections {
|
||||
|
|
Loading…
Reference in New Issue