Replace with Convenience entities

- About
- Alerts
- Dialogs
- InApp
- Reviewer
- SingleOptionViewController
- StrongTableModel
This commit is contained in:
Davide De Rosa 2019-10-11 10:56:23 +02:00
parent ea5d3a48ab
commit 2cd6677e16
29 changed files with 393 additions and 807 deletions

View File

@ -26,6 +26,7 @@
import UIKit import UIKit
import TunnelKit import TunnelKit
import PassepartoutCore import PassepartoutCore
import Convenience
@UIApplicationMain @UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
@ -54,7 +55,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
// splitViewController.preferredDisplayMode = .primaryOverlay // splitViewController.preferredDisplayMode = .primaryOverlay
} }
InAppHelper.shared.requestProducts(withIdentifiers: InApp.allIdentifiers(), completionHandler: nil) ProductManager.shared.listProducts(completionHandler: nil)
return true return true
} }

View File

@ -44,11 +44,11 @@ extension OpenVPN.ConfigurationParser.Result {
} catch let e as ConfigurationError { } catch let e as ConfigurationError {
switch e { switch e {
case .encryptionPassphrase, .unableToDecrypt(_): case .encryptionPassphrase, .unableToDecrypt(_):
let alert = Macros.alert(url.normalizedFilename, L10n.Core.ParsedFile.Alerts.EncryptionPassphrase.message) let alert = UIAlertController.asAlert(url.normalizedFilename, L10n.Core.ParsedFile.Alerts.EncryptionPassphrase.message)
alert.addTextField { (field) in alert.addTextField { (field) in
field.isSecureTextEntry = true field.isSecureTextEntry = true
} }
alert.addDefaultAction(L10n.Core.Global.ok) { alert.addPreferredAction(L10n.Core.Global.ok) {
guard let passphrase = alert.textFields?.first?.text else { guard let passphrase = alert.textFields?.first?.text else {
return return
} }
@ -79,8 +79,8 @@ extension OpenVPN.ConfigurationParser.Result {
} }
private static func alertImportError(url: URL, in vc: UIViewController, withMessage message: String) { private static func alertImportError(url: URL, in vc: UIViewController, withMessage message: String) {
let alert = Macros.alert(url.normalizedFilename, message) let alert = UIAlertController.asAlert(url.normalizedFilename, message)
// alert.addDefaultAction(L10n.Core.ParsedFile.Alerts.Buttons.report) { // alert.addPreferredAction(L10n.Core.ParsedFile.Alerts.Buttons.report) {
// var attach = IssueReporter.Attachments(debugLog: false, configurationURL: url) // var attach = IssueReporter.Attachments(debugLog: false, configurationURL: url)
// attach.description = message // attach.description = message
// IssueReporter.shared.present(in: vc, withAttachments: attach) // IssueReporter.shared.present(in: vc, withAttachments: attach)
@ -91,8 +91,8 @@ extension OpenVPN.ConfigurationParser.Result {
static func alertImportWarning(url: URL, in vc: UIViewController, withWarning warning: ConfigurationError, completionHandler: @escaping (Bool) -> Void) { static func alertImportWarning(url: URL, in vc: UIViewController, withWarning warning: ConfigurationError, completionHandler: @escaping (Bool) -> Void) {
let message = details(forWarning: warning) let message = details(forWarning: warning)
let alert = Macros.alert(url.normalizedFilename, L10n.Core.ParsedFile.Alerts.PotentiallyUnsupported.message(message)) let alert = UIAlertController.asAlert(url.normalizedFilename, L10n.Core.ParsedFile.Alerts.PotentiallyUnsupported.message(message))
alert.addDefaultAction(L10n.Core.Global.ok) { alert.addPreferredAction(L10n.Core.Global.ok) {
completionHandler(true) completionHandler(true)
} }
alert.addCancelAction(L10n.Core.Global.cancel) { alert.addCancelAction(L10n.Core.Global.cancel) {

View File

@ -0,0 +1,49 @@
//
// Donation.swift
// Passepartout-iOS
//
// Created by Davide De Rosa on 10/11/19.
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
enum Donation: String {
case tiny = "com.algoritmico.ios.Passepartout.donations.Tiny"
case small = "com.algoritmico.ios.Passepartout.donations.Small"
case medium = "com.algoritmico.ios.Passepartout.donations.Medium"
case big = "com.algoritmico.ios.Passepartout.donations.Big"
case huge = "com.algoritmico.ios.Passepartout.donations.Huge"
case maxi = "com.algoritmico.ios.Passepartout.donations.Maxi"
static let all: [Donation] = [
.tiny,
.small,
.medium,
.big,
.huge,
.maxi
]
}

View File

@ -1,101 +0,0 @@
//
// Downloader.swift
// Passepartout-iOS
//
// Created by Davide De Rosa on 4/10/19.
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import MBProgressHUD
import SwiftyBeaver
import PassepartoutCore
private let log = SwiftyBeaver.self
class Downloader: NSObject {
static let shared = Downloader(temporaryURL: GroupConstants.App.cachesURL.appendingPathComponent("downloaded.tmp"))
private let temporaryURL: URL
private var hud: MBProgressHUD?
private var completionHandler: ((URL?, Error?) -> Void)?
init(temporaryURL: URL) {
self.temporaryURL = temporaryURL
}
func download(url: URL, in view: UIView, completionHandler: @escaping (URL?, Error?) -> Void) -> Bool {
guard hud == nil else {
log.info("Download in progress, skipping")
return false
}
log.info("Downloading from: \(url)")
let session = URLSession(configuration: .default, delegate: self, delegateQueue: .main)
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: AppConstants.Web.timeout)
let task = session.downloadTask(with: request)
hud = MBProgressHUD.showAdded(to: view, animated: true)
hud?.mode = .annularDeterminate
hud?.progressObject = task.progress
self.completionHandler = completionHandler
task.resume()
return true
}
}
extension Downloader: URLSessionDelegate, URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
log.error("Download failed: \(error)")
hud?.hide(animated: true)
hud = nil
completionHandler?(nil, error)
completionHandler = nil
return
}
completionHandler = nil
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
log.info("Download complete!")
if let url = downloadTask.originalRequest?.url {
log.info("\tFrom: \(url)")
}
log.debug("\tTo: \(location)")
let fm = FileManager.default
do {
try? fm.removeItem(at: temporaryURL)
try fm.copyItem(at: location, to: temporaryURL)
} catch let e {
log.error("Failed to copy downloaded file: \(e)")
return
}
hud?.hide(animated: true)
hud = nil
completionHandler?(temporaryURL, nil)
completionHandler = nil
}
}

View File

@ -1,65 +0,0 @@
//
// HUD.swift
// Passepartout-iOS
//
// Created by Davide De Rosa on 9/18/18.
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright (c) 2018-Present Private Internet Access
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import UIKit
import MBProgressHUD
class HUD {
private let backend: MBProgressHUD
init(label: String? = nil) {
guard let window = UIApplication.shared.windows.first else {
fatalError("Could not locate front window?")
}
backend = MBProgressHUD.showAdded(to: window, animated: true)
backend.label.text = label
backend.backgroundView.backgroundColor = UIColor(white: 0.0, alpha: 0.6)
backend.mode = .indeterminate
backend.removeFromSuperViewOnHide = true
// Theme.current.applyOverlay(hud.backgroundView)
// Theme.current.applyOverlay(hud.bezelView)
}
func show() {
backend.show(animated: true)
}
func hide() {
backend.hide(animated: true)
}
}

View File

@ -42,8 +42,8 @@ class IssueReporter: NSObject {
let app = UIApplication.shared let app = UIApplication.shared
let V = AppConstants.IssueReporter.Email.self let V = AppConstants.IssueReporter.Email.self
let body = V.body(V.template, DebugLog(raw: "--").decoratedString()) let body = V.body(V.template, DebugLog(raw: "--").decoratedString())
guard let url = Utils.mailto(to: V.recipient, subject: V.subject, body: body), app.canOpenURL(url) else { guard let url = URL.mailto(to: V.recipient, subject: V.subject, body: body), app.canOpenURL(url) else {
let alert = Macros.alert(L10n.Core.IssueReporter.title, L10n.Core.Global.emailNotConfigured) let alert = UIAlertController.asAlert(L10n.Core.IssueReporter.title, L10n.Core.Global.emailNotConfigured)
alert.addCancelAction(L10n.Core.Global.ok) alert.addCancelAction(L10n.Core.Global.ok)
viewController.present(alert, animated: true, completion: nil) viewController.present(alert, animated: true, completion: nil)
return return
@ -55,8 +55,8 @@ class IssueReporter: NSObject {
self.viewController = viewController self.viewController = viewController
if issue.debugLog { if issue.debugLog {
let alert = Macros.alert(L10n.Core.IssueReporter.title, L10n.Core.IssueReporter.message) let alert = UIAlertController.asAlert(L10n.Core.IssueReporter.title, L10n.Core.IssueReporter.message)
alert.addDefaultAction(L10n.Core.IssueReporter.Buttons.accept) { alert.addPreferredAction(L10n.Core.IssueReporter.Buttons.accept) {
VPN.shared.requestDebugLog(fallback: AppConstants.Log.debugSnapshot) { VPN.shared.requestDebugLog(fallback: AppConstants.Log.debugSnapshot) {
self.composeEmail(withDebugLog: $0, configurationURL: issue.configurationURL, description: issue.description) self.composeEmail(withDebugLog: $0, configurationURL: issue.configurationURL, description: issue.description)
} }

View File

@ -25,55 +25,6 @@
import UIKit import UIKit
class Macros {
static func alert(_ title: String?, _ message: String?) -> UIAlertController {
return UIAlertController(title: title, message: message, preferredStyle: .alert)
}
static func actionSheet(_ title: String?, _ message: String?) -> UIAlertController {
return UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
}
}
extension UIAlertController {
@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
}
@discardableResult func addCancelAction(_ title: String, handler: (() -> Void)? = nil) -> UIAlertAction {
let action = UIAlertAction(title: title, style: .cancel) { (action) in
handler?()
}
addAction(action)
if actions.count == 1 {
preferredAction = action
}
return action
}
@discardableResult func addAction(_ title: String, handler: @escaping () -> Void) -> UIAlertAction {
let action = UIAlertAction(title: title, style: .default) { (action) in
handler()
}
addAction(action)
return action
}
@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
}
}
extension UIView { extension UIView {
static func get<T: UIView>() -> T { static func get<T: UIView>() -> T {
let name = String(describing: T.self) let name = String(describing: T.self)

View File

@ -1,72 +0,0 @@
//
// OptionViewController.swift
// Passepartout-iOS
//
// Created by Davide De Rosa on 9/5/18.
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import UIKit
class OptionViewController<T: Hashable>: UIViewController, UITableViewDataSource, UITableViewDelegate {
private lazy var tableView = UITableView(frame: .zero, style: .grouped)
var options: [T] = []
var selectedOption: T?
var descriptionBlock: ((T) -> String)?
var selectionBlock: ((T) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(SettingTableViewCell.self, forCellReuseIdentifier: SettingTableViewCell.Provider.identifier)
tableView.frame = view.bounds
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(tableView)
tableView.dataSource = self
tableView.delegate = self
if let selectedOption = selectedOption, let row = options.firstIndex(of: selectedOption) {
tableView.reloadData()
tableView.scrollToRowAsync(at: IndexPath(row: row, section: 0))
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let opt = options[indexPath.row]
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = descriptionBlock?(opt)
cell.applyChecked(opt == selectedOption, Theme.current)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let opt = options[indexPath.row]
selectionBlock?(opt)
}
}

View File

@ -1,5 +1,5 @@
// //
// InApp.swift // ProductManager.swift
// Passepartout-iOS // Passepartout-iOS
// //
// Created by Davide De Rosa on 4/6/19. // Created by Davide De Rosa on 4/6/19.
@ -25,32 +25,28 @@
import Foundation import Foundation
import StoreKit import StoreKit
import Convenience
struct InApp { struct ProductManager {
enum Donation: String { static let shared = ProductManager()
static let all: [Donation] = [
.tiny,
.small,
.medium,
.big,
.huge,
.maxi
]
case tiny = "com.algoritmico.ios.Passepartout.donations.Tiny" private let inApp: InApp<Donation>
case small = "com.algoritmico.ios.Passepartout.donations.Small" private init() {
inApp = InApp()
case medium = "com.algoritmico.ios.Passepartout.donations.Medium"
case big = "com.algoritmico.ios.Passepartout.donations.Big"
case huge = "com.algoritmico.ios.Passepartout.donations.Huge"
case maxi = "com.algoritmico.ios.Passepartout.donations.Maxi"
} }
static func allIdentifiers() -> Set<String> { func listProducts(completionHandler: (([SKProduct]) -> Void)?) {
return Set<String>(Donation.all.map { $0.rawValue }) guard inApp.products.isEmpty else {
completionHandler?(inApp.products)
return
}
inApp.requestProducts(withIdentifiers: Donation.all) { _ in
completionHandler?(self.inApp.products)
}
}
func purchase(_ product: SKProduct, completionHandler: @escaping (InAppPurchaseResult, Error?) -> Void) {
inApp.purchase(product: product, completionHandler: completionHandler)
} }
} }

View File

@ -24,6 +24,7 @@
// //
import UIKit import UIKit
import Convenience
extension UIViewController { extension UIViewController {
func applyMasterTitle(_ theme: Theme) { func applyMasterTitle(_ theme: Theme) {
@ -35,9 +36,9 @@ extension UIViewController {
} }
} }
extension TableModel { extension StrongTableModel {
func headerHeight(for section: Int) -> CGFloat { func headerHeight(for section: Int) -> CGFloat {
guard let title = header(for: section) else { guard let title = header(forSection: section) else {
return 1.0 return 1.0
} }
guard !title.isEmpty else { guard !title.isEmpty else {
@ -47,7 +48,7 @@ extension TableModel {
} }
func footerHeight(for section: Int) -> CGFloat { func footerHeight(for section: Int) -> CGFloat {
guard let title = footer(for: section) else { guard let title = footer(forSection: section) else {
return 1.0 return 1.0
} }
guard !title.isEmpty else { guard !title.isEmpty else {

View File

@ -25,25 +25,26 @@
import UIKit import UIKit
import PassepartoutCore import PassepartoutCore
import Convenience
class AboutViewController: UITableViewController, TableModelHost { class AboutViewController: UITableViewController, StrongTableHost {
// MARK: TableModelHost // MARK: StrongTableHost
let model: TableModel<SectionType, RowType> = { let model: StrongTableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
model.add(.info) model.add(.info)
model.add(.github) model.add(.github)
model.add(.web) model.add(.web)
model.add(.share) model.add(.share)
model.setHeader("", for: .info) model.setHeader("", forSection: .info)
model.setHeader("GitHub", for: .github) model.setHeader("GitHub", forSection: .github)
model.setHeader(L10n.Core.About.Sections.Web.header, for: .web) model.setHeader(L10n.Core.About.Sections.Web.header, forSection: .web)
model.setHeader(L10n.Core.About.Sections.Share.header, for: .share) model.setHeader(L10n.Core.About.Sections.Share.header, forSection: .share)
model.set([.version, .credits], in: .info) model.set([.version, .credits], forSection: .info)
model.set([.readme, .changelog], in: .github) model.set([.readme, .changelog], forSection: .github)
model.set([.website, .faq, .disclaimer, .privacyPolicy], in: .web) model.set([.website, .faq, .disclaimer, .privacyPolicy], forSection: .web)
model.set([.shareTwitter, .shareGeneric], in: .share) model.set([.shareTwitter, .shareGeneric], forSection: .share)
return model return model
}() }()
@ -126,15 +127,15 @@ extension AboutViewController {
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section) return model.footer(forSection: section)
} }
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@ -146,7 +147,7 @@ extension AboutViewController {
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -154,7 +155,7 @@ extension AboutViewController {
switch model.row(at: indexPath) { switch model.row(at: indexPath) {
case .version: case .version:
cell.leftText = L10n.Core.Version.title cell.leftText = L10n.Core.Version.title
cell.rightText = Utils.versionString() cell.rightText = ApplicationInfo.appVersion
case .credits: case .credits:
cell.leftText = L10n.Core.About.Cells.Credits.caption cell.leftText = L10n.Core.About.Cells.Credits.caption

View File

@ -25,8 +25,9 @@
import UIKit import UIKit
import PassepartoutCore import PassepartoutCore
import Convenience
class CreditsViewController: UITableViewController, TableModelHost { class CreditsViewController: UITableViewController, StrongTableHost {
private let licenses = AppConstants.License.all private let licenses = AppConstants.License.all
private let notices = AppConstants.Notice.all private let notices = AppConstants.Notice.all
@ -35,22 +36,22 @@ class CreditsViewController: UITableViewController, TableModelHost {
return Utils.localizedLanguage($0) < Utils.localizedLanguage($1) return Utils.localizedLanguage($0) < Utils.localizedLanguage($1)
} }
// MARK: TableModelHost // MARK: StrongTableHost
var model: TableModel<SectionType, RowType> = TableModel() var model: StrongTableModel<SectionType, RowType> = StrongTableModel()
func reloadModel() { func reloadModel() {
model.add(.licenses) model.add(.licenses)
model.add(.notices) model.add(.notices)
model.add(.translations) model.add(.translations)
model.setHeader(L10n.Core.Credits.Sections.Licenses.header, for: .licenses) model.setHeader(L10n.Core.Credits.Sections.Licenses.header, forSection: .licenses)
model.setHeader(L10n.Core.Credits.Sections.Notices.header, for: .notices) model.setHeader(L10n.Core.Credits.Sections.Notices.header, forSection: .notices)
model.setHeader(L10n.Core.Credits.Sections.Translations.header, for: .translations) model.setHeader(L10n.Core.Credits.Sections.Translations.header, forSection: .translations)
model.set(.license, count: licenses.count, in: .licenses) model.set(.license, count: licenses.count, forSection: .licenses)
model.set(.notice, count: notices.count, in: .notices) model.set(.notice, count: notices.count, forSection: .notices)
model.set(.translation, count: languages.count, in: .translations) model.set(.translation, count: languages.count, forSection: .translations)
} }
// MARK: UIViewController // MARK: UIViewController
@ -108,15 +109,15 @@ extension CreditsViewController {
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

View File

@ -25,6 +25,7 @@
import UIKit import UIKit
import PassepartoutCore import PassepartoutCore
import Convenience
class VersionViewController: UIViewController { class VersionViewController: UIViewController {
@IBOutlet private weak var scrollView: UIScrollView? @IBOutlet private weak var scrollView: UIScrollView?
@ -46,7 +47,7 @@ class VersionViewController: UIViewController {
title = L10n.Core.Version.title title = L10n.Core.Version.title
labelTitle?.text = GroupConstants.App.name labelTitle?.text = GroupConstants.App.name
labelVersion?.text = Utils.versionString() labelVersion?.text = ApplicationInfo.appVersion
labelIntro?.text = L10n.Core.Version.Labels.intro labelIntro?.text = L10n.Core.Version.Labels.intro
scrollView?.applyPrimaryBackground(Theme.current) scrollView?.applyPrimaryBackground(Theme.current)

View File

@ -25,6 +25,7 @@
import UIKit import UIKit
import PassepartoutCore import PassepartoutCore
import Convenience
protocol AccountViewControllerDelegate: class { protocol AccountViewControllerDelegate: class {
func accountController(_: AccountViewController, didEnterCredentials credentials: Credentials) func accountController(_: AccountViewController, didEnterCredentials credentials: Credentials)
@ -32,7 +33,7 @@ protocol AccountViewControllerDelegate: class {
func accountControllerDidComplete(_: AccountViewController) func accountControllerDidComplete(_: AccountViewController)
} }
class AccountViewController: UIViewController, TableModelHost { class AccountViewController: UIViewController, StrongTableHost {
@IBOutlet private weak var tableView: UITableView? @IBOutlet private weak var tableView: UITableView?
private weak var cellUsername: FieldTableViewCell? private weak var cellUsername: FieldTableViewCell?
@ -101,32 +102,32 @@ class AccountViewController: UIViewController, TableModelHost {
weak var delegate: AccountViewControllerDelegate? weak var delegate: AccountViewControllerDelegate?
// MARK: TableModelHost // MARK: StrongTableHost
var model: TableModel<SectionType, RowType> = TableModel() var model: StrongTableModel<SectionType, RowType> = StrongTableModel()
func reloadModel() { func reloadModel() {
model.clear() model.clear()
model.add(.credentials) model.add(.credentials)
model.setHeader(L10n.App.Account.Sections.Credentials.header, for: .credentials) model.setHeader(L10n.App.Account.Sections.Credentials.header, forSection: .credentials)
model.set([.username, .password], in: .credentials) model.set([.username, .password], forSection: .credentials)
if let _ = infrastructureName { if let _ = infrastructureName {
if let guidanceString = guidanceString { if let guidanceString = guidanceString {
if let _ = guidanceURL { if let _ = guidanceURL {
model.add(.guidance) model.add(.guidance)
model.setFooter(guidanceString, for: .guidance) model.setFooter(guidanceString, forSection: .guidance)
model.set([.openGuide], in: .guidance) model.set([.openGuide], forSection: .guidance)
} else { } else {
model.setFooter(guidanceString, for: .credentials) model.setFooter(guidanceString, forSection: .credentials)
} }
model.setHeader("", for: .registration) model.setHeader("", forSection: .registration)
} }
// if let _ = referralURL { // if let _ = referralURL {
// model.add(.registration) // model.add(.registration)
// model.setFooter(L10n.Core.Account.Sections.Registration.footer(name.rawValue), for: .registration) // model.setFooter(L10n.Core.Account.Sections.Registration.footer(name.rawValue), forSection: .registration)
// model.set([.signUp], in: .registration) // model.set([.signUp], forSection: .registration)
// } // }
} }
} }
@ -210,15 +211,15 @@ extension AccountViewController: UITableViewDataSource, UITableViewDelegate, Fie
private static let footerButtonTag = 1000 private static let footerButtonTag = 1000
func numberOfSections(in tableView: UITableView) -> Int { func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section) return model.footer(forSection: section)
} }
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@ -226,7 +227,7 @@ extension AccountViewController: UITableViewDataSource, UITableViewDelegate, Fie
} }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

View File

@ -27,10 +27,11 @@ import UIKit
import TunnelKit import TunnelKit
import SwiftyBeaver import SwiftyBeaver
import PassepartoutCore import PassepartoutCore
import Convenience
private let log = SwiftyBeaver.self private let log = SwiftyBeaver.self
class ConfigurationViewController: UIViewController, TableModelHost { class ConfigurationViewController: UIViewController, StrongTableHost {
@IBOutlet private weak var tableView: UITableView! @IBOutlet private weak var tableView: UITableView!
private lazy var itemRefresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh)) private lazy var itemRefresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh))
@ -47,10 +48,10 @@ class ConfigurationViewController: UIViewController, TableModelHost {
weak var delegate: ConfigurationModificationDelegate? weak var delegate: ConfigurationModificationDelegate?
// MARK: TableModelHost // MARK: StrongTableHost
lazy var model: TableModel<SectionType, RowType> = { lazy var model: StrongTableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
// sections // sections
model.add(.communication) model.add(.communication)
@ -62,24 +63,24 @@ class ConfigurationViewController: UIViewController, TableModelHost {
model.add(.other) model.add(.other)
// headers // headers
model.setHeader(L10n.Core.Configuration.Sections.Communication.header, for: .communication) model.setHeader(L10n.Core.Configuration.Sections.Communication.header, forSection: .communication)
model.setHeader(L10n.Core.Configuration.Sections.Tls.header, for: .tls) model.setHeader(L10n.Core.Configuration.Sections.Tls.header, forSection: .tls)
model.setHeader(L10n.Core.Configuration.Sections.Compression.header, for: .compression) model.setHeader(L10n.Core.Configuration.Sections.Compression.header, forSection: .compression)
model.setHeader(L10n.Core.Configuration.Sections.Other.header, for: .other) model.setHeader(L10n.Core.Configuration.Sections.Other.header, forSection: .other)
// footers // footers
if isEditable { if isEditable {
model.setFooter(L10n.Core.Configuration.Sections.Reset.footer, for: .reset) model.setFooter(L10n.Core.Configuration.Sections.Reset.footer, forSection: .reset)
} }
// rows // rows
model.set([.cipher, .digest], in: .communication) model.set([.cipher, .digest], forSection: .communication)
if isEditable { if isEditable {
model.set([.resetOriginal], in: .reset) model.set([.resetOriginal], forSection: .reset)
} }
model.set([.client, .tlsWrapping, .eku], in: .tls) model.set([.client, .tlsWrapping, .eku], forSection: .tls)
model.set([.compressionFraming, .compressionAlgorithm], in: .compression) model.set([.compressionFraming, .compressionAlgorithm], forSection: .compression)
model.set([.keepAlive, .renegSeconds, .randomEndpoint], in: .other) model.set([.keepAlive, .renegSeconds, .randomEndpoint], forSection: .other)
return model return model
}() }()
@ -193,15 +194,15 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
} }
func numberOfSections(in tableView: UITableView) -> Int { func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section) return model.footer(forSection: section)
} }
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@ -209,7 +210,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
} }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -313,7 +314,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
switch model.row(at: indexPath) { switch model.row(at: indexPath) {
case .cipher: case .cipher:
let vc = OptionViewController<OpenVPN.Cipher>() let vc = SingleOptionViewController<OpenVPN.Cipher>()
vc.title = settingCell?.leftText vc.title = settingCell?.leftText
vc.options = OpenVPN.Cipher.available vc.options = OpenVPN.Cipher.available
vc.selectedOption = configuration.cipher vc.selectedOption = configuration.cipher
@ -325,7 +326,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
navigationController?.pushViewController(vc, animated: true) navigationController?.pushViewController(vc, animated: true)
case .digest: case .digest:
let vc = OptionViewController<OpenVPN.Digest>() let vc = SingleOptionViewController<OpenVPN.Digest>()
vc.title = settingCell?.leftText vc.title = settingCell?.leftText
vc.options = OpenVPN.Digest.available vc.options = OpenVPN.Digest.available
vc.selectedOption = configuration.digest vc.selectedOption = configuration.digest
@ -337,7 +338,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
navigationController?.pushViewController(vc, animated: true) navigationController?.pushViewController(vc, animated: true)
case .compressionFraming: case .compressionFraming:
let vc = OptionViewController<OpenVPN.CompressionFraming>() let vc = SingleOptionViewController<OpenVPN.CompressionFraming>()
vc.title = settingCell?.leftText vc.title = settingCell?.leftText
vc.options = OpenVPN.CompressionFraming.available vc.options = OpenVPN.CompressionFraming.available
vc.selectedOption = configuration.compressionFraming ?? .disabled vc.selectedOption = configuration.compressionFraming ?? .disabled
@ -356,7 +357,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
return return
} }
let vc = OptionViewController<OpenVPN.CompressionAlgorithm>() let vc = SingleOptionViewController<OpenVPN.CompressionAlgorithm>()
vc.title = settingCell?.leftText vc.title = settingCell?.leftText
vc.options = OpenVPN.CompressionAlgorithm.available vc.options = OpenVPN.CompressionAlgorithm.available
vc.selectedOption = configuration.compressionAlgorithm ?? .disabled vc.selectedOption = configuration.compressionAlgorithm ?? .disabled

View File

@ -84,7 +84,7 @@ class DebugLogViewController: UIViewController {
@IBAction private func share(_ sender: Any?) { @IBAction private func share(_ sender: Any?) {
guard let raw = textLog?.text, !raw.isEmpty else { guard let raw = textLog?.text, !raw.isEmpty else {
let alert = Macros.alert(title, L10n.Core.DebugLog.Alerts.EmptyLog.message) let alert = UIAlertController.asAlert(title, L10n.Core.DebugLog.Alerts.EmptyLog.message)
alert.addCancelAction(L10n.Core.Global.ok) alert.addCancelAction(L10n.Core.Global.ok)
present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
return return

View File

@ -26,12 +26,13 @@
import UIKit import UIKit
import TunnelKit import TunnelKit
import PassepartoutCore import PassepartoutCore
import Convenience
protocol EndpointViewControllerDelegate: class { protocol EndpointViewControllerDelegate: class {
func endpointController(_: EndpointViewController, didUpdateWithNewAddress newAddress: String?, newProtocol: EndpointProtocol?) func endpointController(_: EndpointViewController, didUpdateWithNewAddress newAddress: String?, newProtocol: EndpointProtocol?)
} }
class EndpointViewController: UIViewController, TableModelHost { class EndpointViewController: UIViewController, StrongTableHost {
@IBOutlet private weak var tableView: UITableView! @IBOutlet private weak var tableView: UITableView!
private lazy var itemRefresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh)) private lazy var itemRefresh = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refresh))
@ -58,28 +59,28 @@ class EndpointViewController: UIViewController, TableModelHost {
weak var modificationDelegate: ConfigurationModificationDelegate? weak var modificationDelegate: ConfigurationModificationDelegate?
// MARK: TableModelHost // MARK: StrongTableHost
lazy var model: TableModel<SectionType, RowType> = { lazy var model: StrongTableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
model.add(.locationAddresses) model.add(.locationAddresses)
model.add(.locationProtocols) model.add(.locationProtocols)
model.setHeader(L10n.App.Endpoint.Sections.LocationAddresses.header, for: .locationAddresses) model.setHeader(L10n.App.Endpoint.Sections.LocationAddresses.header, forSection: .locationAddresses)
model.setHeader(L10n.App.Endpoint.Sections.LocationProtocols.header, for: .locationProtocols) model.setHeader(L10n.App.Endpoint.Sections.LocationProtocols.header, forSection: .locationProtocols)
if dataSource.canCustomizeEndpoint { if dataSource.canCustomizeEndpoint {
var addressRows: [RowType] = Array(repeating: .availableAddress, count: dataSource.addresses.count) var addressRows: [RowType] = Array(repeating: .availableAddress, count: dataSource.addresses.count)
addressRows.insert(.anyAddress, at: 0) addressRows.insert(.anyAddress, at: 0)
model.set(addressRows, in: .locationAddresses) model.set(addressRows, forSection: .locationAddresses)
var protocolRows: [RowType] = Array(repeating: .availableProtocol, count: dataSource.protocols.count) var protocolRows: [RowType] = Array(repeating: .availableProtocol, count: dataSource.protocols.count)
protocolRows.insert(.anyProtocol, at: 0) protocolRows.insert(.anyProtocol, at: 0)
model.set(protocolRows, in: .locationProtocols) model.set(protocolRows, forSection: .locationProtocols)
} else { } else {
model.set(.availableAddress, count: dataSource.addresses.count, in: .locationAddresses) model.set(.availableAddress, count: dataSource.addresses.count, forSection: .locationAddresses)
model.set(.availableProtocol, count: dataSource.protocols.count, in: .locationProtocols) model.set(.availableProtocol, count: dataSource.protocols.count, forSection: .locationProtocols)
} }
return model return model
@ -188,19 +189,19 @@ extension EndpointViewController: UITableViewDataSource, UITableViewDelegate {
} }
func numberOfSections(in tableView: UITableView) -> Int { func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section) return model.footer(forSection: section)
} }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

View File

@ -27,6 +27,7 @@ import UIKit
import PassepartoutCore import PassepartoutCore
import TunnelKit import TunnelKit
import SwiftyBeaver import SwiftyBeaver
import Convenience
private let log = SwiftyBeaver.self private let log = SwiftyBeaver.self
@ -57,9 +58,9 @@ class NetworkSettingsViewController: UITableViewController {
private let networkSettings = ProfileNetworkSettings() private let networkSettings = ProfileNetworkSettings()
// MARK: TableModelHost // MARK: StrongTableHost
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
func reloadModel() { func reloadModel() {
model.clear() model.clear()
@ -77,24 +78,24 @@ class NetworkSettingsViewController: UITableViewController {
} }
// headers // headers
model.setHeader("", for: .choices) model.setHeader("", forSection: .choices)
model.setHeader(L10n.Core.NetworkSettings.Gateway.title, for: .manualGateway) model.setHeader(L10n.Core.NetworkSettings.Gateway.title, forSection: .manualGateway)
model.setHeader(L10n.Core.NetworkSettings.Dns.title, for: .manualDNS) model.setHeader(L10n.Core.NetworkSettings.Dns.title, forSection: .manualDNS)
model.setHeader(L10n.Core.NetworkSettings.Proxy.title, for: .manualProxy) model.setHeader(L10n.Core.NetworkSettings.Proxy.title, forSection: .manualProxy)
// footers // footers
// model.setFooter(L10n.Core.Configuration.Sections.Reset.footer, for: .reset) // model.setFooter(L10n.Core.Configuration.Sections.Reset.footer, for: .reset)
// rows // rows
model.set([.gateway, .dns, .proxy], in: .choices) model.set([.gateway, .dns, .proxy], forSection: .choices)
model.set([.gatewayIPv4, .gatewayIPv6], in: .manualGateway) model.set([.gatewayIPv4, .gatewayIPv6], forSection: .manualGateway)
var dnsRows: [RowType] = Array(repeating: .dnsAddress, count: networkSettings.dnsServers?.count ?? 0) var dnsRows: [RowType] = Array(repeating: .dnsAddress, count: networkSettings.dnsServers?.count ?? 0)
dnsRows.insert(.dnsDomain, at: 0) dnsRows.insert(.dnsDomain, at: 0)
if networkChoices.dns == .manual { if networkChoices.dns == .manual {
dnsRows.append(.dnsAddAddress) dnsRows.append(.dnsAddAddress)
} }
model.set(dnsRows, in: .manualDNS) model.set(dnsRows, forSection: .manualDNS)
var proxyRows: [RowType] = Array(repeating: .proxyBypass, count: networkSettings.proxyBypassDomains?.count ?? 0) var proxyRows: [RowType] = Array(repeating: .proxyBypass, count: networkSettings.proxyBypassDomains?.count ?? 0)
proxyRows.insert(.proxyAddress, at: 0) proxyRows.insert(.proxyAddress, at: 0)
@ -102,7 +103,7 @@ class NetworkSettingsViewController: UITableViewController {
if networkChoices.proxy == .manual { if networkChoices.proxy == .manual {
proxyRows.append(.proxyAddBypass) proxyRows.append(.proxyAddBypass)
} }
model.set(proxyRows, in: .manualProxy) model.set(proxyRows, forSection: .manualProxy)
} }
// MARK: UIViewController // MARK: UIViewController
@ -262,15 +263,15 @@ extension NetworkSettingsViewController {
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section) return model.footer(forSection: section)
} }
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@ -278,7 +279,7 @@ extension NetworkSettingsViewController {
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -287,19 +288,19 @@ extension NetworkSettingsViewController {
switch row { switch row {
case .gateway: case .gateway:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath) let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = model.header(for: .manualGateway) cell.leftText = model.header(forSection: .manualGateway)
cell.rightText = networkChoices.gateway.description cell.rightText = networkChoices.gateway.description
return cell return cell
case .dns: case .dns:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath) let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = model.header(for: .manualDNS) cell.leftText = model.header(forSection: .manualDNS)
cell.rightText = networkChoices.dns.description cell.rightText = networkChoices.dns.description
return cell return cell
case .proxy: case .proxy:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath) let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = model.header(for: .manualProxy) cell.leftText = model.header(forSection: .manualProxy)
cell.rightText = networkChoices.proxy.description cell.rightText = networkChoices.proxy.description
return cell return cell
@ -405,7 +406,7 @@ extension NetworkSettingsViewController {
switch model.row(at: indexPath) { switch model.row(at: indexPath) {
case .gateway: case .gateway:
let vc = OptionViewController<NetworkChoice>() let vc = SingleOptionViewController<NetworkChoice>()
vc.title = (cell as? SettingTableViewCell)?.leftText vc.title = (cell as? SettingTableViewCell)?.leftText
vc.options = NetworkChoice.choices(for: profile) vc.options = NetworkChoice.choices(for: profile)
vc.descriptionBlock = { $0.description } vc.descriptionBlock = { $0.description }
@ -418,7 +419,7 @@ extension NetworkSettingsViewController {
navigationController?.pushViewController(vc, animated: true) navigationController?.pushViewController(vc, animated: true)
case .dns: case .dns:
let vc = OptionViewController<NetworkChoice>() let vc = SingleOptionViewController<NetworkChoice>()
vc.title = (cell as? SettingTableViewCell)?.leftText vc.title = (cell as? SettingTableViewCell)?.leftText
vc.options = NetworkChoice.choices(for: profile) vc.options = NetworkChoice.choices(for: profile)
vc.descriptionBlock = { $0.description } vc.descriptionBlock = { $0.description }
@ -431,7 +432,7 @@ extension NetworkSettingsViewController {
navigationController?.pushViewController(vc, animated: true) navigationController?.pushViewController(vc, animated: true)
case .proxy: case .proxy:
let vc = OptionViewController<NetworkChoice>() let vc = SingleOptionViewController<NetworkChoice>()
vc.title = (cell as? SettingTableViewCell)?.leftText vc.title = (cell as? SettingTableViewCell)?.leftText
vc.options = NetworkChoice.choices(for: profile) vc.options = NetworkChoice.choices(for: profile)
vc.descriptionBlock = { $0.description } vc.descriptionBlock = { $0.description }

View File

@ -26,9 +26,10 @@
import UIKit import UIKit
import StoreKit import StoreKit
import PassepartoutCore import PassepartoutCore
import Convenience
class DonationViewController: UITableViewController, TableModelHost { class DonationViewController: UITableViewController, StrongTableHost {
private var donationList: [InApp.Donation] = [] private var donationList: [Donation] = []
private var productsByIdentifier: [String: SKProduct] = [:] private var productsByIdentifier: [String: SKProduct] = [:]
@ -44,35 +45,34 @@ class DonationViewController: UITableViewController, TableModelHost {
tableView.reloadData() tableView.reloadData()
} }
// MARK: TableModel // MARK: StrongTableModel
var model: TableModel<SectionType, RowType> = TableModel() var model: StrongTableModel<SectionType, RowType> = StrongTableModel()
func reloadModel() { func reloadModel() {
donationList = [] donationList = []
model.clear() model.clear()
model.add(.oneTime) model.add(.oneTime)
model.setHeader(L10n.Core.Donation.Sections.OneTime.header, for: .oneTime) model.setHeader(L10n.Core.Donation.Sections.OneTime.header, forSection: .oneTime)
model.setFooter(L10n.Core.Donation.Sections.OneTime.footer, for: .oneTime) model.setFooter(L10n.Core.Donation.Sections.OneTime.footer, forSection: .oneTime)
guard !isLoading else { guard !isLoading else {
model.set([.loading], in: .oneTime) model.set([.loading], forSection: .oneTime)
return return
} }
let completeList: [InApp.Donation] = [.tiny, .small, .medium, .big, .huge, .maxi] for row in Donation.all {
for row in completeList {
guard let _ = productsByIdentifier[row.rawValue] else { guard let _ = productsByIdentifier[row.rawValue] else {
continue continue
} }
donationList.append(row) donationList.append(row)
} }
model.set(.donation, count: donationList.count, in: .oneTime) model.set(.donation, count: donationList.count, forSection: .oneTime)
if isPurchasing { if isPurchasing {
model.add(.activity) model.add(.activity)
model.set([.purchasing], in: .activity) model.set([.purchasing], forSection: .activity)
} }
} }
@ -84,15 +84,9 @@ class DonationViewController: UITableViewController, TableModelHost {
title = L10n.Core.Donation.title title = L10n.Core.Donation.title
reloadModel() reloadModel()
let inApp = InAppHelper.shared ProductManager.shared.listProducts {
if inApp.products.isEmpty { self.isLoading = false
inApp.requestProducts(withIdentifiers: InApp.allIdentifiers()) { self.setProducts($0)
self.isLoading = false
self.setProducts($0)
}
} else {
isLoading = false
setProducts(inApp.products)
} }
} }
@ -103,19 +97,19 @@ class DonationViewController: UITableViewController, TableModelHost {
// MARK: UITableViewController // MARK: UITableViewController
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section) return model.footer(forSection: section)
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -159,7 +153,7 @@ class DonationViewController: UITableViewController, TableModelHost {
reloadModel() reloadModel()
tableView.reloadData() tableView.reloadData()
InAppHelper.shared.purchase(product: product) { ProductManager.shared.purchase(product) {
self.handlePurchase(result: $0, error: $1) self.handlePurchase(result: $0, error: $1)
} }
@ -168,7 +162,7 @@ class DonationViewController: UITableViewController, TableModelHost {
} }
} }
private func handlePurchase(result: InAppHelper.PurchaseResult, error: Error?) { private func handlePurchase(result: InAppPurchaseResult, error: Error?) {
let alert: UIAlertController let alert: UIAlertController
switch result { switch result {
case .cancelled: case .cancelled:
@ -178,10 +172,10 @@ class DonationViewController: UITableViewController, TableModelHost {
return return
case .success: case .success:
alert = Macros.alert(L10n.Core.Donation.Alerts.Purchase.Success.title, L10n.Core.Donation.Alerts.Purchase.Success.message) alert = UIAlertController.asAlert(L10n.Core.Donation.Alerts.Purchase.Success.title, L10n.Core.Donation.Alerts.Purchase.Success.message)
case .failure: case .failure:
alert = Macros.alert(title, L10n.Core.Donation.Alerts.Purchase.Failure.message(error?.localizedDescription ?? "")) alert = UIAlertController.asAlert(title, L10n.Core.Donation.Alerts.Purchase.Failure.message(error?.localizedDescription ?? ""))
} }
alert.addCancelAction(L10n.Core.Global.ok) { alert.addCancelAction(L10n.Core.Global.ok) {
self.isPurchasing = false self.isPurchasing = false

View File

@ -51,7 +51,7 @@ class ImportedHostsViewController: UITableViewController {
super.viewDidAppear(animated) super.viewDidAppear(animated)
guard !pendingConfigurationURLs.isEmpty else { guard !pendingConfigurationURLs.isEmpty else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
title, title,
L10n.Core.Organizer.Alerts.AddHost.message L10n.Core.Organizer.Alerts.AddHost.message
) )

View File

@ -27,10 +27,11 @@ import UIKit
import StoreKit import StoreKit
import MessageUI import MessageUI
import PassepartoutCore import PassepartoutCore
import Convenience
// XXX: convoluted due to the separation of provider/host profiles // XXX: convoluted due to the separation of provider/host profiles
class OrganizerViewController: UITableViewController, TableModelHost { class OrganizerViewController: UITableViewController, StrongTableHost {
private let service = TransientStore.shared.service private let service = TransientStore.shared.service
private var providers: [String] = [] private var providers: [String] = []
@ -41,10 +42,10 @@ class OrganizerViewController: UITableViewController, TableModelHost {
private var didShowSubreddit = false private var didShowSubreddit = false
// MARK: TableModelHost // MARK: StrongTableHost
let model: TableModel<SectionType, RowType> = { let model: StrongTableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
model.add(.vpn) model.add(.vpn)
model.add(.providers) model.add(.providers)
model.add(.hosts) model.add(.hosts)
@ -55,27 +56,27 @@ class OrganizerViewController: UITableViewController, TableModelHost {
model.add(.feedback) model.add(.feedback)
model.add(.about) model.add(.about)
model.add(.destruction) model.add(.destruction)
model.setHeader(L10n.App.Service.Sections.Vpn.header, for: .vpn) model.setHeader(L10n.App.Service.Sections.Vpn.header, forSection: .vpn)
model.setHeader(L10n.Core.Organizer.Sections.Providers.header, for: .providers) model.setHeader(L10n.Core.Organizer.Sections.Providers.header, forSection: .providers)
model.setHeader(L10n.Core.Organizer.Sections.Hosts.header, for: .hosts) model.setHeader(L10n.Core.Organizer.Sections.Hosts.header, forSection: .hosts)
model.setFooter(L10n.Core.Organizer.Sections.Providers.footer, for: .providers) model.setFooter(L10n.Core.Organizer.Sections.Providers.footer, forSection: .providers)
model.setFooter(L10n.Core.Organizer.Sections.Hosts.footer, for: .hosts) model.setFooter(L10n.Core.Organizer.Sections.Hosts.footer, forSection: .hosts)
if #available(iOS 12, *) { if #available(iOS 12, *) {
model.setHeader(L10n.Core.Organizer.Sections.Siri.header, for: .siri) model.setHeader(L10n.Core.Organizer.Sections.Siri.header, forSection: .siri)
model.setFooter(L10n.Core.Organizer.Sections.Siri.footer, for: .siri) model.setFooter(L10n.Core.Organizer.Sections.Siri.footer, forSection: .siri)
model.set([.siriShortcuts], in: .siri) model.set([.siriShortcuts], forSection: .siri)
} }
model.setHeader(L10n.Core.Organizer.Sections.Support.header, for: .support) model.setHeader(L10n.Core.Organizer.Sections.Support.header, forSection: .support)
model.setHeader(L10n.Core.Organizer.Sections.Feedback.header, for: .feedback) model.setHeader(L10n.Core.Organizer.Sections.Feedback.header, forSection: .feedback)
model.set([.connectionStatus], in: .vpn) model.set([.connectionStatus], forSection: .vpn)
model.set([.donate, .translate], in: .support) model.set([.donate, .translate], forSection: .support)
model.set([.joinCommunity, .writeReview], in: .feedback) model.set([.joinCommunity, .writeReview], forSection: .feedback)
model.set([.openAbout], in: .about) model.set([.openAbout], forSection: .about)
model.set([.uninstall], in: .destruction) model.set([.uninstall], forSection: .destruction)
if AppConstants.Flags.isBeta { if AppConstants.Flags.isBeta {
model.add(.test) model.add(.test)
model.setHeader("Beta", for: .test) model.setHeader("Beta", forSection: .test)
model.set([.testDisplayLog, .testTermination], in: .test) model.set([.testDisplayLog, .testTermination], forSection: .test)
} }
return model return model
}() }()
@ -89,8 +90,8 @@ class OrganizerViewController: UITableViewController, TableModelHost {
providerRows.append(.addProvider) providerRows.append(.addProvider)
hostRows.append(.addHost) hostRows.append(.addHost)
model.set(providerRows, in: .providers) model.set(providerRows, forSection: .providers)
model.set(hostRows, in: .hosts) model.set(hostRows, forSection: .hosts)
} }
// MARK: UIViewController // MARK: UIViewController
@ -125,8 +126,8 @@ class OrganizerViewController: UITableViewController, TableModelHost {
if !didShowSubreddit && !TransientStore.didHandleSubreddit { if !didShowSubreddit && !TransientStore.didHandleSubreddit {
didShowSubreddit = true didShowSubreddit = true
let alert = Macros.alert(L10n.Core.Reddit.title, L10n.Core.Reddit.message) let alert = UIAlertController.asAlert(L10n.Core.Reddit.title, L10n.Core.Reddit.message)
alert.addDefaultAction(L10n.Core.Reddit.Buttons.subscribe) { alert.addPreferredAction(L10n.Core.Reddit.Buttons.subscribe) {
TransientStore.didHandleSubreddit = true TransientStore.didHandleSubreddit = true
self.subscribeSubreddit() self.subscribeSubreddit()
} }
@ -185,7 +186,7 @@ class OrganizerViewController: UITableViewController, TableModelHost {
private func addNewProvider() { private func addNewProvider() {
let names = service.availableProviderNames() let names = service.availableProviderNames()
guard !names.isEmpty else { guard !names.isEmpty else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Organizer.Sections.Providers.header, L10n.Core.Organizer.Sections.Providers.header,
L10n.Core.Organizer.Alerts.ExhaustedProviders.message L10n.Core.Organizer.Alerts.ExhaustedProviders.message
) )
@ -207,7 +208,7 @@ class OrganizerViewController: UITableViewController, TableModelHost {
private func donateToDeveloper() { private func donateToDeveloper() {
guard SKPaymentQueue.canMakePayments() else { guard SKPaymentQueue.canMakePayments() else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Organizer.Cells.Donate.caption, L10n.Core.Organizer.Cells.Donate.caption,
L10n.Core.Organizer.Alerts.CannotDonate.message L10n.Core.Organizer.Alerts.CannotDonate.message
) )
@ -230,8 +231,8 @@ class OrganizerViewController: UITableViewController, TableModelHost {
guard MFMailComposeViewController.canSendMail() else { guard MFMailComposeViewController.canSendMail() else {
let app = UIApplication.shared let app = UIApplication.shared
guard let url = Utils.mailto(to: recipient, subject: subject, body: body), app.canOpenURL(url) else { guard let url = URL.mailto(to: recipient, subject: subject, body: body), app.canOpenURL(url) else {
let alert = Macros.alert(L10n.Core.Translations.title, L10n.Core.Global.emailNotConfigured) let alert = UIAlertController.asAlert(L10n.Core.Translations.title, L10n.Core.Global.emailNotConfigured)
alert.addCancelAction(L10n.Core.Global.ok) alert.addCancelAction(L10n.Core.Global.ok)
present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
return return
@ -254,7 +255,7 @@ class OrganizerViewController: UITableViewController, TableModelHost {
} }
private func removeProfile(at indexPath: IndexPath) { private func removeProfile(at indexPath: IndexPath) {
let sectionObject = model.section(for: indexPath.section) let sectionObject = model.section(forIndex: indexPath.section)
let rowProfile = profileKey(at: indexPath) let rowProfile = profileKey(at: indexPath)
switch sectionObject { switch sectionObject {
case .providers: case .providers:
@ -288,7 +289,7 @@ class OrganizerViewController: UITableViewController, TableModelHost {
} }
tableView.beginUpdates() tableView.beginUpdates()
model.deleteRow(in: sectionObject, at: indexPath.row) model.deleteRow(at: indexPath.row, ofSection: sectionObject)
tableView.deleteRows(at: [indexPath], with: .automatic) tableView.deleteRows(at: [indexPath], with: .automatic)
// if let fallbackSection = fallbackSection { // if let fallbackSection = fallbackSection {
// let section = model.index(ofSection: fallbackSection) // let section = model.index(ofSection: fallbackSection)
@ -303,11 +304,11 @@ class OrganizerViewController: UITableViewController, TableModelHost {
} }
private func confirmVpnProfileDeletion() { private func confirmVpnProfileDeletion() {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Organizer.Cells.Uninstall.caption, L10n.Core.Organizer.Cells.Uninstall.caption,
L10n.Core.Organizer.Alerts.DeleteVpnProfile.message L10n.Core.Organizer.Alerts.DeleteVpnProfile.message
) )
alert.addDefaultAction(L10n.Core.Global.ok) { alert.addPreferredAction(L10n.Core.Global.ok) {
VPN.shared.uninstall(completionHandler: nil) VPN.shared.uninstall(completionHandler: nil)
} }
alert.addCancelAction(L10n.Core.Global.cancel) alert.addCancelAction(L10n.Core.Global.cancel)
@ -329,7 +330,7 @@ class OrganizerViewController: UITableViewController, TableModelHost {
guard let log = try? String(contentsOf: AppConstants.Log.fileURL) else { guard let log = try? String(contentsOf: AppConstants.Log.fileURL) else {
return return
} }
let alert = Macros.alert("Debug log", log) let alert = UIAlertController.asAlert("Debug log", log)
alert.addCancelAction(L10n.Core.Global.ok) alert.addCancelAction(L10n.Core.Global.ok)
present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} }
@ -399,19 +400,19 @@ extension OrganizerViewController {
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section) return model.footer(forSection: section)
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -480,7 +481,7 @@ extension OrganizerViewController {
case .openAbout: case .openAbout:
let cell = Cells.setting.dequeue(from: tableView, for: indexPath) let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = L10n.Core.Organizer.Cells.About.caption(GroupConstants.App.name) cell.leftText = L10n.Core.Organizer.Cells.About.caption(GroupConstants.App.name)
cell.rightText = Utils.versionString() cell.rightText = ApplicationInfo.appVersion
return cell return cell
case .uninstall: case .uninstall:
@ -562,7 +563,7 @@ extension OrganizerViewController {
private func sectionProfiles(at indexPath: IndexPath) -> [String] { private func sectionProfiles(at indexPath: IndexPath) -> [String] {
let ids: [String] let ids: [String]
let sectionObject = model.section(for: indexPath.section) let sectionObject = model.section(forIndex: indexPath.section)
switch sectionObject { switch sectionObject {
case .providers: case .providers:
ids = providers ids = providers
@ -580,7 +581,7 @@ extension OrganizerViewController {
} }
private func profileKey(at indexPath: IndexPath) -> ProfileKey { private func profileKey(at indexPath: IndexPath) -> ProfileKey {
let section = model.section(for: indexPath.section) let section = model.section(forIndex: indexPath.section)
switch section { switch section {
case .providers: case .providers:
return ProfileKey(.provider, providers[indexPath.row]) return ProfileKey(.provider, providers[indexPath.row])
@ -595,7 +596,7 @@ extension OrganizerViewController {
private func profile(at indexPath: IndexPath) -> ConnectionProfile { private func profile(at indexPath: IndexPath) -> ConnectionProfile {
let id = sectionProfiles(at: indexPath)[indexPath.row] let id = sectionProfiles(at: indexPath)[indexPath.row]
let section = model.section(for: indexPath.section) let section = model.section(forIndex: indexPath.section)
let context: Context let context: Context
switch section { switch section {
case .providers: case .providers:

View File

@ -27,10 +27,11 @@ import UIKit
import TunnelKit import TunnelKit
import SwiftyBeaver import SwiftyBeaver
import PassepartoutCore import PassepartoutCore
import Convenience
private let log = SwiftyBeaver.self private let log = SwiftyBeaver.self
class WizardHostViewController: UITableViewController, TableModelHost { class WizardHostViewController: UITableViewController, StrongTableHost {
@IBOutlet private weak var itemNext: UIBarButtonItem! @IBOutlet private weak var itemNext: UIBarButtonItem!
private let existingHosts: [String] = { private let existingHosts: [String] = {
@ -47,18 +48,18 @@ class WizardHostViewController: UITableViewController, TableModelHost {
private var createdProfile: HostConnectionProfile? private var createdProfile: HostConnectionProfile?
// MARK: TableModelHost // MARK: StrongTableHost
lazy var model: TableModel<SectionType, RowType> = { lazy var model: StrongTableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
model.add(.meta) model.add(.meta)
model.setFooter(L10n.Core.Global.Host.TitleInput.message, for: .meta) model.setFooter(L10n.Core.Global.Host.TitleInput.message, forSection: .meta)
if !existingHosts.isEmpty { if !existingHosts.isEmpty {
model.add(.existing) model.add(.existing)
model.setHeader(L10n.App.Wizards.Host.Sections.Existing.header, for: .existing) model.setHeader(L10n.App.Wizards.Host.Sections.Existing.header, forSection: .existing)
} }
model.set([.titleInput], in: .meta) model.set([.titleInput], forSection: .meta)
model.set(.existingHost, count: existingHosts.count, in: .existing) model.set(.existingHost, count: existingHosts.count, forSection: .existing)
return model return model
}() }()
@ -110,8 +111,8 @@ class WizardHostViewController: UITableViewController, TableModelHost {
let service = TransientStore.shared.service let service = TransientStore.shared.service
guard !service.containsProfile(profile) else { guard !service.containsProfile(profile) else {
let replacedProfile = service.profile(withContext: profile.context, id: profile.id) let replacedProfile = service.profile(withContext: profile.context, id: profile.id)
let alert = Macros.alert(title, L10n.Core.Wizards.Host.Alerts.Existing.message) let alert = UIAlertController.asAlert(title, L10n.Core.Wizards.Host.Alerts.Existing.message)
alert.addDefaultAction(L10n.Core.Global.ok) { alert.addPreferredAction(L10n.Core.Global.ok) {
self.next(withProfile: profile, replacedProfile: replacedProfile) self.next(withProfile: profile, replacedProfile: replacedProfile)
} }
alert.addCancelAction(L10n.Core.Global.cancel) alert.addCancelAction(L10n.Core.Global.cancel)
@ -177,26 +178,26 @@ extension WizardHostViewController {
} }
private var cellTitle: FieldTableViewCell? { private var cellTitle: FieldTableViewCell? {
guard let ip = model.indexPath(row: .titleInput, section: .meta) else { guard let ip = model.indexPath(forRow: .titleInput, ofSection: .meta) else {
return nil return nil
} }
return tableView.cellForRow(at: ip) as? FieldTableViewCell return tableView.cellForRow(at: ip) as? FieldTableViewCell
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return model.footer(for: section) return model.footer(forSection: section)
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -223,7 +224,7 @@ extension WizardHostViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch model.row(at: indexPath) { switch model.row(at: indexPath) {
case .existingHost: case .existingHost:
guard let titleIndexPath = model.indexPath(row: .titleInput, section: .meta) else { guard let titleIndexPath = model.indexPath(forRow: .titleInput, ofSection: .meta) else {
fatalError("Could not found title cell?") fatalError("Could not found title cell?")
} }
let hostTitle = existingHosts[indexPath.row] let hostTitle = existingHosts[indexPath.row]

View File

@ -25,6 +25,7 @@
import UIKit import UIKit
import PassepartoutCore import PassepartoutCore
import Convenience
protocol ProviderPoolViewControllerDelegate: class { protocol ProviderPoolViewControllerDelegate: class {
func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool) func providerPoolController(_: ProviderPoolViewController, didSelectPool pool: Pool)
@ -148,7 +149,7 @@ extension ProviderPoolViewController: UITableViewDataSource, UITableViewDelegate
guard group.pools.count > 1 else { guard group.pools.count > 1 else {
return return
} }
let vc = OptionViewController<Pool>() let vc = SingleOptionViewController<Pool>()
vc.title = group.localizedCountry vc.title = group.localizedCountry
vc.options = group.pools.sorted { vc.options = group.pools.sorted {
guard let lnum = $0.num else { guard let lnum = $0.num else {

View File

@ -28,8 +28,9 @@ import NetworkExtension
import MBProgressHUD import MBProgressHUD
import TunnelKit import TunnelKit
import PassepartoutCore import PassepartoutCore
import Convenience
class ServiceViewController: UIViewController, TableModelHost { class ServiceViewController: UIViewController, StrongTableHost {
@IBOutlet private weak var tableView: UITableView! @IBOutlet private weak var tableView: UITableView!
@IBOutlet private weak var viewWelcome: UIView! @IBOutlet private weak var viewWelcome: UIView!
@ -38,6 +39,11 @@ class ServiceViewController: UIViewController, TableModelHost {
@IBOutlet private weak var itemEdit: UIBarButtonItem! @IBOutlet private weak var itemEdit: UIBarButtonItem!
private let downloader = FileDownloader(
temporaryURL: GroupConstants.App.cachesURL.appendingPathComponent("downloaded.tmp"),
timeout: AppConstants.Web.timeout
)
private var profile: ConnectionProfile? private var profile: ConnectionProfile?
private let service = TransientStore.shared.service private let service = TransientStore.shared.service
@ -54,7 +60,7 @@ class ServiceViewController: UIViewController, TableModelHost {
// MARK: Table // MARK: Table
var model: TableModel<SectionType, RowType> = TableModel() var model: StrongTableModel<SectionType, RowType> = StrongTableModel()
private let trustedNetworks = TrustedNetworksModel() private let trustedNetworks = TrustedNetworksModel()
@ -217,13 +223,13 @@ class ServiceViewController: UIViewController, TableModelHost {
} }
@IBAction private func renameProfile() { @IBAction private func renameProfile() {
let alert = Macros.alert(L10n.Core.Service.Alerts.Rename.title, L10n.Core.Global.Host.TitleInput.message) let alert = UIAlertController.asAlert(L10n.Core.Service.Alerts.Rename.title, L10n.Core.Global.Host.TitleInput.message)
alert.addTextField { (field) in alert.addTextField { (field) in
field.text = self.profile?.id field.text = self.profile?.id
field.applyProfileId(Theme.current) field.applyProfileId(Theme.current)
field.delegate = self field.delegate = self
} }
pendingRenameAction = alert.addDefaultAction(L10n.Core.Global.ok) { pendingRenameAction = alert.addPreferredAction(L10n.Core.Global.ok) {
guard let newId = alert.textFields?.first?.text else { guard let newId = alert.textFields?.first?.text else {
return return
} }
@ -245,7 +251,7 @@ class ServiceViewController: UIViewController, TableModelHost {
IntentDispatcher.donateConnection(with: uncheckedProfile) IntentDispatcher.donateConnection(with: uncheckedProfile)
} }
guard !service.needsCredentials(for: uncheckedProfile) else { guard !service.needsCredentials(for: uncheckedProfile) else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.App.Service.Sections.Vpn.header, L10n.App.Service.Sections.Vpn.header,
L10n.Core.Service.Alerts.CredentialsNeeded.message L10n.Core.Service.Alerts.CredentialsNeeded.message
) )
@ -283,11 +289,11 @@ class ServiceViewController: UIViewController, TableModelHost {
private func confirmVpnReconnection() { private func confirmVpnReconnection() {
guard vpn.status == .disconnected else { guard vpn.status == .disconnected else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Service.Cells.ConnectionStatus.caption, L10n.Core.Service.Cells.ConnectionStatus.caption,
L10n.Core.Service.Alerts.ReconnectVpn.message L10n.Core.Service.Alerts.ReconnectVpn.message
) )
alert.addDefaultAction(L10n.Core.Global.ok) { alert.addPreferredAction(L10n.Core.Global.ok) {
self.vpn.reconnect(completionHandler: nil) self.vpn.reconnect(completionHandler: nil)
} }
alert.addCancelAction(L10n.Core.Global.cancel) alert.addCancelAction(L10n.Core.Global.cancel)
@ -300,7 +306,7 @@ class ServiceViewController: UIViewController, TableModelHost {
private func refreshProviderInfrastructure() { private func refreshProviderInfrastructure() {
let name = uncheckedProviderProfile.name let name = uncheckedProviderProfile.name
let hud = HUD() let hud = HUD(view: view.window!)
let isUpdating = InfrastructureFactory.shared.update(name, notBeforeInterval: AppConstants.Web.minimumUpdateInterval) { (response, error) in let isUpdating = InfrastructureFactory.shared.update(name, notBeforeInterval: AppConstants.Web.minimumUpdateInterval) { (response, error) in
hud.hide() hud.hide()
guard let response = response else { guard let response = response else {
@ -347,11 +353,11 @@ class ServiceViewController: UIViewController, TableModelHost {
completionHandler() completionHandler()
return return
} }
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Service.Sections.Trusted.header, L10n.Core.Service.Sections.Trusted.header,
L10n.Core.Service.Alerts.Trusted.WillDisconnectPolicy.message L10n.Core.Service.Alerts.Trusted.WillDisconnectPolicy.message
) )
alert.addDefaultAction(L10n.Core.Global.ok) { alert.addPreferredAction(L10n.Core.Global.ok) {
completionHandler() completionHandler()
} }
alert.addCancelAction(L10n.Core.Global.cancel) { alert.addCancelAction(L10n.Core.Global.cancel) {
@ -361,11 +367,11 @@ class ServiceViewController: UIViewController, TableModelHost {
} }
private func confirmPotentialTrustedDisconnection(at rowIndex: Int?, completionHandler: @escaping () -> Void) { private func confirmPotentialTrustedDisconnection(at rowIndex: Int?, completionHandler: @escaping () -> Void) {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Service.Sections.Trusted.header, L10n.Core.Service.Sections.Trusted.header,
L10n.Core.Service.Alerts.Trusted.WillDisconnectTrusted.message L10n.Core.Service.Alerts.Trusted.WillDisconnectTrusted.message
) )
alert.addDefaultAction(L10n.Core.Global.ok) { alert.addPreferredAction(L10n.Core.Global.ok) {
completionHandler() completionHandler()
} }
alert.addCancelAction(L10n.Core.Global.cancel) { alert.addCancelAction(L10n.Core.Global.cancel) {
@ -380,12 +386,12 @@ class ServiceViewController: UIViewController, TableModelHost {
} }
private func testInternetConnectivity() { private func testInternetConnectivity() {
let hud = HUD() let hud = HUD(view: view.window!)
Utils.checkConnectivityURL(AppConstants.Web.connectivityURL, timeout: AppConstants.Web.connectivityTimeout) { Utils.checkConnectivityURL(AppConstants.Web.connectivityURL, timeout: AppConstants.Web.connectivityTimeout) {
hud.hide() hud.hide()
let V = L10n.Core.Service.Alerts.TestConnectivity.Messages.self let V = L10n.Core.Service.Alerts.TestConnectivity.Messages.self
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Service.Alerts.TestConnectivity.title, L10n.Core.Service.Alerts.TestConnectivity.title,
$0 ? V.success : V.failure $0 ? V.success : V.failure
) )
@ -396,7 +402,7 @@ class ServiceViewController: UIViewController, TableModelHost {
// private func displayDataCount() { // private func displayDataCount() {
// guard vpn.isEnabled else { // guard vpn.isEnabled else {
// let alert = Macros.alert( // let alert = UIAlertController.asAlert(
// L10n.Core.Service.Cells.DataCount.caption, // L10n.Core.Service.Cells.DataCount.caption,
// L10n.Core.Service.Alerts.DataCount.Messages.notAvailable // L10n.Core.Service.Alerts.DataCount.Messages.notAvailable
// ) // )
@ -412,7 +418,7 @@ class ServiceViewController: UIViewController, TableModelHost {
// } else { // } else {
// message = L10n.Core.Service.Alerts.DataCount.Messages.notAvailable // message = L10n.Core.Service.Alerts.DataCount.Messages.notAvailable
// } // }
// let alert = Macros.alert( // let alert = UIAlertController.asAlert(
// L10n.Core.Service.Cells.DataCount.caption, // L10n.Core.Service.Cells.DataCount.caption,
// message // message
// ) // )
@ -428,7 +434,7 @@ class ServiceViewController: UIViewController, TableModelHost {
} }
guard vpn.status == .disconnected else { guard vpn.status == .disconnected else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Service.Cells.MasksPrivateData.caption, L10n.Core.Service.Cells.MasksPrivateData.caption,
L10n.Core.Service.Alerts.MasksPrivateData.Messages.mustReconnect L10n.Core.Service.Alerts.MasksPrivateData.Messages.mustReconnect
) )
@ -462,26 +468,26 @@ class ServiceViewController: UIViewController, TableModelHost {
return return
} }
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Service.Alerts.Download.title, L10n.Core.Service.Alerts.Download.title,
L10n.Core.Service.Alerts.Download.message(providerProfile.name.rawValue) L10n.Core.Service.Alerts.Download.message(providerProfile.name.rawValue)
) )
alert.addCancelAction(L10n.Core.Global.cancel) alert.addCancelAction(L10n.Core.Global.cancel)
alert.addDefaultAction(L10n.Core.Global.ok) { alert.addPreferredAction(L10n.Core.Global.ok) {
self.confirmDownload(URL(string: downloadURL)!) self.confirmDownload(URL(string: downloadURL)!)
} }
present(alert, animated: true, completion: nil) present(alert, animated: true, completion: nil)
} }
private func confirmDownload(_ url: URL) { private func confirmDownload(_ url: URL) {
_ = Downloader.shared.download(url: url, in: view) { (url, error) in _ = downloader.download(url: url, in: view) { (url, error) in
self.handleDownloadedProviderResources(url: url, error: error) self.handleDownloadedProviderResources(url: url, error: error)
} }
} }
private func handleDownloadedProviderResources(url: URL?, error: Error?) { private func handleDownloadedProviderResources(url: URL?, error: Error?) {
guard let url = url else { guard let url = url else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Service.Alerts.Download.title, L10n.Core.Service.Alerts.Download.title,
L10n.Core.Service.Alerts.Download.failed(error?.localizedDescription ?? "") L10n.Core.Service.Alerts.Download.failed(error?.localizedDescription ?? "")
) )
@ -490,7 +496,7 @@ class ServiceViewController: UIViewController, TableModelHost {
return return
} }
let hud = HUD(label: L10n.Core.Service.Alerts.Download.Hud.extracting) let hud = HUD(view: view.window!, label: L10n.Core.Service.Alerts.Download.Hud.extracting)
hud.show() hud.show()
uncheckedProviderProfile.name.importExternalResources(from: url) { uncheckedProviderProfile.name.importExternalResources(from: url) {
hud.hide() hud.hide()
@ -615,22 +621,22 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
} }
private var statusIndexPath: IndexPath? { private var statusIndexPath: IndexPath? {
return model.indexPath(row: .connectionStatus, section: .vpn) return model.indexPath(forRow: .connectionStatus, ofSection: .vpn)
} }
private var dataCountIndexPath: IndexPath? { private var dataCountIndexPath: IndexPath? {
return model.indexPath(row: .dataCount, section: .diagnostics) return model.indexPath(forRow: .dataCount, ofSection: .diagnostics)
} }
private var endpointIndexPath: IndexPath { private var endpointIndexPath: IndexPath {
guard let ip = model.indexPath(row: .endpoint, section: .configuration) else { guard let ip = model.indexPath(forRow: .endpoint, ofSection: .configuration) else {
fatalError("Could not locate endpointIndexPath") fatalError("Could not locate endpointIndexPath")
} }
return ip return ip
} }
private var providerPresetIndexPath: IndexPath { private var providerPresetIndexPath: IndexPath {
guard let ip = model.indexPath(row: .providerPreset, section: .configuration) else { guard let ip = model.indexPath(forRow: .providerPreset, ofSection: .configuration) else {
fatalError("Could not locate presetIndexPath") fatalError("Could not locate presetIndexPath")
} }
return ip return ip
@ -650,19 +656,19 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
} }
func numberOfSections(in tableView: UITableView) -> Int { func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
let rows = model.rows(for: section) let rows = model.rows(forSection: section)
if rows.contains(.providerRefresh), let date = lastInfrastructureUpdate { if rows.contains(.providerRefresh), let date = lastInfrastructureUpdate {
return L10n.Core.Service.Sections.ProviderInfrastructure.footer(date.timestamp) return L10n.Core.Service.Sections.ProviderInfrastructure.footer(date.timestamp)
} }
return model.footer(for: section) return model.footer(forSection: section)
} }
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@ -670,7 +676,7 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
} }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -931,7 +937,7 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
} }
guard trustedNetworks.addCurrentWifi() else { guard trustedNetworks.addCurrentWifi() else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Service.Sections.Trusted.header, L10n.Core.Service.Sections.Trusted.header,
L10n.Core.Service.Alerts.Trusted.NoNetwork.message L10n.Core.Service.Alerts.Trusted.NoNetwork.message
) )
@ -1033,31 +1039,31 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
} }
// headers // headers
model.setHeader(L10n.App.Service.Sections.Vpn.header, for: .vpn) model.setHeader(L10n.App.Service.Sections.Vpn.header, forSection: .vpn)
if isProvider { if isProvider {
model.setHeader(L10n.App.Service.Sections.Configuration.header, for: .authentication) model.setHeader(L10n.App.Service.Sections.Configuration.header, forSection: .authentication)
} else { } else {
model.setHeader(L10n.App.Service.Sections.Configuration.header, for: .configuration) model.setHeader(L10n.App.Service.Sections.Configuration.header, forSection: .configuration)
} }
if isActiveProfile { if isActiveProfile {
if isProvider { if isProvider {
model.setHeader("", for: .vpnResolvesHostname) model.setHeader("", forSection: .vpnResolvesHostname)
model.setHeader("", for: .vpnSurvivesSleep) model.setHeader("", forSection: .vpnSurvivesSleep)
} }
model.setHeader(L10n.Core.Service.Sections.Trusted.header, for: .trusted) model.setHeader(L10n.Core.Service.Sections.Trusted.header, forSection: .trusted)
model.setHeader(L10n.Core.Service.Sections.Diagnostics.header, for: .diagnostics) model.setHeader(L10n.Core.Service.Sections.Diagnostics.header, forSection: .diagnostics)
model.setHeader(L10n.Core.Organizer.Sections.Feedback.header, for: .feedback) model.setHeader(L10n.Core.Organizer.Sections.Feedback.header, forSection: .feedback)
} }
// footers // footers
if isActiveProfile { if isActiveProfile {
model.setFooter(L10n.Core.Service.Sections.Vpn.footer, for: .vpn) model.setFooter(L10n.Core.Service.Sections.Vpn.footer, forSection: .vpn)
if isProvider { if isProvider {
model.setFooter(L10n.Core.Service.Sections.VpnResolvesHostname.footer, for: .vpnResolvesHostname) model.setFooter(L10n.Core.Service.Sections.VpnResolvesHostname.footer, forSection: .vpnResolvesHostname)
} }
model.setFooter(L10n.Core.Service.Sections.VpnSurvivesSleep.footer, for: .vpnSurvivesSleep) model.setFooter(L10n.Core.Service.Sections.VpnSurvivesSleep.footer, forSection: .vpnSurvivesSleep)
model.setFooter(L10n.Core.Service.Sections.Trusted.footer, for: .trustedPolicy) model.setFooter(L10n.Core.Service.Sections.Trusted.footer, forSection: .trustedPolicy)
model.setFooter(L10n.Core.Service.Sections.Diagnostics.footer, for: .diagnostics) model.setFooter(L10n.Core.Service.Sections.Diagnostics.footer, forSection: .diagnostics)
} }
// rows // rows
@ -1066,30 +1072,30 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
if vpn.isEnabled { if vpn.isEnabled {
rows.append(.reconnect) rows.append(.reconnect)
} }
model.set(rows, in: .vpn) model.set(rows, forSection: .vpn)
} else { } else {
model.set([.useProfile], in: .vpn) model.set([.useProfile], forSection: .vpn)
} }
if isProvider { if isProvider {
model.set([.account], in: .authentication) model.set([.account], forSection: .authentication)
model.set([.providerPool, .endpoint, .providerPreset, .networkSettings], in: .configuration) model.set([.providerPool, .endpoint, .providerPreset, .networkSettings], forSection: .configuration)
model.set([.providerRefresh], in: .providerInfrastructure) model.set([.providerRefresh], forSection: .providerInfrastructure)
} else { } else {
model.set([.account, .endpoint, .hostParameters, .networkSettings], in: .configuration) model.set([.account, .endpoint, .hostParameters, .networkSettings], forSection: .configuration)
} }
if isActiveProfile { if isActiveProfile {
if isProvider { if isProvider {
model.set([.vpnResolvesHostname], in: .vpnResolvesHostname) model.set([.vpnResolvesHostname], forSection: .vpnResolvesHostname)
} }
model.set([.vpnSurvivesSleep], in: .vpnSurvivesSleep) model.set([.vpnSurvivesSleep], forSection: .vpnSurvivesSleep)
model.set([.trustedPolicy], in: .trustedPolicy) model.set([.trustedPolicy], forSection: .trustedPolicy)
model.set([.dataCount, .debugLog, .masksPrivateData], in: .diagnostics) model.set([.dataCount, .debugLog, .masksPrivateData], forSection: .diagnostics)
model.set([.reportIssue], in: .feedback) model.set([.reportIssue], forSection: .feedback)
} }
trustedNetworks.delegate = self trustedNetworks.delegate = self
trustedNetworks.load(from: service.preferences) trustedNetworks.load(from: service.preferences)
model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, in: .trusted) model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, forSection: .trusted)
} }
private func reloadVpnStatus() { private func reloadVpnStatus() {
@ -1170,7 +1176,7 @@ extension ServiceViewController: TrustedNetworksModelDelegate {
} }
func trustedNetworks(_: TrustedNetworksModel, shouldInsertWifiAt rowIndex: Int) { func trustedNetworks(_: TrustedNetworksModel, shouldInsertWifiAt rowIndex: Int) {
model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, in: .trusted) model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, forSection: .trusted)
tableView.insertRows(at: [IndexPath(row: rowIndex, section: trustedSectionIndex)], with: .bottom) tableView.insertRows(at: [IndexPath(row: rowIndex, section: trustedSectionIndex)], with: .bottom)
} }
@ -1186,7 +1192,7 @@ extension ServiceViewController: TrustedNetworksModelDelegate {
} }
func trustedNetworks(_: TrustedNetworksModel, shouldDeleteWifiAt rowIndex: Int) { func trustedNetworks(_: TrustedNetworksModel, shouldDeleteWifiAt rowIndex: Int) {
model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, in: .trusted) model.set(trustedNetworks.rows.map { mappedTrustedNetworksRow($0) }, forSection: .trusted)
tableView.deleteRows(at: [IndexPath(row: rowIndex, section: trustedSectionIndex)], with: .top) tableView.deleteRows(at: [IndexPath(row: rowIndex, section: trustedSectionIndex)], with: .top)
} }

View File

@ -26,24 +26,25 @@
import UIKit import UIKit
import Intents import Intents
import PassepartoutCore import PassepartoutCore
import Convenience
@available(iOS 12, *) @available(iOS 12, *)
class ShortcutsAddViewController: UITableViewController, TableModelHost { class ShortcutsAddViewController: UITableViewController, StrongTableHost {
weak var delegate: ShortcutsIntentDelegate? weak var delegate: ShortcutsIntentDelegate?
// MARK: TableModel // MARK: StrongTableModel
let model: TableModel<SectionType, RowType> = { let model: StrongTableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
model.add(.vpn) model.add(.vpn)
model.add(.wifi) model.add(.wifi)
model.add(.cellular) model.add(.cellular)
model.set([.connect, .enableVPN, .disableVPN], in: .vpn) model.set([.connect, .enableVPN, .disableVPN], forSection: .vpn)
model.set([.trustCurrentWiFi, .untrustCurrentWiFi], in: .wifi) model.set([.trustCurrentWiFi, .untrustCurrentWiFi], forSection: .wifi)
model.set([.trustCellular, .untrustCellular], in: .cellular) model.set([.trustCellular, .untrustCellular], forSection: .cellular)
model.setHeader(L10n.Core.Shortcuts.Add.Sections.Vpn.header, for: .vpn) model.setHeader(L10n.Core.Shortcuts.Add.Sections.Vpn.header, forSection: .vpn)
model.setHeader(L10n.Core.Shortcuts.Add.Sections.Wifi.header, for: .wifi) model.setHeader(L10n.Core.Shortcuts.Add.Sections.Wifi.header, forSection: .wifi)
model.setHeader(L10n.Core.Shortcuts.Add.Sections.Cellular.header, for: .cellular) model.setHeader(L10n.Core.Shortcuts.Add.Sections.Cellular.header, forSection: .cellular)
return model return model
}() }()
@ -85,15 +86,15 @@ class ShortcutsAddViewController: UITableViewController, TableModelHost {
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@ -158,7 +159,7 @@ class ShortcutsAddViewController: UITableViewController, TableModelHost {
private func addConnect() { private func addConnect() {
guard TransientStore.shared.service.hasProfiles() else { guard TransientStore.shared.service.hasProfiles() else {
let alert = Macros.alert( let alert = UIAlertController.asAlert(
L10n.Core.Shortcuts.Add.Cells.Connect.caption, L10n.Core.Shortcuts.Add.Cells.Connect.caption,
L10n.Core.Shortcuts.Add.Alerts.NoProfiles.message L10n.Core.Shortcuts.Add.Alerts.NoProfiles.message
) )

View File

@ -27,9 +27,10 @@ import UIKit
import Intents import Intents
import IntentsUI import IntentsUI
import PassepartoutCore import PassepartoutCore
import Convenience
@available(iOS 12, *) @available(iOS 12, *)
class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewControllerDelegate, TableModelHost { class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewControllerDelegate, StrongTableHost {
private let service = TransientStore.shared.service private let service = TransientStore.shared.service
private var providers: [String] = [] private var providers: [String] = []
@ -40,12 +41,12 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
weak var delegate: ShortcutsIntentDelegate? weak var delegate: ShortcutsIntentDelegate?
// MARK: TableModelHost // MARK: StrongTableHost
let model: TableModel<SectionType, RowType> = { let model: StrongTableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
model.setHeader(L10n.Core.Organizer.Sections.Providers.header, for: .providers) model.setHeader(L10n.Core.Organizer.Sections.Providers.header, forSection: .providers)
model.setHeader(L10n.Core.Organizer.Sections.Hosts.header, for: .hosts) model.setHeader(L10n.Core.Organizer.Sections.Hosts.header, forSection: .hosts)
return model return model
}() }()
@ -55,11 +56,11 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
if !providers.isEmpty { if !providers.isEmpty {
model.add(.providers) model.add(.providers)
model.set(.providerShortcut, count: providers.count, in: .providers) model.set(.providerShortcut, count: providers.count, forSection: .providers)
} }
if !hosts.isEmpty { if !hosts.isEmpty {
model.add(.hosts) model.add(.hosts)
model.set(.hostShortcut, count: hosts.count, in: .hosts) model.set(.hostShortcut, count: hosts.count, forSection: .hosts)
} }
} }
@ -108,15 +109,15 @@ class ShortcutsConnectToViewController: UITableViewController, ProviderPoolViewC
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

View File

@ -27,6 +27,7 @@ import UIKit
import Intents import Intents
import IntentsUI import IntentsUI
import PassepartoutCore import PassepartoutCore
import Convenience
@available(iOS 12, *) @available(iOS 12, *)
protocol ShortcutsIntentDelegate: class { protocol ShortcutsIntentDelegate: class {
@ -63,27 +64,27 @@ private struct ShortcutWrapper: Comparable {
} }
@available(iOS 12, *) @available(iOS 12, *)
class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewControllerDelegate, INUIEditVoiceShortcutViewControllerDelegate, ShortcutsIntentDelegate, TableModelHost { class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewControllerDelegate, INUIEditVoiceShortcutViewControllerDelegate, ShortcutsIntentDelegate, StrongTableHost {
private var wrappers: [ShortcutWrapper]? private var wrappers: [ShortcutWrapper]?
private var pendingShortcut: INShortcut? private var pendingShortcut: INShortcut?
private var editedIndexPath: IndexPath? private var editedIndexPath: IndexPath?
// MARK: TableModel // MARK: StrongTableModel
let model: TableModel<SectionType, RowType> = { let model: StrongTableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel() let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
model.add(.all) model.add(.all)
model.setHeader(L10n.Core.Shortcuts.Edit.Sections.All.header, for: .all) model.setHeader(L10n.Core.Shortcuts.Edit.Sections.All.header, forSection: .all)
model.set([], in: .all) model.set([], forSection: .all)
return model return model
}() }()
func reloadModel() { func reloadModel() {
var rows = [RowType](repeating: .shortcut, count: wrappers?.count ?? 0) var rows = [RowType](repeating: .shortcut, count: wrappers?.count ?? 0)
rows.append(.addShortcut) rows.append(.addShortcut)
model.set(rows, in: .all) model.set(rows, forSection: .all)
} }
// MARK: UIViewController // MARK: UIViewController
@ -119,7 +120,7 @@ class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewCo
private func handleShortcutsFetchError(_ error: Error?) { private func handleShortcutsFetchError(_ error: Error?) {
// TODO: really show it? // TODO: really show it?
// let alert = Macros.alert( // let alert = UIAlertController.asAlert(
// title, // title,
// L10n.Core.Shortcuts.Edit.message(error?.localizedDescription ?? "") // L10n.Core.Shortcuts.Edit.message(error?.localizedDescription ?? "")
// ) // )
@ -167,15 +168,15 @@ class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewCo
} }
override func numberOfSections(in tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return model.count return model.numberOfSections
} }
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return model.header(for: section) return model.header(forSection: section)
} }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section) return model.numberOfRows(forSection: section)
} }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

View File

@ -1,159 +0,0 @@
//
// TableModel.swift
// Passepartout-iOS
//
// Created by Davide De Rosa on 6/25/18.
// Copyright (c) 2019 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
protocol TableModelHost {
associatedtype S: Hashable
associatedtype R: Equatable
var model: TableModel<S, R> { get }
func reloadModel()
}
class TableModel<S: Hashable, R: Equatable> {
private var sections: [S]
private var headerBySection: [S: String]
private var footerBySection: [S: String]
private var rowsBySection: [S: [R]]
init() {
sections = []
headerBySection = [:]
footerBySection = [:]
rowsBySection = [:]
}
func clear() {
sections = []
headerBySection = [:]
footerBySection = [:]
rowsBySection = [:]
}
// MARK: Access
var count: Int {
return sections.count
}
func section(for sectionIndex: Int) -> S {
return sections[sectionIndex]
}
func index(ofSection sectionObject: S) -> Int {
guard let sectionIndex = sections.firstIndex(of: sectionObject) else {
fatalError("Missing section: \(sectionObject)")
}
return sectionIndex
}
func rows(for sectionIndex: Int) -> [R] {
let sectionObject = sections[sectionIndex]
guard let rows = rowsBySection[sectionObject] else {
fatalError("Missing section: \(sectionObject)")
}
return rows
}
func row(at indexPath: IndexPath) -> R {
return rows(for: indexPath.section)[indexPath.row]
}
func count(for sectionIndex: Int) -> Int {
return rows(for: sectionIndex).count
}
func indexPath(row rowObject: R, section sectionObject: S) -> IndexPath? {
guard let sectionIndex = sections.firstIndex(of: sectionObject) else {
return nil
}
guard let row = rowsBySection[sectionObject]?.firstIndex(of: rowObject) else {
return nil
}
return IndexPath(row: row, section: sectionIndex)
}
func header(for sectionIndex: Int) -> String? {
let sectionObject = sections[sectionIndex]
return headerBySection[sectionObject]
}
func header(for sectionObject: S) -> String? {
return headerBySection[sectionObject]
}
func footer(for sectionIndex: Int) -> String? {
let sectionObject = sections[sectionIndex]
return footerBySection[sectionObject]
}
func footer(for sectionObject: S) -> String? {
return footerBySection[sectionObject]
}
// MARK: Modification
func add(_ section: S) {
sections.append(section)
}
func setHeader(_ header: String, for sectionObject: S) {
headerBySection[sectionObject] = header
}
func removeHeader(for sectionObject: S) {
headerBySection.removeValue(forKey: sectionObject)
}
func setFooter(_ footer: String, for sectionObject: S) {
footerBySection[sectionObject] = footer
}
func removeFooter(for sectionObject: S) {
footerBySection.removeValue(forKey: sectionObject)
}
func set(_ rows: [R], in sectionObject: S) {
rowsBySection[sectionObject] = rows
}
func set(_ row: R, count: Int, in sectionObject: S) {
rowsBySection[sectionObject] = [R](repeating: row, count: count)
}
func deleteRow(at indexPath: IndexPath) {
deleteRow(in: section(for: indexPath.section), at: indexPath.row)
}
func deleteRow(in sectionObject: S, at rowIndex: Int) {
rowsBySection[sectionObject]?.remove(at: rowIndex)
}
}

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 50; objectVersion = 51;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -17,7 +17,7 @@
0E1D72B2213BFFCF00BA1586 /* ProviderPresetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B1213BFFCF00BA1586 /* ProviderPresetViewController.swift */; }; 0E1D72B2213BFFCF00BA1586 /* ProviderPresetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B1213BFFCF00BA1586 /* ProviderPresetViewController.swift */; };
0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; }; 0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; };
0E24273A225950450064A1A3 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E24273C225950450064A1A3 /* About.storyboard */; }; 0E24273A225950450064A1A3 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E24273C225950450064A1A3 /* About.storyboard */; };
0E242740225951B00064A1A3 /* InApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E24273F225951B00064A1A3 /* InApp.swift */; }; 0E242740225951B00064A1A3 /* ProductManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E24273F225951B00064A1A3 /* ProductManager.swift */; };
0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E242741225956AC0064A1A3 /* DonationViewController.swift */; }; 0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E242741225956AC0064A1A3 /* DonationViewController.swift */; };
0E2AC24522EC3AC10037B4B0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */; }; 0E2AC24522EC3AC10037B4B0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */; };
0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B493F20FCFF990094784C /* Theme+Titles.swift */; }; 0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B493F20FCFF990094784C /* Theme+Titles.swift */; };
@ -27,7 +27,6 @@
0E3152BB223FA03D00F61841 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCF2214DA9310035E9DE /* AppConstants.swift */; }; 0E3152BB223FA03D00F61841 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCF2214DA9310035E9DE /* AppConstants.swift */; };
0E3152BC223FA03D00F61841 /* ApplicationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6BE13920CFB76800A6DD36 /* ApplicationError.swift */; }; 0E3152BC223FA03D00F61841 /* ApplicationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6BE13920CFB76800A6DD36 /* ApplicationError.swift */; };
0E3152BD223FA03D00F61841 /* GroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */; }; 0E3152BD223FA03D00F61841 /* GroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */; };
0E3152BE223FA03D00F61841 /* Reviewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E78179E21BE852200950C58 /* Reviewer.swift */; };
0E3152C0223FA03D00F61841 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7ED20D539A0002221FF /* Utils.swift */; }; 0E3152C0223FA03D00F61841 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7ED20D539A0002221FF /* Utils.swift */; };
0E3152C1223FA04800F61841 /* GracefulVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5E5DE421511C5F00E318A3 /* GracefulVPN.swift */; }; 0E3152C1223FA04800F61841 /* GracefulVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5E5DE421511C5F00E318A3 /* GracefulVPN.swift */; };
0E3152C2223FA04800F61841 /* MockVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7E020D3E4C5002221FF /* MockVPNProvider.swift */; }; 0E3152C2223FA04800F61841 /* MockVPNProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7E020D3E4C5002221FF /* MockVPNProvider.swift */; };
@ -57,6 +56,7 @@
0E3152DA223FA05800F61841 /* PlaceholderConnectionProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D13E21919EC900BB5FB2 /* PlaceholderConnectionProfile.swift */; }; 0E3152DA223FA05800F61841 /* PlaceholderConnectionProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D13E21919EC900BB5FB2 /* PlaceholderConnectionProfile.swift */; };
0E3152DB223FA05800F61841 /* ProfileKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D14021919F5600BB5FB2 /* ProfileKey.swift */; }; 0E3152DB223FA05800F61841 /* ProfileKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D14021919F5600BB5FB2 /* ProfileKey.swift */; };
0E3152DC223FA05800F61841 /* ProviderConnectionProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE3AA4213DC1B000BFA2F5 /* ProviderConnectionProfile.swift */; }; 0E3152DC223FA05800F61841 /* ProviderConnectionProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE3AA4213DC1B000BFA2F5 /* ProviderConnectionProfile.swift */; };
0E3419AD2350815E00419E18 /* Donation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3419AC2350815E00419E18 /* Donation.swift */; };
0E3586FE225BD34800509A4D /* ActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */; }; 0E3586FE225BD34800509A4D /* ActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */; };
0E36D24D2240234B006AF062 /* ShortcutsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */; }; 0E36D24D2240234B006AF062 /* ShortcutsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */; };
0E36D25822403469006AF062 /* Shortcuts.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E36D25A22403469006AF062 /* Shortcuts.storyboard */; }; 0E36D25822403469006AF062 /* Shortcuts.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E36D25A22403469006AF062 /* Shortcuts.storyboard */; };
@ -90,25 +90,20 @@
0EBE3A79213C4E5500BFA2F5 /* OrganizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */; }; 0EBE3A79213C4E5500BFA2F5 /* OrganizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */; };
0ECC60DE2256B68A0020BEAC /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.swift */; }; 0ECC60DE2256B68A0020BEAC /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.swift */; };
0ECEB10A224FECEA00E9E551 /* DataUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEB109224FECEA00E9E551 /* DataUnit.swift */; }; 0ECEB10A224FECEA00E9E551 /* DataUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEB109224FECEA00E9E551 /* DataUnit.swift */; };
0ECEE44E20E1122200A6BB43 /* TableModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEE44D20E1122200A6BB43 /* TableModel.swift */; };
0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */; }; 0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */; };
0ECF12D7230612F5008E4924 /* InAppHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECF12D6230612F5008E4924 /* InAppHelper.swift */; };
0ED31C2920CF2A340027975F /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2820CF2A340027975F /* AccountViewController.swift */; }; 0ED31C2920CF2A340027975F /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2820CF2A340027975F /* AccountViewController.swift */; };
0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2B20CF2D6F0027975F /* ProviderPoolViewController.swift */; }; 0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2B20CF2D6F0027975F /* ProviderPoolViewController.swift */; };
0ED31C3A20CF39510027975F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED31C3920CF39510027975F /* NetworkExtension.framework */; }; 0ED31C3A20CF39510027975F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED31C3920CF39510027975F /* NetworkExtension.framework */; };
0ED31C3D20CF396E0027975F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED31C3920CF39510027975F /* NetworkExtension.framework */; }; 0ED31C3D20CF396E0027975F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ED31C3920CF39510027975F /* NetworkExtension.framework */; };
0ED38AD9213F33150004D387 /* WizardHostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED38AD8213F33150004D387 /* WizardHostViewController.swift */; }; 0ED38AD9213F33150004D387 /* WizardHostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED38AD8213F33150004D387 /* WizardHostViewController.swift */; };
0ED38ADA213F44D00004D387 /* Organizer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0ED38ADC213F44D00004D387 /* Organizer.storyboard */; }; 0ED38ADA213F44D00004D387 /* Organizer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0ED38ADC213F44D00004D387 /* Organizer.storyboard */; };
0ED38AEA214054A50004D387 /* OptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED38AE9214054A50004D387 /* OptionViewController.swift */; };
0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED38AEB2141260D0004D387 /* ConfigurationModificationDelegate.swift */; }; 0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED38AEB2141260D0004D387 /* ConfigurationModificationDelegate.swift */; };
0ED993B1223FF8C700B0F9C9 /* IntentDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED993B0223FF8C700B0F9C9 /* IntentDispatcher.swift */; }; 0ED993B1223FF8C700B0F9C9 /* IntentDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED993B0223FF8C700B0F9C9 /* IntentDispatcher.swift */; };
0EDE8DC420C86910004C739C /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DC320C86910004C739C /* PacketTunnelProvider.swift */; }; 0EDE8DC420C86910004C739C /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DC320C86910004C739C /* PacketTunnelProvider.swift */; };
0EDE8DC820C86910004C739C /* Passepartout-Tunnel.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0EDE8DBF20C86910004C739C /* Passepartout-Tunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 0EDE8DC820C86910004C739C /* Passepartout-Tunnel.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0EDE8DBF20C86910004C739C /* Passepartout-Tunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
0EE3BBB2215ED3A900F30952 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */; }; 0EE3BBB2215ED3A900F30952 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */; };
0EEB53B2225D525B00746300 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EEB53B1225D525B00746300 /* Downloader.swift */; };
0EEF23412321AC55000AEBE3 /* Issue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EEF23402321AC55000AEBE3 /* Issue.swift */; }; 0EEF23412321AC55000AEBE3 /* Issue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EEF23402321AC55000AEBE3 /* Issue.swift */; };
0EF56BBB2185AC8500B0C8AB /* SwiftGen+Segues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */; }; 0EF56BBB2185AC8500B0C8AB /* SwiftGen+Segues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */; };
0EF5CF252141CE58004FF1BD /* HUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF5CF242141CE58004FF1BD /* HUD.swift */; };
0EF5CF292141F31F004FF1BD /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7ED20D539A0002221FF /* Utils.swift */; }; 0EF5CF292141F31F004FF1BD /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E4FD7ED20D539A0002221FF /* Utils.swift */; };
0EFB901822764689006405E4 /* ProfileNetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB901722764689006405E4 /* ProfileNetworkSettings.swift */; }; 0EFB901822764689006405E4 /* ProfileNetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB901722764689006405E4 /* ProfileNetworkSettings.swift */; };
0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */; }; 0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */; };
@ -172,7 +167,7 @@
0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = "<group>"; }; 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = "<group>"; };
0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; }; 0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
0E24273B225950450064A1A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/About.storyboard; sourceTree = "<group>"; }; 0E24273B225950450064A1A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/About.storyboard; sourceTree = "<group>"; };
0E24273F225951B00064A1A3 /* InApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InApp.swift; sourceTree = "<group>"; }; 0E24273F225951B00064A1A3 /* ProductManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductManager.swift; sourceTree = "<group>"; };
0E242741225956AC0064A1A3 /* DonationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationViewController.swift; sourceTree = "<group>"; }; 0E242741225956AC0064A1A3 /* DonationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationViewController.swift; sourceTree = "<group>"; };
0E2AC24422EC3AC10037B4B0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; }; 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = "<group>"; }; 0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = "<group>"; };
@ -184,6 +179,7 @@
0E31529B223F9EF400F61841 /* PassepartoutCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PassepartoutCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E31529B223F9EF400F61841 /* PassepartoutCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PassepartoutCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0E31529D223F9EF500F61841 /* PassepartoutCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PassepartoutCore.h; sourceTree = "<group>"; }; 0E31529D223F9EF500F61841 /* PassepartoutCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PassepartoutCore.h; sourceTree = "<group>"; };
0E31529E223F9EF500F61841 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 0E31529E223F9EF500F61841 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0E3419AC2350815E00419E18 /* Donation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Donation.swift; sourceTree = "<group>"; };
0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTableViewCell.swift; sourceTree = "<group>"; }; 0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTableViewCell.swift; sourceTree = "<group>"; };
0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsAddViewController.swift; sourceTree = "<group>"; }; 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsAddViewController.swift; sourceTree = "<group>"; };
0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConnectToViewController.swift; sourceTree = "<group>"; }; 0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConnectToViewController.swift; sourceTree = "<group>"; };
@ -243,7 +239,6 @@
0E77663E229D0DA60023FA76 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = "<group>"; }; 0E77663E229D0DA60023FA76 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = "<group>"; };
0E77663F229D0DA70023FA76 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Intents.strings; sourceTree = "<group>"; }; 0E77663F229D0DA70023FA76 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Intents.strings; sourceTree = "<group>"; };
0E776640229D0DA80023FA76 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = "<group>"; }; 0E776640229D0DA80023FA76 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = "<group>"; };
0E78179E21BE852200950C58 /* Reviewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reviewer.swift; sourceTree = "<group>"; };
0E79D13E21919EC900BB5FB2 /* PlaceholderConnectionProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderConnectionProfile.swift; sourceTree = "<group>"; }; 0E79D13E21919EC900BB5FB2 /* PlaceholderConnectionProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderConnectionProfile.swift; sourceTree = "<group>"; };
0E79D14021919F5600BB5FB2 /* ProfileKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileKey.swift; sourceTree = "<group>"; }; 0E79D14021919F5600BB5FB2 /* ProfileKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileKey.swift; sourceTree = "<group>"; };
0E89DFC4213DF7AE00741BA1 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; }; 0E89DFC4213DF7AE00741BA1 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
@ -270,9 +265,7 @@
0ECEB107224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Shortcuts.storyboard; sourceTree = "<group>"; }; 0ECEB107224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Shortcuts.storyboard; sourceTree = "<group>"; };
0ECEB108224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 0ECEB108224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
0ECEB109224FECEA00E9E551 /* DataUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUnit.swift; sourceTree = "<group>"; }; 0ECEB109224FECEA00E9E551 /* DataUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUnit.swift; sourceTree = "<group>"; };
0ECEE44D20E1122200A6BB43 /* TableModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableModel.swift; sourceTree = "<group>"; };
0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Cells.swift"; sourceTree = "<group>"; }; 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Cells.swift"; sourceTree = "<group>"; };
0ECF12D6230612F5008E4924 /* InAppHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppHelper.swift; path = Submodules/Core/Passepartout/Sources/InAppHelper.swift; sourceTree = SOURCE_ROOT; };
0ED31C0F20CF09A30027975F /* Pool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pool.swift; sourceTree = "<group>"; }; 0ED31C0F20CF09A30027975F /* Pool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pool.swift; sourceTree = "<group>"; };
0ED31C1120CF0ABA0027975F /* Infrastructure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Infrastructure.swift; sourceTree = "<group>"; }; 0ED31C1120CF0ABA0027975F /* Infrastructure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Infrastructure.swift; sourceTree = "<group>"; };
0ED31C2820CF2A340027975F /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; }; 0ED31C2820CF2A340027975F /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; };
@ -282,7 +275,6 @@
0ED31C3B20CF39510027975F /* Tunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tunnel.entitlements; sourceTree = "<group>"; }; 0ED31C3B20CF39510027975F /* Tunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tunnel.entitlements; sourceTree = "<group>"; };
0ED38AD8213F33150004D387 /* WizardHostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardHostViewController.swift; sourceTree = "<group>"; }; 0ED38AD8213F33150004D387 /* WizardHostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardHostViewController.swift; sourceTree = "<group>"; };
0ED38AE621404F100004D387 /* EndpointDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointDataSource.swift; sourceTree = "<group>"; }; 0ED38AE621404F100004D387 /* EndpointDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointDataSource.swift; sourceTree = "<group>"; };
0ED38AE9214054A50004D387 /* OptionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionViewController.swift; sourceTree = "<group>"; };
0ED38AEB2141260D0004D387 /* ConfigurationModificationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationModificationDelegate.swift; sourceTree = "<group>"; }; 0ED38AEB2141260D0004D387 /* ConfigurationModificationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationModificationDelegate.swift; sourceTree = "<group>"; };
0ED38AF1214177920004D387 /* VPNProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNProvider.swift; sourceTree = "<group>"; }; 0ED38AF1214177920004D387 /* VPNProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNProvider.swift; sourceTree = "<group>"; };
0ED824C920D12B8700F2FE9E /* ToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleTableViewCell.swift; sourceTree = "<group>"; }; 0ED824C920D12B8700F2FE9E /* ToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleTableViewCell.swift; sourceTree = "<group>"; };
@ -297,10 +289,8 @@
0EDE8DE620C93945004C739C /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = "<group>"; }; 0EDE8DE620C93945004C739C /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = "<group>"; };
0EDE8DED20C93E4C004C739C /* GroupConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupConstants.swift; sourceTree = "<group>"; }; 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupConstants.swift; sourceTree = "<group>"; };
0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; }; 0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
0EEB53B1225D525B00746300 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = "<group>"; };
0EEF23402321AC55000AEBE3 /* Issue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Issue.swift; path = Submodules/Core/Passepartout/Sources/Issue.swift; sourceTree = SOURCE_ROOT; }; 0EEF23402321AC55000AEBE3 /* Issue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Issue.swift; path = Submodules/Core/Passepartout/Sources/Issue.swift; sourceTree = SOURCE_ROOT; };
0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Segues.swift"; sourceTree = "<group>"; }; 0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftGen+Segues.swift"; sourceTree = "<group>"; };
0EF5CF242141CE58004FF1BD /* HUD.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HUD.swift; sourceTree = "<group>"; };
0EFB901722764689006405E4 /* ProfileNetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNetworkSettings.swift; sourceTree = "<group>"; }; 0EFB901722764689006405E4 /* ProfileNetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNetworkSettings.swift; sourceTree = "<group>"; };
0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettingsViewController.swift; sourceTree = "<group>"; }; 0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettingsViewController.swift; sourceTree = "<group>"; };
0EFBFAC021AC464800887A8C /* CreditsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsViewController.swift; sourceTree = "<group>"; }; 0EFBFAC021AC464800887A8C /* CreditsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsViewController.swift; sourceTree = "<group>"; };
@ -452,7 +442,6 @@
children = ( children = (
0EDE8DEC20C93E3B004C739C /* Global */, 0EDE8DEC20C93E3B004C739C /* Global */,
0E1066CA20E0F85C004F98B7 /* Cells */, 0E1066CA20E0F85C004F98B7 /* Cells */,
0ECEE44C20E1120F00A6BB43 /* Tables */,
0EDE8DF120C93ED8004C739C /* Scenes */, 0EDE8DF120C93ED8004C739C /* Scenes */,
0E23B4A12298559800304C30 /* Config.xcconfig */, 0E23B4A12298559800304C30 /* Config.xcconfig */,
0EDE8DE220C86A13004C739C /* Passepartout.entitlements */, 0EDE8DE220C86A13004C739C /* Passepartout.entitlements */,
@ -494,14 +483,6 @@
path = Profiles; path = Profiles;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0ECEE44C20E1120F00A6BB43 /* Tables */ = {
isa = PBXGroup;
children = (
0ECEE44D20E1122200A6BB43 /* TableModel.swift */,
);
path = Tables;
sourceTree = "<group>";
};
0ED31C0E20CF09890027975F /* Model */ = { 0ED31C0E20CF09890027975F /* Model */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -554,12 +535,10 @@
children = ( children = (
0E45E6E222BD793800F19312 /* App.strings */, 0E45E6E222BD793800F19312 /* App.strings */,
0EA068F3218475F800C320AD /* ConfigurationParserResult+Alerts.swift */, 0EA068F3218475F800C320AD /* ConfigurationParserResult+Alerts.swift */,
0EEB53B1225D525B00746300 /* Downloader.swift */, 0E3419AC2350815E00419E18 /* Donation.swift */,
0EF5CF242141CE58004FF1BD /* HUD.swift */,
0E24273F225951B00064A1A3 /* InApp.swift */,
0EFD943D215BE10800529B64 /* IssueReporter.swift */, 0EFD943D215BE10800529B64 /* IssueReporter.swift */,
0E4FD7F020D58618002221FF /* Macros.swift */, 0E4FD7F020D58618002221FF /* Macros.swift */,
0ED38AE9214054A50004D387 /* OptionViewController.swift */, 0E24273F225951B00064A1A3 /* ProductManager.swift */,
0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.swift */, 0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.swift */,
0EDE8DE320C89028004C739C /* SwiftGen+Scenes.swift */, 0EDE8DE320C89028004C739C /* SwiftGen+Scenes.swift */,
0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */, 0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */,
@ -591,9 +570,7 @@
0E39BCF2214DA9310035E9DE /* AppConstants.swift */, 0E39BCF2214DA9310035E9DE /* AppConstants.swift */,
0E6BE13920CFB76800A6DD36 /* ApplicationError.swift */, 0E6BE13920CFB76800A6DD36 /* ApplicationError.swift */,
0EDE8DED20C93E4C004C739C /* GroupConstants.swift */, 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */,
0ECF12D6230612F5008E4924 /* InAppHelper.swift */,
0EEF23402321AC55000AEBE3 /* Issue.swift */, 0EEF23402321AC55000AEBE3 /* Issue.swift */,
0E78179E21BE852200950C58 /* Reviewer.swift */,
0E4FD7ED20D539A0002221FF /* Utils.swift */, 0E4FD7ED20D539A0002221FF /* Utils.swift */,
); );
path = Sources; path = Sources;
@ -965,10 +942,8 @@
0E3152CC223FA04D00F61841 /* WebServices.swift in Sources */, 0E3152CC223FA04D00F61841 /* WebServices.swift in Sources */,
0E3152BB223FA03D00F61841 /* AppConstants.swift in Sources */, 0E3152BB223FA03D00F61841 /* AppConstants.swift in Sources */,
0E3152CA223FA04D00F61841 /* InfrastructurePreset.swift in Sources */, 0E3152CA223FA04D00F61841 /* InfrastructurePreset.swift in Sources */,
0ECF12D7230612F5008E4924 /* InAppHelper.swift in Sources */,
0E3152CE223FA05400F61841 /* ConnectionService.swift in Sources */, 0E3152CE223FA05400F61841 /* ConnectionService.swift in Sources */,
0ED993B1223FF8C700B0F9C9 /* IntentDispatcher.swift in Sources */, 0ED993B1223FF8C700B0F9C9 /* IntentDispatcher.swift in Sources */,
0E3152BE223FA03D00F61841 /* Reviewer.swift in Sources */,
0EEF23412321AC55000AEBE3 /* Issue.swift in Sources */, 0EEF23412321AC55000AEBE3 /* Issue.swift in Sources */,
0E3152C3223FA04800F61841 /* StandardVPNProvider.swift in Sources */, 0E3152C3223FA04800F61841 /* StandardVPNProvider.swift in Sources */,
0E3152D1223FA05400F61841 /* Credentials.swift in Sources */, 0E3152D1223FA05400F61841 /* Credentials.swift in Sources */,
@ -989,12 +964,10 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0ECEE44E20E1122200A6BB43 /* TableModel.swift in Sources */,
0ED38AD9213F33150004D387 /* WizardHostViewController.swift in Sources */, 0ED38AD9213F33150004D387 /* WizardHostViewController.swift in Sources */,
0EE3BBB2215ED3A900F30952 /* AboutViewController.swift in Sources */, 0EE3BBB2215ED3A900F30952 /* AboutViewController.swift in Sources */,
0EBE3A79213C4E5500BFA2F5 /* OrganizerViewController.swift in Sources */, 0EBE3A79213C4E5500BFA2F5 /* OrganizerViewController.swift in Sources */,
0E4FD7F120D58618002221FF /* Macros.swift in Sources */, 0E4FD7F120D58618002221FF /* Macros.swift in Sources */,
0EF5CF252141CE58004FF1BD /* HUD.swift in Sources */,
0E45E6E422BD799700F19312 /* SwiftGen+Strings.swift in Sources */, 0E45E6E422BD799700F19312 /* SwiftGen+Strings.swift in Sources */,
0E05C5D720D1645F006EE732 /* ToggleTableViewCell.swift in Sources */, 0E05C5D720D1645F006EE732 /* ToggleTableViewCell.swift in Sources */,
0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */, 0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */,
@ -1005,14 +978,14 @@
0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */, 0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */,
0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */, 0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */,
0E776642229D0DAE0023FA76 /* Intents.intentdefinition in Sources */, 0E776642229D0DAE0023FA76 /* Intents.intentdefinition in Sources */,
0EEB53B2225D525B00746300 /* Downloader.swift in Sources */,
0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */, 0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */,
0E242740225951B00064A1A3 /* InApp.swift in Sources */, 0E242740225951B00064A1A3 /* ProductManager.swift in Sources */,
0E1066C920E0F84A004F98B7 /* Cells.swift in Sources */, 0E1066C920E0F84A004F98B7 /* Cells.swift in Sources */,
0EF56BBB2185AC8500B0C8AB /* SwiftGen+Segues.swift in Sources */, 0EF56BBB2185AC8500B0C8AB /* SwiftGen+Segues.swift in Sources */,
0E3DA371215CB5BF00B40FC9 /* VersionViewController.swift in Sources */, 0E3DA371215CB5BF00B40FC9 /* VersionViewController.swift in Sources */,
0E05C5D620D1645F006EE732 /* SwiftGen+Scenes.swift in Sources */, 0E05C5D620D1645F006EE732 /* SwiftGen+Scenes.swift in Sources */,
0E773BF8224BF37600CDDC8E /* ShortcutsViewController.swift in Sources */, 0E773BF8224BF37600CDDC8E /* ShortcutsViewController.swift in Sources */,
0E3419AD2350815E00419E18 /* Donation.swift in Sources */,
0EFD9440215BED8E00529B64 /* LabelViewController.swift in Sources */, 0EFD9440215BED8E00529B64 /* LabelViewController.swift in Sources */,
0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */, 0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */,
0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */, 0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */,
@ -1020,7 +993,6 @@
0E89DFCE213EEDFA00741BA1 /* WizardProviderViewController.swift in Sources */, 0E89DFCE213EEDFA00741BA1 /* WizardProviderViewController.swift in Sources */,
0E1D72B2213BFFCF00BA1586 /* ProviderPresetViewController.swift in Sources */, 0E1D72B2213BFFCF00BA1586 /* ProviderPresetViewController.swift in Sources */,
0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */, 0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */,
0ED38AEA214054A50004D387 /* OptionViewController.swift in Sources */,
0EFBFAC121AC464800887A8C /* CreditsViewController.swift in Sources */, 0EFBFAC121AC464800887A8C /* CreditsViewController.swift in Sources */,
0EFD943E215BE10800529B64 /* IssueReporter.swift in Sources */, 0EFD943E215BE10800529B64 /* IssueReporter.swift in Sources */,
0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */, 0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */,