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

View File

@ -44,11 +44,11 @@ extension OpenVPN.ConfigurationParser.Result {
} catch let e as ConfigurationError {
switch e {
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
field.isSecureTextEntry = true
}
alert.addDefaultAction(L10n.Core.Global.ok) {
alert.addPreferredAction(L10n.Core.Global.ok) {
guard let passphrase = alert.textFields?.first?.text else {
return
}
@ -79,8 +79,8 @@ extension OpenVPN.ConfigurationParser.Result {
}
private static func alertImportError(url: URL, in vc: UIViewController, withMessage message: String) {
let alert = Macros.alert(url.normalizedFilename, message)
// alert.addDefaultAction(L10n.Core.ParsedFile.Alerts.Buttons.report) {
let alert = UIAlertController.asAlert(url.normalizedFilename, message)
// alert.addPreferredAction(L10n.Core.ParsedFile.Alerts.Buttons.report) {
// var attach = IssueReporter.Attachments(debugLog: false, configurationURL: url)
// attach.description = message
// 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) {
let message = details(forWarning: warning)
let alert = Macros.alert(url.normalizedFilename, L10n.Core.ParsedFile.Alerts.PotentiallyUnsupported.message(message))
alert.addDefaultAction(L10n.Core.Global.ok) {
let alert = UIAlertController.asAlert(url.normalizedFilename, L10n.Core.ParsedFile.Alerts.PotentiallyUnsupported.message(message))
alert.addPreferredAction(L10n.Core.Global.ok) {
completionHandler(true)
}
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 V = AppConstants.IssueReporter.Email.self
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 {
let alert = Macros.alert(L10n.Core.IssueReporter.title, L10n.Core.Global.emailNotConfigured)
guard let url = URL.mailto(to: V.recipient, subject: V.subject, body: body), app.canOpenURL(url) else {
let alert = UIAlertController.asAlert(L10n.Core.IssueReporter.title, L10n.Core.Global.emailNotConfigured)
alert.addCancelAction(L10n.Core.Global.ok)
viewController.present(alert, animated: true, completion: nil)
return
@ -55,8 +55,8 @@ class IssueReporter: NSObject {
self.viewController = viewController
if issue.debugLog {
let alert = Macros.alert(L10n.Core.IssueReporter.title, L10n.Core.IssueReporter.message)
alert.addDefaultAction(L10n.Core.IssueReporter.Buttons.accept) {
let alert = UIAlertController.asAlert(L10n.Core.IssueReporter.title, L10n.Core.IssueReporter.message)
alert.addPreferredAction(L10n.Core.IssueReporter.Buttons.accept) {
VPN.shared.requestDebugLog(fallback: AppConstants.Log.debugSnapshot) {
self.composeEmail(withDebugLog: $0, configurationURL: issue.configurationURL, description: issue.description)
}

View File

@ -25,55 +25,6 @@
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 {
static func get<T: UIView>() -> T {
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
//
// Created by Davide De Rosa on 4/6/19.
@ -25,32 +25,28 @@
import Foundation
import StoreKit
import Convenience
struct InApp {
enum Donation: String {
static let all: [Donation] = [
.tiny,
.small,
.medium,
.big,
.huge,
.maxi
]
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"
struct ProductManager {
static let shared = ProductManager()
private let inApp: InApp<Donation>
private init() {
inApp = InApp()
}
func listProducts(completionHandler: (([SKProduct]) -> Void)?) {
guard inApp.products.isEmpty else {
completionHandler?(inApp.products)
return
}
inApp.requestProducts(withIdentifiers: Donation.all) { _ in
completionHandler?(self.inApp.products)
}
}
static func allIdentifiers() -> Set<String> {
return Set<String>(Donation.all.map { $0.rawValue })
func purchase(_ product: SKProduct, completionHandler: @escaping (InAppPurchaseResult, Error?) -> Void) {
inApp.purchase(product: product, completionHandler: completionHandler)
}
}

View File

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

View File

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

View File

@ -25,8 +25,9 @@
import UIKit
import PassepartoutCore
import Convenience
class CreditsViewController: UITableViewController, TableModelHost {
class CreditsViewController: UITableViewController, StrongTableHost {
private let licenses = AppConstants.License.all
private let notices = AppConstants.Notice.all
@ -35,22 +36,22 @@ class CreditsViewController: UITableViewController, TableModelHost {
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() {
model.add(.licenses)
model.add(.notices)
model.add(.translations)
model.setHeader(L10n.Core.Credits.Sections.Licenses.header, for: .licenses)
model.setHeader(L10n.Core.Credits.Sections.Notices.header, for: .notices)
model.setHeader(L10n.Core.Credits.Sections.Translations.header, for: .translations)
model.setHeader(L10n.Core.Credits.Sections.Licenses.header, forSection: .licenses)
model.setHeader(L10n.Core.Credits.Sections.Notices.header, forSection: .notices)
model.setHeader(L10n.Core.Credits.Sections.Translations.header, forSection: .translations)
model.set(.license, count: licenses.count, in: .licenses)
model.set(.notice, count: notices.count, in: .notices)
model.set(.translation, count: languages.count, in: .translations)
model.set(.license, count: licenses.count, forSection: .licenses)
model.set(.notice, count: notices.count, forSection: .notices)
model.set(.translation, count: languages.count, forSection: .translations)
}
// MARK: UIViewController
@ -108,15 +109,15 @@ extension CreditsViewController {
}
override func numberOfSections(in tableView: UITableView) -> Int {
return model.count
return model.numberOfSections
}
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 {
return model.count(for: section)
return model.numberOfRows(forSection: section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

View File

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

View File

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

View File

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

View File

@ -84,7 +84,7 @@ class DebugLogViewController: UIViewController {
@IBAction private func share(_ sender: Any?) {
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)
present(alert, animated: true, completion: nil)
return

View File

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

View File

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

View File

@ -26,9 +26,10 @@
import UIKit
import StoreKit
import PassepartoutCore
import Convenience
class DonationViewController: UITableViewController, TableModelHost {
private var donationList: [InApp.Donation] = []
class DonationViewController: UITableViewController, StrongTableHost {
private var donationList: [Donation] = []
private var productsByIdentifier: [String: SKProduct] = [:]
@ -44,35 +45,34 @@ class DonationViewController: UITableViewController, TableModelHost {
tableView.reloadData()
}
// MARK: TableModel
// MARK: StrongTableModel
var model: TableModel<SectionType, RowType> = TableModel()
var model: StrongTableModel<SectionType, RowType> = StrongTableModel()
func reloadModel() {
donationList = []
model.clear()
model.add(.oneTime)
model.setHeader(L10n.Core.Donation.Sections.OneTime.header, for: .oneTime)
model.setFooter(L10n.Core.Donation.Sections.OneTime.footer, for: .oneTime)
model.setHeader(L10n.Core.Donation.Sections.OneTime.header, forSection: .oneTime)
model.setFooter(L10n.Core.Donation.Sections.OneTime.footer, forSection: .oneTime)
guard !isLoading else {
model.set([.loading], in: .oneTime)
model.set([.loading], forSection: .oneTime)
return
}
let completeList: [InApp.Donation] = [.tiny, .small, .medium, .big, .huge, .maxi]
for row in completeList {
for row in Donation.all {
guard let _ = productsByIdentifier[row.rawValue] else {
continue
}
donationList.append(row)
}
model.set(.donation, count: donationList.count, in: .oneTime)
model.set(.donation, count: donationList.count, forSection: .oneTime)
if isPurchasing {
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
reloadModel()
let inApp = InAppHelper.shared
if inApp.products.isEmpty {
inApp.requestProducts(withIdentifiers: InApp.allIdentifiers()) {
self.isLoading = false
self.setProducts($0)
}
} else {
isLoading = false
setProducts(inApp.products)
ProductManager.shared.listProducts {
self.isLoading = false
self.setProducts($0)
}
}
@ -103,19 +97,19 @@ class DonationViewController: UITableViewController, TableModelHost {
// MARK: UITableViewController
override func numberOfSections(in tableView: UITableView) -> Int {
return model.count
return model.numberOfSections
}
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? {
return model.footer(for: section)
return model.footer(forSection: section)
}
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 {
@ -159,7 +153,7 @@ class DonationViewController: UITableViewController, TableModelHost {
reloadModel()
tableView.reloadData()
InAppHelper.shared.purchase(product: product) {
ProductManager.shared.purchase(product) {
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
switch result {
case .cancelled:
@ -178,10 +172,10 @@ class DonationViewController: UITableViewController, TableModelHost {
return
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:
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) {
self.isPurchasing = false

View File

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

View File

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

View File

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

View File

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

View File

@ -28,8 +28,9 @@ import NetworkExtension
import MBProgressHUD
import TunnelKit
import PassepartoutCore
import Convenience
class ServiceViewController: UIViewController, TableModelHost {
class ServiceViewController: UIViewController, StrongTableHost {
@IBOutlet private weak var tableView: UITableView!
@IBOutlet private weak var viewWelcome: UIView!
@ -38,6 +39,11 @@ class ServiceViewController: UIViewController, TableModelHost {
@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 let service = TransientStore.shared.service
@ -54,7 +60,7 @@ class ServiceViewController: UIViewController, TableModelHost {
// MARK: Table
var model: TableModel<SectionType, RowType> = TableModel()
var model: StrongTableModel<SectionType, RowType> = StrongTableModel()
private let trustedNetworks = TrustedNetworksModel()
@ -217,13 +223,13 @@ class ServiceViewController: UIViewController, TableModelHost {
}
@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
field.text = self.profile?.id
field.applyProfileId(Theme.current)
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 {
return
}
@ -245,7 +251,7 @@ class ServiceViewController: UIViewController, TableModelHost {
IntentDispatcher.donateConnection(with: uncheckedProfile)
}
guard !service.needsCredentials(for: uncheckedProfile) else {
let alert = Macros.alert(
let alert = UIAlertController.asAlert(
L10n.App.Service.Sections.Vpn.header,
L10n.Core.Service.Alerts.CredentialsNeeded.message
)
@ -283,11 +289,11 @@ class ServiceViewController: UIViewController, TableModelHost {
private func confirmVpnReconnection() {
guard vpn.status == .disconnected else {
let alert = Macros.alert(
let alert = UIAlertController.asAlert(
L10n.Core.Service.Cells.ConnectionStatus.caption,
L10n.Core.Service.Alerts.ReconnectVpn.message
)
alert.addDefaultAction(L10n.Core.Global.ok) {
alert.addPreferredAction(L10n.Core.Global.ok) {
self.vpn.reconnect(completionHandler: nil)
}
alert.addCancelAction(L10n.Core.Global.cancel)
@ -300,7 +306,7 @@ class ServiceViewController: UIViewController, TableModelHost {
private func refreshProviderInfrastructure() {
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
hud.hide()
guard let response = response else {
@ -347,11 +353,11 @@ class ServiceViewController: UIViewController, TableModelHost {
completionHandler()
return
}
let alert = Macros.alert(
let alert = UIAlertController.asAlert(
L10n.Core.Service.Sections.Trusted.header,
L10n.Core.Service.Alerts.Trusted.WillDisconnectPolicy.message
)
alert.addDefaultAction(L10n.Core.Global.ok) {
alert.addPreferredAction(L10n.Core.Global.ok) {
completionHandler()
}
alert.addCancelAction(L10n.Core.Global.cancel) {
@ -361,11 +367,11 @@ class ServiceViewController: UIViewController, TableModelHost {
}
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.Alerts.Trusted.WillDisconnectTrusted.message
)
alert.addDefaultAction(L10n.Core.Global.ok) {
alert.addPreferredAction(L10n.Core.Global.ok) {
completionHandler()
}
alert.addCancelAction(L10n.Core.Global.cancel) {
@ -380,12 +386,12 @@ class ServiceViewController: UIViewController, TableModelHost {
}
private func testInternetConnectivity() {
let hud = HUD()
let hud = HUD(view: view.window!)
Utils.checkConnectivityURL(AppConstants.Web.connectivityURL, timeout: AppConstants.Web.connectivityTimeout) {
hud.hide()
let V = L10n.Core.Service.Alerts.TestConnectivity.Messages.self
let alert = Macros.alert(
let alert = UIAlertController.asAlert(
L10n.Core.Service.Alerts.TestConnectivity.title,
$0 ? V.success : V.failure
)
@ -396,7 +402,7 @@ class ServiceViewController: UIViewController, TableModelHost {
// private func displayDataCount() {
// guard vpn.isEnabled else {
// let alert = Macros.alert(
// let alert = UIAlertController.asAlert(
// L10n.Core.Service.Cells.DataCount.caption,
// L10n.Core.Service.Alerts.DataCount.Messages.notAvailable
// )
@ -412,7 +418,7 @@ class ServiceViewController: UIViewController, TableModelHost {
// } else {
// message = L10n.Core.Service.Alerts.DataCount.Messages.notAvailable
// }
// let alert = Macros.alert(
// let alert = UIAlertController.asAlert(
// L10n.Core.Service.Cells.DataCount.caption,
// message
// )
@ -428,7 +434,7 @@ class ServiceViewController: UIViewController, TableModelHost {
}
guard vpn.status == .disconnected else {
let alert = Macros.alert(
let alert = UIAlertController.asAlert(
L10n.Core.Service.Cells.MasksPrivateData.caption,
L10n.Core.Service.Alerts.MasksPrivateData.Messages.mustReconnect
)
@ -462,26 +468,26 @@ class ServiceViewController: UIViewController, TableModelHost {
return
}
let alert = Macros.alert(
let alert = UIAlertController.asAlert(
L10n.Core.Service.Alerts.Download.title,
L10n.Core.Service.Alerts.Download.message(providerProfile.name.rawValue)
)
alert.addCancelAction(L10n.Core.Global.cancel)
alert.addDefaultAction(L10n.Core.Global.ok) {
alert.addPreferredAction(L10n.Core.Global.ok) {
self.confirmDownload(URL(string: downloadURL)!)
}
present(alert, animated: true, completion: nil)
}
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)
}
}
private func handleDownloadedProviderResources(url: URL?, error: Error?) {
guard let url = url else {
let alert = Macros.alert(
let alert = UIAlertController.asAlert(
L10n.Core.Service.Alerts.Download.title,
L10n.Core.Service.Alerts.Download.failed(error?.localizedDescription ?? "")
)
@ -490,7 +496,7 @@ class ServiceViewController: UIViewController, TableModelHost {
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()
uncheckedProviderProfile.name.importExternalResources(from: url) {
hud.hide()
@ -615,22 +621,22 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
}
private var statusIndexPath: IndexPath? {
return model.indexPath(row: .connectionStatus, section: .vpn)
return model.indexPath(forRow: .connectionStatus, ofSection: .vpn)
}
private var dataCountIndexPath: IndexPath? {
return model.indexPath(row: .dataCount, section: .diagnostics)
return model.indexPath(forRow: .dataCount, ofSection: .diagnostics)
}
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")
}
return ip
}
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")
}
return ip
@ -650,19 +656,19 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
}
func numberOfSections(in tableView: UITableView) -> Int {
return model.count
return model.numberOfSections
}
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? {
let rows = model.rows(for: section)
let rows = model.rows(forSection: section)
if rows.contains(.providerRefresh), let date = lastInfrastructureUpdate {
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 {
@ -670,7 +676,7 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
}
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 {
@ -931,7 +937,7 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
}
guard trustedNetworks.addCurrentWifi() else {
let alert = Macros.alert(
let alert = UIAlertController.asAlert(
L10n.Core.Service.Sections.Trusted.header,
L10n.Core.Service.Alerts.Trusted.NoNetwork.message
)
@ -1033,31 +1039,31 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
}
// headers
model.setHeader(L10n.App.Service.Sections.Vpn.header, for: .vpn)
model.setHeader(L10n.App.Service.Sections.Vpn.header, forSection: .vpn)
if isProvider {
model.setHeader(L10n.App.Service.Sections.Configuration.header, for: .authentication)
model.setHeader(L10n.App.Service.Sections.Configuration.header, forSection: .authentication)
} else {
model.setHeader(L10n.App.Service.Sections.Configuration.header, for: .configuration)
model.setHeader(L10n.App.Service.Sections.Configuration.header, forSection: .configuration)
}
if isActiveProfile {
if isProvider {
model.setHeader("", for: .vpnResolvesHostname)
model.setHeader("", for: .vpnSurvivesSleep)
model.setHeader("", forSection: .vpnResolvesHostname)
model.setHeader("", forSection: .vpnSurvivesSleep)
}
model.setHeader(L10n.Core.Service.Sections.Trusted.header, for: .trusted)
model.setHeader(L10n.Core.Service.Sections.Diagnostics.header, for: .diagnostics)
model.setHeader(L10n.Core.Organizer.Sections.Feedback.header, for: .feedback)
model.setHeader(L10n.Core.Service.Sections.Trusted.header, forSection: .trusted)
model.setHeader(L10n.Core.Service.Sections.Diagnostics.header, forSection: .diagnostics)
model.setHeader(L10n.Core.Organizer.Sections.Feedback.header, forSection: .feedback)
}
// footers
if isActiveProfile {
model.setFooter(L10n.Core.Service.Sections.Vpn.footer, for: .vpn)
model.setFooter(L10n.Core.Service.Sections.Vpn.footer, forSection: .vpn)
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.Trusted.footer, for: .trustedPolicy)
model.setFooter(L10n.Core.Service.Sections.Diagnostics.footer, for: .diagnostics)
model.setFooter(L10n.Core.Service.Sections.VpnSurvivesSleep.footer, forSection: .vpnSurvivesSleep)
model.setFooter(L10n.Core.Service.Sections.Trusted.footer, forSection: .trustedPolicy)
model.setFooter(L10n.Core.Service.Sections.Diagnostics.footer, forSection: .diagnostics)
}
// rows
@ -1066,30 +1072,30 @@ extension ServiceViewController: UITableViewDataSource, UITableViewDelegate, Tog
if vpn.isEnabled {
rows.append(.reconnect)
}
model.set(rows, in: .vpn)
model.set(rows, forSection: .vpn)
} else {
model.set([.useProfile], in: .vpn)
model.set([.useProfile], forSection: .vpn)
}
if isProvider {
model.set([.account], in: .authentication)
model.set([.providerPool, .endpoint, .providerPreset, .networkSettings], in: .configuration)
model.set([.providerRefresh], in: .providerInfrastructure)
model.set([.account], forSection: .authentication)
model.set([.providerPool, .endpoint, .providerPreset, .networkSettings], forSection: .configuration)
model.set([.providerRefresh], forSection: .providerInfrastructure)
} else {
model.set([.account, .endpoint, .hostParameters, .networkSettings], in: .configuration)
model.set([.account, .endpoint, .hostParameters, .networkSettings], forSection: .configuration)
}
if isActiveProfile {
if isProvider {
model.set([.vpnResolvesHostname], in: .vpnResolvesHostname)
model.set([.vpnResolvesHostname], forSection: .vpnResolvesHostname)
}
model.set([.vpnSurvivesSleep], in: .vpnSurvivesSleep)
model.set([.trustedPolicy], in: .trustedPolicy)
model.set([.dataCount, .debugLog, .masksPrivateData], in: .diagnostics)
model.set([.reportIssue], in: .feedback)
model.set([.vpnSurvivesSleep], forSection: .vpnSurvivesSleep)
model.set([.trustedPolicy], forSection: .trustedPolicy)
model.set([.dataCount, .debugLog, .masksPrivateData], forSection: .diagnostics)
model.set([.reportIssue], forSection: .feedback)
}
trustedNetworks.delegate = self
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() {
@ -1170,7 +1176,7 @@ extension ServiceViewController: TrustedNetworksModelDelegate {
}
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)
}
@ -1186,7 +1192,7 @@ extension ServiceViewController: TrustedNetworksModelDelegate {
}
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)
}

View File

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

View File

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

View File

@ -27,6 +27,7 @@ import UIKit
import Intents
import IntentsUI
import PassepartoutCore
import Convenience
@available(iOS 12, *)
protocol ShortcutsIntentDelegate: class {
@ -63,27 +64,27 @@ private struct ShortcutWrapper: Comparable {
}
@available(iOS 12, *)
class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewControllerDelegate, INUIEditVoiceShortcutViewControllerDelegate, ShortcutsIntentDelegate, TableModelHost {
class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewControllerDelegate, INUIEditVoiceShortcutViewControllerDelegate, ShortcutsIntentDelegate, StrongTableHost {
private var wrappers: [ShortcutWrapper]?
private var pendingShortcut: INShortcut?
private var editedIndexPath: IndexPath?
// MARK: TableModel
// MARK: StrongTableModel
let model: TableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel()
let model: StrongTableModel<SectionType, RowType> = {
let model: StrongTableModel<SectionType, RowType> = StrongTableModel()
model.add(.all)
model.setHeader(L10n.Core.Shortcuts.Edit.Sections.All.header, for: .all)
model.set([], in: .all)
model.setHeader(L10n.Core.Shortcuts.Edit.Sections.All.header, forSection: .all)
model.set([], forSection: .all)
return model
}()
func reloadModel() {
var rows = [RowType](repeating: .shortcut, count: wrappers?.count ?? 0)
rows.append(.addShortcut)
model.set(rows, in: .all)
model.set(rows, forSection: .all)
}
// MARK: UIViewController
@ -119,7 +120,7 @@ class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewCo
private func handleShortcutsFetchError(_ error: Error?) {
// TODO: really show it?
// let alert = Macros.alert(
// let alert = UIAlertController.asAlert(
// title,
// L10n.Core.Shortcuts.Edit.message(error?.localizedDescription ?? "")
// )
@ -167,15 +168,15 @@ class ShortcutsViewController: UITableViewController, INUIAddVoiceShortcutViewCo
}
override func numberOfSections(in tableView: UITableView) -> Int {
return model.count
return model.numberOfSections
}
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 {
return model.count(for: section)
return model.numberOfRows(forSection: section)
}
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;
classes = {
};
objectVersion = 50;
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
@ -17,7 +17,7 @@
0E1D72B2213BFFCF00BA1586 /* ProviderPresetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B1213BFFCF00BA1586 /* ProviderPresetViewController.swift */; };
0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; };
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 */; };
0E2AC24522EC3AC10037B4B0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */; };
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 */; };
0E3152BC223FA03D00F61841 /* ApplicationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6BE13920CFB76800A6DD36 /* ApplicationError.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 */; };
0E3152C1223FA04800F61841 /* GracefulVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5E5DE421511C5F00E318A3 /* GracefulVPN.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 */; };
0E3152DB223FA05800F61841 /* ProfileKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D14021919F5600BB5FB2 /* ProfileKey.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 */; };
0E36D24D2240234B006AF062 /* ShortcutsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */; };
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 */; };
0ECC60DE2256B68A0020BEAC /* SwiftGen+Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.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 */; };
0ECF12D7230612F5008E4924 /* InAppHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECF12D6230612F5008E4924 /* InAppHelper.swift */; };
0ED31C2920CF2A340027975F /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2820CF2A340027975F /* AccountViewController.swift */; };
0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED31C2B20CF2D6F0027975F /* ProviderPoolViewController.swift */; };
0ED31C3A20CF39510027975F /* 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 */; };
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 */; };
0ED993B1223FF8C700B0F9C9 /* IntentDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED993B0223FF8C700B0F9C9 /* IntentDispatcher.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, ); }; };
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 */; };
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 */; };
0EFB901822764689006405E4 /* ProfileNetworkSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFB901722764689006405E4 /* ProfileNetworkSettings.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>"; };
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>"; };
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>"; };
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>"; };
@ -184,6 +179,7 @@
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>"; };
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>"; };
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>"; };
@ -243,7 +239,6 @@
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>"; };
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>"; };
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>"; };
@ -270,9 +265,7 @@
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>"; };
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>"; };
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>"; };
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>"; };
@ -282,7 +275,6 @@
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>"; };
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>"; };
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>"; };
@ -297,10 +289,8 @@
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>"; };
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; };
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>"; };
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>"; };
@ -452,7 +442,6 @@
children = (
0EDE8DEC20C93E3B004C739C /* Global */,
0E1066CA20E0F85C004F98B7 /* Cells */,
0ECEE44C20E1120F00A6BB43 /* Tables */,
0EDE8DF120C93ED8004C739C /* Scenes */,
0E23B4A12298559800304C30 /* Config.xcconfig */,
0EDE8DE220C86A13004C739C /* Passepartout.entitlements */,
@ -494,14 +483,6 @@
path = Profiles;
sourceTree = "<group>";
};
0ECEE44C20E1120F00A6BB43 /* Tables */ = {
isa = PBXGroup;
children = (
0ECEE44D20E1122200A6BB43 /* TableModel.swift */,
);
path = Tables;
sourceTree = "<group>";
};
0ED31C0E20CF09890027975F /* Model */ = {
isa = PBXGroup;
children = (
@ -554,12 +535,10 @@
children = (
0E45E6E222BD793800F19312 /* App.strings */,
0EA068F3218475F800C320AD /* ConfigurationParserResult+Alerts.swift */,
0EEB53B1225D525B00746300 /* Downloader.swift */,
0EF5CF242141CE58004FF1BD /* HUD.swift */,
0E24273F225951B00064A1A3 /* InApp.swift */,
0E3419AC2350815E00419E18 /* Donation.swift */,
0EFD943D215BE10800529B64 /* IssueReporter.swift */,
0E4FD7F020D58618002221FF /* Macros.swift */,
0ED38AE9214054A50004D387 /* OptionViewController.swift */,
0E24273F225951B00064A1A3 /* ProductManager.swift */,
0ECC60DD2256B6890020BEAC /* SwiftGen+Assets.swift */,
0EDE8DE320C89028004C739C /* SwiftGen+Scenes.swift */,
0EF56BBA2185AC8500B0C8AB /* SwiftGen+Segues.swift */,
@ -591,9 +570,7 @@
0E39BCF2214DA9310035E9DE /* AppConstants.swift */,
0E6BE13920CFB76800A6DD36 /* ApplicationError.swift */,
0EDE8DED20C93E4C004C739C /* GroupConstants.swift */,
0ECF12D6230612F5008E4924 /* InAppHelper.swift */,
0EEF23402321AC55000AEBE3 /* Issue.swift */,
0E78179E21BE852200950C58 /* Reviewer.swift */,
0E4FD7ED20D539A0002221FF /* Utils.swift */,
);
path = Sources;
@ -965,10 +942,8 @@
0E3152CC223FA04D00F61841 /* WebServices.swift in Sources */,
0E3152BB223FA03D00F61841 /* AppConstants.swift in Sources */,
0E3152CA223FA04D00F61841 /* InfrastructurePreset.swift in Sources */,
0ECF12D7230612F5008E4924 /* InAppHelper.swift in Sources */,
0E3152CE223FA05400F61841 /* ConnectionService.swift in Sources */,
0ED993B1223FF8C700B0F9C9 /* IntentDispatcher.swift in Sources */,
0E3152BE223FA03D00F61841 /* Reviewer.swift in Sources */,
0EEF23412321AC55000AEBE3 /* Issue.swift in Sources */,
0E3152C3223FA04800F61841 /* StandardVPNProvider.swift in Sources */,
0E3152D1223FA05400F61841 /* Credentials.swift in Sources */,
@ -989,12 +964,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0ECEE44E20E1122200A6BB43 /* TableModel.swift in Sources */,
0ED38AD9213F33150004D387 /* WizardHostViewController.swift in Sources */,
0EE3BBB2215ED3A900F30952 /* AboutViewController.swift in Sources */,
0EBE3A79213C4E5500BFA2F5 /* OrganizerViewController.swift in Sources */,
0E4FD7F120D58618002221FF /* Macros.swift in Sources */,
0EF5CF252141CE58004FF1BD /* HUD.swift in Sources */,
0E45E6E422BD799700F19312 /* SwiftGen+Strings.swift in Sources */,
0E05C5D720D1645F006EE732 /* ToggleTableViewCell.swift in Sources */,
0EFB901A2276D7F1006405E4 /* NetworkSettingsViewController.swift in Sources */,
@ -1005,14 +978,14 @@
0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */,
0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */,
0E776642229D0DAE0023FA76 /* Intents.intentdefinition in Sources */,
0EEB53B2225D525B00746300 /* Downloader.swift in Sources */,
0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */,
0E242740225951B00064A1A3 /* InApp.swift in Sources */,
0E242740225951B00064A1A3 /* ProductManager.swift in Sources */,
0E1066C920E0F84A004F98B7 /* Cells.swift in Sources */,
0EF56BBB2185AC8500B0C8AB /* SwiftGen+Segues.swift in Sources */,
0E3DA371215CB5BF00B40FC9 /* VersionViewController.swift in Sources */,
0E05C5D620D1645F006EE732 /* SwiftGen+Scenes.swift in Sources */,
0E773BF8224BF37600CDDC8E /* ShortcutsViewController.swift in Sources */,
0E3419AD2350815E00419E18 /* Donation.swift in Sources */,
0EFD9440215BED8E00529B64 /* LabelViewController.swift in Sources */,
0ED31C2C20CF2D6F0027975F /* ProviderPoolViewController.swift in Sources */,
0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */,
@ -1020,7 +993,6 @@
0E89DFCE213EEDFA00741BA1 /* WizardProviderViewController.swift in Sources */,
0E1D72B2213BFFCF00BA1586 /* ProviderPresetViewController.swift in Sources */,
0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */,
0ED38AEA214054A50004D387 /* OptionViewController.swift in Sources */,
0EFBFAC121AC464800887A8C /* CreditsViewController.swift in Sources */,
0EFD943E215BE10800529B64 /* IssueReporter.swift in Sources */,
0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */,