diff --git a/Passepartout-iOS/AppDelegate.swift b/Passepartout-iOS/AppDelegate.swift index 316910b2..81c4e41f 100644 --- a/Passepartout-iOS/AppDelegate.swift +++ b/Passepartout-iOS/AppDelegate.swift @@ -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 } diff --git a/Passepartout-iOS/Global/ConfigurationParserResult+Alerts.swift b/Passepartout-iOS/Global/ConfigurationParserResult+Alerts.swift index 0a27376d..bea1d751 100644 --- a/Passepartout-iOS/Global/ConfigurationParserResult+Alerts.swift +++ b/Passepartout-iOS/Global/ConfigurationParserResult+Alerts.swift @@ -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) { diff --git a/Passepartout-iOS/Global/Donation.swift b/Passepartout-iOS/Global/Donation.swift new file mode 100644 index 00000000..5eef090e --- /dev/null +++ b/Passepartout-iOS/Global/Donation.swift @@ -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 . +// + +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 + ] +} diff --git a/Passepartout-iOS/Global/Downloader.swift b/Passepartout-iOS/Global/Downloader.swift deleted file mode 100644 index 3074838b..00000000 --- a/Passepartout-iOS/Global/Downloader.swift +++ /dev/null @@ -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 . -// - -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 - } -} diff --git a/Passepartout-iOS/Global/HUD.swift b/Passepartout-iOS/Global/HUD.swift deleted file mode 100644 index 3b06a243..00000000 --- a/Passepartout-iOS/Global/HUD.swift +++ /dev/null @@ -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 . -// -// 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) - } -} diff --git a/Passepartout-iOS/Global/IssueReporter.swift b/Passepartout-iOS/Global/IssueReporter.swift index 4129f96a..a99ef688 100644 --- a/Passepartout-iOS/Global/IssueReporter.swift +++ b/Passepartout-iOS/Global/IssueReporter.swift @@ -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) } diff --git a/Passepartout-iOS/Global/Macros.swift b/Passepartout-iOS/Global/Macros.swift index 65ed21e2..d3d7837a 100644 --- a/Passepartout-iOS/Global/Macros.swift +++ b/Passepartout-iOS/Global/Macros.swift @@ -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 { let name = String(describing: T.self) diff --git a/Passepartout-iOS/Global/OptionViewController.swift b/Passepartout-iOS/Global/OptionViewController.swift deleted file mode 100644 index e67ffcb0..00000000 --- a/Passepartout-iOS/Global/OptionViewController.swift +++ /dev/null @@ -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 . -// - -import UIKit - -class OptionViewController: 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) - } -} diff --git a/Passepartout-iOS/Global/InApp.swift b/Passepartout-iOS/Global/ProductManager.swift similarity index 54% rename from Passepartout-iOS/Global/InApp.swift rename to Passepartout-iOS/Global/ProductManager.swift index 70132db5..d836ef3a 100644 --- a/Passepartout-iOS/Global/InApp.swift +++ b/Passepartout-iOS/Global/ProductManager.swift @@ -1,5 +1,5 @@ // -// InApp.swift +// ProductManager.swift // Passepartout-iOS // // Created by Davide De Rosa on 4/6/19. @@ -25,32 +25,28 @@ import Foundation import StoreKit +import Convenience -struct InApp { - enum Donation: String { - static let all: [Donation] = [ - .tiny, - .small, - .medium, - .big, - .huge, - .maxi - ] - - case tiny = "com.algoritmico.ios.Passepartout.donations.Tiny" - - case small = "com.algoritmico.ios.Passepartout.donations.Small" - - case medium = "com.algoritmico.ios.Passepartout.donations.Medium" - - case big = "com.algoritmico.ios.Passepartout.donations.Big" - - case huge = "com.algoritmico.ios.Passepartout.donations.Huge" - - case maxi = "com.algoritmico.ios.Passepartout.donations.Maxi" +struct ProductManager { + static let shared = ProductManager() + + private let inApp: InApp + + private init() { + inApp = InApp() + } + + func listProducts(completionHandler: (([SKProduct]) -> Void)?) { + guard inApp.products.isEmpty else { + completionHandler?(inApp.products) + return + } + inApp.requestProducts(withIdentifiers: Donation.all) { _ in + completionHandler?(self.inApp.products) + } } - static func allIdentifiers() -> Set { - return Set(Donation.all.map { $0.rawValue }) + func purchase(_ product: SKProduct, completionHandler: @escaping (InAppPurchaseResult, Error?) -> Void) { + inApp.purchase(product: product, completionHandler: completionHandler) } } diff --git a/Passepartout-iOS/Global/Theme+Titles.swift b/Passepartout-iOS/Global/Theme+Titles.swift index 613658bf..38b11ab4 100644 --- a/Passepartout-iOS/Global/Theme+Titles.swift +++ b/Passepartout-iOS/Global/Theme+Titles.swift @@ -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 { diff --git a/Passepartout-iOS/Scenes/About/AboutViewController.swift b/Passepartout-iOS/Scenes/About/AboutViewController.swift index fff6ee47..4568682c 100644 --- a/Passepartout-iOS/Scenes/About/AboutViewController.swift +++ b/Passepartout-iOS/Scenes/About/AboutViewController.swift @@ -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 = { - let model: TableModel = TableModel() + let model: StrongTableModel = { + let model: StrongTableModel = 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 diff --git a/Passepartout-iOS/Scenes/About/CreditsViewController.swift b/Passepartout-iOS/Scenes/About/CreditsViewController.swift index bcee7c0f..8200d4db 100644 --- a/Passepartout-iOS/Scenes/About/CreditsViewController.swift +++ b/Passepartout-iOS/Scenes/About/CreditsViewController.swift @@ -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 = TableModel() + var model: StrongTableModel = 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 { diff --git a/Passepartout-iOS/Scenes/About/VersionViewController.swift b/Passepartout-iOS/Scenes/About/VersionViewController.swift index 3adf3b1e..69162b74 100644 --- a/Passepartout-iOS/Scenes/About/VersionViewController.swift +++ b/Passepartout-iOS/Scenes/About/VersionViewController.swift @@ -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) diff --git a/Passepartout-iOS/Scenes/AccountViewController.swift b/Passepartout-iOS/Scenes/AccountViewController.swift index da04190d..944b9f0c 100644 --- a/Passepartout-iOS/Scenes/AccountViewController.swift +++ b/Passepartout-iOS/Scenes/AccountViewController.swift @@ -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 = TableModel() + var model: StrongTableModel = 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 { diff --git a/Passepartout-iOS/Scenes/ConfigurationViewController.swift b/Passepartout-iOS/Scenes/ConfigurationViewController.swift index 5f08e86a..60d2ddd2 100644 --- a/Passepartout-iOS/Scenes/ConfigurationViewController.swift +++ b/Passepartout-iOS/Scenes/ConfigurationViewController.swift @@ -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 = { - let model: TableModel = TableModel() + lazy var model: StrongTableModel = { + let model: StrongTableModel = 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() + let vc = SingleOptionViewController() 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() + let vc = SingleOptionViewController() 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() + let vc = SingleOptionViewController() 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() + let vc = SingleOptionViewController() vc.title = settingCell?.leftText vc.options = OpenVPN.CompressionAlgorithm.available vc.selectedOption = configuration.compressionAlgorithm ?? .disabled diff --git a/Passepartout-iOS/Scenes/DebugLogViewController.swift b/Passepartout-iOS/Scenes/DebugLogViewController.swift index 35c76cca..27de4cdb 100644 --- a/Passepartout-iOS/Scenes/DebugLogViewController.swift +++ b/Passepartout-iOS/Scenes/DebugLogViewController.swift @@ -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 diff --git a/Passepartout-iOS/Scenes/EndpointViewController.swift b/Passepartout-iOS/Scenes/EndpointViewController.swift index e0fb8727..4891af23 100644 --- a/Passepartout-iOS/Scenes/EndpointViewController.swift +++ b/Passepartout-iOS/Scenes/EndpointViewController.swift @@ -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 = { - let model: TableModel = TableModel() + lazy var model: StrongTableModel = { + let model: StrongTableModel = 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 { diff --git a/Passepartout-iOS/Scenes/NetworkSettingsViewController.swift b/Passepartout-iOS/Scenes/NetworkSettingsViewController.swift index ff649981..a16e91b6 100644 --- a/Passepartout-iOS/Scenes/NetworkSettingsViewController.swift +++ b/Passepartout-iOS/Scenes/NetworkSettingsViewController.swift @@ -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 = TableModel() + let model: StrongTableModel = 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() + let vc = SingleOptionViewController() 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() + let vc = SingleOptionViewController() 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() + let vc = SingleOptionViewController() vc.title = (cell as? SettingTableViewCell)?.leftText vc.options = NetworkChoice.choices(for: profile) vc.descriptionBlock = { $0.description } diff --git a/Passepartout-iOS/Scenes/Organizer/DonationViewController.swift b/Passepartout-iOS/Scenes/Organizer/DonationViewController.swift index 0dc7defb..015d3242 100644 --- a/Passepartout-iOS/Scenes/Organizer/DonationViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/DonationViewController.swift @@ -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 = TableModel() + var model: StrongTableModel = 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 diff --git a/Passepartout-iOS/Scenes/Organizer/ImportedHostsViewController.swift b/Passepartout-iOS/Scenes/Organizer/ImportedHostsViewController.swift index e490b14a..1c0b3ac9 100644 --- a/Passepartout-iOS/Scenes/Organizer/ImportedHostsViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/ImportedHostsViewController.swift @@ -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 ) diff --git a/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift b/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift index 90526bff..1dbdcd7a 100644 --- a/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift @@ -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 = { - let model: TableModel = TableModel() + let model: StrongTableModel = { + let model: StrongTableModel = 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: diff --git a/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift b/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift index 53ce115d..e0b88045 100644 --- a/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/WizardHostViewController.swift @@ -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 = { - let model: TableModel = TableModel() + lazy var model: StrongTableModel = { + let model: StrongTableModel = 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] diff --git a/Passepartout-iOS/Scenes/ProviderPoolViewController.swift b/Passepartout-iOS/Scenes/ProviderPoolViewController.swift index 750a549f..96b3cb10 100644 --- a/Passepartout-iOS/Scenes/ProviderPoolViewController.swift +++ b/Passepartout-iOS/Scenes/ProviderPoolViewController.swift @@ -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() + let vc = SingleOptionViewController() vc.title = group.localizedCountry vc.options = group.pools.sorted { guard let lnum = $0.num else { diff --git a/Passepartout-iOS/Scenes/ServiceViewController.swift b/Passepartout-iOS/Scenes/ServiceViewController.swift index 698cf4e5..afb58733 100644 --- a/Passepartout-iOS/Scenes/ServiceViewController.swift +++ b/Passepartout-iOS/Scenes/ServiceViewController.swift @@ -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 = TableModel() + var model: StrongTableModel = 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) } diff --git a/Passepartout-iOS/Scenes/Shortcuts/ShortcutsAddViewController.swift b/Passepartout-iOS/Scenes/Shortcuts/ShortcutsAddViewController.swift index 363bfd49..ffecf01c 100644 --- a/Passepartout-iOS/Scenes/Shortcuts/ShortcutsAddViewController.swift +++ b/Passepartout-iOS/Scenes/Shortcuts/ShortcutsAddViewController.swift @@ -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 = { - let model: TableModel = TableModel() + let model: StrongTableModel = { + let model: StrongTableModel = 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 ) diff --git a/Passepartout-iOS/Scenes/Shortcuts/ShortcutsConnectToViewController.swift b/Passepartout-iOS/Scenes/Shortcuts/ShortcutsConnectToViewController.swift index 865fcfe9..ece7c963 100644 --- a/Passepartout-iOS/Scenes/Shortcuts/ShortcutsConnectToViewController.swift +++ b/Passepartout-iOS/Scenes/Shortcuts/ShortcutsConnectToViewController.swift @@ -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 = { - let model: TableModel = TableModel() - model.setHeader(L10n.Core.Organizer.Sections.Providers.header, for: .providers) - model.setHeader(L10n.Core.Organizer.Sections.Hosts.header, for: .hosts) + let model: StrongTableModel = { + let model: StrongTableModel = 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 { diff --git a/Passepartout-iOS/Scenes/Shortcuts/ShortcutsViewController.swift b/Passepartout-iOS/Scenes/Shortcuts/ShortcutsViewController.swift index 0f3a4cac..e8909f89 100644 --- a/Passepartout-iOS/Scenes/Shortcuts/ShortcutsViewController.swift +++ b/Passepartout-iOS/Scenes/Shortcuts/ShortcutsViewController.swift @@ -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 = { - let model: TableModel = TableModel() + let model: StrongTableModel = { + let model: StrongTableModel = 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 { diff --git a/Passepartout-iOS/Tables/TableModel.swift b/Passepartout-iOS/Tables/TableModel.swift deleted file mode 100644 index 049cfdc1..00000000 --- a/Passepartout-iOS/Tables/TableModel.swift +++ /dev/null @@ -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 . -// - -import Foundation - -protocol TableModelHost { - associatedtype S: Hashable - - associatedtype R: Equatable - - var model: TableModel { get } - - func reloadModel() -} - -class TableModel { - 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) - } -} diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index ec0b5756..174f0fdb 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -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 = ""; }; 0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 0E24273B225950450064A1A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/About.storyboard; sourceTree = ""; }; - 0E24273F225951B00064A1A3 /* InApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InApp.swift; sourceTree = ""; }; + 0E24273F225951B00064A1A3 /* ProductManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductManager.swift; sourceTree = ""; }; 0E242741225956AC0064A1A3 /* DonationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationViewController.swift; sourceTree = ""; }; 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = ""; }; @@ -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 = ""; }; 0E31529E223F9EF500F61841 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0E3419AC2350815E00419E18 /* Donation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Donation.swift; sourceTree = ""; }; 0E3586FD225BD34800509A4D /* ActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTableViewCell.swift; sourceTree = ""; }; 0E36D24C2240234B006AF062 /* ShortcutsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsAddViewController.swift; sourceTree = ""; }; 0E36D25B224034AD006AF062 /* ShortcutsConnectToViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsConnectToViewController.swift; sourceTree = ""; }; @@ -243,7 +239,6 @@ 0E77663E229D0DA60023FA76 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = ""; }; 0E77663F229D0DA70023FA76 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Intents.strings; sourceTree = ""; }; 0E776640229D0DA80023FA76 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = ""; }; - 0E78179E21BE852200950C58 /* Reviewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reviewer.swift; sourceTree = ""; }; 0E79D13E21919EC900BB5FB2 /* PlaceholderConnectionProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderConnectionProfile.swift; sourceTree = ""; }; 0E79D14021919F5600BB5FB2 /* ProfileKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileKey.swift; sourceTree = ""; }; 0E89DFC4213DF7AE00741BA1 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; @@ -270,9 +265,7 @@ 0ECEB107224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Shortcuts.storyboard; sourceTree = ""; }; 0ECEB108224FE51400E9E551 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 0ECEB109224FECEA00E9E551 /* DataUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataUnit.swift; sourceTree = ""; }; - 0ECEE44D20E1122200A6BB43 /* TableModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableModel.swift; sourceTree = ""; }; 0ECEE44F20E1182E00A6BB43 /* Theme+Cells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Cells.swift"; sourceTree = ""; }; - 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 = ""; }; 0ED31C1120CF0ABA0027975F /* Infrastructure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Infrastructure.swift; sourceTree = ""; }; 0ED31C2820CF2A340027975F /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; @@ -282,7 +275,6 @@ 0ED31C3B20CF39510027975F /* Tunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tunnel.entitlements; sourceTree = ""; }; 0ED38AD8213F33150004D387 /* WizardHostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardHostViewController.swift; sourceTree = ""; }; 0ED38AE621404F100004D387 /* EndpointDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointDataSource.swift; sourceTree = ""; }; - 0ED38AE9214054A50004D387 /* OptionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionViewController.swift; sourceTree = ""; }; 0ED38AEB2141260D0004D387 /* ConfigurationModificationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationModificationDelegate.swift; sourceTree = ""; }; 0ED38AF1214177920004D387 /* VPNProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNProvider.swift; sourceTree = ""; }; 0ED824C920D12B8700F2FE9E /* ToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleTableViewCell.swift; sourceTree = ""; }; @@ -297,10 +289,8 @@ 0EDE8DE620C93945004C739C /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupConstants.swift; sourceTree = ""; }; 0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; - 0EEB53B1225D525B00746300 /* Downloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = ""; }; 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 = ""; }; - 0EF5CF242141CE58004FF1BD /* HUD.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HUD.swift; sourceTree = ""; }; 0EFB901722764689006405E4 /* ProfileNetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNetworkSettings.swift; sourceTree = ""; }; 0EFB90192276D7F1006405E4 /* NetworkSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettingsViewController.swift; sourceTree = ""; }; 0EFBFAC021AC464800887A8C /* CreditsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsViewController.swift; sourceTree = ""; }; @@ -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 = ""; }; - 0ECEE44C20E1120F00A6BB43 /* Tables */ = { - isa = PBXGroup; - children = ( - 0ECEE44D20E1122200A6BB43 /* TableModel.swift */, - ); - path = Tables; - sourceTree = ""; - }; 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 */,