From 99e87c77ab87f2193d6cb5e76fb26b2d4485cd1e Mon Sep 17 00:00:00 2001 From: Laura Sempere Date: Wed, 6 Sep 2023 13:03:05 +0200 Subject: [PATCH 1/5] Update application --- .github/workflows/vpn-ios.yml | 256 +++++ PIA VPN Tunnel/PacketTunnelProvider.swift | 2 +- PIA VPN UITests/Credentials.plist | 17 + PIA VPN UITests/CredentialsUtil.swift | 47 + PIA VPN UITests/PIALaunchTests.swift | 33 + PIA VPN UITests/PIALoginTests.swift | 145 +++ PIA VPN UITests/PIAUITests.swift | 43 + PIA VPN.xcodeproj/project.pbxproj | 934 ++++++++++++------ .../xcschemes/PIA VPN WG Tunnel.xcscheme | 11 + .../xcschemes/PIA VPN dev.xcscheme | 10 + .../xcshareddata/xcschemes/PIA VPN.xcscheme | 10 + PIA VPN.xcworkspace/contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - PIA VPN/AccountObserver.swift | 1 + PIA VPN/AppConfiguration.swift | 10 +- PIA VPN/AppConstants.swift | 8 +- PIA VPN/AppDelegate.swift | 22 + PIA VPN/AppPreferences.swift | 116 ++- PIA VPN/Bootstrapper.swift | 67 +- PIA VPN/CardFactory.swift | 1 + PIA VPN/CustomDNSSettingsViewController.swift | 1 + PIA VPN/DNSList.swift | 17 + PIA VPN/DashboardViewController.swift | 135 ++- PIA VPN/DedicatedIpEmptyHeaderViewCell.swift | 77 +- PIA VPN/DedicatedIpViewController.swift | 80 +- PIA VPN/GeneralSettingsViewController.swift | 8 +- PIA VPN/IPv4Address.swift | 60 ++ PIA VPN/MenuViewController.swift | 2 +- PIA VPN/MessagesCommands.swift | 7 +- PIA VPN/MessagesManager.swift | 37 +- PIA VPN/NetworkMonitor.swift | 14 + PIA VPN/PIAHotspotHelper.swift | 33 +- PIA VPN/PIAPageControl.swift | 2 +- PIA VPN/PIATunnelProvider+UI.swift | 3 +- PIA VPN/PemUtil.swift | 3 +- PIA VPN/ProtocolSettingsViewController.swift | 4 +- PIA VPN/RatingManager.swift | 120 ++- PIA VPN/Server+UI.swift | 3 +- PIA VPN/SettingOptions.swift | 16 +- .../DevelopmentSettingsViewController.swift | 42 +- .../Settings/HelpSettingsViewController.swift | 6 +- .../NetworkSettingsViewController.swift | 3 +- .../PIABaseSettingsViewController.swift | 3 +- ...rivacyFeaturesSettingsViewController.swift | 115 ++- .../SettingPopoverSelectionView.swift | 4 +- PIA VPN/SettingsDelegate.swift | 3 +- PIA VPN/SettingsViewController.swift | 26 +- .../ShowConnectionStatsViewController.swift | 1 + PIA VPN/String+VPNType.swift | 9 +- PIA VPN/SwiftGen+ScenesStoryboards.swift | 2 +- PIA VPN/SwiftGen+Strings.swift | 74 +- PIA VPN/Theme+App.swift | 1 - PIA VPN/Theme+DarkPalette.swift | 1 + PIA VPN/ThemeStrategy+App.swift | 1 + PIA VPN/Tiles/ConnectionTile.swift | 3 +- PIA VPN/Tiles/FavoriteServersTile.swift | 1 + PIA VPN/Tiles/MessagesTile.swift | 1 + PIA VPN/Tiles/UsageTile.swift | 1 + PIA VPN/UserSurveyManager.swift | 48 + PIA VPN/VPNPermissionViewController.swift | 1 + PIA VPN/WifiNetworkMonitor.swift | 69 ++ PIA VPN/ar.lproj/Localizable.strings | 17 +- PIA VPN/da.lproj/Localizable.strings | 17 +- PIA VPN/de.lproj/Localizable.strings | 17 +- PIA VPN/en.lproj/Localizable.strings | 23 +- PIA VPN/es-MX.lproj/Localizable.strings | 17 +- PIA VPN/fr.lproj/Localizable.strings | 17 +- PIA VPN/it.lproj/Localizable.strings | 17 +- PIA VPN/ja.lproj/Localizable.strings | 17 +- PIA VPN/ko.lproj/Localizable.strings | 17 +- PIA VPN/nb.lproj/Localizable.strings | 17 +- PIA VPN/nl.lproj/Localizable.strings | 17 +- PIA VPN/pl.lproj/Localizable.strings | 17 +- PIA VPN/pt-BR.lproj/Localizable.strings | 17 +- PIA VPN/ru.lproj/Localizable.strings | 17 +- PIA VPN/th.lproj/Localizable.strings | 17 +- PIA VPN/zh-Hans.lproj/Localizable.strings | 17 +- PIA VPN/zh-Hant.lproj/Localizable.strings | 17 +- .../Cache/WidgetPersistenceDatasource.swift | 17 + .../Cache/WidgetUserDefaultsDatasource.swift | 71 ++ PIAWidget/Data/Model/WidgetInformation.swift | 31 + PIAWidget/Domain/UI/PIACircleVpnButton.swift | 44 + PIAWidget/Domain/UI/PIAIconView.swift | 31 + PIAWidget/Domain/UI/PIAWidgetView.swift | 52 + .../Domain/UI/PIAWidgetVpnDetailsView.swift | 33 + .../Domain/UI/PIAWidgetVpnDetaislRow.swift | 38 + PIAWidget/Domain/Widget/PIAWidget.swift | 48 + .../Domain/Widget/PIAWidgetPreview.swift | 38 + .../Domain/Widget/PIAWidgetProvider.swift | 73 ++ Podfile | 155 --- Podfile.lock | 298 ------ README.md | 23 - Resources/UI/en.lproj/Main.storyboard | 6 +- 93 files changed, 2948 insertions(+), 1093 deletions(-) create mode 100644 .github/workflows/vpn-ios.yml create mode 100644 PIA VPN UITests/Credentials.plist create mode 100644 PIA VPN UITests/CredentialsUtil.swift create mode 100644 PIA VPN UITests/PIALaunchTests.swift create mode 100644 PIA VPN UITests/PIALoginTests.swift create mode 100644 PIA VPN UITests/PIAUITests.swift delete mode 100644 PIA VPN.xcworkspace/contents.xcworkspacedata delete mode 100644 PIA VPN.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 PIA VPN/IPv4Address.swift create mode 100644 PIA VPN/NetworkMonitor.swift create mode 100644 PIA VPN/UserSurveyManager.swift create mode 100644 PIA VPN/WifiNetworkMonitor.swift create mode 100644 PIAWidget/Data/Cache/WidgetPersistenceDatasource.swift create mode 100644 PIAWidget/Data/Cache/WidgetUserDefaultsDatasource.swift create mode 100644 PIAWidget/Data/Model/WidgetInformation.swift create mode 100644 PIAWidget/Domain/UI/PIACircleVpnButton.swift create mode 100644 PIAWidget/Domain/UI/PIAIconView.swift create mode 100644 PIAWidget/Domain/UI/PIAWidgetView.swift create mode 100644 PIAWidget/Domain/UI/PIAWidgetVpnDetailsView.swift create mode 100644 PIAWidget/Domain/UI/PIAWidgetVpnDetaislRow.swift create mode 100644 PIAWidget/Domain/Widget/PIAWidget.swift create mode 100644 PIAWidget/Domain/Widget/PIAWidgetPreview.swift create mode 100644 PIAWidget/Domain/Widget/PIAWidgetProvider.swift delete mode 100644 Podfile delete mode 100644 Podfile.lock diff --git a/.github/workflows/vpn-ios.yml b/.github/workflows/vpn-ios.yml new file mode 100644 index 000000000..dbb57814a --- /dev/null +++ b/.github/workflows/vpn-ios.yml @@ -0,0 +1,256 @@ +name: pia-mobile/ios/vpn-ios +on: + push: + workflow_dispatch: +concurrency: + group: "${{ github.ref }}" + cancel-in-progress: true +env: + PIA_STAGING_ENDPOINT: "${{ secrets.PIA_STAGING_ENDPOINT }}" + PIA_CUSTOM_SERVERS: chipotle251:US:chipotle251.londontrustmedia.com:108.61.57.211:8080:500 sharingan:GB:sharingan.londontrustmedia.com:185.195.200.20:8080:500 + PIA_FIREBASE_PLIST: CLIENT_ID599746893663-naub4m5ppoos0tuodrjuk67t0q5saj3m.apps.googleusercontent.comREVERSED_CLIENT_IDcom.googleusercontent.apps.599746893663-naub4m5ppoos0tuodrjuk67t0q5saj3mAPI_KEYAIzaSyD_9Gdzi4WgNfAwl9PPupph1eWnf0zstA4GCM_SENDER_ID599746893663PLIST_VERSION1BUNDLE_IDcom.privateinternetaccess.ios.PIA-VPNPROJECT_IDpia-ios-e7da0STORAGE_BUCKETpia-ios-e7da0.appspot.comIS_ADS_ENABLEDIS_ANALYTICS_ENABLEDIS_APPINVITE_ENABLEDIS_GCM_ENABLEDIS_SIGNIN_ENABLEDGOOGLE_APP_ID1:599746893663:ios:9a64d6b91b33eb1f3088eaDATABASE_URLhttps://pia-ios-e7da0.firebaseio.com + APPCENTER_API_TOKEN: "${{ secrets.APPCENTER_API_TOKEN }}" + APPCENTER_OWNER_NAME: Kape + APPCENTER_APP_NAME: PIA-VPN + APPCENTER_DISTRIBUTE_DESTINATIONS: QA,Developers + APPSTORE_USERNAME: "${{ secrets.APPSTORE_USERNAME }}" + APPSTORE_SPECIFIC_PWD: "${{ secrets.APPSTORE_SPECIFIC_PWD }}" + APPSTORE_PASSWORD: 7Uyz@VouY3pvn!gdU7Zr + APP_IDENTIFIER: "${{ secrets.APP_IDENTIFIER }}" + APPSTORE_CONNECT_TEAM_ID: '609225' + APPSTORE_DEVELOPER_TEAM_ID: "${{ secrets.APPSTORE_DEVELOPER_TEAM_ID }}" + FASTLANE_USER: "${{ secrets.FASTLANE_USER }}" + FASTLANE_PASSWORD: 7Uyz@VouY3pvn!gdU7Zr + MATCH_PASSWORD: "${{ secrets.MATCH_PASSWORD }}" + SLACK_URL: "${{ secrets.SLACK_URL }}" + DELIVER_USER: "${{ secrets.DELIVER_USER }}" +jobs: + qa_archive: + runs-on: + - self-hosted + - ios + if: (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads//^release.*$/') + timeout-minutes: 60 + env: + LC_ALL: en_US.UTF-8 + LANG: en_US.UTF-8 + STAGE_BUILD_PATH: build + STAGE_ARTIFACTS_PATH: dist + STAGE_TESTFLIGHT_ARTIFACTS_PATH: apple_dist + STAGE_ARCHIVE_NAME: pia-vpn + SERIALIZED_ARCHIVE_JSON: "$STAGE_ARTIFACTS_PATH/notify.json" + MATCH_TYPE: adhoc + GYM_SCHEME: PIA VPN dev + GYM_EXPORT_METHOD: ad-hoc + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 50 + lfs: true + - run: gem install bundler --no-ri --no-rdoc + - run: bundle update + - run: echo "$PIA_STAGING_ENDPOINT" >"Resources/staging.endpoint" + - run: echo "$PIA_CUSTOM_SERVERS" >"Resources/custom.servers" + - run: echo "$PIA_FIREBASE_PLIST" >"Resources/GoogleService-Info.plist" + - run: bundle exec fastlane create_archive + - uses: actions/upload-artifact@v2 + if: success() + with: + name: "${{ github.job }}" + retention-days: 7 + path: "$STAGE_ARTIFACTS_PATH/$STAGE_ARCHIVE_NAME.*" + beta_manual_archive: + runs-on: + - self-hosted + - ios + if: github.event_name == 'workflow_dispatch' + timeout-minutes: 60 + env: + LC_ALL: en_US.UTF-8 + LANG: en_US.UTF-8 + STAGE_BUILD_PATH: build + STAGE_ARTIFACTS_PATH: dist + STAGE_TESTFLIGHT_ARTIFACTS_PATH: apple_dist + STAGE_ARCHIVE_NAME: pia-vpn + SERIALIZED_ARCHIVE_JSON: "$STAGE_ARTIFACTS_PATH/notify.json" + MATCH_TYPE: appstore + GYM_SCHEME: PIA VPN + GYM_EXPORT_METHOD: app-store + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 50 + lfs: true + - run: gem install bundler --no-ri --no-rdoc + - run: bundle update + - run: echo "$PIA_STAGING_ENDPOINT" >"Resources/staging.endpoint" + - run: echo "$PIA_CUSTOM_SERVERS" >"Resources/custom.servers" + - run: echo "$PIA_FIREBASE_PLIST" >"Resources/GoogleService-Info.plist" + - run: bundle exec fastlane create_beta_archive + - uses: actions/upload-artifact@v2 + if: success() + with: + name: "${{ github.job }}" + retention-days: 7 + path: "$STAGE_TESTFLIGHT_ARTIFACTS_PATH/$STAGE_ARCHIVE_NAME.*" + manual_qa_archive: + runs-on: + - self-hosted + - ios + if: github.event_name == 'workflow_dispatch' + timeout-minutes: 60 + env: + LC_ALL: en_US.UTF-8 + LANG: en_US.UTF-8 + STAGE_BUILD_PATH: build + STAGE_ARTIFACTS_PATH: dist + STAGE_TESTFLIGHT_ARTIFACTS_PATH: apple_dist + STAGE_ARCHIVE_NAME: pia-vpn + SERIALIZED_ARCHIVE_JSON: "$STAGE_ARTIFACTS_PATH/notify.json" + MATCH_TYPE: adhoc + GYM_SCHEME: PIA VPN dev + GYM_EXPORT_METHOD: ad-hoc + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 50 + lfs: true + - run: gem install bundler --no-ri --no-rdoc + - run: bundle update + - run: echo "$PIA_STAGING_ENDPOINT" >"Resources/staging.endpoint" + - run: echo "$PIA_CUSTOM_SERVERS" >"Resources/custom.servers" + - run: echo "$PIA_FIREBASE_PLIST" >"Resources/GoogleService-Info.plist" + - run: bundle exec fastlane create_archive + - uses: actions/upload-artifact@v2 + if: success() + with: + name: "${{ github.job }}" + retention-days: 7 + path: "$STAGE_ARTIFACTS_PATH/$STAGE_ARCHIVE_NAME.*" + qa_deploy: + needs: + - qa_archive + - beta_manual_archive + - manual_qa_archive + runs-on: + - self-hosted + - ios + if: (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads//^release.*$/') + environment: + name: hockey + url: "$HOCKEY_URL" + timeout-minutes: 60 + env: + LC_ALL: en_US.UTF-8 + LANG: en_US.UTF-8 + STAGE_BUILD_PATH: build + STAGE_ARTIFACTS_PATH: dist + STAGE_TESTFLIGHT_ARTIFACTS_PATH: apple_dist + STAGE_ARCHIVE_NAME: pia-vpn + SERIALIZED_ARCHIVE_JSON: "$STAGE_ARTIFACTS_PATH/notify.json" + IPA_OUTPUT_PATH: "$STAGE_ARTIFACTS_PATH/$STAGE_ARCHIVE_NAME.ipa" + FL_HOCKEY_IPA: "$STAGE_ARTIFACTS_PATH/$STAGE_ARCHIVE_NAME.ipa" + FL_HOCKEY_COMMIT_SHA: "${{ github.sha }}" + FL_HOCKEY_BUILD_SERVER_URL: "${{ github.server_url }}/${{ github.repository }}/-/jobs/${{ github.job }}" + FL_HOCKEY_REPOSITORY_URL: "${{ github.server_url }}/${{ github.repository }}" + FL_HOCKEY_NOTIFY: 'false' + FL_HOCKEY_STRATEGY: replace + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 50 + lfs: true + - uses: actions/download-artifact@v2 + - run: gem install bundler --no-ri --no-rdoc + - run: bundle update + - run: echo "$PIA_STAGING_ENDPOINT" >"Resources/staging.endpoint" + - run: echo "$PIA_CUSTOM_SERVERS" >"Resources/custom.servers" + - run: echo "$PIA_FIREBASE_PLIST" >"Resources/GoogleService-Info.plist" + - run: bundle exec fastlane qa_deploy + - uses: actions/upload-artifact@v2 + if: success() + with: + name: "${{ github.job }}" + retention-days: 7 + path: "$SERIALIZED_ARCHIVE_JSON" + beta_manual_deploy: + needs: + - qa_archive + - beta_manual_archive + - manual_qa_archive + runs-on: + - self-hosted + - ios + if: github.event_name == 'workflow_dispatch' + environment: + name: testflight + timeout-minutes: 60 + env: + LC_ALL: en_US.UTF-8 + LANG: en_US.UTF-8 + STAGE_BUILD_PATH: build + STAGE_ARTIFACTS_PATH: dist + STAGE_TESTFLIGHT_ARTIFACTS_PATH: apple_dist + STAGE_ARCHIVE_NAME: pia-vpn + SERIALIZED_ARCHIVE_JSON: "$STAGE_ARTIFACTS_PATH/notify.json" + PILOT_IPA: "$STAGE_TESTFLIGHT_ARTIFACTS_PATH/$STAGE_ARCHIVE_NAME.ipa" + PILOT_DISTRIBUTE_EXTERNAL: 'true' + DEMO_ACCOUNT_REQUIRED: 'true' + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 50 + lfs: true + - uses: actions/download-artifact@v2 + - run: gem install bundler --no-ri --no-rdoc + - run: bundle update + - run: echo "$PIA_STAGING_ENDPOINT" >"Resources/staging.endpoint" + - run: echo "$PIA_CUSTOM_SERVERS" >"Resources/custom.servers" + - run: echo "$PIA_FIREBASE_PLIST" >"Resources/GoogleService-Info.plist" + - run: bundle exec fastlane beta_deploy + manual_qa_deploy: + needs: + - qa_archive + - beta_manual_archive + - manual_qa_archive + runs-on: + - self-hosted + - ios + if: github.event_name == 'workflow_dispatch' + environment: + name: hockey + url: "$HOCKEY_URL" + timeout-minutes: 60 + env: + LC_ALL: en_US.UTF-8 + LANG: en_US.UTF-8 + STAGE_BUILD_PATH: build + STAGE_ARTIFACTS_PATH: dist + STAGE_TESTFLIGHT_ARTIFACTS_PATH: apple_dist + STAGE_ARCHIVE_NAME: pia-vpn + SERIALIZED_ARCHIVE_JSON: "$STAGE_ARTIFACTS_PATH/notify.json" + IPA_OUTPUT_PATH: "$STAGE_ARTIFACTS_PATH/$STAGE_ARCHIVE_NAME.ipa" + FL_HOCKEY_IPA: "$STAGE_ARTIFACTS_PATH/$STAGE_ARCHIVE_NAME.ipa" + FL_HOCKEY_COMMIT_SHA: "${{ github.sha }}" + FL_HOCKEY_BUILD_SERVER_URL: "${{ github.server_url }}/${{ github.repository }}/-/jobs/${{ github.job }}" + FL_HOCKEY_REPOSITORY_URL: "${{ github.server_url }}/${{ github.repository }}" + FL_HOCKEY_NOTIFY: 'false' + FL_HOCKEY_STRATEGY: replace + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 50 + lfs: true + - uses: actions/download-artifact@v2 + - run: gem install bundler --no-ri --no-rdoc + - run: bundle update + - run: echo "$PIA_STAGING_ENDPOINT" >"Resources/staging.endpoint" + - run: echo "$PIA_CUSTOM_SERVERS" >"Resources/custom.servers" + - run: echo "$PIA_FIREBASE_PLIST" >"Resources/GoogleService-Info.plist" + - run: bundle exec fastlane qa_deploy + - uses: actions/upload-artifact@v2 + if: success() + with: + name: "${{ github.job }}" + retention-days: 7 + path: "$SERIALIZED_ARCHIVE_JSON" diff --git a/PIA VPN Tunnel/PacketTunnelProvider.swift b/PIA VPN Tunnel/PacketTunnelProvider.swift index 36c8eb1d4..67aa1e0c5 100644 --- a/PIA VPN Tunnel/PacketTunnelProvider.swift +++ b/PIA VPN Tunnel/PacketTunnelProvider.swift @@ -20,7 +20,7 @@ // Internet Access iOS Client. If not, see . // -import TunnelKit +import TunnelKitOpenVPNAppExtension class PacketTunnelProvider: OpenVPNTunnelProvider { } diff --git a/PIA VPN UITests/Credentials.plist b/PIA VPN UITests/Credentials.plist new file mode 100644 index 000000000..e256b0454 --- /dev/null +++ b/PIA VPN UITests/Credentials.plist @@ -0,0 +1,17 @@ + + + + + expired + + valid + + invalid + + username + random.username + password + passWord@533 + + + diff --git a/PIA VPN UITests/CredentialsUtil.swift b/PIA VPN UITests/CredentialsUtil.swift new file mode 100644 index 000000000..82d372ea4 --- /dev/null +++ b/PIA VPN UITests/CredentialsUtil.swift @@ -0,0 +1,47 @@ +// +// CredentialsUtil.swift +// PIA VPN +// +// Created by Waleed Mahmood on 08.03.22. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation + +public enum CredentialsType: String { + case valid = "valid" + case expired = "expired" + case invalid = "invalid" +} + +public struct Credentials: Codable { + let username: String + let password: String + + init(from dictionary: Any) throws { + let data = try JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted) + let decoder = JSONDecoder() + self = try decoder.decode(Self.self, from: data) + } +} + +public class CredentialsUtil { + public static func credentials(type: CredentialsType) -> Credentials { + let bundle = Bundle(for: CredentialsUtil.self) + guard let filePath = bundle.path(forResource: "Credentials", ofType: "plist") else { + fatalError("Couldn't find file 'Credentials.plist'") + } + + let plist = NSDictionary(contentsOfFile: filePath) + guard let dictionary = plist?.object(forKey: type.rawValue) as? [String: String] else { + fatalError("Couldn't find key '\(type.rawValue)' in 'Credentials.plist'") + } + + do { + return try Credentials(from: dictionary) + } + catch { +  fatalError("Credential file does not contain required information") + } + } +} diff --git a/PIA VPN UITests/PIALaunchTests.swift b/PIA VPN UITests/PIALaunchTests.swift new file mode 100644 index 000000000..fc904bb62 --- /dev/null +++ b/PIA VPN UITests/PIALaunchTests.swift @@ -0,0 +1,33 @@ +// +// PIALaunchTests.swift +// PIA VPN UITests +// +// Created by Waleed Mahmood on 01.03.22. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +class PIALaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/PIA VPN UITests/PIALoginTests.swift b/PIA VPN UITests/PIALoginTests.swift new file mode 100644 index 000000000..af87077ab --- /dev/null +++ b/PIA VPN UITests/PIALoginTests.swift @@ -0,0 +1,145 @@ +// +// PIALoginTests.swift +// PIA VPN UITests +// +// Created by Waleed Mahmood on 01.03.22. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import XCTest +import PIALibrary +import Pods_PIA_VPN_dev + +class PIALoginTests: XCTestCase { + + static let timeoutUIOps: TimeInterval = 10.0 + let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + app.launch() + navigateToGetStartedViewController() + } + + override func tearDownWithError() throws { + // when test finishes logout + navigateToGetStartedViewController() + } + + private func navigateToGetStartedViewController() { + // check if we have a side menu + if app.navigationBars.buttons[Accessibility.Id.Dashboard.menu].exists { + openSideMenuAndTapLogout() + } + } + + private func openSideMenuAndTapLogout() { + app.navigationBars.buttons[Accessibility.Id.Dashboard.menu].tap() + + if app.cells[Accessibility.Id.Menu.logout].waitForExistence(timeout: PIALoginTests.timeoutUIOps) { + app.cells[Accessibility.Id.Menu.logout].tap() + } else { + XCTAssert(false, "PIALoginTests:: A side menu is found but no logout cell is found") + } + if app.buttons[Accessibility.Id.Dialog.destructive].waitForExistence(timeout: PIALoginTests.timeoutUIOps) { + app.buttons[Accessibility.Id.Dialog.destructive].tap() + } else { + XCTAssert(false, "PIALoginTests:: Logout alert destructive button is not found") + } + } + + private func navigateToLoginViewController() { + var conversionSubviewVisible = false + + // wait for feature flags + var submitButtonExists = app.buttons[Accessibility.Id.Login.submit].waitForExistence(timeout: PIALoginTests.timeoutUIOps) + + // check if new button should be used + if !submitButtonExists { + submitButtonExists = app.buttons[Accessibility.Id.Login.submitNew].waitForExistence(timeout: PIALoginTests.timeoutUIOps) + conversionSubviewVisible = true + } + + if submitButtonExists { + if submitButtonExists && !conversionSubviewVisible { + app.buttons[Accessibility.Id.Login.submit].tap() + } else { + app.buttons[Accessibility.Id.Login.submitNew].tap() + } + + } else { + XCTAssert(false, "PIALoginTests:: One of the Login buttons on GetStartedViewController is either not identifiable or have been moved") + } + } + + private func fillLoginScreen(with credentials: Credentials) { + let usernameTextField = app.textFields[Accessibility.Id.Login.username] + let passwordTextField = app.secureTextFields[Accessibility.Id.Login.password] + + if usernameTextField.exists && passwordTextField.exists { + // Type username + usernameTextField.tap() + usernameTextField.typeText(credentials.username) + + // Type password + passwordTextField.tap() + passwordTextField.typeText(credentials.password) + } else { + XCTAssert(false, "PIALoginTests:: Username and Password text fields on LoginViewController are either not identifiable or are moved") + } + } + + private func loginUser(ofType: CredentialsType) { + navigateToLoginViewController() + switch ofType { + case .valid: + fillLoginScreen(with: CredentialsUtil.credentials(type: .valid)) + case .expired: + fillLoginScreen(with: CredentialsUtil.credentials(type: .expired)) + case .invalid: + fillLoginScreen(with: CredentialsUtil.credentials(type: .invalid)) + } + + let loginButton = app.buttons[Accessibility.Id.Login.submit] + loginButton.tap() + } + + func testInvalidUserLogin() throws { + loginUser(ofType: .invalid) + + let bannerViewExists = app.staticTexts["Your username or password is incorrect."].waitForExistence(timeout: PIALoginTests.timeoutUIOps) + XCTAssertTrue(bannerViewExists, "PIALoginTests::testInvalidUserLogin() failed") + } + + func testExpiredUserLogin() throws { + app.switchEnvironment(to: .staging) + loginUser(ofType: .expired) + + let bannerViewExists = app.staticTexts["Your username or password is incorrect."].waitForExistence(timeout: PIALoginTests.timeoutUIOps) + XCTAssertTrue(bannerViewExists, "PIALoginTests::testExpiredUserLogin() failed") + } + + func testValidUserLogin() throws { + app.switchEnvironment(to: .production) + loginUser(ofType: .valid) + + let viewTitleExists = app.staticTexts["PIA needs access to your VPN profiles to secure your traffic"].waitForExistence(timeout: PIALoginTests.timeoutUIOps) + let okButtonExist = app.buttons[Accessibility.Id.Permissions.submit].waitForExistence(timeout: PIALoginTests.timeoutUIOps) + XCTAssertTrue(viewTitleExists && okButtonExist, "PIALoginTests::testActiveUserLogin() failed") + } +} + +private extension XCUIApplication { + + func switchEnvironment(to environment: Client.Environment) { + // wait until the button is available + _ = self.buttons[Accessibility.Id.Welcome.environment].waitForExistence(timeout: PIALoginTests.timeoutUIOps) + let environmentButton = self.buttons[Accessibility.Id.Welcome.environment] + + if environmentButton.label.lowercased() != environment.rawValue.lowercased() { + + // then click it to switch environment + environmentButton.tap() + } + } +} diff --git a/PIA VPN UITests/PIAUITests.swift b/PIA VPN UITests/PIAUITests.swift new file mode 100644 index 000000000..c220431f0 --- /dev/null +++ b/PIA VPN UITests/PIAUITests.swift @@ -0,0 +1,43 @@ +// +// PIAUITests.swift +// PIA VPN UITests +// +// Created by Waleed Mahmood on 01.03.22. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +class PIAUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/PIA VPN.xcodeproj/project.pbxproj b/PIA VPN.xcodeproj/project.pbxproj index 1e43708e9..622eb63a9 100644 --- a/PIA VPN.xcodeproj/project.pbxproj +++ b/PIA VPN.xcodeproj/project.pbxproj @@ -3,11 +3,10 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ - 078FA875634A84BD6728DF93 /* Pods_PIA_VPN_WG_Tunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28DFD7434ECA3047EB4A2C75 /* Pods_PIA_VPN_WG_Tunnel.framework */; }; 0E0715E7201CBB7100D6F666 /* Flags-dev.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0E0715E5201CBB7100D6F666 /* Flags-dev.plist */; }; 0E0786DE1EFA7EAE00F77466 /* Components.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0E0786DD1EFA7EAE00F77466 /* Components.plist */; }; 0E1F318620176A5F00FC1000 /* Theme+DarkPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F318520176A5F00FC1000 /* Theme+DarkPalette.swift */; }; @@ -128,7 +127,6 @@ 0EFDC1ED1FE4B9DC007C0B9B /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFDC1EB1FE4B9DC007C0B9B /* AppConstants.swift */; }; 0EFDC1EF1FE4B9E6007C0B9B /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFDC1EE1FE4B9E6007C0B9B /* AppConfiguration.swift */; }; 0EFDC1F01FE4B9E6007C0B9B /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFDC1EE1FE4B9E6007C0B9B /* AppConfiguration.swift */; }; - 12CE273914ED9E7533E3A51E /* Pods_PIA_VPN_dev.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CA493DF6602BFDE96E0E77F /* Pods_PIA_VPN_dev.framework */; }; 2909869018566430002D9687 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2909868F18566430002D9687 /* Security.framework */; }; 291C6380183EBC210039EC03 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 291C637F183EBC210039EC03 /* Foundation.framework */; }; 291C6382183EBC210039EC03 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 291C6381183EBC210039EC03 /* CoreGraphics.framework */; }; @@ -147,8 +145,17 @@ 3545E98226AADB2B00B812CC /* ServerSelectingTileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3545E98126AADB2B00B812CC /* ServerSelectingTileCell.swift */; }; 3545E98326AADC7C00B812CC /* ServerSelectingTileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3545E98126AADB2B00B812CC /* ServerSelectingTileCell.swift */; }; 3545E98426AADC7E00B812CC /* ServerSelectionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3545E97F26AAD60C00B812CC /* ServerSelectionDelegate.swift */; }; - 3732E0F0C64DA8513903087D /* Pods_PIA_VPN_Tunnel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02823EF3398B7E2C32FA7544 /* Pods_PIA_VPN_Tunnel.framework */; }; - 73E0B0544DF023785B11C4B9 /* Pods_PIA_VPN.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E23999135612F39173E9C1E /* Pods_PIA_VPN.framework */; }; + 69446CB32AA7502E0080F446 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69446CB22AA7502E0080F446 /* PIALibrary */; }; + 69446CB52AA7503A0080F446 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69446CB42AA7503A0080F446 /* PIALibrary */; }; + 69446CB72AA750440080F446 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69446CB62AA750440080F446 /* PIALibrary */; }; + 69446CB92AA7504B0080F446 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69446CB82AA7504B0080F446 /* PIALibrary */; }; + 7EB8D11F27CE2B020030B060 /* PIAUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB8D11E27CE2B020030B060 /* PIAUITests.swift */; }; + 7EB8D12127CE2B5D0030B060 /* PIALaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB8D12027CE2B5D0030B060 /* PIALaunchTests.swift */; }; + 7EB8D12327CE7D4C0030B060 /* PIALoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB8D12227CE7D4C0030B060 /* PIALoginTests.swift */; }; + 7EC2972F27D8B8580061C56A /* CredentialsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC2972D27D8B8580061C56A /* CredentialsUtil.swift */; }; + 7EC2973027D8B8580061C56A /* Credentials.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7EC2972E27D8B8580061C56A /* Credentials.plist */; }; + 7ECBB8DD27B6C4F500C0C774 /* UserSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECBB8DC27B6C4F500C0C774 /* UserSurveyManager.swift */; }; + 7ECBB8DE27BA5FCE00C0C774 /* UserSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECBB8DC27B6C4F500C0C774 /* UserSurveyManager.swift */; }; 821674F12678A1BE0028E4FD /* SettingsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821674F02678A1BE0028E4FD /* SettingsDelegate.swift */; }; 821674F22678A1BE0028E4FD /* SettingsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821674F02678A1BE0028E4FD /* SettingsDelegate.swift */; }; 82183D7B2500FD460033023F /* String+Substrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82183D7A2500FD460033023F /* String+Substrings.swift */; }; @@ -162,7 +169,7 @@ 82183D9C25014FDC0033023F /* PIAHeaderCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 82183D9525014FDC0033023F /* PIAHeaderCollectionViewCell.xib */; }; 82183D9D25014FDC0033023F /* PIAHeaderCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 82183D9525014FDC0033023F /* PIAHeaderCollectionViewCell.xib */; }; 8221922B24CECFE700C24F1C /* NMTTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8221922A24CECFE700C24F1C /* NMTTests.swift */; }; - 822F97B4251DD53100644EF2 /* WidgetUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822F97B3251DD53100644EF2 /* WidgetUtils.swift */; }; + 822F97B4251DD53100644EF2 /* WidgetUserDefaultsDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822F97B3251DD53100644EF2 /* WidgetUserDefaultsDatasource.swift */; }; 824C530824C046CB003DB740 /* ConnectionTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824C530724C046CA003DB740 /* ConnectionTile.swift */; }; 824C530924C046CB003DB740 /* ConnectionTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824C530724C046CA003DB740 /* ConnectionTile.swift */; }; 824C531524C04796003DB740 /* ConnectionTileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824C531324C04796003DB740 /* ConnectionTileCollectionViewCell.swift */; }; @@ -176,7 +183,7 @@ 8269A6DA251CB5E0000B4DBF /* PIAWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8269A6D9251CB5E0000B4DBF /* PIAWidget.swift */; }; 8269A6DD251CB5E3000B4DBF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8269A6DC251CB5E3000B4DBF /* Assets.xcassets */; }; 8269A6E3251CB5E3000B4DBF /* PIAWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8269A6D5251CB5E0000B4DBF /* PIAWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 8269A6FE251CBB36000B4DBF /* WidgetContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8269A6FD251CBB36000B4DBF /* WidgetContent.swift */; }; + 8269A6FE251CBB36000B4DBF /* WidgetInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8269A6FD251CBB36000B4DBF /* WidgetInformation.swift */; }; 826BE8EE253861BE002339F3 /* DedicatedRegionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826BE8ED253861BE002339F3 /* DedicatedRegionCell.swift */; }; 826BE8EF253861BE002339F3 /* DedicatedRegionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826BE8ED253861BE002339F3 /* DedicatedRegionCell.swift */; }; 8272C62726540B2100D846A8 /* ProtocolSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8272C62626540B2100D846A8 /* ProtocolSettingsViewController.swift */; }; @@ -258,7 +265,28 @@ 82CAB8FE255C115100BB08EF /* MessagesTile.xib in Resources */ = {isa = PBXBuildFile; fileRef = 82CAB8FC255C115100BB08EF /* MessagesTile.xib */; }; 82F41376264E8AC20098FF4B /* SettingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F41375264E8AC20098FF4B /* SettingOptions.swift */; }; 82F41377264E8AC20098FF4B /* SettingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F41375264E8AC20098FF4B /* SettingOptions.swift */; }; - 929703E310E04502651A34E9 /* Pods_PIA_VPNTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F453DF1F58B8C2676041EDF /* Pods_PIA_VPNTests.framework */; }; + AA36CDBB28A6622A00180A33 /* TweetNacl in Frameworks */ = {isa = PBXBuildFile; productRef = AA36CDBA28A6622A00180A33 /* TweetNacl */; }; + AA36CDC428A6711300180A33 /* Popover in Frameworks */ = {isa = PBXBuildFile; productRef = AA36CDC328A6711300180A33 /* Popover */; }; + AA36CDC928A6733500180A33 /* DZNEmptyDataSet in Frameworks */ = {isa = PBXBuildFile; productRef = AA36CDC828A6733500180A33 /* DZNEmptyDataSet */; }; + AA36CDCC28A673C900180A33 /* GradientProgressBar in Frameworks */ = {isa = PBXBuildFile; productRef = AA36CDCB28A673C900180A33 /* GradientProgressBar */; }; + AA36CDCF28A6746500180A33 /* SideMenu in Frameworks */ = {isa = PBXBuildFile; productRef = AA36CDCE28A6746500180A33 /* SideMenu */; }; + AA36CDDB28A6860F00180A33 /* TweetNacl in Frameworks */ = {isa = PBXBuildFile; productRef = AA36CDDA28A6860F00180A33 /* TweetNacl */; }; + AA36CDDE28A6878000180A33 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = AA36CDDD28A6878000180A33 /* AlamofireImage */; }; + AA52C59F28E5ECD400D025AF /* PIAWidgetVpnDetaislRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA52C59E28E5ECD400D025AF /* PIAWidgetVpnDetaislRow.swift */; }; + AABF826D28AD185E00CDAC64 /* TweetNacl in Frameworks */ = {isa = PBXBuildFile; productRef = AABF826C28AD185E00CDAC64 /* TweetNacl */; }; + AABF826F28AD187800CDAC64 /* Popover in Frameworks */ = {isa = PBXBuildFile; productRef = AABF826E28AD187800CDAC64 /* Popover */; }; + AABF827128AD188700CDAC64 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = AABF827028AD188700CDAC64 /* AlamofireImage */; }; + AABF827328AE2FF500CDAC64 /* GradientProgressBar in Frameworks */ = {isa = PBXBuildFile; productRef = AABF827228AE2FF500CDAC64 /* GradientProgressBar */; }; + AABF827528AE2FFE00CDAC64 /* SideMenu in Frameworks */ = {isa = PBXBuildFile; productRef = AABF827428AE2FFE00CDAC64 /* SideMenu */; }; + AABF827628AE302300CDAC64 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD606AB921C7A17900E0781D /* NetworkExtension.framework */; }; + AABF827828AE333200CDAC64 /* DZNEmptyDataSet in Frameworks */ = {isa = PBXBuildFile; productRef = AABF827728AE333200CDAC64 /* DZNEmptyDataSet */; }; + AAE8789C28E4679500557F26 /* WidgetPersistenceDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE8789B28E4679500557F26 /* WidgetPersistenceDatasource.swift */; }; + AAE8789F28E4696300557F26 /* PIAWidgetProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE8789E28E4696300557F26 /* PIAWidgetProvider.swift */; }; + AAE878A128E46C1600557F26 /* PIAWidgetPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE878A028E46C1600557F26 /* PIAWidgetPreview.swift */; }; + AAE878A328E46D2B00557F26 /* PIAWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE878A228E46D2B00557F26 /* PIAWidgetView.swift */; }; + AAE878A528E4723B00557F26 /* PIACircleVpnButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE878A428E4723B00557F26 /* PIACircleVpnButton.swift */; }; + AAE878A728E473A400557F26 /* PIAWidgetVpnDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE878A628E473A400557F26 /* PIAWidgetVpnDetailsView.swift */; }; + AAE878A928E4765F00557F26 /* PIAIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE878A828E4765F00557F26 /* PIAIconView.swift */; }; DD052AC32419003300AD3A24 /* ProtocolTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD052AC22419003300AD3A24 /* ProtocolTableViewCell.swift */; }; DD052AC42419020E00AD3A24 /* ProtocolTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD052AC22419003300AD3A24 /* ProtocolTableViewCell.swift */; }; DD07AACC242CBFF2000EE1A3 /* AddEmailToAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD07AACB242CBFF2000EE1A3 /* AddEmailToAccountViewController.swift */; }; @@ -310,7 +338,6 @@ DD51F8C52372E4C8009FEED3 /* PemUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD51F8C32372E4C8009FEED3 /* PemUtil.swift */; }; DD522D22237D74380072F555 /* BooleanUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD522D21237D74380072F555 /* BooleanUtil.swift */; }; DD522D23237D74380072F555 /* BooleanUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD522D21237D74380072F555 /* BooleanUtil.swift */; }; - DD58F4B821AD579A00D043F7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DD58F4B721AD579A00D043F7 /* GoogleService-Info.plist */; }; DD58F4BF21B12CFE00D043F7 /* PIAConnectionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD58F4BE21B12CFE00D043F7 /* PIAConnectionButton.swift */; }; DD58F4C021B12CFE00D043F7 /* PIAConnectionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD58F4BE21B12CFE00D043F7 /* PIAConnectionButton.swift */; }; DD606ABA21C7A17900E0781D /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD606AB921C7A17900E0781D /* NetworkExtension.framework */; }; @@ -386,6 +413,12 @@ DDFCFA9521E892130081F235 /* RegionTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFCFA9321E892130081F235 /* RegionTile.swift */; }; DDFCFA9721E8921F0081F235 /* RegionTile.xib in Resources */ = {isa = PBXBuildFile; fileRef = DDFCFA9621E8921F0081F235 /* RegionTile.xib */; }; DDFCFA9821E8921F0081F235 /* RegionTile.xib in Resources */ = {isa = PBXBuildFile; fileRef = DDFCFA9621E8921F0081F235 /* RegionTile.xib */; }; + E5F52A182A8A5D5300828883 /* WifiNetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A172A8A5D5300828883 /* WifiNetworkMonitor.swift */; }; + E5F52A192A8A5D5300828883 /* WifiNetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A172A8A5D5300828883 /* WifiNetworkMonitor.swift */; }; + E5F52A1B2A8A5E1E00828883 /* IPv4Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A1A2A8A5E1E00828883 /* IPv4Address.swift */; }; + E5F52A1C2A8A5E1E00828883 /* IPv4Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A1A2A8A5E1E00828883 /* IPv4Address.swift */; }; + E5F52A1F2A8A614900828883 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A1E2A8A614900828883 /* NetworkMonitor.swift */; }; + E5F52A202A8A614900828883 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5F52A1E2A8A614900828883 /* NetworkMonitor.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -431,6 +464,13 @@ remoteGlobalIDString = 0EFB606F203D7A2C0095398C; remoteInfo = "PIA VPN AdBlocker"; }; + 7EC2973127D8BCB60061C56A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 291C6374183EBC210039EC03 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0EE2200B1F4EF307002805AE; + remoteInfo = "PIA VPN dev"; + }; 8269A6E1251CB5E3000B4DBF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 291C6374183EBC210039EC03 /* Project object */; @@ -438,6 +478,20 @@ remoteGlobalIDString = 8269A6D4251CB5E0000B4DBF; remoteInfo = PIAWidgetExtension; }; + AABF827928AE336000CDAC64 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 291C6374183EBC210039EC03 /* Project object */; + proxyType = 1; + remoteGlobalIDString = DDC8F5EB23EC106F005D19C6; + remoteInfo = "PIA VPN WG Tunnel"; + }; + AABF827B28AE336F00CDAC64 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 291C6374183EBC210039EC03 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8269A6D4251CB5E0000B4DBF; + remoteInfo = PIAWidgetExtension; + }; DDC8F5F223EC1070005D19C6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 291C6374183EBC210039EC03 /* Project object */; @@ -488,8 +542,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 02823EF3398B7E2C32FA7544 /* Pods_PIA_VPN_Tunnel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIA_VPN_Tunnel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 0BE4A5FC9BACD5CAB3D6CEF0 /* Pods-PIA VPN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPN.release.xcconfig"; path = "Target Support Files/Pods-PIA VPN/Pods-PIA VPN.release.xcconfig"; sourceTree = ""; }; 0E0715E5201CBB7100D6F666 /* Flags-dev.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Flags-dev.plist"; sourceTree = ""; }; 0E0786DD1EFA7EAE00F77466 /* Components.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Components.plist; sourceTree = ""; }; 0E1F318520176A5F00FC1000 /* Theme+DarkPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Theme+DarkPalette.swift"; sourceTree = ""; }; @@ -594,7 +646,6 @@ 0EFDC1E51FE4ABAA007C0B9B /* Notification+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+App.swift"; sourceTree = ""; }; 0EFDC1EB1FE4B9DC007C0B9B /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 0EFDC1EE1FE4B9E6007C0B9B /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; - 28DFD7434ECA3047EB4A2C75 /* Pods_PIA_VPN_WG_Tunnel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIA_VPN_WG_Tunnel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2909868F18566430002D9687 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 291C637C183EBC210039EC03 /* PIA VPN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PIA VPN.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 291C637F183EBC210039EC03 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -612,10 +663,13 @@ 3524670D26B432ED00E3F0AC /* DashboardViewController+ServerSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DashboardViewController+ServerSelection.swift"; sourceTree = ""; }; 3545E97F26AAD60C00B812CC /* ServerSelectionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionDelegate.swift; sourceTree = ""; }; 3545E98126AADB2B00B812CC /* ServerSelectingTileCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectingTileCell.swift; sourceTree = ""; }; - 59EC919445EA680B691ACC88 /* Pods-PIA VPNTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPNTests.debug.xcconfig"; path = "Target Support Files/Pods-PIA VPNTests/Pods-PIA VPNTests.debug.xcconfig"; sourceTree = ""; }; - 678A887CFC02122F4FB83216 /* Pods-PIA VPNTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPNTests.release.xcconfig"; path = "Target Support Files/Pods-PIA VPNTests/Pods-PIA VPNTests.release.xcconfig"; sourceTree = ""; }; - 6CA493DF6602BFDE96E0E77F /* Pods_PIA_VPN_dev.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIA_VPN_dev.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 7F6FF659836C1192332662A8 /* Pods-PIA VPN dev.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPN dev.debug.xcconfig"; path = "Target Support Files/Pods-PIA VPN dev/Pods-PIA VPN dev.debug.xcconfig"; sourceTree = ""; }; + 7EB8D11327CCF4C20030B060 /* PIA VPN UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "PIA VPN UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7EB8D11E27CE2B020030B060 /* PIAUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAUITests.swift; sourceTree = ""; }; + 7EB8D12027CE2B5D0030B060 /* PIALaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIALaunchTests.swift; sourceTree = ""; }; + 7EB8D12227CE7D4C0030B060 /* PIALoginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIALoginTests.swift; sourceTree = ""; }; + 7EC2972D27D8B8580061C56A /* CredentialsUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsUtil.swift; sourceTree = ""; }; + 7EC2972E27D8B8580061C56A /* Credentials.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Credentials.plist; sourceTree = ""; }; + 7ECBB8DC27B6C4F500C0C774 /* UserSurveyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSurveyManager.swift; sourceTree = ""; }; 821674F02678A1BE0028E4FD /* SettingsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDelegate.swift; sourceTree = ""; }; 82183D7A2500FD460033023F /* String+Substrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Substrings.swift"; sourceTree = ""; }; 82183D9225014FDC0033023F /* NetworkCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NetworkCollectionViewCell.xib; sourceTree = ""; }; @@ -623,7 +677,7 @@ 82183D9425014FDC0033023F /* NetworkFooterCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NetworkFooterCollectionViewCell.xib; sourceTree = ""; }; 82183D9525014FDC0033023F /* PIAHeaderCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PIAHeaderCollectionViewCell.xib; sourceTree = ""; }; 8221922A24CECFE700C24F1C /* NMTTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NMTTests.swift; sourceTree = ""; }; - 822F97B3251DD53100644EF2 /* WidgetUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetUtils.swift; sourceTree = ""; }; + 822F97B3251DD53100644EF2 /* WidgetUserDefaultsDatasource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetUserDefaultsDatasource.swift; sourceTree = ""; }; 824C530724C046CA003DB740 /* ConnectionTile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionTile.swift; sourceTree = ""; }; 824C531324C04796003DB740 /* ConnectionTileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionTileCollectionViewCell.swift; sourceTree = ""; }; 824C531424C04796003DB740 /* ConnectionTileCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConnectionTileCollectionViewCell.xib; sourceTree = ""; }; @@ -635,7 +689,7 @@ 8269A6DC251CB5E3000B4DBF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8269A6DE251CB5E3000B4DBF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8269A6ED251CB5ED000B4DBF /* PIAWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PIAWidgetExtension.entitlements; sourceTree = ""; }; - 8269A6FD251CBB36000B4DBF /* WidgetContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetContent.swift; sourceTree = ""; }; + 8269A6FD251CBB36000B4DBF /* WidgetInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetInformation.swift; sourceTree = ""; }; 826BE8ED253861BE002339F3 /* DedicatedRegionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DedicatedRegionCell.swift; sourceTree = ""; }; 8272C62626540B2100D846A8 /* ProtocolSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolSettingsViewController.swift; sourceTree = ""; }; 8272C62926551F9B00D846A8 /* SettingPopoverSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SettingPopoverSelectionView.swift; path = Settings/SettingPopoverSelectionView.swift; sourceTree = ""; }; @@ -678,11 +732,14 @@ 82CAB8FC255C115100BB08EF /* MessagesTile.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MessagesTile.xib; sourceTree = ""; }; 82E20B0D24F5666E0065EFE3 /* PIAAccount.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PIAAccount.framework; path = ../account/build/bin/iOS/PIAAccountReleaseFramework/PIAAccount.framework; sourceTree = ""; }; 82F41375264E8AC20098FF4B /* SettingOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingOptions.swift; sourceTree = ""; }; - 8E23999135612F39173E9C1E /* Pods_PIA_VPN.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIA_VPN.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9F453DF1F58B8C2676041EDF /* Pods_PIA_VPNTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PIA_VPNTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - A6454C66DD80FE480E48BFA8 /* Pods-PIA VPN WG Tunnel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPN WG Tunnel.debug.xcconfig"; path = "Target Support Files/Pods-PIA VPN WG Tunnel/Pods-PIA VPN WG Tunnel.debug.xcconfig"; sourceTree = ""; }; - AC26C61F8D1F24341AD67A4A /* Pods-PIA VPN Tunnel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPN Tunnel.release.xcconfig"; path = "Target Support Files/Pods-PIA VPN Tunnel/Pods-PIA VPN Tunnel.release.xcconfig"; sourceTree = ""; }; - B5E30A2B8E23D816328C690E /* Pods-PIA VPN WG Tunnel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPN WG Tunnel.release.xcconfig"; path = "Target Support Files/Pods-PIA VPN WG Tunnel/Pods-PIA VPN WG Tunnel.release.xcconfig"; sourceTree = ""; }; + AA52C59E28E5ECD400D025AF /* PIAWidgetVpnDetaislRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAWidgetVpnDetaislRow.swift; sourceTree = ""; }; + AAE8789B28E4679500557F26 /* WidgetPersistenceDatasource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPersistenceDatasource.swift; sourceTree = ""; }; + AAE8789E28E4696300557F26 /* PIAWidgetProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAWidgetProvider.swift; sourceTree = ""; }; + AAE878A028E46C1600557F26 /* PIAWidgetPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAWidgetPreview.swift; sourceTree = ""; }; + AAE878A228E46D2B00557F26 /* PIAWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAWidgetView.swift; sourceTree = ""; }; + AAE878A428E4723B00557F26 /* PIACircleVpnButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIACircleVpnButton.swift; sourceTree = ""; }; + AAE878A628E473A400557F26 /* PIAWidgetVpnDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAWidgetVpnDetailsView.swift; sourceTree = ""; }; + AAE878A828E4765F00557F26 /* PIAIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAIconView.swift; sourceTree = ""; }; DD052AC22419003300AD3A24 /* ProtocolTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolTableViewCell.swift; sourceTree = ""; }; DD07AACB242CBFF2000EE1A3 /* AddEmailToAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEmailToAccountViewController.swift; sourceTree = ""; }; DD125DC421E77046004ECCB6 /* QuickConnectTile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectTile.swift; sourceTree = ""; }; @@ -708,7 +765,6 @@ DD51F8B22372E494009FEED3 /* PIA-RSA-4096.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "PIA-RSA-4096.pem"; sourceTree = ""; }; DD51F8C32372E4C8009FEED3 /* PemUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PemUtil.swift; sourceTree = ""; }; DD522D21237D74380072F555 /* BooleanUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BooleanUtil.swift; sourceTree = ""; }; - DD58F4B721AD579A00D043F7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; DD58F4BE21B12CFE00D043F7 /* PIAConnectionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAConnectionButton.swift; sourceTree = ""; }; DD606AB921C7A17900E0781D /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; DD606ABB21C904BB00E0781D /* PIAHotspotHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAHotspotHelper.swift; sourceTree = ""; }; @@ -752,9 +808,9 @@ DDFCFA8E21E892070081F235 /* RegionTileCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RegionTileCollectionViewCell.xib; sourceTree = ""; }; DDFCFA9321E892130081F235 /* RegionTile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionTile.swift; sourceTree = ""; }; DDFCFA9621E8921F0081F235 /* RegionTile.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RegionTile.xib; sourceTree = ""; }; - E65358880CC5204F785ADE10 /* Pods-PIA VPN Tunnel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPN Tunnel.debug.xcconfig"; path = "Target Support Files/Pods-PIA VPN Tunnel/Pods-PIA VPN Tunnel.debug.xcconfig"; sourceTree = ""; }; - EFF4BBB886BD153CAD50F8E2 /* Pods-PIA VPN dev.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPN dev.release.xcconfig"; path = "Target Support Files/Pods-PIA VPN dev/Pods-PIA VPN dev.release.xcconfig"; sourceTree = ""; }; - F489C6FC39A7285DDE2A26F7 /* Pods-PIA VPN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PIA VPN.debug.xcconfig"; path = "Target Support Files/Pods-PIA VPN/Pods-PIA VPN.debug.xcconfig"; sourceTree = ""; }; + E5F52A172A8A5D5300828883 /* WifiNetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiNetworkMonitor.swift; sourceTree = ""; }; + E5F52A1A2A8A5E1E00828883 /* IPv4Address.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPv4Address.swift; sourceTree = ""; }; + E5F52A1E2A8A614900828883 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -762,7 +818,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 3732E0F0C64DA8513903087D /* Pods_PIA_VPN_Tunnel.framework in Frameworks */, + 69446CB72AA750440080F446 /* PIALibrary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -770,16 +826,23 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + AABF827128AD188700CDAC64 /* AlamofireImage in Frameworks */, + AABF827628AE302300CDAC64 /* NetworkExtension.framework in Frameworks */, 0EE220571F4EF307002805AE /* Security.framework in Frameworks */, 0EE220581F4EF307002805AE /* SystemConfiguration.framework in Frameworks */, + AABF827328AE2FF500CDAC64 /* GradientProgressBar in Frameworks */, + AABF827828AE333200CDAC64 /* DZNEmptyDataSet in Frameworks */, 0EE220591F4EF307002805AE /* libz.dylib in Frameworks */, 0EE2205A1F4EF307002805AE /* CoreText.framework in Frameworks */, 0EE2205B1F4EF307002805AE /* QuartzCore.framework in Frameworks */, 0EE2205C1F4EF307002805AE /* CoreGraphics.framework in Frameworks */, + AABF826F28AD187800CDAC64 /* Popover in Frameworks */, + 0EE2205F1F4EF307002805AE /* StoreKit.framework in Frameworks */, + 69446CB52AA7503A0080F446 /* PIALibrary in Frameworks */, 0EE2205D1F4EF307002805AE /* UIKit.framework in Frameworks */, 0EE2205E1F4EF307002805AE /* Foundation.framework in Frameworks */, - 0EE2205F1F4EF307002805AE /* StoreKit.framework in Frameworks */, - 12CE273914ED9E7533E3A51E /* Pods_PIA_VPN_dev.framework in Frameworks */, + AABF826D28AD185E00CDAC64 /* TweetNacl in Frameworks */, + AABF827528AE2FFE00CDAC64 /* SideMenu in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -787,7 +850,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 929703E310E04502651A34E9 /* Pods_PIA_VPNTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -802,6 +864,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + AA36CDC428A6711300180A33 /* Popover in Frameworks */, + AA36CDCC28A673C900180A33 /* GradientProgressBar in Frameworks */, + AA36CDCF28A6746500180A33 /* SideMenu in Frameworks */, + AA36CDBB28A6622A00180A33 /* TweetNacl in Frameworks */, + AA36CDDE28A6878000180A33 /* AlamofireImage in Frameworks */, 2909869018566430002D9687 /* Security.framework in Frameworks */, 299E58511856BD31004CFD63 /* SystemConfiguration.framework in Frameworks */, 299E585F1856C6EE004CFD63 /* libz.dylib in Frameworks */, @@ -809,10 +876,18 @@ 2985E5671856BD1200D70E28 /* QuartzCore.framework in Frameworks */, 291C6382183EBC210039EC03 /* CoreGraphics.framework in Frameworks */, 291C6384183EBC210039EC03 /* UIKit.framework in Frameworks */, + 69446CB32AA7502E0080F446 /* PIALibrary in Frameworks */, DD606ABA21C7A17900E0781D /* NetworkExtension.framework in Frameworks */, + AA36CDC928A6733500180A33 /* DZNEmptyDataSet in Frameworks */, 291C6380183EBC210039EC03 /* Foundation.framework in Frameworks */, 0E9785861DA82FF000711A24 /* StoreKit.framework in Frameworks */, - 73E0B0544DF023785B11C4B9 /* Pods_PIA_VPN.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7EB8D11027CCF4C20030B060 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; @@ -829,8 +904,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 69446CB92AA7504B0080F446 /* PIALibrary in Frameworks */, + AA36CDDB28A6860F00180A33 /* TweetNacl in Frameworks */, DDC8F5F923EC10E4005D19C6 /* NetworkExtension.framework in Frameworks */, - 078FA875634A84BD6728DF93 /* Pods_PIA_VPN_WG_Tunnel.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -946,6 +1022,7 @@ 0E76AE201D35447A00421248 /* Utils */ = { isa = PBXGroup; children = ( + E5F52A1D2A8A613400828883 /* Network */, 0EB0A849204F0CE2008BCF1D /* DataCounter.h */, 0EB0A84A204F0CE2008BCF1D /* DataCounter.m */, 0EB0A84B204F0CE2008BCF1D /* DataCounter.swift */, @@ -958,6 +1035,7 @@ DD51F8C32372E4C8009FEED3 /* PemUtil.swift */, DD522D21237D74380072F555 /* BooleanUtil.swift */, DD918575246BFA7F006B3A2B /* RatingManager.swift */, + 7ECBB8DC27B6C4F500C0C774 /* UserSurveyManager.swift */, DDD65312247E66AD00F0A897 /* CoordinatesFinder.swift */, 829EB51B2535A8FF003E74DD /* CollectionViewAutoSizeUtils.swift */, 3524670A26B431B800E3F0AC /* TrustedNetworkHelper.swift */, @@ -1019,7 +1097,6 @@ 0E0786DD1EFA7EAE00F77466 /* Components.plist */, 0E3C9A5D20EC004D00B199F9 /* custom.servers */, 0ED66BCF20A9918000333B35 /* staging.endpoint */, - DD58F4B721AD579A00D043F7 /* GoogleService-Info.plist */, ); path = Resources; sourceTree = ""; @@ -1045,9 +1122,9 @@ 0EEE1C191E4F719E00397DE2 /* Resources */, DDC8F5ED23EC1070005D19C6 /* PIA VPN WG Tunnel */, 8269A6D8251CB5E0000B4DBF /* PIAWidget */, + 7EB8D11427CCF4C20030B060 /* PIA VPN UITests */, 291C637E183EBC210039EC03 /* Frameworks */, 291C637D183EBC210039EC03 /* Products */, - 90B2842B07AEDA4CF8A2035B /* Pods */, ); sourceTree = ""; }; @@ -1061,6 +1138,7 @@ 0EFB6070203D7A2C0095398C /* PIA VPN AdBlocker.appex */, DDC8F5EC23EC106F005D19C6 /* PIA VPN WG Tunnel.appex */, 8269A6D5251CB5E0000B4DBF /* PIAWidgetExtension.appex */, + 7EB8D11327CCF4C20030B060 /* PIA VPN UITests.xctest */, ); name = Products; sourceTree = ""; @@ -1085,11 +1163,6 @@ 0E257AC41DA45D2F0000D3C3 /* NotificationCenter.framework */, 8269A67C251CB4BA000B4DBF /* WidgetKit.framework */, 8269A67E251CB4BA000B4DBF /* SwiftUI.framework */, - 8E23999135612F39173E9C1E /* Pods_PIA_VPN.framework */, - 02823EF3398B7E2C32FA7544 /* Pods_PIA_VPN_Tunnel.framework */, - 28DFD7434ECA3047EB4A2C75 /* Pods_PIA_VPN_WG_Tunnel.framework */, - 6CA493DF6602BFDE96E0E77F /* Pods_PIA_VPN_dev.framework */, - 9F453DF1F58B8C2676041EDF /* Pods_PIA_VPNTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -1161,6 +1234,18 @@ name = UI; sourceTree = ""; }; + 7EB8D11427CCF4C20030B060 /* PIA VPN UITests */ = { + isa = PBXGroup; + children = ( + 7EC2972E27D8B8580061C56A /* Credentials.plist */, + 7EC2972D27D8B8580061C56A /* CredentialsUtil.swift */, + 7EB8D12227CE7D4C0030B060 /* PIALoginTests.swift */, + 7EB8D11E27CE2B020030B060 /* PIAUITests.swift */, + 7EB8D12027CE2B5D0030B060 /* PIALaunchTests.swift */, + ); + path = "PIA VPN UITests"; + sourceTree = ""; + }; 82183D8425014F940033023F /* Menu */ = { isa = PBXGroup; children = ( @@ -1187,9 +1272,8 @@ 8269A6D8251CB5E0000B4DBF /* PIAWidget */ = { isa = PBXGroup; children = ( - 8269A6D9251CB5E0000B4DBF /* PIAWidget.swift */, - 8269A6FD251CBB36000B4DBF /* WidgetContent.swift */, - 822F97B3251DD53100644EF2 /* WidgetUtils.swift */, + AAE8789728E45C6200557F26 /* Data */, + AAE8789628E45C5D00557F26 /* Domain */, 82BAACFD25B09C9200B3C733 /* PIAWidget.intentdefinition */, 8269A6DC251CB5E3000B4DBF /* Assets.xcassets */, 8269A6DE251CB5E3000B4DBF /* Info.plist */, @@ -1243,21 +1327,61 @@ name = Settings; sourceTree = ""; }; - 90B2842B07AEDA4CF8A2035B /* Pods */ = { + AAE8789628E45C5D00557F26 /* Domain */ = { + isa = PBXGroup; + children = ( + AAE8789D28E468F400557F26 /* Widget */, + AAE8789A28E45EC500557F26 /* UI */, + ); + path = Domain; + sourceTree = ""; + }; + AAE8789728E45C6200557F26 /* Data */ = { + isa = PBXGroup; + children = ( + AAE8789928E45DDE00557F26 /* Model */, + AAE8789828E45DD500557F26 /* Cache */, + ); + path = Data; + sourceTree = ""; + }; + AAE8789828E45DD500557F26 /* Cache */ = { isa = PBXGroup; children = ( - F489C6FC39A7285DDE2A26F7 /* Pods-PIA VPN.debug.xcconfig */, - 0BE4A5FC9BACD5CAB3D6CEF0 /* Pods-PIA VPN.release.xcconfig */, - E65358880CC5204F785ADE10 /* Pods-PIA VPN Tunnel.debug.xcconfig */, - AC26C61F8D1F24341AD67A4A /* Pods-PIA VPN Tunnel.release.xcconfig */, - A6454C66DD80FE480E48BFA8 /* Pods-PIA VPN WG Tunnel.debug.xcconfig */, - B5E30A2B8E23D816328C690E /* Pods-PIA VPN WG Tunnel.release.xcconfig */, - 7F6FF659836C1192332662A8 /* Pods-PIA VPN dev.debug.xcconfig */, - EFF4BBB886BD153CAD50F8E2 /* Pods-PIA VPN dev.release.xcconfig */, - 59EC919445EA680B691ACC88 /* Pods-PIA VPNTests.debug.xcconfig */, - 678A887CFC02122F4FB83216 /* Pods-PIA VPNTests.release.xcconfig */, - ); - path = Pods; + 822F97B3251DD53100644EF2 /* WidgetUserDefaultsDatasource.swift */, + AAE8789B28E4679500557F26 /* WidgetPersistenceDatasource.swift */, + ); + path = Cache; + sourceTree = ""; + }; + AAE8789928E45DDE00557F26 /* Model */ = { + isa = PBXGroup; + children = ( + 8269A6FD251CBB36000B4DBF /* WidgetInformation.swift */, + ); + path = Model; + sourceTree = ""; + }; + AAE8789A28E45EC500557F26 /* UI */ = { + isa = PBXGroup; + children = ( + AAE878A228E46D2B00557F26 /* PIAWidgetView.swift */, + AAE878A428E4723B00557F26 /* PIACircleVpnButton.swift */, + AAE878A628E473A400557F26 /* PIAWidgetVpnDetailsView.swift */, + AAE878A828E4765F00557F26 /* PIAIconView.swift */, + AA52C59E28E5ECD400D025AF /* PIAWidgetVpnDetaislRow.swift */, + ); + path = UI; + sourceTree = ""; + }; + AAE8789D28E468F400557F26 /* Widget */ = { + isa = PBXGroup; + children = ( + 8269A6D9251CB5E0000B4DBF /* PIAWidget.swift */, + AAE8789E28E4696300557F26 /* PIAWidgetProvider.swift */, + AAE878A028E46C1600557F26 /* PIAWidgetPreview.swift */, + ); + path = Widget; sourceTree = ""; }; DD1C137E21E6073A004004B3 /* Tiles */ = { @@ -1396,6 +1520,24 @@ path = "PIA VPN WG Tunnel"; sourceTree = ""; }; + E5F52A162A8A5D3800828883 /* Wifi */ = { + isa = PBXGroup; + children = ( + E5F52A172A8A5D5300828883 /* WifiNetworkMonitor.swift */, + ); + name = Wifi; + sourceTree = ""; + }; + E5F52A1D2A8A613400828883 /* Network */ = { + isa = PBXGroup; + children = ( + E5F52A162A8A5D3800828883 /* Wifi */, + E5F52A1A2A8A5E1E00828883 /* IPv4Address.swift */, + E5F52A1E2A8A614900828883 /* NetworkMonitor.swift */, + ); + name = Network; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1403,7 +1545,6 @@ isa = PBXNativeTarget; buildConfigurationList = 0E67FC2E1E3F802D00EF9929 /* Build configuration list for PBXNativeTarget "PIA VPN Tunnel" */; buildPhases = ( - 5FDBE92B797C696CA4F5D6E3 /* [CP] Check Pods Manifest.lock */, 0E67FC1E1E3F802D00EF9929 /* Sources */, 0E67FC1F1E3F802D00EF9929 /* Frameworks */, 0E67FC201E3F802D00EF9929 /* Resources */, @@ -1413,6 +1554,9 @@ dependencies = ( ); name = "PIA VPN Tunnel"; + packageProductDependencies = ( + 69446CB62AA750440080F446 /* PIALibrary */, + ); productName = "PIA OpenVPN"; productReference = 0E67FC221E3F802D00EF9929 /* PIA VPN Tunnel.appex */; productType = "com.apple.product-type.app-extension"; @@ -1421,7 +1565,6 @@ isa = PBXNativeTarget; buildConfigurationList = 0EE220771F4EF307002805AE /* Build configuration list for PBXNativeTarget "PIA VPN dev" */; buildPhases = ( - C26EC034B9AB0A02D7164F82 /* [CP] Check Pods Manifest.lock */, 0EF9ABF71FF792DD005E1418 /* SwiftGen */, 0EE2200F1F4EF307002805AE /* Sources */, 0EE220561F4EF307002805AE /* Frameworks */, @@ -1429,16 +1572,25 @@ 0EE220711F4EF307002805AE /* Download Latest Regions List */, 0EE220731F4EF307002805AE /* Embed App Extensions */, 0E60FF9E1F4F50A2001D30DB /* Embed Frameworks */, - DD58F4B921AE84B300D043F7 /* ShellScript */, - DA22F2CE535A37D07F2DE296 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 0EE2200C1F4EF307002805AE /* PBXTargetDependency */, 0EFB6078203D7A2C0095398C /* PBXTargetDependency */, + AABF827A28AE336000CDAC64 /* PBXTargetDependency */, + AABF827C28AE336F00CDAC64 /* PBXTargetDependency */, ); name = "PIA VPN dev"; + packageProductDependencies = ( + AABF826C28AD185E00CDAC64 /* TweetNacl */, + AABF826E28AD187800CDAC64 /* Popover */, + AABF827028AD188700CDAC64 /* AlamofireImage */, + AABF827228AE2FF500CDAC64 /* GradientProgressBar */, + AABF827428AE2FFE00CDAC64 /* SideMenu */, + AABF827728AE333200CDAC64 /* DZNEmptyDataSet */, + 69446CB42AA7503A0080F446 /* PIALibrary */, + ); productName = "PIA VPN"; productReference = 0EE2207A1F4EF307002805AE /* PIA VPN dev.app */; productType = "com.apple.product-type.application"; @@ -1447,11 +1599,9 @@ isa = PBXNativeTarget; buildConfigurationList = 0EEE1BF01E4F6EF400397DE2 /* Build configuration list for PBXNativeTarget "PIA VPNTests" */; buildPhases = ( - B6830C11847908188577F5A5 /* [CP] Check Pods Manifest.lock */, 0EEE1BE31E4F6EF400397DE2 /* Sources */, 0EEE1BE41E4F6EF400397DE2 /* Frameworks */, 0EEE1BE51E4F6EF400397DE2 /* Resources */, - 6B3D203586AD5EDAB8192C77 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1485,13 +1635,11 @@ isa = PBXNativeTarget; buildConfigurationList = 291C63AE183EBC220039EC03 /* Build configuration list for PBXNativeTarget "PIA VPN" */; buildPhases = ( - 11E8771AD2F21EF4B428154B /* [CP] Check Pods Manifest.lock */, 291C6378183EBC210039EC03 /* Sources */, 291C6379183EBC210039EC03 /* Frameworks */, 291C637A183EBC210039EC03 /* Resources */, 2931563B18513F6500E769A7 /* Download Latest Regions List */, 0E98CF0E1DCBFB3B003F1986 /* Embed App Extensions */, - 7103EE1907DBBDBB001EC846 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1502,10 +1650,37 @@ 8269A6E2251CB5E3000B4DBF /* PBXTargetDependency */, ); name = "PIA VPN"; + packageProductDependencies = ( + AA36CDBA28A6622A00180A33 /* TweetNacl */, + AA36CDC328A6711300180A33 /* Popover */, + AA36CDC828A6733500180A33 /* DZNEmptyDataSet */, + AA36CDCB28A673C900180A33 /* GradientProgressBar */, + AA36CDCE28A6746500180A33 /* SideMenu */, + AA36CDDD28A6878000180A33 /* AlamofireImage */, + 69446CB22AA7502E0080F446 /* PIALibrary */, + ); productName = "PIA VPN"; productReference = 291C637C183EBC210039EC03 /* PIA VPN.app */; productType = "com.apple.product-type.application"; }; + 7EB8D11227CCF4C20030B060 /* PIA VPN UITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7EB8D11D27CCF4C20030B060 /* Build configuration list for PBXNativeTarget "PIA VPN UITests" */; + buildPhases = ( + 7EB8D10F27CCF4C20030B060 /* Sources */, + 7EB8D11027CCF4C20030B060 /* Frameworks */, + 7EB8D11127CCF4C20030B060 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 7EC2973227D8BCB60061C56A /* PBXTargetDependency */, + ); + name = "PIA VPN UITests"; + productName = "PIA VPN UITests"; + productReference = 7EB8D11327CCF4C20030B060 /* PIA VPN UITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 8269A6D4251CB5E0000B4DBF /* PIAWidgetExtension */ = { isa = PBXNativeTarget; buildConfigurationList = 8269A6E4251CB5E3000B4DBF /* Build configuration list for PBXNativeTarget "PIAWidgetExtension" */; @@ -1527,7 +1702,6 @@ isa = PBXNativeTarget; buildConfigurationList = DDC8F5F723EC1070005D19C6 /* Build configuration list for PBXNativeTarget "PIA VPN WG Tunnel" */; buildPhases = ( - 31FE519987479E33FFCDAB88 /* [CP] Check Pods Manifest.lock */, DDC8F5E823EC106F005D19C6 /* Sources */, DDC8F5E923EC106F005D19C6 /* Frameworks */, DDC8F5EA23EC106F005D19C6 /* Resources */, @@ -1537,6 +1711,10 @@ dependencies = ( ); name = "PIA VPN WG Tunnel"; + packageProductDependencies = ( + AA36CDDA28A6860F00180A33 /* TweetNacl */, + 69446CB82AA7504B0080F446 /* PIALibrary */, + ); productName = "PIA VPN WG Tunnel"; productReference = DDC8F5EC23EC106F005D19C6 /* PIA VPN WG Tunnel.appex */; productType = "com.apple.product-type.app-extension"; @@ -1548,7 +1726,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = PIA; - LastSwiftUpdateCheck = 1200; + LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "Private Internet Access Inc."; TargetAttributes = { @@ -1620,6 +1798,12 @@ }; }; }; + 7EB8D11227CCF4C20030B060 = { + CreatedOnToolsVersion = 13.0; + DevelopmentTeam = 5357M5NW9W; + ProvisioningStyle = Automatic; + TestTargetID = 0EE2200B1F4EF307002805AE; + }; 8269A6D4251CB5E0000B4DBF = { CreatedOnToolsVersion = 12.0; DevelopmentTeam = 5357M5NW9W; @@ -1658,6 +1842,15 @@ Base, ); mainGroup = 291C6373183EBC210039EC03; + packageReferences = ( + AA36CDB928A6622A00180A33 /* XCRemoteSwiftPackageReference "tweetnacl-swiftwrap" */, + AA36CDC228A6711300180A33 /* XCRemoteSwiftPackageReference "Popover" */, + AA36CDC728A6733500180A33 /* XCRemoteSwiftPackageReference "DZNEmptyDataSet" */, + AA36CDCA28A673C900180A33 /* XCRemoteSwiftPackageReference "GradientProgressBar" */, + AA36CDCD28A6746500180A33 /* XCRemoteSwiftPackageReference "SideMenu" */, + AA36CDDC28A6878000180A33 /* XCRemoteSwiftPackageReference "AlamofireImage" */, + 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */, + ); productRefGroup = 291C637D183EBC210039EC03 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -1669,6 +1862,7 @@ DDC8F5EB23EC106F005D19C6 /* PIA VPN WG Tunnel */, 8269A6D4251CB5E0000B4DBF /* PIAWidgetExtension */, 0EEE1BE61E4F6EF400397DE2 /* PIA VPNTests */, + 7EB8D11227CCF4C20030B060 /* PIA VPN UITests */, ); }; /* End PBXProject section */ @@ -1694,7 +1888,6 @@ 0E0715E7201CBB7100D6F666 /* Flags-dev.plist in Resources */, DD1C138A21E60C63004004B3 /* IPTile.xib in Resources */, 82C9F3CF25C43863005039F9 /* ActiveDedicatedIpHeaderViewCell.xib in Resources */, - DD58F4B821AD579A00D043F7 /* GoogleService-Info.plist in Resources */, 82CAB8F5255C0CD100BB08EF /* MessagesTileCollectionViewCell.xib in Resources */, DD76291421ECBD8B0092DF50 /* SubscriptionTileCollectionViewCell.xib in Resources */, DD51F8BA2372E494009FEED3 /* PIA-RSA-4096.pem in Resources */, @@ -1794,6 +1987,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7EB8D11127CCF4C20030B060 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7EC2973027D8B8580061C56A /* Credentials.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8269A6D3251CB5E0000B4DBF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1840,28 +2041,6 @@ shellPath = /bin/sh; shellScript = "if which swiftgen >/dev/null; then\n set -e\n swiftgen\nelse\n echo \"warning: SwiftGen not installed, download it from https://github.com/SwiftGen/SwiftGen\"\nfi\n"; }; - 11E8771AD2F21EF4B428154B /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PIA VPN-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 2931563B18513F6500E769A7 /* Download Latest Regions List */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 12; @@ -1876,157 +2055,6 @@ shellPath = /bin/bash; shellScript = "# update max once an hour\nset -e\n\nREGIONS_FILE=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Regions.json\"\n\nif [[ ! -r \"${REGIONS_FILE}\" || $(find \"${REGIONS_FILE}\" -mmin +60) ]]; then\necho \"downloading regions list to ${REGIONS_FILE}\"\ncurl -sfo \"/tmp/piaios.tmp\" \"https://serverlist.piaservers.net/vpninfo/servers/v6\"\n\nif [ $? -ne 0 ]; then\necho \"failed to fetch regions list from server\"\nexit 1\nfi\n\nhead -n 1 \"/tmp/piaios.tmp\" > \"${REGIONS_FILE}\"\nrm \"/tmp/piaios.tmp\"\nfi\n"; }; - 31FE519987479E33FFCDAB88 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PIA VPN WG Tunnel-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 5FDBE92B797C696CA4F5D6E3 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PIA VPN Tunnel-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 6B3D203586AD5EDAB8192C77 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PIA VPNTests/Pods-PIA VPNTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 7103EE1907DBBDBB001EC846 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PIA VPN/Pods-PIA VPN-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - B6830C11847908188577F5A5 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PIA VPNTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C26EC034B9AB0A02D7164F82 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PIA VPN dev-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - DA22F2CE535A37D07F2DE296 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PIA VPN dev/Pods-PIA VPN dev-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - DD58F4B921AE84B300D043F7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${PODS_ROOT}/Fabric/run\" 970f0999b1ac221604548824b2a49d005754ca32 5606a7ca9a2b622029ba4b67c2adf2e419d60e5460b92564806db96e3141bb72\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -2047,6 +2075,7 @@ 8272C6342657EE4E00D846A8 /* AutomationSettingsViewController.swift in Sources */, 3524670F26B4332E00E3F0AC /* DashboardViewController+ServerSelection.swift in Sources */, 0E9452A01FDB547D00891948 /* ExpirationCell.swift in Sources */, + E5F52A202A8A614900828883 /* NetworkMonitor.swift in Sources */, 0EFDC1F01FE4B9E6007C0B9B /* AppConfiguration.swift in Sources */, DD76292321ECCD5C0092DF50 /* UsageTile.swift in Sources */, DDD65314247E66AD00F0A897 /* CoordinatesFinder.swift in Sources */, @@ -2067,6 +2096,7 @@ 0E1F318720176A6300FC1000 /* Theme+DarkPalette.swift in Sources */, 8272C6312657D42700D846A8 /* PrivacyFeaturesSettingsViewController.swift in Sources */, 0ECC1E3F1FDB3F2F0039891D /* RegionsViewController.swift in Sources */, + E5F52A192A8A5D5300828883 /* WifiNetworkMonitor.swift in Sources */, 8272C62B26551F9B00D846A8 /* SettingPopoverSelectionView.swift in Sources */, 0EB9ED1B1FDA1C4D00D1214D /* SettingsViewController.swift in Sources */, 0EB966751FDF0D6E0086ABC2 /* ServerProvider+UI.swift in Sources */, @@ -2138,6 +2168,7 @@ 829EB51225359DBB003E74DD /* DedicatedIpRowViewCell.swift in Sources */, DDFCFA9521E892130081F235 /* RegionTile.swift in Sources */, 0EFDC1E71FE4ABAA007C0B9B /* Notification+App.swift in Sources */, + E5F52A1C2A8A5E1E00828883 /* IPv4Address.swift in Sources */, 827570E224DAC2C6008F9800 /* CustomNetworkCollectionViewCell.swift in Sources */, DD918577246BFA7F006B3A2B /* RatingManager.swift in Sources */, 82C0071425231F2800F21AF2 /* String+VPNType.swift in Sources */, @@ -2148,6 +2179,7 @@ 0EFDC1E11FE4A450007C0B9B /* AppPreferences.swift in Sources */, 0E441E272055AEDF007528D5 /* ThemeStrategy+App.swift in Sources */, DDC8125021761B0B00CB290C /* SwiftGen+ScenesStoryboards.swift in Sources */, + 7ECBB8DE27BA5FCE00C0C774 /* UserSurveyManager.swift in Sources */, 0E492C681FE60907007F23DF /* Flags.swift in Sources */, DD172A972254C35000071CFB /* FavoriteServersTile.swift in Sources */, 0EE14D191FF15812008D9AC2 /* ModalNavigationSegue.swift in Sources */, @@ -2234,6 +2266,7 @@ 82A1AD2F24B87CF20003DD02 /* PIACard.swift in Sources */, 8272C6392657F54400D846A8 /* DevelopmentSettingsViewController.swift in Sources */, 0EFB839020209CF200980F69 /* VPNPermissionViewController.swift in Sources */, + 7ECBB8DD27B6C4F500C0C774 /* UserSurveyManager.swift in Sources */, 821674F12678A1BE0028E4FD /* SettingsDelegate.swift in Sources */, 0EFDC1D71FE46177007C0B9B /* SensitiveOperation.swift in Sources */, 82C0071325231F2800F21AF2 /* String+VPNType.swift in Sources */, @@ -2256,6 +2289,7 @@ DD9329A2237AFD0A0025B6BC /* ShowQuickSettingsViewController.swift in Sources */, 0E2215C920084CD700F5FB4D /* SwiftGen+Strings.swift in Sources */, 827570D824DAB6E4008F9800 /* NetworkFooterCollectionViewCell.swift in Sources */, + E5F52A1F2A8A614900828883 /* NetworkMonitor.swift in Sources */, DD3B504324B7576F0002F4B5 /* Card.swift in Sources */, 82CAB8F2255C0CD100BB08EF /* MessagesTileCollectionViewCell.swift in Sources */, DD606ABC21C904BB00E0781D /* PIAHotspotHelper.swift in Sources */, @@ -2270,6 +2304,7 @@ 0EA660081FEC7A9500CB2B0D /* PIATunnelProvider+UI.swift in Sources */, 0E7361EB1FD99A1000706BFF /* MenuViewController.swift in Sources */, DD1C139921E65F90004004B3 /* IPTileCollectionViewCell.swift in Sources */, + E5F52A182A8A5D5300828883 /* WifiNetworkMonitor.swift in Sources */, 827570CF24DAA128008F9800 /* NetworkCollectionViewCell.swift in Sources */, 0EB966771FDF11B80086ABC2 /* Server+UI.swift in Sources */, DD052AC32419003300AD3A24 /* ProtocolTableViewCell.swift in Sources */, @@ -2313,20 +2348,40 @@ DD4E84572243BD1200929B39 /* DashboardCollectionViewUtil.swift in Sources */, 829EB507253598BD003E74DD /* DedicatedIpViewController.swift in Sources */, DD74695A217F07AC00B7BD73 /* DNSList.swift in Sources */, + E5F52A1B2A8A5E1E00828883 /* IPv4Address.swift in Sources */, 0E9452AE1FDB5F7A00891948 /* PIAPageControl.swift in Sources */, DDD271F021D6718F00B6D20F /* RegionFilter.swift in Sources */, 82CAB87A255AEA3500BB08EF /* MessagesManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 7EB8D10F27CCF4C20030B060 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7EC2972F27D8B8580061C56A /* CredentialsUtil.swift in Sources */, + 7EB8D12327CE7D4C0030B060 /* PIALoginTests.swift in Sources */, + 7EB8D11F27CE2B020030B060 /* PIAUITests.swift in Sources */, + 7EB8D12127CE2B5D0030B060 /* PIALaunchTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8269A6D1251CB5E0000B4DBF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 8269A6FE251CBB36000B4DBF /* WidgetContent.swift in Sources */, - 822F97B4251DD53100644EF2 /* WidgetUtils.swift in Sources */, + 8269A6FE251CBB36000B4DBF /* WidgetInformation.swift in Sources */, + 822F97B4251DD53100644EF2 /* WidgetUserDefaultsDatasource.swift in Sources */, 8269A6DA251CB5E0000B4DBF /* PIAWidget.swift in Sources */, + AAE878A928E4765F00557F26 /* PIAIconView.swift in Sources */, + AAE8789F28E4696300557F26 /* PIAWidgetProvider.swift in Sources */, + AAE878A128E46C1600557F26 /* PIAWidgetPreview.swift in Sources */, + AAE878A528E4723B00557F26 /* PIACircleVpnButton.swift in Sources */, 82BAACFB25B09C9200B3C733 /* PIAWidget.intentdefinition in Sources */, + AAE8789C28E4679500557F26 /* WidgetPersistenceDatasource.swift in Sources */, + AAE878A328E46D2B00557F26 /* PIAWidgetView.swift in Sources */, + AAE878A728E473A400557F26 /* PIAWidgetVpnDetailsView.swift in Sources */, + AA52C59F28E5ECD400D025AF /* PIAWidgetVpnDetaislRow.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2371,11 +2426,26 @@ target = 0EFB606F203D7A2C0095398C /* PIA VPN AdBlocker */; targetProxy = 0EFB607E203D893E0095398C /* PBXContainerItemProxy */; }; + 7EC2973227D8BCB60061C56A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0EE2200B1F4EF307002805AE /* PIA VPN dev */; + targetProxy = 7EC2973127D8BCB60061C56A /* PBXContainerItemProxy */; + }; 8269A6E2251CB5E3000B4DBF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8269A6D4251CB5E0000B4DBF /* PIAWidgetExtension */; targetProxy = 8269A6E1251CB5E3000B4DBF /* PBXContainerItemProxy */; }; + AABF827A28AE336000CDAC64 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DDC8F5EB23EC106F005D19C6 /* PIA VPN WG Tunnel */; + targetProxy = AABF827928AE336000CDAC64 /* PBXContainerItemProxy */; + }; + AABF827C28AE336F00CDAC64 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8269A6D4251CB5E0000B4DBF /* PIAWidgetExtension */; + targetProxy = AABF827B28AE336F00CDAC64 /* PBXContainerItemProxy */; + }; DDC8F5F323EC1070005D19C6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DDC8F5EB23EC106F005D19C6 /* PIA VPN WG Tunnel */; @@ -2455,7 +2525,6 @@ /* Begin XCBuildConfiguration section */ 0E67FC2C1E3F802D00EF9929 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E65358880CC5204F785ADE10 /* Pods-PIA VPN Tunnel.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_MODULES = YES; @@ -2463,14 +2532,18 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN Tunnel/PIA VPN Tunnel.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN Tunnel/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 3.14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.23.0; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.Tunnel"; PRODUCT_NAME = "PIA VPN Tunnel"; @@ -2485,7 +2558,6 @@ }; 0E67FC2D1E3F802D00EF9929 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AC26C61F8D1F24341AD67A4A /* Pods-PIA VPN Tunnel.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_MODULES = YES; @@ -2494,14 +2566,18 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN Tunnel/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 3.14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.23.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.Tunnel"; PRODUCT_NAME = "PIA VPN Tunnel"; @@ -2514,7 +2590,6 @@ }; 0EE220781F4EF307002805AE /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7F6FF659836C1192332662A8 /* Pods-PIA VPN dev.debug.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; ARCHS = "$(ARCHS_STANDARD)"; @@ -2523,9 +2598,9 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN/PIA VPN.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEVELOPMENT_TEAM = 5357M5NW9W; - ENABLE_BITCODE = YES; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -2535,12 +2610,15 @@ ); INFOPLIST_FILE = "PIA VPN/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", ); - MARKETING_VERSION = 3.14.0; + MARKETING_VERSION = 3.23.0; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN"; PRODUCT_MODULE_NAME = PIA_VPN_dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2556,7 +2634,6 @@ }; 0EE220791F4EF307002805AE /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EFF4BBB886BD153CAD50F8E2 /* Pods-PIA VPN dev.release.xcconfig */; buildSettings = { APPLICATION_EXTENSION_API_ONLY = NO; ARCHS = "$(ARCHS_STANDARD)"; @@ -2565,9 +2642,9 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN/PIA VPN.entitlements"; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEVELOPMENT_TEAM = 5357M5NW9W; - ENABLE_BITCODE = YES; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -2577,12 +2654,15 @@ ); INFOPLIST_FILE = "PIA VPN/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", ); - MARKETING_VERSION = 3.14.0; + MARKETING_VERSION = 3.23.0; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN"; PRODUCT_MODULE_NAME = PIA_VPN_dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2597,7 +2677,6 @@ }; 0EEE1BEE1E4F6EF400397DE2 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 59EC919445EA680B691ACC88 /* Pods-PIA VPNTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -2612,7 +2691,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPNTests/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPNTests"; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; @@ -2629,7 +2712,6 @@ }; 0EEE1BEF1E4F6EF400397DE2 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 678A887CFC02122F4FB83216 /* Pods-PIA VPNTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; @@ -2645,7 +2727,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPNTests/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPNTests"; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; @@ -2668,15 +2754,19 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN AdBlocker/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 3.14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.23.0; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.AdBlocker"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2701,15 +2791,19 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN AdBlocker/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 3.14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.23.0; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.AdBlocker"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2823,7 +2917,8 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; @@ -2831,7 +2926,6 @@ }; 291C63AF183EBC220039EC03 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F489C6FC39A7285DDE2A26F7 /* Pods-PIA VPN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ARCHS = "$(ARCHS_STANDARD)"; @@ -2840,9 +2934,9 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN/PIA VPN.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEVELOPMENT_TEAM = 5357M5NW9W; - ENABLE_BITCODE = YES; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_ROOT}/PIAAccount/frameworks/iPhone\"", @@ -2850,12 +2944,15 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; INFOPLIST_FILE = "PIA VPN/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", ); - MARKETING_VERSION = 3.14.0; + MARKETING_VERSION = 3.23.0; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development com.privateinternetaccess.ios.PIA-VPN"; @@ -2868,7 +2965,6 @@ }; 291C63B0183EBC220039EC03 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0BE4A5FC9BACD5CAB3D6CEF0 /* Pods-PIA VPN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ARCHS = "$(ARCHS_STANDARD)"; @@ -2877,9 +2973,9 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN/PIA VPN.entitlements"; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEVELOPMENT_TEAM = 5357M5NW9W; - ENABLE_BITCODE = YES; + ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"${PODS_ROOT}/PIAAccount/frameworks/iPhone\"", @@ -2887,12 +2983,15 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; INFOPLIST_FILE = "PIA VPN/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)", ); - MARKETING_VERSION = 3.14.0; + MARKETING_VERSION = 3.23.0; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.privateinternetaccess.ios.PIA-VPN"; @@ -2902,6 +3001,81 @@ }; name = Release; }; + 7EB8D11B27CCF4C20030B060 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 5357M5NW9W; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.cyberghostsrl.PIA-VPN-UITests"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "PIA VPN dev"; + }; + name = Debug; + }; + 7EB8D11C27CCF4C20030B060 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 5357M5NW9W; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.cyberghostsrl.PIA-VPN-UITests"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "PIA VPN dev"; + }; + name = Release; + }; 8269A6E5251CB5E3000B4DBF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2917,15 +3091,19 @@ CODE_SIGN_ENTITLEMENTS = PIAWidgetExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = PIAWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 3.14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.23.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.PIAWidget"; @@ -2955,15 +3133,19 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = PIAWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 3.14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.23.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.PIAWidget"; @@ -2977,7 +3159,6 @@ }; DDC8F5F523EC1070005D19C6 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A6454C66DD80FE480E48BFA8 /* Pods-PIA VPN WG Tunnel.debug.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2988,15 +3169,19 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN WG Tunnel/PIA_VPN_WG_Tunnel.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN WG Tunnel/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 3.14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.23.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.WG-Tunnel"; @@ -3012,7 +3197,6 @@ }; DDC8F5F623EC1070005D19C6 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B5E30A2B8E23D816328C690E /* Pods-PIA VPN WG Tunnel.release.xcconfig */; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -3024,15 +3208,19 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 19748; + CURRENT_PROJECT_VERSION = 20034; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN WG Tunnel/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 3.14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 3.23.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.WG-Tunnel"; @@ -3101,6 +3289,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 7EB8D11D27CCF4C20030B060 /* Build configuration list for PBXNativeTarget "PIA VPN UITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7EB8D11B27CCF4C20030B060 /* Debug */, + 7EB8D11C27CCF4C20030B060 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 8269A6E4251CB5E3000B4DBF /* Build configuration list for PBXNativeTarget "PIAWidgetExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -3120,6 +3317,153 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "git@github.com:pia-foss/client-library-apple.git"; + requirement = { + branch = master; + kind = branch; + }; + }; + AA36CDB928A6622A00180A33 /* XCRemoteSwiftPackageReference "tweetnacl-swiftwrap" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/bitmark-inc/tweetnacl-swiftwrap.git"; + requirement = { + kind = exactVersion; + version = 1.1.0; + }; + }; + AA36CDC228A6711300180A33 /* XCRemoteSwiftPackageReference "Popover" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sarathsarah/Popover.git"; + requirement = { + branch = master; + kind = branch; + }; + }; + AA36CDC728A6733500180A33 /* XCRemoteSwiftPackageReference "DZNEmptyDataSet" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/dzenbot/DZNEmptyDataSet.git"; + requirement = { + branch = master; + kind = branch; + }; + }; + AA36CDCA28A673C900180A33 /* XCRemoteSwiftPackageReference "GradientProgressBar" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/fxm90/GradientProgressBar.git"; + requirement = { + kind = exactVersion; + version = 2.0.3; + }; + }; + AA36CDCD28A6746500180A33 /* XCRemoteSwiftPackageReference "SideMenu" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/jonkykong/SideMenu.git"; + requirement = { + kind = exactVersion; + version = 6.5.0; + }; + }; + AA36CDDC28A6878000180A33 /* XCRemoteSwiftPackageReference "AlamofireImage" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/AlamofireImage.git"; + requirement = { + kind = exactVersion; + version = 4.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 69446CB22AA7502E0080F446 /* PIALibrary */ = { + isa = XCSwiftPackageProductDependency; + package = 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */; + productName = PIALibrary; + }; + 69446CB42AA7503A0080F446 /* PIALibrary */ = { + isa = XCSwiftPackageProductDependency; + package = 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */; + productName = PIALibrary; + }; + 69446CB62AA750440080F446 /* PIALibrary */ = { + isa = XCSwiftPackageProductDependency; + package = 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */; + productName = PIALibrary; + }; + 69446CB82AA7504B0080F446 /* PIALibrary */ = { + isa = XCSwiftPackageProductDependency; + package = 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */; + productName = PIALibrary; + }; + AA36CDBA28A6622A00180A33 /* TweetNacl */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDB928A6622A00180A33 /* XCRemoteSwiftPackageReference "tweetnacl-swiftwrap" */; + productName = TweetNacl; + }; + AA36CDC328A6711300180A33 /* Popover */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDC228A6711300180A33 /* XCRemoteSwiftPackageReference "Popover" */; + productName = Popover; + }; + AA36CDC828A6733500180A33 /* DZNEmptyDataSet */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDC728A6733500180A33 /* XCRemoteSwiftPackageReference "DZNEmptyDataSet" */; + productName = DZNEmptyDataSet; + }; + AA36CDCB28A673C900180A33 /* GradientProgressBar */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDCA28A673C900180A33 /* XCRemoteSwiftPackageReference "GradientProgressBar" */; + productName = GradientProgressBar; + }; + AA36CDCE28A6746500180A33 /* SideMenu */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDCD28A6746500180A33 /* XCRemoteSwiftPackageReference "SideMenu" */; + productName = SideMenu; + }; + AA36CDDA28A6860F00180A33 /* TweetNacl */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDB928A6622A00180A33 /* XCRemoteSwiftPackageReference "tweetnacl-swiftwrap" */; + productName = TweetNacl; + }; + AA36CDDD28A6878000180A33 /* AlamofireImage */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDDC28A6878000180A33 /* XCRemoteSwiftPackageReference "AlamofireImage" */; + productName = AlamofireImage; + }; + AABF826C28AD185E00CDAC64 /* TweetNacl */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDB928A6622A00180A33 /* XCRemoteSwiftPackageReference "tweetnacl-swiftwrap" */; + productName = TweetNacl; + }; + AABF826E28AD187800CDAC64 /* Popover */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDC228A6711300180A33 /* XCRemoteSwiftPackageReference "Popover" */; + productName = Popover; + }; + AABF827028AD188700CDAC64 /* AlamofireImage */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDDC28A6878000180A33 /* XCRemoteSwiftPackageReference "AlamofireImage" */; + productName = AlamofireImage; + }; + AABF827228AE2FF500CDAC64 /* GradientProgressBar */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDCA28A673C900180A33 /* XCRemoteSwiftPackageReference "GradientProgressBar" */; + productName = GradientProgressBar; + }; + AABF827428AE2FFE00CDAC64 /* SideMenu */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDCD28A6746500180A33 /* XCRemoteSwiftPackageReference "SideMenu" */; + productName = SideMenu; + }; + AABF827728AE333200CDAC64 /* DZNEmptyDataSet */ = { + isa = XCSwiftPackageProductDependency; + package = AA36CDC728A6733500180A33 /* XCRemoteSwiftPackageReference "DZNEmptyDataSet" */; + productName = DZNEmptyDataSet; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 291C6374183EBC210039EC03 /* Project object */; } diff --git a/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN WG Tunnel.xcscheme b/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN WG Tunnel.xcscheme index 117e38cf1..abcf43837 100644 --- a/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN WG Tunnel.xcscheme +++ b/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN WG Tunnel.xcscheme @@ -43,6 +43,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + diff --git a/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN dev.xcscheme b/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN dev.xcscheme index 8baec6c39..a41ce8df3 100644 --- a/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN dev.xcscheme +++ b/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN dev.xcscheme @@ -75,6 +75,16 @@ ReferencedContainer = "container:PIA VPN.xcodeproj"> + + + + + + + + - - - - - - diff --git a/PIA VPN.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PIA VPN.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/PIA VPN.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/PIA VPN/AccountObserver.swift b/PIA VPN/AccountObserver.swift index 3a6d3ee0d..2588e36ef 100644 --- a/PIA VPN/AccountObserver.swift +++ b/PIA VPN/AccountObserver.swift @@ -23,6 +23,7 @@ import Foundation import PIALibrary import SwiftyBeaver +import UIKit private let log = SwiftyBeaver.self diff --git a/PIA VPN/AppConfiguration.swift b/PIA VPN/AppConfiguration.swift index 3e6ce758a..ce0f3cc10 100644 --- a/PIA VPN/AppConfiguration.swift +++ b/PIA VPN/AppConfiguration.swift @@ -22,7 +22,9 @@ import Foundation import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN +import UIKit struct AppConfiguration { private static let customClientEnvironment: Client.Environment = .staging @@ -65,7 +67,7 @@ struct AppConfiguration { static let profileName = "Private Internet Access" - static let piaDefaultConfigurationBuilder: OpenVPNTunnelProvider.ConfigurationBuilder = { + static let piaDefaultConfigurationBuilder: OpenVPNProvider.ConfigurationBuilder = { var sessionBuilder = OpenVPN.ConfigurationBuilder() sessionBuilder.renegotiatesAfter = piaRenegotiationInterval sessionBuilder.cipher = .aes128gcm @@ -76,7 +78,7 @@ struct AppConfiguration { sessionBuilder.endpointProtocols = piaAutomaticProtocols sessionBuilder.dnsServers = [] sessionBuilder.usesPIAPatches = true - var builder = OpenVPNTunnelProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) + var builder = OpenVPNProvider.ConfigurationBuilder(sessionConfiguration: sessionBuilder.build()) if AppPreferences.shared.useSmallPackets { builder.sessionConfiguration.mtu = AppConstants.OpenVPNPacketSize.smallPacketSize } else { @@ -123,7 +125,7 @@ struct AppConfiguration { } struct Rating { - + static let successDisconnectionsUntilPrompt: Int = 2 static let successConnectionsUntilPrompt: Int = 3 static let successConnectionsUntilPromptAgain: Int = 50 static let errorInConnectionsUntilPrompt: Int = 1 diff --git a/PIA VPN/AppConstants.swift b/PIA VPN/AppConstants.swift index 3432a5407..bd3dd4da1 100644 --- a/PIA VPN/AppConstants.swift +++ b/PIA VPN/AppConstants.swift @@ -89,6 +89,8 @@ struct AppConstants { static let csEmail = "helpdesk+vpnpermissions.ios@privateinternetaccess.com" static let ovpnMigrationURL = URL(string: "https://www.privateinternetaccess.com/helpdesk/kb/articles/removing-openvpn-handshake-and-authentication-settings")! + + static let leakProtectionURL = URL(string: "\(Self.supportURL.absoluteString)/kb/articles/what-is-pia-s-leak-protection-feature-on-ios")! static var stagingEndpointURL: URL? = { guard let path = Bundle.main.path(forResource: "staging", ofType: "endpoint") else { @@ -191,5 +193,9 @@ struct AppConstants { static let connect = "piavpn:connect" static let view = "piavpn:view" } - + + struct Survey { + static let numberOfConnectionsUntilPrompt = 15 + static let formURL = URL(string: "https://privateinternetaccess.typeform.com/to/WTFcN77r")! + } } diff --git a/PIA VPN/AppDelegate.swift b/PIA VPN/AppDelegate.swift index 1f0145230..350977a1a 100644 --- a/PIA VPN/AppDelegate.swift +++ b/PIA VPN/AppDelegate.swift @@ -198,6 +198,8 @@ class AppDelegate: NSObject, UIApplicationDelegate { func applicationDidBecomeActive(_ application: UIApplication) { application.applicationIconBadgeNumber = 0 + // Remove the Non compliant Wifi local notification as the app is in foreground now + Macros.removeLocalNotification(NotificationCategory.nonCompliantWifi) } private func refreshShortcutItems(in application: UIApplication) { @@ -255,6 +257,8 @@ class AppDelegate: NSObject, UIApplicationDelegate { case .disconnect: if Client.providers.vpnProvider.isVPNConnected { + // Dismiss the Leak Protection alert if present when disconnecting from a Quick Action + dismissLeakProtectionAlert() // 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 @@ -312,4 +316,22 @@ extension AppDelegate { return UIApplication.shared.delegate as! AppDelegate } + class func getRootViewController() -> UIViewController? { + return AppDelegate.delegate().topViewControllerWithRootViewController(rootViewController: UIApplication.shared.keyWindow?.rootViewController) + } + +} + + +extension AppDelegate { + + private func dismissLeakProtectionAlert() { + if let presentedAlert = window?.rootViewController?.presentedViewController as? UIAlertController { + let leakProtectionAlertTitle = L10n.Dashboard.Vpn.Leakprotection.Alert.title + + if presentedAlert.title == leakProtectionAlertTitle { + presentedAlert.dismiss(animated: true) + } + } + } } diff --git a/PIA VPN/AppPreferences.swift b/PIA VPN/AppPreferences.swift index 9388046b4..faa821d9d 100644 --- a/PIA VPN/AppPreferences.swift +++ b/PIA VPN/AppPreferences.swift @@ -22,9 +22,11 @@ import Foundation import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN import SwiftyBeaver import Intents +import UIKit private let log = SwiftyBeaver.self @@ -51,7 +53,8 @@ class AppPreferences { static let useSmallPackets = "UseSmallPackets" static let wireGuardUseSmallPackets = "WireGuardUseSmallPackets" static let ikeV2UseSmallPackets = "IKEV2UseSmallPackets" - + static let usesCustomDNS = "usesCustomDNS" + static let favoriteServerIdentifiersGen4_deprecated = "FavoriteServerIdentifiersGen4" static let regionFilter = "RegionFilter" @@ -80,6 +83,7 @@ class AppPreferences { static let failureConnections = "failureConnections" static let canAskAgainForReview = "canAskAgainForReview" static let lastRatingRejection = "lastRatingRejection" + static let successDisconnections = "successDisconnections" // GEO servers static let showGeoServers = "ShowGeoServers" @@ -91,18 +95,24 @@ class AppPreferences { static let tokenIPRelation_deprecated = "TokenIPRelation" // In app messages - static let stopInAppMessages = "stopInAppMessages" + static let showServiceMessages = "showServiceMessages" // Features static let showsDedicatedIPView = "showsDedicatedIPView" static let disablesMultiDipTokens = "disablesMultiDipTokens" static let checksDipExpirationRequest = "checksDipExpirationRequest" static let showNewInitialScreen = "showNewInitialScreen" + static let showLeakProtection = "showLeakProtection" + static let showLeakProtectionNotifications = "showLeakProtectionNotifications" + + // Survey + static let userInteractedWithSurvey = "userInteractedWithSurvey" + static let successConnectionsUntilSurvey = "successConnectionsUntilSurvey" // Dev static let appEnvironmentIsProduction = "AppEnvironmentIsProduction" static let stagingVersion = "StagingVersion" - + } static let shared = AppPreferences() @@ -308,6 +318,15 @@ class AppPreferences { } } + var usesCustomDNS: Bool { + get { + return defaults.bool(forKey: Entries.usesCustomDNS) + } + set { + defaults.set(newValue, forKey: Entries.usesCustomDNS) + } + } + var dedicatedTokenIPReleation: [String: String] { get { let keychain = PIALibrary.Keychain(team: AppConstants.teamId, group: AppConstants.appGroup) @@ -438,6 +457,15 @@ class AppPreferences { } } + var successDisconnections: Int { + get { + return defaults.integer(forKey: Entries.successDisconnections) + } + set { + defaults.set(newValue, forKey: Entries.successDisconnections) + } + } + var appVersion: String? { get { return defaults.string(forKey: Entries.appVersion) @@ -456,12 +484,12 @@ class AppPreferences { } } - var stopInAppMessages: Bool { + var showServiceMessages: Bool { get { - return defaults.bool(forKey: Entries.stopInAppMessages) + return defaults.bool(forKey: Entries.showServiceMessages) } set { - defaults.set(newValue, forKey: Entries.stopInAppMessages) + defaults.set(newValue, forKey: Entries.showServiceMessages) } } @@ -492,6 +520,24 @@ class AppPreferences { } } + var showLeakProtection: Bool { + get { + return defaults.bool(forKey: Entries.showLeakProtection) + } + set { + defaults.set(newValue, forKey: Entries.showLeakProtection) + } + } + + var showLeakProtectionNotifications: Bool { + get { + return defaults.bool(forKey: Entries.showLeakProtectionNotifications) + } + set { + defaults.set(newValue, forKey: Entries.showLeakProtectionNotifications) + } + } + var checksDipExpirationRequest: Bool { get { return defaults.bool(forKey: Entries.checksDipExpirationRequest) @@ -518,7 +564,25 @@ class AppPreferences { defaults.set(newValue, forKey: Entries.stagingVersion) } } - + + var userInteractedWithSurvey: Bool { + get { + return defaults.bool(forKey: Entries.userInteractedWithSurvey) + } + set { + defaults.set(newValue, forKey: Entries.userInteractedWithSurvey) + } + } + + var successConnectionsUntilSurvey: Int? { + get { + return defaults.value(forKey: Entries.successConnectionsUntilSurvey) as? Int + } + set { + defaults.set(newValue, forKey: Entries.successConnectionsUntilSurvey) + } + } + private init() { guard let defaults = UserDefaults(suiteName: AppConstants.appGroup) else { fatalError("Unable to initialize app preferences") @@ -546,15 +610,18 @@ class AppPreferences { Entries.useSmallPackets: false, Entries.wireGuardUseSmallPackets: true, Entries.ikeV2UseSmallPackets: true, + Entries.usesCustomDNS: false, Entries.canAskAgainForReview: false, + Entries.successDisconnections: 0, Entries.successConnections: 0, Entries.failureConnections: 0, Entries.showGeoServers: true, Entries.dismissedMessages: [], - Entries.stopInAppMessages: false, + Entries.showServiceMessages: false, Entries.showsDedicatedIPView: true, Entries.disablesMultiDipTokens: true, Entries.checksDipExpirationRequest: true, + Entries.userInteractedWithSurvey: false, Entries.stagingVersion: 0, Entries.appEnvironmentIsProduction: Client.environment == .production ? true : false, ]) @@ -590,8 +657,8 @@ class AppPreferences { func migrateOVPN() { - guard let currentOpenVPNConfiguration = Client.preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration ?? - Client.preferences.defaults.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration else { + guard let currentOpenVPNConfiguration = Client.preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration ?? + Client.preferences.defaults.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration else { return } @@ -613,7 +680,7 @@ class AppPreferences { } if shouldUpdate { - var builder = OpenVPNTunnelProvider.ConfigurationBuilder(sessionConfiguration: pendingOpenVPNConfiguration.build()) + var builder = OpenVPNProvider.ConfigurationBuilder(sessionConfiguration: pendingOpenVPNConfiguration.build()) if AppPreferences.shared.useSmallPackets { builder.sessionConfiguration.mtu = AppConstants.OpenVPNPacketSize.smallPacketSize } else { @@ -771,18 +838,24 @@ class AppPreferences { quickSettingPrivateBrowserVisible = true useSmallPackets = false ikeV2UseSmallPackets = true + usesCustomDNS = false wireGuardUseSmallPackets = true todayWidgetVpnProtocol = IKEv2Profile.vpnType todayWidgetVpnPort = "500" todayWidgetVpnSocket = "UDP" todayWidgetTrustedNetwork = false Client.resetServers(completionBlock: {_ in }) + successDisconnections = 0 + successConnections = 0 failureConnections = 0 showGeoServers = true - stopInAppMessages = false + showServiceMessages = false dedicatedTokenIPReleation = [:] appEnvironmentIsProduction = Client.environment == .production ? true : false MessagesManager.shared.reset() + userInteractedWithSurvey = false + successConnectionsUntilSurvey = nil + Client.preferences.lastKnownException = nil } func clean() { @@ -805,17 +878,23 @@ class AppPreferences { quickSettingPrivateBrowserVisible = true useSmallPackets = false ikeV2UseSmallPackets = true + usesCustomDNS = false wireGuardUseSmallPackets = true let preferences = Client.preferences.editable().reset() preferences.commit() Client.resetServers(completionBlock: {_ in }) + successDisconnections = 0 + successConnections = 0 failureConnections = 0 showGeoServers = true - stopInAppMessages = false + showServiceMessages = false dismissedMessages = [] dedicatedTokenIPReleation = [:] MessagesManager.shared.reset() appEnvironmentIsProduction = Client.environment == .production ? true : false + userInteractedWithSurvey = false + successConnectionsUntilSurvey = nil + Client.preferences.lastKnownException = nil } // + (void)eraseForTesting; @@ -858,4 +937,13 @@ class AppPreferences { } } } + + // MARK: Connections + func incrementSuccessConnections() { + successConnections += 1 + } + + func incrementSuccessDisconnections() { + successDisconnections += 1 + } } diff --git a/PIA VPN/Bootstrapper.swift b/PIA VPN/Bootstrapper.swift index a1243066a..0f5f4581a 100644 --- a/PIA VPN/Bootstrapper.swift +++ b/PIA VPN/Bootstrapper.swift @@ -22,14 +22,10 @@ import Foundation import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN import SwiftyBeaver import PIAWireguard -#if PIA_DEV -import Firebase -import Fabric -import Crashlytics -#endif class Bootstrapper { @@ -45,26 +41,37 @@ class Bootstrapper { return false #endif } + + var isDevelopmentBuild: Bool { + #if PIA_DEV + return true + #else + return false + #endif + } + + /// Update the values of the flags from the CSI server + private func updateFeatureFlagsForReleaseIfNeeded() { + // Some feature flags like Leak Protection are controled from the Developer menu on Dev builds. + // So we skip updating the flag from the server on dev builds + guard !isDevelopmentBuild else { return } + + // Leak Protection feature flags + AppPreferences.shared.showLeakProtection = Client.configuration.featureFlags.contains(Client.FeatureFlags.showLeakProtection) + AppPreferences.shared.showLeakProtectionNotifications = Client.configuration.featureFlags.contains(Client.FeatureFlags.showLeakProtectionNotifications) + } func bootstrap() { let console = ConsoleDestination() #if PIA_DEV console.minLevel = .debug - - if let path = Bundle.main.url(forResource: "GoogleService-Info", withExtension: "plist"), - let plist = NSDictionary(contentsOf: path) as? [String: Any], - plist.count > 0 { - FirebaseApp.configure() - Fabric.sharedSDK().debug = true - Fabric.with([Crashlytics.self()]) - } #else console.minLevel = .info #endif SwiftyBeaver.addDestination(console) // Load the database first - Client.database = Client.Database(team: AppConstants.teamId, group: AppConstants.appGroup) + Client.database = Client.Database(group: AppConstants.appGroup) // Check if should clean the account after delete the app and install again if Client.providers.accountProvider.shouldCleanAccount { @@ -142,8 +149,13 @@ class Bootstrapper { AppPreferences.shared.checksDipExpirationRequest = Client.configuration.featureFlags.contains(Client.FeatureFlags.checkDipExpirationRequest) AppPreferences.shared.disablesMultiDipTokens = Client.configuration.featureFlags.contains(Client.FeatureFlags.disableMultiDipTokens) AppPreferences.shared.showNewInitialScreen = Client.configuration.featureFlags.contains(Client.FeatureFlags.showNewInitialScreen) + + + /// Updates the feature flags values to the ones set on the server only on Release builds. + /// (like Leak protection feature) + self.updateFeatureFlagsForReleaseIfNeeded() + }) - MessagesManager.shared.refreshMessages() //FORCE THE MIGRATION TO GEN4 if Client.providers.vpnProvider.needsMigrationToGEN4() { @@ -230,7 +242,7 @@ class Bootstrapper { if AppPreferences.shared.checksDipExpirationRequest, let dipToken = Client.providers.serverProvider.dipTokens?.first { Client.providers.serverProvider.handleDIPTokenExpiration(dipToken: dipToken, nil) } - + setupExceptionHandler() } private func setDefaultPlanProducts() { @@ -241,7 +253,13 @@ class Bootstrapper { func dispose() { Client.dispose() } - + + private func setupExceptionHandler() { + NSSetUncaughtExceptionHandler { exception in + Client.preferences.lastKnownException = "$exception,\n\(exception.callStackSymbols.joined(separator: "\n"))" + } + } + // MARK: Certificate func rsa4096Certificate() -> String? { @@ -256,10 +274,17 @@ class Bootstrapper { } @objc private func vpnStatusDidChange(notification: Notification) { - guard (Client.providers.vpnProvider.vpnStatus == .connected) else { - return + let vpnStatus = Client.providers.vpnProvider.vpnStatus + switch vpnStatus { + case .connected: + AppPreferences.shared.incrementSuccessConnections() + UserSurveyManager.shared.handleConnectionSuccess() + case .disconnected: + AppPreferences.shared.incrementSuccessDisconnections() + default: + break } - RatingManager.shared.logSuccessConnection() + RatingManager.shared.handleConnectionStatusChanged() } @objc private func internetReachable(notification: Notification) { diff --git a/PIA VPN/CardFactory.swift b/PIA VPN/CardFactory.swift index 90f16e03b..5faf0b2ae 100644 --- a/PIA VPN/CardFactory.swift +++ b/PIA VPN/CardFactory.swift @@ -22,6 +22,7 @@ import Foundation import PIALibrary import PIAWireguard +import UIKit struct CardFactory { diff --git a/PIA VPN/CustomDNSSettingsViewController.swift b/PIA VPN/CustomDNSSettingsViewController.swift index 1066cde57..d6527fd7c 100644 --- a/PIA VPN/CustomDNSSettingsViewController.swift +++ b/PIA VPN/CustomDNSSettingsViewController.swift @@ -22,6 +22,7 @@ import Foundation import PIALibrary +import UIKit class CustomDNSSettingsViewController: AutolayoutViewController { diff --git a/PIA VPN/DNSList.swift b/PIA VPN/DNSList.swift index 683a976e1..eeb75d689 100644 --- a/PIA VPN/DNSList.swift +++ b/PIA VPN/DNSList.swift @@ -21,6 +21,7 @@ // import Foundation +import PIALibrary class DNSList: NSObject { @@ -157,6 +158,22 @@ class DNSList: NSObject { return L10n.Settings.Dns.custom } + /// Return if a custom DNS is set for given protocol and its configured DNS servers + func hasCustomDNS(for vpnType: String, in dnsServers: [String]) -> Bool { + guard vpnType != IKEv2Profile.vpnType && !dnsServers.isEmpty else { + return false + } + + for dns in self.dnsList { + for (_, ipsList) in dns { + if dnsServers == ipsList { + return true + } + } + } + return false + } + /// Updates the content of the dnsList object into the plist private func updatePlist() { (self.dnsList as NSArray).write(toFile: self.plistPathInDocument, diff --git a/PIA VPN/DashboardViewController.swift b/PIA VPN/DashboardViewController.swift index 75d0503bb..dfa6fe800 100644 --- a/PIA VPN/DashboardViewController.swift +++ b/PIA VPN/DashboardViewController.swift @@ -25,6 +25,7 @@ import PIALibrary import SideMenu import SwiftyBeaver import WidgetKit +import NetworkExtension private let log = SwiftyBeaver.self @@ -76,6 +77,8 @@ class DashboardViewController: AutolayoutViewController { self.updateTileLayout() } } + + private var shouldReconnect = false deinit { NotificationCenter.default.removeObserver(self) @@ -117,6 +120,10 @@ class DashboardViewController: AutolayoutViewController { nc.addObserver(self, selector: #selector(checkInternetConnection), name: .PIADaemonsDidUpdateConnectivity, object: nil) nc.addObserver(self, selector: #selector(checkVPNConnectingStatus(notification:)), name: .PIADaemonsConnectingVPNStatus, object: nil) + nc.addObserver(self, selector: #selector(connectionVPNStatusDidChange(_:)), name: NSNotification.Name.NEVPNStatusDidChange, object: nil) + nc.addObserver(self, selector: #selector(handleDidConnectToRFC1918CompliantWifi(_:)), name: NSNotification.Name.DeviceDidConnectToRFC1918CompliantWifi, object: nil) + nc.addObserver(self, selector: #selector(checkConnectToRFC1918VulnerableWifi(_:)), name: NSNotification.Name.DeviceDidConnectToRFC1918VulnerableWifi, object: nil) + self.viewContentHeight = self.viewContentHeightConstraint.constant } @@ -155,6 +162,10 @@ class DashboardViewController: AutolayoutViewController { updateCurrentStatus() setupCallingCards() + // Checks if survey needs to be shown + if UserSurveyManager.shouldShowSurveyMessage() { + MessagesManager.shared.showInAppSurveyMessage() + } } override func viewDidAppear(_ animated: Bool) { @@ -248,6 +259,7 @@ class DashboardViewController: AutolayoutViewController { ) } navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Menu.Accessibility.item + navigationItem.leftBarButtonItem?.accessibilityIdentifier = Accessibility.Id.Dashboard.menu if navigationItem.rightBarButtonItem == nil { navigationItem.rightBarButtonItem = UIBarButtonItem( @@ -267,6 +279,7 @@ class DashboardViewController: AutolayoutViewController { action: #selector(closeTileEditingMode(_:)) ) navigationItem.leftBarButtonItem?.accessibilityLabel = L10n.Global.cancel + navigationItem.leftBarButtonItem?.accessibilityIdentifier = nil navigationItem.rightBarButtonItem = nil } @@ -359,7 +372,8 @@ class DashboardViewController: AutolayoutViewController { } @objc private func openMenu(_ sender: Any?) { - perform(segue: StoryboardSegue.Main.menuSegueIdentifier) + Theme.current.applySideMenu() + present(SideMenuManager.default.leftMenuNavigationController!, animated: true) } @objc private func closeTileEditingMode(_ sender: Any?) { @@ -404,7 +418,7 @@ class DashboardViewController: AutolayoutViewController { guard let weakSelf = self else { return } if let _ = error { - RatingManager.shared.logError() + RatingManager.shared.handleConnectionError() } let preferences = Client.preferences.editable() @@ -487,7 +501,7 @@ class DashboardViewController: AutolayoutViewController { override func prepare(for segue: UIStoryboardSegue, sender: Any?) { navigationItem.setEmptyBackButton() - if let sideMenu = segue.destination as? UISideMenuNavigationController { + if let sideMenu = segue.destination as? SideMenuNavigationController { setMenuDelegate(menuNavigationController: sideMenu) } else if let nmt = segue.destination as? TrustedNetworksViewController { nmt.shouldReconnectAutomatically = true @@ -596,12 +610,125 @@ class DashboardViewController: AutolayoutViewController { } } + @objc func connectionVPNStatusDidChange(_ notification: Notification? = nil) { + guard let connection = notification?.object as? NEVPNConnection else { return } + + switch connection.status { + case .connected: + if !Client.providers.vpnProvider.isVPNConnected { + handleNonCompliantWifiConnection() + } + case .disconnected: + + let state = UIApplication.shared.applicationState + + // Only remove the notification if the app is on the foreground + if state == .active { + removeNonCompliantWifiLocalNotification() + } + + if shouldReconnect { + Client.providers.vpnProvider.connect { _ in } + shouldReconnect = false + } + default: + break + } + } + + @objc func checkConnectToRFC1918VulnerableWifi(_ notification: Notification? = nil) { + guard Client.providers.vpnProvider.isVPNConnected else { return } + + handleNonCompliantWifiConnection() + } + + @objc func handleDidConnectToRFC1918CompliantWifi(_ notification: Notification) { + // Remove non compliant wifi notification if it was present in notification center + removeNonCompliantWifiLocalNotification() + + // Remove leak protection alert when connecting to a compliant Wi-Fi + removeLeakProtectionAlert() + } + + private func handleNonCompliantWifiConnection() { + guard WifiNetworkMonitor().isConnected() else { return } + + guard Client.preferences.currentRFC1918VulnerableWifi != nil + || WifiNetworkMonitor().checkForRFC1918Vulnerability() else { return } + + guard Client.preferences.allowLocalDeviceAccess + && Client.preferences.leakProtection else { return } + + guard AppPreferences.shared.showLeakProtectionNotifications else { return } + + let currentRFC1918VulnerableWifiName = Client.preferences.currentRFC1918VulnerableWifi ?? "" + + DispatchQueue.main.async { + self.presentNonCompliantWifiAlert() + self.showNonCompliantWifiLocalNotification(currentRFC1918VulnerableWifiName: currentRFC1918VulnerableWifiName) + } + } + + private func presentNonCompliantWifiAlert() { + guard let window = UIApplication.shared.delegate?.window, + let presentedViewController = window?.rootViewController?.presentedViewController ?? window?.rootViewController + else { return } + + let title = L10n.Dashboard.Vpn.Leakprotection.Alert.title + + if let alertController = presentedViewController as? UIAlertController, + alertController.title == title { return } + + let sheet = Macros.alertController(title, L10n.Dashboard.Vpn.Leakprotection.Alert.message) + sheet.addAction(UIAlertAction(title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta1, style: .default, handler: { _ in + Client.preferences.allowLocalDeviceAccess = false + Client.providers.vpnProvider.disconnect { _ in + self.shouldReconnect = true + } + })) + + // Learn More action + sheet.addAction(UIAlertAction(title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta2, style: .default, handler: { _ in + let application = UIApplication.shared + let learnMoreURL = AppConstants.Web.leakProtectionURL + + if application.canOpenURL(learnMoreURL) { + application.open(learnMoreURL) + } + })) + + sheet.addAction(UIAlertAction(title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta3, style: .default, handler: nil)) + + presentedViewController.present(sheet, animated: true, completion: nil) + } + + func showNonCompliantWifiLocalNotification(currentRFC1918VulnerableWifiName: String) { + // 1. Remove previous non-compliant wifi notification + removeNonCompliantWifiLocalNotification() + + // 2. Show the local notification for the current non-compliant wifi + Macros.showLocalNotificationIfNotAlreadyPresent(NotificationCategory.nonCompliantWifi, type: NotificationCategory.nonCompliantWifi, body: L10n.LocalNotification.NonCompliantWifi.text, title: L10n.LocalNotification.NonCompliantWifi.title(currentRFC1918VulnerableWifiName), delay: 0) + } + + private func removeNonCompliantWifiLocalNotification() { + // Remove non compliant wifi notification if it was present in notification center + Macros.removeLocalNotification(NotificationCategory.nonCompliantWifi) + } + + private func removeLeakProtectionAlert() { + guard let presentedLeakProtectionAlert = UIApplication.shared.delegate?.window??.rootViewController?.presentedViewController as? UIAlertController, + presentedLeakProtectionAlert.title == L10n.Dashboard.Vpn.Leakprotection.Alert.title else { return } + + presentedLeakProtectionAlert.dismiss(animated: true) + } + + // MARK: Helpers @objc private func vpnDidFail() { if !isDisconnecting { isDisconnecting = true Client.providers.vpnProvider.disconnect { _ in - RatingManager.shared.logError() + RatingManager.shared.handleConnectionError() self.isDisconnecting = false self.connectingStatus = .none } diff --git a/PIA VPN/DedicatedIpEmptyHeaderViewCell.swift b/PIA VPN/DedicatedIpEmptyHeaderViewCell.swift index 1aa32a16f..d044f4970 100644 --- a/PIA VPN/DedicatedIpEmptyHeaderViewCell.swift +++ b/PIA VPN/DedicatedIpEmptyHeaderViewCell.swift @@ -23,8 +23,7 @@ import UIKit import PIALibrary protocol DedicatedIpEmptyHeaderViewCellDelegate: AnyObject { - func getTimeToRetryDIP() -> TimeInterval? - func setTimeToRetryDIP(newInterval: TimeInterval) + func handleDIPActivation(with token: String, cell: DedicatedIpEmptyHeaderViewCell) } class DedicatedIpEmptyHeaderViewCell: UITableViewCell { @@ -78,79 +77,15 @@ class DedicatedIpEmptyHeaderViewCell: UITableViewCell { subtitle.style(style: TextStyle.textStyle8) } - private var invalidTokenLocalisedString: String { - get { - return L10n.Dedicated.Ip.Message.Invalid.token - } - } - - private func showInvalidTokenMessage() { - Macros.displayStickyNote(withMessage: invalidTokenLocalisedString, andImage: Asset.iconWarning.image) - } - - private func displayErrorMessage(errorMessage: String?, displayDuration: Double? = nil) { - Macros.displayImageNote(withImage: Asset.iconWarning.image, message: errorMessage ?? invalidTokenLocalisedString, andDuration: displayDuration) - } - - private func handleDIPActivationError(_ error: ClientError) { - switch error { - case .unauthorized: - Client.providers.accountProvider.logout(nil) - Macros.postNotification(.PIAUnauthorized) - case .throttled(let retryAfter): - let retryAfterSeconds = Double(retryAfter) - let localisedThrottlingString = L10n.Dedicated.Ip.Message.Error.retryafter("\(Int(retryAfter))") - - self.displayErrorMessage(errorMessage: NSLocalizedString(localisedThrottlingString, comment: localisedThrottlingString), - displayDuration: retryAfterSeconds) - self.delegate?.setTimeToRetryDIP(newInterval: Date().timeIntervalSince1970 + retryAfterSeconds) - default: - self.showInvalidTokenMessage() - } - } - - private func handleDIPActivation(token: String) { - NotificationCenter.default.post(name: .DedicatedIpShowAnimation, object: nil) - Client.providers.serverProvider.activateDIPToken(token) { [weak self] (server, error) in - NotificationCenter.default.post(name: .DedicatedIpHideAnimation, object: nil) - self?.addTokenTextfield.text = "" - guard let dipServer = server else { - - guard let error = error as? ClientError else { - self?.showInvalidTokenMessage() - return - } - - self?.handleDIPActivationError(error) - return - } - switch dipServer?.dipStatus { - case .active: - Macros.displaySuccessImageNote(withImage: Asset.iconWarning.image, message: L10n.Dedicated.Ip.Message.Valid.token) - case .expired: - print(L10n.Dedicated.Ip.Message.Expired.token) // we dont show the message to the user - default: - Macros.displayStickyNote(withMessage: self?.invalidTokenLocalisedString ?? "", andImage: Asset.iconWarning.image) - } - NotificationCenter.default.post(name: .DedicatedIpReload, object: nil) - NotificationCenter.default.post(name: .PIAThemeDidChange, object: nil) + @IBAction private func activateToken() { + if let token = addTokenTextfield.text { + self.delegate?.handleDIPActivation(with: token, cell: self) } } - @IBAction private func activateToken() { - if let timeUntilNextTry = self.delegate?.getTimeToRetryDIP()?.timeSinceNow() { - displayErrorMessage(errorMessage: L10n.Dedicated.Ip.Message.Error.retryafter("\(Int(timeUntilNextTry))"), displayDuration: timeUntilNextTry) - return - } - - if let token = addTokenTextfield.text, !token.isEmpty { - handleDIPActivation(token: token) - } else { - Macros.displayStickyNote(withMessage: L10n.Dedicated.Ip.Message.Incorrect.token, - andImage: Asset.iconWarning.image) - } + func emptyTokenTextField() { + addTokenTextfield.text = "" } - } extension DedicatedIpEmptyHeaderViewCell: UITextFieldDelegate { diff --git a/PIA VPN/DedicatedIpViewController.swift b/PIA VPN/DedicatedIpViewController.swift index 5d6f60a1e..97cf12a0d 100644 --- a/PIA VPN/DedicatedIpViewController.swift +++ b/PIA VPN/DedicatedIpViewController.swift @@ -22,6 +22,7 @@ import Foundation import PIALibrary import SwiftyBeaver +import UIKit private let log = SwiftyBeaver.self @@ -123,7 +124,39 @@ class DedicatedIpViewController: AutolayoutViewController { self.tableView.reloadData() } - + + // MARK: DIP Token handling + + private var invalidTokenLocalisedString: String { + get { + return L10n.Dedicated.Ip.Message.Invalid.token + } + } + + private func showInvalidTokenMessage() { + Macros.displayStickyNote(withMessage: invalidTokenLocalisedString, andImage: Asset.iconWarning.image) + } + + private func displayErrorMessage(errorMessage: String?, displayDuration: Double? = nil) { + Macros.displayImageNote(withImage: Asset.iconWarning.image, message: errorMessage ?? invalidTokenLocalisedString, andDuration: displayDuration) + } + + private func handleDIPActivationError(_ error: ClientError) { + switch error { + case .unauthorized: + Client.providers.accountProvider.logout(nil) + Macros.postNotification(.PIAUnauthorized) + case .throttled(let retryAfter): + let retryAfterSeconds = Double(retryAfter) + let localisedThrottlingString = L10n.Dedicated.Ip.Message.Error.retryafter("\(Int(retryAfter))") + + displayErrorMessage(errorMessage: NSLocalizedString(localisedThrottlingString, comment: localisedThrottlingString), + displayDuration: retryAfterSeconds) + timeToRetryDIP = Date().timeIntervalSince1970 + retryAfterSeconds + default: + showInvalidTokenMessage() + } + } } extension DedicatedIpViewController: UITableViewDelegate, UITableViewDataSource { @@ -210,13 +243,42 @@ extension DedicatedIpViewController: UITableViewDelegate, UITableViewDataSource } extension DedicatedIpViewController: DedicatedIpEmptyHeaderViewCellDelegate { - func getTimeToRetryDIP() -> TimeInterval? { - return timeToRetryDIP - } - - func setTimeToRetryDIP(newInterval: TimeInterval) { - timeToRetryDIP = newInterval + func handleDIPActivation(with token: String, cell: DedicatedIpEmptyHeaderViewCell) { + if let timeUntilNextTry = timeToRetryDIP?.timeSinceNow() { + displayErrorMessage(errorMessage: L10n.Dedicated.Ip.Message.Error.retryafter("\(Int(timeUntilNextTry))"), displayDuration: timeUntilNextTry) + return + } + + if token.isEmpty { + Macros.displayStickyNote(withMessage: L10n.Dedicated.Ip.Message.Incorrect.token, + andImage: Asset.iconWarning.image) + return + } + + NotificationCenter.default.post(name: .DedicatedIpShowAnimation, object: nil) + Client.providers.serverProvider.activateDIPToken(token) { [weak self] (server, error) in + NotificationCenter.default.post(name: .DedicatedIpHideAnimation, object: nil) + cell.emptyTokenTextField() + guard let dipServer = server else { + + guard let error = error as? ClientError else { + self?.showInvalidTokenMessage() + return + } + + self?.handleDIPActivationError(error) + return + } + switch dipServer?.dipStatus { + case .active: + Macros.displaySuccessImageNote(withImage: Asset.iconWarning.image, message: L10n.Dedicated.Ip.Message.Valid.token) + case .expired: + print(L10n.Dedicated.Ip.Message.Expired.token) // we dont show the message to the user + default: + Macros.displayStickyNote(withMessage: self?.invalidTokenLocalisedString ?? "", andImage: Asset.iconWarning.image) + } + NotificationCenter.default.post(name: .DedicatedIpReload, object: nil) + NotificationCenter.default.post(name: .PIAThemeDidChange, object: nil) + } } - - } diff --git a/PIA VPN/GeneralSettingsViewController.swift b/PIA VPN/GeneralSettingsViewController.swift index d23b7e3e1..41f1e4e3a 100644 --- a/PIA VPN/GeneralSettingsViewController.swift +++ b/PIA VPN/GeneralSettingsViewController.swift @@ -37,7 +37,7 @@ class GeneralSettingsViewController: PIABaseSettingsViewController { tableView.estimatedSectionFooterHeight = 1.0 switchGeoServers.addTarget(self, action: #selector(toggleGEOServers(_:)), for: .valueChanged) - switchInAppMessages.addTarget(self, action: #selector(toggleStopInAppMessages(_:)), for: .valueChanged) + switchInAppMessages.addTarget(self, action: #selector(toggleShowServiceMessages(_:)), for: .valueChanged) tableView.delegate = self tableView.dataSource = self @@ -60,8 +60,8 @@ class GeneralSettingsViewController: PIABaseSettingsViewController { self.tableView.reloadData() } - @objc private func toggleStopInAppMessages(_ sender: UISwitch) { - AppPreferences.shared.stopInAppMessages = sender.isOn + @objc private func toggleShowServiceMessages(_ sender: UISwitch) { + AppPreferences.shared.showServiceMessages = sender.isOn } @objc private func toggleGEOServers(_ sender: UISwitch) { @@ -129,7 +129,7 @@ extension GeneralSettingsViewController: UITableViewDelegate, UITableViewDataSou case .showServiceCommunicationMessages: cell.accessoryView = switchInAppMessages cell.selectionStyle = .none - switchInAppMessages.isOn = AppPreferences.shared.stopInAppMessages //invert the boolean as the title has change to Show messages instead of Stop messages + switchInAppMessages.isOn = AppPreferences.shared.showServiceMessages //invert the boolean as the title has change to Show messages instead of Stop messages case .showGeoRegions: cell.textLabel?.numberOfLines = 0 diff --git a/PIA VPN/IPv4Address.swift b/PIA VPN/IPv4Address.swift new file mode 100644 index 000000000..f39a032f3 --- /dev/null +++ b/PIA VPN/IPv4Address.swift @@ -0,0 +1,60 @@ +// +// IPv4Address.swift +// PIA VPN +// +// Created by Said Rehouni on 11/8/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import Network + +extension IPv4Address { + + /// https://datatracker.ietf.org/doc/html/rfc1918 + public var isRFC1918Compliant: Bool { + inRange("10.0.0.0"..."10.255.255.255") || inRange("172.16.0.0"..."172.31.255.255") + || inRange("192.168.0.0"..."192.168.255.255") + } + + /// Checks if IPAddress is in range of other address + /// - Parameter range: A range of IPAddress + /// - Returns: True if this address is within range + public func inRange(_ range: ClosedRange) -> Bool { + range.contains(self) + } +} + +extension IPv4Address: Comparable { + /// Comparison is done by converting Ipaddress to Integer + public static func < (lhs: IPv4Address, rhs: IPv4Address) -> Bool { + let lhsIntValue = representAsInteger(ipAddress: lhs) + let rhsIntValue = representAsInteger(ipAddress: rhs) + return lhsIntValue < rhsIntValue + } + + /// Converts IPAddress as integer values. Loops over each address section, shifts them and accumulate the result. + /// - Parameter ipAddress: IPAddress for conversion + /// - Returns: Integer representation + static func representAsInteger(ipAddress: IPv4Address) -> Int { + var result: Int = 0 + let octets = ipAddress.octets + for i in stride(from: 3, through: 0, by: -1) { + result += octets[3 - i] << (i * 8) + } + return result + } + + // IPAddress as an Integer array + var octets: [Int] { + return self.rawValue.map { Int($0) } + } +} + +extension IPv4Address: ExpressibleByStringLiteral { + /// Initialize from a Static String + /// Intentionally crashes when value is a bad IPaddresses + public init(stringLiteral value: StringLiteralType) { + self.init(value)! + } +} diff --git a/PIA VPN/MenuViewController.swift b/PIA VPN/MenuViewController.swift index cf3cda55f..170ee4077 100644 --- a/PIA VPN/MenuViewController.swift +++ b/PIA VPN/MenuViewController.swift @@ -460,7 +460,7 @@ extension MenuViewController: UITableViewDataSource, UITableViewDelegate { cell.accessibilityIdentifier = "uitests.menu.account" case .logout: - cell.accessibilityIdentifier = "uitests.menu.logout" + cell.accessibilityIdentifier = Accessibility.Id.Menu.logout default: break diff --git a/PIA VPN/MessagesCommands.swift b/PIA VPN/MessagesCommands.swift index db8b58bb2..04ac97ffe 100644 --- a/PIA VPN/MessagesCommands.swift +++ b/PIA VPN/MessagesCommands.swift @@ -23,7 +23,8 @@ import Foundation import UIKit import PIALibrary import PIAWireguard -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN protocol Command { func execute() @@ -157,8 +158,8 @@ class ActionCommand: Command { private func activateOpenVPN() { let preferences = Client.preferences.editable() - guard let currentVPNConfiguration = preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration ?? - Client.preferences.defaults.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration else { + guard let currentVPNConfiguration = preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration ?? + Client.preferences.defaults.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration else { fatalError("No default VPN custom configuration provided for PIA OpenVPN protocol") } diff --git a/PIA VPN/MessagesManager.swift b/PIA VPN/MessagesManager.swift index 7a913bcf5..6b7b4634b 100644 --- a/PIA VPN/MessagesManager.swift +++ b/PIA VPN/MessagesManager.swift @@ -22,13 +22,15 @@ import Foundation import PIALibrary import PIAAccount +import UIKit public class MessagesManager: NSObject { public static let shared = MessagesManager() private var apiMessage: InAppMessage! private var systemMessage: InAppMessage! - + private static let surveyMessageID = "take-the-survey-message-banner" + public override init() { super.init() NotificationCenter.default.addObserver(self, selector: #selector(presentExpiringDIPRegionSystemMessage(notification:)), name: .PIADIPRegionExpiring, object: nil) @@ -38,18 +40,6 @@ public class MessagesManager: NSObject { deinit { NotificationCenter.default.removeObserver(self) } - - func refreshMessages() { - - if !AppPreferences.shared.stopInAppMessages { - Client.providers.accountProvider.inAppMessages(forAppVersion: Macros.localizedVersionNumber()) { (message, error) in - if let message = message, !message.wasDismissed() { - self.apiMessage = message - Macros.postNotification(.PIAUpdateFixedTiles) - } - } - } - } func postSystemMessage(message: InAppMessage) { self.systemMessage = message @@ -71,6 +61,10 @@ public class MessagesManager: NSObject { } func dismiss(message id: String) { + if id == MessagesManager.surveyMessageID { + AppPreferences.shared.userInteractedWithSurvey = true + } + AppPreferences.shared.dismissedMessages.append(id) if apiMessage != nil, id == apiMessage.id { apiMessage = nil @@ -126,7 +120,7 @@ extension InAppMessage { } command?.execute() - + executionCompletionHandler?() } func localisedMessage() -> String { @@ -199,4 +193,19 @@ extension MessagesManager { AppPreferences.shared.dedicatedTokenIPReleation[token] = ip } } + + + func showInAppSurveyMessage() { + let message = InAppMessage(withMessage: ["en-US": L10n.Account.Survey.message.appendDetailSymbol()], id: MessagesManager.surveyMessageID, link: ["en-US": L10n.Account.Survey.messageLink.appendDetailSymbol()], type: .link, level: .api, actions: nil, view: nil, uri: AppConstants.Survey.formURL.absoluteString) { [weak self] in + self?.dismiss(message: MessagesManager.surveyMessageID) + } + MessagesManager.shared.postSystemMessage(message: message) + } +} + +private extension String { + func appendDetailSymbol() -> String { + let symbol = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft ? "⟨ " : " ⟩" + return "\(self)\(symbol)" + } } diff --git a/PIA VPN/NetworkMonitor.swift b/PIA VPN/NetworkMonitor.swift new file mode 100644 index 000000000..ecd302b38 --- /dev/null +++ b/PIA VPN/NetworkMonitor.swift @@ -0,0 +1,14 @@ +// +// NetworkMonitor.swift +// PIA VPN +// +// Created by Said Rehouni on 14/8/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Foundation + +protocol NetworkMonitor { + func checkForRFC1918Vulnerability() -> Bool + func isConnected() -> Bool +} diff --git a/PIA VPN/PIAHotspotHelper.swift b/PIA VPN/PIAHotspotHelper.swift index c9ee827b0..f8e5728a0 100644 --- a/PIA VPN/PIAHotspotHelper.swift +++ b/PIA VPN/PIAHotspotHelper.swift @@ -24,6 +24,7 @@ import Foundation import NetworkExtension import PIALibrary import SwiftyBeaver +import UIKit private let log = SwiftyBeaver.self @@ -41,9 +42,11 @@ public protocol PIAHotspotHelperDelegate: class { class PIAHotspotHelper { private var delegate: PIAHotspotHelperDelegate? + private let networkMonitor: NetworkMonitor - init(withDelegate delegate: PIAHotspotHelperDelegate? = nil) { + init(withDelegate delegate: PIAHotspotHelperDelegate? = nil, networkMonitor: NetworkMonitor = WifiNetworkMonitor()) { self.delegate = delegate + self.networkMonitor = networkMonitor } /** @@ -92,6 +95,13 @@ class PIAHotspotHelper { } response.deliver() } else if cmd.commandType == .evaluate { + + if AppPreferences.shared.showLeakProtectionNotifications { + weakSelf.checkForRFC1918VulnerableWifi(cmd: cmd) + } else { + Client.preferences.currentRFC1918VulnerableWifi = nil + } + if let currentNetwork = cmd.network { if !currentNetwork.isSecure { // Open WiFi log.info("Evaluate") @@ -122,6 +132,18 @@ class PIAHotspotHelper { } } + private func checkForRFC1918VulnerableWifi(cmd: NEHotspotHelperCommand) { + if networkMonitor.checkForRFC1918Vulnerability() { + log.info("HotspotHelper: APIHotspotDidDetectRFC1918VulnerableWifi detected") + Client.preferences.currentRFC1918VulnerableWifi = cmd.network?.ssid.trimmingCharacters(in: CharacterSet.whitespaces) + NotificationCenter.default.post(name: .DeviceDidConnectToRFC1918VulnerableWifi, object: nil) + } else { + log.info("HotspotHelper: APIHotspotDidDetectRFC1918VulnerableWifi NOT detected") + Client.preferences.currentRFC1918VulnerableWifi = nil + NotificationCenter.default.post(name: .DeviceDidConnectToRFC1918CompliantWifi, object: nil) + } + } + private func hotspotHelperMessage() -> String { if Client.preferences.nmtRulesEnabled, Client.preferences.useWiFiProtection { @@ -200,3 +222,12 @@ class PIAHotspotHelper { } } + +public extension Notification.Name { + + /// Posted when device detects RFC1918 vulnerable Wifi + static let DeviceDidConnectToRFC1918VulnerableWifi: Notification.Name = Notification.Name("DeviceDidConnectToRFC1918VulnerableWifi") + + /// Posted when device detects RFC1918 compliant Wifi + static let DeviceDidConnectToRFC1918CompliantWifi: Notification.Name = Notification.Name("DeviceDidConnectToRFC1918CompliantWifi") +} diff --git a/PIA VPN/PIAPageControl.swift b/PIA VPN/PIAPageControl.swift index de0b14a2d..77ef66359 100644 --- a/PIA VPN/PIAPageControl.swift +++ b/PIA VPN/PIAPageControl.swift @@ -20,7 +20,7 @@ // Internet Access iOS Client. If not, see . // -import FXPageControl +import __PIALibraryNative class PIAPageControl: FXPageControl { override func draw(_ rect: CGRect) { diff --git a/PIA VPN/PIATunnelProvider+UI.swift b/PIA VPN/PIATunnelProvider+UI.swift index b97d7c017..3d36081b5 100644 --- a/PIA VPN/PIATunnelProvider+UI.swift +++ b/PIA VPN/PIATunnelProvider+UI.swift @@ -21,7 +21,8 @@ // import Foundation -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN extension SocketType: CustomStringConvertible { public var description: String { diff --git a/PIA VPN/PemUtil.swift b/PIA VPN/PemUtil.swift index 2220f398f..e952cb829 100644 --- a/PIA VPN/PemUtil.swift +++ b/PIA VPN/PemUtil.swift @@ -22,7 +22,8 @@ // import Foundation -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN public extension OpenVPN.Configuration { diff --git a/PIA VPN/ProtocolSettingsViewController.swift b/PIA VPN/ProtocolSettingsViewController.swift index c6821e6dc..fe38f7147 100644 --- a/PIA VPN/ProtocolSettingsViewController.swift +++ b/PIA VPN/ProtocolSettingsViewController.swift @@ -23,7 +23,8 @@ import UIKit import Popover import SwiftyBeaver import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN class ProtocolSettingsViewController: PIABaseSettingsViewController { @@ -98,6 +99,7 @@ class ProtocolSettingsViewController: PIABaseSettingsViewController { let height = heightForOptions(options) //Default height * 3 for 3 protocols let optionsView = ProtocolPopoverSelectionView(frame: CGRect(x: 0, y: 0, width: Int(width), height: height)) optionsView.pendingPreferences = self.pendingPreferences + optionsView.settingsDelegate = self.settingsDelegate optionsView.currentPopover = protocolPopover optionsView.protocols = options protocolPopover.show(optionsView, fromView: sender) diff --git a/PIA VPN/RatingManager.swift b/PIA VPN/RatingManager.swift index 5546985db..f40ed816c 100644 --- a/PIA VPN/RatingManager.swift +++ b/PIA VPN/RatingManager.swift @@ -33,26 +33,53 @@ class RatingManager { static let shared = RatingManager() + private var successDisconnectionsUntilPrompt: Int private var successConnectionsUntilPrompt: Int private var successConnectionsUntilPromptAgain: Int private var timeIntervalUntilPromptAgain: Double private var errorInConnectionsUntilPrompt: Int - + + private var isRatingFlagAvailable: Bool { + return Client.configuration.featureFlags.contains(Client.FeatureFlags.disableSystemRatingDialog) + } + + private var targetDisconnectionsReachedForPrompt: Bool { + guard AppPreferences.shared.successConnections < self.successConnectionsUntilPrompt else { + return false // We do not check this because alert was already shown + } + return AppPreferences.shared.successDisconnections == self.successDisconnectionsUntilPrompt + } + + private var targetConnectionsReachedForPrompt: Bool { + return AppPreferences.shared.successConnections == self.successConnectionsUntilPrompt + } + init() { + self.successDisconnectionsUntilPrompt = AppConfiguration.Rating.successDisconnectionsUntilPrompt self.successConnectionsUntilPrompt = AppConfiguration.Rating.successConnectionsUntilPrompt self.successConnectionsUntilPromptAgain = AppConfiguration.Rating.successConnectionsUntilPromptAgain self.errorInConnectionsUntilPrompt = AppConfiguration.Rating.errorInConnectionsUntilPrompt self.timeIntervalUntilPromptAgain = AppConfiguration.Rating.timeIntervalUntilPromptAgain } - func logSuccessConnection() { - - AppPreferences.shared.successConnections += 1 - if AppPreferences.shared.successConnections == self.successConnectionsUntilPrompt { + func handleConnectionStatusChanged() { + // By default do not use custom alert + // and comparison should be: when vpn is disconnected + if Client.providers.vpnProvider.vpnStatus == (isRatingFlagAvailable ? .connected : .disconnected) { + showAppReviewWith(customPopup: isRatingFlagAvailable) + } + } + + private func showAppReviewWith(customPopup useCustomDialog: Bool) { + let shouldShowRatingAlert = useCustomDialog ? targetConnectionsReachedForPrompt : targetDisconnectionsReachedForPrompt + if shouldShowRatingAlert { log.debug("Show rating") - reviewApp() + if useCustomDialog { + showCustomAlertForReview() + } else { + showDefaultAlertForReview() + } } else if AppPreferences.shared.canAskAgainForReview { - let now = Date() if AppPreferences.shared.successConnections >= self.successConnectionsUntilPromptAgain, @@ -62,10 +89,9 @@ class RatingManager { reviewAppWithoutPrompt() } } - } - func logError() { + func handleConnectionError() { if Client.daemons.isNetworkReachable { if AppPreferences.shared.failureConnections == self.errorInConnectionsUntilPrompt { askForConnectionIssuesFeedback() @@ -74,7 +100,15 @@ class RatingManager { AppPreferences.shared.failureConnections += 1 } } - + + private func handleRatingAlertCancel() { + log.debug("No review but maybe we can try in the future") + AppPreferences.shared.canAskAgainForReview = true + if AppPreferences.shared.lastRatingRejection == nil { + AppPreferences.shared.lastRatingRejection = Date() + } + } + private func openRatingViewInAppstore() { let urlStr = AppConstants.Reviews.appReviewUrl @@ -94,9 +128,53 @@ class RatingManager { SKStoreReviewController.requestReview() } - private func reviewApp() { + // MARK: Default Alerts + + private func showDefaultAlertForReview() { + guard let rootView = AppDelegate.getRootViewController() else { + return + } + + let sheet = Macros.alertController(L10n.Rating.Enjoy.question, nil) + sheet.addAction(UIAlertAction(title: L10n.Rating.Alert.Button.notreally, style: .default, handler: { action in + // Ask for feedback + let alert = self.createDefaultFeedbackDialog() + rootView.present(alert, animated: true, completion: nil) + })) + sheet.addAction(UIAlertAction(title: L10n.Global.yes, style: .default, handler: { action in + let alert = self.createDefaultReviewAlert() + rootView.present(alert, animated: true, completion: nil) + })) + rootView.present(sheet, animated: true, completion: nil) + } + + private func createDefaultFeedbackDialog() -> UIAlertController { + let sheet = Macros.alertController(L10n.Rating.Problems.question, L10n.Rating.Problems.subtitle) + sheet.addAction(UIAlertAction(title: L10n.Global.no, style: .default, handler: { action in + log.debug("No feedback") + })) + sheet.addAction(UIAlertAction(title: L10n.Global.yes, style: .default, handler: { action in + self.openFeedbackWebsite() + })) + return sheet + } + + private func createDefaultReviewAlert() -> UIAlertController { + let sheet = Macros.alertController(L10n.Rating.Rate.question, nil) + sheet.addAction(UIAlertAction(title: L10n.Rating.Alert.Button.nothanks, style: .default, handler: { action in + self.handleRatingAlertCancel() + })) + sheet.addAction(UIAlertAction(title: L10n.Rating.Alert.Button.oksure, style: .default, handler: { action in + self.openRatingViewInAppstore() + })) + return sheet + } + + // MARK: Custom Alerts + + private func showCustomAlertForReview() { - guard let rootView = AppDelegate.delegate().topViewControllerWithRootViewController(rootViewController: UIApplication.shared.keyWindow?.rootViewController) else { + guard let rootView = AppDelegate.getRootViewController() else { return } @@ -106,12 +184,12 @@ class RatingManager { ) sheet.addCancelActionWithTitle(L10n.Global.no, handler: { // Ask for feedback - let alert = self.feedback() + let alert = self.createCustomFeedbackDialog() rootView.present(alert, animated: true, completion: nil) }) sheet.addActionWithTitle(L10n.Global.yes) { - let alert = self.askForReview() + let alert = self.createCustomReviewDialog() rootView.present(alert, animated: true, completion: nil) } @@ -119,7 +197,7 @@ class RatingManager { } - private func feedback() -> PopupDialog { + private func createCustomFeedbackDialog() -> PopupDialog { let sheet = Macros.alert( L10n.Rating.Problems.question, @@ -137,18 +215,14 @@ class RatingManager { } - private func askForReview() -> PopupDialog { + private func createCustomReviewDialog() -> PopupDialog { let sheet = Macros.alert( - L10n.Rating.Rate.question, + L10n.Rating.Review.question, L10n.Rating.Rate.subtitle ) sheet.addCancelActionWithTitle(L10n.Global.no, handler: { - log.debug("No review but maybe we can try in the future") - AppPreferences.shared.canAskAgainForReview = true - if AppPreferences.shared.lastRatingRejection == nil { - AppPreferences.shared.lastRatingRejection = Date() - } + self.handleRatingAlertCancel() }) sheet.addActionWithTitle(L10n.Global.yes) { @@ -161,7 +235,7 @@ class RatingManager { private func askForConnectionIssuesFeedback() { - guard let rootView = AppDelegate.delegate().topViewControllerWithRootViewController(rootViewController: UIApplication.shared.keyWindow?.rootViewController) else { + guard let rootView = AppDelegate.getRootViewController() else { return } diff --git a/PIA VPN/Server+UI.swift b/PIA VPN/Server+UI.swift index 7d2cbf519..21763a819 100644 --- a/PIA VPN/Server+UI.swift +++ b/PIA VPN/Server+UI.swift @@ -22,7 +22,8 @@ import Foundation import PIALibrary -import AlamofireImage +import Alamofire +import UIKit extension Server: CustomStringConvertible { func name(forStatus status: VPNStatus) -> String { diff --git a/PIA VPN/SettingOptions.swift b/PIA VPN/SettingOptions.swift index cbb745cee..d72c74427 100644 --- a/PIA VPN/SettingOptions.swift +++ b/PIA VPN/SettingOptions.swift @@ -190,12 +190,16 @@ public enum NetworkSections: Int, SettingSection, EnumsBuilder { public enum PrivacyFeaturesSections: Int, SettingSection, EnumsBuilder { case killswitch = 0 + case leakProtection + case allowAccessOnLocalNetwork case safariContentBlocker case refresh public func localizedTitleMessage() -> String { switch self { case .killswitch: return L10n.Settings.ApplicationSettings.KillSwitch.title + case .leakProtection: return L10n.Settings.ApplicationSettings.LeakProtection.title + case .allowAccessOnLocalNetwork: return L10n.Settings.ApplicationSettings.AllowLocalNetwork.title case .safariContentBlocker: return L10n.Settings.ContentBlocker.title case .refresh: return L10n.Settings.ContentBlocker.Refresh.title } @@ -204,13 +208,15 @@ public enum PrivacyFeaturesSections: Int, SettingSection, EnumsBuilder { public func localizedSubtitleMessage() -> String { switch self { case .killswitch: return "" + case .leakProtection: return "" + case .allowAccessOnLocalNetwork: return "" case .safariContentBlocker: return "" case .refresh: return "" } } public static func all() -> [Self] { - return [.killswitch, .safariContentBlocker, .refresh] + return [.killswitch, .leakProtection, .allowAccessOnLocalNetwork, .safariContentBlocker, .refresh] } } @@ -289,6 +295,8 @@ public enum DevelopmentSections: Int, SettingSection, EnumsBuilder { case resolveGoogleAdsDomain case deleteKeychain case crash + case leakProtectionFlag + case leakProtectionNotificationsFlag public func localizedTitleMessage() -> String { switch self { @@ -301,6 +309,8 @@ public enum DevelopmentSections: Int, SettingSection, EnumsBuilder { case .resolveGoogleAdsDomain: return "Resolve Google Ads Domain" case .deleteKeychain: return "Delete the Keychain" case .crash: return "Crash the app" + case .leakProtectionFlag: return "FF - Leak Protection" + case .leakProtectionNotificationsFlag: return "FF - Leak Protection Notifications" } } @@ -315,11 +325,13 @@ public enum DevelopmentSections: Int, SettingSection, EnumsBuilder { case .resolveGoogleAdsDomain: return "" case .deleteKeychain: return "" case .crash: return "" + case .leakProtectionFlag: return "" + case .leakProtectionNotificationsFlag: return "" } } public static func all() -> [Self] { - return [.stagingVersion, .customServers, .publicUsername, .username, .password, .environment, .resolveGoogleAdsDomain, .deleteKeychain, .crash] + return [.stagingVersion, .customServers, .publicUsername, .username, .password, .environment, .resolveGoogleAdsDomain, .deleteKeychain, .crash, .leakProtectionFlag, .leakProtectionNotificationsFlag] } } diff --git a/PIA VPN/Settings/DevelopmentSettingsViewController.swift b/PIA VPN/Settings/DevelopmentSettingsViewController.swift index 0199d0e93..3d0793ac5 100644 --- a/PIA VPN/Settings/DevelopmentSettingsViewController.swift +++ b/PIA VPN/Settings/DevelopmentSettingsViewController.swift @@ -32,6 +32,8 @@ class DevelopmentSettingsViewController: PIABaseSettingsViewController { @IBOutlet weak var tableView: UITableView! private lazy var switchEnvironment = UISwitch() + private lazy var switchLeakProtectionFlag = UISwitch() + private lazy var switchLeakProtectionNotificationsFlag = UISwitch() private var controller: OptionsViewController? override func viewDidLoad() { @@ -44,6 +46,8 @@ class DevelopmentSettingsViewController: PIABaseSettingsViewController { tableView.delegate = self tableView.dataSource = self switchEnvironment.addTarget(self, action: #selector(toggleEnv(_:)), for: .valueChanged) + + addFeatureFlagsTogglesActions() NotificationCenter.default.addObserver(self, selector: #selector(reloadSettings), name: .PIASettingsHaveChanged, object: nil) } @@ -145,6 +149,18 @@ extension DevelopmentSettingsViewController: UITableViewDelegate, UITableViewDat case .crash: cell.textLabel?.text = "Crash" cell.detailTextLabel?.text = nil + case .leakProtectionFlag: + cell.textLabel?.text = "FF - Leak Protection" + cell.detailTextLabel?.text = nil + cell.accessoryView = switchLeakProtectionFlag + cell.selectionStyle = .none + switchLeakProtectionFlag.isOn = AppPreferences.shared.showLeakProtection + case .leakProtectionNotificationsFlag: + cell.textLabel?.text = "FF - Leak Protection Notifications" + cell.detailTextLabel?.text = nil + cell.accessoryView = switchLeakProtectionNotificationsFlag + cell.selectionStyle = .none + switchLeakProtectionNotificationsFlag.isOn = AppPreferences.shared.showLeakProtectionNotifications } } @@ -231,7 +247,7 @@ extension DevelopmentSettingsViewController: UITableViewDelegate, UITableViewDat } case .crash: - fatalError("Crashing staging app") + crashStagingApp() case .deleteKeychain: deleteKeychain() default: break @@ -240,6 +256,10 @@ extension DevelopmentSettingsViewController: UITableViewDelegate, UITableViewDat tableView.deselectRow(at: indexPath, animated: true) } + private func crashStagingApp() { + NSException(name: NSExceptionName(rawValue: "App Crash"), reason: "Crashing the staging app manually").raise() + } + private func deleteKeychain() { let secItemClasses = [kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey, kSecClassIdentity] for itemClass in secItemClasses { @@ -342,3 +362,23 @@ extension DevelopmentSettingsViewController: OptionsViewControllerDelegate { } } + + +// MARK: - Feature Flags Toggles + +extension DevelopmentSettingsViewController { + @objc private func toggleLeakProtectionFlag(_ sender: UISwitch) { + AppPreferences.shared.showLeakProtection = sender.isOn + } + + @objc private func toggleLeakProtectionNotificationsFlag(_ sender: UISwitch) { + AppPreferences.shared.showLeakProtectionNotifications = sender.isOn + } + + private func addFeatureFlagsTogglesActions() { + switchLeakProtectionFlag.addTarget(self, action: #selector(toggleLeakProtectionFlag(_:)), for: .valueChanged) + switchLeakProtectionNotificationsFlag.addTarget(self, action: #selector(toggleLeakProtectionNotificationsFlag(_:)), for: .valueChanged) + + // Additional Feature Flags toggles actions here + } +} diff --git a/PIA VPN/Settings/HelpSettingsViewController.swift b/PIA VPN/Settings/HelpSettingsViewController.swift index c02b7109c..fec85269c 100644 --- a/PIA VPN/Settings/HelpSettingsViewController.swift +++ b/PIA VPN/Settings/HelpSettingsViewController.swift @@ -79,14 +79,16 @@ class HelpSettingsViewController: PIABaseSettingsViewController { @objc private func toggleShareServiceQualityData(_ sender: UISwitch) { let preferences = Client.preferences.editable() preferences.shareServiceQualityData = sender.isOn - preferences.commit() if sender.isOn { + preferences.versionWhenServiceQualityOpted = Macros.versionString() ServiceQualityManager.shared.start() } else { + preferences.versionWhenServiceQualityOpted = nil ServiceQualityManager.shared.stop() } + preferences.commit() reloadSettings() } @@ -230,7 +232,7 @@ extension HelpSettingsViewController: UITableViewDelegate, UITableViewDataSource } @objc private func showShareDataInformation() { - let storyboard = UIStoryboard(name: "Signup", bundle: Bundle(for: ShareDataInformationViewController.self)) + let storyboard = Client.signupStoryboard() let shareDataInformationViewController = storyboard.instantiateViewController(withIdentifier: ViewControllerIdentifiers.shareDataInformation) presentModally(viewController: shareDataInformationViewController) } diff --git a/PIA VPN/Settings/NetworkSettingsViewController.swift b/PIA VPN/Settings/NetworkSettingsViewController.swift index 6deea4a3b..37719ff3c 100644 --- a/PIA VPN/Settings/NetworkSettingsViewController.swift +++ b/PIA VPN/Settings/NetworkSettingsViewController.swift @@ -23,7 +23,8 @@ import UIKit import Popover import SwiftyBeaver import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN import PIAWireguard protocol DNSSettingsDelegate: AnyObject { diff --git a/PIA VPN/Settings/PIABaseSettingsViewController.swift b/PIA VPN/Settings/PIABaseSettingsViewController.swift index 960423357..33e3c9a31 100644 --- a/PIA VPN/Settings/PIABaseSettingsViewController.swift +++ b/PIA VPN/Settings/PIABaseSettingsViewController.swift @@ -21,7 +21,8 @@ import UIKit import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN class PIABaseSettingsViewController: AutolayoutViewController { diff --git a/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift b/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift index a4d465dff..f6fe859bb 100644 --- a/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift +++ b/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift @@ -31,11 +31,25 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { @IBOutlet weak var tableView: UITableView! private lazy var switchPersistent = UISwitch() private lazy var switchContentBlocker = UISwitch() + private lazy var switchLeakProtection = UISwitch() + private lazy var switchAllowDevicesOnLocalNetwork = UISwitch() private var isContentBlockerEnabled = false + + private var preferences: AppPreferences? + private var sections = [PrivacyFeaturesSections]() + private var vpnProvider: VPNProvider? override func viewDidLoad() { super.viewDidLoad() + preferences = AppPreferences.shared + vpnProvider = Client.providers.vpnProvider + + if let preferences = preferences, preferences.showLeakProtection { + sections = PrivacyFeaturesSections.all() + } else { + sections = PrivacyFeaturesSections.all().filter { $0 != .leakProtection && $0 != .allowAccessOnLocalNetwork } + } tableView.sectionFooterHeight = UITableView.automaticDimension tableView.estimatedSectionFooterHeight = 1.0 @@ -45,6 +59,8 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { switchPersistent.addTarget(self, action: #selector(togglePersistentConnection(_:)), for: .valueChanged) switchContentBlocker.addTarget(self, action: #selector(showContentBlockerTutorial), for: .touchUpInside) + switchLeakProtection.addTarget(self, action: #selector(toggleLeakProtection(_:)), for: .valueChanged) + switchAllowDevicesOnLocalNetwork.addTarget(self, action: #selector(toggleAllowDevicesOnLocalNetwork(_:)), for: .valueChanged) NotificationCenter.default.addObserver(self, selector: #selector(reloadSettings), name: .PIASettingsHaveChanged, object: nil) NotificationCenter.default.addObserver(self, @@ -116,7 +132,27 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { @objc private func showContentBlockerTutorial() { perform(segue: StoryboardSegue.Main.contentBlockerSegueIdentifier) } - + + @objc private func toggleLeakProtection(_ sender: UISwitch) { + Client.preferences.leakProtection = sender.isOn + tableView.reloadData() + presentUpdateSettingsAlertWhenConnected() + } + + @objc private func toggleAllowDevicesOnLocalNetwork(_ sender: UISwitch) { + Client.preferences.allowLocalDeviceAccess = sender.isOn + presentUpdateSettingsAlertWhenConnected() + } + + private func presentUpdateSettingsAlertWhenConnected() { + guard let vpnProvider = vpnProvider, vpnProvider.vpnStatus == .connected else { + return + } + + let sheet = Macros.alertController(L10n.Settings.ApplicationSettings.LeakProtection.Alert.title, nil) + sheet.addAction(UIAlertAction(title: L10n.Global.ok, style: .default, handler: nil)) + present(sheet, animated: true) + } // MARK: Restylable @@ -139,7 +175,7 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { extension PrivacyFeaturesSettingsViewController: UITableViewDelegate, UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - return PrivacyFeaturesSections.all().count + return sections.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -147,19 +183,38 @@ extension PrivacyFeaturesSettingsViewController: UITableViewDelegate, UITableVie } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + guard sections.count > section, + let cell = tableView.dequeueReusableCell(withIdentifier: Cells.footer) else { + return nil + } - if section != PrivacyFeaturesSections.refresh.rawValue, let cell = tableView.dequeueReusableCell(withIdentifier: Cells.footer) { - cell.textLabel?.numberOfLines = 0 - cell.textLabel?.style(style: TextStyle.textStyle21) - cell.backgroundColor = .clear - if section == PrivacyFeaturesSections.killswitch.rawValue { - cell.textLabel?.text = L10n.Settings.ApplicationSettings.KillSwitch.footer - } else { - cell.textLabel?.text = L10n.Settings.ContentBlocker.footer - } + let privacySettingsSection = sections[section] + + cell.textLabel?.numberOfLines = 0 + cell.textLabel?.style(style: TextStyle.textStyle21) + cell.backgroundColor = .clear + + switch privacySettingsSection { + case .killswitch: + cell.textLabel?.text = L10n.Settings.ApplicationSettings.KillSwitch.footer + return cell + case .leakProtection: + let leakProtectionDescription = L10n.Settings.ApplicationSettings.LeakProtection.footer + let attributtedDescription = NSMutableAttributedString(string: leakProtectionDescription) + let moreInfoText = L10n.Settings.ApplicationSettings.LeakProtection.moreInfo + let moreInfoTextRange = (leakProtectionDescription as NSString).range(of: moreInfoText) + attributtedDescription.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: moreInfoTextRange) + cell.textLabel?.attributedText = attributtedDescription + return cell + case .allowAccessOnLocalNetwork: + cell.textLabel?.text = L10n.Settings.ApplicationSettings.AllowLocalNetwork.footer return cell + case .safariContentBlocker: + cell.textLabel?.text = L10n.Settings.ContentBlocker.footer + return cell + case .refresh: + return nil } - return nil } @@ -170,7 +225,7 @@ extension PrivacyFeaturesSettingsViewController: UITableViewDelegate, UITableVie cell.selectionStyle = .default cell.detailTextLabel?.text = nil - let section = PrivacyFeaturesSections.all()[indexPath.section] + let section = sections[indexPath.section] switch section { case .killswitch: @@ -179,8 +234,19 @@ extension PrivacyFeaturesSettingsViewController: UITableViewDelegate, UITableVie cell.accessoryView = switchPersistent cell.selectionStyle = .none switchPersistent.isOn = pendingPreferences.isPersistentConnection - - + case .leakProtection: + cell.textLabel?.text = L10n.Settings.ApplicationSettings.LeakProtection.title + cell.detailTextLabel?.text = nil + cell.accessoryView = switchLeakProtection + cell.selectionStyle = .none + switchLeakProtection.isOn = Client.preferences.leakProtection + case .allowAccessOnLocalNetwork: + cell.textLabel?.text = L10n.Settings.ApplicationSettings.AllowLocalNetwork.title + cell.detailTextLabel?.text = nil + cell.accessoryView = switchAllowDevicesOnLocalNetwork + cell.selectionStyle = .none + switchAllowDevicesOnLocalNetwork.isEnabled = Client.preferences.leakProtection + switchAllowDevicesOnLocalNetwork.isOn = !Client.preferences.leakProtection ? false : Client.preferences.allowLocalDeviceAccess case .safariContentBlocker: cell.textLabel?.text = L10n.Settings.ContentBlocker.title cell.detailTextLabel?.text = nil @@ -214,12 +280,21 @@ extension PrivacyFeaturesSettingsViewController: UITableViewDelegate, UITableVie func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let section = PrivacyFeaturesSections.all()[indexPath.section] - + let section = sections[indexPath.section] + switch section { - case .refresh: - refreshContentBlockerRules() - default: break + case .refresh: + refreshContentBlockerRules() + case .leakProtection: + let application = UIApplication.shared + let learnMoreURL = AppConstants.Web.leakProtectionURL + + // Open the Learn more url when the user taps on the Leak Protection cell + if application.canOpenURL(learnMoreURL) { + application.open(learnMoreURL) + } + + default: break } tableView.deselectRow(at: indexPath, animated: true) diff --git a/PIA VPN/Settings/SettingPopoverSelectionView.swift b/PIA VPN/Settings/SettingPopoverSelectionView.swift index 1734bd0eb..df42d0b07 100644 --- a/PIA VPN/Settings/SettingPopoverSelectionView.swift +++ b/PIA VPN/Settings/SettingPopoverSelectionView.swift @@ -22,7 +22,8 @@ import UIKit import PIALibrary import Popover -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN class SettingPopoverSelectionView: UIView { @@ -110,6 +111,7 @@ extension ProtocolPopoverSelectionView: UITableViewDelegate, UITableViewDataSour let vpnType = protocols[indexPath.row] pendingPreferences.vpnType = vpnType + settingsDelegate.updateSetting(ProtocolsSections.protocolSelection, withValue: nil) Macros.postNotification(.PIASettingsHaveChanged) Macros.postNotification(.RefreshSettings) diff --git a/PIA VPN/SettingsDelegate.swift b/PIA VPN/SettingsDelegate.swift index be9293aba..ae1aec848 100644 --- a/PIA VPN/SettingsDelegate.swift +++ b/PIA VPN/SettingsDelegate.swift @@ -21,7 +21,8 @@ import Foundation import PIAWireguard -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN protocol SettingsDelegate: AnyObject { diff --git a/PIA VPN/SettingsViewController.swift b/PIA VPN/SettingsViewController.swift index df82a1a1b..af0dd1093 100644 --- a/PIA VPN/SettingsViewController.swift +++ b/PIA VPN/SettingsViewController.swift @@ -22,11 +22,13 @@ import UIKit import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN import SafariServices import SwiftyBeaver import PIAWireguard import WidgetKit +import AlamofireImage private let log = SwiftyBeaver.self @@ -264,7 +266,7 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { preferences.ikeV2PacketSize = pendingPreferences.ikeV2PacketSize preferences.commit() - guard let currentOpenVPNConfiguration = pendingPreferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration else { + guard let currentOpenVPNConfiguration = pendingPreferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration else { fatalError("No default VPN custom configuration provided for PIA protocol") } AppPreferences.shared.reset() @@ -412,8 +414,8 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { @objc func reloadSettings() { pendingPreferences = Client.preferences.editable() - guard let currentOpenVPNConfiguration = pendingPreferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration ?? - Client.preferences.defaults.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration else { + guard let currentOpenVPNConfiguration = pendingPreferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration ?? + Client.preferences.defaults.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration else { fatalError("No default VPN custom configuration provided for PIA OpenVPN protocol") } @@ -439,7 +441,7 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { log.debug("OpenVPN endpoints: \(pendingOpenVPNConfiguration.endpointProtocols ?? [])") if pendingPreferences.vpnType == PIATunnelProfile.vpnType { - var builder = OpenVPNTunnelProvider.ConfigurationBuilder(sessionConfiguration: pendingOpenVPNConfiguration.build()) + var builder = OpenVPNProvider.ConfigurationBuilder(sessionConfiguration: pendingOpenVPNConfiguration.build()) if pendingPreferences.vpnType == PIATunnelProfile.vpnType { if AppPreferences.shared.useSmallPackets { @@ -460,10 +462,24 @@ class SettingsViewController: AutolayoutViewController, SettingsDelegate { } } + updateCustomDNSAppPreferences() refreshSettings() reportUpdatedPreferences() } + private func updateCustomDNSAppPreferences() { + var dnsServers = pendingOpenVPNConfiguration.dnsServers + if pendingPreferences.vpnType == PIAWGTunnelProfile.vpnType { + dnsServers = pendingWireguardVPNConfiguration.customDNSServers + } + + if let dnsServers = dnsServers { + AppPreferences.shared.usesCustomDNS = DNSList.shared.hasCustomDNS(for: pendingPreferences.vpnType, in: dnsServers) + } else { + AppPreferences.shared.usesCustomDNS = false + } + } + // MARK: ModalController override func dismissModal() { diff --git a/PIA VPN/ShowConnectionStatsViewController.swift b/PIA VPN/ShowConnectionStatsViewController.swift index cb697bc11..aa6ff8e56 100644 --- a/PIA VPN/ShowConnectionStatsViewController.swift +++ b/PIA VPN/ShowConnectionStatsViewController.swift @@ -21,6 +21,7 @@ import Foundation import PIALibrary +import UIKit class ShowConnectionStatsViewController: AutolayoutViewController { diff --git a/PIA VPN/String+VPNType.swift b/PIA VPN/String+VPNType.swift index f496752b1..e2de89442 100644 --- a/PIA VPN/String+VPNType.swift +++ b/PIA VPN/String+VPNType.swift @@ -21,7 +21,8 @@ import Foundation import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN import PIAWireguard public extension String { @@ -46,7 +47,7 @@ public extension String { case PIATunnelProfile.vpnType: if AppPreferences.shared.piaSocketType != nil { let preferences = Client.preferences.editable() - if let currentOpenVPNConfiguration = preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration { + if let currentOpenVPNConfiguration = preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration { let port = currentOpenVPNConfiguration.sessionConfiguration.builder().endpointProtocols?.first?.port ?? 0 return "\(port)" } @@ -93,7 +94,7 @@ public extension String { return "ChaCha20" case PIATunnelProfile.vpnType: let preferences = Client.preferences.editable() - if let currentOpenVPNConfiguration = preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration { + if let currentOpenVPNConfiguration = preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration { return currentOpenVPNConfiguration.sessionConfiguration.builder().cipher?.rawValue ?? "" } return "---" @@ -111,7 +112,7 @@ public extension String { return "Poly1305" case PIATunnelProfile.vpnType: let preferences = Client.preferences.editable() - if let currentOpenVPNConfiguration = preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNTunnelProvider.Configuration { + if let currentOpenVPNConfiguration = preferences.vpnCustomConfiguration(for: PIATunnelProfile.vpnType) as? OpenVPNProvider.Configuration { return currentOpenVPNConfiguration.sessionConfiguration.builder().digest?.rawValue ?? "" } return "---" diff --git a/PIA VPN/SwiftGen+ScenesStoryboards.swift b/PIA VPN/SwiftGen+ScenesStoryboards.swift index 65b7d0076..5ce12dc8b 100644 --- a/PIA VPN/SwiftGen+ScenesStoryboards.swift +++ b/PIA VPN/SwiftGen+ScenesStoryboards.swift @@ -18,7 +18,7 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: Main.self) - internal static let sideMenuNavigationController = SceneType(storyboard: Main.self, identifier: "SideMenuNavigationController") + internal static let sideMenuNavigationController = SceneType(storyboard: Main.self, identifier: "SideMenuNavigationController") internal static let vpnPermissionViewController = SceneType(storyboard: Main.self, identifier: "VPNPermissionViewController") } diff --git a/PIA VPN/SwiftGen+Strings.swift b/PIA VPN/SwiftGen+Strings.swift index 85d15c45b..aa1043fa6 100644 --- a/PIA VPN/SwiftGen+Strings.swift +++ b/PIA VPN/SwiftGen+Strings.swift @@ -111,6 +111,13 @@ internal enum L10n { internal static let message = L10n.tr("Localizable", "account.subscriptions.short.message") } } + internal enum Survey { + /// Want to help make PIA better? Let us know how we can improve! + /// Take The Survey + internal static let message = L10n.tr("Localizable", "account.survey.message") + /// Take The Survey + internal static let messageLink = L10n.tr("Localizable", "account.survey.messageLink") + } internal enum Update { internal enum Email { internal enum Require { @@ -201,6 +208,20 @@ internal enum L10n { /// This network is untrusted. Do you really want to disconnect the VPN? internal static let untrusted = L10n.tr("Localizable", "dashboard.vpn.disconnect.untrusted") } + internal enum Leakprotection { + internal enum Alert { + /// Disable Now + internal static let cta1 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta1", fallback: "Disable Now") + /// Learn more + internal static let cta2 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta2", fallback: "Learn more") + /// Ignore + internal static let cta3 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.cta3", fallback: "Ignore") + /// To prevent data leaks, tap Disable Now to turn off “Allow access to devices on local network" and automatically reconnect. + internal static let message = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.message", fallback: "To prevent data leaks, tap Disable Now to turn off “Allow access to devices on local network\" and automatically reconnect.") + /// Unsecured Wi-Fi detected + internal static let title = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.title", fallback: "Unsecured Wi-Fi detected") + } + } } } @@ -498,6 +519,17 @@ internal enum L10n { } } } + + internal enum LocalNotification { + internal enum NonCompliantWifi { + /// Tap here to secure your device + internal static let text = L10n.tr("Localizable", "local_notification.non_compliant_wifi.text", fallback: "Tap here to secure your device") + /// Unsecured Wi-Fi: %@ + internal static func title(_ p1: Any) -> String { + return L10n.tr("Localizable", "local_notification.non_compliant_wifi.title", String(describing: p1), fallback: "Unsecured Wi-Fi: \(p1)") + } + } + } internal enum Menu { internal enum Accessibility { @@ -633,6 +665,16 @@ internal enum L10n { } internal enum Rating { + internal enum Alert { + internal enum Button { + /// No, thanks. + internal static let nothanks = L10n.tr("Localizable", "rating.alert.button.nothanks") + /// Not Really + internal static let notreally = L10n.tr("Localizable", "rating.alert.button.notreally") + /// Ok, sure! + internal static let oksure = L10n.tr("Localizable", "rating.alert.button.oksure") + } + } internal enum Enjoy { /// Are you enjoying PIA VPN? internal static let question = L10n.tr("Localizable", "rating.enjoy.question") @@ -656,11 +698,15 @@ internal enum L10n { internal static let subtitle = L10n.tr("Localizable", "rating.problems.subtitle") } internal enum Rate { - /// How about an AppStore review? + /// How about a rating on the AppStore? internal static let question = L10n.tr("Localizable", "rating.rate.question") /// We appreciate you sharing your experience internal static let subtitle = L10n.tr("Localizable", "rating.rate.subtitle") } + internal enum Review { + /// How about an AppStore review? + internal static let question = L10n.tr("Localizable", "rating.review.question") + } } internal enum Region { @@ -785,12 +831,34 @@ internal enum L10n { /// VPN Kill Switch internal static let title = L10n.tr("Localizable", "settings.application_settings.kill_switch.title") } + internal enum LeakProtection { + /// iOS includes features designed to operate outside the VPN by default, such as AirDrop, CarPlay, AirPlay, and Personal Hotspots. Enabling custom leak protection routes this traffic through the VPN but may affect how these features function. More info + internal static let footer = L10n.tr("Localizable", "settings.application_settings.leak_protection.footer", fallback: "iOS includes features designed to operate outside the VPN by default, such as AirDrop, CarPlay, AirPlay, and Personal Hotspots. Enabling custom leak protection routes this traffic through the VPN but may affect how these features function. More info") + /// More info + internal static let moreInfo = L10n.tr("Localizable", "settings.application_settings.leak_protection.more_info", fallback: "More info") + /// Leak Protection + internal static let title = L10n.tr("Localizable", "settings.application_settings.leak_protection.title", fallback: "Leak Protection") + internal enum Alert { + /// Changes to the VPN Settings will take effect on the next connection + internal static let title = L10n.tr("Localizable", "settings.application_settings.leak_protection.alert.title", fallback: "Changes to the VPN Settings will take effect on the next connection") + } + } + internal enum AllowLocalNetwork { + /// Stay connected to local devices like printers or file servers while connected to the VPN. (Allow this only if you trust the people and devices on your network.) + internal static let footer = L10n.tr("Localizable", "settings.application_settings.allow_local_network.footer", fallback: "Stay connected to local devices like printers or file servers while connected to the VPN. (Allow this only if you trust the people and devices on your network.)") + /// Allow access to devices on local network + internal static let title = L10n.tr("Localizable", "settings.application_settings.allow_local_network.title", fallback: "Allow access to devices on local network") + } internal enum Mace { /// PIA MACE™ blocks ads, trackers, and malware while you're connected to the VPN. internal static let footer = L10n.tr("Localizable", "settings.application_settings.mace.footer") /// PIA MACE™ internal static let title = L10n.tr("Localizable", "settings.application_settings.mace.title") } + internal enum LeakProtectionAlert { + /// VPN Leak Protection update settings alert + internal static let title = L10n.tr("Localizable", "settings.application_settings.leak_protection.alert.title") + } } internal enum Cards { internal enum History { @@ -1249,8 +1317,8 @@ internal enum L10n { // MARK: - Implementation Details extension L10n { - private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { - let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table) + private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String? = nil) -> String { + let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table) return String(format: format, locale: Locale.current, arguments: args) } } diff --git a/PIA VPN/Theme+App.swift b/PIA VPN/Theme+App.swift index e2898e32f..304c42bc2 100644 --- a/PIA VPN/Theme+App.swift +++ b/PIA VPN/Theme+App.swift @@ -23,7 +23,6 @@ import Foundation import PIALibrary import SideMenu -import FXPageControl import UIKit extension Theme { diff --git a/PIA VPN/Theme+DarkPalette.swift b/PIA VPN/Theme+DarkPalette.swift index 54c38a506..47b8fcb80 100644 --- a/PIA VPN/Theme+DarkPalette.swift +++ b/PIA VPN/Theme+DarkPalette.swift @@ -22,6 +22,7 @@ import Foundation import PIALibrary +import UIKit extension Theme.Palette { static var dark: Theme.Palette { diff --git a/PIA VPN/ThemeStrategy+App.swift b/PIA VPN/ThemeStrategy+App.swift index d61a62006..244189e05 100644 --- a/PIA VPN/ThemeStrategy+App.swift +++ b/PIA VPN/ThemeStrategy+App.swift @@ -22,6 +22,7 @@ import Foundation import PIALibrary +import UIKit extension ThemeCode { func apply(theme: Theme, reload: Bool) { diff --git a/PIA VPN/Tiles/ConnectionTile.swift b/PIA VPN/Tiles/ConnectionTile.swift index c3fc8a05a..227360069 100644 --- a/PIA VPN/Tiles/ConnectionTile.swift +++ b/PIA VPN/Tiles/ConnectionTile.swift @@ -21,7 +21,8 @@ import UIKit import PIALibrary -import TunnelKit +import TunnelKitCore +import TunnelKitOpenVPN import WidgetKit class ConnectionTile: UIView, Tileable { diff --git a/PIA VPN/Tiles/FavoriteServersTile.swift b/PIA VPN/Tiles/FavoriteServersTile.swift index a0e57ab10..dd94a7ab4 100644 --- a/PIA VPN/Tiles/FavoriteServersTile.swift +++ b/PIA VPN/Tiles/FavoriteServersTile.swift @@ -23,6 +23,7 @@ import Foundation import PIALibrary +import UIKit class FavoriteServersTile: UIView, Tileable { diff --git a/PIA VPN/Tiles/MessagesTile.swift b/PIA VPN/Tiles/MessagesTile.swift index b1e5a1ec8..230b54b01 100644 --- a/PIA VPN/Tiles/MessagesTile.swift +++ b/PIA VPN/Tiles/MessagesTile.swift @@ -21,6 +21,7 @@ import Foundation import PIALibrary +import UIKit class MessagesTile: UIView, Tileable { diff --git a/PIA VPN/Tiles/UsageTile.swift b/PIA VPN/Tiles/UsageTile.swift index 8efe0d41c..4067f1cfb 100644 --- a/PIA VPN/Tiles/UsageTile.swift +++ b/PIA VPN/Tiles/UsageTile.swift @@ -23,6 +23,7 @@ import Foundation import PIALibrary +import UIKit class UsageTile: UIView, Tileable { diff --git a/PIA VPN/UserSurveyManager.swift b/PIA VPN/UserSurveyManager.swift new file mode 100644 index 000000000..bcbdbf6a8 --- /dev/null +++ b/PIA VPN/UserSurveyManager.swift @@ -0,0 +1,48 @@ +// +// UserSurveyManager.swift +// PIA VPN +// +// Created by Waleed Mahmood on 11.02.22. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import PIALibrary + +class UserSurveyManager { + static let shared = UserSurveyManager() + + private init() { + let nc = NotificationCenter.default + nc.addObserver(self, selector: #selector(setupConnectionCounters), name: .PIAAccountDidRefresh, object: nil) + + setupConnectionCounters() + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + @objc private func setupConnectionCounters() { + let appPreferences = AppPreferences.shared + if let _ = appPreferences.successConnectionsUntilSurvey { + return + } + appPreferences.successConnectionsUntilSurvey = appPreferences.successConnections + AppConstants.Survey.numberOfConnectionsUntilPrompt + } + + func handleConnectionSuccess() { + if UserSurveyManager.shouldShowSurveyMessage() { + MessagesManager.shared.showInAppSurveyMessage() + } + } + + // MARK: Survey Settings + static func shouldShowSurveyMessage() -> Bool { + let appPreferences = AppPreferences.shared + guard let successConnectionUntilSurvey = appPreferences.successConnectionsUntilSurvey else { + return false + } + return !appPreferences.userInteractedWithSurvey && appPreferences.successConnections >= successConnectionUntilSurvey + } +} diff --git a/PIA VPN/VPNPermissionViewController.swift b/PIA VPN/VPNPermissionViewController.swift index 4e805a78a..922be68f7 100644 --- a/PIA VPN/VPNPermissionViewController.swift +++ b/PIA VPN/VPNPermissionViewController.swift @@ -112,6 +112,7 @@ class VPNPermissionViewController: AutolayoutViewController { buttonSubmit.style(style: TextStyle.Buttons.piaGreenButton) buttonSubmit.setTitle(L10n.Global.ok.uppercased(), for: []) + buttonSubmit.accessibilityIdentifier = Accessibility.Id.Permissions.submit } } diff --git a/PIA VPN/WifiNetworkMonitor.swift b/PIA VPN/WifiNetworkMonitor.swift new file mode 100644 index 000000000..8a96c9b64 --- /dev/null +++ b/PIA VPN/WifiNetworkMonitor.swift @@ -0,0 +1,69 @@ +// +// WifiNetworkMonitor.swift +// PIA VPN +// +// Created by Said Rehouni on 11/8/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import Network +import NetworkExtension + +class WifiNetworkMonitor: NetworkMonitor { + + private func getWifiAndEthernetIpAddress() -> [IPv4Address] { + var addresses = [IPv4Address]() + // Get list of all interfaces + var ifaddr: UnsafeMutablePointer? + defer { + freeifaddrs(ifaddr) + } + guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else { + return [] + } + + // Loop every interface, retrieve IPAddress and interface name (en0, en1, tun1) + for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { + let flags = Int32(ptr.pointee.ifa_flags) + // Find all active ifaddrs and avoid loopback interface. + if (flags & (IFF_UP | IFF_RUNNING | IFF_LOOPBACK)) == (IFF_UP | IFF_RUNNING) { + let addr = ptr.pointee.ifa_addr.pointee + if addr.sa_family == UInt8(AF_INET) { + // Convert interface address to a human readable string: + var ipAddressStr = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + let nameInfo = getnameinfo( + ptr.pointee.ifa_addr, + socklen_t(addr.sa_len), + &ipAddressStr, + socklen_t(ipAddressStr.count), + nil, + socklen_t(0), + NI_NUMERICHOST + ) + if nameInfo == 0, + let ipAddress = IPv4Address(.init(cString: ipAddressStr)) + { + let interfaceName = String(cString: ptr.pointee.ifa_name) + if ipAddress.isLinkLocal == false && interfaceName.starts(with: "en") { + addresses.append(ipAddress) + } + } + } + } + } + return addresses + } + + func checkForRFC1918Vulnerability() -> Bool { + let wifiIPAddresses = getWifiAndEthernetIpAddress() + return wifiIPAddresses.contains(where: { $0.isRFC1918Compliant == false }) + } + + func isConnected() -> Bool { + if let currentNetworks = NEHotspotHelper.supportedNetworkInterfaces() { + return currentNetworks.contains(where: { $0 is NEHotspotNetwork }) + } + return false + } +} diff --git a/PIA VPN/ar.lproj/Localizable.strings b/PIA VPN/ar.lproj/Localizable.strings index 29f7826f7..969fb18dc 100644 --- a/PIA VPN/ar.lproj/Localizable.strings +++ b/PIA VPN/ar.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "خطة شهرية"; "account.subscriptions.trial" = "خطة تجريبية"; "account.unauthorized" = "حدث خطأ. يرجى محاولة تسجيل الدخول مرة أخرى"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "حذف الحساب"; +"account.delete.alert.title" = "هل أنت متأكد؟"; +"account.delete.alert.message" = "يعد حذف حساب PIA الخاص بك نهائي ولا رجعة فيه. لن تتمكن من استرجاع بيانات اعتماد PIA الخاصة بك بعد تنفيذ هذا الإجراء. يرجى الملاحظة بأن هذا الإجراء يعني حذف حسابك من قاعدة بياناتنا فقط، ولا يعني حذف اشتراكك. ستحتاج إلى زيارة حساب Apple الخاص بك وحذف اشتراك Private Internet Access الخاص بك من هناك. خلافاً لذلك، ستستمر في دفع رسوم الاشتراك رغم أن حسابك لن يكون نشطاً."; +"account.delete.alert.failureMessage" = "حدث خطأ أثناء حذف حسابك. يرجى المحاولة مرة أخرى."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "نأمل أن يلبي منتجنا لشبكات VPN توقعاتك"; "rating.problems.question" = "ما الخلل الذي حصل؟"; "rating.problems.subtitle" = "هل لديك أي ملاحظات؟ يمكننا مساعدتك على تحسين تجربتك في استخدام PIA"; -"rating.rate.question" = "ما رأيك بترك مراجعة في AppStore؟"; +"rating.review.question" = "ما رأيك بترك مراجعة في AppStore؟"; +"rating.rate.question" = "ماذا عن ترك تقييم في App Store؟"; "rating.rate.subtitle" = "نقدّر لك مشاركة تجربتك"; "rating.error.question" = "تعذّر تأسيس اتصال"; "rating.error.subtitle" = "يمكنك محاولة تحديد منطقة مختلفة أو إعلامنا بها عن طريق فتح تذكرة دعم."; "rating.error.button.send" = "إرسال ملاحظات"; +"rating.alert.button.notreally" = "لا أرغب"; +"rating.alert.button.nothanks" = "لا، شكرًا."; +"rating.alert.button.oksure" = "بالتأكيد"; // CALLING CARDS "card.wireguard.title" = "جرّب ®WireGuard اليوم!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "تم تنشيط عنوان IP المخصّص بنجاح. سيكون متاحًا في قائمة اختيار المنطقة."; "dedicated.ip.message.expired.token" = "انتهت صلاحية الرمز. يرجى إنشاء رمز جديد من صفحة حسابك على الموقع الإلكتروني."; "dedicated.ip.message.error.token" = "انتهت صلاحية الرمز. يرجى إنشاء رمز جديد من صفحة حسابك على الموقع الإلكتروني."; +"dedicated.ip.message.error.retryafter" = "عدد طلبات تفعيل التوكن كبير جداً. يرجى إعادة المحاولة بعد %@ ثانية."; "dedicated.ip.message.token.willexpire" = "ستنتهي صلاحية عنوان IP المخصّص قريبًا. احصل على واحد جديد"; "dedicated.ip.message.token.willexpire.link" = "احصل على واحد جديد"; "dedicated.ip.message.ip.updated" = "تم تحديث عنوان IP المخصّص"; diff --git a/PIA VPN/da.lproj/Localizable.strings b/PIA VPN/da.lproj/Localizable.strings index 5190ecf99..1e53c5174 100644 --- a/PIA VPN/da.lproj/Localizable.strings +++ b/PIA VPN/da.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Månedsabonnement"; "account.subscriptions.trial" = "Prøveabonnement"; "account.unauthorized" = "Noget gik galt. Prøv at logge på igen."; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Slet konto"; +"account.delete.alert.title" = "Er du sikker?"; +"account.delete.alert.message" = "Hvis du sletter din PIA-konto, er det permanent og uigenkaldeligt. Du vil ikke kunne gendanne dine PIA-legitimationsoplysninger, når du har udført denne handling. Bemærk, at denne handling kun sletter din PIA-konto fra vores database, men IKKE sletter dit aabonnement. Du er nødt til at gå til din Apple-konto og annullere dit Private Internet Access-abonnement derfra. Ellers vil du fortsat blive faktureret, også selv om din PIA-konto ikke længere er aktiv."; +"account.delete.alert.failureMessage" = "Noget gik galt, da du slettede din konto, prøv igen senere."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Vi håber, at vores VPN lever op til dine forventninger."; "rating.problems.question" = "Hvad gik galt?"; "rating.problems.subtitle" = "Vil du give noget feedback? Vi kan hjælpe dig med at forbedre din oplevelse med PIA"; -"rating.rate.question" = "Hvad med en AppStore-anmeldelse?"; +"rating.review.question" = "Hvad med en AppStore-anmeldelse?"; +"rating.rate.question" = "Hvad med en vurdering i AppStore?"; "rating.rate.subtitle" = "Vi sætter pris på, at du deler din oplevelse"; "rating.error.question" = "Forbindelsen kunne ikke oprettes"; "rating.error.subtitle" = "Du kan prøve at vælge en anden region, eller fortælle os om det, ved at åbne en supportbillet."; "rating.error.button.send" = "Send feedback"; +"rating.alert.button.notreally" = "Nej, helst ikke"; +"rating.alert.button.nothanks" = "Nej tak."; +"rating.alert.button.oksure" = "OK, selvfølgelig!"; // CALLING CARDS "card.wireguard.title" = "Prøv WireGuard® i dag!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Din dedikerede IP er blevet aktiveret. Den vil være tilgængelig på din liste over Valg af regioner."; "dedicated.ip.message.expired.token" = "Dit token er udløbet. Generer et nyt fra din kontoside på websiden."; "dedicated.ip.message.error.token" = "Dit token er udløbet. Generer et nyt fra din kontoside på websiden."; +"dedicated.ip.message.error.retryafter" = "For mange mislykkede anmodninger om aktivering af token. Prøv igen efter %@ sekund(er)."; "dedicated.ip.message.token.willexpire" = "Din dedikerede IP udløber snart. Få en ny"; "dedicated.ip.message.token.willexpire.link" = "Få en ny"; "dedicated.ip.message.ip.updated" = "Din dedikerede IP blev opdateret"; diff --git a/PIA VPN/de.lproj/Localizable.strings b/PIA VPN/de.lproj/Localizable.strings index a97ddde8f..3ad30822c 100644 --- a/PIA VPN/de.lproj/Localizable.strings +++ b/PIA VPN/de.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Monatsabo"; "account.subscriptions.trial" = "Testversion"; "account.unauthorized" = "Etwas ging schief. Bitte versuchen, erneut anzumelden"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Konto löschen"; +"account.delete.alert.title" = "Sind Sie sicher?"; +"account.delete.alert.message" = "Die Löschung Ihres PIA-Kontos ist dauerhaft und unwiderruflich. Sie können Ihre PIA-Anmeldedaten nach dieser Aktion nicht wiederherstellen. Bitte beachten Sie, dass diese Aktion nur Ihr PIA-Konto aus unserer Datenbank löscht, aber NICHT Ihr Abonnement. Sie müssen zu Ihrem Apple-Konto gehen und das Private Internet Access-Abonnement von dort aus kündigen. Anderenfalls werden Sie weiterhin belastet, auch wenn Ihr PIA-Konto nicht mehr aktiv ist."; +"account.delete.alert.failureMessage" = "Etwas schlug beim Löschen Ihres Kontos fehl. Versuchen Sie es bitte später noch einmal."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Wir hoffen, dass unsere VPN-Lösung Ihre Erwartungen erfüllt."; "rating.problems.question" = "Was ist schiefgelaufen?"; "rating.problems.subtitle" = "Möchten Sie Feedback geben? Wir können Ihnen helfen, Ihre Erfahrungen mit PIA zu verbessern"; -"rating.rate.question" = "Wie wäre es mit einer Bewertung im AppStore?"; +"rating.review.question" = "Wie wäre es mit einer Rezension im App Store?"; +"rating.rate.question" = "Wie wäre es mit einer Bewertung im App Store?"; "rating.rate.subtitle" = "Wir wissen es zu schätzen, dass Sie uns Ihre Erfahrungen mitteilen"; "rating.error.question" = "Die Verbindung konnte nicht hergestellt werden"; "rating.error.subtitle" = "Sie können versuchen, eine andere Region auszuwählen oder uns darüber zu informieren, indem Sie ein Support-Ticket öffnen."; "rating.error.button.send" = "Feedback senden"; +"rating.alert.button.notreally" = "Nicht wirklich"; +"rating.alert.button.nothanks" = "Nein danke."; +"rating.alert.button.oksure" = "Okay, sicher!"; // CALLING CARDS "card.wireguard.title" = "WireGuard® noch heute testen!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Ihre Dedizierte IP wurde erfolgreich aktiviert. Sie wird in der Auswahlliste Ihrer Region verfügbar sein."; "dedicated.ip.message.expired.token" = "Ihr Token ist abgelaufen. Bitte generieren Sie von Ihrer Konto-Seite auf der Webseite ein neues."; "dedicated.ip.message.error.token" = "Ihr Token ist abgelaufen. Bitte generieren Sie ein neues auf Ihrer Konto-Seite auf der Webseite."; +"dedicated.ip.message.error.retryafter" = "Zu viele fehlgeschlagene Token-Aktivierungsanforderungen. Bitte versuche es nach %@ Sekunde(n) erneut."; "dedicated.ip.message.token.willexpire" = "Ihre dedizierte IP wird bald ablaufen. Eine neue abrufen"; "dedicated.ip.message.token.willexpire.link" = "Eine neue abrufen"; "dedicated.ip.message.ip.updated" = "Ihre dedizierte IP wurde aktualisiert"; diff --git a/PIA VPN/en.lproj/Localizable.strings b/PIA VPN/en.lproj/Localizable.strings index 66aa92638..8d0c2ac64 100644 --- a/PIA VPN/en.lproj/Localizable.strings +++ b/PIA VPN/en.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Renewal"; "expiration.message" = "Your subscription expires soon. Renew to stay protected."; +"local_notification.non_compliant_wifi.title" = "Unsecured Wi-Fi: %@"; +"local_notification.non_compliant_wifi.text" = "Tap here to secure your device"; + // SHORTCUTS "shortcuts.connect" = "Connect"; @@ -113,6 +116,8 @@ "account.delete.alert.title" = "Are you sure?"; "account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; "account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -132,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Active theme"; "settings.application_settings.kill_switch.title" = "VPN Kill Switch"; "settings.application_settings.kill_switch.footer" = "The VPN kill switch prevents access to the Internet if the VPN connection is reconnecting. This excludes disconnecting manually."; +"settings.application_settings.leak_protection.title" = "Leak Protection"; +"settings.application_settings.leak_protection.footer" = "iOS includes features designed to operate outside the VPN by default, such as AirDrop, CarPlay, AirPlay, and Personal Hotspots. Enabling custom leak protection routes this traffic through the VPN but may affect how these features function. More info"; +"settings.application_settings.leak_protection.more_info" = "More info"; +"settings.application_settings.allow_local_network.title" = "Allow access to devices on local network"; +"settings.application_settings.allow_local_network.footer" = "Stay connected to local devices like printers or file servers while connected to the VPN. (Allow this only if you trust the people and devices on your network.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ blocks ads, trackers, and malware while you're connected to the VPN."; +"settings.application_settings.leak_protection.alert.title" = "Changes to the VPN Settings will take effect on the next connection"; "settings.content_blocker.title" = "Safari Content Blocker state"; "settings.content_blocker.state.title" = "Current state"; @@ -213,6 +224,12 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN Connection button. The VPN is currently connected"; "dashboard.accessibility.vpn.button.isOff" = "VPN Connection button. The VPN is currently disconnected"; +"dashboard.vpn.leakprotection.alert.title" = "Unsecured Wi-Fi detected"; +"dashboard.vpn.leakprotection.alert.message" = "To prevent data leaks, tap Disable Now to turn off “Allow access to devices on local network\" and automatically reconnect."; +"dashboard.vpn.leakprotection.alert.cta1" = "Disable Now"; +"dashboard.vpn.leakprotection.alert.cta2" = "Learn more"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignore"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -370,11 +387,15 @@ "rating.enjoy.subtitle" = "We hope our VPN product is meeting your expectations"; "rating.problems.question" = "What went wrong?"; "rating.problems.subtitle" = "Do you want to give feedback? We can help you to improve your experience using PIA"; -"rating.rate.question" = "How about an AppStore review?"; +"rating.review.question" = "How about an AppStore review?"; +"rating.rate.question" = "How about a rating on the AppStore?"; "rating.rate.subtitle" = "We appreciate you sharing your experience"; "rating.error.question" = "The connection couldn't be established"; "rating.error.subtitle" = "You can try selecting a different region or letting us know about it by opening a support ticket."; "rating.error.button.send" = "Send feedback"; +"rating.alert.button.notreally" = "Not Really"; +"rating.alert.button.nothanks" = "No, thanks."; +"rating.alert.button.oksure" = "Ok, sure!"; // CALLING CARDS "card.wireguard.title" = "Try WireGuard® today!"; diff --git a/PIA VPN/es-MX.lproj/Localizable.strings b/PIA VPN/es-MX.lproj/Localizable.strings index 472abb401..f55860ac2 100644 --- a/PIA VPN/es-MX.lproj/Localizable.strings +++ b/PIA VPN/es-MX.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Plan mensual"; "account.subscriptions.trial" = "Plan de prueba"; "account.unauthorized" = "Se ha producido un error. Intenta volver a iniciar sesión."; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Eliminar cuenta"; +"account.delete.alert.title" = "¿Estás seguro?"; +"account.delete.alert.message" = "Eliminar tu cuenta de PIA es permanente e irreversible. Una vez hecho, no podrás recuperar tus credenciales de PIA. Ten en cuenta que esta acción solo elimina tu cuenta de PIA de nuestra base de datos, pero NO cancela tu suscripción. Deberás ir a tu cuenta de Apple y cancelar allí la suscripción a Private Internet Access. De lo contrario, se te seguirá cobrando, aunque la cuenta de PIA ya no estará activa."; +"account.delete.alert.failureMessage" = "Algo ha fallado durante la eliminación de la cuenta. Inténtalo más tarde, por favor."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Esperamos que nuestro producto de VPN cumpla tus expectativas."; "rating.problems.question" = "¿Qué ha salido mal?"; "rating.problems.subtitle" = "¿Quieres darnos tu opinión? Podemos ayudarte a mejorar tu experiencia con PIA."; -"rating.rate.question" = "¿Qué tal si nos valoras en App Store?"; +"rating.review.question" = "¿Quieres escribir una reseña en el App Store?"; +"rating.rate.question" = "¿Quieres dar una puntuación en el App Store?"; "rating.rate.subtitle" = "Te agradecemos que compartas tu experiencia."; "rating.error.question" = "No se pudo establecer la conexión."; "rating.error.subtitle" = "Puedes probar a seleccionar otra región o abrir un ticket de asistencia para comentarnos el problema."; "rating.error.button.send" = "Enviar opinión"; +"rating.alert.button.notreally" = "Realmente no"; +"rating.alert.button.nothanks" = "No, gracias."; +"rating.alert.button.oksure" = "¡Claro que sí!"; // CALLING CARDS "card.wireguard.title" = "¡Prueba WireGuard® hoy!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Tu IP dedicada se activó correctamente. Estará disponible en tu lista de selección de región."; "dedicated.ip.message.expired.token" = "Tu token caducó. Genera uno nuevo en la página de tu cuenta en el sitio web."; "dedicated.ip.message.error.token" = "Tu token caducó. Genera uno nuevo en la página de tu cuenta en el sitio web."; +"dedicated.ip.message.error.retryafter" = "Demasiadas solicitudes de activación de token fallidas. Vuelve a intentarlo pasados %@ segundo(s)."; "dedicated.ip.message.token.willexpire" = "Tu IP dedicada caducará pronto. Obtén una nueva."; "dedicated.ip.message.token.willexpire.link" = "Obtén una nueva"; "dedicated.ip.message.ip.updated" = "Tu IP dedicada perfil se actualizó."; diff --git a/PIA VPN/fr.lproj/Localizable.strings b/PIA VPN/fr.lproj/Localizable.strings index 287bf89b9..ad9c5c439 100644 --- a/PIA VPN/fr.lproj/Localizable.strings +++ b/PIA VPN/fr.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Forfait mensuel"; "account.subscriptions.trial" = "Forfait d'essai"; "account.unauthorized" = "Un problème est survenu. Veuillez réessayer de vous connecter"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Supprimer le compte"; +"account.delete.alert.title" = "Êtes-vous sûr ?"; +"account.delete.alert.message" = "La suppression de votre compte PIA est permanente et irréversible. Vous ne pourrez plus récupérer vos identifiants PIA après avoir effectué cette opération. Veuillez noter que cette opération supprime uniquement votre compte PIA de notre base de données, mais qu’elle ne supprime PAS votre abonnement. Vous devrez ensuite accéder à votre compte Apple et annuler l’abonnement à Private Internet Access à cet endroit. Vous risquez sinon de continuer à payer, même si votre compte PIA n’est plus actif."; +"account.delete.alert.failureMessage" = "Un problème est survenu lors de la suppression de votre compte. Veuillez réessayer plus tard."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Nous espérons que notre produit VPN répond à vos attentes"; "rating.problems.question" = "Quel était le problème ?"; "rating.problems.subtitle" = "Vous voulez nous envoyer un commentaire ? Nous pouvons vous aider à améliorer votre expérience sur PIA"; -"rating.rate.question" = "Et un avis sur l'App Store ?"; +"rating.review.question" = "Et un avis sur l'App Store ?"; +"rating.rate.question" = "Et une note sur l’App Store ?"; "rating.rate.subtitle" = "Nous apprécions que vous partagiez votre expérience"; "rating.error.question" = "La connexion n'a pas pu être établie"; "rating.error.subtitle" = "Vous pouvez essayer de sélectionner une autre région ou de nous en informer en ouvrant un ticket de support."; "rating.error.button.send" = "Envoyer un commentaire"; +"rating.alert.button.notreally" = "Non merci"; +"rating.alert.button.nothanks" = "Non, merci."; +"rating.alert.button.oksure" = "Bien sûr !"; // CALLING CARDS "card.wireguard.title" = "Essayez WireGuard® aujourd'hui !"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Votre adresse IP dédiée a été activée avec succès. Elle sera disponible dans votre liste de sélection de région."; "dedicated.ip.message.expired.token" = "Votre jeton est expiré. Veuillez en générer un nouveau à partir de la page de votre compte sur le site Web."; "dedicated.ip.message.error.token" = "Votre jeton est expiré. Veuillez en générer un nouveau à partir de la page de votre compte sur le site Web."; +"dedicated.ip.message.error.retryafter" = "Nombre trop élevé de demandes d’activation de jeton manquées. Veuillez réessayer dans %@ seconde(s)."; "dedicated.ip.message.token.willexpire" = "Votre adresse IP dédiée expirera bientôt. Obtenez-en une nouvelle"; "dedicated.ip.message.token.willexpire.link" = "Obtenez-en une nouvelle"; "dedicated.ip.message.ip.updated" = "Votre IP dédiée a été mise à jour"; diff --git a/PIA VPN/it.lproj/Localizable.strings b/PIA VPN/it.lproj/Localizable.strings index f74c2389e..fa8d23a0c 100644 --- a/PIA VPN/it.lproj/Localizable.strings +++ b/PIA VPN/it.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Piano mensile"; "account.subscriptions.trial" = "Piano di prova"; "account.unauthorized" = "Qualcosa è andato storto. Prova ad accedere di nuovo"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Elimina account"; +"account.delete.alert.title" = "Continuare?"; +"account.delete.alert.message" = "L’eliminazione del tuo account PIA è un’operazione definitiva e irreversibile, al termine della quale non potrai più recuperare le tue credenziali PIA. Tieni presente che questa azione elimina solo il tuo account PIA dal nostro database, NON l’abbonamento. Per cancellare l’abbonamento a Private Internet Access, dovrai accedere al tuo account Apple e cancellarlo da lì. Altrimenti continuerai a pagarlo anche se il tuo account PIA non sarà più attivo."; +"account.delete.alert.failureMessage" = "Qualcosa non ha funzionato durante l’eliminazione dell’account. Riprova più tardi."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Speriamo che il nostro prodotto VPN soddisfi le tue aspettative"; "rating.problems.question" = "Cosa non ha funzionato?"; "rating.problems.subtitle" = "Vuoi offrire il tuo feedback? Possiamo aiutarti a migliorare la tua esperienza d'uso di PIA"; -"rating.rate.question" = "Che ne pensi di condividere una recensione sull'AppStore?"; +"rating.review.question" = "Che ne pensi di condividere una recensione sull'AppStore?"; +"rating.rate.question" = "Che ne dici di una valutazione su App Store?"; "rating.rate.subtitle" = "Grazie per aver condiviso la tua esperienza"; "rating.error.question" = "Impossibile stabilire la connessione"; "rating.error.subtitle" = "Puoi provare a selezionare un altro paese o comunicarcelo aprendo un ticket di supporto."; "rating.error.button.send" = "Invia feedback"; +"rating.alert.button.notreally" = "No, grazie"; +"rating.alert.button.nothanks" = "No, grazie."; +"rating.alert.button.oksure" = "Certo!"; // CALLING CARDS "card.wireguard.title" = "Prova subito WireGuard®!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Il tuo IP dedicato è stato attivato. Sarà disponibile nell'elenco di selezione del paese."; "dedicated.ip.message.expired.token" = "Token scaduto. Generane uno nuovo dalla pagina del tuo account sul sito web."; "dedicated.ip.message.error.token" = "Token scaduto. Generane uno nuovo dalla pagina del tuo account sul sito web."; +"dedicated.ip.message.error.retryafter" = "Troppe richieste di attivazione token non riuscite. Riprova tra %@ secondo/i."; "dedicated.ip.message.token.willexpire" = "Il tuo IP dedicato scade a breve. Ottieni uno nuovo"; "dedicated.ip.message.token.willexpire.link" = "Ottieni uno nuovo"; "dedicated.ip.message.ip.updated" = "Il tuo IP dedicato è aggiornato"; diff --git a/PIA VPN/ja.lproj/Localizable.strings b/PIA VPN/ja.lproj/Localizable.strings index b328b549f..468ed7676 100644 --- a/PIA VPN/ja.lproj/Localizable.strings +++ b/PIA VPN/ja.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "月間プラン"; "account.subscriptions.trial" = "トライアルプラン"; "account.unauthorized" = "問題が発生しました。もう一度ログインしてみてください"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "アカウントを削除"; +"account.delete.alert.title" = "本当によろしいですか?"; +"account.delete.alert.message" = "PIA アカウントの削除は永久的な操作であり、元に戻すことはできません。この操作を実行した後に、お持ちのPIA資格情報を取得することはできません。この操作ではお持ちのPIAアカウントが削除されるだけで、ご契約が削除されるわけではないことにご留意ください。お持ちのAppleアカウントにアクセスし、そこでPrivate Internet Access契約をキャンセルしない限り、お持ちのPIAアカウントが無効になった後も請求が行われます。"; +"account.delete.alert.failureMessage" = "お持ちのアカウントを削除中に問題が発生しました。後でもう一度試してください。"; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "VPN製品にご満足いただいていることを願っています"; "rating.problems.question" = "どのような問題があったのでしょうか?"; "rating.problems.subtitle" = "フィードバックを送信しますか?PIAご利用にあたり、お客様のエクスペリエンス改善のお手伝いをいたします。"; -"rating.rate.question" = "AppStoreでレビューしていただけますか?"; +"rating.review.question" = "AppStoreでレビューしていただけますか?"; +"rating.rate.question" = "AppStoreで評価していただけますか?"; "rating.rate.subtitle" = "お客様のご感想を共有していただければありがたいです"; "rating.error.question" = "接続を確立できませんでした"; "rating.error.subtitle" = "別の地域を選択するか、サポートチケットを開いてそれについてお知らせください。"; "rating.error.button.send" = "フィードバックを送信"; +"rating.alert.button.notreally" = "いいえ"; +"rating.alert.button.nothanks" = "いいえ、結構です"; +"rating.alert.button.oksure" = "はい、評価します"; // CALLING CARDS "card.wireguard.title" = "WireGuard®を今すぐお試しください!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "専用IPが正常にアクティベートされました。地域選択リストで利用できるようになります。"; "dedicated.ip.message.expired.token" = "トークンの有効期限が切れています。ウェブサイトのアカウントページから新しいトークンを生成してください。"; "dedicated.ip.message.error.token" = "トークンの有効期限が切れています。ウェブサイトのアカウントページから新しいトークンを生成してください。"; +"dedicated.ip.message.error.retryafter" = "失敗したトークン有効化リクエストの数が多すぎます。%@秒後にもう一度お試しください。"; "dedicated.ip.message.token.willexpire" = "あなたの専用IPは間もなく期限が切れます。新しい専用IPを入手してください"; "dedicated.ip.message.token.willexpire.link" = "新しい専用IPを入手してください"; "dedicated.ip.message.ip.updated" = "あなたの専用IPが更新されました"; diff --git a/PIA VPN/ko.lproj/Localizable.strings b/PIA VPN/ko.lproj/Localizable.strings index 1aa9b6a92..c156af612 100644 --- a/PIA VPN/ko.lproj/Localizable.strings +++ b/PIA VPN/ko.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "월간 플랜"; "account.subscriptions.trial" = "체험 플랜"; "account.unauthorized" = "문제가 발생했습니다. 다시 로그인해 보세요"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "계정 삭제"; +"account.delete.alert.title" = "계속하시겠습니까?"; +"account.delete.alert.message" = "PIA 계정 삭제는 영구적이고 되돌릴 수 없습니다. 이 작업을 수행한 후에는 PIA 인증 정보를 복구할 수 없습니다. 이 작업은 본사 데이터베이스에서 사용자의 PIA 계정만 삭제합니다. 사용자의 구독은 삭제되지 않습니다. 따라서 자신의 Apple 계정으로 이동해서 Private Internet Access 구독을 취소해야 합니다. 그렇지 않으면 PIA를 더 이상 사용할 수 없더라도 요금이 부과됩니다."; +"account.delete.alert.failureMessage" = "계정을 삭제하는 중 문제가 발생했습니다. 나중에 다시 시도하십시오."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "저희 VPN 제품이 고객님의 기대에 부응하기를 바랍니다"; "rating.problems.question" = "어떤 문제가 있었습니까?"; "rating.problems.subtitle" = "피드백을 보내 주시겠습니까? 더 나은 PIA를 경험하실 수 있도록 도와 드리겠습니다"; -"rating.rate.question" = "App Store에 리뷰를 남겨 주시겠습니까?"; +"rating.review.question" = "App Store에 리뷰를 남겨 주시겠습니까?"; +"rating.rate.question" = "App Store에서 점수를 매겨주시겠어요?"; "rating.rate.subtitle" = "고객님의 경험을 공유해 주시면 감사하겠습니다"; "rating.error.question" = "연결을 수립할 수 없습니다"; "rating.error.subtitle" = "다른 지역을 선택해 보시거나 지원 티켓을 열어서 저희에게 알려 주세요."; "rating.error.button.send" = "피드백 보내기"; +"rating.alert.button.notreally" = "아니요"; +"rating.alert.button.nothanks" = "아니요."; +"rating.alert.button.oksure" = "네, 좋아요!"; // CALLING CARDS "card.wireguard.title" = "지금 WireGuard®를 사용해 보세요!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "회원님의 전용 IP가 성공적으로 활성화되었습니다. 지역 선택 목록에서 사용하실 수 있습니다."; "dedicated.ip.message.expired.token" = "회원님의 토큰은 만료되었습니다. 웹사이트의 계정 페이지에서 새로운 토큰을 생성하세요."; "dedicated.ip.message.error.token" = "회원님의 토큰은 만료되었습니다. 웹사이트의 계정 페이지에서 새로운 토큰을 생성하세요."; +"dedicated.ip.message.error.retryafter" = "토큰 활성화 요청이 너무 많이 실패했습니다. %@초 후 다시 시도하십시오."; "dedicated.ip.message.token.willexpire" = "회원님의 전용 IP가 곧 만료됩니다. 새로 획득하세요"; "dedicated.ip.message.token.willexpire.link" = "새로 획득하세요"; "dedicated.ip.message.ip.updated" = "회원님의 전용 IP가 업데이트되었습니다"; diff --git a/PIA VPN/nb.lproj/Localizable.strings b/PIA VPN/nb.lproj/Localizable.strings index ed4416b06..22237a276 100644 --- a/PIA VPN/nb.lproj/Localizable.strings +++ b/PIA VPN/nb.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Månedsplan"; "account.subscriptions.trial" = "Prøveplan"; "account.unauthorized" = "Noe gikk galt. Prøv å logge inn igjen."; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Slett konto"; +"account.delete.alert.title" = "Er du sikker?"; +"account.delete.alert.message" = "Sletting av PIA-kontoen er permanent og kan ikke reverseres. Du vil ikke kunne hente PIA-legitimasjonen etter at denne handlingen er utført. Vær oppmerksom på at denne handlingen bare sletter PIA-kontoen fra databasen, og sletter IKKE abonnementet. Du må gå til Apple-kontoen og kansellere abonnementet på Private Internet Access derfra. Ellers blir du fortsatt belastet, selv om PIA-kontoen ikke lenger er aktiv."; +"account.delete.alert.failureMessage" = "Noe gikk galt ved sletting av kontoen. Prøv på nytt senere."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Vi håper VPN-produktet svarer til forventningene dine"; "rating.problems.question" = "Hva gikk galt?"; "rating.problems.subtitle" = "Ønsker du å sende en tilbakemelding? Vi kan bidra til å forbedre opplevelsen din med PIA"; -"rating.rate.question" = "Hva med en anmeldelse på AppStore?"; +"rating.review.question" = "Hva med en anmeldelse på AppStore?"; +"rating.rate.question" = "Hva med en vurdering i AppStore?"; "rating.rate.subtitle" = "Vi setter pris på at du deler opplevelsen din"; "rating.error.question" = "Tilkoblingen kunne ikke opprettes"; "rating.error.subtitle" = "Du kan velge en annen region eller fortelle oss om det ved å åpne en støttesak."; "rating.error.button.send" = "Send tilbakemelding"; +"rating.alert.button.notreally" = "Ikke egentlig"; +"rating.alert.button.nothanks" = "Nei takk."; +"rating.alert.button.oksure" = "Klart det!"; // CALLING CARDS "card.wireguard.title" = "Prøv WireGuard® i dag!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Den dedikerte IP-en din ble aktivert. Den blir tilgjengelig i regionvalg-listen."; "dedicated.ip.message.expired.token" = "Tokenet er utløpt. Generer et nytt et fra kontosiden din på nettstedet."; "dedicated.ip.message.error.token" = "Tokenet er utløpt. Generer et nytt et fra kontosiden din på nettstedet."; +"dedicated.ip.message.error.retryafter" = "For mange mislykkede forespørsler om aktivering av token. Prøv på nytt etter %@ sekund(er)."; "dedicated.ip.message.token.willexpire" = "Den dedikerte IP-en din utløper snart. Skaff deg en ny en"; "dedicated.ip.message.token.willexpire.link" = "Skaff deg en ny en"; "dedicated.ip.message.ip.updated" = "Den dedikerte IP-en din ble oppdatert"; diff --git a/PIA VPN/nl.lproj/Localizable.strings b/PIA VPN/nl.lproj/Localizable.strings index 20d904003..f1f62738f 100644 --- a/PIA VPN/nl.lproj/Localizable.strings +++ b/PIA VPN/nl.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Maandelijks abonnement"; "account.subscriptions.trial" = "Proefabonnement"; "account.unauthorized" = "Er is iets fout gegaan. Probeer opnieuw in te loggen."; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Verwijder account"; +"account.delete.alert.title" = "Weet u het zeker?"; +"account.delete.alert.message" = "Het verwijderen van uw PIA-account is definitief en onomkeerbaar. U zult uw PIA-inloggegevens niet kunnen ophalen na het uitvoeren van deze actie. Houd ermee rekening dat deze actie alleen uw PIA-account verwijdert uit onze database, maar NIET uw abonnement. Hiervoor moet u naar uw Apple-account gaan en daar het Private Internet Access-abonnement annuleren. Anders worden u nog steeds kosten aangerekend, hoewel uw PIA-account niet langer actief is."; +"account.delete.alert.failureMessage" = "Er ging iets fout bij het verwijderen van uw account. Probeer het later nog een keer."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "We hopen dat ons VPN-product aan uw verwachtingen voldoet"; "rating.problems.question" = "Wat is er fout gegaan?"; "rating.problems.subtitle" = "Wilt u feedback geven? We kunnen u helpen met het verbeteren van uw ervaring met PIA."; -"rating.rate.question" = "Waarom laat u geen beoordeling achter in de AppStore?"; +"rating.review.question" = "Waarom laat u geen beoordeling achter in de AppStore?"; +"rating.rate.question" = "Wilt u een waardering geven in de App Store?"; "rating.rate.subtitle" = "We stellen het op prijs dat u uw ervaring deelt"; "rating.error.question" = "De verbinding kan niet tot stand worden gebracht"; "rating.error.subtitle" = "U kunt een andere regio selecteren of ons hiervan op de hoogte stellen door een supportticket te openen."; "rating.error.button.send" = "Feedback sturen"; +"rating.alert.button.notreally" = "Niet echt"; +"rating.alert.button.nothanks" = "Nee, bedankt."; +"rating.alert.button.oksure" = "Graag!"; // CALLING CARDS "card.wireguard.title" = "Probeer WireGuard® vandaag nog!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Uw dedicated IP is geactiveerd. Het is beschikbaar in uw regioselectielijst."; "dedicated.ip.message.expired.token" = "Uw code is verlopen. Genereer een nieuwe via uw accountpagina op de website."; "dedicated.ip.message.error.token" = "Uw code is verlopen. Genereer een nieuwe via uw accountpagina op de website."; +"dedicated.ip.message.error.retryafter" = "Teveel mislukte aanvragen voor het activeren van tokens. Probeer het opnieuw na %@ seconde(n)."; "dedicated.ip.message.token.willexpire" = "Uw dedicated IP verloopt binnenkort. Haal een nieuwe"; "dedicated.ip.message.token.willexpire.link" = "Haal een nieuwe"; "dedicated.ip.message.ip.updated" = "Uw dedicated IP is bijgewerkt"; diff --git a/PIA VPN/pl.lproj/Localizable.strings b/PIA VPN/pl.lproj/Localizable.strings index 0866f2e5a..fe3905363 100644 --- a/PIA VPN/pl.lproj/Localizable.strings +++ b/PIA VPN/pl.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Plan miesięczny"; "account.subscriptions.trial" = "Plan próbny"; "account.unauthorized" = "Coś poszło nie tak. zalogować się ponownie."; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Usuń konto"; +"account.delete.alert.title" = "Czy na pewno?"; +"account.delete.alert.message" = "Usunięcie Twojego konta PIA będzie trwałe i nieodwracalne. Po tej akcji nie będziesz mógł pobrać swoich danych logowania PIA. Ta akcja powoduje tylko usunięcie konta PIA z naszej bazy danych, ale NIE powoduje usunięcia subskrypcji. W tym celu należy przejść do swojego konta Apple i anulować tam subskrypcję Private Internet Access. W przeciwnym razie nadal będą pobierane opłaty, mimo że konto PIA nie będzie już aktywne."; +"account.delete.alert.failureMessage" = "Coś poszło nie tak podczas usuwania konta, spróbuj jeszcze raz później."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Mamy nadzieję, że nasz produkt VPN spełnia Twoje oczekiwania"; "rating.problems.question" = "Co poszło źle?"; "rating.problems.subtitle" = "Czy zechcesz podzielić się z nami swoją opinią? Może pomóc i sprawić, ze będzie Ci się lepiej korzystało z PIA"; -"rating.rate.question" = "A może napiszesz recenzję w App Store?"; +"rating.review.question" = "A może napiszesz recenzję w App Store?"; +"rating.rate.question" = "A może ocenisz nas w sklepie App Store?"; "rating.rate.subtitle" = "Będziemy wdzięczni za opowiedzenie o swoich doświadczeniach"; "rating.error.question" = "Nie udało się nawiązać połączenia"; "rating.error.subtitle" = "Możesz spróbować wybrać inny region lub powiadomić nas o tym, wypełniając zgłoszenie do pomocy technicznej."; "rating.error.button.send" = "Wyślij opinię"; +"rating.alert.button.notreally" = "Nie za bardzo"; +"rating.alert.button.nothanks" = "Nie, dziękuję."; +"rating.alert.button.oksure" = "OK, jasne!"; // CALLING CARDS "card.wireguard.title" = "Wypróbuj WireGuard® dzisiaj!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Twój dedykowany adres IP został aktywowany. Będzie dostępny na liście wyboru regionu."; "dedicated.ip.message.expired.token" = "Twój token stracił ważność. Wygeneruj nowy na stronie swojego konta w serwisie."; "dedicated.ip.message.error.token" = "Twój token stracił ważność. Wygeneruj nowy na stronie swojego konta w serwisie."; +"dedicated.ip.message.error.retryafter" = "Za dużo nieprawidłowych żądań aktywacji tokena. Spróbuj ponownie za %@ s."; "dedicated.ip.message.token.willexpire" = "Twój dedykowany adres IP wkrótce wygasa. Kup nowy adres."; "dedicated.ip.message.token.willexpire.link" = "Kup nowy adres."; "dedicated.ip.message.ip.updated" = "Zaktualizowano Twój dedykowany adres IP"; diff --git a/PIA VPN/pt-BR.lproj/Localizable.strings b/PIA VPN/pt-BR.lproj/Localizable.strings index fc79a486e..470b23c15 100644 --- a/PIA VPN/pt-BR.lproj/Localizable.strings +++ b/PIA VPN/pt-BR.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Plano mensal"; "account.subscriptions.trial" = "Plano de avaliação"; "account.unauthorized" = "Ocorreu um erro. Tente fazer login novamente."; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Excluir conta"; +"account.delete.alert.title" = "Tem certeza?"; +"account.delete.alert.message" = "A exclusão da sua conta da PIA é permanente e irreversível. Você não poderá recuperar suas credenciais da PIA após executar essa ação. Observe que essa ação exclui apenas sua conta da PIA do nosso banco de dados, mas NÃO exclui sua assinatura. Você precisará ir até sua conta da Apple e cancelar a assinatura da Private Internet Access de lá. Caso contrário, você ainda será cobrado, mesmo que sua conta da PIA não esteja mais ativa."; +"account.delete.alert.failureMessage" = "Algo deu errado ao excluir sua conta. Tente de novo mais tarde."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Esperamos que nosso produto de VPN atenda às suas expectativas"; "rating.problems.question" = "O que aconteceu de errado?"; "rating.problems.subtitle" = "Deseja dar seu feedback? Podemos ajudá-lo a melhorar sua experiência com o uso da PIA"; -"rating.rate.question" = "Que tal uma avaliação na App Store?"; +"rating.review.question" = "Que tal uma avaliação na App Store?"; +"rating.rate.question" = "Que tal uma classificação na App Store?"; "rating.rate.subtitle" = "Agradecemos o compartilhamento da sua experiência"; "rating.error.question" = "Não foi possível estabelecer a conexão"; "rating.error.subtitle" = "Você pode tentar selecionar uma região diferente ou nos contar o que aconteceu abrindo um tíquete de suporte."; "rating.error.button.send" = "Enviar feedback"; +"rating.alert.button.notreally" = "Não, obrigado"; +"rating.alert.button.nothanks" = "Não, obrigado."; +"rating.alert.button.oksure" = "Sim, claro!"; // CALLING CARDS "card.wireguard.title" = "Experimente o WireGuard® hoje mesmo!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "O seu IP dedicado foi ativado com sucesso. Ele estará disponível em sua lista de seleção de regiões."; "dedicated.ip.message.expired.token" = "Seu token expirou. Gere um novo na página da sua conta no site."; "dedicated.ip.message.error.token" = "Seu token expirou. Gere um novo na página da sua conta no site."; +"dedicated.ip.message.error.retryafter" = "Muitas solicitações de ativação de token malsucedidas. Tente novamente após %@ segundo(s)."; "dedicated.ip.message.token.willexpire" = "O seu IP dedicado irá expirar em breve. Obtenha um novo"; "dedicated.ip.message.token.willexpire.link" = "Obtenha um novo"; "dedicated.ip.message.ip.updated" = "O seu IP dedicado foi atualizado"; diff --git a/PIA VPN/ru.lproj/Localizable.strings b/PIA VPN/ru.lproj/Localizable.strings index 0a0df92ab..f81c82060 100644 --- a/PIA VPN/ru.lproj/Localizable.strings +++ b/PIA VPN/ru.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "Месячный план"; "account.subscriptions.trial" = "Пробный план"; "account.unauthorized" = "Что-то пошло не так. Попробуйте выполнить вход еще раз"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "Удалить аккаунт"; +"account.delete.alert.title" = "Вы уверены?"; +"account.delete.alert.message" = "Удаление аккаунта PIA окончательно и необратимо. Выполнив это действие, вы не сможете восстановить свои учетные данные PIA. Обратите внимание, что это действие приведет только к удалению аккаунта из нашей базы данных, Но НЕ к удалению подписки. Вам потребуется перейти в аккаунт Apple и отменить подписку на Private Internet Access. В противном случае плата будет начисляться даже в отсутствие активного аккаунта PIA."; +"account.delete.alert.failureMessage" = "При удалении аккаунта что-то пошло не так. Повторите попытку позже."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "Надеемся, что наш продукт VPN соответствует вашим ожиданиям"; "rating.problems.question" = "Что пошло не так?"; "rating.problems.subtitle" = "Хотите дать отзыв? Мы сделаем все возможное, чтобы ваши впечатления от PIA стали лучше"; -"rating.rate.question" = "Как насчет отзыва в AppStore?"; +"rating.review.question" = "Как насчет отзыва в App Store?"; +"rating.rate.question" = "Как насчет оценки в App Store?"; "rating.rate.subtitle" = "Благодарим за согласие поделиться своим опытом"; "rating.error.question" = "Не удалось установить соединение"; "rating.error.subtitle" = "Попробуйте выбрать другой регион или расскажите нам, что произошло, открыв заявку в службу поддержки."; "rating.error.button.send" = "Отправить отзыв"; +"rating.alert.button.notreally" = "Не особо"; +"rating.alert.button.nothanks" = "Спасибо, не надо."; +"rating.alert.button.oksure" = "С удовольствием!"; // CALLING CARDS "card.wireguard.title" = "Опробуйте WireGuard® сегодня!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "Ваш выделенный IP-адрес успешно активирован. Теперь он будет доступен в списке выбора региона."; "dedicated.ip.message.expired.token" = "Срок действия вашего токена истек. Сгенерируйте новый на странице своей учетной записи на нашем веб-сайте."; "dedicated.ip.message.error.token" = "Срок действия вашего токена истек. Сгенерируйте новый на странице своей учетной записи на нашем веб-сайте."; +"dedicated.ip.message.error.retryafter" = "Слишком много неудачных запросов активации токена. Повторите попытку через %@ сек."; "dedicated.ip.message.token.willexpire" = "Действие вашего выделенного IP-адреса скоро заканчивается. Получите новый адрес"; "dedicated.ip.message.token.willexpire.link" = "Получите новый адрес"; "dedicated.ip.message.ip.updated" = "Ваш выделенный IP-адрес обновлен"; diff --git a/PIA VPN/th.lproj/Localizable.strings b/PIA VPN/th.lproj/Localizable.strings index 266b7c0b6..5ecc26642 100644 --- a/PIA VPN/th.lproj/Localizable.strings +++ b/PIA VPN/th.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "แผนรายเดือน"; "account.subscriptions.trial" = "แผนทดลองใช้"; "account.unauthorized" = "บางอย่างผิดปกติ โปรดลองลงชื่อเข้าใช้อีกครั้ง"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "ลบบัญชี"; +"account.delete.alert.title" = "คุณแน่ใจหรือไม่"; +"account.delete.alert.message" = "การลบบัญชี PIA ของคุณจะเป็นการถาวรและไม่สามารถย้อนกลับได้ คุณจะไม่สามารถดึงข้อมูลประจำตัว PIA ของคุณหลังจากดำเนินการนี้ โปรดทราบว่าการดำเนินการนี้จะลบบัญชี PIA ของคุณจากฐานข้อมูลของเราเท่านั้น แต่จะไม่ลบการสมัครใช้งานของคุณ คุณจะต้องไปที่บัญชี Apple ของคุณแล้วยกเลิกการสมัครใช้งาน Private Internet Access จากที่นั่น ไม่เช่นนั้นคุณจะยังคงถูกเรียกเก็บค่าบริการแม้ว่าบัญชี PIA ของคุณจะไม่เปิดใช้งานอีกต่อไป"; +"account.delete.alert.failureMessage" = "เกิดข้อผิดพลาดขณะลบบัญชีของคุณ โปรดลองอีกครั้งในภายหลัง"; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "เราหวังว่าผลิตภัณฑ์ VPN ของเราจะตอบโจทย์ความต้องการของคุณ"; "rating.problems.question" = "มีอะไรผิดปกติเหรอ"; "rating.problems.subtitle" = "คุณอยากที่จะให้ข้อเสนอแนะไหม เราช่วยปรับปรุงประสบการณ์การใช้งาน PIA ของคุณได้"; -"rating.rate.question" = "รีวิว AppStore เป็นอย่างไร?"; +"rating.review.question" = "รีวิวเราใน AppStore หน่อยไหม?"; +"rating.rate.question" = "ให้คะแนนเราใน AppStore หน่อยไหม?"; "rating.rate.subtitle" = "ขอขอบคุณที่คุณช่วยแชร์ประสบการณ์"; "rating.error.question" = "ไม่สามารถสร้างการเชื่อมต่อได้"; "rating.error.subtitle" = "คุณสามารถลองเลือกภูมิภาคอื่นหรือแจ้งให้เราทราบโดยเปิดทิกเก็ตความช่วยเหลือ"; "rating.error.button.send" = "ส่งความคิดเห็น"; +"rating.alert.button.notreally" = "ไม่ดีกว่า"; +"rating.alert.button.nothanks" = "ไม่ล่ะ ขอบคุณ."; +"rating.alert.button.oksure" = "ได้เลย!"; // CALLING CARDS "card.wireguard.title" = "ลอง WireGuard® ได้แล้ววันนี้!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "IP เฉพาะของคุณเปิดใช้งานสำเร็จแล้ว จะมีอยู่ในรายการการเลือกภูมิภาคของคุณ"; "dedicated.ip.message.expired.token" = "โทเค็นของคุณหมดอายุแล้ว โปรดสร้างใหม่จากหน้าบัญชีของคุณบนเว็บไซต์"; "dedicated.ip.message.error.token" = "โทเค็นของคุณหมดอายุแล้ว โปรดสร้างใหม่จากหน้าบัญชีของคุณบนเว็บไซต์"; +"dedicated.ip.message.error.retryafter" = "มีคำขอเปิดใช้งานโทเค็นที่ล้มเหลวมากเกินไป โปรดลองอีกครั้งหลังเวลาผ่านไป %@ วินาที"; "dedicated.ip.message.token.willexpire" = "IP เฉพาะของคุณจะหมดอายุในไม่ช้า รับใหม่"; "dedicated.ip.message.token.willexpire.link" = "รับใหม่"; "dedicated.ip.message.ip.updated" = "อัปเดต IP เฉพาะของคุณแล้ว"; diff --git a/PIA VPN/zh-Hans.lproj/Localizable.strings b/PIA VPN/zh-Hans.lproj/Localizable.strings index af9cca3a7..93625b161 100644 --- a/PIA VPN/zh-Hans.lproj/Localizable.strings +++ b/PIA VPN/zh-Hans.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "包月套餐"; "account.subscriptions.trial" = "试用套餐"; "account.unauthorized" = "出现问题。请尝试重新登录"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "删除账户"; +"account.delete.alert.title" = "确定?"; +"account.delete.alert.message" = "删除 PIA 账户是永久的、不可挽回的。执行此操作后,您将无法检索自己的 PIA 登录信息。请注意,此操作只会从我们的数据库中删除 PIA 帐户,但不会删除您的订阅。您需要进入苹果账户,从那里取消 Private Internet Access。否则,即使您的 PIA 账户不再有效,您仍将被收取费用。"; +"account.delete.alert.failureMessage" = "删除账户出错,请稍后再试。"; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "希望我们的 VPN 产品符合您的期望"; "rating.problems.question" = "出什么问题了?"; "rating.problems.subtitle" = "您希望提供反馈意见吗?我们可以帮助您改善使用 PIA 的体验"; -"rating.rate.question" = "在 AppStore 上发表评论如何?"; +"rating.review.question" = "在 AppStore 上发表评论如何?"; +"rating.rate.question" = "在 AppStore 上评级如何?"; "rating.rate.subtitle" = "感谢您分享使用体验"; "rating.error.question" = "无法建立连接"; "rating.error.subtitle" = "您可以尝试选择其他地区,也可以提交支持请求来告知我们。"; "rating.error.button.send" = "发送反馈"; +"rating.alert.button.notreally" = "不太想"; +"rating.alert.button.nothanks" = "不用,谢谢."; +"rating.alert.button.oksure" = "好的,没问题!"; // CALLING CARDS "card.wireguard.title" = "立即试用 WireGuard®!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "您的专用 IP 已成功激活。您可以在“地区”选择列表中使用该专用 IP。"; "dedicated.ip.message.expired.token" = "您的令牌已过期。请从网站的“账户”页面中生成一个新的令牌。"; "dedicated.ip.message.error.token" = "您的令牌已过期。请从网站的“账户”页面中生成一个新的令牌。"; +"dedicated.ip.message.error.retryafter" = "令牌激活请求失败次数太多。请在 %@ 秒后重试。"; "dedicated.ip.message.token.willexpire" = "您的专用 IP 即将过期。请获取一个新的专用 IP"; "dedicated.ip.message.token.willexpire.link" = "请获取一个新的专用 IP"; "dedicated.ip.message.ip.updated" = "您的专用 IP 已更新"; diff --git a/PIA VPN/zh-Hant.lproj/Localizable.strings b/PIA VPN/zh-Hant.lproj/Localizable.strings index 80fe87c0d..98795d30e 100644 --- a/PIA VPN/zh-Hant.lproj/Localizable.strings +++ b/PIA VPN/zh-Hant.lproj/Localizable.strings @@ -109,10 +109,12 @@ "account.subscriptions.monthly" = "每月方案"; "account.subscriptions.trial" = "試用方案"; "account.unauthorized" = "發生錯誤,請稍後再嘗試登入。"; -"account.delete" = "Delete Account"; -"account.delete.alert.title" = "Are you sure?"; -"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; -"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.delete" = "刪除帳戶"; +"account.delete.alert.title" = "是否確定?"; +"account.delete.alert.message" = "PIA 帳戶一經刪除即無法還原。一旦執行這個動作,您會無法取回 PIA 憑證。請注意,這個動作只會在資料庫中刪除您的 PIA 帳戶,不會刪除訂購計劃。您必須前往 Apple 帳戶,在那裡取消 Private Internet Access 訂購計劃,否則就算 PIA 帳戶已經停用,您還是會遭到扣款。"; +"account.delete.alert.failureMessage" = "刪除帳戶時發生錯誤,請再試一次。"; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; // SETTINGS @@ -370,11 +372,15 @@ "rating.enjoy.subtitle" = "我們希望我們的 VPN 產品符合您的期望"; "rating.problems.question" = "發生了什麼錯誤?"; "rating.problems.subtitle" = "您想提供反饋嗎?我們可以幫助您改善使用 PIA 的體驗"; -"rating.rate.question" = "可否為我們在 AppStore 評價?"; +"rating.review.question" = "可否為我們在 AppStore 評價?"; +"rating.rate.question" = "幫我們在 App Store 上評分好嗎?"; "rating.rate.subtitle" = "我們非常感謝您分享您的經驗"; "rating.error.question" = "無法建立連線"; "rating.error.subtitle" = "您可嘗試選擇不同區域,或開立支援單來通知我們。"; "rating.error.button.send" = "發送反饋"; +"rating.alert.button.notreally" = "不要"; +"rating.alert.button.nothanks" = "不了,謝謝."; +"rating.alert.button.oksure" = "沒問題!"; // CALLING CARDS "card.wireguard.title" = "立即體驗 WireGuard®!"; @@ -395,6 +401,7 @@ "dedicated.ip.message.valid.token" = "您的專屬 IP 已成功啟用。它將會出現在區域選擇清單中。"; "dedicated.ip.message.expired.token" = "您的權杖已到期。請到網站的帳戶頁面產生新權杖。"; "dedicated.ip.message.error.token" = "您的權杖已到期。請到網站的帳戶頁面產生新權杖。"; +"dedicated.ip.message.error.retryafter" = "權杖啟用要求失敗次數太多。請在 %@ 秒後再試一次。"; "dedicated.ip.message.token.willexpire" = "您的專屬 IP 即將到期。取得新權杖"; "dedicated.ip.message.token.willexpire.link" = "取得新權杖"; "dedicated.ip.message.ip.updated" = "您的專屬 IP 已更新"; diff --git a/PIAWidget/Data/Cache/WidgetPersistenceDatasource.swift b/PIAWidget/Data/Cache/WidgetPersistenceDatasource.swift new file mode 100644 index 000000000..5e1bdd1ec --- /dev/null +++ b/PIAWidget/Data/Cache/WidgetPersistenceDatasource.swift @@ -0,0 +1,17 @@ +// +// WidgetPersistenceDatasource.swift +// PIAWidgetExtension +// +// Created by Juan Docal on 2022-09-28. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation + +internal protocol WidgetPersistenceDatasource { + func getIsVPNConnected() -> Bool + func getIsTrustedNetwork() -> Bool + func getVpnProtocol() -> String + func getVpnPort() -> String + func getVpnSocket() -> String +} diff --git a/PIAWidget/Data/Cache/WidgetUserDefaultsDatasource.swift b/PIAWidget/Data/Cache/WidgetUserDefaultsDatasource.swift new file mode 100644 index 000000000..0c25e32c0 --- /dev/null +++ b/PIAWidget/Data/Cache/WidgetUserDefaultsDatasource.swift @@ -0,0 +1,71 @@ +// +// WidgetUserDefaultsDatasource.swift +// PIA VPN +// +// Created by Jose Blaya on 25/09/2020. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// 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 Foundation + +internal class WidgetUserDefaultsDatasource: WidgetPersistenceDatasource { + + private let appGroup = "group.com.privateinternetaccess" + + // MARK: WidgetPersistenceDatasource + + func getIsVPNConnected() -> Bool { + var connected = false + if let sharedDefaults = UserDefaults(suiteName: appGroup), + let status = sharedDefaults.string(forKey: "vpn.status") { + if status == "connected" { + connected = true + } + } + return connected + } + + func getIsTrustedNetwork() -> Bool { + if let sharedDefaults = UserDefaults(suiteName: appGroup) { + return sharedDefaults.bool(forKey: "vpn.widget.trusted.network") + } + return false + } + + func getVpnProtocol() -> String { + if let sharedDefaults = UserDefaults(suiteName: appGroup), + let value = sharedDefaults.string(forKey: "vpn.widget.protocol") { + return value + } + return "--" + } + + func getVpnPort() -> String { + if let sharedDefaults = UserDefaults(suiteName: appGroup), + let value = sharedDefaults.string(forKey: "vpn.widget.port") { + return value + } + return "--" + } + + func getVpnSocket() -> String { + if let sharedDefaults = UserDefaults(suiteName: appGroup), + let value = sharedDefaults.string(forKey: "vpn.widget.socket") { + return value + } + return "--" + } +} diff --git a/PIAWidget/Data/Model/WidgetInformation.swift b/PIAWidget/Data/Model/WidgetInformation.swift new file mode 100644 index 000000000..da8c874f3 --- /dev/null +++ b/PIAWidget/Data/Model/WidgetInformation.swift @@ -0,0 +1,31 @@ +// +// WidgetInformation.swift +// PIA VPN +// +// Created by Jose Blaya on 24/09/2020. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// 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 Foundation +import WidgetKit + +struct WidgetInformation: Codable, TimelineEntry { + var date: Date = Date() + let connected: Bool + let vpnProtocol: String + let vpnPort: String + let vpnSocket: String +} diff --git a/PIAWidget/Domain/UI/PIACircleVpnButton.swift b/PIAWidget/Domain/UI/PIACircleVpnButton.swift new file mode 100644 index 000000000..75a08e9d9 --- /dev/null +++ b/PIAWidget/Domain/UI/PIACircleVpnButton.swift @@ -0,0 +1,44 @@ +// +// PIACircleVpnButton.swift +// PIAWidgetExtension +// +// Created by Juan Docal on 2022-09-28. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import SwiftUI + +internal struct PIACircleVpnButton: View { + + internal let color: Color + + private let buttonSize: CGFloat = 40.0 + private let strokeWidth: CGFloat = 8.0 + + @State private var outerCircle: CGFloat + @State private var innerCircle: CGFloat + + init(color: Color) { + self.color = color + self.outerCircle = buttonSize + self.innerCircle = buttonSize - strokeWidth + } + + var body: some View { + return Circle() + .strokeBorder(Color("BorderColor"), lineWidth: strokeWidth * 0.75) + .background(Circle() + .strokeBorder(color, lineWidth: strokeWidth) + .background(Image("vpn-button") + .resizable() + .renderingMode(.template) + .foregroundColor(color) + .aspectRatio(contentMode: .fit) + .frame(width: buttonSize, height: buttonSize) + .padding(0.0) + ) + ) + .padding(buttonSize / 2.0) + } +} diff --git a/PIAWidget/Domain/UI/PIAIconView.swift b/PIAWidget/Domain/UI/PIAIconView.swift new file mode 100644 index 000000000..22c18fabe --- /dev/null +++ b/PIAWidget/Domain/UI/PIAIconView.swift @@ -0,0 +1,31 @@ +// +// PIAIconView.swift +// PIAWidgetExtension +// +// Created by Juan Docal on 2022-09-28. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import SwiftUI + +internal struct PIAIconView: View { + + private let iconRotation: CGFloat = -35.0 + private let iconSize: CGFloat + private let padding: CGFloat + + init(iconSize: CGFloat, padding: CGFloat) { + self.iconSize = iconSize + self.padding = padding + } + + var body: some View { + return Image("robot") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: iconSize, height: iconSize, alignment: .center) + .rotationEffect(Angle(degrees: iconRotation)) + .padding(padding) + } +} diff --git a/PIAWidget/Domain/UI/PIAWidgetView.swift b/PIAWidget/Domain/UI/PIAWidgetView.swift new file mode 100644 index 000000000..d1f5b4f04 --- /dev/null +++ b/PIAWidget/Domain/UI/PIAWidgetView.swift @@ -0,0 +1,52 @@ +// +// PIAWidgetView.swift +// PIAWidgetExtension +// +// Created by Juan Docal on 2022-09-28. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import SwiftUI + +struct PIAWidgetView : View { + + @Environment(\.widgetFamily) var widgetFamily + + let entry: PIAWidgetProvider.Entry + let widgetPersistenceDatasource: WidgetPersistenceDatasource + + init( + entry: PIAWidgetProvider.Entry, + widgetPersistenceDatasource: WidgetPersistenceDatasource + ) { + self.entry = entry + self.widgetPersistenceDatasource = widgetPersistenceDatasource + } + + var body: some View { + ZStack(alignment: .bottomTrailing) { + let targetIconSize = widgetFamily == .systemMedium ? 100.0 : 50.0 + let targetPadding = widgetFamily == .systemMedium ? -25.0 : -9.0 + PIAIconView(iconSize: targetIconSize, padding: targetPadding) + HStack { + if widgetPersistenceDatasource.getIsTrustedNetwork() { + PIACircleVpnButton(color: Color("TrustedNetworkColor")) + } else { + let targetColor = widgetPersistenceDatasource.getIsVPNConnected() ? "AccentColor" : "RedColor" + PIACircleVpnButton(color: Color(targetColor)) + } + + if widgetFamily == .systemMedium { + PIAWidgetVpnDetailsView( + vpnProtocol: widgetPersistenceDatasource.getVpnProtocol(), + port: widgetPersistenceDatasource.getVpnPort(), + socket: widgetPersistenceDatasource.getVpnSocket() + ) + } + } + + }.widgetURL(URL(string: widgetFamily == .systemMedium ? "piavpn:view" : "piavpn:connect")) + .background(Color("WidgetBackground")) + } +} diff --git a/PIAWidget/Domain/UI/PIAWidgetVpnDetailsView.swift b/PIAWidget/Domain/UI/PIAWidgetVpnDetailsView.swift new file mode 100644 index 000000000..08404b645 --- /dev/null +++ b/PIAWidget/Domain/UI/PIAWidgetVpnDetailsView.swift @@ -0,0 +1,33 @@ +// +// PIAWidgetVpnDetailsView.swift +// PIAWidgetExtension +// +// Created by Juan Docal on 2022-09-28. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import SwiftUI + +internal struct PIAWidgetVpnDetailsView: View { + + private let viewTrailingPadding: CGFloat = 40.0 + + internal let vpnProtocol: String + internal let port: String + internal let socket: String + + init(vpnProtocol: String, port: String, socket: String) { + self.vpnProtocol = vpnProtocol + self.port = port + self.socket = socket + } + + var body: some View { + return VStack { + PIAWidgetVpnDetaislRow(iconName: "icon-protocol", text: vpnProtocol) + PIAWidgetVpnDetaislRow(iconName: "icon-port", text: port) + PIAWidgetVpnDetaislRow(iconName: "icon-socket", text: socket) + }.padding(.trailing, viewTrailingPadding) + } +} diff --git a/PIAWidget/Domain/UI/PIAWidgetVpnDetaislRow.swift b/PIAWidget/Domain/UI/PIAWidgetVpnDetaislRow.swift new file mode 100644 index 000000000..4026469e0 --- /dev/null +++ b/PIAWidget/Domain/UI/PIAWidgetVpnDetaislRow.swift @@ -0,0 +1,38 @@ +// +// PIAWidgetVpnDetaislRow.swift +// PIAWidgetExtension +// +// Created by Juan Docal on 2022-09-29. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import SwiftUI + +internal struct PIAWidgetVpnDetaislRow: View { + + private let fontSize: CGFloat = 14.0 + private let iconsSize: CGFloat = 25.0 + private let rowSpacing: CGFloat = 0.0 + + internal let iconName: String + internal let text: String + + init(iconName: String, text: String) { + self.iconName = iconName + self.text = text + } + + var body: some View { + HStack(alignment: .center, spacing: rowSpacing) { + Image(iconName) + .resizable() + .frame(width: iconsSize, height: iconsSize, alignment: .leading) + Spacer() + Text(text) + .font(.system(size: fontSize)) + .foregroundColor(Color("FontColor")) + .frame(maxWidth: .infinity, alignment: .leading) + } + } +} diff --git a/PIAWidget/Domain/Widget/PIAWidget.swift b/PIAWidget/Domain/Widget/PIAWidget.swift new file mode 100644 index 000000000..35268132b --- /dev/null +++ b/PIAWidget/Domain/Widget/PIAWidget.swift @@ -0,0 +1,48 @@ +// +// PIAWidget.swift +// PIA VPN +// +// Created by Jose Blaya on 24/09/2020. +// Copyright © 2020 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access iOS Client. +// +// 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 WidgetKit +import SwiftUI +import Intents + +@main +struct PIAWidget: Widget { + + let kind: String = "PIAWidget" + let displayName: String = "PIA VPN" + + var body: some WidgetConfiguration { + let widgetPersistenceDatasource = WidgetUserDefaultsDatasource() + return StaticConfiguration( + kind: kind, + provider: PIAWidgetProvider( + widgetPersistenceDatasource: widgetPersistenceDatasource + ) + ) { entry in + PIAWidgetView( + entry: entry, + widgetPersistenceDatasource: widgetPersistenceDatasource + ) + } + .configurationDisplayName(displayName) + .supportedFamilies([.systemSmall, .systemMedium]) + } +} diff --git a/PIAWidget/Domain/Widget/PIAWidgetPreview.swift b/PIAWidget/Domain/Widget/PIAWidgetPreview.swift new file mode 100644 index 000000000..3bfd659c6 --- /dev/null +++ b/PIAWidget/Domain/Widget/PIAWidgetPreview.swift @@ -0,0 +1,38 @@ +// +// PIAWidgetPreview.swift +// PIAWidgetExtension +// +// Created by Juan Docal on 2022-09-28. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import SwiftUI +import WidgetKit + +struct PIAWidgetPreview: PreviewProvider { + + static var previews: some View { + let widgetPersistenceDatasource = WidgetUserDefaultsDatasource() + PIAWidgetView( + entry: WidgetInformation( + date: Date(), + connected: true, + vpnProtocol: "IKEv2", + vpnPort: "500", + vpnSocket: "UDP" + ), + widgetPersistenceDatasource: widgetPersistenceDatasource + ).previewContext(WidgetPreviewContext(family: .systemSmall)) + PIAWidgetView( + entry: WidgetInformation( + date: Date(), + connected: false, + vpnProtocol: "WireGuard", + vpnPort: "1443", + vpnSocket: "UDP" + ), + widgetPersistenceDatasource: widgetPersistenceDatasource + ).previewContext(WidgetPreviewContext(family: .systemMedium)) + } +} diff --git a/PIAWidget/Domain/Widget/PIAWidgetProvider.swift b/PIAWidget/Domain/Widget/PIAWidgetProvider.swift new file mode 100644 index 000000000..269b10bd2 --- /dev/null +++ b/PIAWidget/Domain/Widget/PIAWidgetProvider.swift @@ -0,0 +1,73 @@ +// +// PIAWidgetProvider.swift +// PIAWidgetExtension +// +// Created by Juan Docal on 2022-09-28. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation +import WidgetKit + +struct PIAWidgetProvider: TimelineProvider { + + let widgetPersistenceDatasource: WidgetPersistenceDatasource + + init(widgetPersistenceDatasource: WidgetPersistenceDatasource) { + self.widgetPersistenceDatasource = widgetPersistenceDatasource + } + + func placeholder(in context: Context) -> WidgetInformation { + WidgetInformation( + date: Date(), + connected: false, + vpnProtocol: "IPSec (IKEv2)", + vpnPort: "500", + vpnSocket: "UDP" + ) + } + + func getSnapshot( + in context: Context, + completion: @escaping (WidgetInformation) -> () + ) { + let entry: WidgetInformation + entry = WidgetInformation( + date: Date(), + connected: widgetPersistenceDatasource.getIsVPNConnected(), + vpnProtocol: widgetPersistenceDatasource.getVpnProtocol(), + vpnPort: widgetPersistenceDatasource.getVpnPort(), + vpnSocket: widgetPersistenceDatasource.getVpnSocket() + ) + completion(entry) + } + + func getTimeline( + in context: Context, + completion: @escaping (Timeline) -> () + ) { + var entries: [WidgetInformation] = [] + + // Generate a timeline consisting of five entries an hour apart, + // starting from the current date. + let currentDate = Date() + for hourOffset in 0 ..< 5 { + let entryDate = Calendar.current.date( + byAdding: .hour, + value: hourOffset, + to: currentDate + )! + let entry = WidgetInformation( + date: entryDate, + connected: widgetPersistenceDatasource.getIsVPNConnected(), + vpnProtocol: widgetPersistenceDatasource.getVpnProtocol(), + vpnPort: widgetPersistenceDatasource.getVpnPort(), + vpnSocket: widgetPersistenceDatasource.getVpnSocket() + ) + entries.append(entry) + } + + let timeline = Timeline(entries: entries, policy: .atEnd) + completion(timeline) + } +} diff --git a/Podfile b/Podfile deleted file mode 100644 index 3b145c576..000000000 --- a/Podfile +++ /dev/null @@ -1,155 +0,0 @@ -source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '12.0' -use_frameworks! - -install! 'cocoapods', - :deterministic_uuids => false, - :disable_input_output_paths => true #fix dSym issue https://github.com/CocoaPods/CocoaPods/issues/9185 - - -# ignore all warnings from all pods -inhibit_all_warnings! - -# Libraries - -$git_root = "https://github.com/pia-foss" -$gitlab_vpn_root = "git@gitlab.kape.com:pia-mobile/ios" -$gitlab_kn_root = "git@gitlab.kape.com:pia-mobile/shared" - -$library_pod = 'PIALibrary' -$library_repo = 'client-library-apple' -$library_gitlab_repo = 'client-library-apple.git' -$library_subspecs = [ - 'Library', - 'UI', - 'Mock', - 'VPN' -] - -$regions_repo = 'mobile-common-regions' -$accounts_repo = 'mobile-common-account' -$csi_repo = 'mobile-common-csi' - -$regions_gitlab_repo = 'regions.git' -$accounts_gitlab_repo = 'account.git' -$csi_gitlab_repo = 'csi.git' -$kpi_gitlab_repo = 'kpi.git' - -def library_by_path(root) - $library_subspecs.each { |name| - pod "#{$library_pod}/#{name}", :path => "#{root}/#{$library_repo}" - } -end - -def library_by_git(sha) - $library_subspecs.each { |name| - pod "#{$library_pod}/#{name}", :git => "#{$git_root}/#{$library_repo}", :commit => sha - } -end - -def library_by_gitlab_branch(branch) - $library_subspecs.each { |name| - pod "#{$library_pod}/#{name}", :git => "#{$gitlab_vpn_root}/#{$library_gitlab_repo}", :branch => branch - } -end - -def library_by_gitlab_by_git(sha) - $library_subspecs.each { |name| - pod "#{$library_pod}/#{name}", :git => "#{$gitlab_vpn_root}/#{$library_gitlab_repo}", :commit => sha - } -end - -def library_by_version(version) - $library_subspecs.each { |name| - pod "#{$library_pod}/#{name}", version - } -end - -# Pod groups - -def shared_main_pods - pod 'AlamofireImage' - - #pod "PIAAccountModule", :git => "#{$git_root}/#{$accounts_repo}" - pod "PIAAccountModule", :git => "#{$gitlab_kn_root}/#{$accounts_gitlab_repo}", :commit => '697fd4f' - #pod "PIARegionsModule", :git => "#{$git_root}/#{$regions_repo}" - pod "PIARegionsModule", :git => "#{$gitlab_kn_root}/#{$regions_gitlab_repo}", :branch => 'release/1.3.1' - #pod "PIACSIModule", :git => "#{$git_root}/#{$csi_repo}" - pod "PIACSIModule", :git => "#{$gitlab_kn_root}/#{$csi_gitlab_repo}", :commit => 'b62d1bab' - pod "PIAKPIModule", :git => "#{$gitlab_kn_root}/#{$kpi_gitlab_repo}", :commit => '31186b1d' - - #library_by_path('~/Repositories') - #library_by_git('') - #library_by_gitlab_branch('') - library_by_gitlab_by_git('6d34ee34') - #library_by_version('~> 1.1.3') -end - -def app_pods - shared_main_pods - pod 'TPKeyboardAvoiding' - pod 'SideMenu', '6.1.3' - pod 'DZNEmptyDataSet' - pod 'PopupDialog' - pod 'ReachabilitySwift', '~> 4.3.0' - pod 'GradientProgressBar', '~> 2.0' - pod 'Popover' -end - -def tunnel_pods - pod 'TunnelKit', :git => 'https://github.com/pia-foss/tunnelkit', :branch => 'master' - pod 'OpenSSL-Apple', :git => 'https://github.com/keeshux/openssl-apple' -end - -def piawireguard_pod - pod 'PIAWireguard', :git => "#{$git_root}/pia-wireguard" -end - -def piawireguard_gitlab_pod - pod 'PIAWireguard', :git => "#{$gitlab_vpn_root}/pia-wireguard.git", :commit => '7e9d8d48' -end - -# Targets - -target 'PIA VPN' do - app_pods -end - -target 'PIA VPN dev' do - app_pods - #only use the following pods for internal (non-public) builds - pod 'Firebase/Core', '6.5.0' - pod 'Crashlytics' - pod 'Fabric' -end - -target 'PIA VPN Tunnel' do - tunnel_pods -end - -target 'PIA VPN WG Tunnel' do - #piawireguard_pod - piawireguard_gitlab_pod -end - -target 'PIA VPNTests' do - app_pods - pod 'Firebase/Core', '6.5.0' - pod 'Crashlytics' - pod 'Fabric' -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - if ['PopupDialog'].include? target.name - target.build_configurations.each do |config| - config.build_settings['SWIFT_VERSION'] = '4.2' - end - end - if ['SwiftEntryKit', 'QuickLayout'].include? target.name - target.build_configurations.each do |config| - config.build_settings['SWIFT_VERSION'] = '4.0' - end - end - end -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index 4080a35a3..000000000 --- a/Podfile.lock +++ /dev/null @@ -1,298 +0,0 @@ -PODS: - - Alamofire (4.9.1) - - AlamofireImage (3.6.0): - - Alamofire (~> 4.9) - - Crashlytics (3.14.0): - - Fabric (~> 1.10.2) - - DynamicBlurView (4.1.0) - - DZNEmptyDataSet (1.8.1) - - Fabric (1.10.2) - - Firebase/Core (6.5.0): - - Firebase/CoreOnly - - FirebaseAnalytics (= 6.0.4) - - Firebase/CoreOnly (6.5.0): - - FirebaseCore (= 6.1.0) - - FirebaseAnalytics (6.0.4): - - FirebaseCore (~> 6.1) - - FirebaseInstanceID (~> 4.2) - - GoogleAppMeasurement (= 6.0.4) - - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - - GoogleUtilities/MethodSwizzler (~> 6.0) - - GoogleUtilities/Network (~> 6.0) - - "GoogleUtilities/NSData+zlib (~> 6.0)" - - nanopb (~> 0.3) - - FirebaseCore (6.1.0): - - GoogleUtilities/Environment (~> 6.0) - - GoogleUtilities/Logger (~> 6.0) - - FirebaseInstanceID (4.2.7): - - FirebaseCore (~> 6.0) - - GoogleUtilities/Environment (~> 6.0) - - GoogleUtilities/UserDefaults (~> 6.0) - - FXPageControl (1.5) - - Gloss (2.1.1) - - GoogleAppMeasurement (6.0.4): - - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - - GoogleUtilities/MethodSwizzler (~> 6.0) - - GoogleUtilities/Network (~> 6.0) - - "GoogleUtilities/NSData+zlib (~> 6.0)" - - nanopb (~> 0.3) - - GoogleUtilities/AppDelegateSwizzler (6.7.2): - - GoogleUtilities/Environment - - GoogleUtilities/Logger - - GoogleUtilities/Network - - GoogleUtilities/Environment (6.7.2): - - PromisesObjC (~> 1.2) - - GoogleUtilities/Logger (6.7.2): - - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (6.7.2): - - GoogleUtilities/Logger - - GoogleUtilities/Network (6.7.2): - - GoogleUtilities/Logger - - "GoogleUtilities/NSData+zlib" - - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (6.7.2)" - - GoogleUtilities/Reachability (6.7.2): - - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (6.7.2): - - GoogleUtilities/Logger - - GradientProgressBar (2.0.3): - - LightweightObservable (~> 2.0) - - LightweightObservable (2.1.2) - - lottie-ios (3.2.3) - - nanopb (0.3.9011): - - nanopb/decode (= 0.3.9011) - - nanopb/encode (= 0.3.9011) - - nanopb/decode (0.3.9011) - - nanopb/encode (0.3.9011) - - OpenSSL-Apple (1.1.1j.11) - - PIAAccountModule (1.2.1): - - PIAAccountModule/account (= 1.2.1) - - PIAAccountModule/gradle (= 1.2.1) - - PIAAccountModule/account (1.2.1) - - PIAAccountModule/gradle (1.2.1) - - PIACSIModule (1.0.1): - - PIACSIModule/csi (= 1.0.1) - - PIACSIModule/gradle (= 1.0.1) - - PIACSIModule/csi (1.0.1) - - PIACSIModule/gradle (1.0.1) - - PIAKPIModule (1.0.1): - - PIAKPIModule/gradle (= 1.0.1) - - PIAKPIModule/kpi (= 1.0.1) - - PIAKPIModule/gradle (1.0.1) - - PIAKPIModule/kpi (1.0.1) - - PIALibrary/Core (2.14.0): - - PIAAccountModule - - PIALibrary/Library (2.14.0): - - Alamofire (~> 4) - - Gloss (~> 2) - - PIAAccountModule - - PIACSIModule - - PIAKPIModule - - PIALibrary/Core - - PIALibrary/Util - - PIARegionsModule - - PopupDialog - - ReachabilitySwift - - SwiftyBeaver - - PIALibrary/Mock (2.14.0): - - PIALibrary/Library - - PIALibrary/UI (2.14.0): - - FXPageControl - - lottie-ios - - PIALibrary/Library - - SwiftEntryKit (= 0.7.2) - - SwiftyBeaver - - TPKeyboardAvoiding - - PIALibrary/Util (2.14.0): - - PIALibrary/Core - - PIALibrary/VPN (2.14.0): - - PIALibrary/Library - - PIAWireguard - - TunnelKit - - PIARegionsModule (1.3.1): - - PIARegionsModule/gradle (= 1.3.1) - - PIARegionsModule/regions (= 1.3.1) - - PIARegionsModule/gradle (1.3.1) - - PIARegionsModule/regions (1.3.1) - - PIAWireguard (1.2.0): - - PIAWireguard/AppExtension (= 1.2.0) - - PIAWireguard/Core (= 1.2.0) - - PIAWireguard/AppExtension (1.2.0): - - PIAWireguard/Core - - PIAWireguard/Core (1.2.0): - - Alamofire - - TweetNacl - - Popover (1.3.0) - - PopupDialog (1.1.1): - - DynamicBlurView (~> 4.0) - - PromisesObjC (1.2.12) - - QuickLayout (2.0.2) - - ReachabilitySwift (4.3.1) - - SideMenu (6.1.3) - - SwiftEntryKit (0.7.2): - - QuickLayout (= 2.0.2) - - SwiftyBeaver (1.9.5) - - TPKeyboardAvoiding (1.3.5) - - TunnelKit (3.3.0): - - TunnelKit/Protocols/OpenVPN (= 3.3.0) - - TunnelKit/AppExtension (3.3.0): - - SwiftyBeaver - - TunnelKit/Core - - TunnelKit/Core (3.3.0): - - SwiftyBeaver - - TunnelKit/Manager (3.3.0): - - SwiftyBeaver - - TunnelKit/Protocols/OpenVPN (3.3.0): - - OpenSSL-Apple (~> 1.1.1h.10) - - TunnelKit/AppExtension - - TunnelKit/Core - - TunnelKit/Manager - - TweetNacl (1.0.2) - -DEPENDENCIES: - - AlamofireImage - - Crashlytics - - DZNEmptyDataSet - - Fabric - - Firebase/Core (= 6.5.0) - - GradientProgressBar (~> 2.0) - - OpenSSL-Apple (from `https://github.com/keeshux/openssl-apple`) - - "PIAAccountModule (from `git@gitlab.kape.com:pia-mobile/shared/account.git`, commit `697fd4f`)" - - "PIACSIModule (from `git@gitlab.kape.com:pia-mobile/shared/csi.git`, commit `b62d1bab`)" - - "PIAKPIModule (from `git@gitlab.kape.com:pia-mobile/shared/kpi.git`, commit `31186b1d`)" - - "PIALibrary/Library (from `git@gitlab.kape.com:pia-mobile/ios/client-library-apple.git`, commit `6d34ee34`)" - - "PIALibrary/Mock (from `git@gitlab.kape.com:pia-mobile/ios/client-library-apple.git`, commit `6d34ee34`)" - - "PIALibrary/UI (from `git@gitlab.kape.com:pia-mobile/ios/client-library-apple.git`, commit `6d34ee34`)" - - "PIALibrary/VPN (from `git@gitlab.kape.com:pia-mobile/ios/client-library-apple.git`, commit `6d34ee34`)" - - "PIARegionsModule (from `git@gitlab.kape.com:pia-mobile/shared/regions.git`, branch `release/1.3.1`)" - - "PIAWireguard (from `git@gitlab.kape.com:pia-mobile/ios/pia-wireguard.git`, commit `7e9d8d48`)" - - Popover - - PopupDialog - - ReachabilitySwift (~> 4.3.0) - - SideMenu (= 6.1.3) - - TPKeyboardAvoiding - - TunnelKit (from `https://github.com/pia-foss/tunnelkit`, branch `master`) - -SPEC REPOS: - https://github.com/CocoaPods/Specs.git: - - Alamofire - - AlamofireImage - - Crashlytics - - DynamicBlurView - - DZNEmptyDataSet - - Fabric - - Firebase - - FirebaseAnalytics - - FirebaseCore - - FirebaseInstanceID - - FXPageControl - - Gloss - - GoogleAppMeasurement - - GoogleUtilities - - GradientProgressBar - - LightweightObservable - - lottie-ios - - nanopb - - Popover - - PopupDialog - - PromisesObjC - - QuickLayout - - ReachabilitySwift - - SideMenu - - SwiftEntryKit - - SwiftyBeaver - - TPKeyboardAvoiding - - TweetNacl - -EXTERNAL SOURCES: - OpenSSL-Apple: - :git: https://github.com/keeshux/openssl-apple - PIAAccountModule: - :commit: 697fd4f - :git: "git@gitlab.kape.com:pia-mobile/shared/account.git" - PIACSIModule: - :commit: b62d1bab - :git: "git@gitlab.kape.com:pia-mobile/shared/csi.git" - PIAKPIModule: - :commit: 31186b1d - :git: "git@gitlab.kape.com:pia-mobile/shared/kpi.git" - PIALibrary: - :commit: 6d34ee34 - :git: "git@gitlab.kape.com:pia-mobile/ios/client-library-apple.git" - PIARegionsModule: - :branch: release/1.3.1 - :git: "git@gitlab.kape.com:pia-mobile/shared/regions.git" - PIAWireguard: - :commit: 7e9d8d48 - :git: "git@gitlab.kape.com:pia-mobile/ios/pia-wireguard.git" - TunnelKit: - :branch: master - :git: https://github.com/pia-foss/tunnelkit - -CHECKOUT OPTIONS: - OpenSSL-Apple: - :commit: ada87845f854a21fea436c76deb97be2acc58c8a - :git: https://github.com/keeshux/openssl-apple - PIAAccountModule: - :commit: 697fd4f - :git: "git@gitlab.kape.com:pia-mobile/shared/account.git" - PIACSIModule: - :commit: b62d1bab - :git: "git@gitlab.kape.com:pia-mobile/shared/csi.git" - PIAKPIModule: - :commit: 31186b1d - :git: "git@gitlab.kape.com:pia-mobile/shared/kpi.git" - PIALibrary: - :commit: 6d34ee34 - :git: "git@gitlab.kape.com:pia-mobile/ios/client-library-apple.git" - PIARegionsModule: - :commit: 6b3b763b3b1d066cb73d2d8833563669ff8e87bb - :git: "git@gitlab.kape.com:pia-mobile/shared/regions.git" - PIAWireguard: - :commit: 7e9d8d48 - :git: "git@gitlab.kape.com:pia-mobile/ios/pia-wireguard.git" - TunnelKit: - :commit: 2556fbe9feb53adc88e97e5bdc09779da31468f5 - :git: https://github.com/pia-foss/tunnelkit - -SPEC CHECKSUMS: - Alamofire: 85e8a02c69d6020a0d734f6054870d7ecb75cf18 - AlamofireImage: be9963c6582d68b39e89191f64c82a7d7bf40fdd - Crashlytics: 9220f5bc89e7a618df411b4f639389dbfb0e03d2 - DynamicBlurView: 58e18fae80bb614e34681a4486870e7d257b62e8 - DZNEmptyDataSet: 9525833b9e68ac21c30253e1d3d7076cc828eaa7 - Fabric: ea977e3cd9c20425516d3dafd3bf8c941c51223f - Firebase: dedc9e48ea3f3649ad5f6b982f8a0c73508a14b5 - FirebaseAnalytics: 3fb375bc9d13779add4039716f868d233a473fad - FirebaseCore: aecf02fb2274ec361b9bebeac112f5daa18273bd - FirebaseInstanceID: ebd2ea79ee38db0cb5f5167b17a0d387e1cc7b6e - FXPageControl: 97620412515365d10a3282ec0660f49f6401a8f0 - Gloss: 9df15229b2f6484c14752a738474fd2528b4c5cd - GoogleAppMeasurement: 183bd916af7f80deb67c01888368f1108d641832 - GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 - GradientProgressBar: f11fb57cffc615a4794da558ce283171a992130a - LightweightObservable: c5ac85423a5edbed9a920b4d5c7b8f94ab3688c4 - lottie-ios: c058aeafa76daa4cf64d773554bccc8385d0150e - nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd - OpenSSL-Apple: bb7c9715a259404de040f5359ed3b3170cedf8d6 - PIAAccountModule: 31264ad54dfa98cd8be6810885f910b8bedc9592 - PIACSIModule: 32df98c20a0fc4cad5fadbb1b72f7b74315aa8ea - PIAKPIModule: ec04bedfc7e6eccc7dfe4bc630a99c104ffbb7ea - PIALibrary: d52e06ca2995dd5692dcadb678475acbb0000cd2 - PIARegionsModule: eff00bd28dea554d7b766ec5d7e9a74ab448f5fe - PIAWireguard: e6fc3a37758af8d83704dd61e327c2ff6da88b13 - Popover: 10e1d9528f81d9504df984b7b3f491292bc1822d - PopupDialog: 720c92befd8bc23c13442254945213db5612f149 - PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 - QuickLayout: a730730b646b231fd4ef7cffaeb1e81fe0e1ca92 - ReachabilitySwift: 4032e2f59586e11e3b0ebe15b167abdd587a388b - SideMenu: 46c1b79dab51fc2f1c1ff3e1212ca47cc231b0bf - SwiftEntryKit: 83d312243af7397e38a222b17b7a744b9a7d2145 - SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 - TPKeyboardAvoiding: d55b3ea7b362540af8fcf36aa3ed2e87bf210cc6 - TunnelKit: 2a6aadea2d772a2760b153aee27d1c334c9ca6db - TweetNacl: 3abf4d1d2082b0114e7a67410e300892448951e6 - -PODFILE CHECKSUM: 5b7ee3e88860d684f51498a697fb96c685f63397 - -COCOAPODS: 1.11.2 diff --git a/README.md b/README.md index a870fa1bc..feea50969 100644 --- a/README.md +++ b/README.md @@ -31,33 +31,11 @@ The PIA VPN app features: - Xcode 9+ (Swift 4) - Git (preinstalled with Xcode Command Line Tools) - Ruby (preinstalled with macOS) -- [CocoaPods 1.5.0][dep-cocoapods] - [SwiftGen][dep-swiftgen] - [Go][dep-golang] It's highly recommended to use the Git and Ruby packages provided by [Homebrew][dep-brew]. -### Testing - -Download the app codebase locally: - - $ git clone https://github.com/pia-foss/vpn-ios.git - -Assuming you have a [working CocoaPods environment][dep-cocoapods], setting up the app workspace only requires installing the pod dependencies: - - $ pod install - -After that, open `PIA VPN.xcworkspace` in Xcode and run the `PIA VPN` target. - -If the build does not complete due to missing modules (often `PIAAccount`), run `pod install` again with the partial build, then build again. - -For the VPN to work properly, the app requires: - -- _App Groups_ and _Keychain Sharing_ capabilities -- App IDs with _Packet Tunnel_ entitlements - -both in the main app and the tunnel extension target. - ### Hotspot Helper API We use a special entitlement to participate in the process of joining Wi-Fi/hotspot networks (https://developer.apple.com/documentation/networkextension/nehotspothelper) @@ -101,7 +79,6 @@ This project is licensed under the [MIT (Expat) license](https://choosealicense. [pia-url]: https://www.privateinternetaccess.com/ [pia-wiki]: https://en.wikipedia.org/wiki/Private_Internet_Access -[dep-cocoapods]: https://guides.cocoapods.org/using/getting-started.html [dep-swiftgen]: https://github.com/SwiftGen/SwiftGen [dep-jazzy]: https://github.com/realm/jazzy [dep-brew]: https://brew.sh/ diff --git a/Resources/UI/en.lproj/Main.storyboard b/Resources/UI/en.lproj/Main.storyboard index ec71595b5..b6eec2637 100644 --- a/Resources/UI/en.lproj/Main.storyboard +++ b/Resources/UI/en.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -1425,7 +1425,7 @@ - + From 374153f972b1507c420ef67841d898b1d0c28b49 Mon Sep 17 00:00:00 2001 From: kp-laura-sempere <46663952+kp-laura-sempere@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:28:53 +0100 Subject: [PATCH 2/5] PIA-809: Update iOS application (#31) * Adds the functionality of in-app message for user survey and point to new commit sha on PIALibary module * Corrects a module rename and points to new commit * Code cleanup on weak self. * Refactors the survey control logic into a UserSurveyManager. This also changes responsibility of incrementing successConnections and moves it to AppPreferences. Small refactoring of RatingManager. * Update for KPI module and new commit sha for client-library-apple. * Refactors showRating method * Fixes the > issue with take a survey CTA text * Code refactor for handling connection success on rating and survey managers * Revert to accidentally committing swiftgen file * Removes more unwanted swiftgen changes * Moves the string extension to private scope and applies a code refactor * Update for an edge case where user has to interact with the survey message * Points to correct commit sha for PIAKPI and PIALibrary modules * Updates PIALibrary commit sha * Updates commit sha * Adds functionality to have a separate connections-counter for UserSurvey message banner logic * Code refactor for User survey's separate counter * Refactor on successConnectionUntilSurvey * Updates commit sha and time_to_connect collection logic * Code refactor and updates commit sha * Code refactor * Updates commit sha * Updates to correct commit sha for KPI library * Updates KPI commit sha * Updates the commit for wrong ip fix * Adds UI Test target * Updates commit sha after merging client-library-apple for timeToConnect MR * Adds a UI test for invalid user * Updates commit sha * Code refactor and adds PIALibrary to UITests target for accessibility identifiers * Fixes a keyboard typing issue * Adds a user credentials json file for testing different users * Point to PIALibrary release/2.15.0 * Bump version to 3.15.0 * Use credentials from plist * Refactor invalid user test to reuse it other tests * Update PIALibrary commit, refactor and add valid user UI Test with updated accessibility identifiers * Fix the environment and UI test target selection issues * Remove XCTAssert as a function * Remove unnecessary sleeps * Update commit sha PIALibrary * Update PIA Library commit & accessibility strings and code improvements * Rename conversion bool * Download translations and new en placeholders * Update commit sha for PIALibrary * Add deviceType and DNS info in user setting for CSI (wip) * Update PIALibrary commit sha to avoid conflicts on closing release * Update checksum * Update integration with CSI V1.0.2 * Fix setting's delegate reference for updating DNS info in app preferences * Update pod file commit sha * Refactor code for device type * Bump PIALibrary to 2.16.0 commit sha * Bump version to 3.16.0 * Convert rating alert UI to use default iOS UX widgets * Bump commit sha for feature flag * Add control logic for showing default UX of rating alert * Code cleaning * Bump the version to 3.16.1 * Bump commit sha for token migration hotfix * Flag rename and bump PIALibrary commit sha * Code refactor * Add translations for rating alert and DIP retry message * Bump commit sha * Update PIALibrary commit sha * Bump commit sha for based PIALibrary * Code clean * Bump PIALibrary to v2.17.0 * Bump version to 3.17.0 * Bump PIARegions to v1.3.2 * Bump CSI to v1.1.1 and update support for Last Know Exception category on PIALibrary * Bump PIALibrary to latest commit * Refactor dip header view cell to move the network request code out of it * Fix a bug where rating alert was re-presented when app was updated * Refactor if into a guard statement * Remove redundant deviceInfo key and bump PIALibrary to whitelist user_setting commit * Bump to the fixed commit sha of file references fixes in PIALibrary * Bump commit for PIALibrary * Refactor fatalError to NSException for better CSI reports * Rename user preference flag stopInAppMessages to showServiceMessages and bump the commit sha of PIALibrary This is to make UI menu name consistent with flag name * Fix the logic when API call should be made based on flag * Bump PIALibrary to 2.18.0 commit sha * Bump version 3.18.0 * Merge `release/3.19.0` into `master` (#1131) * Fix develop branch for an issue where commit sha does not exist on client-library-apple * Fix CSI and KPI dependencies * Migrate to SPM * Widget Cleanup * Update to wireguard-apple `1.0.15-26` * Add workflow pia-mobile/ios/vpn-ios * Sync OpenVPN TunnelKit to version 4.0.3 (#1128) * wip * wip * update dep * bump version * bump build number * Fix signup storyboard crash (#1129) * fix signup storyboard crash * update to client-library-apple merged revision * bump build version number to 20030 (#1130) --------- Co-authored-by: Waleed Mahmood Co-authored-by: Juan Docal Co-authored-by: Bogdan Danila <109506906+kp-bogdan-danila@users.noreply.github.com> * remove messages logic (#1133) * bump version to 3.20.0 (#1134) * bump version to 3.21.0 (#1135) * update openssl to version 3.0.2 (#1136) * bump version to 3.22.0 (#1137) * bump version to 3.23.0 (#1138) * CXAPP-3161: Show leak protection toggles on privacy settings * CXAPP-3160: Add feature flag feetching for leak protection * CXAPP-3231: Add leak protections UI behind feature flag * CXAPP-3162: Add logic to show and hide leak protection toggles * CXAPP-3162: Add logic to show and hide leak protection toggles * CXAPP-3165: Add alert for leak protection changes when VPN is connected * CXAPP-3165: Update PIA iOS Library to the latest version * PIA-68: Add Network monitor to identify non-compliant Wifi * PIA-68: Integrate network monitor on HospotHelper * PIA-54: Migrate currentRFC1918VulnerableWifi to Client preferences * PIA-54: Add english localization for non compliant alert * PIA-54: Add logic to show the non compliant WIFI alert * PIA-54: Add logic to disable and reconnect CTA * PIA-57: Update feature flags on dev builds from debug menu * PIA-57: Skip updating feature flag values when pulling from CSI server on dev builds * PIA-57: Show leak protection local notification when connected to a non-compliant Wi-Fi network * PIA-61: Remove leak protection notification when the device connects to a compliant wifi * PIA-62: Remove leak protection local notification when VPN is disconnected * PIA-325: Add logic to make sure we reconnect after disconnection was completed * PIA-326: Show alert whencurrent vpn status is not connected * PIA-56: Handle learn more action on leak protection alert * PIA-63: Handle 'More info' action from the Leak Protection settings description * PIA-337: Show leak protection content in English if no translations are available in other languages * PIA-314: Add non-compliant check for current WIFI * PIA-62: Remove leak proteciton notification when vpn is disconnected and app in the foreground * PIA-335: Dismiss leak protection alert when disconnecting from quick action * PIA-350: Show leak protection local notification in English when untranslated * PIA-362: Remove leak protection alert when user connects to a compliant Wi-Fi * Bump version to 3.23.1 * PIA-454: Disable UI tests and failing tests from target * PIA-65: Hide leak protection settings when Wireguard is selected * PIA-454: Add Fastlane setup * PIA-454: Add CI congifuration * PIA-415: Connection Live Activity and Dynamic Island POC * PIA-490: Set feature flag for Dynamic Island Live Activity * PIA-66: Add strings for non IKEV2 protocol alert * PIA-66: Show non compliant alert for non IKEV2 protocol * PIA-66: Enable leak protection and disable allow local devices * Bump version to 3.23.2 * PIA-509: Execute pending actions when switching to IKEV2 * PIA-504: Update connection live activity and dynamic islanc UI * PIA-417: Hook Connection state to Live Activity and Dynamic Island * PIA-438: Bump version * PIA-438: Add uploading to Testflight action * PIA-438: Update CI to sign and upload builds * PIA-438: Automate build number increase * PIA-620: Update PIAAccount framework * PIA-504: Update disconnected button and icon on Live Activity widget and Dynamic Island * PIA-504: Localize text displayed on Live Activity and Dynamic Island * PIA-540: PIA e2e testing setup Login screen e2e tests * PIA-540: Remove legacy ui testing target * PIA-540: Set user credentials from ENV variables * PIA-556: Update translations * PIA-541: Allow VPN Profile installation e2e test And add e2e example test on how to launch the app on authenticated state and with the VPN Profile installation complete * PIA-680: Add e2e workflow on CI * - Integrated framework with Quick and Nimble - Added Tests using Quick and Nimble - Started the restructuring of directories * - Integrated framework with Quick and Nimble - Added Tests using Quick and Nimble - Started the restructuring of directories - Added Tests * - Enhanced based on PR Comments * PIA-566: Update UI on regions cell to accomodate full text on smaller devices and different languages * Enhanced FW by adding screens, helpers and rearranged structure * PIA-560: Handle first region button from Quick Connect section * PIA-832: Update app version to 3.23.4 * PIA-809: Update XCode on CI pipeline --------- Co-authored-by: Waleed Mahmood Co-authored-by: Miguel Berrocal Co-authored-by: Helge Becker Co-authored-by: kp-juan-docal <109512072+kp-juan-docal@users.noreply.github.com> Co-authored-by: Juan Docal Co-authored-by: Bogdan Danila <109506906+kp-bogdan-danila@users.noreply.github.com> Co-authored-by: Said Rehouni Co-authored-by: kp-said-rehouni <109279805+kp-said-rehouni@users.noreply.github.com> Co-authored-by: xv-laura-sempere <46663952+xv-laura-s@users.noreply.github.com> Co-authored-by: Geneva Parayno Co-authored-by: xv-geneva-parayno <121007829+xv-geneva-parayno@users.noreply.github.com> --- .github/workflows/pull_request.yml | 46 ++ .github/workflows/testflight_deploy.yml | 51 ++ Gemfile | 3 + PIA VPN.xcodeproj/project.pbxproj | 407 ++++++---- .../xcschemes/PIA VPN dev.xcscheme | 31 +- .../xcshareddata/xcschemes/PIA VPN.xcscheme | 14 + .../xcschemes/PIA-VPN_E2E_Tests.xcscheme | 77 ++ PIA VPN/AccessibilityId.swift | 48 ++ PIA VPN/AppDelegate.swift | 21 +- PIA VPN/AppPreferences.swift | 10 + PIA VPN/Bootstrapper.swift | 6 +- PIA VPN/DashboardViewController.swift | 184 ++++- PIA VPN/Info.plist | 2 + PIA VPN/MessagesManager.swift | 2 +- PIA VPN/PIAConnectionButton.swift | 1 + PIA VPN/ServerProvider+UI.swift | 26 +- PIA VPN/SettingOptions.swift | 5 +- .../DevelopmentSettingsViewController.swift | 30 +- ...rivacyFeaturesSettingsViewController.swift | 24 +- PIA VPN/SwiftGen+Strings.swift | 20 + PIA VPN/VPNPermissionViewController.swift | 3 +- PIA VPN/ar.lproj/Localizable.strings | 22 + PIA VPN/da.lproj/Localizable.strings | 22 + PIA VPN/de.lproj/Localizable.strings | 22 + PIA VPN/en.lproj/Localizable.strings | 7 + PIA VPN/es-MX.lproj/Localizable.strings | 22 + PIA VPN/fr.lproj/Localizable.strings | 22 + PIA VPN/it.lproj/Localizable.strings | 22 + PIA VPN/ja.lproj/Localizable.strings | 22 + PIA VPN/ko.lproj/Localizable.strings | 22 + PIA VPN/nb.lproj/Localizable.strings | 22 + PIA VPN/nl.lproj/Localizable.strings | 22 + PIA VPN/pl.lproj/Localizable.strings | 22 + PIA VPN/pt-BR.lproj/Localizable.strings | 22 + PIA VPN/ru.lproj/Localizable.strings | 22 + PIA VPN/th.lproj/Localizable.strings | 22 + PIA VPN/tr.lproj/Localizable.strings | 721 ++++++++++-------- PIA VPN/zh-Hans.lproj/Localizable.strings | 22 + PIA VPN/zh-Hant.lproj/Localizable.strings | 22 + .../Core/Utils/PIAHotspotHelperTests.swift | 3 +- PIA-VPN_E2E_Tests/Core/BaseTest.swift | 28 + PIA-VPN_E2E_Tests/Helpers/ElementHelper.swift | 55 ++ PIA-VPN_E2E_Tests/Helpers/WaitHelper.swift | 55 ++ .../PIA-VPN-e2e-simulator.xctestplan | 41 + PIA-VPN_E2E_Tests/Screens/Common.swift | 31 + PIA-VPN_E2E_Tests/Screens/HomeScreen.swift | 40 + PIA-VPN_E2E_Tests/Screens/LoginScreen.swift | 55 ++ .../Screens/VPNPermissionScreen.swift | 28 + PIA-VPN_E2E_Tests/Screens/WelcomeScreen.swift | 29 + PIA-VPN_E2E_Tests/Tests/OnboardingTests.swift | 35 + .../PIAExampleWithAuthenticatedAppTest.swift | 45 ++ PIA-VPN_E2E_Tests/Tests/SignInTests.swift | 32 + PIA-VPN_E2E_Tests/Util/CredentialsUtil.swift | 32 + .../PIA-logo.imageset/Contents.json | 15 + .../PIA-logo.imageset/Group.pdf | Bin 0 -> 7194 bytes .../connected-button.imageset/Contents.json | 15 + .../connected-button.imageset/Group 13.pdf | Bin 0 -> 3654 bytes .../connecting-button.imageset/Contents.json | 15 + .../State=Connecting.pdf | Bin 0 -> 1837808 bytes .../ButtonDisconnected.pdf | Bin 0 -> 3654 bytes .../Contents.json | 15 + .../disconnected-cross.imageset/Contents.json | 15 + .../IconDisconnected.pdf | Bin 0 -> 2044 bytes .../green-checkmark.imageset/Contents.json | 15 + .../green-checkmark.imageset/Icon.pdf | Bin 0 -> 1783 bytes .../ios-widget.imageset/Badge.pdf | Bin 0 -> 14438 bytes .../ios-widget.imageset/Contents.json | 15 + PIAWidget/Domain/UI/PIACircleIcon.swift | 26 + PIAWidget/Domain/UI/PIACircleImageView.swift | 26 + PIAWidget/Domain/UI/PIAConnectionView.swift | 61 ++ .../Widget/PIAConnectionActivityWidget.swift | 84 ++ .../PIAConnectionLiveActivityManager.swift | 85 +++ PIAWidget/Domain/Widget/PIAWidget.swift | 3 +- .../Domain/Widget/PIAWidgetAttributes.swift | 17 + PIAWidget/Domain/Widget/PIAWidgetBundle.swift | 14 + PIAWidget/PIAWidget.swift | 166 ---- PIAWidget/WidgetContent.swift | 34 - PIAWidget/WidgetUtils.swift | 73 -- Resources/GoogleService-Info.plist | 5 - Resources/UI/en.lproj/Main.storyboard | 346 ++++----- fastlane/Appfile | 6 + fastlane/Certificates | 33 + fastlane/Fastfile | 105 +++ 83 files changed, 2839 insertions(+), 950 deletions(-) create mode 100644 .github/workflows/pull_request.yml create mode 100644 .github/workflows/testflight_deploy.yml create mode 100644 Gemfile create mode 100644 PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA-VPN_E2E_Tests.xcscheme create mode 100644 PIA VPN/AccessibilityId.swift create mode 100644 PIA-VPN_E2E_Tests/Core/BaseTest.swift create mode 100644 PIA-VPN_E2E_Tests/Helpers/ElementHelper.swift create mode 100644 PIA-VPN_E2E_Tests/Helpers/WaitHelper.swift create mode 100644 PIA-VPN_E2E_Tests/PIA-VPN-e2e-simulator.xctestplan create mode 100644 PIA-VPN_E2E_Tests/Screens/Common.swift create mode 100644 PIA-VPN_E2E_Tests/Screens/HomeScreen.swift create mode 100644 PIA-VPN_E2E_Tests/Screens/LoginScreen.swift create mode 100644 PIA-VPN_E2E_Tests/Screens/VPNPermissionScreen.swift create mode 100644 PIA-VPN_E2E_Tests/Screens/WelcomeScreen.swift create mode 100644 PIA-VPN_E2E_Tests/Tests/OnboardingTests.swift create mode 100644 PIA-VPN_E2E_Tests/Tests/PIAExampleWithAuthenticatedAppTest.swift create mode 100644 PIA-VPN_E2E_Tests/Tests/SignInTests.swift create mode 100644 PIA-VPN_E2E_Tests/Util/CredentialsUtil.swift create mode 100644 PIAWidget/Assets.xcassets/PIA-logo.imageset/Contents.json create mode 100644 PIAWidget/Assets.xcassets/PIA-logo.imageset/Group.pdf create mode 100644 PIAWidget/Assets.xcassets/connected-button.imageset/Contents.json create mode 100644 PIAWidget/Assets.xcassets/connected-button.imageset/Group 13.pdf create mode 100644 PIAWidget/Assets.xcassets/connecting-button.imageset/Contents.json create mode 100644 PIAWidget/Assets.xcassets/connecting-button.imageset/State=Connecting.pdf create mode 100644 PIAWidget/Assets.xcassets/disconnected-button.imageset/ButtonDisconnected.pdf create mode 100644 PIAWidget/Assets.xcassets/disconnected-button.imageset/Contents.json create mode 100644 PIAWidget/Assets.xcassets/disconnected-cross.imageset/Contents.json create mode 100644 PIAWidget/Assets.xcassets/disconnected-cross.imageset/IconDisconnected.pdf create mode 100644 PIAWidget/Assets.xcassets/green-checkmark.imageset/Contents.json create mode 100644 PIAWidget/Assets.xcassets/green-checkmark.imageset/Icon.pdf create mode 100644 PIAWidget/Assets.xcassets/ios-widget.imageset/Badge.pdf create mode 100644 PIAWidget/Assets.xcassets/ios-widget.imageset/Contents.json create mode 100644 PIAWidget/Domain/UI/PIACircleIcon.swift create mode 100644 PIAWidget/Domain/UI/PIACircleImageView.swift create mode 100644 PIAWidget/Domain/UI/PIAConnectionView.swift create mode 100644 PIAWidget/Domain/Widget/PIAConnectionActivityWidget.swift create mode 100644 PIAWidget/Domain/Widget/PIAConnectionLiveActivityManager.swift create mode 100644 PIAWidget/Domain/Widget/PIAWidgetAttributes.swift create mode 100644 PIAWidget/Domain/Widget/PIAWidgetBundle.swift delete mode 100644 PIAWidget/PIAWidget.swift delete mode 100644 PIAWidget/WidgetContent.swift delete mode 100644 PIAWidget/WidgetUtils.swift delete mode 100644 Resources/GoogleService-Info.plist create mode 100644 fastlane/Appfile create mode 100644 fastlane/Certificates create mode 100644 fastlane/Fastfile diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 000000000..f381b5738 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,46 @@ +name: pia_vpn_ios +on: + pull_request: + workflow_dispatch: +concurrency: + group: "${{ github.ref }}" + cancel-in-progress: true +jobs: + build: + runs-on: macos-13 + env: + TEST_RUNNER_PIA_TEST_USER: ${{ secrets.PIA_ACCOUNT_USERNAME}} + TEST_RUNNER_PIA_TEST_PASSWORD: ${{ secrets.PIA_ACCOUNT_PASSWORD }} + + steps: + - name: Setup Git credentials + run: | + git config --global url."https://${{ secrets.ORG_GITHUB_USERNAME }}:${{ secrets.ORG_GITHUB_TOKEN }}@github.com/".insteadOf "git@github.com:" + + - uses: actions/checkout@v3 + + - name: Select XCode version + run: sudo xcode-select -s /Applications/Xcode_15.0.app + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + + - name: Install Fastlane + run: gem install fastlane + + - name: Set credentials in fastlane env + run: echo "TEST_RUNNER_PIA_TEST_USER=${{ secrets.PIA_ACCOUNT_USERNAME}}"\n"TEST_RUNNER_PIA_TEST_PASSWORD=${{ secrets.PIA_ACCOUNT_PASSWORD }}" > fastlane/.env + + - name: Check fastlane env + run: cat fastlane/.env + + - name: Run e2e tests with fastlane + run: bundle exec fastlane e2e_tests + continue-on-error: true + + - name: Run tests + run: bundle exec fastlane tests + + \ No newline at end of file diff --git a/.github/workflows/testflight_deploy.yml b/.github/workflows/testflight_deploy.yml new file mode 100644 index 000000000..6be48ccfa --- /dev/null +++ b/.github/workflows/testflight_deploy.yml @@ -0,0 +1,51 @@ +name: testflight_deploy +on: + push: + branches: + - master + + workflow_dispatch: +concurrency: + group: "${{ github.ref }}" + cancel-in-progress: true +jobs: + build: + runs-on: macos-13 + env: + CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }} + KEYCHAIN_NAME: ${{ secrets.KEYCHAIN_NAME }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} + APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} + APP_STORE_CONNECT_KEY: ${{ secrets.APP_STORE_CONNECT_KEY }} + + steps: + - name: Setup Git credentials + run: | + git config --global url."https://${{ secrets.ORG_GITHUB_USERNAME }}:${{ secrets.ORG_GITHUB_TOKEN }}@github.com/".insteadOf "git@github.com:" + + - uses: actions/checkout@v3 + + - name: Select XCode version + run: sudo xcode-select -s /Applications/Xcode_15.0.app + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + + - name: Install Fastlane + run: gem install fastlane + + - name: Decode Certificates + run: echo "${{ secrets.IOS_CERTIFICATE }}" | base64 --decode > ./fastlane/Certificate.p12 + + - name: Run tests + run: bundle exec fastlane tests + + - name: Set up certificates and profiles + run: bundle exec fastlane get_profiles > /dev/null 2>&1 + + - name: Upload version to TestFlight + run: bundle exec fastlane testflight_build + \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..7a118b49b --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/PIA VPN.xcodeproj/project.pbxproj b/PIA VPN.xcodeproj/project.pbxproj index 622eb63a9..8a554e48b 100644 --- a/PIA VPN.xcodeproj/project.pbxproj +++ b/PIA VPN.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -145,15 +145,42 @@ 3545E98226AADB2B00B812CC /* ServerSelectingTileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3545E98126AADB2B00B812CC /* ServerSelectingTileCell.swift */; }; 3545E98326AADC7C00B812CC /* ServerSelectingTileCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3545E98126AADB2B00B812CC /* ServerSelectingTileCell.swift */; }; 3545E98426AADC7E00B812CC /* ServerSelectionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3545E97F26AAD60C00B812CC /* ServerSelectionDelegate.swift */; }; - 69446CB32AA7502E0080F446 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69446CB22AA7502E0080F446 /* PIALibrary */; }; - 69446CB52AA7503A0080F446 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69446CB42AA7503A0080F446 /* PIALibrary */; }; - 69446CB72AA750440080F446 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69446CB62AA750440080F446 /* PIALibrary */; }; - 69446CB92AA7504B0080F446 /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69446CB82AA7504B0080F446 /* PIALibrary */; }; - 7EB8D11F27CE2B020030B060 /* PIAUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB8D11E27CE2B020030B060 /* PIAUITests.swift */; }; - 7EB8D12127CE2B5D0030B060 /* PIALaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB8D12027CE2B5D0030B060 /* PIALaunchTests.swift */; }; - 7EB8D12327CE7D4C0030B060 /* PIALoginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB8D12227CE7D4C0030B060 /* PIALoginTests.swift */; }; - 7EC2972F27D8B8580061C56A /* CredentialsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC2972D27D8B8580061C56A /* CredentialsUtil.swift */; }; - 7EC2973027D8B8580061C56A /* Credentials.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7EC2972E27D8B8580061C56A /* Credentials.plist */; }; + 35950B312ADD2996006F3CD9 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = 35950B302ADD2996006F3CD9 /* Quick */; }; + 35950B342ADD2EE5006F3CD9 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = 35950B332ADD2EE5006F3CD9 /* Nimble */; }; + 35EDD6282ADE5D31007B9ACB /* BaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD6272ADE5D31007B9ACB /* BaseTest.swift */; }; + 35EDD62A2ADE5F08007B9ACB /* SignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD6292ADE5F08007B9ACB /* SignInTests.swift */; }; + 35EDD6332ADE7281007B9ACB /* OnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD6322ADE7281007B9ACB /* OnboardingTests.swift */; }; + 35EDD6352ADE7424007B9ACB /* VPNPermissionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD6342ADE7424007B9ACB /* VPNPermissionScreen.swift */; }; + 35EDD6372ADE761A007B9ACB /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD6362ADE761A007B9ACB /* HomeScreen.swift */; }; + 35EDD63B2AE62D15007B9ACB /* ElementHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD63A2AE62D15007B9ACB /* ElementHelper.swift */; }; + 35EDD63E2AE76A3B007B9ACB /* WaitHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD63D2AE76A3B007B9ACB /* WaitHelper.swift */; }; + 35EDD6422AE7A83D007B9ACB /* WelcomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD6412AE7A83D007B9ACB /* WelcomeScreen.swift */; }; + 35EDD6442AE7B480007B9ACB /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35EDD6432AE7B480007B9ACB /* Common.swift */; }; + 6924831A2AB045A5002A0407 /* PIAWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692483192AB045A5002A0407 /* PIAWidgetAttributes.swift */; }; + 6924831B2AB045A5002A0407 /* PIAWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692483192AB045A5002A0407 /* PIAWidgetAttributes.swift */; }; + 6924831C2AB045A5002A0407 /* PIAWidgetAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692483192AB045A5002A0407 /* PIAWidgetAttributes.swift */; }; + 6924831E2AB04FFD002A0407 /* PIAWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6924831D2AB04FFD002A0407 /* PIAWidgetBundle.swift */; }; + 692483202AB05F18002A0407 /* PIAConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6924831F2AB05F18002A0407 /* PIAConnectionView.swift */; }; + 692483222AB05F37002A0407 /* PIACircleIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692483212AB05F37002A0407 /* PIACircleIcon.swift */; }; + 692483262AB05F85002A0407 /* PIAConnectionActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692483252AB05F85002A0407 /* PIAConnectionActivityWidget.swift */; }; + 695BF81D2AC30EFB00D1139C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E7EC043209326E30029811E /* Localizable.strings */; }; + 695BF81F2AC410E000D1139C /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2215C820084CD700F5FB4D /* SwiftGen+Strings.swift */; }; + 698F4F2B2AB8A2080010B2B0 /* PIAWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 8269A6D5251CB5E0000B4DBF /* PIAWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 698F4F2D2AB978BF0010B2B0 /* PIACircleImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698F4F2C2AB978BF0010B2B0 /* PIACircleImageView.swift */; }; + 698F4F2E2AB97BAD0010B2B0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 291C6397183EBC210039EC03 /* Images.xcassets */; }; + 698F4F302ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698F4F2F2ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift */; }; + 698F4F312ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698F4F2F2ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift */; }; + 698F4F322ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698F4F2F2ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift */; }; + 69B70AB52ACBF51C0072A09D /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B70AB42ACBF51C0072A09D /* LoginScreen.swift */; }; + 69B70ABC2ACBF8300072A09D /* CredentialsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC2972D27D8B8580061C56A /* CredentialsUtil.swift */; }; + 69B70ABE2ACC2CFE0072A09D /* AccessibilityId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B70ABD2ACC2CFE0072A09D /* AccessibilityId.swift */; }; + 69B70ABF2ACC2CFE0072A09D /* AccessibilityId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B70ABD2ACC2CFE0072A09D /* AccessibilityId.swift */; }; + 69B70AC02ACC2CFE0072A09D /* AccessibilityId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B70ABD2ACC2CFE0072A09D /* AccessibilityId.swift */; }; + 69C587FD2AD00C6300B95EF9 /* PIAExampleWithAuthenticatedAppTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69C587FC2AD00C6300B95EF9 /* PIAExampleWithAuthenticatedAppTest.swift */; }; + 69D57FB82AFA2B8200CEC43E /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69D57FB72AFA2B8200CEC43E /* PIALibrary */; }; + 69D57FBA2AFA2B9700CEC43E /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69D57FB92AFA2B9700CEC43E /* PIALibrary */; }; + 69D57FBC2AFA2BA300CEC43E /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69D57FBB2AFA2BA300CEC43E /* PIALibrary */; }; + 69D57FBE2AFA2BB000CEC43E /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 69D57FBD2AFA2BB000CEC43E /* PIALibrary */; }; 7ECBB8DD27B6C4F500C0C774 /* UserSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECBB8DC27B6C4F500C0C774 /* UserSurveyManager.swift */; }; 7ECBB8DE27BA5FCE00C0C774 /* UserSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECBB8DC27B6C4F500C0C774 /* UserSurveyManager.swift */; }; 821674F12678A1BE0028E4FD /* SettingsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821674F02678A1BE0028E4FD /* SettingsDelegate.swift */; }; @@ -254,7 +281,6 @@ 82CAB87B255AEA3500BB08EF /* MessagesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAB879255AEA3500BB08EF /* MessagesManager.swift */; }; 82CAB8B0255B050000BB08EF /* MessagesCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAB8AF255B050000BB08EF /* MessagesCommands.swift */; }; 82CAB8B1255B050000BB08EF /* MessagesCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAB8AF255B050000BB08EF /* MessagesCommands.swift */; }; - 82CAB8DA255BEC7000BB08EF /* PIACommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAB8D9255BEC7000BB08EF /* PIACommandTests.swift */; }; 82CAB8E8255C0CB000BB08EF /* MessagesTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAB8E7255C0CB000BB08EF /* MessagesTile.swift */; }; 82CAB8E9255C0CB000BB08EF /* MessagesTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAB8E7255C0CB000BB08EF /* MessagesTile.swift */; }; 82CAB8F2255C0CD100BB08EF /* MessagesTileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CAB8F0255C0CD100BB08EF /* MessagesTileCollectionViewCell.swift */; }; @@ -464,7 +490,7 @@ remoteGlobalIDString = 0EFB606F203D7A2C0095398C; remoteInfo = "PIA VPN AdBlocker"; }; - 7EC2973127D8BCB60061C56A /* PBXContainerItemProxy */ = { + 69B70AB62ACBF51C0072A09D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 291C6374183EBC210039EC03 /* Project object */; proxyType = 1; @@ -532,6 +558,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 698F4F2B2AB8A2080010B2B0 /* PIAWidgetExtension.appex in Embed App Extensions */, DD1AB0E923F2993600396E74 /* PIA VPN WG Tunnel.appex in Embed App Extensions */, 0EFB6079203D7A2C0095398C /* PIA VPN AdBlocker.appex in Embed App Extensions */, 0EE220741F4EF307002805AE /* PIA VPN Tunnel.appex in Embed App Extensions */, @@ -550,9 +577,9 @@ 0E257AC41DA45D2F0000D3C3 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; 0E325DA62093277F0020BEDB /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = ""; }; 0E392DA51FE3283C0002160D /* TransientState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientState.swift; sourceTree = ""; }; - 0E3A35271FD9A960000B0F99 /* DashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardViewController.swift; sourceTree = ""; }; + 0E3A35271FD9A960000B0F99 /* DashboardViewController.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = DashboardViewController.swift; sourceTree = ""; tabWidth = 4; }; 0E3A352B1FD9CDC5000B0F99 /* Theme+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+App.swift"; sourceTree = ""; }; - 0E3A35341FD9EBDA000B0F99 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 0E3A35341FD9EBDA000B0F99 /* AppDelegate.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; tabWidth = 4; }; 0E3C9A5D20EC004D00B199F9 /* custom.servers */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = custom.servers; sourceTree = ""; }; 0E441E252055AEDF007528D5 /* ThemeStrategy+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThemeStrategy+App.swift"; sourceTree = ""; }; 0E492C661FE60907007F23DF /* Flags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Flags.swift; sourceTree = ""; }; @@ -610,7 +637,7 @@ 0E9452AA1FDB5EF600891948 /* UINavigationItem+Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationItem+Shortcuts.swift"; sourceTree = ""; }; 0E9452AD1FDB5F7A00891948 /* PIAPageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAPageControl.swift; sourceTree = ""; }; 0E9785851DA82FF000711A24 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; - 0E98BB6D1FD5BC6200B41D6B /* Bootstrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bootstrapper.swift; sourceTree = ""; }; + 0E98BB6D1FD5BC6200B41D6B /* Bootstrapper.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = Bootstrapper.swift; sourceTree = ""; tabWidth = 4; }; 0E9AEA6120683FDF00B6E59A /* AboutComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutComponent.swift; sourceTree = ""; }; 0EA660071FEC7A9500CB2B0D /* PIATunnelProvider+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PIATunnelProvider+UI.swift"; sourceTree = ""; }; 0EB0A849204F0CE2008BCF1D /* DataCounter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataCounter.h; sourceTree = ""; }; @@ -663,12 +690,28 @@ 3524670D26B432ED00E3F0AC /* DashboardViewController+ServerSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DashboardViewController+ServerSelection.swift"; sourceTree = ""; }; 3545E97F26AAD60C00B812CC /* ServerSelectionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionDelegate.swift; sourceTree = ""; }; 3545E98126AADB2B00B812CC /* ServerSelectingTileCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectingTileCell.swift; sourceTree = ""; }; - 7EB8D11327CCF4C20030B060 /* PIA VPN UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "PIA VPN UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - 7EB8D11E27CE2B020030B060 /* PIAUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAUITests.swift; sourceTree = ""; }; - 7EB8D12027CE2B5D0030B060 /* PIALaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIALaunchTests.swift; sourceTree = ""; }; - 7EB8D12227CE7D4C0030B060 /* PIALoginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIALoginTests.swift; sourceTree = ""; }; - 7EC2972D27D8B8580061C56A /* CredentialsUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsUtil.swift; sourceTree = ""; }; - 7EC2972E27D8B8580061C56A /* Credentials.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Credentials.plist; sourceTree = ""; }; + 35EDD6272ADE5D31007B9ACB /* BaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTest.swift; sourceTree = ""; }; + 35EDD6292ADE5F08007B9ACB /* SignInTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInTests.swift; sourceTree = ""; }; + 35EDD6322ADE7281007B9ACB /* OnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTests.swift; sourceTree = ""; }; + 35EDD6342ADE7424007B9ACB /* VPNPermissionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNPermissionScreen.swift; sourceTree = ""; }; + 35EDD6362ADE761A007B9ACB /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; + 35EDD63A2AE62D15007B9ACB /* ElementHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementHelper.swift; sourceTree = ""; }; + 35EDD63D2AE76A3B007B9ACB /* WaitHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitHelper.swift; sourceTree = ""; }; + 35EDD6412AE7A83D007B9ACB /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = ""; }; + 35EDD6432AE7B480007B9ACB /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; + 692483192AB045A5002A0407 /* PIAWidgetAttributes.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = PIAWidgetAttributes.swift; sourceTree = ""; tabWidth = 4; }; + 6924831D2AB04FFD002A0407 /* PIAWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIAWidgetBundle.swift; sourceTree = ""; }; + 6924831F2AB05F18002A0407 /* PIAConnectionView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = PIAConnectionView.swift; sourceTree = ""; tabWidth = 4; }; + 692483212AB05F37002A0407 /* PIACircleIcon.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = PIACircleIcon.swift; sourceTree = ""; tabWidth = 4; }; + 692483252AB05F85002A0407 /* PIAConnectionActivityWidget.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = PIAConnectionActivityWidget.swift; sourceTree = ""; tabWidth = 4; }; + 6947AADB2ACDC8AE001BCC66 /* PIA-VPN-e2e-simulator.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "PIA-VPN-e2e-simulator.xctestplan"; sourceTree = ""; }; + 698F4F2C2AB978BF0010B2B0 /* PIACircleImageView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = PIACircleImageView.swift; sourceTree = ""; tabWidth = 4; }; + 698F4F2F2ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = PIAConnectionLiveActivityManager.swift; sourceTree = ""; tabWidth = 4; }; + 69B70AB02ACBF51C0072A09D /* PIA-VPN_E2E_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "PIA-VPN_E2E_Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 69B70AB42ACBF51C0072A09D /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; + 69B70ABD2ACC2CFE0072A09D /* AccessibilityId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityId.swift; sourceTree = ""; }; + 69C587FC2AD00C6300B95EF9 /* PIAExampleWithAuthenticatedAppTest.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = PIAExampleWithAuthenticatedAppTest.swift; sourceTree = ""; tabWidth = 2; }; + 7EC2972D27D8B8580061C56A /* CredentialsUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = CredentialsUtil.swift; sourceTree = ""; tabWidth = 4; }; 7ECBB8DC27B6C4F500C0C774 /* UserSurveyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSurveyManager.swift; sourceTree = ""; }; 821674F02678A1BE0028E4FD /* SettingsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDelegate.swift; sourceTree = ""; }; 82183D7A2500FD460033023F /* String+Substrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Substrings.swift"; sourceTree = ""; }; @@ -818,7 +861,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 69446CB72AA750440080F446 /* PIALibrary in Frameworks */, + 69D57FBC2AFA2BA300CEC43E /* PIALibrary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -838,7 +881,7 @@ 0EE2205C1F4EF307002805AE /* CoreGraphics.framework in Frameworks */, AABF826F28AD187800CDAC64 /* Popover in Frameworks */, 0EE2205F1F4EF307002805AE /* StoreKit.framework in Frameworks */, - 69446CB52AA7503A0080F446 /* PIALibrary in Frameworks */, + 69D57FBA2AFA2B9700CEC43E /* PIALibrary in Frameworks */, 0EE2205D1F4EF307002805AE /* UIKit.framework in Frameworks */, 0EE2205E1F4EF307002805AE /* Foundation.framework in Frameworks */, AABF826D28AD185E00CDAC64 /* TweetNacl in Frameworks */, @@ -876,7 +919,7 @@ 2985E5671856BD1200D70E28 /* QuartzCore.framework in Frameworks */, 291C6382183EBC210039EC03 /* CoreGraphics.framework in Frameworks */, 291C6384183EBC210039EC03 /* UIKit.framework in Frameworks */, - 69446CB32AA7502E0080F446 /* PIALibrary in Frameworks */, + 69D57FB82AFA2B8200CEC43E /* PIALibrary in Frameworks */, DD606ABA21C7A17900E0781D /* NetworkExtension.framework in Frameworks */, AA36CDC928A6733500180A33 /* DZNEmptyDataSet in Frameworks */, 291C6380183EBC210039EC03 /* Foundation.framework in Frameworks */, @@ -884,10 +927,12 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7EB8D11027CCF4C20030B060 /* Frameworks */ = { + 69B70AAD2ACBF51C0072A09D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 35950B312ADD2996006F3CD9 /* Quick in Frameworks */, + 35950B342ADD2EE5006F3CD9 /* Nimble in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -904,7 +949,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 69446CB92AA7504B0080F446 /* PIALibrary in Frameworks */, + 69D57FBE2AFA2BB000CEC43E /* PIALibrary in Frameworks */, AA36CDDB28A6860F00180A33 /* TweetNacl in Frameworks */, DDC8F5F923EC10E4005D19C6 /* NetworkExtension.framework in Frameworks */, ); @@ -1122,7 +1167,7 @@ 0EEE1C191E4F719E00397DE2 /* Resources */, DDC8F5ED23EC1070005D19C6 /* PIA VPN WG Tunnel */, 8269A6D8251CB5E0000B4DBF /* PIAWidget */, - 7EB8D11427CCF4C20030B060 /* PIA VPN UITests */, + 69B70AB12ACBF51C0072A09D /* PIA-VPN_E2E_Tests */, 291C637E183EBC210039EC03 /* Frameworks */, 291C637D183EBC210039EC03 /* Products */, ); @@ -1138,7 +1183,7 @@ 0EFB6070203D7A2C0095398C /* PIA VPN AdBlocker.appex */, DDC8F5EC23EC106F005D19C6 /* PIA VPN WG Tunnel.appex */, 8269A6D5251CB5E0000B4DBF /* PIAWidgetExtension.appex */, - 7EB8D11327CCF4C20030B060 /* PIA VPN UITests.xctest */, + 69B70AB02ACBF51C0072A09D /* PIA-VPN_E2E_Tests.xctest */, ); name = Products; sourceTree = ""; @@ -1218,6 +1263,7 @@ 0E2215C820084CD700F5FB4D /* SwiftGen+Strings.swift */, DD58F4BE21B12CFE00D043F7 /* PIAConnectionButton.swift */, DD125DD021E7A694004ECCB6 /* ServerButton.swift */, + 69B70ABD2ACC2CFE0072A09D /* AccessibilityId.swift */, ); name = Shared; sourceTree = ""; @@ -1234,16 +1280,64 @@ name = UI; sourceTree = ""; }; - 7EB8D11427CCF4C20030B060 /* PIA VPN UITests */ = { + 35EDD6262ADE5D1E007B9ACB /* Tests */ = { + isa = PBXGroup; + children = ( + 69C587FC2AD00C6300B95EF9 /* PIAExampleWithAuthenticatedAppTest.swift */, + 35EDD6292ADE5F08007B9ACB /* SignInTests.swift */, + 35EDD6322ADE7281007B9ACB /* OnboardingTests.swift */, + ); + path = Tests; + sourceTree = ""; + }; + 35EDD62B2ADE62FE007B9ACB /* Screens */ = { + isa = PBXGroup; + children = ( + 69B70AB42ACBF51C0072A09D /* LoginScreen.swift */, + 35EDD6342ADE7424007B9ACB /* VPNPermissionScreen.swift */, + 35EDD6362ADE761A007B9ACB /* HomeScreen.swift */, + 35EDD6412AE7A83D007B9ACB /* WelcomeScreen.swift */, + 35EDD6432AE7B480007B9ACB /* Common.swift */, + ); + path = Screens; + sourceTree = ""; + }; + 35EDD6382AE62CF0007B9ACB /* Helpers */ = { + isa = PBXGroup; + children = ( + 35EDD63A2AE62D15007B9ACB /* ElementHelper.swift */, + 35EDD63D2AE76A3B007B9ACB /* WaitHelper.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 35EDD63C2AE7679B007B9ACB /* Core */ = { + isa = PBXGroup; + children = ( + 35EDD6272ADE5D31007B9ACB /* BaseTest.swift */, + ); + path = Core; + sourceTree = ""; + }; + 69B70AB12ACBF51C0072A09D /* PIA-VPN_E2E_Tests */ = { + isa = PBXGroup; + children = ( + 35EDD63C2AE7679B007B9ACB /* Core */, + 35EDD6382AE62CF0007B9ACB /* Helpers */, + 35EDD62B2ADE62FE007B9ACB /* Screens */, + 35EDD6262ADE5D1E007B9ACB /* Tests */, + 6947AADB2ACDC8AE001BCC66 /* PIA-VPN-e2e-simulator.xctestplan */, + 69D12D152ACC75140053A81B /* Util */, + ); + path = "PIA-VPN_E2E_Tests"; + sourceTree = ""; + }; + 69D12D152ACC75140053A81B /* Util */ = { isa = PBXGroup; children = ( - 7EC2972E27D8B8580061C56A /* Credentials.plist */, 7EC2972D27D8B8580061C56A /* CredentialsUtil.swift */, - 7EB8D12227CE7D4C0030B060 /* PIALoginTests.swift */, - 7EB8D11E27CE2B020030B060 /* PIAUITests.swift */, - 7EB8D12027CE2B5D0030B060 /* PIALaunchTests.swift */, ); - path = "PIA VPN UITests"; + path = Util; sourceTree = ""; }; 82183D8425014F940033023F /* Menu */ = { @@ -1370,6 +1464,9 @@ AAE878A628E473A400557F26 /* PIAWidgetVpnDetailsView.swift */, AAE878A828E4765F00557F26 /* PIAIconView.swift */, AA52C59E28E5ECD400D025AF /* PIAWidgetVpnDetaislRow.swift */, + 6924831F2AB05F18002A0407 /* PIAConnectionView.swift */, + 692483212AB05F37002A0407 /* PIACircleIcon.swift */, + 698F4F2C2AB978BF0010B2B0 /* PIACircleImageView.swift */, ); path = UI; sourceTree = ""; @@ -1380,6 +1477,10 @@ 8269A6D9251CB5E0000B4DBF /* PIAWidget.swift */, AAE8789E28E4696300557F26 /* PIAWidgetProvider.swift */, AAE878A028E46C1600557F26 /* PIAWidgetPreview.swift */, + 692483192AB045A5002A0407 /* PIAWidgetAttributes.swift */, + 6924831D2AB04FFD002A0407 /* PIAWidgetBundle.swift */, + 692483252AB05F85002A0407 /* PIAConnectionActivityWidget.swift */, + 698F4F2F2ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift */, ); path = Widget; sourceTree = ""; @@ -1555,7 +1656,7 @@ ); name = "PIA VPN Tunnel"; packageProductDependencies = ( - 69446CB62AA750440080F446 /* PIALibrary */, + 69D57FBB2AFA2BA300CEC43E /* PIALibrary */, ); productName = "PIA OpenVPN"; productReference = 0E67FC221E3F802D00EF9929 /* PIA VPN Tunnel.appex */; @@ -1589,7 +1690,7 @@ AABF827228AE2FF500CDAC64 /* GradientProgressBar */, AABF827428AE2FFE00CDAC64 /* SideMenu */, AABF827728AE333200CDAC64 /* DZNEmptyDataSet */, - 69446CB42AA7503A0080F446 /* PIALibrary */, + 69D57FB92AFA2B9700CEC43E /* PIALibrary */, ); productName = "PIA VPN"; productReference = 0EE2207A1F4EF307002805AE /* PIA VPN dev.app */; @@ -1657,28 +1758,32 @@ AA36CDCB28A673C900180A33 /* GradientProgressBar */, AA36CDCE28A6746500180A33 /* SideMenu */, AA36CDDD28A6878000180A33 /* AlamofireImage */, - 69446CB22AA7502E0080F446 /* PIALibrary */, + 69D57FB72AFA2B8200CEC43E /* PIALibrary */, ); productName = "PIA VPN"; productReference = 291C637C183EBC210039EC03 /* PIA VPN.app */; productType = "com.apple.product-type.application"; }; - 7EB8D11227CCF4C20030B060 /* PIA VPN UITests */ = { + 69B70AAF2ACBF51C0072A09D /* PIA-VPN_E2E_Tests */ = { isa = PBXNativeTarget; - buildConfigurationList = 7EB8D11D27CCF4C20030B060 /* Build configuration list for PBXNativeTarget "PIA VPN UITests" */; + buildConfigurationList = 69B70ABA2ACBF51C0072A09D /* Build configuration list for PBXNativeTarget "PIA-VPN_E2E_Tests" */; buildPhases = ( - 7EB8D10F27CCF4C20030B060 /* Sources */, - 7EB8D11027CCF4C20030B060 /* Frameworks */, - 7EB8D11127CCF4C20030B060 /* Resources */, + 69B70AAC2ACBF51C0072A09D /* Sources */, + 69B70AAD2ACBF51C0072A09D /* Frameworks */, + 69B70AAE2ACBF51C0072A09D /* Resources */, ); buildRules = ( ); dependencies = ( - 7EC2973227D8BCB60061C56A /* PBXTargetDependency */, + 69B70AB72ACBF51C0072A09D /* PBXTargetDependency */, ); - name = "PIA VPN UITests"; - productName = "PIA VPN UITests"; - productReference = 7EB8D11327CCF4C20030B060 /* PIA VPN UITests.xctest */; + name = "PIA-VPN_E2E_Tests"; + packageProductDependencies = ( + 35950B302ADD2996006F3CD9 /* Quick */, + 35950B332ADD2EE5006F3CD9 /* Nimble */, + ); + productName = "PIA-VPN_E2E_Tests"; + productReference = 69B70AB02ACBF51C0072A09D /* PIA-VPN_E2E_Tests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; 8269A6D4251CB5E0000B4DBF /* PIAWidgetExtension */ = { @@ -1713,7 +1818,7 @@ name = "PIA VPN WG Tunnel"; packageProductDependencies = ( AA36CDDA28A6860F00180A33 /* TweetNacl */, - 69446CB82AA7504B0080F446 /* PIALibrary */, + 69D57FBD2AFA2BB000CEC43E /* PIALibrary */, ); productName = "PIA VPN WG Tunnel"; productReference = DDC8F5EC23EC106F005D19C6 /* PIA VPN WG Tunnel.appex */; @@ -1726,7 +1831,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = PIA; - LastSwiftUpdateCheck = 1300; + LastSwiftUpdateCheck = 1430; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "Private Internet Access Inc."; TargetAttributes = { @@ -1754,9 +1859,7 @@ }; 0EEE1BE61E4F6EF400397DE2 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 5357M5NW9W; LastSwiftMigration = 1010; - ProvisioningStyle = Automatic; TestTargetID = 291C637B183EBC210039EC03; }; 0EFB606F203D7A2C0095398C = { @@ -1798,10 +1901,8 @@ }; }; }; - 7EB8D11227CCF4C20030B060 = { - CreatedOnToolsVersion = 13.0; - DevelopmentTeam = 5357M5NW9W; - ProvisioningStyle = Automatic; + 69B70AAF2ACBF51C0072A09D = { + CreatedOnToolsVersion = 14.3.1; TestTargetID = 0EE2200B1F4EF307002805AE; }; 8269A6D4251CB5E0000B4DBF = { @@ -1849,7 +1950,9 @@ AA36CDCA28A673C900180A33 /* XCRemoteSwiftPackageReference "GradientProgressBar" */, AA36CDCD28A6746500180A33 /* XCRemoteSwiftPackageReference "SideMenu" */, AA36CDDC28A6878000180A33 /* XCRemoteSwiftPackageReference "AlamofireImage" */, - 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */, + 35950B2F2ADD2877006F3CD9 /* XCRemoteSwiftPackageReference "Quick" */, + 35950B322ADD2EE5006F3CD9 /* XCRemoteSwiftPackageReference "Nimble" */, + 69D57FB62AFA2B8200CEC43E /* XCRemoteSwiftPackageReference "mobile-ios-library" */, ); productRefGroup = 291C637D183EBC210039EC03 /* Products */; projectDirPath = ""; @@ -1862,7 +1965,7 @@ DDC8F5EB23EC106F005D19C6 /* PIA VPN WG Tunnel */, 8269A6D4251CB5E0000B4DBF /* PIAWidgetExtension */, 0EEE1BE61E4F6EF400397DE2 /* PIA VPNTests */, - 7EB8D11227CCF4C20030B060 /* PIA VPN UITests */, + 69B70AAF2ACBF51C0072A09D /* PIA-VPN_E2E_Tests */, ); }; /* End PBXProject section */ @@ -1987,11 +2090,10 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7EB8D11127CCF4C20030B060 /* Resources */ = { + 69B70AAE2ACBF51C0072A09D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7EC2973027D8B8580061C56A /* Credentials.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2000,6 +2102,8 @@ buildActionMask = 2147483647; files = ( 8269A6DD251CB5E3000B4DBF /* Assets.xcassets in Resources */, + 695BF81D2AC30EFB00D1139C /* Localizable.strings in Resources */, + 698F4F2E2AB97BAD0010B2B0 /* Images.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2039,7 +2143,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if which swiftgen >/dev/null; then\n set -e\n swiftgen\nelse\n echo \"warning: SwiftGen not installed, download it from https://github.com/SwiftGen/SwiftGen\"\nfi\n"; + shellScript = "if which swiftgen >/dev/null; then\n set -e\n #swiftgen\nelse\n echo \"warning: SwiftGen not installed, download it from https://github.com/SwiftGen/SwiftGen\"\nfi\n"; }; 2931563B18513F6500E769A7 /* Download Latest Regions List */ = { isa = PBXShellScriptBuildPhase; @@ -2080,6 +2184,7 @@ DD76292321ECCD5C0092DF50 /* UsageTile.swift in Sources */, DDD65314247E66AD00F0A897 /* CoordinatesFinder.swift in Sources */, DDD271DF21D616AA00B6D20F /* Server+Favorite.swift in Sources */, + 698F4F312ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift in Sources */, DD125DC621E77046004ECCB6 /* QuickConnectTile.swift in Sources */, 82A1AD2724B86AF60003DD02 /* PIACardsViewController.swift in Sources */, 829EB508253598BD003E74DD /* DedicatedIpViewController.swift in Sources */, @@ -2159,6 +2264,7 @@ 0E2215CD2008C01D00F5FB4D /* SwiftGen+Assets.swift in Sources */, 8272C62E2657B46100D846A8 /* NetworkSettingsViewController.swift in Sources */, DDFCFA9021E892070081F235 /* RegionTileCollectionViewCell.swift in Sources */, + 6924831B2AB045A5002A0407 /* PIAWidgetAttributes.swift in Sources */, DD9706A5224262BF00630220 /* QuickSettingsTile.swift in Sources */, 82CAB8E9255C0CB000BB08EF /* MessagesTile.swift in Sources */, DD3B504424B7576F0002F4B5 /* Card.swift in Sources */, @@ -2179,6 +2285,7 @@ 0EFDC1E11FE4A450007C0B9B /* AppPreferences.swift in Sources */, 0E441E272055AEDF007528D5 /* ThemeStrategy+App.swift in Sources */, DDC8125021761B0B00CB290C /* SwiftGen+ScenesStoryboards.swift in Sources */, + 69B70ABF2ACC2CFE0072A09D /* AccessibilityId.swift in Sources */, 7ECBB8DE27BA5FCE00C0C774 /* UserSurveyManager.swift in Sources */, 0E492C681FE60907007F23DF /* Flags.swift in Sources */, DD172A972254C35000071CFB /* FavoriteServersTile.swift in Sources */, @@ -2208,7 +2315,6 @@ DD606AC821C9344100E0781D /* AppTests.swift in Sources */, 82A1AD2324B7C0020003DD02 /* PIACardTests.swift in Sources */, 8221922B24CECFE700C24F1C /* NMTTests.swift in Sources */, - 82CAB8DA255BEC7000BB08EF /* PIACommandTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2256,6 +2362,7 @@ 0ECF5C082017EBAD0047596C /* ThemeCode.swift in Sources */, 3545E98026AAD60C00B812CC /* ServerSelectionDelegate.swift in Sources */, 0E392DA61FE3283C0002160D /* TransientState.swift in Sources */, + 6924831A2AB045A5002A0407 /* PIAWidgetAttributes.swift in Sources */, 82F41376264E8AC20098FF4B /* SettingOptions.swift in Sources */, 824C531524C04796003DB740 /* ConnectionTileCollectionViewCell.swift in Sources */, 0E3A35351FD9EBDA000B0F99 /* AppDelegate.swift in Sources */, @@ -2287,6 +2394,7 @@ 827570D124DAA128008F9800 /* PIAHeaderCollectionViewCell.swift in Sources */, DD125DD121E7A694004ECCB6 /* ServerButton.swift in Sources */, DD9329A2237AFD0A0025B6BC /* ShowQuickSettingsViewController.swift in Sources */, + 69B70ABE2ACC2CFE0072A09D /* AccessibilityId.swift in Sources */, 0E2215C920084CD700F5FB4D /* SwiftGen+Strings.swift in Sources */, 827570D824DAB6E4008F9800 /* NetworkFooterCollectionViewCell.swift in Sources */, E5F52A1F2A8A614900828883 /* NetworkMonitor.swift in Sources */, @@ -2340,6 +2448,7 @@ 8272C62726540B2100D846A8 /* ProtocolSettingsViewController.swift in Sources */, 82CAB8B0255B050000BB08EF /* MessagesCommands.swift in Sources */, DDE432DF2498EADB0095B197 /* Array+Group.swift in Sources */, + 698F4F302ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift in Sources */, DDB6B95321C95CD400DE8C5F /* EnumsBuilder.swift in Sources */, 3545E98226AADB2B00B812CC /* ServerSelectingTileCell.swift in Sources */, 829EB5322535AD27003E74DD /* DedicatedIpEmptyHeaderViewCell.swift in Sources */, @@ -2355,14 +2464,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 7EB8D10F27CCF4C20030B060 /* Sources */ = { + 69B70AAC2ACBF51C0072A09D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7EC2972F27D8B8580061C56A /* CredentialsUtil.swift in Sources */, - 7EB8D12327CE7D4C0030B060 /* PIALoginTests.swift in Sources */, - 7EB8D11F27CE2B020030B060 /* PIAUITests.swift in Sources */, - 7EB8D12127CE2B5D0030B060 /* PIALaunchTests.swift in Sources */, + 35EDD6442AE7B480007B9ACB /* Common.swift in Sources */, + 35EDD63E2AE76A3B007B9ACB /* WaitHelper.swift in Sources */, + 35EDD6282ADE5D31007B9ACB /* BaseTest.swift in Sources */, + 35EDD6352ADE7424007B9ACB /* VPNPermissionScreen.swift in Sources */, + 69B70ABC2ACBF8300072A09D /* CredentialsUtil.swift in Sources */, + 35EDD6372ADE761A007B9ACB /* HomeScreen.swift in Sources */, + 35EDD63B2AE62D15007B9ACB /* ElementHelper.swift in Sources */, + 69C587FD2AD00C6300B95EF9 /* PIAExampleWithAuthenticatedAppTest.swift in Sources */, + 35EDD6422AE7A83D007B9ACB /* WelcomeScreen.swift in Sources */, + 35EDD62A2ADE5F08007B9ACB /* SignInTests.swift in Sources */, + 69B70AB52ACBF51C0072A09D /* LoginScreen.swift in Sources */, + 35EDD6332ADE7281007B9ACB /* OnboardingTests.swift in Sources */, + 69B70AC02ACC2CFE0072A09D /* AccessibilityId.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2371,15 +2489,23 @@ buildActionMask = 2147483647; files = ( 8269A6FE251CBB36000B4DBF /* WidgetInformation.swift in Sources */, + 695BF81F2AC410E000D1139C /* SwiftGen+Strings.swift in Sources */, + 698F4F2D2AB978BF0010B2B0 /* PIACircleImageView.swift in Sources */, 822F97B4251DD53100644EF2 /* WidgetUserDefaultsDatasource.swift in Sources */, 8269A6DA251CB5E0000B4DBF /* PIAWidget.swift in Sources */, + 692483222AB05F37002A0407 /* PIACircleIcon.swift in Sources */, AAE878A928E4765F00557F26 /* PIAIconView.swift in Sources */, + 6924831E2AB04FFD002A0407 /* PIAWidgetBundle.swift in Sources */, AAE8789F28E4696300557F26 /* PIAWidgetProvider.swift in Sources */, AAE878A128E46C1600557F26 /* PIAWidgetPreview.swift in Sources */, + 692483262AB05F85002A0407 /* PIAConnectionActivityWidget.swift in Sources */, AAE878A528E4723B00557F26 /* PIACircleVpnButton.swift in Sources */, 82BAACFB25B09C9200B3C733 /* PIAWidget.intentdefinition in Sources */, AAE8789C28E4679500557F26 /* WidgetPersistenceDatasource.swift in Sources */, AAE878A328E46D2B00557F26 /* PIAWidgetView.swift in Sources */, + 6924831C2AB045A5002A0407 /* PIAWidgetAttributes.swift in Sources */, + 698F4F322ABA1DA10010B2B0 /* PIAConnectionLiveActivityManager.swift in Sources */, + 692483202AB05F18002A0407 /* PIAConnectionView.swift in Sources */, AAE878A728E473A400557F26 /* PIAWidgetVpnDetailsView.swift in Sources */, AA52C59F28E5ECD400D025AF /* PIAWidgetVpnDetaislRow.swift in Sources */, ); @@ -2426,10 +2552,10 @@ target = 0EFB606F203D7A2C0095398C /* PIA VPN AdBlocker */; targetProxy = 0EFB607E203D893E0095398C /* PBXContainerItemProxy */; }; - 7EC2973227D8BCB60061C56A /* PBXTargetDependency */ = { + 69B70AB72ACBF51C0072A09D /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 0EE2200B1F4EF307002805AE /* PIA VPN dev */; - targetProxy = 7EC2973127D8BCB60061C56A /* PBXContainerItemProxy */; + targetProxy = 69B70AB62ACBF51C0072A09D /* PBXContainerItemProxy */; }; 8269A6E2251CB5E3000B4DBF /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -2532,7 +2658,7 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN Tunnel/PIA VPN Tunnel.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 20042; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; @@ -2543,7 +2669,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.3; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.Tunnel"; PRODUCT_NAME = "PIA VPN Tunnel"; @@ -2566,9 +2692,10 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 20042; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5357M5NW9W; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5357M5NW9W; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN Tunnel/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.1; @@ -2577,12 +2704,13 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.3; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.Tunnel"; PRODUCT_NAME = "PIA VPN Tunnel"; PROVISIONING_PROFILE = "b5b9e54d-7aba-4fc6-9320-adbce64c544a"; PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.privateinternetaccess.ios.PIA-VPN.Tunnel"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.privateinternetaccess.ios.PIA-VPN.Tunnel"; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2598,7 +2726,7 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN/PIA VPN.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 5357M5NW9W; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -2618,7 +2746,7 @@ "$(inherited)", "$(SRCROOT)", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.4; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN"; PRODUCT_MODULE_NAME = PIA_VPN_dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2642,7 +2770,7 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN/PIA VPN.entitlements"; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 5357M5NW9W; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -2662,7 +2790,7 @@ "$(inherited)", "$(SRCROOT)", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.4; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN"; PRODUCT_MODULE_NAME = PIA_VPN_dev; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2718,7 +2846,6 @@ CLANG_ENABLE_MODULES = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -2737,7 +2864,6 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PIA VPN.app/PIA VPN"; @@ -2754,7 +2880,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 20042; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2766,7 +2892,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.3; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.AdBlocker"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2791,9 +2917,10 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 20042; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5357M5NW9W; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN AdBlocker/Info.plist"; @@ -2803,12 +2930,13 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.3; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.AdBlocker"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "5aba703f-4bee-46e6-a5e4-42b785d1db55"; PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.privateinternetaccess.ios.PIA-VPN.AdBlocker"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.privateinternetaccess.ios.PIA-VPN.AdBlocker"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2934,7 +3062,7 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN/PIA VPN.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 5357M5NW9W; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -2952,7 +3080,7 @@ "$(inherited)", "$(SRCROOT)", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.4; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development com.privateinternetaccess.ios.PIA-VPN"; @@ -2971,10 +3099,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "PIA VPN/PIA VPN.entitlements"; - CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_IDENTITY = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 5357M5NW9W; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5357M5NW9W; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -2991,22 +3121,23 @@ "$(inherited)", "$(SRCROOT)", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.4; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.privateinternetaccess.ios.PIA-VPN"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.privateinternetaccess.ios.PIA-VPN"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.privateinternetaccess.ios.PIA-VPN"; SWIFT_OBJC_BRIDGING_HEADER = ""; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; name = Release; }; - 7EB8D11B27CCF4C20030B060 /* Debug */ = { + 69B70AB82ACBF51C0072A09D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; @@ -3018,18 +3149,13 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.cyberghostsrl.PIA-VPN-UITests"; - "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; + PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.e2e-Tests.PIA-VPN-E2E-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "420cc0bf-ef09-4d4c-b1b3-9d7cffd4d201"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -3039,12 +3165,12 @@ }; name = Debug; }; - 7EB8D11C27CCF4C20030B060 /* Release */ = { + 69B70AB92ACBF51C0072A09D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; @@ -3057,18 +3183,13 @@ GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + IPHONEOS_DEPLOYMENT_TARGET = 16.0; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.cyberghostsrl.PIA-VPN-UITests"; - "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; + PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.e2e-Tests.PIA-VPN-E2E-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = "420cc0bf-ef09-4d4c-b1b3-9d7cffd4d201"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3091,7 +3212,7 @@ CODE_SIGN_ENTITLEMENTS = PIAWidgetExtension.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 20042; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3103,7 +3224,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.PIAWidget"; @@ -3133,9 +3254,10 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 20042; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5357M5NW9W; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = PIAWidget/Info.plist; @@ -3145,12 +3267,13 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.3; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.PIAWidget"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.privateinternetaccess.ios.PIA-VPN.PIAWidget"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.privateinternetaccess.ios.PIA-VPN.PIAWidget"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3169,7 +3292,7 @@ CODE_SIGN_ENTITLEMENTS = "PIA VPN WG Tunnel/PIA_VPN_WG_Tunnel.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 20042; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3181,7 +3304,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.WG-Tunnel"; @@ -3208,9 +3331,10 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 20034; + CURRENT_PROJECT_VERSION = 20042; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5357M5NW9W; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5357M5NW9W; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "PIA VPN WG Tunnel/Info.plist"; @@ -3220,12 +3344,13 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.23.0; + MARKETING_VERSION = 3.23.3; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.privateinternetaccess.ios.PIA-VPN.WG-Tunnel"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.privateinternetaccess.ios.PIA-VPN.WG-Tunnel"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.privateinternetaccess.ios.PIA-VPN.WG-Tunnel"; SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3289,11 +3414,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 7EB8D11D27CCF4C20030B060 /* Build configuration list for PBXNativeTarget "PIA VPN UITests" */ = { + 69B70ABA2ACBF51C0072A09D /* Build configuration list for PBXNativeTarget "PIA-VPN_E2E_Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 7EB8D11B27CCF4C20030B060 /* Debug */, - 7EB8D11C27CCF4C20030B060 /* Release */, + 69B70AB82ACBF51C0072A09D /* Debug */, + 69B70AB92ACBF51C0072A09D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -3319,9 +3444,25 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */ = { + 35950B2F2ADD2877006F3CD9 /* XCRemoteSwiftPackageReference "Quick" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Quick/Quick"; + requirement = { + kind = exactVersion; + version = 7.3.0; + }; + }; + 35950B322ADD2EE5006F3CD9 /* XCRemoteSwiftPackageReference "Nimble" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Quick/Nimble"; + requirement = { + kind = exactVersion; + version = 13.0.0; + }; + }; + 69D57FB62AFA2B8200CEC43E /* XCRemoteSwiftPackageReference "mobile-ios-library" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "git@github.com:pia-foss/client-library-apple.git"; + repositoryURL = "git@github.com:pia-foss/mobile-ios-library.git"; requirement = { branch = master; kind = branch; @@ -3378,24 +3519,34 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 69446CB22AA7502E0080F446 /* PIALibrary */ = { + 35950B302ADD2996006F3CD9 /* Quick */ = { + isa = XCSwiftPackageProductDependency; + package = 35950B2F2ADD2877006F3CD9 /* XCRemoteSwiftPackageReference "Quick" */; + productName = Quick; + }; + 35950B332ADD2EE5006F3CD9 /* Nimble */ = { + isa = XCSwiftPackageProductDependency; + package = 35950B322ADD2EE5006F3CD9 /* XCRemoteSwiftPackageReference "Nimble" */; + productName = Nimble; + }; + 69D57FB72AFA2B8200CEC43E /* PIALibrary */ = { isa = XCSwiftPackageProductDependency; - package = 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */; + package = 69D57FB62AFA2B8200CEC43E /* XCRemoteSwiftPackageReference "mobile-ios-library" */; productName = PIALibrary; }; - 69446CB42AA7503A0080F446 /* PIALibrary */ = { + 69D57FB92AFA2B9700CEC43E /* PIALibrary */ = { isa = XCSwiftPackageProductDependency; - package = 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */; + package = 69D57FB62AFA2B8200CEC43E /* XCRemoteSwiftPackageReference "mobile-ios-library" */; productName = PIALibrary; }; - 69446CB62AA750440080F446 /* PIALibrary */ = { + 69D57FBB2AFA2BA300CEC43E /* PIALibrary */ = { isa = XCSwiftPackageProductDependency; - package = 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */; + package = 69D57FB62AFA2B8200CEC43E /* XCRemoteSwiftPackageReference "mobile-ios-library" */; productName = PIALibrary; }; - 69446CB82AA7504B0080F446 /* PIALibrary */ = { + 69D57FBD2AFA2BB000CEC43E /* PIALibrary */ = { isa = XCSwiftPackageProductDependency; - package = 69446CB12AA74F880080F446 /* XCRemoteSwiftPackageReference "client-library-apple" */; + package = 69D57FB62AFA2B8200CEC43E /* XCRemoteSwiftPackageReference "mobile-ios-library" */; productName = PIALibrary; }; AA36CDBA28A6622A00180A33 /* TweetNacl */ = { diff --git a/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN dev.xcscheme b/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN dev.xcscheme index a41ce8df3..8a19b76d3 100644 --- a/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN dev.xcscheme +++ b/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN dev.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -54,7 +54,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "NO"> + + + + + + + + + + @@ -76,12 +94,13 @@ + skipped = "NO" + parallelizable = "YES"> diff --git a/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN.xcscheme b/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN.xcscheme index c8602f4fa..9860758c1 100644 --- a/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN.xcscheme +++ b/PIA VPN.xcodeproj/xcshareddata/xcschemes/PIA VPN.xcscheme @@ -48,6 +48,20 @@ ReferencedContainer = "container:PIA VPN.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PIA VPN/AccessibilityId.swift b/PIA VPN/AccessibilityId.swift new file mode 100644 index 000000000..a83f3db2c --- /dev/null +++ b/PIA VPN/AccessibilityId.swift @@ -0,0 +1,48 @@ + +import Foundation + +public struct AccessibilityId { + public struct VPNPermission { + public static let screen = "id.vpnPermission.screen" + public static let submit = "id.vpnPermission.ok.button" + } + + public struct Dashboard { + public static let connectionButton = "id.dashboard.connection.button" + } + +} + + +/// This is the same struct `Accessibility` from `PIALibrary` +/// It has been copied over to the VPN ios client app to +/// facilitate e2e testing. +/// In the future, we will move the Welcome and Login ViewControllers and storyboards from `PIALibrary` into vpn ios +/// And this duplication will not be necessary +public struct PIALibraryAccessibility { + public struct Id { + public struct Welcome { + public static let environment = "id.welcome.environment" + } + public struct Login { + public static let submit = "id.login.submit" + public static let submitNew = "id.login.submit.new" + public static let username = "id.login.username" + public static let password = "id.login.submit" + public struct Error { + public static let banner = "id.login.error.banner" + } + } + + public struct Dashboard { + public static let menu = "id.dashboard.menu" + } + public struct Menu { + public static let logout = "id.menu.logout" + } + public struct Dialog { + public static let destructive = "id.dialog.destructive.button" + } + } +} + diff --git a/PIA VPN/AppDelegate.swift b/PIA VPN/AppDelegate.swift index 350977a1a..c921da89c 100644 --- a/PIA VPN/AppDelegate.swift +++ b/PIA VPN/AppDelegate.swift @@ -42,6 +42,7 @@ class AppDelegate: NSObject, UIApplicationDelegate { var window: UIWindow? private var hotspotHelper: PIAHotspotHelper! + private (set) var liveActivityManager: PIAConnectionLiveActivityManagerType? deinit { NotificationCenter.default.removeObserver(self) @@ -55,12 +56,27 @@ class AppDelegate: NSObject, UIApplicationDelegate { application.shortcutItems = [] hotspotHelper = PIAHotspotHelper() _ = hotspotHelper.configureHotspotHelper() - + + instantiateLiveActivityManagerIfNeeded() return true } + + private func instantiateLiveActivityManagerIfNeeded() { + if #available(iOS 16.2, *) { + // Only instantiates the LiveActivities if the Feature Flag for it is enabled + guard AppPreferences.shared.showDynamicIslandLiveActivity else { + liveActivityManager = nil + return + } + + liveActivityManager = PIAConnectionLiveActivityManager.shared + } + } func applicationWillTerminate(_ application: UIApplication) { Bootstrapper.shared.dispose() + + liveActivityManager?.endLiveActivities() } // MARK: Orientations @@ -200,6 +216,9 @@ class AppDelegate: NSObject, UIApplicationDelegate { application.applicationIconBadgeNumber = 0 // Remove the Non compliant Wifi local notification as the app is in foreground now Macros.removeLocalNotification(NotificationCategory.nonCompliantWifi) + + instantiateLiveActivityManagerIfNeeded() + } private func refreshShortcutItems(in application: UIApplication) { diff --git a/PIA VPN/AppPreferences.swift b/PIA VPN/AppPreferences.swift index faa821d9d..003c49a30 100644 --- a/PIA VPN/AppPreferences.swift +++ b/PIA VPN/AppPreferences.swift @@ -104,6 +104,7 @@ class AppPreferences { static let showNewInitialScreen = "showNewInitialScreen" static let showLeakProtection = "showLeakProtection" static let showLeakProtectionNotifications = "showLeakProtectionNotifications" + static let showDynamicIslandLiveActivity = "showDynamicIslandLiveActivity" // Survey static let userInteractedWithSurvey = "userInteractedWithSurvey" @@ -537,6 +538,15 @@ class AppPreferences { defaults.set(newValue, forKey: Entries.showLeakProtectionNotifications) } } + + var showDynamicIslandLiveActivity: Bool { + get { + return defaults.bool(forKey: Entries.showDynamicIslandLiveActivity) + } + set { + defaults.set(newValue, forKey: Entries.showDynamicIslandLiveActivity) + } + } var checksDipExpirationRequest: Bool { get { diff --git a/PIA VPN/Bootstrapper.swift b/PIA VPN/Bootstrapper.swift index 0f5f4581a..0ef950e81 100644 --- a/PIA VPN/Bootstrapper.swift +++ b/PIA VPN/Bootstrapper.swift @@ -35,7 +35,7 @@ class Bootstrapper { } private var isSimulator: Bool { - #if arch(i386) || arch(x86_64) + #if targetEnvironment(simulator) return true #else return false @@ -59,6 +59,10 @@ class Bootstrapper { // Leak Protection feature flags AppPreferences.shared.showLeakProtection = Client.configuration.featureFlags.contains(Client.FeatureFlags.showLeakProtection) AppPreferences.shared.showLeakProtectionNotifications = Client.configuration.featureFlags.contains(Client.FeatureFlags.showLeakProtectionNotifications) + + // DynamicIsland LiveActivity + AppPreferences.shared.showDynamicIslandLiveActivity = Client.configuration.featureFlags.contains(Client.FeatureFlags.showDynamicIslandLiveActivity) + } func bootstrap() { diff --git a/PIA VPN/DashboardViewController.swift b/PIA VPN/DashboardViewController.swift index dfa6fe800..8bad1a3e3 100644 --- a/PIA VPN/DashboardViewController.swift +++ b/PIA VPN/DashboardViewController.swift @@ -26,6 +26,7 @@ import SideMenu import SwiftyBeaver import WidgetKit import NetworkExtension +import ActivityKit private let log = SwiftyBeaver.self @@ -69,7 +70,13 @@ class DashboardViewController: AutolayoutViewController { private var isDisconnecting = false private var isUnauthorized = false - private var currentStatus: VPNStatus = .disconnected + private var currentStatus: VPNStatus = .disconnected { + didSet { + if #available(iOS 16.2, *) { + startConnectionLiveActivityIfNeeded() + } + } + } private var connectingStatus: DashboardVPNConnectingStatus = .none private var tileModeStatus: TileStatus = .normal { @@ -130,7 +137,7 @@ class DashboardViewController: AutolayoutViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - + setupNavigationBarButtons() AppPreferences.shared.wasLaunched = true @@ -390,7 +397,6 @@ class DashboardViewController: AutolayoutViewController { } @IBAction func vpnButtonClicked(_ sender: Any?) { - if canConnectVPN() { manuallyConnect() } else { @@ -656,50 +662,141 @@ class DashboardViewController: AutolayoutViewController { guard Client.preferences.currentRFC1918VulnerableWifi != nil || WifiNetworkMonitor().checkForRFC1918Vulnerability() else { return } - guard Client.preferences.allowLocalDeviceAccess - && Client.preferences.leakProtection else { return } - guard AppPreferences.shared.showLeakProtectionNotifications else { return } let currentRFC1918VulnerableWifiName = Client.preferences.currentRFC1918VulnerableWifi ?? "" + let selectedProtocol = Client.preferences.vpnType.vpnProtocol + let isWireguardSelected = selectedProtocol == PIAWGTunnelProfile.vpnType.vpnProtocol + let isOpenVPNSelected = selectedProtocol == PIATunnelProfile.vpnType.vpnProtocol + + guard !isWireguardSelected, + !isOpenVPNSelected else { + DispatchQueue.main.async { + self.presentNonCompliantWireguardWifiAlert() + self.showNonCompliantWifiLocalNotification(currentRFC1918VulnerableWifiName: currentRFC1918VulnerableWifiName) + } + + return + } + + guard Client.preferences.allowLocalDeviceAccess + && Client.preferences.leakProtection else { return } + DispatchQueue.main.async { self.presentNonCompliantWifiAlert() self.showNonCompliantWifiLocalNotification(currentRFC1918VulnerableWifiName: currentRFC1918VulnerableWifiName) } } - private func presentNonCompliantWifiAlert() { - guard let window = UIApplication.shared.delegate?.window, + //MARK: Non compliant Wifi alert + + private struct WifiAlertAction { + let title: String + let style: UIAlertAction.Style + let action: ((UIAlertAction) -> Void)? + } + + private func showNonCompliantWifiAlert(title: String, message: String, actions: [WifiAlertAction]) { + guard + let window = UIApplication.shared.delegate?.window, let presentedViewController = window?.rootViewController?.presentedViewController ?? window?.rootViewController else { return } - let title = L10n.Dashboard.Vpn.Leakprotection.Alert.title + if let alertController = presentedViewController as? UIAlertController, alertController.title == title { return } - if let alertController = presentedViewController as? UIAlertController, - alertController.title == title { return } + let sheet = Macros.alertController(title, message) - let sheet = Macros.alertController(title, L10n.Dashboard.Vpn.Leakprotection.Alert.message) - sheet.addAction(UIAlertAction(title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta1, style: .default, handler: { _ in - Client.preferences.allowLocalDeviceAccess = false - Client.providers.vpnProvider.disconnect { _ in - self.shouldReconnect = true - } - })) + for action in actions { + let alertAction = UIAlertAction(title: action.title, + style: action.style, + handler: action.action) + sheet.addAction(alertAction) + } - // Learn More action - sheet.addAction(UIAlertAction(title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta2, style: .default, handler: { _ in - let application = UIApplication.shared - let learnMoreURL = AppConstants.Web.leakProtectionURL - - if application.canOpenURL(learnMoreURL) { - application.open(learnMoreURL) - } - })) + presentedViewController.present(sheet, animated: true, completion: nil) + } + + private func presentNonCompliantWifiAlert() { + let title = L10n.Dashboard.Vpn.Leakprotection.Alert.title + let message = L10n.Dashboard.Vpn.Leakprotection.Alert.message + + var alertActions = [WifiAlertAction]() + let reconnectAction = WifiAlertAction( + title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta1, + style: .default, + action: handleDisconnectAndReconnectAction) + alertActions.append(reconnectAction) - sheet.addAction(UIAlertAction(title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta3, style: .default, handler: nil)) + let learnMoreAction = WifiAlertAction( + title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta2, + style: .default, + action: handleLearnMoreAction) + alertActions.append(learnMoreAction) + + let cancelAction = WifiAlertAction( + title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta3, + style: .cancel, + action: nil) + alertActions.append(cancelAction) + + showNonCompliantWifiAlert(title: title, message: message, actions: alertActions) + } - presentedViewController.present(sheet, animated: true, completion: nil) + private func presentNonCompliantWireguardWifiAlert() { + let title = L10n.Dashboard.Vpn.Leakprotection.Alert.title + let message = L10n.Dashboard.Vpn.Leakprotection.Alert.IKEV2.message + + var alertActions = [WifiAlertAction]() + let reconnectAction = WifiAlertAction( + title: L10n.Dashboard.Vpn.Leakprotection.Alert.IKEV2.cta1, + style: .default, + action: handleSwitchProtocolAction) + alertActions.append(reconnectAction) + + let learnMoreAction = WifiAlertAction( + title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta2, + style: .default, + action: handleLearnMoreAction) + alertActions.append(learnMoreAction) + + let cancelAction = WifiAlertAction( + title: L10n.Dashboard.Vpn.Leakprotection.Alert.cta3, + style: .cancel, + action: nil) + alertActions.append(cancelAction) + + showNonCompliantWifiAlert(title: title, message: message, actions: alertActions) + } + + private func handleDisconnectAndReconnectAction(_ action: UIAlertAction) { + Client.preferences.allowLocalDeviceAccess = false + Client.providers.vpnProvider.disconnect { _ in + self.shouldReconnect = true + } + } + + private func handleLearnMoreAction(_ action: UIAlertAction) { + let application = UIApplication.shared + let learnMoreURL = AppConstants.Web.leakProtectionURL + + if application.canOpenURL(learnMoreURL) { + application.open(learnMoreURL) + } + } + + private func handleSwitchProtocolAction(_ action: UIAlertAction) { + let editable = Client.preferences.editable() + editable.vpnType = IKEv2Profile.vpnType + let action = editable.requiredVPNAction() + editable.commit() + + Client.preferences.leakProtection = true + Client.preferences.allowLocalDeviceAccess = false + + action?.execute { _ in + self.shouldReconnect = true + } } func showNonCompliantWifiLocalNotification(currentRFC1918VulnerableWifiName: String) { @@ -771,6 +868,10 @@ class DashboardViewController: AutolayoutViewController { guard Client.providers.accountProvider.isLoggedIn else { return } + + if #available(iOS 16.2, *) { + startConnectionLiveActivityIfNeeded() + } currentStatus = Client.providers.vpnProvider.vpnStatus @@ -1126,3 +1227,28 @@ extension DashboardViewController: UICollectionViewDelegate, UICollectionViewDat } } } + + +// MARK: Live Activities + +extension DashboardViewController { + @available(iOS 16.2, *) + private func makeLiveActivityStateForCurrentConnection() -> PIAConnectionAttributes.ContentState { + let vpnProvider = Client.providers.vpnProvider + let currentServer = Client.preferences.displayedServer + + let vpnProtocol = vpnProvider.currentVPNType.vpnProtocol + + let state = PIAConnectionAttributes.ContentState(connected: vpnProvider.isVPNConnected, regionName: currentServer.name, regionFlag: "flag-\(currentServer.country.lowercased())", vpnProtocol: vpnProtocol) + return state + } + + + @available(iOS 16.2, *) + private func startConnectionLiveActivityIfNeeded() { + guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, + let liveActivityManager = appDelegate.liveActivityManager else { return } + let connState = makeLiveActivityStateForCurrentConnection() + liveActivityManager.startLiveActivity(with: connState) + } +} diff --git a/PIA VPN/Info.plist b/PIA VPN/Info.plist index 53cb1dce7..cbeba7e2c 100644 --- a/PIA VPN/Info.plist +++ b/PIA VPN/Info.plist @@ -51,6 +51,8 @@ We need camera access to scan a code from an gift card NSFaceIDUsageDescription Authenticate to reveal + NSSupportsLiveActivities + NSUserActivityTypes PIAConfigurationIntent diff --git a/PIA VPN/MessagesManager.swift b/PIA VPN/MessagesManager.swift index 6b7b4634b..6905be26b 100644 --- a/PIA VPN/MessagesManager.swift +++ b/PIA VPN/MessagesManager.swift @@ -21,7 +21,7 @@ import Foundation import PIALibrary -import PIAAccount +import account import UIKit public class MessagesManager: NSObject { diff --git a/PIA VPN/PIAConnectionButton.swift b/PIA VPN/PIAConnectionButton.swift index 8030960e9..c525268ad 100644 --- a/PIA VPN/PIAConnectionButton.swift +++ b/PIA VPN/PIAConnectionButton.swift @@ -68,6 +68,7 @@ class PIAConnectionButton: UIButton, Restylable { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.setupView() + self.accessibilityIdentifier = AccessibilityId.Dashboard.connectionButton } deinit { diff --git a/PIA VPN/ServerProvider+UI.swift b/PIA VPN/ServerProvider+UI.swift index 77b6ca710..228b24891 100644 --- a/PIA VPN/ServerProvider+UI.swift +++ b/PIA VPN/ServerProvider+UI.swift @@ -31,6 +31,10 @@ extension Client.Preferences { return preferredServer ?? .automatic } set { + guard newValue != displayedServer else { + connectToSelectedServerIfNeeded() + return + } let ed = editable() if newValue.isAutomatic { ed.preferredServer = nil @@ -41,13 +45,21 @@ extension Client.Preferences { let action = ed.requiredVPNAction() ed.commit() - action?.execute { (error) in - let vpn = Client.providers.vpnProvider - if (vpn.vpnStatus != .disconnected) { - vpn.reconnect(after: nil, forceDisconnect: true, nil) - } else { - vpn.connect(nil) - } + action?.execute { [weak self] (error) in + self?.connectToSelectedServerIfNeeded(shouldReconnect: true) + } + } + } + + private func connectToSelectedServerIfNeeded(shouldReconnect: Bool = false) { + let vpn = Client.providers.vpnProvider + + switch vpn.vpnStatus { + case .disconnected: + vpn.connect(nil) + default: + if shouldReconnect { + vpn.reconnect(after: nil, forceDisconnect: true, nil) } } } diff --git a/PIA VPN/SettingOptions.swift b/PIA VPN/SettingOptions.swift index d72c74427..a287a6800 100644 --- a/PIA VPN/SettingOptions.swift +++ b/PIA VPN/SettingOptions.swift @@ -297,6 +297,7 @@ public enum DevelopmentSections: Int, SettingSection, EnumsBuilder { case crash case leakProtectionFlag case leakProtectionNotificationsFlag + case dynamicIslandLiveActivityFlag public func localizedTitleMessage() -> String { switch self { @@ -311,6 +312,7 @@ public enum DevelopmentSections: Int, SettingSection, EnumsBuilder { case .crash: return "Crash the app" case .leakProtectionFlag: return "FF - Leak Protection" case .leakProtectionNotificationsFlag: return "FF - Leak Protection Notifications" + case .dynamicIslandLiveActivityFlag: return "FF - Dynamic Island Live Activity" } } @@ -327,11 +329,12 @@ public enum DevelopmentSections: Int, SettingSection, EnumsBuilder { case .crash: return "" case .leakProtectionFlag: return "" case .leakProtectionNotificationsFlag: return "" + case .dynamicIslandLiveActivityFlag: return "" } } public static func all() -> [Self] { - return [.stagingVersion, .customServers, .publicUsername, .username, .password, .environment, .resolveGoogleAdsDomain, .deleteKeychain, .crash, .leakProtectionFlag, .leakProtectionNotificationsFlag] + return [.stagingVersion, .customServers, .publicUsername, .username, .password, .environment, .resolveGoogleAdsDomain, .deleteKeychain, .crash, .leakProtectionFlag, .leakProtectionNotificationsFlag, .dynamicIslandLiveActivityFlag] } } diff --git a/PIA VPN/Settings/DevelopmentSettingsViewController.swift b/PIA VPN/Settings/DevelopmentSettingsViewController.swift index 3d0793ac5..91f746153 100644 --- a/PIA VPN/Settings/DevelopmentSettingsViewController.swift +++ b/PIA VPN/Settings/DevelopmentSettingsViewController.swift @@ -34,6 +34,7 @@ class DevelopmentSettingsViewController: PIABaseSettingsViewController { private lazy var switchEnvironment = UISwitch() private lazy var switchLeakProtectionFlag = UISwitch() private lazy var switchLeakProtectionNotificationsFlag = UISwitch() + private lazy var switchDynamicIslandLiveActivityFlag = UISwitch() private var controller: OptionsViewController? override func viewDidLoad() { @@ -150,18 +151,26 @@ extension DevelopmentSettingsViewController: UITableViewDelegate, UITableViewDat cell.textLabel?.text = "Crash" cell.detailTextLabel?.text = nil case .leakProtectionFlag: - cell.textLabel?.text = "FF - Leak Protection" - cell.detailTextLabel?.text = nil - cell.accessoryView = switchLeakProtectionFlag - cell.selectionStyle = .none + cell.textLabel?.text = "FF - Leak Protection" + cell.detailTextLabel?.text = nil + cell.accessoryView = switchLeakProtectionFlag + cell.selectionStyle = .none switchLeakProtectionFlag.isOn = AppPreferences.shared.showLeakProtection case .leakProtectionNotificationsFlag: - cell.textLabel?.text = "FF - Leak Protection Notifications" - cell.detailTextLabel?.text = nil - cell.accessoryView = switchLeakProtectionNotificationsFlag - cell.selectionStyle = .none + cell.textLabel?.text = "FF - Leak Protection Notifications" + cell.detailTextLabel?.text = nil + cell.accessoryView = switchLeakProtectionNotificationsFlag + cell.selectionStyle = .none switchLeakProtectionNotificationsFlag.isOn = AppPreferences.shared.showLeakProtectionNotifications + case .dynamicIslandLiveActivityFlag: + cell.textLabel?.text = "FF - Dynamic Island Live Activity" + cell.detailTextLabel?.text = nil + cell.accessoryView = switchDynamicIslandLiveActivityFlag + cell.selectionStyle = .none + switchDynamicIslandLiveActivityFlag.isOn = AppPreferences.shared.showDynamicIslandLiveActivity + } + } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -375,9 +384,14 @@ extension DevelopmentSettingsViewController { AppPreferences.shared.showLeakProtectionNotifications = sender.isOn } + @objc private func toggleDynamicIslandLiveActivityFlag(_ sender: UISwitch) { + AppPreferences.shared.showDynamicIslandLiveActivity = sender.isOn + } + private func addFeatureFlagsTogglesActions() { switchLeakProtectionFlag.addTarget(self, action: #selector(toggleLeakProtectionFlag(_:)), for: .valueChanged) switchLeakProtectionNotificationsFlag.addTarget(self, action: #selector(toggleLeakProtectionNotificationsFlag(_:)), for: .valueChanged) + switchDynamicIslandLiveActivityFlag.addTarget(self, action: #selector(toggleDynamicIslandLiveActivityFlag(_:)), for: .valueChanged) // Additional Feature Flags toggles actions here } diff --git a/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift b/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift index f6fe859bb..2639af4f9 100644 --- a/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift +++ b/PIA VPN/Settings/PrivacyFeaturesSettingsViewController.swift @@ -45,7 +45,11 @@ class PrivacyFeaturesSettingsViewController: PIABaseSettingsViewController { preferences = AppPreferences.shared vpnProvider = Client.providers.vpnProvider - if let preferences = preferences, preferences.showLeakProtection { + // Show Leak protection settings when: + // - The feature flag is ON (`showLeakProtection`) + // - Wireguard or OpenVPN is NOT selected + if let preferences = preferences, preferences.showLeakProtection, + !isCurrentProtocolWireguardOrOpenVPN() { sections = PrivacyFeaturesSections.all() } else { sections = PrivacyFeaturesSections.all().filter { $0 != .leakProtection && $0 != .allowAccessOnLocalNetwork } @@ -321,3 +325,21 @@ extension PrivacyFeaturesSettingsViewController: UITableViewDelegate, UITableVie } } + + +extension PrivacyFeaturesSettingsViewController { + func isCurrentProtocolWireguardOrOpenVPN() -> Bool { + + // Selected protocol is OpenVPN + if pendingPreferences.vpnType == PIATunnelProfile.vpnType { + return true + } + + // Selected protocol is Wireguard + if pendingPreferences.vpnType == PIAWGTunnelProfile.vpnType { + return true + } + + return false + } +} diff --git a/PIA VPN/SwiftGen+Strings.swift b/PIA VPN/SwiftGen+Strings.swift index aa1043fa6..c22d791db 100644 --- a/PIA VPN/SwiftGen+Strings.swift +++ b/PIA VPN/SwiftGen+Strings.swift @@ -220,6 +220,12 @@ internal enum L10n { internal static let message = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.message", fallback: "To prevent data leaks, tap Disable Now to turn off “Allow access to devices on local network\" and automatically reconnect.") /// Unsecured Wi-Fi detected internal static let title = L10n.tr("Localizable", "dashboard.vpn.leakprotection.alert.title", fallback: "Unsecured Wi-Fi detected") + internal enum IKEV2 { + /// Switch now + internal static let cta1 = L10n.tr("Localizable", "dashboard.vpn.leakprotection.ikev2.alert.cta1", fallback: "Switch Now") + /// To prevent data leaks, tap Switch Now to change to the IKEv2 VPN protocol and automatically reconnect. + internal static let message = L10n.tr("Localizable", "dashboard.vpn.leakprotection.ikev2.alert.message", fallback: "To prevent data leaks, tap Switch Now to change to the IKEv2 VPN protocol and automatically reconnect.") + } } } } @@ -1310,6 +1316,20 @@ internal enum L10n { } } } + + internal enum Widget { + internal enum LiveActivity { + internal enum SelectedProtocol { + /// Protocol + internal static let title = L10n.tr("Localizable", "widget.liveActivity.protocol.title", fallback: "Protocol") + } + internal enum Region { + /// Region + internal static let title = L10n.tr("Localizable", "widget.liveActivity.region.title", fallback: "Region") + } + } + } + } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces diff --git a/PIA VPN/VPNPermissionViewController.swift b/PIA VPN/VPNPermissionViewController.swift index 922be68f7..a34176fa1 100644 --- a/PIA VPN/VPNPermissionViewController.swift +++ b/PIA VPN/VPNPermissionViewController.swift @@ -46,6 +46,7 @@ class VPNPermissionViewController: AutolayoutViewController { title = L10n.VpnPermission.title navigationItem.hidesBackButton = true + self.view.accessibilityIdentifier = AccessibilityId.VPNPermission.screen imvPicture.image = Asset.imageVpnAllow.image labelTitle.text = L10n.VpnPermission.Body.title @@ -112,7 +113,7 @@ class VPNPermissionViewController: AutolayoutViewController { buttonSubmit.style(style: TextStyle.Buttons.piaGreenButton) buttonSubmit.setTitle(L10n.Global.ok.uppercased(), for: []) - buttonSubmit.accessibilityIdentifier = Accessibility.Id.Permissions.submit + buttonSubmit.accessibilityIdentifier = AccessibilityId.VPNPermission.submit } } diff --git a/PIA VPN/ar.lproj/Localizable.strings b/PIA VPN/ar.lproj/Localizable.strings index 969fb18dc..f5d0448f2 100644 --- a/PIA VPN/ar.lproj/Localizable.strings +++ b/PIA VPN/ar.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "تجديد"; "expiration.message" = "سينتهي اشتراكك قريبًا. قم بتجديده لتبقى محميًا."; +"local_notification.non_compliant_wifi.title" = "واي فاي غير آمنة: %@"; +"local_notification.non_compliant_wifi.text" = "اضغط هنا لحماية جهازك"; + // SHORTCUTS "shortcuts.connect" = "اتصال"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "النسق النشط"; "settings.application_settings.kill_switch.title" = "مفتاح إيقاف VPN"; "settings.application_settings.kill_switch.footer" = "يؤدي استخدام مفتاح إيقاف VPN إلى تعطيل اتصالك بالإنترنت إذا كان الـ VPN في وضع إعادة الاتصال. لا ينطبق ذلك ذلك على الاتصال اليدوي."; +"settings.application_settings.leak_protection.title" = "حماية من تسريب المعلومات"; +"settings.application_settings.leak_protection.footer" = "يتضمن نظام iOS ميزات مصممة للعمل خارج شبكة VPN افتراضيًا مثل AirDrop و CarPlay و AirPlay ونقاط الاتصال الشخصية. يؤدي تمكين الحماية المخصصة من تسريب المعلومات إلى توجيه حركة البيانات عبر VPN ولكنه قد يؤثر على كيفية عمل هذه الميزات. المزيد من المعلومات"; +"settings.application_settings.leak_protection.more_info" = "المزيد من المعلومات"; +"settings.application_settings.allow_local_network.title" = "السماح بالوصول إلى الأجهزة الموجودة على الشبكة المحلية"; +"settings.application_settings.allow_local_network.footer" = "كن متصلًا بالأجهزة المحلية مثل الطابعات أو خوادم الملفات أثناء الاتصال بشبكة VPN. (اسمح بذلك فقط إذا كنت تثق في الأشخاص المتصلين بشبكتك والأجهزة التي عليها.)"; "settings.application_settings.mace.title" = "™PIA MACE"; "settings.application_settings.mace.footer" = "يحجب ™PIA MACE الإعلانات والمتعقبين والبرمجيات الضارة عندما تكون متصلًا بخدمة VPN."; +"settings.application_settings.leak_protection.alert.title" = "سيتم تفعيل التعديلات على إعدادات الـ VPN عند معاودة الاتصال"; "settings.content_blocker.title" = "حالة حظر المحتوى في Safari"; "settings.content_blocker.state.title" = "الحالة الحالية"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "زر اتصال VPN. خدمة VPN متصلة حاليًا"; "dashboard.accessibility.vpn.button.isOff" = "زر اتصال VPN. خدمة VPN غير متصلة حاليًا"; +"dashboard.vpn.leakprotection.alert.title" = "تم العثور على شبكة واي فاي غير آمنة"; +"dashboard.vpn.leakprotection.alert.message" = "لمنع تسريب البيانات، اضغط على تعطيل الآن لإغلاق \"السماح بالوصول إلى الأجهزة الموجودة على الشبكة المحلية\" وإعادة الاتصال تلقائيًا."; +"dashboard.vpn.leakprotection.alert.cta1" = "تعطيل الآن"; +"dashboard.vpn.leakprotection.alert.cta2" = "اعرف أكثر"; +"dashboard.vpn.leakprotection.alert.cta3" = "تجاهل"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "لمنع تسرب البيانات، اضغط على التبديل الآن للتغير إلى بروتوكول IKEv2 VPN وإعادة الاتصال تلقائيًا."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "التبديل الآن"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "إظهار رسائل اتصال الخدمة"; "inapp.messages.settings.updated" = "تم تحديث الإعدادات"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "المنطقة"; +"widget.liveActivity.protocol.title" = "البروتوكول"; diff --git a/PIA VPN/da.lproj/Localizable.strings b/PIA VPN/da.lproj/Localizable.strings index 1e53c5174..da4073046 100644 --- a/PIA VPN/da.lproj/Localizable.strings +++ b/PIA VPN/da.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Fornyelse"; "expiration.message" = "Dit abonnement udløber snart. Forny for at forblive beskyttet."; +"local_notification.non_compliant_wifi.title" = "Usikret wi-fi: %@"; +"local_notification.non_compliant_wifi.text" = "Tryk her for at sikre din enhed"; + // SHORTCUTS "shortcuts.connect" = "Forbind"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Aktivt tema"; "settings.application_settings.kill_switch.title" = "VPN-afbryderknap"; "settings.application_settings.kill_switch.footer" = "VPN-kill switchen forebygger kontakt til internettet, hvis VPN-forbindelsen genoprettes. Dette udelukker manuel frakobling."; +"settings.application_settings.leak_protection.title" = "Lækagebeskyttelse"; +"settings.application_settings.leak_protection.footer" = "iOS inkluderer funktioner designet til at fungere uden for VPN'et som standard, såsom AirDrop, CarPlay, AirPlay og personlige hotspots. Aktivering af tilpasset lækagebeskyttelse dirigerer denne trafik gennem VPN'et, men kan påvirke, hvordan disse funktioner fungerer. Mere info"; +"settings.application_settings.leak_protection.more_info" = "Mere info"; +"settings.application_settings.allow_local_network.title" = "Tillad adgang til enheder på det lokale netværk"; +"settings.application_settings.allow_local_network.footer" = "Hold forbindelsen til lokale enheder såsom printere eller filservere, mens du er tilsluttet til VPN'et. (Tillad kun dette, hvis du har tillid til personerne og enhederne på dit netværk.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ blokerer annoncer, trackers og malware, mens du har forbindelse til VPN."; +"settings.application_settings.leak_protection.alert.title" = "Ændringer af VPN-indstillingerne træder i kraft ved næste forbindelse"; "settings.content_blocker.title" = "Safari Indholdsblokeringstilstand"; "settings.content_blocker.state.title" = "Aktuel status"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN-forbindelse-knap. VPN er i øjeblikket forbundet"; "dashboard.accessibility.vpn.button.isOff" = "VPN-forbindelse-knap. VPN er i øjeblikket afbrudt"; +"dashboard.vpn.leakprotection.alert.title" = "Usikret wi-fi registreret"; +"dashboard.vpn.leakprotection.alert.message" = "For at forhindre datalækage skal du trykke på Deaktiver nu for at deaktivere \"Tillad adgang til enheder på det lokale netværk\" og automatisk oprette forbindelse igen."; +"dashboard.vpn.leakprotection.alert.cta1" = "Deaktiver nu"; +"dashboard.vpn.leakprotection.alert.cta2" = "Få flere oplysninger"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignorer"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "For at forhindre datalækage skal du trykke på Skift nu for at skifte til IKEv2 VPN-protokollen og automatisk oprette forbindelse igen."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Skift nu"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Vis servicekommunikationsmeddelelser"; "inapp.messages.settings.updated" = "Indstillinger er blevet opdateret"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Region"; +"widget.liveActivity.protocol.title" = "Protokol"; diff --git a/PIA VPN/de.lproj/Localizable.strings b/PIA VPN/de.lproj/Localizable.strings index 3ad30822c..e5ec3a0d7 100644 --- a/PIA VPN/de.lproj/Localizable.strings +++ b/PIA VPN/de.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Verlängerung"; "expiration.message" = "Dein Abonnement endet bald. Verlängere deinen Schutz."; +"local_notification.non_compliant_wifi.title" = "Ungesichertes WLAN: %@"; +"local_notification.non_compliant_wifi.text" = "Tippe hier, um dein Gerät zu schützen"; + // SHORTCUTS "shortcuts.connect" = "Verbinden"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Aktives Theme"; "settings.application_settings.kill_switch.title" = "VPN-Killswitch"; "settings.application_settings.kill_switch.footer" = "Der VPN-Notausschalter unterbricht den Internetzugang, wenn die VPN-Verbindung neu aufgebaut wird. Dies schließt manuelles Trennen aus."; +"settings.application_settings.leak_protection.title" = "Schutz vor Datenlecks"; +"settings.application_settings.leak_protection.footer" = "iOS enthält Funktionen, die standardmäßig außerhalb des VPNs funktionieren, wie AirDrop, CarPlay, AirPlay und „Persönliche Hotspots“. Die Aktivierung des benutzerdefinierten Schutzes vor Datenlecks leitet diesen Datenverkehr durch das VPN, kann aber die Funktionsweise dieser Funktionen beeinträchtigen. Weitere Informationen"; +"settings.application_settings.leak_protection.more_info" = "Weitere Informationen"; +"settings.application_settings.allow_local_network.title" = "Zugriff auf Geräte im lokalen Netzwerk erlauben"; +"settings.application_settings.allow_local_network.footer" = "Die Verbindung zu lokalen Geräten wie Druckern oder Dateiservern während einer Verbindung zum VPN halten. (Erlaube dies nur, wenn du den Personen und Geräten in deinem Netzwerk vertraust.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ blockiert Werbung, Tracker und Malware bei aktiver VPN-Verbindung."; +"settings.application_settings.leak_protection.alert.title" = "Änderungen an den VPN-Einstellungen werden bei der nächsten Verbindung aktiv"; "settings.content_blocker.title" = "Status Inhalts-Blocker für Safari"; "settings.content_blocker.state.title" = "Aktueller Status"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN-Verbindungs-Schaltfläche. VPN derzeit verbunden"; "dashboard.accessibility.vpn.button.isOff" = "VPN-Verbindungs-Schaltfläche. VPN derzeit nicht verbunden"; +"dashboard.vpn.leakprotection.alert.title" = "Ungesichertes WLAN erkannt"; +"dashboard.vpn.leakprotection.alert.message" = "Um Datenlecks zu verhindern, tippe auf „Jetzt deaktivieren“, um „Zugriff auf Geräte im lokalen Netzwerk erlauben“ zu deaktivieren und automatisch erneut eine Verbindung herzustellen."; +"dashboard.vpn.leakprotection.alert.cta1" = "Jetzt deaktivieren"; +"dashboard.vpn.leakprotection.alert.cta2" = "Mehr erfahren"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignorieren"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Um Datenlecks zu verhindern, tippe auf „Jetzt wechseln“, um zum IKEv2 VPN-Protokoll zu wechseln und automatisch erneut eine Verbindung herzustellen."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Jetzt wechseln"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Meldungen der Servicekommunikation anzeigen"; "inapp.messages.settings.updated" = "Die Einstellungen wurden aktualisiert"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Region"; +"widget.liveActivity.protocol.title" = "Protokoll"; diff --git a/PIA VPN/en.lproj/Localizable.strings b/PIA VPN/en.lproj/Localizable.strings index 8d0c2ac64..489233b1a 100644 --- a/PIA VPN/en.lproj/Localizable.strings +++ b/PIA VPN/en.lproj/Localizable.strings @@ -230,6 +230,9 @@ "dashboard.vpn.leakprotection.alert.cta2" = "Learn more"; "dashboard.vpn.leakprotection.alert.cta3" = "Ignore"; +"dashboard.vpn.leakprotection.ikev2.alert.message" = "To prevent data leaks, tap Switch Now to change to the IKEv2 VPN protocol and automatically reconnect."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Switch Now"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -431,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Show Service Communication Messages"; "inapp.messages.settings.updated" = "Settings have been updated"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Region"; +"widget.liveActivity.protocol.title" = "Protocol"; diff --git a/PIA VPN/es-MX.lproj/Localizable.strings b/PIA VPN/es-MX.lproj/Localizable.strings index f55860ac2..3e646e38d 100644 --- a/PIA VPN/es-MX.lproj/Localizable.strings +++ b/PIA VPN/es-MX.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Renovación"; "expiration.message" = "Tu suscripción expira pronto. Renueva para estar protegido."; +"local_notification.non_compliant_wifi.title" = "Wifi no protegida: %@"; +"local_notification.non_compliant_wifi.text" = "Toca aquí para proteger el dispositivo"; + // SHORTCUTS "shortcuts.connect" = "Conectar"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Activar tema"; "settings.application_settings.kill_switch.title" = "Interruptor de corte de VPN"; "settings.application_settings.kill_switch.footer" = "El botón de pánico de VPN evita el acceso a Internet si la conexión VPN se está volviendo a conectar. Esto excluye la desconexión manual."; +"settings.application_settings.leak_protection.title" = "Protección contra filtraciones"; +"settings.application_settings.leak_protection.footer" = "iOS incluye funciones diseñadas para operar fuera de la VPN por omisión, como AirDrop, CarPlay, AirPlay y los puntos de acceso personales. Habilitar la protección personalizada contra las filtraciones dirige este tráfico a través de la VPN, pero puede afectar a su funcionamiento. Más información"; +"settings.application_settings.leak_protection.more_info" = "Más información"; +"settings.application_settings.allow_local_network.title" = "Permitir acceso a dispositivos de la red local"; +"settings.application_settings.allow_local_network.footer" = "Mantente conectado a dispositivos locales como impresoras o servidores de archivos mientras estés conectado a la VPN. (Solo debes permitirlo si confías en las personas y los dispositivos de tu red.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ bloquea publicidad, rastreadores y malware mientras estás conectado a la VPN."; +"settings.application_settings.leak_protection.alert.title" = "Los cambios en la configuración de la VPN tendrán efecto en la próxima conexión"; "settings.content_blocker.title" = "Estado del bloqueador de contenido de Safari"; "settings.content_blocker.state.title" = "Estado actual"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "Botón de conexión de VPN. La VPN está conectada en este momento."; "dashboard.accessibility.vpn.button.isOff" = "Botón de conexión de VPN. La VPN está desconectada en este momento."; +"dashboard.vpn.leakprotection.alert.title" = "Detectada red wifi no protegida"; +"dashboard.vpn.leakprotection.alert.message" = "Para evitar filtraciones de datos, toca en \"Desactivar ahora\" para desactivar la opción \"Permitir acceso a dispositivos de la red local\" y restablecer la conexión automáticamente."; +"dashboard.vpn.leakprotection.alert.cta1" = "Desactivar ahora"; +"dashboard.vpn.leakprotection.alert.cta2" = "Más información"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignorar"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Para evitar las filtraciones de datos, toca en \"Cambiar ahora\" para cambiar al protocolo VPN IKEv2 y restablecer la conexión automáticamente."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Cambiar ahora"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Mostrar mensajes de comunicación de servicio"; "inapp.messages.settings.updated" = "Se actualizaron los ajustes"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Región"; +"widget.liveActivity.protocol.title" = "Protocolo"; diff --git a/PIA VPN/fr.lproj/Localizable.strings b/PIA VPN/fr.lproj/Localizable.strings index ad9c5c439..b2d8e6082 100644 --- a/PIA VPN/fr.lproj/Localizable.strings +++ b/PIA VPN/fr.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Renouvellement"; "expiration.message" = "Votre adhésion expire bientôt. Renouvelez pour rester protégé."; +"local_notification.non_compliant_wifi.title" = "Wi-Fi non sécurisé : %@"; +"local_notification.non_compliant_wifi.text" = "Appuyez ici pour sécuriser votre appareil"; + // SHORTCUTS "shortcuts.connect" = "Se connecter"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Thème actif"; "settings.application_settings.kill_switch.title" = "Killswitch du VPN"; "settings.application_settings.kill_switch.footer" = "Le killswitch du VPN empêche l'accès à Internet si la connexion VPN est en cours de reconnexion. Cela exclut la déconnexion manuelle."; +"settings.application_settings.leak_protection.title" = "Protection contre les fuites de données"; +"settings.application_settings.leak_protection.footer" = "iOS comprend des fonctionnalités conçues pour opérer par défaut en dehors du VPN, comme AirDrop, CarPlay, AirPlay et le partage de connexion. L’activation de la protection personnalisée contre les fuites de données achemine ce trafic à travers le VPN, mais risque de perturber ces fonctionnalités. En savoir plus"; +"settings.application_settings.leak_protection.more_info" = "En savoir plus"; +"settings.application_settings.allow_local_network.title" = "Autoriser l’accès aux appareils sur le réseau local"; +"settings.application_settings.allow_local_network.footer" = "Restez connecté aux appareils locaux tels que les imprimantes ou les serveurs de fichiers tout en étant connecté au VPN. (N’accordez cette autorisation que si vous faites confiance aux personnes et aux appareils de votre réseau.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ bloque les publicités, les traqueurs et les logiciels malveillants quand vous êtes connecté au VPN."; +"settings.application_settings.leak_protection.alert.title" = "Les modifications apportées aux réglages VPN prendront effet à la prochaine connexion"; "settings.content_blocker.title" = "État du bloqueur de contenu Safari"; "settings.content_blocker.state.title" = "État actuel"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "Bouton de connexion VPN. Le VPN est actuellement connecté"; "dashboard.accessibility.vpn.button.isOff" = "Bouton de connexion VPN. Le VPN est actuellement déconnecté"; +"dashboard.vpn.leakprotection.alert.title" = "Wi-Fi non sécurisé détecté"; +"dashboard.vpn.leakprotection.alert.message" = "Pour éviter les fuites de données, appuyez sur « Désactiver » afin de désactiver l’option « Autoriser l’accès aux appareils sur le réseau local » et permettre la reconnexion automatique."; +"dashboard.vpn.leakprotection.alert.cta1" = "Désactiver"; +"dashboard.vpn.leakprotection.alert.cta2" = "En savoir plus"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignorer"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Pour éviter les fuites de données, appuyez sur « Changer » afin de passer au protocole VPN IKEv2 et de permettre la reconnexion automatique."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Changer"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Afficher les messages de communication de service"; "inapp.messages.settings.updated" = "Les paramètres ont été mis à jour"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Région"; +"widget.liveActivity.protocol.title" = "Protocole"; diff --git a/PIA VPN/it.lproj/Localizable.strings b/PIA VPN/it.lproj/Localizable.strings index fa8d23a0c..5fef616f0 100644 --- a/PIA VPN/it.lproj/Localizable.strings +++ b/PIA VPN/it.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Rinnovo"; "expiration.message" = "Il tuo abbonamento scade presto. Rinnovalo per mantenere la protezione."; +"local_notification.non_compliant_wifi.title" = "Wi-Fi non protetto: %@"; +"local_notification.non_compliant_wifi.text" = "Tocca qui per proteggere il tuo dispositivo"; + // SHORTCUTS "shortcuts.connect" = "Connetti"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Tema attivo"; "settings.application_settings.kill_switch.title" = "Killswitch VPN"; "settings.application_settings.kill_switch.footer" = "Il killswitch VPN impedisce l'accesso a Internet quando la connessione VPN si sta riconnettendo. Questo esclude la disconnessione manuale."; +"settings.application_settings.leak_protection.title" = "Protezione perdita dati"; +"settings.application_settings.leak_protection.footer" = "iOS include funzioni per operare di default al di fuori della VPN, come AirDrop, CarPlay, AirPlay e hotspot personali. L’attivazione di una protezione personalizzata contro la perdita dei dati instrada questo traffico attraverso la VPN, ma può influire su queste funzioni. Ulteriori informazioni"; +"settings.application_settings.leak_protection.more_info" = "Ulteriori informazioni"; +"settings.application_settings.allow_local_network.title" = "Consenti l’accesso ai dispositivi sulla rete locale"; +"settings.application_settings.allow_local_network.footer" = "Rimani connesso ai dispositivi locali come stampanti o file server durante la connessione alla VPN. (Consenti solo se ti fidi delle persone e dei dispositivi sulla rete.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ blocca pubblicità, tracker e malware mentre sei connesso alla VPN."; +"settings.application_settings.leak_protection.alert.title" = "Le modifiche alle impostazioni della VPN avranno effetto alla prossima connessione"; "settings.content_blocker.title" = "Stato di blocco contenuti Safari"; "settings.content_blocker.state.title" = "Stato corrente"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "Pulsante di connessione VPN. VPN attualmente connesso"; "dashboard.accessibility.vpn.button.isOff" = "Pulsante di connessione VPN. VPN attualmente disconnesso"; +"dashboard.vpn.leakprotection.alert.title" = "Rilevato Wi-Fi non sicuro"; +"dashboard.vpn.leakprotection.alert.message" = "Per prevenire perdite di dati, tocca Disabilita ora per disattivare “Consenti l’accesso ai dispositivi sulla rete locale“ e riconnettersi automaticamente."; +"dashboard.vpn.leakprotection.alert.cta1" = "Disabilita ora"; +"dashboard.vpn.leakprotection.alert.cta2" = "Ulteriori informazioni"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignora"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Per prevenire le perdite di dati, tocca Cambia ora per passare al protocollo VPN IKEv2 e riconnettersi automaticamente."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Cambia ora"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Mostra i messaggi di comunicazione sul servizio"; "inapp.messages.settings.updated" = "Impostazioni aggiornate"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Area"; +"widget.liveActivity.protocol.title" = "Protocollo"; diff --git a/PIA VPN/ja.lproj/Localizable.strings b/PIA VPN/ja.lproj/Localizable.strings index 468ed7676..fea22e5db 100644 --- a/PIA VPN/ja.lproj/Localizable.strings +++ b/PIA VPN/ja.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "更新"; "expiration.message" = "ご利用のサブスクリプションがもうすぐ期限切れとなります。更新して保護された状態を保ちましょう。"; +"local_notification.non_compliant_wifi.title" = "保護されていないWiFi:%@"; +"local_notification.non_compliant_wifi.text" = "お使いのデバイスを保護するには、ここをタップしてください"; + // SHORTCUTS "shortcuts.connect" = "接続"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "アクティブなテーマ"; "settings.application_settings.kill_switch.title" = "VPNキルスイッチ"; "settings.application_settings.kill_switch.footer" = "VPNキルスイッチは、VPNの再接続時にインターネット接続を無効にします。これには手動での切断が含まれません。"; +"settings.application_settings.leak_protection.title" = "漏えい防止"; +"settings.application_settings.leak_protection.footer" = "iOSには、デフォルトでAirDrop、CarPlay、AirPlay、パーソナルホットスポットなど、VPNの外部で動作するように設計された機能が含まれています。カスタムリーク保護機能を有効にすると、このトラフィックはVPN経由でルーティングされますが、これらの機能の動作に影響する可能性があります。詳細"; +"settings.application_settings.leak_protection.more_info" = "詳細"; +"settings.application_settings.allow_local_network.title" = "ローカルネットワーク内のデバイスへのアクセスを許可"; +"settings.application_settings.allow_local_network.footer" = "VPNに接続している間、プリンタやファイルサーバーなどのローカルデバイスへの接続を維持できます(ネットワーク内のユーザーとデバイスを信頼できる場合にのみ許可してください。)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™はVPNに接続されている間、広告、閲覧行動の追跡、そしてマルウェアをブロックします。"; +"settings.application_settings.leak_protection.alert.title" = "VPN設定への変更は次回の接続時に反映されます"; "settings.content_blocker.title" = "Safariコンテンツブロッカーの状態"; "settings.content_blocker.state.title" = "現在の状況"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN接続ボタン。VPNに現在接続されています"; "dashboard.accessibility.vpn.button.isOff" = "VPN接続ボタン。VPNは現在切断されています"; +"dashboard.vpn.leakprotection.alert.title" = "保護されていないWiFiが検出されました"; +"dashboard.vpn.leakprotection.alert.message" = "データ漏洩を防ぐには、[今すぐ無効にする]をタップして[ローカルネットワーク内のデバイスへのアクセスを許可]をオフにし、自動的に再接続してください。"; +"dashboard.vpn.leakprotection.alert.cta1" = "今すぐ無効にする"; +"dashboard.vpn.leakprotection.alert.cta2" = "詳細"; +"dashboard.vpn.leakprotection.alert.cta3" = "無視"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "データ漏洩を防ぐには、[今すぐ切り替える]をタップしてIKEv2 VPNプロトコルに変更し、自動的に再接続してください。"; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "今すぐ切り替える"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "サービス通信メッセージを表示"; "inapp.messages.settings.updated" = "設定が更新されました"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "地域"; +"widget.liveActivity.protocol.title" = "プロトコル"; diff --git a/PIA VPN/ko.lproj/Localizable.strings b/PIA VPN/ko.lproj/Localizable.strings index c156af612..029680fff 100644 --- a/PIA VPN/ko.lproj/Localizable.strings +++ b/PIA VPN/ko.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "갱신"; "expiration.message" = "구독이 곧 만료됩니다. 갱신하여 계속 보호를 받으세요."; +"local_notification.non_compliant_wifi.title" = "비보안 Wi-Fi: %@"; +"local_notification.non_compliant_wifi.text" = "장치를 보호하려면 여기를 누르세요."; + // SHORTCUTS "shortcuts.connect" = "연결"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "활성화된 테마"; "settings.application_settings.kill_switch.title" = "VPN 킬 스위치"; "settings.application_settings.kill_switch.footer" = "VPN 킬 스위치는 VPN 연결이 다시 연결 중이면 인터넷에 대한 액세스를 방지합니다. 수동으로 연결을 해제하는 경우는 제외입니다."; +"settings.application_settings.leak_protection.title" = "보호 누출"; +"settings.application_settings.leak_protection.footer" = "iOS에는 기본적으로 AirDrop, CarPlay, AirPlay, 개인 핫스팟과 같이 VPN 외부에서 작동하도록 설계된 기능이 포함됩니다. 사용자 지정 누출 보호를 활성화하면 이 트래픽이 VPN을 통해 라우팅되지만 이러한 기능의 작동 방식에 영향을 줄 수 있습니다. 자세히 알아보기"; +"settings.application_settings.leak_protection.more_info" = "자세히 알아보기"; +"settings.application_settings.allow_local_network.title" = "로컬 네트워크의 장치 액세스 허용"; +"settings.application_settings.allow_local_network.footer" = "VPN에 연결된 상태에서 프린터 또는 파일 서버와 같은 로컬 장치 연결을 유지하세요. (네트워크의 사용자 및 장치를 신뢰하는 경우에만 이 옵션 허용)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™는 VPN에 연결되어 있을 때 광고, 트래커 및 악성코드를 차단합니다."; +"settings.application_settings.leak_protection.alert.title" = "VPN 설정을 변경하면 다음에 연결할 때 적용됩니다."; "settings.content_blocker.title" = "Safari 콘텐츠 차단기 상태"; "settings.content_blocker.state.title" = "현재 상태"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN 연결 버튼. VPN이 현재 연결되었습니다"; "dashboard.accessibility.vpn.button.isOff" = "VPN 연결 버튼. VPN이 현재 연결 해제되었습니다"; +"dashboard.vpn.leakprotection.alert.title" = "안전하지 않은 Wi-Fi 감지됨"; +"dashboard.vpn.leakprotection.alert.message" = "데이터 누출을 방지하려면 지금 비활성화를 눌러서 \"로컬 네트워크의 장치 액세스 허용\"을 해제하고 자동으로 다시 연결하세요."; +"dashboard.vpn.leakprotection.alert.cta1" = "지금 비활성화"; +"dashboard.vpn.leakprotection.alert.cta2" = "자세히 알아보기"; +"dashboard.vpn.leakprotection.alert.cta3" = "무시"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "데이터 누출을 방지하려면 지금 전환을 눌러서 IKEv2 VPN 프로토콜로 변경하고 자동으로 다시 연결하세요."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "지금 전환"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "서비스 통신 메시지 표시"; "inapp.messages.settings.updated" = "설정이 업데이트되었습니다"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "지역"; +"widget.liveActivity.protocol.title" = "프로토콜"; diff --git a/PIA VPN/nb.lproj/Localizable.strings b/PIA VPN/nb.lproj/Localizable.strings index 22237a276..bed2092a0 100644 --- a/PIA VPN/nb.lproj/Localizable.strings +++ b/PIA VPN/nb.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Forny"; "expiration.message" = "Abonnementet ditt utløper snart. Forny det for å forbli beskyttet."; +"local_notification.non_compliant_wifi.title" = "Usikret WiFi:%@"; +"local_notification.non_compliant_wifi.text" = "Trykk her for å sikre enheten din"; + // SHORTCUTS "shortcuts.connect" = "Koble til"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Aktivt tema"; "settings.application_settings.kill_switch.title" = "Kill switch for VPN"; "settings.application_settings.kill_switch.footer" = "Kill switch for VPN hindrer tilgang til Internett hvis VPN-tilkoblingen mister forbindelsen og prøver å koble til igjen. Dette ekskluderer manuell frakobling."; +"settings.application_settings.leak_protection.title" = "Lekkasjebeskyttelse"; +"settings.application_settings.leak_protection.footer" = "iOS har funksjoner som er utviklet for å fungere utenfor VPN-et, for eksempel AirDrop, CarPlay, AirPlay og delt Internett. Ved å aktivere tilpasset lekkasjebeskyttelse kan du rute denne trafikken gjennom VPN-et, men det kan påvirke hvordan disse funksjonene fungerer. Mer informasjon"; +"settings.application_settings.leak_protection.more_info" = "Mer informasjon"; +"settings.application_settings.allow_local_network.title" = "Tillat tilgang til enheter på lokalnettverk"; +"settings.application_settings.allow_local_network.footer" = "Behold tilgang til lokale enheter som skrivere eller filservere mens du er koblet til VPN-et. (Tillat dette bare hvis du stoler på personene og enhetene på nettverket ditt.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ blokkerer reklamer, sporere og skadelig programvare når du er koblet til VPN-en."; +"settings.application_settings.leak_protection.alert.title" = "Endringer i VPN-innstillingene vil tre i kraft ved neste tilkobling"; "settings.content_blocker.title" = "Safari Innholdsblokkerer – status"; "settings.content_blocker.state.title" = "Gjeldene status"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN-tilkoblingsknapp. VPN-klienten er for øyeblikket tilkoblet"; "dashboard.accessibility.vpn.button.isOff" = "VPN-tilkoblingsknapp. VPN-klienten er for øyeblikket frakoblet"; +"dashboard.vpn.leakprotection.alert.title" = "Usikret WiFi oppdaget"; +"dashboard.vpn.leakprotection.alert.message" = "For å hindre datalekkasjer trykker du på Deaktiver nå for å slå av \"Tillat tillat tilgang til enheter på lokalnettverk\" og automatisk koble til på nytt."; +"dashboard.vpn.leakprotection.alert.cta1" = "Deaktiver nå"; +"dashboard.vpn.leakprotection.alert.cta2" = "Les mer"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignorer"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "For å hindre datalekkasjer trykker du på Bytt nå for å bytte til VPN-protokollen IKEv2 og automatisk koble til på nytt."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Bytt nå"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Vis tjenestekommunikasjonsmeldinger"; "inapp.messages.settings.updated" = "Innstillingene ble oppdatert"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Region"; +"widget.liveActivity.protocol.title" = "Protokoll"; diff --git a/PIA VPN/nl.lproj/Localizable.strings b/PIA VPN/nl.lproj/Localizable.strings index f1f62738f..c6153cfd9 100644 --- a/PIA VPN/nl.lproj/Localizable.strings +++ b/PIA VPN/nl.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Vernieuwing"; "expiration.message" = "Uw abonnement verloopt binnenkort. Vernieuw uw abonnement om beschermd te blijven."; +"local_notification.non_compliant_wifi.title" = "Onbeveiligde wifi: %@"; +"local_notification.non_compliant_wifi.text" = "Tik hier om je apparaat te beschermen"; + // SHORTCUTS "shortcuts.connect" = "Verbinden"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Actief thema"; "settings.application_settings.kill_switch.title" = "VPN-killswitch"; "settings.application_settings.kill_switch.footer" = "De VPN kill switch voorkomt toegang tot internet als de VPN-verbinding opnieuw tot stand wordt gebracht. Dit geldt niet wanneer de verbinding handmatig wordt verbroken."; +"settings.application_settings.leak_protection.title" = "Lekbescherming"; +"settings.application_settings.leak_protection.footer" = "iOS bevat functies die ontworpen zijn om standaard te werken buiten het VPN, zoals AirDrop, CarPlay, AirPlay en persoonlijke hotspots. Het inschakelen van aangepaste lekbescherming leidt dit verkeer om via het VPN, maar kan de werking van deze functies beïnvloeden. Meer info"; +"settings.application_settings.leak_protection.more_info" = "Meer info"; +"settings.application_settings.allow_local_network.title" = "Sta toegang toe tot apparaten op een lokaal netwerk"; +"settings.application_settings.allow_local_network.footer" = "Blijf verbonden met lokale apparaten, zoals printers of bestandsservers die verbonden zijn met het VPN. (Sta dit alleen toe als je de personen en apparaten op je netwerk vertrouwt.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ blokkeert advertenties, trackers en malware als u met de VPN bent verbonden."; +"settings.application_settings.leak_protection.alert.title" = "Wijzigingen aan de VPN-instellingen worden van kracht bij de volgende verbinding"; "settings.content_blocker.title" = "Status Safari Content Blocker"; "settings.content_blocker.state.title" = "Huidige status"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN-verbindingsknop. De VPN is verbonden"; "dashboard.accessibility.vpn.button.isOff" = "VPN-verbindingsknop. De VPN is niet verbonden"; +"dashboard.vpn.leakprotection.alert.title" = "Onbeveiligde wifi gedetecteerd"; +"dashboard.vpn.leakprotection.alert.message" = "Om gegevenslekken te voorkomen, tik je op \"Schakel nu uit\" om \"Sta toegang toe tot apparaten op een lokaal netwerk\" uit te schakelen en automatisch opnieuw verbinding te maken."; +"dashboard.vpn.leakprotection.alert.cta1" = "Schakel nu uit"; +"dashboard.vpn.leakprotection.alert.cta2" = "Meer informatie"; +"dashboard.vpn.leakprotection.alert.cta3" = "Negeer"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Om gegevenslekken te voorkomen, tik je op \"Schakel nu\" om te wijzigen naar het IKEv2 VPN-protocol en automatisch opnieuw verbinding te maken."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Schakel nu"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Service-communicatieberichten weergeven"; "inapp.messages.settings.updated" = "De instellingen zijn bijgewerkt"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Regio"; +"widget.liveActivity.protocol.title" = "Protocol"; diff --git a/PIA VPN/pl.lproj/Localizable.strings b/PIA VPN/pl.lproj/Localizable.strings index fe3905363..2e2f5fd85 100644 --- a/PIA VPN/pl.lproj/Localizable.strings +++ b/PIA VPN/pl.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Odnowienie"; "expiration.message" = "Twoja subskrypcja wkrótce wygasa. Odnów ją, aby utrzymać ochronę."; +"local_notification.non_compliant_wifi.title" = "Niezabezpieczona sieć Wi-Fi: %@"; +"local_notification.non_compliant_wifi.text" = "Dotknij tutaj, aby zabezpieczyć urządzenie"; + // SHORTCUTS "shortcuts.connect" = "Połącz"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Aktywny motyw"; "settings.application_settings.kill_switch.title" = "Wyłącznik awaryjny VPN"; "settings.application_settings.kill_switch.footer" = "Wyłącznik awaryjny VPN zapobiega dostępowi do internetu, jeśli połączenie VPN jest nawiązywane ponownie. Nie obejmuje to ręcznego rozłączenia."; +"settings.application_settings.leak_protection.title" = "Ochrona przed wyciekami"; +"settings.application_settings.leak_protection.footer" = "iOS zawiera funkcje zaprojektowane domyślnie do działania poza VPN, takie jak AirDrop, CarPlay, AirPlay i osobiste hotspoty. Włączenie niestandardowej ochrony przed wyciekiem kieruje ten ruch przez VPN, ale może wpłynąć na działanie tych funkcji. Więcej informacji"; +"settings.application_settings.leak_protection.more_info" = "Więcej informacji"; +"settings.application_settings.allow_local_network.title" = "Zezwól na dostęp do urządzeń w sieci lokalnej"; +"settings.application_settings.allow_local_network.footer" = "Pozostań w kontakcie z lokalnymi urządzeniami, takimi jak drukarki lub serwery plików, podczas połączenia z VPN. (Zezwalaj na to tylko wtedy, gdy ufasz osobom i urządzeniom w Twojej sieci)."; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ blokuje reklamy, programy śledzące i złośliwe oprogramowanie w czasie połączenia przez VPN."; +"settings.application_settings.leak_protection.alert.title" = "Zmiany w Ustawieniach VPN zostaną zastosowane przy kolejnym nawiązaniu połączenia"; "settings.content_blocker.title" = "Stan blokady zawartości Safari"; "settings.content_blocker.state.title" = "Bieżący stan"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "Przycisk połączenia z VPN. VPN jest teraz podłączona."; "dashboard.accessibility.vpn.button.isOff" = "Przycisk połączenia z VPN. VPN jest teraz odłączona."; +"dashboard.vpn.leakprotection.alert.title" = "Wykryto niezabezpieczoną sieć Wi-Fi"; +"dashboard.vpn.leakprotection.alert.message" = "Aby zapobiec wyciekom danych, dotknij Wyłącz teraz, aby wyłączyć opcję „Zezwól na dostęp do urządzeń w sieci lokalnej” i automatycznie połączyć się ponownie."; +"dashboard.vpn.leakprotection.alert.cta1" = "Wyłącz teraz"; +"dashboard.vpn.leakprotection.alert.cta2" = "Dowiedz się więcej"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignoruj"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Aby zapobiec wyciekom danych, dotknij opcji Przełącz teraz, aby zmienić protokół na IKEv2 VPN i automatycznie połączyć się ponownie."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Przełącz teraz"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Pokaż komunikaty dotyczące komunikacji usługi"; "inapp.messages.settings.updated" = "Zaktualizowano ustawienia"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Region"; +"widget.liveActivity.protocol.title" = "Protokół"; diff --git a/PIA VPN/pt-BR.lproj/Localizable.strings b/PIA VPN/pt-BR.lproj/Localizable.strings index 470b23c15..c4fa37acd 100644 --- a/PIA VPN/pt-BR.lproj/Localizable.strings +++ b/PIA VPN/pt-BR.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Renovação"; "expiration.message" = "Sua assinatura expirará em breve. Renove para continuar protegido."; +"local_notification.non_compliant_wifi.title" = "Wi-Fi não seguro: %@"; +"local_notification.non_compliant_wifi.text" = "Toque aqui para proteger seu dispositivo"; + // SHORTCUTS "shortcuts.connect" = "Conectar"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Tema ativo"; "settings.application_settings.kill_switch.title" = "Kill switch de VPN"; "settings.application_settings.kill_switch.footer" = "O kill switch do VPN impede o acesso à internet se a conexão de VPN estiver se reconectando. Isto exclui a desconexão manual."; +"settings.application_settings.leak_protection.title" = "Proteção contra vazamentos"; +"settings.application_settings.leak_protection.footer" = "O iOS inclui recursos projetados para operar fora da VPN por padrão, como AirDrop, CarPlay, AirPlay e Acesso Pessoal. A ativação da proteção personalizada contra vazamentos roteia esse tráfego por meio da VPN, mas pode afetar o funcionamento desses recursos. Mais informações"; +"settings.application_settings.leak_protection.more_info" = "Mais informações"; +"settings.application_settings.allow_local_network.title" = "Permitir acesso a dispositivos na rede local"; +"settings.application_settings.allow_local_network.footer" = "Permaneça conectado a dispositivos locais, como impressoras ou servidores de arquivos, enquanto estiver conectado à VPN. (Permita essa opção apenas se você confiar nas pessoas e nos dispositivos da sua rede.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "O PIA MACE™ bloqueia anúncios, rastreadores e malware enquanto você está conectado à VPN."; +"settings.application_settings.leak_protection.alert.title" = "As alterações nas configurações da VPN entrarão em vigor na próxima conexão"; "settings.content_blocker.title" = "Status do bloqueador de conteúdo do Safari"; "settings.content_blocker.state.title" = "Situação atual"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "Botão de conexão da VPN. A VPN está conectada no momento."; "dashboard.accessibility.vpn.button.isOff" = "Botão de conexão da VPN. A VPN está desconectada no momento."; +"dashboard.vpn.leakprotection.alert.title" = "Wi-Fi não seguro detectado"; +"dashboard.vpn.leakprotection.alert.message" = "Para evitar vazamentos de dados, toque em “Desabilitar agora” para desativar a opção “Permitir acesso a dispositivos na rede local” e reconectar automaticamente."; +"dashboard.vpn.leakprotection.alert.cta1" = "Desabilitar agora"; +"dashboard.vpn.leakprotection.alert.cta2" = "Saiba mais"; +"dashboard.vpn.leakprotection.alert.cta3" = "Ignorar"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Para evitar vazamentos de dados, toque em “Alternar agora” para mudar para o protocolo VPN IKEv2 e reconectar automaticamente."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Alternar agora"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Mostrar mensagens de comunicação de serviço"; "inapp.messages.settings.updated" = "As configurações foram atualizadas"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Região"; +"widget.liveActivity.protocol.title" = "Protocolo"; diff --git a/PIA VPN/ru.lproj/Localizable.strings b/PIA VPN/ru.lproj/Localizable.strings index f81c82060..b5e47cc79 100644 --- a/PIA VPN/ru.lproj/Localizable.strings +++ b/PIA VPN/ru.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "Продление"; "expiration.message" = "Срок действия подписки истекает в ближайшее время. Продлите подписку, чтобы не потерять защиту."; +"local_notification.non_compliant_wifi.title" = "Незащищенная сеть Wi-Fi: %@"; +"local_notification.non_compliant_wifi.text" = "Нажмите здесь, чтобы защитить ваше устройство"; + // SHORTCUTS "shortcuts.connect" = "Подключиться"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "Активная тема"; "settings.application_settings.kill_switch.title" = "Авторазрыв соединения"; "settings.application_settings.kill_switch.footer" = "Функция авторазрыва соединения рвет Интернет-соединение при отключении VPN. Она не срабатывает при ручном отключении VPN."; +"settings.application_settings.leak_protection.title" = "Защита от утечек"; +"settings.application_settings.leak_protection.footer" = "iOS включает функции, по умолчанию предназначенные для работы вне VPN, такие как AirDrop, CarPlay, AirPlay и Персональная точка доступа. Включение защиты от утечек перенаправляет этот трафик через VPN, но может отразиться на работе этих функций. Подробнее"; +"settings.application_settings.leak_protection.more_info" = "Подробнее"; +"settings.application_settings.allow_local_network.title" = "Разрешить доступ к устройствам в локальной сети"; +"settings.application_settings.allow_local_network.footer" = "Оставайтесь подключенными к устройствам в локальной сети, таким как принтеры или файловые серверы, пользуясь при этом VPN-подключением (включайте эту опцию только если доверяете людям и устройствам в вашей сети.)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ блокирует рекламу, следящие программы и вредоносное ПО, когда вы подключены к VPN."; +"settings.application_settings.leak_protection.alert.title" = "Изменения в настройках VPN будут применены при следующем подключении"; "settings.content_blocker.title" = "Состояние блокировщика контента для Safari"; "settings.content_blocker.state.title" = "Текущее состояние"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "Кнопка VPN-подключения. VPN сейчас подключен"; "dashboard.accessibility.vpn.button.isOff" = "Кнопка VPN-подключения. VPN сейчас отключен"; +"dashboard.vpn.leakprotection.alert.title" = "Обнаружена незащищенная сеть Wi-Fi"; +"dashboard.vpn.leakprotection.alert.message" = "Во избежание утечек данных, нажмите «Отключить сейчас», чтобы выключить опцию «Разрешить доступ к устройствам в локальной сети» и автоматически переподключиться."; +"dashboard.vpn.leakprotection.alert.cta1" = "Отключить сейчас"; +"dashboard.vpn.leakprotection.alert.cta2" = "Узнать больше"; +"dashboard.vpn.leakprotection.alert.cta3" = "Игнорировать"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Для предотвращения утечек данных, нажмите «Переключить сейчас», чтобы перейти на протокол IKEv2 VPN и автоматически переподключиться."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Переключить сейчас"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "Показать служебные сообщения обмена данными"; "inapp.messages.settings.updated" = "Настройки обновлены"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Регион"; +"widget.liveActivity.protocol.title" = "Протокол"; diff --git a/PIA VPN/th.lproj/Localizable.strings b/PIA VPN/th.lproj/Localizable.strings index 5ecc26642..e4f396f7b 100644 --- a/PIA VPN/th.lproj/Localizable.strings +++ b/PIA VPN/th.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "การต่ออายุ"; "expiration.message" = "สถานะสมาชิกของคุณจะหมดอายุเร็วๆนี้ ต่ออายุเพื่อรับการป้องกันต่อ"; +"local_notification.non_compliant_wifi.title" = "Wi-Fi ที่ไม่ปลอดภัย: %@"; +"local_notification.non_compliant_wifi.text" = "แตะที่นี่เพื่อรักษาความปลอดภัยให้อุปกรณ์ของคุณ"; + // SHORTCUTS "shortcuts.connect" = "เชื่อมต่อ"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "ธีมที่ใช้งานอยู่"; "settings.application_settings.kill_switch.title" = "ปุ่มตัดการทำงาน VPN"; "settings.application_settings.kill_switch.footer" = "Kill switch ของ VPN จะป้องกันการเข้าถึงอินเทอร์เน็ตหากการเชื่อมต่อ VPN กำลังเชื่อมต่อใหม่ ไม่รวมการตัดการเชื่อมต่อด้วยตนเอง"; +"settings.application_settings.leak_protection.title" = "การป้องกันการรั่วไหล"; +"settings.application_settings.leak_protection.footer" = "iOS มีคุณสมบัติที่ออกแบบมาเพื่อทำงานนอก VPN ตามค่าเริ่มต้น เช่น AirDrop, CarPlay, AirPlay และฮอตสปอตส่วนบุคคล การเปิดใช้งานการปกป้องการรั่วไหลแบบกำหนดเองจะนำทางการรับส่งข้อมูลเหล่านี้ผ่าน VPN แต่อาจส่งผลกระทบต่อวิธีการทำงานของคุณสมบัติดังกล่าว ข้อมูลเพิ่มเติม"; +"settings.application_settings.leak_protection.more_info" = "ข้อมูลเพิ่มเติม"; +"settings.application_settings.allow_local_network.title" = "อนุญาตการเข้าถึงอุปกรณ์บนเครือข่ายท้องถิ่น"; +"settings.application_settings.allow_local_network.footer" = "คงความเชื่อมต่อกับอุปกรณ์ท้องถิ่นอย่างเครื่องพิมพ์หรือเซิร์ฟเวอร์ไฟล์ระหว่างที่เชื่อมต่อกับ VPN (อนุญาตเฉพาะหากคุณไว้ใจผู้คนและอุปกรณ์ในเครือข่ายของคุณ)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ บล็อกโฆษณา, ตัวติดตาม และมัลแวร์ ในขณะที่คุณกำลังเชื่อมต่อ VPN อยู่"; +"settings.application_settings.leak_protection.alert.title" = "การเปลี่ยนแปลงการตั้งค่า VPN จะมีผลในการเชื่อมต่อครั้งต่อไป"; "settings.content_blocker.title" = "สถานะตัวปิดกั้นเนื้อหา Safari"; "settings.content_blocker.state.title" = "สถานะปัจจุบัน"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "ปุ่มเชื่อมต่อ VPN ขณะนี้ VPN เชื่อมต่ออยู่"; "dashboard.accessibility.vpn.button.isOff" = "ปุ่มเชื่อมต่อ VPN ขณะนี้ VPN ขาดการเชื่อมต่อ"; +"dashboard.vpn.leakprotection.alert.title" = "ตรวจพบ Wi-Fi ที่ไม่ปลอดภัย"; +"dashboard.vpn.leakprotection.alert.message" = "หากต้องการป้องกันการรั่วไหลของข้อมูล ให้แตะ ปิดใช้งานเลย เพื่อปิด “อนุญาตการเข้าถึงอุปกรณ์บนเครือข่ายท้องถิ่น” และเชื่อมต่อใหม่โดยอัตโนมัติ"; +"dashboard.vpn.leakprotection.alert.cta1" = "ปิดใช้งานเลย"; +"dashboard.vpn.leakprotection.alert.cta2" = "เรียนรู้เพิ่มเติม"; +"dashboard.vpn.leakprotection.alert.cta3" = "เพิกเฉย"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "หากต้องการป้องกันการรั่วไหลของข้อมูล ให้แตะ เปลี่ยนเลย เพื่อเปลี่ยนเป็นโปรโตคอล VPN IKEv2 และเชื่อมต่อใหม่โดยอัตโนมัติ"; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "เปลี่ยนเลย"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "แสดงข้อความสื่อสารบริการ"; "inapp.messages.settings.updated" = "อัปเดตการตั้งค่าแล้ว"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "ภูมิภาค"; +"widget.liveActivity.protocol.title" = "โปรโตคอล"; diff --git a/PIA VPN/tr.lproj/Localizable.strings b/PIA VPN/tr.lproj/Localizable.strings index 66f0be472..7a498a6b3 100644 --- a/PIA VPN/tr.lproj/Localizable.strings +++ b/PIA VPN/tr.lproj/Localizable.strings @@ -1,345 +1,440 @@ -"Localizable" = "Yerelleştirilebilir"; -"about.accessibility.component.expand" = "Lisansın tamamını görmek için dokunun"; -"about.app" = "Private Internet Access'ten VPN"; -"about.intro" = "Bu program şu bileşenleri kullanmaktadır:"; -"account.accessibility.eye" = "Göz simgesi"; -"account.accessibility.eye.hint.conceal" = "Şifreyi gizlemek için dokunun"; -"account.accessibility.eye.hint.reveal" = "Şifreyi açmak için dokunun"; -"account.email.caption" = "E-posta"; -"account.email.placeholder" = "E-posta adresi"; -"account.error.unauthorized" = "Kullanıcı adınız ya da şifreniz hatalı."; -"account.expiry_date.expired" = "Planınızın süresi dolmuş."; -"account.expiry_date.information" = "Planınızın süresi %@ tarihinde sona erecek."; -"account.eye.footer" = "Şifrenizi açmak ya da gizlemek için göz simgesine dokunun."; -"account.other.footer" = "Diğer cihazlarınıza Private Internet Access uygulamasını yükleyip yukarıdaki kullanıcı adı ve şifre ile giriş yaparak İnternet'e güvenli şekilde bağlanın."; -"account.password.caption" = "Şifre"; -"account.restore.button" = "Satın alınanı geri yükle"; -"account.restore.description" = "Planınızı yenilemenize rağmen hesabınızdan hâlâ süresinin dolmak üzere olduğu uyarısını alıyorsanız, buradan yenilemeyi baştan başlatabilirsiniz. Bu işlem için sizden para kesilmeyecektir."; -"account.restore.failure.message" = "Yenileme işlemi için geri yüklenebilecek satın alım işlemi bulunamadı."; -"account.restore.failure.title" = "Satın alınanı geri yükle"; -"account.restore.title" = "Hesaba yansıtılmamış satın alma işlemini geri yükleyin"; -"account.reveal.prompt" = "Ortaya çıkarmak için doğrulayın"; -"account.save.item" = "E-postayı güncelle"; -"account.save.prompt" = "Değişiklikleri kaydetmek için doğrulayın"; -"account.save.success" = "E-posta adresiniz kaydedildi."; -"account.set.email.error" = "E-posta adresi eklenirken bir hata oluştu. Lütfen daha sonra tekrar deneyin."; -"account.subscriptions.linkMessage" = "buraya tıklayın."; -"account.subscriptions.message" = "Üyelik ayarlarınızı değiştirmek için buraya tıklayın."; -"account.subscriptions.monthly" = "Aylık plan"; -"account.subscriptions.short.linkMessage" = "Aboneliği yönetin"; -"account.subscriptions.short.message" = "Aboneliği yönetin"; -"account.subscriptions.trial" = "Deneme planı"; -"account.subscriptions.yearly" = "Yıllık plan"; -"account.unauthorized" = "Bir sorun oluştu. Lütfen tekrar giriş yapmayı deneyin"; -"account.update.email.require.password.button" = "Gönder"; -"account.update.email.require.password.message" = "Güvenlik açısından hesabınızda değişiklik yapabilmeniz için PIA şifrenizi girmelisiniz. Devam etmek için lütfen PIA şifrenizi girin."; -"account.update.email.require.password.title" = "PIA Şifresi Gerekli"; -"account.username.caption" = "Kullanıcı Adı"; -"card.wireguard.cta.activate" = "WireGuard®'ı şimdi deneyin"; -"card.wireguard.cta.learn" = "Daha fazla bilgi"; -"card.wireguard.cta.settings" = "Ayarları açın"; -"card.wireguard.description" = "Bu, daha iyi performans, daha düşük CPU kullanımı ve daha uzun pil ömrü sunan, yeni, daha verimli bir VPN protokolü."; -"card.wireguard.title" = "WireGuard®'ı bugün deneyin!"; -"content_blocker.body.footer" = "Lütfen unutmayın: Bu İçerik Engelleyicinin çalışması için VPN bağlantınızın olmasına gerek yoktur ancak yalnızca Safari ile gezindiğinizde çalışacaktır."; -"content_blocker.body.subtitle" = "İçerik Engelleyicimizin Safari ile kullanılmasını etkinleştirmek için lütfen Genel bölümü altından Ayarlar > Safari kısmına gidin ve PIA VPN'de İçerik Engelleyicileri açık hale getirin."; -"content_blocker.title" = "Safari İçerik Engelleyici"; -"dashboard.accessibility.vpn.button" = "VPN Bağlantı düğmesi"; -"dashboard.accessibility.vpn.button.isOff" = "VPN Bağlantı düğmesi. Şu anda VPN'ye bağlı değil"; -"dashboard.accessibility.vpn.button.isOn" = "VPN Bağlantı düğmesi. Şu anda VPN'ye bağlı değil"; -"dashboard.connection.ip.caption" = "HERKESE AÇIK IP"; -"dashboard.connection.ip.unreachable" = "İnternet'e erişilemiyor"; -"dashboard.connection.region.caption" = "MEVCUT BÖLGE"; -"dashboard.connection.region.change" = "BÖLGEYİ DEĞİŞTİR"; -"dashboard.content_blocker.intro.message" = "Bu sürüm, MACE'i Safari İçerik Engelleyicimizle değiştirir.\n\n'Ayarlar' bölümünde bunu işaretleyin."; -"dashboard.status" = "Durum"; -"dashboard.vpn.changing_region" = "Bölge değiştiriliyor..."; -"dashboard.vpn.connected" = "VPN'ye Bağlandı"; -"dashboard.vpn.connecting" = "Bağlanıyor..."; -"dashboard.vpn.disconnect.untrusted" = "Bu güvenilir bir ağ değil. VPN bağlantısını kesmek istediğinizden emin misiniz?"; -"dashboard.vpn.disconnected" = "Bağlantı Kesildi"; -"dashboard.vpn.disconnecting" = "Bağlantı Kesiliyor"; -"dashboard.vpn.on" = "VPN: AÇIK"; -"dedicated.ip.activate.button.title" = "Etkinleştir"; -"dedicated.ip.activation.description" = "Belirtecinizi aşağı yapıştırarak Özel IP'nizi etkinleştirin. Kısa süre önce bir özel IP aldıysanız PIA web sitesine giderek belirteci üretebilirsiniz."; -"dedicated.ip.country.flag.accessibility" = "%@ için ülke bayrağı"; -"dedicated.ip.limit.title" = "Seçeceğiniz bir ülkeden bir özel IP alarak, herhangi bir valığa olan uzak bağlantılarınızı güvence altına alın. Aboneliğiniz süresince bu IP yalnızca ama yalnızca size ait olacak ve veri aktarımlarınızı mümkün olan en güçlü şifrelemeyle koruyacak."; -"dedicated.ip.message.error.token" = "Belirtecinizin süresi dolmuş. Lütfen web sitesindeki Hesap sayfanıza giderek yenisini üretin."; -"dedicated.ip.message.expired.token" = "Belirtecinizin süresi dolmuş. Lütfen web sitesindeki Hesap sayfanıza giderek yenisini üretin."; -"dedicated.ip.message.incorrect.token" = "Lütfen belirteci doğru şekilde girdiğinizden emin olun"; -"dedicated.ip.message.invalid.token" = "Belirteciniz geçersiz. Lütfen belirteci doğru şekilde girdiğinizden emin olun"; -"dedicated.ip.message.ip.updated" = "Özel IP'niz güncellendi"; -"dedicated.ip.message.token.willexpire" = "Özel IP'nizin süresi yakında dolacak. Yenisini alın"; -"dedicated.ip.message.token.willexpire.link" = "Yenisini alın"; -"dedicated.ip.message.valid.token" = "Özel IP'niz başarıyla etkinleştirildi. Bölge seçim listenizde mevcut olacak."; -"dedicated.ip.plural.title" = "Özel IP'leriniz"; -"dedicated.ip.remove" = "Seçilen bölgeyi silmek istediğinizden emin misiniz?"; -"dedicated.ip.title" = "Özel IP"; -"dedicated.ip.token.textfield.accessibility" = "Özel IP belirtecinin yazılacağı metin alanı"; -"dedicated.ip.token.textfield.placeholder" = "Belirtecinizi buraya yapıştırın"; -"expiration.message" = "Üyeliğiniz yakında sona erecek. Koruma altında kalmak için yenileyin."; -"expiration.title" = "Yenileme"; -"friend.referrals.days.acquired" = "Ücretsiz günler alındı"; -"friend.referrals.days.number" = "%d gün"; -"friend.referrals.description.short" = "BİR ARKADAŞINIZI ARAMIZA KATIN. 30 GÜN ÜCRETSİZ KULLANIN."; -"friend.referrals.email.validation" = "Geçersiz e-posta adresi. Lütfen tekrar deneyin."; -"friend.referrals.family.friends.program" = "Aile ve Arkadaş Referans Programı"; -"friend.referrals.friends.family.title" = "Arkadaşlarınız ve aile bireylerinizi aramıza katın. Kaydolan her kişi için her ikinize birden 30'ar gün ücretsiz kullanım hakkı vereceğiz. "; -"friend.referrals.fullName" = "Adı ve soyadı"; -"friend.referrals.invitation.terms" = "Bu daveti göndererek Aile ve Arkadaş Referans Programının tüm şart ve koşullarını kabul ediyorum."; -"friend.referrals.invite.error" = "Davet tekrar gönderilemedi. Daha sonra tekrar deneyin."; -"friend.referrals.invite.success" = "Davet başarıyla gönderildi"; -"friend.referrals.invites.number" = "%d davet gönderdiniz"; -"friend.referrals.invites.sent.title" = "Davetler gönderildi"; -"friend.referrals.pending.invites" = "%d davet bekliyor"; -"friend.referrals.pending.invites.title" = "Bekleyen davet"; -"friend.referrals.privacy.note" = "Gizlilikle ilgili sebeplerden ötürü bekleme süresi 30 günü aşan davetler silinir."; -"friend.referrals.reward.given" = "Ödül verildi"; -"friend.referrals.send.invite" = "Davet gönder"; -"friend.referrals.share.link" = "Benzersiz referans linkinizi paylaşın"; -"friend.referrals.share.link.terms" = "Bu daveti gönderdiğinizde Aile ve Arkadaş Referans Programının tüm şart ve koşullarını kabul etmiş olursunuz."; -"friend.referrals.signedup" = "Kaydolan"; -"friend.referrals.signups.number" = "%d kayıt"; -"friend.referrals.title" = "Arkadaşınızı Aramıza Katın"; -"friend.referrals.view.invites.sent" = "Gönderilen davetleri göster"; -"gdpr.accept.button.title" = "Kabul edip devam et"; -"gdpr.collect.data.description" = "E-posta Adresi sadece hesap yönetimi ve ihlale karşı koruma amaçlarıyla kullanılır.\n\nE-posta adresi; abonelik bilgilerini, ödeme onaylarını, müşteri yazışmalarını ve Private Internet Access promosyonlarını göndermek için kullanılır."; -"gdpr.collect.data.title" = "Topladığımız kişisel bilgiler"; -"global.add" = "Ekle"; -"global.automatic" = "Otomatik"; +// GLOBAL + +"global.ok" = "Tamam"; "global.cancel" = "İptal"; -"global.clear" = "Temizle"; "global.close" = "Kapat"; -"global.copied" = "Panoya kopyalandı"; -"global.copy" = "Kopyala"; -"global.disable" = "Kapat"; -"global.disabled" = "Kapalı"; -"global.edit" = "Düzenle"; -"global.empty" = "Boş"; -"global.enable" = "Aç"; -"global.enabled" = "Açık"; "global.error" = "Hata"; -"global.general.settings" = "Genel Ayarlar"; -"global.no" = "Hayır"; -"global.ok" = "Tamam"; -"global.optional" = "İsteğe bağlı"; -"global.or" = "veya"; -"global.remove" = "Kaldır"; -"global.required" = "Gerekli"; -"global.row.selection" = "Sıra seçimi"; -"global.share" = "Paylaş"; -"global.unreachable" = "İnternet bağlantısı bulunamadı. Lütfen İnternet bağlantınızın olduğunu doğrulayın."; +"global.automatic" = "Otomatik"; +"global.required" = "Required"; +"global.optional" = "Optional"; +"global.clear" = "Temizle"; "global.update" = "Güncelle"; -"global.version" = "Sürüm"; -"global.vpn.settings" = "VPN Ayarları"; +"global.edit" = "Düzenle"; +"global.unreachable" = "No internet connection found. Please confirm that you have an internet connection."; +"global.enabled" = "Etkin"; +"global.disabled" = "Devre Dışı"; +"global.enable" = "Etkinleştir"; +"global.disable" = "Kapat"; +"global.add" = "Ekle"; +"global.remove" = "Kaldır"; +"global.empty" = "Empty"; +"global.copy" = "Kopyala"; +"global.share" = "Share"; +"global.copied" = "Panoya kopyalandı"; "global.yes" = "Evet"; -"hotspothelper.display.name" = "🔒 Bu bağlantıyı güvenlik altına almak için PIA Ayarları kısmından VPN WiFi Korumasını açın."; -"hotspothelper.display.protected.name" = "🔒 PIA VPN WiFi Koruması Açık! Arkanızdayız."; -"inapp.messages.settings.updated" = "Ayarlar güncellendi"; -"inapp.messages.toggle.title" = "Hizmet İletişim Mesajlarını Göster"; +"global.no" = "Hayır"; +"global.or" = "or"; +"global.row.selection" = "Row selection"; +"global.vpn.settings" = "VPN Ayarları"; +"global.general.settings" = "General Settings"; +"global.version" = "Sürüm"; + +// NOTIFICATIONS + +"notifications.disabled.title" = "Notifications disabled"; +"notifications.disabled.message" = "Enable notifications to get a reminder to renew your subscription before it expires."; +"notifications.disabled.settings" = "Ayarlar"; + +"expiration.title" = "Renewal"; +"expiration.message" = "Your subscription expires soon. Renew to stay protected."; + +"local_notification.non_compliant_wifi.title" = "Güvenilmeyen Wi-Fi: %@"; +"local_notification.non_compliant_wifi.text" = "Cihazınızı güvence altına almak için buraya dokunun"; + +// SHORTCUTS + +"shortcuts.connect" = "Bağlan"; +"shortcuts.disconnect" = "bağlantısını sonlandırın"; +"shortcuts.select_region" = "Select a region"; + +// RENEWAL + +"renewal.success.title" = "Thank you"; +"renewal.success.message" = "Your account was successfully renewed."; +"renewal.failure.message" = "Your purchase receipt couldn't be submitted, please retry at a later time."; + +// MENU "menu.accessibility.edit.tile" = "Düzenle"; "menu.accessibility.item" = "Menü"; -"menu.accessibility.logged_as" = "%@ olarak giriş yapıldı"; +"menu.accessibility.logged_as" = "Logged in as %@"; +"menu.expiration.expires_in" = "Subscription expires in"; "menu.expiration.days" = "%d gün"; -"menu.expiration.expires_in" = "Üyeliğin sona erme süresi:"; "menu.expiration.hours" = "%d saat"; "menu.expiration.one_hour" = "bir saat"; -"menu.expiration.upgrade" = "HESABI YÜKSELTİN"; +"menu.expiration.upgrade" = "UPGRADE ACCOUNT"; +"menu.item.region" = "Region selection"; "menu.item.about" = "Hakkında"; "menu.item.account" = "Hesap"; -"menu.item.logout" = "Çıkış yap"; -"menu.item.region" = "Bölge seçimi"; "menu.item.settings" = "Ayarlar"; -"menu.item.web.home" = "Ana sayfa"; -"menu.item.web.privacy" = "Gizlilik politikası"; +"menu.item.logout" = "Çıkış yap"; +"menu.item.web.privacy" = "Privacy policy"; +"menu.item.web.home" = "Home page"; "menu.item.web.support" = "Destek"; -"menu.logout.confirm" = "Çıkış yap"; -"menu.logout.message" = "Çıkış yaptığınızda VPN devre dışı kalacak ve korunmasız olacaksınız."; + "menu.logout.title" = "Çıkış yap"; -"menu.renewal.message.trial" = "Deneme hesabı yenilenemez. Hizmeti kullanmaya devam etmek için süresi dolduktan sonra lütfen yeni bir hesap satın alın."; -"menu.renewal.message.unavailable" = "Apple sunucuları şu anda kullanılamıyor. Lütfen daha sonra tekrar deneyin."; -"menu.renewal.message.website" = "Lütfen üyeliğinizi yenilemek için internet sitemizi kullanın."; +"menu.logout.message" = "Logging out will disable the VPN and leave you unprotected."; +"menu.logout.confirm" = "Çıkış yap"; + +"menu.renewal.title" = "Renewal"; "menu.renewal.purchase" = "Satın Al"; "menu.renewal.renew" = "Yenile"; -"menu.renewal.title" = "Yenileme"; -"network.management.tool.add.rule" = "Yeni kural ekle"; -"network.management.tool.alert" = "Otomatik işlev ayarlarınız, mevcut ağ koşulları altında VPN'nin bağlantısını kesik tutacak şekilde yapılandırılmış."; -"network.management.tool.always.connect" = "Daima VPN'ye bağlan"; -"network.management.tool.always.disconnect" = "Daima VPN bağlantısını kes"; -"network.management.tool.choose.wifi" = "Yeni kural eklemek için bir WiFi ağı seçin. "; -"network.management.tool.disable" = "Otomasyonu Kapat"; -"network.management.tool.enable.automation" = "Otomasyonu Etkinleştir"; -"network.management.tool.mobile.data" = "Mobil veri"; -"network.management.tool.open.wifi" = "Ortak WiFi"; -"network.management.tool.retain.state" = "VPN Durumunu Koru"; -"network.management.tool.secure.wifi" = "Güvenli VPN"; -"network.management.tool.title" = "Otomasyonu Yönet"; -"notifications.disabled.message" = "Aboneliğinizi süresi dolmadan önce yenilemek için bildirimleri etkinleştirerek bir hatırlatıcı alın."; -"notifications.disabled.settings" = "Ayarlar"; -"notifications.disabled.title" = "Bildirimler kapalı"; -"rating.enjoy.question" = "PIA VPN hoşunuza gidiyor mu?"; -"rating.enjoy.subtitle" = "Umarız VPN ürünümüz beklentilerinizi karşılıyordur"; -"rating.error.button.send" = "Geri bildirim gönder"; -"rating.error.question" = "Bağlantı kurulamadı"; -"rating.error.subtitle" = "Farklı bir bölge seçebilir ya da bir destek bileti açarak bize bilgi verebilirsiniz."; -"rating.problems.question" = "Sorun nedir?"; -"rating.problems.subtitle" = "Geri bildirim göndermek ister misiniz? PIA kullanırken yaşadığınız deneyimi geliştirmenize yardımcı olabiliriz"; -"rating.rate.question" = "AppStore değerlendirmesi verebilir misiniz?"; -"rating.rate.subtitle" = "Deneyiminizi paylaşmanız bizi memnun eder"; -"region.accessibility.favorite" = "Favori bölge ekleyin"; -"region.accessibility.filter" = "Filtre"; -"region.accessibility.unfavorite" = "Bir favori bölgeyi kaldırın"; -"region.filter.favorites" = "Favoriler"; -"region.filter.latency" = "Gecikme"; -"region.filter.name" = "Ad"; -"region.filter.sortby" = "Bölgeleri sıralama kıstası:"; -"region.search.placeholder" = "Bir bölge arayın"; -"renewal.failure.message" = "Satın alım işleminizin faturası gönderilemedi; lütfen daha sonra tekrar deneyin."; -"renewal.success.message" = "Hesabınız başarıyla yenilendi."; -"renewal.success.title" = "Teşekkürler"; -"server.reconnection.please.wait" = "Lütfen bekleyin..."; -"server.reconnection.still.connection" = "Bağlanma işlemi devam ediyor..."; -"set.email.error.validation" = "Bir e-posta adresi girmeniz gerekiyor."; -"set.email.form.email" = "E-posta adresinizi girin"; -"set.email.password.caption" = "Şifre"; -"set.email.success.message_format" = "Hesap kullanıcı adınızı ve şifrenizi %@ adresinize gönderdik"; -"set.email.why" = "Kullanıcı adınızla şifrenizi gönderebilmemiz için e-posta adresinize ihtiyacımız var."; -"settings.application_information.debug.empty.message" = "Hata ayıklama bilgileri boş; lütfen tekrar göndermeyi denemeden önce bir bağlantı kurun."; -"settings.application_information.debug.empty.title" = "Boş hata ayıklama bilgisi"; -"settings.application_information.debug.failure.message" = "Hata ayıklama bilgileri gönderilemedi."; -"settings.application_information.debug.failure.title" = "Gönderme sırasında hata oluştu"; -"settings.application_information.debug.success.message" = "Hata ayıklama bilgileri başarıyla gönderildi.\nKimlik: %@\nLütfen bu kimliği not edin; destek ekibimiz gönderdiğiniz bilgileri bulmak için bu kimliği kullanacaktır."; -"settings.application_information.debug.success.title" = "Hata ayıklama bilgileri gönderildi"; -"settings.application_information.debug.title" = "Hata Ayıklamayı destek birimine gönderin"; -"settings.application_information.title" = "UYGULAMA BİLGİLERİ"; -"settings.application_settings.active_theme.title" = "Aktif tema"; -"settings.application_settings.dark_theme.title" = "Koyu tema"; -"settings.application_settings.kill_switch.footer" = "VPN kill switch, VPN bağlantısı kesilip yeniden bağlanmaya çalışırken İnternet bağlantısını devre dışı bırakır. Bağlantının elle kesildiği durumlar hariç."; -"settings.application_settings.kill_switch.title" = "VPN Sonlandırma Anahtarı"; -"settings.application_settings.mace.footer" = "PIA MACE™ VPN'e bağlı iken reklam, iz sürücüler ve kötü amaçlı yazılımları engeller."; -"settings.application_settings.mace.title" = "PIA MACE™"; -"settings.application_settings.title" = "UYGULAMA AYARLARI"; -"settings.cards.history.title" = "Son Haberler"; -"settings.commit.buttons.later" = "Daha Sonra"; -"settings.commit.buttons.reconnect" = "Tekrar Bağlan"; -"settings.commit.messages.must_disconnect" = "Bazı değişikliklerin etki etmesi için VPN'nin tekrar bağlanması gerekmektedir."; -"settings.commit.messages.should_reconnect" = "Değişikliklerin etki etmesi için VPN'yi tekrar bağlayın."; -"settings.connection.remote_port.title" = "Uzaktan Bağlantı Noktası"; -"settings.connection.socket_protocol.title" = "Socket"; +"menu.renewal.message.trial" = "Deneme hesabı yenilenemez. Hizmeti kullanmaya devam etmek için süresi dolduktan sonra lütfen yeni bir hesap satın alın."; +"menu.renewal.message.website" = "Please use our website to renew your subscription."; +"menu.renewal.message.unavailable" = "Apple servers currently unavailable. Please try again later."; + +// ACCOUNT + +"account.email.caption" = "E-posta"; +"account.email.placeholder" = "E-posta adresi"; +"account.username.caption" = "Kullanıcı Adı"; +"account.other.footer" = "Get the Private Internet Access app for your other devices and use the above username and password to login and secure your connection."; +"account.restore.title" = "Restore uncredited purchase"; +"account.restore.description" = "If you renewed your plan but your account still says it's about to expire, you can restart the renewal from here. You will not be charged during this process."; +"account.restore.button" = "RESTORE PURCHASE"; +"account.restore.failure.title" = "Restore purchase"; +"account.restore.failure.message" = "No redeemable purchase was found for renewal."; +"account.save.item" = "E-posta adresini güncelle"; +"account.save.prompt" = "Authenticate to save changes"; +"account.save.success" = "Your email address has been saved."; +"account.reveal.prompt" = "Authenticate to reveal"; +"account.expiry_date.information" = "Your plan will expire on %@."; +"account.expiry_date.expired" = "Your plan has expired."; +"account.update.email.require.password.title" = "PIA Parolası Gerekli"; +"account.update.email.require.password.message" = "Güvenlik nedenleriyle, hesabınızda herhangi bir değişiklik yapmak için PIA parolanızı girmeniz gerekli. Lütfen devam etmek için PIA parolanızı girin."; +"account.update.email.require.password.button" = "Gönder"; +"account.error.unauthorized" = "Your username or password is incorrect."; +"account.subscriptions.message" = "You can manage your subscription from here."; +"account.subscriptions.linkMessage" = "buradan ulaşabilirsiniz"; +"account.set.email.error" = "There was an error adding email. Please try again later."; +"account.subscriptions.short.message" = "Manage subscription"; +"account.subscriptions.short.linkMessage" = "Manage subscription"; +"account.subscriptions.yearly" = "Yearly plan"; +"account.subscriptions.monthly" = "Monthly plan"; +"account.subscriptions.trial" = "Trial plan"; +"account.unauthorized" = "Bir sorun oluştu. Lütfen tekrar oturum açmayı deneyin"; +"account.delete" = "Hesabı Sil"; +"account.delete.alert.title" = "Emin misiniz?"; +"account.delete.alert.message" = "Deleting your PIA account is permanent and irreversible. You will not be able to retrieve your PIA credentials after performing this action. Please note that this action only deletes your PIA account from our database, but it does NOT delete your subscription. You will need to go to your Apple account and cancel the Private Internet Access subscription from there. Otherwise, you will still be charged, even though your PIA account will no longer be active."; +"account.delete.alert.failureMessage" = "Something went wrong while deleting your account, please try again later."; +"account.survey.message" = "Want to help make PIA better? Let us know how we can improve!\nTake The Survey"; +"account.survey.messageLink" = "Take The Survey"; + +// SETTINGS + "settings.connection.title" = "BAĞLANTI"; -"settings.connection.transport.title" = "Nakil"; "settings.connection.vpn_protocol.title" = "Protokol Seçimi"; -"settings.content_blocker.footer" = "İçerik Engelleyiciyi etkinleştirmek veya devre dışı bırakmak için Ayarlar > Safari > İçerik Engelleyici bölümüne gidin ve PIA VPN'i açık hale getirin."; -"settings.content_blocker.refresh.title" = "Engelleme listesini yenile"; -"settings.content_blocker.state.title" = "Mevcut durum"; -"settings.content_blocker.title" = "Safari İçerik Engelleyici durumu"; -"settings.dns.alert.clear.message" = "Bu işlem özel DNS'yi temizler ve varsayılan PIA DNS ayarını uygular."; -"settings.dns.alert.clear.title" = "DNS'yi temizle"; -"settings.dns.alert.create.message" = "PIA DNS harici bir şey kullandığınızda DNS trafiğiniz üçüncü kısımlara açık hale gelebilir ve gizliliğiniz risk altına girebilir."; +"settings.connection.transport.title" = "Taşıma"; +"settings.connection.socket_protocol.title" = "Socket"; +"settings.connection.remote_port.title" = "Uzak Bağlantı Noktası"; + +"settings.encryption.title" = "ŞİFRELEME"; +"settings.encryption.cipher.title" = "Veri Şifrelemesi"; +"settings.encryption.digest.title" = "Veri Kimlik Doğrulama"; +"settings.encryption.handshake.title" = "El sıkışma"; + +"settings.application_settings.title" = "APPLICATION SETTINGS"; +"settings.application_settings.dark_theme.title" = "Dark theme"; +"settings.application_settings.active_theme.title" = "Active theme"; +"settings.application_settings.kill_switch.title" = "VPN Acil Anahtarı"; +"settings.application_settings.kill_switch.footer" = "The VPN kill switch prevents access to the Internet if the VPN connection is reconnecting. This excludes disconnecting manually."; +"settings.application_settings.leak_protection.title" = "Sızıntı Koruması"; +"settings.application_settings.leak_protection.footer" = "iOS, varsayılan olarak VPN dışında çalışacak şekilde tasarlanmış özellikleri içerir. Bunlar AirDrop, CarPlay, AirPlay ve Kişisel Erişim Noktaları gibi özellikleri içerir. Özel sızıntı koruması etkinleştirildiğinde, bu trafiğin VPN üzerinden yönlendirilmesi ancak bu özelliklerin işlevselliğini etkileyebilir. Daha fazla bilgi"; +"settings.application_settings.leak_protection.more_info" = "Daha fazla bilgi"; +"settings.application_settings.allow_local_network.title" = "Yerel ağdaki cihazlara erişim izni ver"; +"settings.application_settings.allow_local_network.footer" = "VPN'e bağlıyken yazıcılar veya dosya sunucuları gibi yerel cihazlara bağlı kalın. (Buna yalnızca ağınızdaki kişilere ve cihazlara güveniyorsanız izin verin.)"; +"settings.application_settings.mace.title" = "PIA MACE™"; +"settings.application_settings.mace.footer" = "PIA MACE™ blocks ads, trackers, and malware while you're connected to the VPN."; +"settings.application_settings.leak_protection.alert.title" = "VPN Ayarlarındaki değişiklikler, bir sonraki bağlantıda etkili olacak"; + +"settings.content_blocker.title" = "Safari Content Blocker state"; +"settings.content_blocker.state.title" = "Current state"; +"settings.content_blocker.refresh.title" = "Refresh block list"; +"settings.content_blocker.footer" = "To enable or disable Content Blocker go to Settings > Safari > Content Blockers and toggle PIA VPN."; + +"settings.application_information.title" = "UYGULAMA BİLGİLERİ"; +"settings.application_information.debug.title" = "Hata Ayıklama Kaydını Destek Ekibine Gönderin"; +"settings.application_information.debug.failure.title" = "Error during submission"; +"settings.application_information.debug.failure.message" = "Debug information could not be submitted."; +"settings.application_information.debug.empty.title" = "Empty debug information"; +"settings.application_information.debug.empty.message" = "Debug information is empty, please attempt a connection before retrying submission."; +"settings.application_information.debug.success.title" = "Hata ayıklama bilgileri gönderildi"; +"settings.application_information.debug.success.message" = "Debug information successfully submitted.\nID: %@\nPlease note this ID, as our support team will require this to locate your submission."; + +"settings.commit.messages.must_disconnect" = "The VPN must reconnect for some changes to take effect."; +"settings.commit.messages.should_reconnect" = "Reconnect the VPN to apply changes."; +"settings.commit.buttons.reconnect" = "Yeniden bağlan"; +"settings.commit.buttons.later" = "Daha sonra"; + +"settings.reset.title" = "RESET"; +"settings.reset.defaults.title" = "Ayarları varsayılanlara sıfırla"; +"settings.reset.defaults.confirm.title" = "Reset settings"; +"settings.reset.defaults.confirm.message" = "Bu işlem uygulama ayarlarını sıfırlayacak. Yapmış olduğunuz bütün değişiklikler kaybolacaktır."; +"settings.reset.defaults.confirm.button" = "Sıfırla"; +"settings.reset.footer" = "This will reset all of the above settings to default."; + +"settings.small.packets.title" = "Küçük Paketler Kullan"; +"settings.small.packets.description" = "Bazı yönlendirici ve mobil ağlarla olan uyumluluğu artırmak için IP paket boyutunu hafif düşürecek."; + +"settings.log.connected.error" = "A VPN connection is required. Please connect to the VPN and retry."; +"settings.log.information" = "Sorunların giderilmesine yardımcı olması için teknik desteğe gönderilebilen hata ayıklama günlüklerini kaydedin."; + +"settings.preview.title" = "Preview"; +"settings.server.network.description" = "Next generation network"; +"settings.server.network.alert" = "The VPN has to be disconnected to change the server network."; + +"settings.geo.servers.description" = "Coğrafi Konum Bölgelerini Göster"; +"settings.cards.history.title" = "Son haberler"; + +"settings.ovpn.migration.footer" = "OpenVPN uygulamamızı güncelliyoruz, daha fazla bilgi için buraya tıklayın"; +"settings.ovpn.migration.footer.link" = "buradan"; + +"settings.section.general" = "Genel"; +"settings.section.protocols" = "Protokoller."; +"settings.section.network" = "AĞ"; +"settings.section.privacyFeatures" = "Privacy Features"; +"settings.section.automation" = "Otomasyon"; +"settings.section.help" = "Yardım"; + +// CONTENT BLOCKER + +"content_blocker.title" = "Safari Content Blocker"; +"content_blocker.body.subtitle" = "To enable our Content Blocker for use with Safari please go to Settings > Safari, and under General touch Content Blockers toggle on PIA VPN."; +"content_blocker.body.footer" = "Please note: You do not need to be connected to the VPN for this Content Blocker to work, but it will only work while browsing with Safari."; + +// ABOUT + +"about.app" = "Private Internet Access'ten VPN"; +"about.intro" = "This program uses the following components:"; +"about.accessibility.component.expand" = "Tap to read full license"; + +// DASHBOARD + +"dashboard.content_blocker.intro.message" = "This version replaces MACE with our Safari Content Blocker.\n\nCheck it out in the 'Settings' section."; + +"dashboard.connection.ip.unreachable" = "Internet unreachable"; + +"dashboard.vpn.disconnected" = "Bağlantı Kesildi"; +"dashboard.vpn.connecting" = "Bağlanıyor..."; +"dashboard.vpn.connected" = "Connected to VPN"; +"dashboard.vpn.on" = "VPN: ON"; +"dashboard.vpn.disconnecting" = "Bağlantı kesiliyor..."; +"dashboard.vpn.changing_region" = "Changing region..."; +"dashboard.vpn.disconnect.untrusted" = "This network is untrusted. Do you really want to disconnect the VPN?"; +"dashboard.accessibility.vpn.button" = "VPN Connection button"; +"dashboard.accessibility.vpn.button.isOn" = "VPN Connection button. The VPN is currently connected"; +"dashboard.accessibility.vpn.button.isOff" = "VPN Connection button. The VPN is currently disconnected"; + +"dashboard.vpn.leakprotection.alert.title" = "Güvenilmeyen Wi-Fi algılandı"; +"dashboard.vpn.leakprotection.alert.message" = "Veri sızıntısını önlemek için, \"Yerel ağdaki cihazlara erişime izin ver\" özelliğini devre dışı bırakmak ve otomatik olarak yeniden bağlanmak için Şimdi Devre Dışı Bırak'a dokunun."; +"dashboard.vpn.leakprotection.alert.cta1" = "Şimdi Kapat"; +"dashboard.vpn.leakprotection.alert.cta2" = "Daha fazlasını öğrenin"; +"dashboard.vpn.leakprotection.alert.cta3" = "Yoksay"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "Veri sızıntısını önlemek için, IKEv2 VPN protokolüne geçmek ve otomatik olarak yeniden bağlanmak için Şimdi Değiştir'e dokunun."; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "Şimdi Değiştir"; + +// VPN PERMISSION + +"vpn_permission.title" = "PIA"; +"vpn_permission.body.title" = "PIA needs access to your VPN profiles to secure your traffic"; +"vpn_permission.body.subtitle" = "You’ll see a prompt for PIA VPN and need to allow access to VPN configurations.\nTo proceed tap on “%@”."; +"vpn_permission.body.footer" = "We don’t monitor, filter or log any network activity."; +"vpn_permission.disallow.message.basic" = "We need this permission for the application to function."; +"vpn_permission.disallow.message.support" = "You can also get in touch with customer support if you need assistance."; +"vpn_permission.disallow.contact" = "İletişim"; + +// CUSTOM DNS + "settings.dns.custom" = "Özel"; +"settings.dns.alert.create.message" = "Using non PIA DNS could expose your DNS traffic to third parties and compromise your privacy."; +"settings.dns.alert.clear.title" = "Clear DNS"; +"settings.dns.alert.clear.message" = "This will clear your custom DNS and default to PIA DNS."; "settings.dns.custom.dns" = "Özel DNS"; "settings.dns.primaryDNS" = "Birincil DNS"; "settings.dns.secondaryDNS" = "İkincil DNS"; +"settings.dns.validation.primary.mandatory" = "Primary DNS is mandatory."; "settings.dns.validation.primary.invalid" = "Birincil DNS geçerli değil."; -"settings.dns.validation.primary.mandatory" = "Birincil DNS boş bırakılamaz."; "settings.dns.validation.secondary.invalid" = "İkincil DNS geçerli değil."; -"settings.encryption.cipher.title" = "Veri Şifreleme"; -"settings.encryption.digest.title" = "Veri Doğrulama"; -"settings.encryption.handshake.title" = "Handshake"; -"settings.encryption.title" = "ŞİFRELEME"; -"settings.geo.servers.description" = "Coğrafi Konum Bölgelerini Göster"; -"settings.hotspothelper.all.description" = "VPN WiFi Koruması, güvenilen ağlar da dâhil olmak üzere tüm ağlarda etkinleşecek."; -"settings.hotspothelper.all.title" = "Tüm ağları koru"; -"settings.hotspothelper.available.add.help" = "Güvenilen ağları eklemek için + simgesine dokunun."; -"settings.hotspothelper.available.help" = "Bu listeyi doldurmak için iOS Ayarları > WiFi kısmına gidin."; -"settings.hotspothelper.cellular.description" = "Bu seçenek etkinleştirildiğinde, PIA, hücresel ağlara bağlanırken VPN'yi otomatik olarak açar."; -"settings.hotspothelper.cellular.networks" = "Hücresel ağlar"; -"settings.hotspothelper.cellular.title" = "Hücresel ağlarda korunun"; -"settings.hotspothelper.description" = "WiFi ağına ya da hücresel ağlara bağlandığınızda PIA'nın nasıl davranacağını yapılandırın. Elle bağlanma durumunda geçerli değildir."; -"settings.hotspothelper.enable.description" = "Bu seçenek açıldığında, PIA, güvenilmeyen WiFi ağlarına bağlandığınızda VPN'yi otomatik olarak açar."; -"settings.hotspothelper.rules.title" = "Kurallar"; -"settings.hotspothelper.title" = "Ağ yönetim aracı"; -"settings.hotspothelper.wifi.networks" = "WiFi ağları"; -"settings.hotspothelper.wifi.trust.title" = "VPN WiFi Koruması"; -"settings.log.connected.error" = "VPN bağlantısı gerekli. Lütfen VPN'ye bağlanıp tekrar deneyin."; -"settings.log.information" = "Sorunların giderilmesine yardımcı olması için, teknik desteğe gönderilebilen hata ayıklama günlüklerini kaydedin."; -"settings.nmt.killswitch.disabled" = "VPN sonlandırma anahtarı şu anda devre dışı. Ağ Yönetim Aracının çalıştığından ve ağ değiştirdiğinizde bağlanabileceğinizden emin olmak için lütfen ayarlarınızdan VPN sonlandırma anahtarını etkinleştirin."; -"settings.nmt.optout.disconnect.alerts" = "Bağlantı kesilmesi onay uyarısını kapat"; -"settings.nmt.optout.disconnect.alerts.description" = "VPN bağlantısı kesilirken gösterilen uyarıyı devre dışı bırakır."; -"settings.nmt.wireguard.warning" = "Farklı ağlar arasında geçiş yaptığınızda WireGuard®'ın tekrar bağlanmasına gerek yoktur. VPN bağlantısını güvenilir ağlarda manuel yoldan kesmeniz gerekebilir."; -"settings.ovpn.migration.footer" = "OpenVPN hizmetinin gerçekleştirilme şeklini güncelliyoruz; daha çok bilgi almak için buraya tıklayın"; -"settings.ovpn.migration.footer.link" = "buraya"; -"settings.preview.title" = "Önizleme"; -"settings.reset.defaults.confirm.button" = "Sıfırla"; -"settings.reset.defaults.confirm.message" = "Bu işlem uygulama ayarlarını sıfırlayacak. Yapmış olduğun bütün değişiklikler kaybolacaktır."; -"settings.reset.defaults.confirm.title" = "Ayarları sıfırla"; -"settings.reset.defaults.title" = "Ayarları varsayılana sıfırla"; -"settings.reset.footer" = "Bu işlem yukarıdaki bütün ayarları sıfırlayarak varsayılan değerlere döndürecektir."; -"settings.reset.title" = "SIFIRLA"; -"settings.section.automation" = "Otomasyon"; -"settings.section.general" = "Genel"; -"settings.section.help" = "Yardım"; -"settings.section.network" = "Ağ"; -"settings.section.privacyFeatures" = "Gizlilik Özellikleri"; -"settings.section.protocols" = "Protokoller"; -"settings.server.network.alert" = "Sunucu ağını değiştirmek için VPN bağlantısının sonlandırılması gerekiyor."; -"settings.server.network.description" = "Yeni nesil ağ"; -"settings.service.quality.share.description" = "VPN bağlantı istatistiklerini paylaşarak gelişmemize yardımcı olun. Bu raporlarda asla kişiyi tanımlayabilecek bilgiler yer almaz."; -"settings.service.quality.share.findoutmore" = "Daha çok bilgi"; -"settings.service.quality.share.title" = "PIA gelişimine yardım edin"; -"settings.service.quality.show.title" = "Bağlantı istatistikleri"; -"settings.small.packets.description" = "Bazı yönlendirici ve mobil ağlarla olan uyumluluğu artırmak için IP paket boyutunu hafif düşürecek."; -"settings.small.packets.title" = "Küçük Paketler Kullanın"; -"settings.trusted.networks.connect.message" = "VPN'ye bağlanarak bu ağı korumak istiyor musunuz?"; -"settings.trusted.networks.message" = "PIA, bu ağlara otomatik olarak bağlanmayacak."; -"settings.trusted.networks.sections.available" = "Mevcut ağlar"; -"settings.trusted.networks.sections.current" = "Mevcut ağ"; -"settings.trusted.networks.sections.trusted" = "Güvenilen ağlar"; -"settings.trusted.networks.sections.trusted.rule.action" = "PIA VPN bağlantısını kes"; -"settings.trusted.networks.sections.trusted.rule.description" = "PIA'nın WiFi ağında ve hücresel ağlarda nasıl davranacağını ayarlamak için VPN sonlandırma anahtarını etkinleştirdikten sonra bu özelliği açın. Bağlantıyı elle sonlandırdığınızda, Ağ Yönetim Aracının işlevselliğinin devre dışı kalacağını lütfen göz önünde bulundurun."; -"settings.trusted.networks.sections.untrusted" = "Güvenilmeyen ağlar"; -"shortcuts.connect" = "Bağlan"; -"shortcuts.disconnect" = "Bağlantıyı Kes"; -"shortcuts.select_region" = "Bir Bölge Seçin"; -"siri.shortcuts.add.error" = "Siri kısayolu eklenirken bir hata meydana geldi. Lütfen tekrar deneyin."; -"siri.shortcuts.connect.row.title" = "'Bağlan' Siri Kısayolu"; -"siri.shortcuts.connect.title" = "PIA VPN'ye bağlan"; -"siri.shortcuts.disconnect.row.title" = "'Bağlantıyı Kes' Siri Kısayolu"; -"siri.shortcuts.disconnect.title" = "PIA VPN bağlantısını kes"; -"tiles.accessibility.invisible.tile.action" = "Bu kısmı panoya eklemek için dokunun"; -"tiles.accessibility.visible.tile.action" = "Bu kısmı panodan kaldırmak için dokunun"; -"tiles.favorite.servers.title" = "Favori sunucular"; -"tiles.nmt.accessibility.trusted" = "Güvenilir ağ"; -"tiles.nmt.accessibility.untrusted" = "Güvenilmeyen ağ"; -"tiles.nmt.cellular" = "Hücresel"; -"tiles.quick.connect.title" = "Hızlı bağlan"; + +// HOTSPOT HELPER + +"settings.hotspothelper.title" = "Network management tool"; +"settings.hotspothelper.description" = "Configure how PIA will behaves on connection to WiFi or cellular networks. This excludes disconnecting manually."; +"settings.hotspothelper.enable.description" = "PIA automatically enables the VPN when connecting to untrusted WiFi networks if this option is enabled."; +"settings.hotspothelper.all.title" = "Protect all networks"; +"settings.hotspothelper.all.description" = "VPN WiFi Protection will activate on all networks, including trusted networks."; +"settings.hotspothelper.available.help" = "To populate this list go to iOS Settings > WiFi."; +"settings.hotspothelper.available.add.help" = "Tap + to add to Trusted networks."; +"settings.trusted.networks.sections.current" = "Current network"; +"settings.trusted.networks.sections.available" = "Available networks"; +"settings.trusted.networks.sections.trusted" = "Trusted networks"; +"settings.trusted.networks.message" = "PIA won't automatically connect on these networks."; +"settings.trusted.networks.connect.message" = "Protect this network by connecting to VPN?"; +"hotspothelper.display.name" = "🔒 Activate VPN WiFi Protection in PIA Settings to secure this connection."; +"hotspothelper.display.protected.name" = "🔒 PIA VPN WiFi Protection Enabled - We got your back."; +"settings.hotspothelper.cellular.title" = "Protect over cellular networks"; +"settings.hotspothelper.wifi.trust.title" = "VPN WiFi Protection"; +"settings.hotspothelper.cellular.description" = "PIA automatically enables the VPN when connecting to cellular networks if this option is enabled."; +"settings.hotspothelper.cellular.networks" = "Cellular networks"; +"settings.hotspothelper.wifi.networks" = "WiFi networks"; +"settings.trusted.networks.sections.untrusted" = "Untrusted networks"; +"settings.hotspothelper.rules.title" = "Rules"; +"settings.trusted.networks.sections.trusted.rule.description" = "Enable this feature, with the VPN kill switch enabled, to customize how PIA will behave on WiFi and cellular networks. Please be aware, functionality of the Network Management Tool will be disabled if you manually disconnect."; +"settings.trusted.networks.sections.trusted.rule.action" = "Disconnect from PIA VPN"; "tiles.quicksetting.nmt.title" = "Ağ Yönetimi"; "tiles.quicksetting.private.browser.title" = "Özel Tarayıcı"; -"tiles.quicksettings.min.elements.message" = "Hızlı Ayarlar Kısmında en az bir unsuru görünür tutmanız gerekiyor"; -"tiles.quicksettings.title" = "Hızlı ayarlar"; +"settings.nmt.killswitch.disabled" = "The VPN kill switch is currently disabled. In order to ensure that the Network Management Tool is functioning, and that you are able to reconnect when switching networks, please enable the VPN kill switch in your settings."; +"settings.nmt.optout.disconnect.alerts" = "Opt-out disconnect confirmation alert"; +"settings.nmt.optout.disconnect.alerts.description" = "Disables the warning alert when disconnecting from the VPN."; +"settings.nmt.wireguard.warning" = "WireGuard® doesn't need to reconnect when you switch between different networks. It may be necessary to manually disconnect the VPN on trusted networks."; +"settings.service.quality.share.title" = "PIA'nın iyileştirilmesine yardım edin"; +"settings.service.quality.share.description" = "VPN bağlantı istatistiklerini bizimle paylaşarak uygulamamızı geliştirmemize yardımcı olun. Bu raporlar hiç bir şekilde kişisel olarak tanımlanabilir bilgiler içermez."; +"settings.service.quality.share.findoutmore" = "Daha çok bilgi"; +"settings.service.quality.show.title" = "Connection stats"; + +"network.management.tool.open.wifi" = "Open WiFi"; +"network.management.tool.secure.wifi" = "Secure WiFi"; +"network.management.tool.mobile.data" = "Mobil veri"; +"network.management.tool.always.connect" = "Her zaman VPN bağlantısı yap"; +"network.management.tool.always.disconnect" = "Her zaman VPN bağlantısını kes"; +"network.management.tool.retain.state" = "Retain VPN State"; +"network.management.tool.add.rule" = "Yeni kural ekle"; +"network.management.tool.choose.wifi" = "Choose a WiFi network to add a new rule. "; +"network.management.tool.title" = "Otomasyonu Yönet"; +"network.management.tool.alert" = "Your automation settings are configured to keep the VPN disconnected under the current network conditions."; +"network.management.tool.disable" = "Disable Automation"; +"network.management.tool.enable.automation" = "Enable Automation"; + +// REGION + +"region.search.placeholder" = "Bir bölge ara"; +"region.accessibility.filter" = "Filter"; +"region.filter.sortby" = "Bölgeleri sırala:"; +"region.filter.name" = "İsim"; +"region.filter.latency" = "Gecikme"; +"region.filter.favorites" = "Favoriler"; +"region.accessibility.favorite" = "Add a favorite region"; +"region.accessibility.unfavorite" = "Remove a favorite region"; + +// TILES +"tiles.quick.connect.title" = "Quick connect"; +"tiles.favorite.servers.title" = "Favorite servers"; "tiles.region.title" = "VPN Sunucusu"; -"tiles.subscription.days.left" = "(%d gün kaldı)"; -"tiles.subscription.monthly" = "Aylık"; "tiles.subscription.title" = "Üyelik"; "tiles.subscription.trial" = "Deneme"; +"tiles.subscription.monthly" = "Aylık"; "tiles.subscription.yearly" = "Yıllık"; +"tiles.subscription.days.left" = "(%d gün kaldı)"; +"tiles.usage.title" = "Usage"; +"tiles.usage.ipsec.title" = "USAGE (Not available on IKEv2)"; +"tiles.usage.upload" = "Yükle"; "tiles.usage.download" = "İndir"; -"tiles.usage.ipsec.title" = "KULLANIM (IKEv2 üzerinde kullanılamaz)"; -"tiles.usage.title" = "Kullanım"; -"tiles.usage.upload" = "Karşıya yükle"; -"today.widget.login" = "Giriş"; -"vpn_permission.body.footer" = "Hiçbir ağ aktivitesini izlemiyor, filtrelemiyor ya da kayıt altına almıyoruz."; -"vpn_permission.body.subtitle" = "PIA VPN için bir açılır pencere göreceksiniz ve VPN yapılandırmalarına erişim izni vermeniz gerekecek.\nDevam etmek için “%@” düğmesine dokunun."; -"vpn_permission.body.title" = "PIA'nın veri trafiğinizi güven altına alabilmesi için VPN yapılandırma ayarlarınıza erişebilmesi gerekmektedir."; -"vpn_permission.disallow.contact" = "İletişim"; -"vpn_permission.disallow.message.basic" = "Uygulamanın çalışabilmesi için bu izne ihtiyaç duymaktayız."; -"vpn_permission.disallow.message.support" = "Yardıma ihtiyacınız olursa, müşteri desteği ile iletişime geçebilirsiniz."; -"vpn_permission.title" = "PIA"; -"walkthrough.action.done" = "BİTTİ"; -"walkthrough.action.next" = "İLERİ"; -"walkthrough.action.skip" = "ATLA"; -"walkthrough.page.1.description" = "Aynı anda 10 cihazda koruma sağlayın."; -"walkthrough.page.1.title" = "Aynı anda 10 cihazı destekler"; -"walkthrough.page.2.description" = "Dünya çapındaki sunucularla daima koruma altındasınız."; -"walkthrough.page.2.title" = "Herhangi bir bölgeye kolayca bağlanın"; -"walkthrough.page.3.description" = "İçerik Engelleyicimizi etkinleştirdiğinizde Safari'de reklamların gösterilmesi engellenir."; -"walkthrough.page.3.title" = "Kendinizi reklamlardan koruyun"; +"tiles.nmt.cellular" = "Cellular"; +"tiles.nmt.accessibility.trusted" = "Trusted network"; +"tiles.nmt.accessibility.untrusted" = "Untrusted network"; +"tiles.quicksettings.title" = "Quick settings"; +"tiles.quicksettings.min.elements.message" = "You should keep at least one element visible in the Quick Settings Tile"; +"tiles.accessibility.visible.tile.action" = "Tap to remove this tile from the dashboard"; +"tiles.accessibility.invisible.tile.action" = "Tap to add this tile to the dashboard"; + +//SIRI SHORTCUTS +"siri.shortcuts.connect.row.title" = "'Connect' Siri Shortcut"; +"siri.shortcuts.disconnect.row.title" = "'Disconnect' Siri Shortcut"; +"siri.shortcuts.connect.title" = "Connect PIA VPN"; +"siri.shortcuts.disconnect.title" = "Disconnect PIA VPN"; +"siri.shortcuts.add.error" = "There was an error adding the Siri shortcut. Please, try it again."; + +//TODAY WIDGET +"today.widget.login" = "Giriş Yapın"; + +//FRIEND REFERRALS +"friend.referrals.email.validation" = "Invalid email. Please try again."; +"friend.referrals.title" = "Arkadaş Referansı"; +"friend.referrals.view.invites.sent" = "View invites sent"; +"friend.referrals.pending.invites" = "%d bekleyen davet"; +"friend.referrals.signups.number" = "%d kayıt"; +"friend.referrals.fullName" = "Ad soyad"; +"friend.referrals.invitation.terms" = "By sending this invitation, I agree to all of the terms and conditions of the Family and Friends Referral Program."; +"friend.referrals.family.friends.program" = "Family and Friends Referral Program"; +"friend.referrals.send.invite" = "Send invite"; +"friend.referrals.invite.error" = "Could not resend invite. Try again later."; +"friend.referrals.invite.success" = "Invite sent successfully"; +"friend.referrals.share.link" = "Kişiye özel tavsiye bağlantınızı paylaşın"; +"friend.referrals.share.link.terms" = "By sharing this link, you agree to all of the terms and conditions of the Family and Friends Referral Program."; +"friend.referrals.invites.sent.title" = "Invites sent"; +"friend.referrals.pending.invites.title" = "Bekleyen davet"; +"friend.referrals.invites.number" = "%d davet gönderdiniz"; +"friend.referrals.privacy.note" = "Lütfen unutmayın, gizlilik nedeniyle, 30 günden eski tüm davetler silinir."; +"friend.referrals.signedup" = "Kayıt yaptı"; +"friend.referrals.reward.given" = "Verilen ödül"; +"friend.referrals.days.acquired" = "Alınan ücretsiz günler"; +"friend.referrals.days.number" = "%d gün"; + +"friend.referrals.friends.family.title" = "Arkadaşlarınıza ve aile bireylerinize referans olun. Her kayıtta, hem kaydolan kişi hem de siz 30'ar gün ücretsiz kullanım hakkı alacaksınız. "; +"friend.referrals.description.short" = "BİR ARKADAŞINIZI ARAMIZA KATIN. 30 GÜN ÜCRETSİZ KULLANIM KAZANIN."; + +// GDPR +"gdpr.collect.data.title" = "Personal information we collect"; +"gdpr.collect.data.description" = "E-mail Address for the purposes of account management and protection from abuse.\n\nE-mail address is used to send subscription information, payment confirmations, customer correspondence, and Private Internet Access promotional offers only."; +"gdpr.accept.button.title" = "Agree and continue"; + +// SET EMAIL +"set.email.form.email" = "E-posta adresinizi girin"; +"set.email.why" = "Kullanıcı adınızla parolanızı gönderebilmemiz için e-posta adresinize ihtiyacımız var."; +"set.email.success.message_format" = "We have sent your account username and password at your email address at %@"; +"set.email.password.caption" = "Şifre"; +"set.email.error.validation" = "You must enter an email address."; + +// RATING +"rating.enjoy.question" = "PIA VPN'i beğeniyor musunuz?"; +"rating.enjoy.subtitle" = "Umarız VPN ürünümüz beklentilerinizi karşılıyordur"; +"rating.problems.question" = "Sorun nedir?"; +"rating.problems.subtitle" = "Geri bildirim göndermek ister misiniz? Bu, PIA kullanma deneyiminizi iyileştirmemize yardımcı olabilir"; +"rating.review.question" = "How about an AppStore review?"; +"rating.rate.question" = "How about a rating on the AppStore?"; +"rating.rate.subtitle" = "Deneyiminizi paylaşmanız bizi memnun eder"; +"rating.error.question" = "Bağlantı kurulamadı"; +"rating.error.subtitle" = "Farklı bir bölge seçebilir ya da bir destek bileti açarak bize bilgi verebilirsiniz."; +"rating.error.button.send" = "Send feedback"; +"rating.alert.button.notreally" = "Not Really"; +"rating.alert.button.nothanks" = "No, thanks."; +"rating.alert.button.oksure" = "Ok, sure!"; + +// CALLING CARDS +"card.wireguard.title" = "WireGuard®'ı bugün deneyin!"; +"card.wireguard.description" = "Bu, daha iyi performans, daha düşük CPU kullanımı ve daha uzun pil ömrü sunan, yeni, daha verimli bir VPN protokolü."; +"card.wireguard.cta.activate" = "WireGuard®'ı hemen deneyin"; +"card.wireguard.cta.settings" = "Ayarlar'ı Açın"; +"card.wireguard.cta.learn" = "Daha fazlasını öğrenin"; + +// DEDICATED IP +"dedicated.ip.title" = "Atanmış IP VPN"; +"dedicated.ip.plural.title" = "Your Dedicated IPs"; +"dedicated.ip.activation.description" = "Belirtecinizi aşağıdaki forma yapıştırarak Özel IP'nizi etkinleştirin. Kısa süre önce bir özel IP satın aldıysanız belirteci PIA web sitesine giderek oluşturabilirsiniz."; +"dedicated.ip.token.textfield.placeholder" = "Belirtecinizi buraya yapıştırın"; +"dedicated.ip.token.textfield.accessibility" = "The textfield to type the Dedicated IP token"; +"dedicated.ip.activate.button.title" = "Etkinleştir"; +"dedicated.ip.message.incorrect.token" = "Please make sure you have entered the token correctly"; +"dedicated.ip.message.invalid.token" = "Belirteciniz geçersiz. Lütfen belirteci doğru şekilde girdiğinizden emin olun."; +"dedicated.ip.message.valid.token" = "Özel IP'niz başarıyla etkinleştirildi. Bölge seçim listenizde mevcut olacak."; +"dedicated.ip.message.expired.token" = "Your token is expired. Please generate a new one from your Account page on the website."; +"dedicated.ip.message.error.token" = "Your token is expired. Please generate a new one from your Account page on the website."; +"dedicated.ip.message.error.retryafter" = "Too many failed token activation requests. Please try again after %@ second(s)."; +"dedicated.ip.message.token.willexpire" = "Your dedicated IP will expire soon. Get a new one"; +"dedicated.ip.message.token.willexpire.link" = "Yenisini al"; +"dedicated.ip.message.ip.updated" = "Özel IP'niz güncellendi"; +"dedicated.ip.country.flag.accessibility" = "Country flag for %@"; +"dedicated.ip.remove" = "Are you sure you want to remove the selected region?"; +"dedicated.ip.limit.title" = "Herhangi bir varlıkla olan uzaktan bağlantılarınızı, seçtiğiniz bir ülkeden özel bir IP ile sabitleyin. Aboneliğiniz sırasında bu IP sadece size ait olacak ve veri transferleriniz en güçlü şifrelemeyle koruyacaktır."; + +// RECONNECTIONS +"server.reconnection.please.wait" = "Lütfen bekleyin..."; +"server.reconnection.still.connection" = "Hâlâ bağlanmaya çalışıyor..."; + +// INAPP MESSAGES +"inapp.messages.toggle.title" = "Servis İletişim Mesajlarını Göster"; +"inapp.messages.settings.updated" = "Ayarlar güncellendi"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "Bölge"; +"widget.liveActivity.protocol.title" = "Protokol"; diff --git a/PIA VPN/zh-Hans.lproj/Localizable.strings b/PIA VPN/zh-Hans.lproj/Localizable.strings index 93625b161..89aa75b87 100644 --- a/PIA VPN/zh-Hans.lproj/Localizable.strings +++ b/PIA VPN/zh-Hans.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "续订"; "expiration.message" = "您的订阅即将到期。请续期以持续受到保护。"; +"local_notification.non_compliant_wifi.title" = "不安全的 Wi-Fi:%@"; +"local_notification.non_compliant_wifi.text" = "轻点此处以保护您的设备"; + // SHORTCUTS "shortcuts.connect" = "连接"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "活跃主题"; "settings.application_settings.kill_switch.title" = "VPN 切断开关"; "settings.application_settings.kill_switch.footer" = "如果 VPN 连接重新连线,VPN 终止开关会阻止访问互联网。这不包括手动断开连接。"; +"settings.application_settings.leak_protection.title" = "泄露保护"; +"settings.application_settings.leak_protection.footer" = "iOS 包含默认在 VPN 之外运行的功能,例如 AirDrop、CarPlay、AirPlay 和个人热点。启用自定义泄露保护会通过 VPN 路由这些流量,但可能会影响这些功能的运行。更多信息"; +"settings.application_settings.leak_protection.more_info" = "更多信息"; +"settings.application_settings.allow_local_network.title" = "允许访问本地网络上的设备"; +"settings.application_settings.allow_local_network.footer" = "连接到 VPN 时,保持与打印机或文件服务器等本地设备的连接。(仅当您信任网络上的人员和设备时才允许此功能。)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™可在您连接到 VPN 时阻止广告、跟踪和恶意软件。"; +"settings.application_settings.leak_protection.alert.title" = "对 VPN 设置的更改将在下次连接时生效"; "settings.content_blocker.title" = "Safari 内容拦截器状态"; "settings.content_blocker.state.title" = "当前状态"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN 连接按钮。VPN 当前已断开连接"; "dashboard.accessibility.vpn.button.isOff" = "VPN 连接按钮。VPN 当前已断开连接"; +"dashboard.vpn.leakprotection.alert.title" = "检测到不安全的 Wi-Fi"; +"dashboard.vpn.leakprotection.alert.message" = "为防止数据泄露,请轻点“立即停用”以关闭“允许访问本地网络上的设备”并自动重新连接。"; +"dashboard.vpn.leakprotection.alert.cta1" = "立即停用"; +"dashboard.vpn.leakprotection.alert.cta2" = "了解详情"; +"dashboard.vpn.leakprotection.alert.cta3" = "忽略"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "为防止数据泄露,请轻点“立即切换”以更改为 IKEv2 VPN 协议并自动重新连接。"; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "立即切换"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "显示服务通信消息"; "inapp.messages.settings.updated" = "设置已更新"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "地区"; +"widget.liveActivity.protocol.title" = "协议"; diff --git a/PIA VPN/zh-Hant.lproj/Localizable.strings b/PIA VPN/zh-Hant.lproj/Localizable.strings index 98795d30e..48a0d5a27 100644 --- a/PIA VPN/zh-Hant.lproj/Localizable.strings +++ b/PIA VPN/zh-Hant.lproj/Localizable.strings @@ -38,6 +38,9 @@ "expiration.title" = "續訂"; "expiration.message" = "您的訂購計劃即將屆滿,請續約以讓自己繼續獲得保障。"; +"local_notification.non_compliant_wifi.title" = "不安全的 Wi-Fi 網路:%@"; +"local_notification.non_compliant_wifi.text" = "點一下這裡即可保護裝置"; + // SHORTCUTS "shortcuts.connect" = "連線"; @@ -134,8 +137,14 @@ "settings.application_settings.active_theme.title" = "使用中的主題"; "settings.application_settings.kill_switch.title" = "VPN 切斷開關"; "settings.application_settings.kill_switch.footer" = "如果 VPN 重新連接,VPN 的終止開關將阻止互聯網連線。這不包括手動斷開連接。"; +"settings.application_settings.leak_protection.title" = "防漏"; +"settings.application_settings.leak_protection.footer" = "iOS 內建功能依預設能在 VPN 以外運作,例如 AirDrop、CarPlay、AirPlay 和個人熱點。如果啟用自定防漏功能,就會把這個流量透過 VPN 轉出,但也可能影響這些功能。詳情"; +"settings.application_settings.leak_protection.more_info" = "詳情"; +"settings.application_settings.allow_local_network.title" = "同意取用本機網路上的裝置"; +"settings.application_settings.allow_local_network.footer" = "在連接到 VPN 時繼續連線到本機裝置,例如印表機或檔案伺服器。(只有在您相信網路上的人和裝置時才能同意)"; "settings.application_settings.mace.title" = "PIA MACE™"; "settings.application_settings.mace.footer" = "PIA MACE™ 會在您連線到 VPN 時封鎖廣告、追蹤器及惡意程式。"; +"settings.application_settings.leak_protection.alert.title" = "更改 VPN 設定後,將在下次連線時生效"; "settings.content_blocker.title" = "Safari 內容阻擋器狀態"; "settings.content_blocker.state.title" = "目前狀態"; @@ -215,6 +224,15 @@ "dashboard.accessibility.vpn.button.isOn" = "VPN 連線按鈕。目前已連線至 VPN"; "dashboard.accessibility.vpn.button.isOff" = "VPN 連線按鈕。目前已解除 VPN 連線"; +"dashboard.vpn.leakprotection.alert.title" = "系統偵測到無安全保護的 Wi-Fi 網路"; +"dashboard.vpn.leakprotection.alert.message" = "若要避免資料洩漏,請點一下「立即停用」,以便關掉「同意取用本機網路上的裝置」,然後自動重新連線。"; +"dashboard.vpn.leakprotection.alert.cta1" = "立即停用"; +"dashboard.vpn.leakprotection.alert.cta2" = "更多內容"; +"dashboard.vpn.leakprotection.alert.cta3" = "忽略"; + +"dashboard.vpn.leakprotection.ikev2.alert.message" = "若要避免資料洩漏,請點一下「立即切換」,以便改成 IKEv2 VPN 通訊協定,然後自動重新連線。"; +"dashboard.vpn.leakprotection.ikev2.alert.cta1" = "立即切換"; + // VPN PERMISSION "vpn_permission.title" = "PIA"; @@ -416,3 +434,7 @@ // INAPP MESSAGES "inapp.messages.toggle.title" = "顯示服務通訊訊息"; "inapp.messages.settings.updated" = "已更新設定"; + +// PIA WIDGET +"widget.liveActivity.region.title" = "區域"; +"widget.liveActivity.protocol.title" = "通訊協定"; diff --git a/PIA VPNTests/Core/Utils/PIAHotspotHelperTests.swift b/PIA VPNTests/Core/Utils/PIAHotspotHelperTests.swift index 26140bcbe..cb578b8f4 100644 --- a/PIA VPNTests/Core/Utils/PIAHotspotHelperTests.swift +++ b/PIA VPNTests/Core/Utils/PIAHotspotHelperTests.swift @@ -69,7 +69,7 @@ class PIAHotspotHelperTests: XCTestCase { } func testConfiguration() { - + /* #if arch(i386) || arch(x86_64) XCTAssertTrue(true) #else @@ -87,5 +87,6 @@ class PIAHotspotHelperTests: XCTestCase { response = hotspotHelper.configureHotspotHelper() XCTAssertFalse(response) #endif + */ } } diff --git a/PIA-VPN_E2E_Tests/Core/BaseTest.swift b/PIA-VPN_E2E_Tests/Core/BaseTest.swift new file mode 100644 index 000000000..837cfc9b1 --- /dev/null +++ b/PIA-VPN_E2E_Tests/Core/BaseTest.swift @@ -0,0 +1,28 @@ +// +// BaseTest.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 17/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Quick +import Nimble +import XCTest + +class BaseTest: QuickSpec{ + static var app: XCUIApplication! + + override class func spec(){ + beforeEach { + app = XCUIApplication() + app.launch() + app.logOut() + app.navigateToLoginScreen() + } + + afterEach { + app.terminate() + } + } +} diff --git a/PIA-VPN_E2E_Tests/Helpers/ElementHelper.swift b/PIA-VPN_E2E_Tests/Helpers/ElementHelper.swift new file mode 100644 index 000000000..91c468989 --- /dev/null +++ b/PIA-VPN_E2E_Tests/Helpers/ElementHelper.swift @@ -0,0 +1,55 @@ +// +// ElementHelper.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 23/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +extension XCUIApplication { + var defaultTimeout: TimeInterval { 10.0 } + var shortTimeout: TimeInterval { 5.0 } + + func navigationbar(with id: String) -> XCUIElement{ + return navigationBars[id] + } + + func button(with id: String) -> XCUIElement { + return buttons[id] + } + + func textField(with id: String) -> XCUIElement { + return textFields[id] + } + + func secureTextField(with id: String) -> XCUIElement { + return secureTextFields[id] + } + + func staticText(with id: String) -> XCUIElement{ + return staticTexts[id] + } + + func alert(with id: String) -> XCUIElement{ + return alerts[id] + } + + func otherElement(with id: String) -> XCUIElement { + return otherElements[id] + } + + func cell(with id: String) -> XCUIElement { + return cells[id] + } + + func searchField(with id: String) -> XCUIElement { + return searchFields[id] + } + + func image(with id: String) -> XCUIElement { + return images[id] + } +} + diff --git a/PIA-VPN_E2E_Tests/Helpers/WaitHelper.swift b/PIA-VPN_E2E_Tests/Helpers/WaitHelper.swift new file mode 100644 index 000000000..b761a71fc --- /dev/null +++ b/PIA-VPN_E2E_Tests/Helpers/WaitHelper.swift @@ -0,0 +1,55 @@ +// +// WaitHelper.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 24/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +enum ElementError: Error, CustomStringConvertible{ + case visibilityTimeout + case invisibilityTimeout + + var description: String{ + switch self{ + case .visibilityTimeout: + return "Element visibility check timed out." + case .invisibilityTimeout: + return "Element invisibility check timed out." + } + } +} + +class WaitHelper{ + static var app: XCUIApplication! + + static func waitForElementToBeVisible(_ element:XCUIElement,timeout: TimeInterval = app.defaultTimeout, onSuccess:() -> Void, onFailure:(ElementError) -> Void){ + let predicate = NSPredicate(format:"exists == true") + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element) + let result = XCTWaiter.wait(for: [expectation], timeout: timeout) + + if result == .completed { + onSuccess() + } + + else{ + onFailure(.visibilityTimeout) + } + } + + static func waitForElementToNotBeVisible(_ element:XCUIElement, timeout: TimeInterval = app.defaultTimeout, onSuccess:() -> Void, onFailure:(ElementError) -> Void){ + let predicate = NSPredicate(format:"exists == false") + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element) + let result = XCTWaiter.wait(for: [expectation], timeout: timeout) + + if result == .completed { + onSuccess() + } + + else{ + onFailure(.invisibilityTimeout) + } + } +} diff --git a/PIA-VPN_E2E_Tests/PIA-VPN-e2e-simulator.xctestplan b/PIA-VPN_E2E_Tests/PIA-VPN-e2e-simulator.xctestplan new file mode 100644 index 000000000..c1a05911c --- /dev/null +++ b/PIA-VPN_E2E_Tests/PIA-VPN-e2e-simulator.xctestplan @@ -0,0 +1,41 @@ +{ + "configurations" : [ + { + "id" : "9111258F-C195-402A-BA43-7C730C200FB6", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "environmentVariableEntries" : [ + { + "key" : "PIA_TEST_PASSWORD", + "value" : "xxxx" + }, + { + "key" : "PIA_TEST_USER", + "value" : "xyz" + } + ], + "targetForVariableExpansion" : { + "containerPath" : "container:PIA VPN.xcodeproj", + "identifier" : "69B70AAF2ACBF51C0072A09D", + "name" : "PIA-VPN_E2E_Tests" + }, + "testExecutionOrdering" : "random", + "uiTestingScreenshotsLifetime" : "keepNever" + }, + "testTargets" : [ + { + "parallelizable" : true, + "target" : { + "containerPath" : "container:PIA VPN.xcodeproj", + "identifier" : "69B70AAF2ACBF51C0072A09D", + "name" : "PIA-VPN_E2E_Tests" + } + } + ], + "version" : 1 +} diff --git a/PIA-VPN_E2E_Tests/Screens/Common.swift b/PIA-VPN_E2E_Tests/Screens/Common.swift new file mode 100644 index 000000000..a8b44cbff --- /dev/null +++ b/PIA-VPN_E2E_Tests/Screens/Common.swift @@ -0,0 +1,31 @@ +// +// Common.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 24/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +extension XCUIApplication{ + /// Sometimes a system alert to request permissions about Notifications or VPN profile installation can appear + /// at any time when the app is running + /// This makes not possible to contitnue with the test unless the alert is dismissed + /// This method dismisses any system alert by pressing 'Allow' button + /// It is adviced that we call this method from all the `setUp` method of the tests from the authentication flow + /// For tests where we require the app to be authenticated, + /// use the method `loginAndInstallVPNProfile(from test: XCTestCase)` from the `setUp` method of the `XCTestCase` + func dismissAnyPermissionSystemAlert(from test: XCTestCase) { + test.addUIInterruptionMonitor(withDescription: "Any system permission alert") { element in + + let allowButton = element.buttons["Allow"].firstMatch + if element.elementType == .alert && allowButton.exists { + allowButton.tap() + return true + } else { + return false + } + } + } +} diff --git a/PIA-VPN_E2E_Tests/Screens/HomeScreen.swift b/PIA-VPN_E2E_Tests/Screens/HomeScreen.swift new file mode 100644 index 000000000..ad183db20 --- /dev/null +++ b/PIA-VPN_E2E_Tests/Screens/HomeScreen.swift @@ -0,0 +1,40 @@ +// +// DashboardScreen.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 17/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +extension XCUIApplication{ + var dashboardMenuButton: XCUIElement{ + button(with: PIALibraryAccessibility.Id.Dashboard.menu) + } + + var connectionButton: XCUIElement { + button(with: AccessibilityId.Dashboard.connectionButton) + } + + var confirmationDialogButton: XCUIElement{ + button(with: PIALibraryAccessibility.Id.Dialog.destructive) + } + + var logOutButton: XCUIElement{ + staticText(with: "Log out") + } + + func logOut() { + guard dashboardMenuButton.exists else { return } + dashboardMenuButton.tap() + + if logOutButton.waitForExistence(timeout: defaultTimeout) { + logOutButton.tap() + if confirmationDialogButton.waitForExistence(timeout: shortTimeout) { + confirmationDialogButton.tap() + } + welcomeLoginButton.waitForExistence(timeout: defaultTimeout) + } + } +} diff --git a/PIA-VPN_E2E_Tests/Screens/LoginScreen.swift b/PIA-VPN_E2E_Tests/Screens/LoginScreen.swift new file mode 100644 index 000000000..17701694b --- /dev/null +++ b/PIA-VPN_E2E_Tests/Screens/LoginScreen.swift @@ -0,0 +1,55 @@ + +import XCTest + +extension XCUIApplication { + var loginButton: XCUIElement { + button(with: PIALibraryAccessibility.Id.Login.submit) + } + + var loginUsernameTextField: XCUIElement { + textField(with: PIALibraryAccessibility.Id.Login.username) + } + + var loginPasswordTextField: XCUIElement { + secureTextField(with: PIALibraryAccessibility.Id.Login.password) + } + + var loginErrorMessage: XCUIElement { + otherElement(with: PIALibraryAccessibility.Id.Login.Error.banner) + } + + func fillLoginScreen(with credentials: Credentials) { + loginUsernameTextField.waitForExistence(timeout: defaultTimeout) && loginPasswordTextField.waitForExistence(timeout: defaultTimeout) + loginUsernameTextField.tap() + loginUsernameTextField.typeText(credentials.username) + loginPasswordTextField.tap() + loginPasswordTextField.typeText(credentials.password) + } + + /// This method authenticates the user and installs the VPN profile + /// Use this method when we are testing flows where the app has to be logged in already. + /// In such cases, it is recommended to call this method from the `setUp` of each `XCTestCase` class + /// NOTE: the app must be already in the Login Screen for this method to work + /// So before calling this method, make sure to navigate to the login screen + func loginAndInstallVPNProfile(from test: XCTestCase) { + // Listens to any interruption due to a system alert permission + // and presses the 'Allow' button + // (like the VPN Permission system alert) + dismissAnyPermissionSystemAlert(from: test) + + // Log out if needed + logOut() + + navigateToLoginScreen() + fillLoginScreen(with: CredentialsUtil.credentials(type: .valid)) + loginButton.tap() + + guard vpnPermissionScreen.waitForExistence(timeout: defaultTimeout) else { return } + guard vpnPermissionButton.exists else { return } + vpnPermissionButton.tap() + + swipeUp() + + connectionButton.waitForExistence(timeout: defaultTimeout) + } +} diff --git a/PIA-VPN_E2E_Tests/Screens/VPNPermissionScreen.swift b/PIA-VPN_E2E_Tests/Screens/VPNPermissionScreen.swift new file mode 100644 index 000000000..d75594192 --- /dev/null +++ b/PIA-VPN_E2E_Tests/Screens/VPNPermissionScreen.swift @@ -0,0 +1,28 @@ +// +// VPNPermissionScreen.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 17/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +extension XCUIApplication{ + + var vpnPermissionScreen: XCUIElement { + otherElement(with: AccessibilityId.VPNPermission.screen) + } + + var vpnPermissionButton: XCUIElement{ + button(with: AccessibilityId.VPNPermission.submit) + } + + var vpnPermissionAlertText: XCUIElement{ + alert(with: "PIA VPN dev” Would Like to Add VPN Configurations") + } + + var vpnAllowButton: XCUIElement{ + button(with: "Allow").firstMatch + } +} diff --git a/PIA-VPN_E2E_Tests/Screens/WelcomeScreen.swift b/PIA-VPN_E2E_Tests/Screens/WelcomeScreen.swift new file mode 100644 index 000000000..b02e21827 --- /dev/null +++ b/PIA-VPN_E2E_Tests/Screens/WelcomeScreen.swift @@ -0,0 +1,29 @@ +// +// WelcomeScreen.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 24/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +extension XCUIApplication { + var welcomeLoginButton: XCUIElement { + button(with: PIALibraryAccessibility.Id.Login.submitNew) + } + + var welcomeLoginButtonOldVersion: XCUIElement { + button(with: PIALibraryAccessibility.Id.Login.submit) + } + + func navigateToLoginScreen() { + if welcomeLoginButton.waitForExistence(timeout: shortTimeout) { + welcomeLoginButton.tap() + } else { + if welcomeLoginButtonOldVersion.waitForExistence(timeout: shortTimeout) { + welcomeLoginButtonOldVersion.tap() + } + } + } +} diff --git a/PIA-VPN_E2E_Tests/Tests/OnboardingTests.swift b/PIA-VPN_E2E_Tests/Tests/OnboardingTests.swift new file mode 100644 index 000000000..cad093f5c --- /dev/null +++ b/PIA-VPN_E2E_Tests/Tests/OnboardingTests.swift @@ -0,0 +1,35 @@ +// +// OnboardingTests.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 17/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Nimble + +class OnboardingTests:BaseTest{ + override class func spec(){ + super.spec() + + describe("onboarding vpn permission tests"){ + context("vpn profile installation permission"){ + it("should display the home screen after allowing vpn profile installation"){ + app.fillLoginScreen(with: CredentialsUtil.credentials(type: .valid)) + app.loginButton.tap() + app.vpnPermissionScreen.waitForExistence(timeout:app.defaultTimeout) + app.vpnPermissionButton.waitForExistence(timeout:app.defaultTimeout) + + app.vpnPermissionButton.tap() + + app.vpnPermissionAlertText.waitForExistence(timeout: app.defaultTimeout) + app.vpnAllowButton.waitForExistence(timeout: app.defaultTimeout) + app.swipeUp() + + expect(app.dashboardMenuButton.waitForExistence(timeout: app.defaultTimeout)) + expect(app.vpnPermissionScreen.exists).to(beFalse()) + } + } + } + } +} diff --git a/PIA-VPN_E2E_Tests/Tests/PIAExampleWithAuthenticatedAppTest.swift b/PIA-VPN_E2E_Tests/Tests/PIAExampleWithAuthenticatedAppTest.swift new file mode 100644 index 000000000..720a33649 --- /dev/null +++ b/PIA-VPN_E2E_Tests/Tests/PIAExampleWithAuthenticatedAppTest.swift @@ -0,0 +1,45 @@ +// +// PIAExampleWithAuthenticatedAppTest.swift +// PIA-VPN_E2E_Tests +// +// Created by Laura S on 10/6/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import XCTest + +/// This is an example of how to setup a XCTestCase +/// with the app on Authenticated state and the VPN Profile installed +/// from the Onboarding flow +final class PIAExampleWithAuthenticatedAppTest: XCTestCase { + private var app: XCUIApplication! + + override func setUpWithError() throws { + continueAfterFailure = false + // 1. Instantiate the app process + app = XCUIApplication(bundleIdentifier: "com.privateinternetaccess.ios.PIA-VPN") + // 2. Launch the app process + app.launch() + + // 3. Authenticates with valid credentials coming from the Environment variables and installs the VPN Profile + app.loginAndInstallVPNProfile(from: self) + + // NOTE: To add valid credentials in the Environment Variables: + // - Select `PIA-VPN_E2E_Tests` schema + // - On the dropdown menu -> Edit Scheme + // - Tap the 'Run' Button from the left side + // - Add correct values on the Env Variables called `PIA_TEST_USER` and `PIA_TEST_PASSWORD` -> IMPORTANT: Do not commit these updates + } + + override func tearDownWithError() throws { + // Terminates the app process everytime a test has finished its execution + app.terminate() + } + + func testExample() throws { + + // Additional test steps here... + } + + +} diff --git a/PIA-VPN_E2E_Tests/Tests/SignInTests.swift b/PIA-VPN_E2E_Tests/Tests/SignInTests.swift new file mode 100644 index 000000000..3d7479659 --- /dev/null +++ b/PIA-VPN_E2E_Tests/Tests/SignInTests.swift @@ -0,0 +1,32 @@ +// +// SignInTests.swift +// PIA-VPN_E2E_Tests +// +// Created by Geneva Parayno on 17/10/23. +// Copyright © 2023 Private Internet Access Inc. All rights reserved. +// + +import Nimble + +class SignInTests: BaseTest { + override class func spec(){ + super.spec() + + describe("test sign-in"){ + context("account validations"){ + it("should successfully sign in with valid credentials"){ + app.fillLoginScreen(with: CredentialsUtil.credentials(type: .valid)) + app.loginButton.tap() + expect(app.vpnPermissionScreen.waitForExistence(timeout:app.defaultTimeout)) + } + + it("should display error mesages with invalid credentials"){ + app.fillLoginScreen(with: CredentialsUtil.credentials(type: .invalid)) + app.loginButton.tap() + expect(app.loginErrorMessage.waitForExistence(timeout: app.shortTimeout)) + expect(app.vpnPermissionScreen.waitForExistence(timeout:app.defaultTimeout)).to(beFalse()) + } + } + } + } +} diff --git a/PIA-VPN_E2E_Tests/Util/CredentialsUtil.swift b/PIA-VPN_E2E_Tests/Util/CredentialsUtil.swift new file mode 100644 index 000000000..48d744d0e --- /dev/null +++ b/PIA-VPN_E2E_Tests/Util/CredentialsUtil.swift @@ -0,0 +1,32 @@ +// +// CredentialsUtil.swift +// PIA VPN +// +// Created by Waleed Mahmood on 08.03.22. +// Copyright © 2022 Private Internet Access Inc. All rights reserved. +// + +import Foundation + +public enum CredentialsType: String { + case valid = "valid" + case invalid = "invalid" +} + +public struct Credentials: Codable { + let username: String + let password: String +} + +public class CredentialsUtil { + public static func credentials(type: CredentialsType) -> Credentials { + switch type { + case .invalid: + return Credentials(username: "fakeUser", password: "fakePassword123") + case .valid: + let testUser = ProcessInfo.processInfo.environment["PIA_TEST_USER"] ?? "user-not-found" + let testPassword = ProcessInfo.processInfo.environment["PIA_TEST_PASSWORD"] ?? "password-not-found" + return Credentials(username: testUser, password: testPassword) + } + } +} diff --git a/PIAWidget/Assets.xcassets/PIA-logo.imageset/Contents.json b/PIAWidget/Assets.xcassets/PIA-logo.imageset/Contents.json new file mode 100644 index 000000000..02b5d87ae --- /dev/null +++ b/PIAWidget/Assets.xcassets/PIA-logo.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Group.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/PIAWidget/Assets.xcassets/PIA-logo.imageset/Group.pdf b/PIAWidget/Assets.xcassets/PIA-logo.imageset/Group.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9936158f29a6f515a1838b9bbb5cf834072fb9be GIT binary patch literal 7194 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 E0b~x300000 literal 0 HcmV?d00001 diff --git a/PIAWidget/Assets.xcassets/connected-button.imageset/Contents.json b/PIAWidget/Assets.xcassets/connected-button.imageset/Contents.json new file mode 100644 index 000000000..84d9e024a --- /dev/null +++ b/PIAWidget/Assets.xcassets/connected-button.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Group 13.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/PIAWidget/Assets.xcassets/connected-button.imageset/Group 13.pdf b/PIAWidget/Assets.xcassets/connected-button.imageset/Group 13.pdf new file mode 100644 index 0000000000000000000000000000000000000000..af5a8894394f174787700f8308a924c5213a3f51 GIT binary patch literal 3654 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

k|StwD+QWzv(ruabis%?Y-#UZ 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 z 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 z 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