Replace with Convenience entities
- About - Alerts - Dialogs - InApp - Reviewer - SingleOptionViewController - StrongTableModel
This commit is contained in:
parent
ea5d3a48ab
commit
2cd6677e16
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
]
|
||||
struct ProductManager {
|
||||
static let shared = ProductManager()
|
||||
|
||||
case tiny = "com.algoritmico.ios.Passepartout.donations.Tiny"
|
||||
private let inApp: InApp<Donation>
|
||||
|
||||
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"
|
||||
private init() {
|
||||
inApp = InApp()
|
||||
}
|
||||
|
||||
static func allIdentifiers() -> Set<String> {
|
||||
return Set<String>(Donation.all.map { $0.rawValue })
|
||||
func listProducts(completionHandler: (([SKProduct]) -> Void)?) {
|
||||
guard inApp.products.isEmpty else {
|
||||
completionHandler?(inApp.products)
|
||||
return
|
||||
}
|
||||
inApp.requestProducts(withIdentifiers: Donation.all) { _ in
|
||||
completionHandler?(self.inApp.products)
|
||||
}
|
||||
}
|
||||
|
||||
func purchase(_ product: SKProduct, completionHandler: @escaping (InAppPurchaseResult, Error?) -> Void) {
|
||||
inApp.purchase(product: product, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 */,
|
||||
|
|
Loading…
Reference in New Issue