Commit 3fbf74ec authored by Thomas Markiewicz's avatar Thomas Markiewicz

Merged latest upstream PIA VPN

parents 10d775ee 64aed58b
......@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Liberty Tunnel AdBlocker</string>
<string>Librem Tunnel AdBlocker</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
......@@ -28,6 +28,6 @@
<string>$(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler</string>
</dict>
<key>NSHumanReadableDescription</key>
<string>PIA VPN AdBlocker</string>
<string>Librem Tunnel AdBlocker</string>
</dict>
</plist>
......@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Liberty VPN Tunnel</string>
<string>Librem Tunnel</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
......
This diff is collapsed.
......@@ -56,6 +56,16 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EE2D77F1CB40A98000DEC4D"
BuildableName = "PIA VPNUITests.xctest"
BlueprintName = "PIA VPNUITests"
ReferencedContainer = "container:PIA VPN.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
......
......@@ -28,17 +28,18 @@ class AboutNoticeCell: UITableViewCell, Restylable {
labelCopyright.text = component.copyright
labelNotice.text = component.notice
buttonName.accessibilityTraits = UIAccessibilityTraitNone
buttonName.accessibilityTraits = UIAccessibilityTraits.none
buttonName.accessibilityLabel = component.name
}
// MARK: Restylable
func viewShouldRestyle() {
Theme.current.applySolidLightBackground(self)
Theme.current.applyTextButton(buttonName)
Theme.current.applySmallInfo(labelCopyright, appearance: .dark)
Theme.current.applySmallInfo(labelNotice, appearance: .dark)
Theme.current.applySecondaryBackground(self)
buttonName.style(style: TextStyle.textStyle9)
Theme.current.applySubtitle(labelCopyright)
Theme.current.applySubtitle(labelNotice)
}
}
......@@ -110,7 +111,7 @@ class AboutLicenseCell: UITableViewCell, Restylable {
}
buttonMore.setImage(moreImage, for: .normal)
buttonName.accessibilityTraits = UIAccessibilityTraitNone
buttonName.accessibilityTraits = UIAccessibilityTraits.none
buttonName.accessibilityLabel = component.name
buttonName.accessibilityHint = L10n.About.Accessibility.Component.expand
}
......@@ -152,11 +153,11 @@ class AboutLicenseCell: UITableViewCell, Restylable {
// MARK: Restylable
func viewShouldRestyle() {
Theme.current.applySolidLightBackground(self)
Theme.current.applyLightBackground(textLicense)
Theme.current.applyTextButton(buttonName)
Theme.current.applySmallInfo(labelCopyright, appearance: .dark)
Theme.current.applyBody1Monospace(textLicense, appearance: .dark)
Theme.current.applySecondaryBackground(self)
Theme.current.applyPrincipalBackground(textLicense)
buttonName.style(style: TextStyle.textStyle9)
Theme.current.applySubtitle(labelCopyright)
Theme.current.applyLicenseMonospaceFontAndColor(textLicense, appearance: .dark)
buttonMore.tintColor = textLicense.textColor
gradientLicense?.removeFromSuperview()
......
......@@ -36,10 +36,13 @@ class AboutViewController: AutolayoutViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = L10n.Menu.Item.about
let textApp = L10n.About.app
labelIntro.text = "Copyright © \(AppConfiguration.About.copyright) \(AppConfiguration.About.companyName)\n\(textApp) \(Macros.versionFullString()!)"
tableView.scrollsToTop = true
let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(viewHasRotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
......@@ -48,13 +51,18 @@ class AboutViewController: AutolayoutViewController {
loadLicensesInBackground()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
styleNavigationBarWithTitle(L10n.Menu.Item.about)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let headerView = tableView.tableHeaderView else {
return
}
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var frame = headerView.frame
frame.size.height = height
headerView.frame = frame
......@@ -63,8 +71,16 @@ class AboutViewController: AutolayoutViewController {
headerView.layoutIfNeeded()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: Helpers
@objc private func viewHasRotated() {
styleNavigationBarWithTitle(L10n.Menu.Item.settings)
}
private func loadLicensesInBackground() {
for component in components.licenses {
performSelector(inBackground: #selector(fetchLicenseWithComponent(_:)), with: component)
......@@ -78,7 +94,7 @@ class AboutViewController: AutolayoutViewController {
DispatchQueue.main.async {
self.licenseByComponentName[component.name] = license
guard let index = self.components.licenses.index(of: component) else {
guard let index = self.components.licenses.firstIndex(of: component) else {
return
}
self.tableView.reloadRows(at: [IndexPath(row: index, section: 1)], with: .none)
......@@ -86,12 +102,21 @@ class AboutViewController: AutolayoutViewController {
}
// MARK: Restylable
override func viewShouldRestyle() {
super.viewShouldRestyle()
Theme.current.applySolidLightBackground(tableView)
Theme.current.applyBody1(labelIntro, appearance: .dark)
tableView.separatorInset = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 0)
Theme.current.applyDividerToSeparator(tableView)
styleNavigationBarWithTitle(L10n.Menu.Item.about)
// XXX: for some reason, UITableView is not affected by appearance updates
if let viewContainer = viewContainer {
Theme.current.applyPrincipalBackground(view)
Theme.current.applyPrincipalBackground(viewContainer)
}
Theme.current.applyPrincipalBackground(tableView)
Theme.current.applySubtitle(labelIntro)
}
}
......
......@@ -29,7 +29,7 @@ class AccountViewController: AutolayoutViewController {
@IBOutlet private weak var labelUsername: UILabel!
@IBOutlet private weak var textUsername: BorderedTextField!
@IBOutlet private weak var textUsername: UITextField!
@IBOutlet private weak var labelFooterOther: UILabel!
......@@ -37,6 +37,8 @@ class AccountViewController: AutolayoutViewController {
@IBOutlet private weak var itemUpdate: UIBarButtonItem!
@IBOutlet private weak var viewAccountInfo: UIView!
@IBOutlet private weak var viewSeparator: UIView!
@IBOutlet private weak var viewUncredited: UIView!
......@@ -80,14 +82,13 @@ class AccountViewController: AutolayoutViewController {
textEmail.isEditable = false
textEmail.isEnabled = false
textEmail.isUserInteractionEnabled = false
textUsername.isEditable = false
textUsername.isEnabled = false
textUsername.isUserInteractionEnabled = false
canSaveAccount = false
let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(redisplayAccount), name: .PIAAccountDidRefresh, object: nil)
nc.addObserver(self, selector: #selector(accountDidLogout(notification:)), name: .PIAAccountDidLogout, object: nil)
nc.addObserver(self, selector: #selector(viewHasRotated), name: UIDevice.orientationDidChangeNotification, object: nil)
Client.providers.accountProvider.retrieveAccount()
}
......@@ -96,6 +97,7 @@ class AccountViewController: AutolayoutViewController {
super.viewWillAppear(animated)
// update local state immediately
styleNavigationBarWithTitle(L10n.Menu.Item.account)
redisplayAccount()
}
......@@ -119,6 +121,10 @@ class AccountViewController: AutolayoutViewController {
establishUncreditedVisibility()
}
@objc private func viewHasRotated() {
styleNavigationBarWithTitle(L10n.Menu.Item.settings)
}
// MARK: Actions
@IBAction private func saveChanges(_ sender: Any?) {
......@@ -133,9 +139,9 @@ class AccountViewController: AutolayoutViewController {
return
}
let alert = Macros.alert(L10n.Account.Update.Email.Require.Password.title,
L10n.Account.Update.Email.Require.Password.message)
let alert = Macros.alertController(L10n.Account.Update.Email.Require.Password.title,
L10n.Account.Update.Email.Require.Password.message)
alert.addCancelAction(L10n.Global.cancel)
let action = UIAlertAction(title: L10n.Account.Update.Email.Require.Password.button,
style: .default) { [weak self] (alertAction) in
......@@ -147,11 +153,11 @@ class AccountViewController: AutolayoutViewController {
log.debug("Account: Modifying account email...")
let request = UpdateAccountRequest(email: email)
let hud = HUD()
weakSelf.showLoadingAnimation()
Client.providers.accountProvider.update(with: request,
andPassword: password) { (info, error) in
hud.hide()
weakSelf.hideLoadingAnimation()
guard let _ = info else {
if let error = error {
......@@ -162,7 +168,7 @@ class AccountViewController: AutolayoutViewController {
weakSelf.textEmail.text = ""
let alert = Macros.alert(L10n.Global.error, L10n.Account.Error.unauthorized)
alert.addCancelAction(L10n.Global.close)
alert.addDefaultAction(L10n.Global.close)
self?.present(alert, animated: true, completion: nil)
return
......@@ -170,7 +176,7 @@ class AccountViewController: AutolayoutViewController {
log.debug("Account: Email successfully modified")
let alert = Macros.alert(nil, L10n.Account.Save.success)
alert.addCancelAction(L10n.Global.ok)
alert.addDefaultAction(L10n.Global.ok)
weakSelf.present(alert, animated: true, completion: nil)
weakSelf.textEmail.text = email
weakSelf.textEmail.endEditing(true)
......@@ -215,14 +221,14 @@ class AccountViewController: AutolayoutViewController {
log.error("IAP: Failed to restore payment receipt (error: \(error?.localizedDescription ?? ""))")
let alert = Macros.alert(L10n.Global.error, error?.localizedDescription)
alert.addCancelAction(L10n.Global.close)
alert.addDefaultAction(L10n.Global.close)
present(alert, animated: true, completion: nil)
}
private func handleReceiptSubmissionWithError(_ error: Error?) {
if let error = error {
let alert = Macros.alert(L10n.Global.error, error.localizedDescription)
alert.addCancelAction(L10n.Global.close)
alert.addDefaultAction(L10n.Global.close)
present(alert, animated: true, completion: nil)
return
}
......@@ -233,7 +239,7 @@ class AccountViewController: AutolayoutViewController {
L10n.Renewal.Success.title,
L10n.Renewal.Success.message
)
alert.addCancelAction(L10n.Global.close)
alert.addDefaultAction(L10n.Global.close)
present(alert, animated: true, completion: nil)
redisplayAccount()
......@@ -244,15 +250,11 @@ class AccountViewController: AutolayoutViewController {
L10n.Account.Restore.Failure.title,
L10n.Account.Restore.Failure.message
)
alert.addCancelAction(L10n.Global.close)
alert.addDefaultAction(L10n.Global.close)
present(alert, animated: true, completion: nil)
}
// MARK: Notifications
@objc private func accountDidLogout(notification: Notification) {
dismissModal()
}
@objc private func redisplayAccount() {
currentUser = Client.providers.accountProvider.currentUser
......@@ -292,19 +294,27 @@ class AccountViewController: AutolayoutViewController {
override func viewShouldRestyle() {
super.viewShouldRestyle()
for label in [labelEmail!, labelUsername!] {
Theme.current.applyLabel(label, appearance: .dark)
styleNavigationBarWithTitle(L10n.Menu.Item.account)
if let viewContainer = viewContainer {
Theme.current.applyPrincipalBackground(view)
Theme.current.applyPrincipalBackground(viewContainer)
}
Theme.current.applySecondaryBackground(viewAccountInfo)
Theme.current.applySubtitle(labelEmail)
Theme.current.applySubtitle(labelUsername)
Theme.current.applyInput(textEmail)
Theme.current.applyInput(textUsername)
Theme.current.applyDivider(viewSeparator)
Theme.current.applyClearTextfield(textUsername)
for label in [labelFooterOther!, labelExpiryInformation!] {
Theme.current.applySmallInfo(label, appearance: .dark)
Theme.current.applySubtitle(label)
}
Theme.current.applyBody2(labelRestoreTitle, appearance: .dark)
Theme.current.applyBody1(labelRestoreInfo, appearance: .dark)
Theme.current.applyTextButton(buttonRestore)
Theme.current.applyTitle(labelRestoreTitle, appearance: .dark)
Theme.current.applySubtitle(labelRestoreInfo)
buttonRestore.style(style: TextStyle.textStyle9)
styleExpirationDate()
}
......
......@@ -24,8 +24,8 @@ struct AppConfiguration {
}
struct Welcome {
static func defaultPreset() -> PIAWelcomeViewController.Preset {
var preset = PIAWelcomeViewController.Preset()
static func defaultPreset() -> Preset {
var preset = Preset()
guard Flags.shared.customizesWelcomePreset else {
return preset
}
......
......@@ -48,6 +48,11 @@ struct AppConstants {
static let adBlockerBundleIdentifier = "one.librem.tunnel.AdBlocker"
}
struct SiriShortcuts {
static let shortcutConnect = "com.privateinternetaccess.ios.PIA-VPN.connect"
static let shortcutDisconnect = "com.privateinternetaccess.ios.PIA-VPN.disconnect"
}
struct Web {
static let homeURL = URL(string: "https://librem.one/")!
......@@ -118,4 +123,10 @@ struct AppConstants {
return typeface
}()
}
struct VPNWidget {
static let vpnStatus = "vpn.status"
static let vpnButtonDescription = "vpn.button.description"
}
}
......@@ -32,7 +32,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
NotificationCenter.default.removeObserver(self)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
Bootstrapper.shared.bootstrap()
application.shortcutItems = []
......@@ -62,8 +62,8 @@ class AppDelegate: NSObject, UIApplicationDelegate {
L10n.Notifications.Disabled.title,
L10n.Notifications.Disabled.message
)
alert.addDefaultAction(L10n.Notifications.Disabled.settings) {
application.openURL(URL(string: UIApplicationOpenSettingsURLString)!)
alert.addActionWithTitle(L10n.Notifications.Disabled.settings) {
application.openURL(URL(string: UIApplication.openSettingsURLString)!)
}
alert.addCancelAction(L10n.Global.ok)
window?.rootViewController?.present(alert, animated: true, completion: nil)
......@@ -75,7 +75,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
application.applicationIconBadgeNumber = 0
}
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
log.debug("Opened app from URL: \(url)")
guard let host = url.host else {
return false
......@@ -87,6 +87,15 @@ class AppDelegate: NSObject, UIApplicationDelegate {
// in case it's too early for notification delivery (vc not loaded)
TransientState.shouldDisplayRegionPicker = true
case VPNStatus.connected.rawValue:
if Client.providers.vpnProvider.isVPNConnected {
disconnectAfter(milliseconds: 200)
}
case VPNStatus.disconnected.rawValue:
if !Client.providers.vpnProvider.isVPNConnected {
connectAfter(milliseconds: 200)
}
default:
return false
}
......@@ -166,9 +175,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
case .connect:
if !Client.providers.vpnProvider.isVPNConnected {
// this time delay seems to fix a strange issue of the VPN connecting from a fresh launch
Macros.dispatch(after: .milliseconds(200)) {
Client.providers.vpnProvider.connect(nil)
}
connectAfter(milliseconds: 200)
}
case .disconnect:
......@@ -176,9 +183,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
// this time delay seems to fix a strange issue of the VPN disconnecting and
// then automatically reconnecting when it's done from a fresh launch
Macros.dispatch(after: .milliseconds(200)) {
Client.providers.vpnProvider.disconnect(nil)
}
disconnectAfter(milliseconds: 200)
}
case .selectRegion:
......@@ -192,4 +197,32 @@ class AppDelegate: NSObject, UIApplicationDelegate {
}
}
//MARK: Siri Shortcuts
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if userActivity.activityType == AppConstants.SiriShortcuts.shortcutConnect {
if AppPreferences.shared.useConnectSiriShortcuts {
connectAfter(milliseconds: 200)
}
return AppPreferences.shared.useConnectSiriShortcuts
} else {
if AppPreferences.shared.useDisconnectSiriShortcuts {
disconnectAfter(milliseconds: 200)
}
return AppPreferences.shared.useDisconnectSiriShortcuts
}
}
private func connectAfter(milliseconds: Int) {
Macros.dispatch(after: .milliseconds(milliseconds)) {
Client.providers.vpnProvider.connect(nil)
}
}
private func disconnectAfter(milliseconds: Int) {
Macros.dispatch(after: .milliseconds(milliseconds)) {
Client.providers.vpnProvider.disconnect(nil)
}
}
}
......@@ -10,11 +10,14 @@ import Foundation
import PIALibrary
import PIATunnel
import SwiftyBeaver
import Intents
private let log = SwiftyBeaver.self
class AppPreferences {
private struct Entries {
static let version = "Version"
static let launched = "Launched" // discard 2.2 key and invert logic
......@@ -28,6 +31,22 @@ class AppPreferences {
static let lastVPNConnectionStatus = "LastVPNConnectionStatus"
static let piaSocketType = "PIASocketType"
static let favoriteServerIdentifiers = "FavoriteServerIdentifiers"
static let regionFilter = "RegionFilter"
static let useConnectSiriShortcuts = "UseConnectSiriShortcuts"
static let connectShortcut = "ConnectShortcut"
static let useDisconnectSiriShortcuts = "UseDisconnectSiriShortcuts"
static let disconnectShortcut = "disconnectShortcut"
static let todayWidgetVpnStatus = "vpn.status"
static let todayWidgetButtonTitle = "vpn.button.description"
static let optOutAskDisconnectVPNUsingNMT = "OptOutAskDisconnectVPNUsingNMT"
}
static let shared = AppPreferences()
......@@ -104,6 +123,109 @@ class AppPreferences {
}
}
var favoriteServerIdentifiers: [String] {
get {
if let serverIdentifiers = defaults.array(forKey: Entries.favoriteServerIdentifiers) as? [String] {
return serverIdentifiers
}
return []
}
set {
defaults.set(newValue, forKey: Entries.favoriteServerIdentifiers)
}
}
var regionFilter: RegionFilter {
get {
guard let rawValue = defaults.string(forKey: Entries.regionFilter) else {
return .name
}
return RegionFilter(rawValue: rawValue) ?? .name
}
set {
defaults.set(newValue.rawValue, forKey: Entries.regionFilter)
}
}
var useConnectSiriShortcuts: Bool {
get {
return defaults.bool(forKey: Entries.useConnectSiriShortcuts)
}
set {
defaults.set(newValue, forKey: Entries.useConnectSiriShortcuts)
}
}
var useDisconnectSiriShortcuts: Bool {
get {
return defaults.bool(forKey: Entries.useDisconnectSiriShortcuts)
}
set {
defaults.set(newValue, forKey: Entries.useDisconnectSiriShortcuts)
}
}
var todayWidgetVpnStatus: String? {
get {
return defaults.string(forKey: Entries.todayWidgetVpnStatus) ?? L10n.Today.Widget.login
}
set {
defaults.set(newValue, forKey: Entries.todayWidgetVpnStatus)