Add minimal TV app

Closes #315
This commit is contained in:
Davide De Rosa 2023-12-16 20:58:54 +01:00
parent 47c6b02c4d
commit 5c5697762b
No known key found for this signature in database
GPG Key ID: A48836171C759F5E
261 changed files with 2155 additions and 223 deletions

7
.env.tvos Normal file
View File

@ -0,0 +1,7 @@
INFO_PLIST_ROOT="Passepartout/App"
MATCH_PLATFORM="tvos"
GYM_SCHEME="Passepartout"
DELIVER_PLATFORM="appletvos"
DELIVER_METADATA_PATH="Passepartout/App/fastlane/tvos/metadata"
DELIVER_SCREENSHOTS_PATH="Passepartout/App/fastlane/tvos/screenshots"
CHANGELOG="CHANGELOG.md"

View File

@ -17,7 +17,7 @@ env:
jobs: jobs:
build_upload: build_upload:
name: Distribute Private Beta name: Distribute Private Beta
runs-on: macos-12 runs-on: macos-13
environment: environment:
name: private_beta name: private_beta
strategy: strategy:
@ -50,7 +50,7 @@ jobs:
go-version: "^1.17" go-version: "^1.17"
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: latest-stable xcode-version: "15.1"
- name: Create keychain - name: Create keychain
uses: ./.github/actions/create-keychain uses: ./.github/actions/create-keychain
with: with:

View File

@ -16,7 +16,7 @@ env:
jobs: jobs:
build_upload: build_upload:
name: Upload to ASC name: Upload to ASC
runs-on: macos-12 runs-on: macos-13
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
@ -48,7 +48,7 @@ jobs:
go-version: "^1.17" go-version: "^1.17"
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: latest-stable xcode-version: "15.1"
- name: Store app version - name: Store app version
id: app_version id: app_version
if: ${{ matrix.use_version }} if: ${{ matrix.use_version }}

View File

@ -18,7 +18,7 @@ concurrency:
jobs: jobs:
run_tests: run_tests:
name: Run tests name: Run tests
runs-on: macos-12 runs-on: macos-13
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -26,7 +26,7 @@ jobs:
submodules: true submodules: true
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: latest-stable xcode-version: "15.1"
- uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@v1
with: with:
bundler-cache: true bundler-cache: true

View File

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- App for tvOS. [#315](https://github.com/passepartoutvpn/passepartout-apple/issues/315)
- WireGuard: Show data count. [#312](https://github.com/passepartoutvpn/passepartout-apple/issues/312) - WireGuard: Show data count. [#312](https://github.com/passepartoutvpn/passepartout-apple/issues/312)
### Changed ### Changed

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 54; objectVersion = 55;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -19,7 +19,7 @@
0E0838FA2877325A00A34EC0 /* LightProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0838F92877325A00A34EC0 /* LightProviderManager.swift */; }; 0E0838FA2877325A00A34EC0 /* LightProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0838F92877325A00A34EC0 /* LightProviderManager.swift */; };
0E0838FB2877325A00A34EC0 /* LightProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0838F92877325A00A34EC0 /* LightProviderManager.swift */; }; 0E0838FB2877325A00A34EC0 /* LightProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0838F92877325A00A34EC0 /* LightProviderManager.swift */; };
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0838FC2877334300A34EC0 /* DefaultLightProviderManager.swift */; }; 0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0838FC2877334300A34EC0 /* DefaultLightProviderManager.swift */; };
0E09E35D2834172800BE1BAE /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 0E09E35C2834172800BE1BAE /* Credits.rtf */; }; 0E09E35D2834172800BE1BAE /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 0E09E35C2834172800BE1BAE /* Credits.rtf */; platformFilter = maccatalyst; };
0E0BD27327B2EA2C00583AC5 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27227B2EA2C00583AC5 /* MainView.swift */; }; 0E0BD27327B2EA2C00583AC5 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27227B2EA2C00583AC5 /* MainView.swift */; };
0E0BD27627B2EB2200583AC5 /* DonateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27527B2EB2200583AC5 /* DonateView.swift */; }; 0E0BD27627B2EB2200583AC5 /* DonateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27527B2EB2200583AC5 /* DonateView.swift */; };
0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */; }; 0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */; };
@ -30,6 +30,7 @@
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; }; 0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0F4C6529C84CF60022E884 /* LogoView.swift */; };
0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */; }; 0E1AD5CE2A268645002AE6E6 /* Errors+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */; };
0E1B5F5C29C506AD00FE7D18 /* DiagnosticsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* DiagnosticsSection.swift */; }; 0E1B5F5C29C506AD00FE7D18 /* DiagnosticsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1B5F5B29C506AC00FE7D18 /* DiagnosticsSection.swift */; };
0E1DC1BF2B3618EE008B755E /* ProfileView+TV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1DC1BE2B3618EE008B755E /* ProfileView+TV.swift */; };
0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */; }; 0E1F5628287F0ECB00F8ADD7 /* ProviderProfileItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */; };
0E1F562B287F0EF100F8ADD7 /* ProviderProfileItem+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5629287F0EEE00F8ADD7 /* ProviderProfileItem+ViewModel.swift */; }; 0E1F562B287F0EF100F8ADD7 /* ProviderProfileItem+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1F5629287F0EEE00F8ADD7 /* ProviderProfileItem+ViewModel.swift */; };
0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E293856285A73BC002A6E0E /* AppContext+Shared.swift */; }; 0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E293856285A73BC002A6E0E /* AppContext+Shared.swift */; };
@ -47,6 +48,9 @@
0E2E0B762B335AAB00E3204A /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B722B335AAB00E3204A /* UpgradeManagerStrategy.swift */; }; 0E2E0B762B335AAB00E3204A /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B722B335AAB00E3204A /* UpgradeManagerStrategy.swift */; };
0E2E0B772B335AAB00E3204A /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B732B335AAB00E3204A /* UpgradeManager.swift */; }; 0E2E0B772B335AAB00E3204A /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B732B335AAB00E3204A /* UpgradeManager.swift */; };
0E2E0B782B335AAB00E3204A /* PersistenceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B742B335AAB00E3204A /* PersistenceManager.swift */; }; 0E2E0B782B335AAB00E3204A /* PersistenceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2E0B742B335AAB00E3204A /* PersistenceManager.swift */; };
0E330F532B30469700930C7C /* MockProfileRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E330F522B30469700930C7C /* MockProfileRepository.swift */; };
0E330F552B30946600930C7C /* ActiveProfileView+TV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E330F542B30946600930C7C /* ActiveProfileView+TV.swift */; };
0E330F572B30952300930C7C /* ProfilesList+TV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E330F562B30952300930C7C /* ProfilesList+TV.swift */; };
0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */; }; 0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */; };
0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */; }; 0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */; };
0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */; }; 0E34A2CF27CADA6300C73B67 /* GenericVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */; };
@ -108,6 +112,7 @@
0E7A8C0C2A1D4A6100780F4B /* PassepartoutLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0E7A8C0B2A1D4A6100780F4B /* PassepartoutLibrary */; }; 0E7A8C0C2A1D4A6100780F4B /* PassepartoutLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0E7A8C0B2A1D4A6100780F4B /* PassepartoutLibrary */; };
0E7A8C0F2A1D54DE00780F4B /* Picker+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */; }; 0E7A8C0F2A1D54DE00780F4B /* Picker+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */; };
0E7A8C102A1D54DE00780F4B /* Picker+Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */; }; 0E7A8C102A1D54DE00780F4B /* Picker+Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */; };
0E859B832B2EE08700F80D92 /* OrganizerView+TV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E859B822B2EE08700F80D92 /* OrganizerView+TV.swift */; };
0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */; }; 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */; };
0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */; }; 0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */; };
0E92D7C927F1042A0033CB7B /* ProfileView+Extra.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */; }; 0E92D7C927F1042A0033CB7B /* ProfileView+Extra.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */; };
@ -184,10 +189,12 @@
0ED89C1727DE0E05008B36D6 /* IntentEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */; }; 0ED89C1727DE0E05008B36D6 /* IntentEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1627DE0E05008B36D6 /* IntentEditView.swift */; };
0ED89C1C27DE3ABC008B36D6 /* ShortcutsView+Add.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */; }; 0ED89C1C27DE3ABC008B36D6 /* ShortcutsView+Add.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */; };
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */; }; 0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */; };
0EDDEC7D28D0DC140017802E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EDDEC7C28D0DC130017802E /* LaunchScreen.storyboard */; };
0EDE02C227F61C79000FBE3C /* EditableTextList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */; }; 0EDE02C227F61C79000FBE3C /* EditableTextList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */; };
0EE11CD2280D8317003BE431 /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE11CD1280D8317003BE431 /* SettingsButton.swift */; }; 0EE11CD2280D8317003BE431 /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE11CD1280D8317003BE431 /* SettingsButton.swift */; };
0EE562782B2EE3EC000C52F6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EE562772B2EE3EC000C52F6 /* LaunchScreen.storyboard */; platformFilters = (ios, maccatalyst, ); };
0EE79B2F2B2ED99500C1220C /* MainView+TV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE79B2E2B2ED99500C1220C /* MainView+TV.swift */; };
0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */; }; 0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */; };
0EED5B9D2B3700AB009D1E97 /* TunnelError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF656402B36C00E00CEFC96 /* TunnelError.swift */; };
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0FAF527DD0211007EB181 /* PaywallView.swift */; }; 0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0FAF527DD0211007EB181 /* PaywallView.swift */; };
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA591122733DD4E0096F796 /* IntentDispatcher.swift */; }; 0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA591122733DD4E0096F796 /* IntentDispatcher.swift */; };
0EF0FAF927DD212C007EB181 /* IntentActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0FAF827DD212C007EB181 /* IntentActivity.swift */; }; 0EF0FAF927DD212C007EB181 /* IntentActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF0FAF827DD212C007EB181 /* IntentActivity.swift */; };
@ -195,6 +202,10 @@
0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */; }; 0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */; };
0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */; }; 0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */; };
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */; }; 0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */; };
0EF6563E2B36BFCD00CEFC96 /* NEPacketTunnelProvider+Expiration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF6563D2B36BFCD00CEFC96 /* NEPacketTunnelProvider+Expiration.swift */; };
0EF6563F2B36BFCD00CEFC96 /* NEPacketTunnelProvider+Expiration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF6563D2B36BFCD00CEFC96 /* NEPacketTunnelProvider+Expiration.swift */; };
0EF656422B36C01200CEFC96 /* TunnelError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF656402B36C00E00CEFC96 /* TunnelError.swift */; };
0EF656432B36C01200CEFC96 /* TunnelError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF656402B36C00E00CEFC96 /* TunnelError.swift */; };
0EF8C5A828213C510053CE89 /* OrganizerView+Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */; }; 0EF8C5A828213C510053CE89 /* OrganizerView+Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */; };
A38D607728AFCFD20005C271 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A38D607628AFCFD20005C271 /* SettingsView.swift */; }; A38D607728AFCFD20005C271 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A38D607628AFCFD20005C271 /* SettingsView.swift */; };
A3A7CC462878DC8300172D7D /* ProviderServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3A7CC452878DC8300172D7D /* ProviderServerItem.swift */; }; A3A7CC462878DC8300172D7D /* ProviderServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3A7CC452878DC8300172D7D /* ProviderServerItem.swift */; };
@ -248,6 +259,13 @@
remoteGlobalIDString = 0ECF71F327B6D9CD00CDB528; remoteGlobalIDString = 0ECF71F327B6D9CD00CDB528;
remoteInfo = WireGuardGo; remoteInfo = WireGuardGo;
}; };
0EE79B342B2EDB9C00C1220C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0E57F63020C83FC5008323CF /* Project object */;
proxyType = 1;
remoteGlobalIDString = 0EE79B302B2EDB5D00C1220C;
remoteInfo = WireGuardGoTV;
};
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@ -311,6 +329,7 @@
0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Errors+L10n.swift"; sourceTree = "<group>"; }; 0E1AD5CD2A268645002AE6E6 /* Errors+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Errors+L10n.swift"; sourceTree = "<group>"; };
0E1B5F5B29C506AC00FE7D18 /* DiagnosticsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsSection.swift; sourceTree = "<group>"; }; 0E1B5F5B29C506AC00FE7D18 /* DiagnosticsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsSection.swift; sourceTree = "<group>"; };
0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
0E1DC1BE2B3618EE008B755E /* ProfileView+TV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+TV.swift"; sourceTree = "<group>"; };
0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileItem.swift; sourceTree = "<group>"; }; 0E1F5627287F0ECB00F8ADD7 /* ProviderProfileItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileItem.swift; sourceTree = "<group>"; };
0E1F5629287F0EEE00F8ADD7 /* ProviderProfileItem+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProviderProfileItem+ViewModel.swift"; sourceTree = "<group>"; }; 0E1F5629287F0EEE00F8ADD7 /* ProviderProfileItem+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProviderProfileItem+ViewModel.swift"; sourceTree = "<group>"; };
0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; }; 0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
@ -329,6 +348,9 @@
0E2E0B722B335AAB00E3204A /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; }; 0E2E0B722B335AAB00E3204A /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; };
0E2E0B732B335AAB00E3204A /* UpgradeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; }; 0E2E0B732B335AAB00E3204A /* UpgradeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; };
0E2E0B742B335AAB00E3204A /* PersistenceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistenceManager.swift; sourceTree = "<group>"; }; 0E2E0B742B335AAB00E3204A /* PersistenceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistenceManager.swift; sourceTree = "<group>"; };
0E330F522B30469700930C7C /* MockProfileRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfileRepository.swift; sourceTree = "<group>"; };
0E330F542B30946600930C7C /* ActiveProfileView+TV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ActiveProfileView+TV.swift"; sourceTree = "<group>"; };
0E330F562B30952300930C7C /* ProfilesList+TV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfilesList+TV.swift"; sourceTree = "<group>"; };
0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenVPN+L10n.swift"; sourceTree = "<group>"; }; 0E34A2AF27CAA84500C73B67 /* OpenVPN+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenVPN+L10n.swift"; sourceTree = "<group>"; };
0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Core+L10n.swift"; sourceTree = "<group>"; }; 0E34A2B527CAA8CC00C73B67 /* Core+L10n.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Core+L10n.swift"; sourceTree = "<group>"; };
0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericVersionView.swift; sourceTree = "<group>"; }; 0E34A2CE27CADA6300C73B67 /* GenericVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericVersionView.swift; sourceTree = "<group>"; };
@ -391,6 +413,7 @@
0E7577DE2817E22C00081CBE /* VPNToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNToggle.swift; sourceTree = "<group>"; }; 0E7577DE2817E22C00081CBE /* VPNToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNToggle.swift; sourceTree = "<group>"; };
0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Picker+OpenVPN.swift"; sourceTree = "<group>"; }; 0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Picker+OpenVPN.swift"; sourceTree = "<group>"; };
0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Picker+Network.swift"; sourceTree = "<group>"; }; 0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Picker+Network.swift"; sourceTree = "<group>"; };
0E859B822B2EE08700F80D92 /* OrganizerView+TV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+TV.swift"; sourceTree = "<group>"; };
0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostViewModel.swift; sourceTree = "<group>"; }; 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostViewModel.swift; sourceTree = "<group>"; };
0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Configuration.swift"; sourceTree = "<group>"; }; 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Configuration.swift"; sourceTree = "<group>"; };
0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Extra.swift"; sourceTree = "<group>"; }; 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Extra.swift"; sourceTree = "<group>"; };
@ -493,13 +516,14 @@
0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShortcutsView+Add.swift"; sourceTree = "<group>"; }; 0ED89C1B27DE3ABC008B36D6 /* ShortcutsView+Add.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShortcutsView+Add.swift"; sourceTree = "<group>"; };
0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentAddView.swift; sourceTree = "<group>"; }; 0ED89C1D27DE3F8D008B36D6 /* IntentAddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentAddView.swift; sourceTree = "<group>"; };
0EDCEF692B337BEB0023A7FF /* PassepartoutLibrary.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PassepartoutLibrary.xctestplan; sourceTree = "<group>"; }; 0EDCEF692B337BEB0023A7FF /* PassepartoutLibrary.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PassepartoutLibrary.xctestplan; sourceTree = "<group>"; };
0EDDEC7C28D0DC130017802E /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
0EDE02C127F61C79000FBE3C /* EditableTextList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextList.swift; sourceTree = "<group>"; }; 0EDE02C127F61C79000FBE3C /* EditableTextList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextList.swift; sourceTree = "<group>"; };
0EDE8DBF20C86910004C739C /* PassepartoutOpenVPNTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutOpenVPNTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 0EDE8DBF20C86910004C739C /* PassepartoutOpenVPNTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutOpenVPNTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
0EDE8DC320C86910004C739C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 0EDE8DC320C86910004C739C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0EDE8DD220C86978004C739C /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; 0EDE8DD220C86978004C739C /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; };
0EDE8DE220C86A13004C739C /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; }; 0EDE8DE220C86A13004C739C /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
0EE11CD1280D8317003BE431 /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.swift; sourceTree = "<group>"; }; 0EE11CD1280D8317003BE431 /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.swift; sourceTree = "<group>"; };
0EE562772B2EE3EC000C52F6 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
0EE79B2E2B2ED99500C1220C /* MainView+TV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainView+TV.swift"; sourceTree = "<group>"; };
0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VPNProtocolType+FileExtensions.swift"; sourceTree = "<group>"; }; 0EE8B7E227FF340F00B68621 /* VPNProtocolType+FileExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VPNProtocolType+FileExtensions.swift"; sourceTree = "<group>"; };
0EF0FAF527DD0211007EB181 /* PaywallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallView.swift; sourceTree = "<group>"; }; 0EF0FAF527DD0211007EB181 /* PaywallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallView.swift; sourceTree = "<group>"; };
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentActivity.swift; sourceTree = "<group>"; }; 0EF0FAF827DD212C007EB181 /* IntentActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentActivity.swift; sourceTree = "<group>"; };
@ -507,6 +531,8 @@
0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderView.swift; sourceTree = "<group>"; }; 0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderView.swift; sourceTree = "<group>"; };
0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProfileView.swift; sourceTree = "<group>"; }; 0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProfileView.swift; sourceTree = "<group>"; };
0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderViewModel.swift; sourceTree = "<group>"; }; 0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderViewModel.swift; sourceTree = "<group>"; };
0EF6563D2B36BFCD00CEFC96 /* NEPacketTunnelProvider+Expiration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEPacketTunnelProvider+Expiration.swift"; sourceTree = "<group>"; };
0EF656402B36C00E00CEFC96 /* TunnelError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelError.swift; sourceTree = "<group>"; };
0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Profiles.swift"; sourceTree = "<group>"; }; 0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Profiles.swift"; sourceTree = "<group>"; };
A373484D29DC4F4500D1613C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; }; A373484D29DC4F4500D1613C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
A373484E29DC504000D1613C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; }; A373484E29DC504000D1613C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -572,6 +598,7 @@
0E021D9B284E68580077EF5D /* AppContext.swift */, 0E021D9B284E68580077EF5D /* AppContext.swift */,
0E293856285A73BC002A6E0E /* AppContext+Shared.swift */, 0E293856285A73BC002A6E0E /* AppContext+Shared.swift */,
0E021D9A284E68580077EF5D /* CoreContext.swift */, 0E021D9A284E68580077EF5D /* CoreContext.swift */,
0E330F522B30469700930C7C /* MockProfileRepository.swift */,
); );
path = Context; path = Context;
sourceTree = "<group>"; sourceTree = "<group>";
@ -630,6 +657,7 @@
0E35C0AE280EF8A80071FA35 /* Views */ = { 0E35C0AE280EF8A80071FA35 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0EE79B2D2B2ED96D00C1220C /* TV */,
0E44689B27B11B5300A14CE4 /* AboutView.swift */, 0E44689B27B11B5300A14CE4 /* AboutView.swift */,
0ECF71ED27B6A99300CDB528 /* AccountView.swift */, 0ECF71ED27B6A99300CDB528 /* AccountView.swift */,
0E039278281890B100827C10 /* AddHostView.swift */, 0E039278281890B100827C10 /* AddHostView.swift */,
@ -673,6 +701,7 @@
0E3CD482280DAE92007075C0 /* ProfileView+MainMenu.swift */, 0E3CD482280DAE92007075C0 /* ProfileView+MainMenu.swift */,
0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */, 0E3B7FD927E51A0200C66F13 /* ProfileView+Provider.swift */,
0EBC074B27EB673C00208AD9 /* ProfileView+Rename.swift */, 0EBC074B27EB673C00208AD9 /* ProfileView+Rename.swift */,
0E1DC1BE2B3618EE008B755E /* ProfileView+TV.swift */,
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */, 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */,
0E71ACF027C1073800F85C4B /* ProviderLocationView.swift */, 0E71ACF027C1073800F85C4B /* ProviderLocationView.swift */,
0E71ACEE27C106B400F85C4B /* ProviderPresetView.swift */, 0E71ACEE27C106B400F85C4B /* ProviderPresetView.swift */,
@ -889,7 +918,7 @@
0E57F64720C83FC7008323CF /* Info.plist */, 0E57F64720C83FC7008323CF /* Info.plist */,
0E0C072B236087A100155AAC /* InfoPlist.strings */, 0E0C072B236087A100155AAC /* InfoPlist.strings */,
0E9E5AE227B44CF1008C95DA /* Localizable.strings */, 0E9E5AE227B44CF1008C95DA /* Localizable.strings */,
0EDDEC7C28D0DC130017802E /* LaunchScreen.storyboard */, 0EE562772B2EE3EC000C52F6 /* LaunchScreen.storyboard */,
0E3FC6852867A3F9009B851C /* AppDelegate.swift */, 0E3FC6852867A3F9009B851C /* AppDelegate.swift */,
0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */, 0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */,
0E0F4C5929C761850022E884 /* SceneDelegate.swift */, 0E0F4C5929C761850022E884 /* SceneDelegate.swift */,
@ -975,6 +1004,8 @@
0ED2B33D27D3C53400FD8EA9 /* WireGuard */, 0ED2B33D27D3C53400FD8EA9 /* WireGuard */,
0ED31C3B20CF39510027975F /* Tunnel.entitlements */, 0ED31C3B20CF39510027975F /* Tunnel.entitlements */,
0EDE8DC320C86910004C739C /* Info.plist */, 0EDE8DC320C86910004C739C /* Info.plist */,
0EF6563D2B36BFCD00CEFC96 /* NEPacketTunnelProvider+Expiration.swift */,
0EF656402B36C00E00CEFC96 /* TunnelError.swift */,
); );
path = Tunnel; path = Tunnel;
sourceTree = "<group>"; sourceTree = "<group>";
@ -987,6 +1018,17 @@
name = Packages; name = Packages;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0EE79B2D2B2ED96D00C1220C /* TV */ = {
isa = PBXGroup;
children = (
0E330F542B30946600930C7C /* ActiveProfileView+TV.swift */,
0EE79B2E2B2ED99500C1220C /* MainView+TV.swift */,
0E859B822B2EE08700F80D92 /* OrganizerView+TV.swift */,
0E330F562B30952300930C7C /* ProfilesList+TV.swift */,
);
path = TV;
sourceTree = "<group>";
};
374B9F085E1148C37CF9117A /* Frameworks */ = { 374B9F085E1148C37CF9117A /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1014,6 +1056,20 @@
passBuildSettingsInEnvironment = 1; passBuildSettingsInEnvironment = 1;
productName = PassepartoutWireGuard; productName = PassepartoutWireGuard;
}; };
0EE79B302B2EDB5D00C1220C /* WireGuardGoTV */ = {
isa = PBXLegacyTarget;
buildArgumentsString = "$(ACTION)";
buildConfigurationList = 0EE79B312B2EDB5D00C1220C /* Build configuration list for PBXLegacyTarget "WireGuardGoTV" */;
buildPhases = (
);
buildToolPath = "$(PROJECT_DIR)/Passepartout/App/Scripts/build_wireguard_go_bridge.sh";
buildWorkingDirectory = "";
dependencies = (
);
name = WireGuardGoTV;
passBuildSettingsInEnvironment = 1;
productName = PassepartoutWireGuard;
};
/* End PBXLegacyTarget section */ /* End PBXLegacyTarget section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -1052,9 +1108,10 @@
dependencies = ( dependencies = (
0ECB78E7285F5CC400B0E460 /* PBXTargetDependency */, 0ECB78E7285F5CC400B0E460 /* PBXTargetDependency */,
0E41BDAB286713F6006346B4 /* PBXTargetDependency */, 0E41BDAB286713F6006346B4 /* PBXTargetDependency */,
0ECF71FC27B6DA6700CDB528 /* PBXTargetDependency */,
0EB2B14A2733FB6F007705AB /* PBXTargetDependency */, 0EB2B14A2733FB6F007705AB /* PBXTargetDependency */,
0ED2B36227D3C99100FD8EA9 /* PBXTargetDependency */, 0ED2B36227D3C99100FD8EA9 /* PBXTargetDependency */,
0ECF71FC27B6DA6700CDB528 /* PBXTargetDependency */,
0EE79B352B2EDB9C00C1220C /* PBXTargetDependency */,
); );
name = Passepartout; name = Passepartout;
packageProductDependencies = ( packageProductDependencies = (
@ -1210,8 +1267,9 @@
0ECB78D9285F52F700B0E460 /* PassepartoutMac */, 0ECB78D9285F52F700B0E460 /* PassepartoutMac */,
0E41BD96286711C3006346B4 /* PassepartoutLauncher */, 0E41BD96286711C3006346B4 /* PassepartoutLauncher */,
0EDE8DBE20C86910004C739C /* OpenVPNTunnel */, 0EDE8DBE20C86910004C739C /* OpenVPNTunnel */,
0ECF71F327B6D9CD00CDB528 /* WireGuardGo */,
0ED2B33E27D3C77800FD8EA9 /* WireGuardTunnel */, 0ED2B33E27D3C77800FD8EA9 /* WireGuardTunnel */,
0ECF71F327B6D9CD00CDB528 /* WireGuardGo */,
0EE79B302B2EDB5D00C1220C /* WireGuardGoTV */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -1229,8 +1287,8 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0E6059CB27FCC5DE003F4063 /* Flags.xcassets in Resources */, 0E6059CB27FCC5DE003F4063 /* Flags.xcassets in Resources */,
0EE562782B2EE3EC000C52F6 /* LaunchScreen.storyboard in Resources */,
0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */, 0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */,
0EDDEC7D28D0DC140017802E /* LaunchScreen.storyboard in Resources */,
0E6059CC27FCC5DE003F4063 /* Providers.xcassets in Resources */, 0E6059CC27FCC5DE003F4063 /* Providers.xcassets in Resources */,
0E6059CD27FCC5DE003F4063 /* Assets.xcassets in Resources */, 0E6059CD27FCC5DE003F4063 /* Assets.xcassets in Resources */,
0E9E5AEF27B44CF1008C95DA /* Localizable.strings in Resources */, 0E9E5AEF27B44CF1008C95DA /* Localizable.strings in Resources */,
@ -1376,6 +1434,7 @@
0E2DE71C27DCCFE80067B9E1 /* TunnelKit+Extensions.swift in Sources */, 0E2DE71C27DCCFE80067B9E1 /* TunnelKit+Extensions.swift in Sources */,
0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */, 0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */,
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */, 0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */,
0EED5B9D2B3700AB009D1E97 /* TunnelError.swift in Sources */,
0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */, 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */,
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */, 0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */,
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */, 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */,
@ -1411,6 +1470,7 @@
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */, 0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */,
0E5468002867AC9A00F74D1C /* MacUtils.swift in Sources */, 0E5468002867AC9A00F74D1C /* MacUtils.swift in Sources */,
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */, 0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */,
0E330F552B30946600930C7C /* ActiveProfileView+TV.swift in Sources */,
0E96D3052872010A005EFBCF /* DefaultLightVPNManager.swift in Sources */, 0E96D3052872010A005EFBCF /* DefaultLightVPNManager.swift in Sources */,
0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */, 0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */,
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Restricted.swift in Sources */, 0ED30DCF27EA1EF80057D8A3 /* PaywallView+Restricted.swift in Sources */,
@ -1421,6 +1481,7 @@
0E96D30228720067005EFBCF /* LightVPNManager.swift in Sources */, 0E96D30228720067005EFBCF /* LightVPNManager.swift in Sources */,
0ED89C1727DE0E05008B36D6 /* IntentEditView.swift in Sources */, 0ED89C1727DE0E05008B36D6 /* IntentEditView.swift in Sources */,
0E70589B28377DC40075D1D2 /* VPNStatusText.swift in Sources */, 0E70589B28377DC40075D1D2 /* VPNStatusText.swift in Sources */,
0EE79B2F2B2ED99500C1220C /* MainView+TV.swift in Sources */,
0E71ACE927C1055300F85C4B /* NetworkSettingsView.swift in Sources */, 0E71ACE927C1055300F85C4B /* NetworkSettingsView.swift in Sources */,
0EB34BCA27C6A70200B126DA /* OnDemandView.swift in Sources */, 0EB34BCA27C6A70200B126DA /* OnDemandView.swift in Sources */,
0E0838FA2877325A00A34EC0 /* LightProviderManager.swift in Sources */, 0E0838FA2877325A00A34EC0 /* LightProviderManager.swift in Sources */,
@ -1432,6 +1493,7 @@
0EB17EBA27D2560300D473B5 /* PassepartoutProviders+Extensions.swift in Sources */, 0EB17EBA27D2560300D473B5 /* PassepartoutProviders+Extensions.swift in Sources */,
0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */, 0E3B7FDA27E51A0200C66F13 /* ProfileView+Provider.swift in Sources */,
0E2E0B6F2B335A8E00E3204A /* AppPreference.swift in Sources */, 0E2E0B6F2B335A8E00E3204A /* AppPreference.swift in Sources */,
0E859B832B2EE08700F80D92 /* OrganizerView+TV.swift in Sources */,
0E5468062867AEC500F74D1C /* MacMenu.swift in Sources */, 0E5468062867AEC500F74D1C /* MacMenu.swift in Sources */,
0E71ACE327C0F2E400F85C4B /* Providers+L10n.swift in Sources */, 0E71ACE327C0F2E400F85C4B /* Providers+L10n.swift in Sources */,
0E2E0B752B335AAB00E3204A /* IntentsManager.swift in Sources */, 0E2E0B752B335AAB00E3204A /* IntentsManager.swift in Sources */,
@ -1457,6 +1519,7 @@
0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */, 0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */,
0EBC075527EBC83800208AD9 /* MailComposerView.swift in Sources */, 0EBC075527EBC83800208AD9 /* MailComposerView.swift in Sources */,
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */, 0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
0E330F572B30952300930C7C /* ProfilesList+TV.swift in Sources */,
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */, 0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */,
0E2E0B772B335AAB00E3204A /* UpgradeManager.swift in Sources */, 0E2E0B772B335AAB00E3204A /* UpgradeManager.swift in Sources */,
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */, 0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */,
@ -1471,6 +1534,7 @@
0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */, 0E34A2B927CAA96A00C73B67 /* OpenVPN+L10n.swift in Sources */,
0E2E0B782B335AAB00E3204A /* PersistenceManager.swift in Sources */, 0E2E0B782B335AAB00E3204A /* PersistenceManager.swift in Sources */,
0EF8C5A828213C510053CE89 /* OrganizerView+Profiles.swift in Sources */, 0EF8C5A828213C510053CE89 /* OrganizerView+Profiles.swift in Sources */,
0E330F532B30469700930C7C /* MockProfileRepository.swift in Sources */,
0E3CD483280DAE92007075C0 /* ProfileView+MainMenu.swift in Sources */, 0E3CD483280DAE92007075C0 /* ProfileView+MainMenu.swift in Sources */,
0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */, 0E71ACEB27C1060D00F85C4B /* EndpointView.swift in Sources */,
0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */, 0E293857285A73BC002A6E0E /* AppContext+Shared.swift in Sources */,
@ -1478,6 +1542,7 @@
0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */, 0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */,
0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */, 0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */,
0EF2212B27E667EA001D0BD7 /* AddProviderView+Name.swift in Sources */, 0EF2212B27E667EA001D0BD7 /* AddProviderView+Name.swift in Sources */,
0E1DC1BF2B3618EE008B755E /* ProfileView+TV.swift in Sources */,
0E065F112813269500062CAF /* WelcomeView.swift in Sources */, 0E065F112813269500062CAF /* WelcomeView.swift in Sources */,
0E2DE71F27DCD0290067B9E1 /* TunnelKit+L10n.swift in Sources */, 0E2DE71F27DCD0290067B9E1 /* TunnelKit+L10n.swift in Sources */,
0E49F6BF27D764AF00385834 /* EndpointAdvancedView.swift in Sources */, 0E49F6BF27D764AF00385834 /* EndpointAdvancedView.swift in Sources */,
@ -1500,9 +1565,11 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0EF6563F2B36BFCD00CEFC96 /* NEPacketTunnelProvider+Expiration.swift in Sources */,
0ED2B35B27D3C94F00FD8EA9 /* PacketTunnelProvider.swift in Sources */, 0ED2B35B27D3C94F00FD8EA9 /* PacketTunnelProvider.swift in Sources */,
0ED30DDD27EA35230057D8A3 /* Constants.swift in Sources */, 0ED30DDD27EA35230057D8A3 /* Constants.swift in Sources */,
0ED1A5FD2B2B98CC00A0EA90 /* Constants+Tunnel.swift in Sources */, 0ED1A5FD2B2B98CC00A0EA90 /* Constants+Tunnel.swift in Sources */,
0EF656432B36C01200CEFC96 /* TunnelError.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -1510,9 +1577,11 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
0EF6563E2B36BFCD00CEFC96 /* NEPacketTunnelProvider+Expiration.swift in Sources */,
0E9AA978259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */, 0E9AA978259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */,
0EB17EA727D226B400D473B5 /* Constants.swift in Sources */, 0EB17EA727D226B400D473B5 /* Constants.swift in Sources */,
0ED30DDB27EA351C0057D8A3 /* Constants+Tunnel.swift in Sources */, 0ED30DDB27EA351C0057D8A3 /* Constants+Tunnel.swift in Sources */,
0EF656422B36C01200CEFC96 /* TunnelError.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -1538,6 +1607,10 @@
}; };
0ECF71FC27B6DA6700CDB528 /* PBXTargetDependency */ = { 0ECF71FC27B6DA6700CDB528 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
platformFilters = (
ios,
maccatalyst,
);
target = 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */; target = 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */;
targetProxy = 0ECF71FB27B6DA6700CDB528 /* PBXContainerItemProxy */; targetProxy = 0ECF71FB27B6DA6700CDB528 /* PBXContainerItemProxy */;
}; };
@ -1551,6 +1624,14 @@
target = 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */; target = 0ECF71F327B6D9CD00CDB528 /* WireGuardGo */;
targetProxy = 0ED2B36A27D3CAB100FD8EA9 /* PBXContainerItemProxy */; targetProxy = 0ED2B36A27D3CAB100FD8EA9 /* PBXContainerItemProxy */;
}; };
0EE79B352B2EDB9C00C1220C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilters = (
tvos,
);
target = 0EE79B302B2EDB5D00C1220C /* WireGuardGoTV */;
targetProxy = 0EE79B342B2EDB9C00C1220C /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
@ -1730,7 +1811,8 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6"; TARGETED_DEVICE_FAMILY = "1,2,3";
TVOS_DEPLOYMENT_TARGET = 17.0;
}; };
name = Debug; name = Debug;
}; };
@ -1791,7 +1873,8 @@
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6"; TARGETED_DEVICE_FAMILY = "1,2,3";
TVOS_DEPLOYMENT_TARGET = 17.0;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;
@ -1801,6 +1884,7 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
"ASSETCATALOG_COMPILER_APPICON_NAME[sdk=appletvos*]" = TV;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Passepartout/App/App.entitlements; CODE_SIGN_ENTITLEMENTS = Passepartout/App/App.entitlements;
@ -1814,11 +1898,12 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID)"; PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID)";
PRODUCT_NAME = Passepartout; PRODUCT_NAME = Passepartout;
PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout"; PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout";
"PROVISIONING_PROFILE_SPECIFIER[sdk=appletvos*]" = "match Development com.algoritmico.ios.Passepartout tvos";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout catalyst"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout catalyst";
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = targeted; SWIFT_STRICT_CONCURRENCY = targeted;
TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
}; };
@ -1827,6 +1912,7 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
"ASSETCATALOG_COMPILER_APPICON_NAME[sdk=appletvos*]" = TV;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Passepartout/App/App.entitlements; CODE_SIGN_ENTITLEMENTS = Passepartout/App/App.entitlements;
@ -1840,10 +1926,11 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID)"; PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID)";
PRODUCT_NAME = Passepartout; PRODUCT_NAME = Passepartout;
PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout"; PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout";
"PROVISIONING_PROFILE_SPECIFIER[sdk=appletvos*]" = "match Development com.algoritmico.ios.Passepartout tvos";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout catalyst"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout catalyst";
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
SWIFT_STRICT_CONCURRENCY = targeted; SWIFT_STRICT_CONCURRENCY = targeted;
TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;
}; };
@ -1959,8 +2046,10 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).WireGuardTunnel"; PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).WireGuardTunnel";
PRODUCT_NAME = PassepartoutWireGuardTunnel; PRODUCT_NAME = PassepartoutWireGuardTunnel;
PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel"; PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel";
"PROVISIONING_PROFILE_SPECIFIER[sdk=appletvos*]" = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel tvos";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel catalyst"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel catalyst";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
}; };
name = Debug; name = Debug;
@ -1979,8 +2068,10 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).WireGuardTunnel"; PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).WireGuardTunnel";
PRODUCT_NAME = PassepartoutWireGuardTunnel; PRODUCT_NAME = PassepartoutWireGuardTunnel;
PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel"; PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel";
"PROVISIONING_PROFILE_SPECIFIER[sdk=appletvos*]" = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel tvos";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel catalyst"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.WireGuardTunnel catalyst";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
}; };
name = Release; name = Release;
@ -1999,8 +2090,10 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).OpenVPNTunnel"; PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).OpenVPNTunnel";
PRODUCT_NAME = PassepartoutOpenVPNTunnel; PRODUCT_NAME = PassepartoutOpenVPNTunnel;
PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel"; PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel";
"PROVISIONING_PROFILE_SPECIFIER[sdk=appletvos*]" = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel tvos";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel catalyst"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel catalyst";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
}; };
name = Debug; name = Debug;
@ -2019,12 +2112,51 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).OpenVPNTunnel"; PRODUCT_BUNDLE_IDENTIFIER = "$(CFG_APP_ID).OpenVPNTunnel";
PRODUCT_NAME = PassepartoutOpenVPNTunnel; PRODUCT_NAME = PassepartoutOpenVPNTunnel;
PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel"; PROVISIONING_PROFILE_SPECIFIER = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel";
"PROVISIONING_PROFILE_SPECIFIER[sdk=appletvos*]" = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel tvos";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel catalyst"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "match Development com.algoritmico.ios.Passepartout.OpenVPNTunnel catalyst";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
}; };
name = Release; name = Release;
}; };
0EE79B322B2EDB5D00C1220C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUGGING_SYMBOLS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = DTDYD63ZX9;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
};
name = Debug;
};
0EE79B332B2EDB5D00C1220C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = DTDYD63ZX9;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CFLAGS = "";
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
};
name = Release;
};
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
@ -2091,6 +2223,15 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
0EE79B312B2EDB5D00C1220C /* Build configuration list for PBXLegacyTarget "WireGuardGoTV" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0EE79B322B2EDB5D00C1220C /* Debug */,
0EE79B332B2EDB5D00C1220C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */

View File

@ -1,70 +1,67 @@
{ {
"object": { "pins" : [
"pins": [ {
{ "identity" : "dtfoundation",
"package": "DTFoundation", "kind" : "remoteSourceControl",
"repositoryURL": "https://github.com/Cocoanetics/DTFoundation.git", "location" : "https://github.com/Cocoanetics/DTFoundation.git",
"state": { "state" : {
"branch": null, "revision" : "76062513434421cb6c8a1ae1d4f8368a7ebc2da3",
"revision": "76062513434421cb6c8a1ae1d4f8368a7ebc2da3", "version" : "1.7.18"
"version": "1.7.18"
}
},
{
"package": "GenericJSON",
"repositoryURL": "https://github.com/zoul/generic-json-swift",
"state": {
"branch": null,
"revision": "0a06575f4038b504e78ac330913d920f1630f510",
"version": "2.0.2"
}
},
{
"package": "Kvitto",
"repositoryURL": "https://github.com/Cocoanetics/Kvitto",
"state": {
"branch": null,
"revision": "88888674d772ddcf19671159ed0022cb0bc37be2",
"version": "1.0.6"
}
},
{
"package": "openssl-apple",
"repositoryURL": "https://github.com/passepartoutvpn/openssl-apple",
"state": {
"branch": null,
"revision": "026702febcaebcbf9ea68f2fa66b017eba998cdf",
"version": "3.2.105"
}
},
{
"package": "SwiftyBeaver",
"repositoryURL": "https://github.com/SwiftyBeaver/SwiftyBeaver",
"state": {
"branch": null,
"revision": "12b5acf96d98f91d50de447369bd18df74600f1a",
"version": "1.9.6"
}
},
{
"package": "TunnelKit",
"repositoryURL": "https://github.com/passepartoutvpn/tunnelkit",
"state": {
"branch": null,
"revision": "bda84bf569792fbb702d0173de3c9c58768f9153",
"version": null
}
},
{
"package": "WireGuardKit",
"repositoryURL": "https://github.com/passepartoutvpn/wireguard-apple",
"state": {
"branch": null,
"revision": "73d9152fa0cb661db0348a1ac11dbbf998422a50",
"version": "1.0.17"
}
} }
] },
}, {
"version": 1 "identity" : "generic-json-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zoul/generic-json-swift",
"state" : {
"revision" : "0a06575f4038b504e78ac330913d920f1630f510",
"version" : "2.0.2"
}
},
{
"identity" : "kvitto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Cocoanetics/Kvitto",
"state" : {
"revision" : "88888674d772ddcf19671159ed0022cb0bc37be2",
"version" : "1.0.6"
}
},
{
"identity" : "openssl-apple",
"kind" : "remoteSourceControl",
"location" : "https://github.com/passepartoutvpn/openssl-apple",
"state" : {
"revision" : "026702febcaebcbf9ea68f2fa66b017eba998cdf",
"version" : "3.2.105"
}
},
{
"identity" : "swiftybeaver",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SwiftyBeaver/SwiftyBeaver",
"state" : {
"revision" : "12b5acf96d98f91d50de447369bd18df74600f1a",
"version" : "1.9.6"
}
},
{
"identity" : "tunnelkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/passepartoutvpn/tunnelkit",
"state" : {
"revision" : "708c785e615f5715ce08386c772c92fb45730a3a"
}
},
{
"identity" : "wireguard-apple",
"kind" : "remoteSourceControl",
"location" : "https://github.com/passepartoutvpn/wireguard-apple",
"state" : {
"branch" : "develop",
"revision" : "b79f0f150356d8200a64922ecf041dd020140aa0"
}
}
],
"version" : 2
} }

View File

@ -127,7 +127,7 @@
<EnvironmentVariable <EnvironmentVariable
key = "APP_TYPE" key = "APP_TYPE"
value = "2" value = "2"
isEnabled = "YES"> isEnabled = "NO">
</EnvironmentVariable> </EnvironmentVariable>
<EnvironmentVariable <EnvironmentVariable
key = "LOG_LEVEL" key = "LOG_LEVEL"

View File

@ -7,6 +7,7 @@
<key>com.apple.developer.icloud-container-identifiers</key> <key>com.apple.developer.icloud-container-identifiers</key>
<array> <array>
<string>iCloud.com.algoritmico.Passepartout</string> <string>iCloud.com.algoritmico.Passepartout</string>
<string>iCloud.com.algoritmico.Passepartout.Shared</string>
</array> </array>
<key>com.apple.developer.icloud-services</key> <key>com.apple.developer.icloud-services</key>
<array> <array>

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"idiom" : "tv"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,14 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.imagestacklayer"
},
{
"filename" : "Back.imagestacklayer"
}
]
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"idiom" : "tv"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1,18 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"idiom" : "tv",
"scale" : "1x"
},
{
"filename" : "AppIcon@2x.png",
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,14 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.imagestacklayer"
},
{
"filename" : "Back.imagestacklayer"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,18 @@
{
"images" : [
{
"filename" : "AppIcon.png",
"idiom" : "tv",
"scale" : "1x"
},
{
"filename" : "AppIcon@2x.png",
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,32 @@
{
"assets" : [
{
"filename" : "App Icon - App Store.imagestack",
"idiom" : "tv",
"role" : "primary-app-icon",
"size" : "1280x768"
},
{
"filename" : "App Icon.imagestack",
"idiom" : "tv",
"role" : "primary-app-icon",
"size" : "400x240"
},
{
"filename" : "Top Shelf Image Wide.imageset",
"idiom" : "tv",
"role" : "top-shelf-image-wide",
"size" : "2320x720"
},
{
"filename" : "Top Shelf Image.imageset",
"idiom" : "tv",
"role" : "top-shelf-image",
"size" : "1920x720"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,18 @@
{
"images" : [
{
"filename" : "TopShelf.png",
"idiom" : "tv",
"scale" : "1x"
},
{
"filename" : "TopShelf@2x.png",
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -0,0 +1,18 @@
{
"images" : [
{
"filename" : "TopShelf.png",
"idiom" : "tv",
"scale" : "1x"
},
{
"filename" : "TopShelf@2x.png",
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -44,6 +44,8 @@ extension Constants {
enum CloudKit { enum CloudKit {
static let containerId: String = bundleConfig("cloudkit_id") static let containerId: String = bundleConfig("cloudkit_id")
static let sharedContainerId: String = bundleConfig("cloudkit_shared_id")
static let coreDataZone = "com.apple.coredata.cloudkit.zone" static let coreDataZone = "com.apple.coredata.cloudkit.zone"
} }
@ -84,6 +86,8 @@ extension Constants {
return [] return []
} }
#endif #endif
static let tvLimitedMinutes = 10
} }
} }
@ -131,6 +135,8 @@ extension Constants {
enum Persistence { enum Persistence {
static let profilesContainerName = "Profiles" static let profilesContainerName = "Profiles"
static let sharedProfilesContainerName = "SharedProfiles"
static let providersContainerName = "Providers" static let providersContainerName = "Providers"
} }

View File

@ -23,7 +23,9 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import LocalAuthentication import LocalAuthentication
#endif
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -33,20 +35,28 @@ extension View {
} }
var themeIsiPadPortrait: Bool { var themeIsiPadPortrait: Bool {
#if !os(tvOS)
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
false false
#else #else
let device: UIDevice = .current let device: UIDevice = .current
return device.userInterfaceIdiom == .pad && device.orientation.isPortrait return device.userInterfaceIdiom == .pad && device.orientation.isPortrait
#endif #endif
#else
false
#endif
} }
var themeIsiPadMultitasking: Bool { var themeIsiPadMultitasking: Bool {
#if !os(tvOS)
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
false false
#else #else
UIDevice.current.userInterfaceIdiom == .pad UIDevice.current.userInterfaceIdiom == .pad
#endif #endif
#else
false
#endif
} }
} }
@ -55,6 +65,7 @@ extension View {
extension View { extension View {
func themeGlobal() -> some View { func themeGlobal() -> some View {
themeNavigationViewStyle() themeNavigationViewStyle()
#if !os(tvOS)
#if !targetEnvironment(macCatalyst) #if !targetEnvironment(macCatalyst)
.themeLockScreen() .themeLockScreen()
#endif #endif
@ -62,22 +73,39 @@ extension View {
.listStyle(themeListStyleValue()) .listStyle(themeListStyleValue())
.toggleStyle(themeToggleStyleValue()) .toggleStyle(themeToggleStyleValue())
.menuStyle(.borderlessButton) .menuStyle(.borderlessButton)
#endif
.withErrorHandler() .withErrorHandler()
} }
#if os(tvOS)
func themeTV() -> some View {
GeometryReader { geo in
self
.padding(.horizontal, 0.25 * geo.size.width)
.scrollClipDisabled()
}
}
#endif
func themePrimaryView() -> some View { func themePrimaryView() -> some View {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
navigationBarTitleDisplayMode(.inline) navigationBarTitleDisplayMode(.inline)
.themeSidebarListStyle() .themeSidebarListStyle()
#else #elseif !os(tvOS)
navigationBarTitleDisplayMode(.large) navigationBarTitleDisplayMode(.large)
.navigationTitle(Unlocalized.appName) .navigationTitle(Unlocalized.appName)
.themeSidebarListStyle() .themeSidebarListStyle()
#else
self
#endif #endif
} }
func themeSecondaryView() -> some View { func themeSecondaryView() -> some View {
#if !os(tvOS)
navigationBarTitleDisplayMode(.inline) navigationBarTitleDisplayMode(.inline)
#else
self
#endif
} }
@ViewBuilder @ViewBuilder
@ -93,6 +121,7 @@ extension View {
@ViewBuilder @ViewBuilder
private func themeSidebarListStyle() -> some View { private func themeSidebarListStyle() -> some View {
#if !os(tvOS)
switch themeIdiom { switch themeIdiom {
case .phone: case .phone:
listStyle(.insetGrouped) listStyle(.insetGrouped)
@ -100,6 +129,9 @@ extension View {
default: default:
listStyle(.sidebar) listStyle(.sidebar)
} }
#else
self
#endif
} }
@ViewBuilder @ViewBuilder
@ -108,11 +140,19 @@ extension View {
} }
private func themeListStyleValue() -> some ListStyle { private func themeListStyleValue() -> some ListStyle {
#if !os(tvOS)
.insetGrouped .insetGrouped
#else
PlainListStyle()
#endif
} }
private func themeToggleStyleValue() -> some ToggleStyle { private func themeToggleStyleValue() -> some ToggleStyle {
#if !os(tvOS)
.switch .switch
#else
DefaultToggleStyle()
#endif
} }
} }
@ -179,6 +219,10 @@ extension View {
"eye" "eye"
} }
var themeAppleTVImage: String {
"tv"
}
// MARK: Organizer // MARK: Organizer
func themeAssetsProviderImage(_ providerName: ProviderName) -> String { func themeAssetsProviderImage(_ providerName: ProviderName) -> String {
@ -387,6 +431,7 @@ extension View {
// MARK: Shortcuts // MARK: Shortcuts
#if !os(tvOS)
extension ShortcutType { extension ShortcutType {
var themeImageName: String { var themeImageName: String {
switch self { switch self {
@ -401,6 +446,7 @@ extension ShortcutType {
} }
} }
} }
#endif
// MARK: Animations // MARK: Animations
@ -497,6 +543,7 @@ extension View {
// MARK: Lock screen // MARK: Lock screen
#if !os(tvOS)
extension View { extension View {
func themeLockScreen() -> some View { func themeLockScreen() -> some View {
@AppStorage(AppPreference.locksInBackground.key) var locksInBackground = false @AppStorage(AppPreference.locksInBackground.key) var locksInBackground = false
@ -528,6 +575,7 @@ extension View {
} }
} }
} }
#endif
// MARK: Validation // MARK: Validation

View File

@ -66,6 +66,7 @@ final class AppContext {
persistenceManager = PersistenceManager( persistenceManager = PersistenceManager(
store: store, store: store,
ckContainerId: Constants.CloudKit.containerId, ckContainerId: Constants.CloudKit.containerId,
ckSharedContainerId: Constants.CloudKit.sharedContainerId,
ckCoreDataZone: Constants.CloudKit.coreDataZone ckCoreDataZone: Constants.CloudKit.coreDataZone
) )
@ -94,6 +95,10 @@ final class AppContext {
private extension AppContext { private extension AppContext {
func configureObjects() { func configureObjects() {
coreContext.profileManager.willSaveSharedProfile = { [unowned self] in
willSaveSharedProfile(withNewProfile: $0, existingProfile: $1)
}
coreContext.vpnManager.isOnDemandRulesSupported = { coreContext.vpnManager.isOnDemandRulesSupported = {
self.isEligibleForOnDemandRules() self.isEligibleForOnDemandRules()
} }
@ -101,6 +106,13 @@ private extension AppContext {
self.isEligibleForNetworkSettings() self.isEligibleForNetworkSettings()
} }
coreContext.vpnManager.userData = {
if let expirationDate = $0.connectionExpirationDate {
return [Constants.Tunnel.expirationTimeIntervalKey: expirationDate.timeIntervalSinceReferenceDate]
}
return nil
}
coreContext.vpnManager.currentState.$vpnStatus coreContext.vpnManager.currentState.$vpnStatus
.removeDuplicates() .removeDuplicates()
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
@ -138,4 +150,41 @@ private extension AppContext {
} }
return true return true
} }
// eligibility: expire restricted TV profiles after N minutes
func willSaveSharedProfile(withNewProfile newProfile: Profile, existingProfile: Profile?) -> Profile {
if let existingProfile {
assert(newProfile.id == existingProfile.id)
}
guard productManager.isEligible(forFeature: .appleTV) else {
var restricted = newProfile
let remainingMinutes: Int
let expirationDate: Date
// retain current expiration period if any
if let currentExpirationDate = existingProfile?.connectionExpirationDate {
remainingMinutes = Int(currentExpirationDate.timeIntervalSinceNow / 60.0)
expirationDate = currentExpirationDate
}
// otherwise, expire in N minutes from now
else {
remainingMinutes = Constants.InApp.tvLimitedMinutes
expirationDate = Date()
.addingTimeInterval(TimeInterval(remainingMinutes) * 60.0)
restricted.connectionExpirationDate = expirationDate
}
if remainingMinutes > 0 {
pp_log.warning("\(newProfile.logDescription): TV connection expires in \(remainingMinutes) minutes (at \(expirationDate))")
} else {
pp_log.warning("\(newProfile.logDescription): TV connection expired at \(expirationDate)")
}
return restricted
}
return newProfile
}
} }

View File

@ -44,9 +44,14 @@ final class CoreContext {
init(persistenceManager: PersistenceManager) { init(persistenceManager: PersistenceManager) {
store = persistenceManager.store store = persistenceManager.store
#if !os(tvOS)
let vpnPersistence = persistenceManager.loadVPNPersistence( let vpnPersistence = persistenceManager.loadVPNPersistence(
withName: Constants.Persistence.profilesContainerName withName: Constants.Persistence.profilesContainerName
) )
#endif
let sharedVPNPersistence = persistenceManager.loadSharedVPNPersistence(
withName: Constants.Persistence.sharedProfilesContainerName
)
let providersPersistence = persistenceManager.loadProvidersPersistence( let providersPersistence = persistenceManager.loadProvidersPersistence(
withName: Constants.Persistence.providersContainerName withName: Constants.Persistence.providersContainerName
) )
@ -68,10 +73,20 @@ final class CoreContext {
remoteProvidersStrategy: remoteProvidersStrategy remoteProvidersStrategy: remoteProvidersStrategy
) )
let tvProfileRepository = sharedVPNPersistence.profileRepository()
#if !os(tvOS)
let profileRepository = vpnPersistence.profileRepository()
let sharedProfileRepository = tvProfileRepository
#else
let profileRepository = tvProfileRepository
let sharedProfileRepository: ProfileRepository? = nil
#endif
profileManager = ProfileManager( profileManager = ProfileManager(
store: store, store: store,
providerManager: providerManager, providerManager: providerManager,
profileRepository: vpnPersistence.profileRepository(), profileRepository: profileRepository,
sharedProfileRepository: sharedProfileRepository,
keychain: KeychainSecretRepository(appGroup: Constants.App.appGroupId), keychain: KeychainSecretRepository(appGroup: Constants.App.appGroupId),
keychainEntry: Unlocalized.Keychain.passwordEntry, keychainEntry: Unlocalized.Keychain.passwordEntry,
keychainLabel: Unlocalized.Keychain.passwordLabel keychainLabel: Unlocalized.Keychain.passwordLabel

View File

@ -0,0 +1,60 @@
//
// MockProfileRepository.swift
// Passepartout
//
// Created by Davide De Rosa on 12/18/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Combine
import Foundation
import PassepartoutLibrary
final class MockProfileRepository: ProfileRepository, ObservableObject {
@Published var profiles: [UUID: Profile]
init() {
profiles = [:]
}
func allProfiles() -> [UUID: Profile] {
profiles
}
func profile(withId id: UUID) -> Profile? {
profiles[id]
}
func saveProfiles(_ profiles: [Profile]) throws {
profiles.forEach {
self.profiles[$0.id] = $0
}
}
func removeProfiles(withIds ids: [UUID]) {
ids.forEach {
profiles.removeValue(forKey: $0)
}
}
func willUpdateProfiles() -> AnyPublisher<[UUID: Profile], Never> {
$profiles.eraseToAnyPublisher()
}
}

View File

@ -33,6 +33,8 @@ enum AppError: Error {
case vpn(Passepartout.VPNError) case vpn(Passepartout.VPNError)
case tunnel(TunnelError)
case generic(Error) case generic(Error)
init(_ error: Error) { init(_ error: Error) {

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import Foundation import Foundation
import Intents import Intents
import PassepartoutLibrary import PassepartoutLibrary
@ -157,3 +158,4 @@ extension IntentDispatcher {
} }
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import Foundation import Foundation
import Intents import Intents
import PassepartoutLibrary import PassepartoutLibrary
@ -157,3 +158,4 @@ private extension INInteraction {
} }
} }
} }
#endif

View File

@ -24,7 +24,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(CFG_APP_ID)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
@ -80,7 +80,7 @@
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
<array> <array>
<string>armv7</string> <string>arm64</string>
</array> </array>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
@ -99,10 +99,12 @@
<dict> <dict>
<key>appstore_id</key> <key>appstore_id</key>
<string>$(CFG_APPSTORE_ID)</string> <string>$(CFG_APPSTORE_ID)</string>
<key>group_id</key>
<string>group.$(CFG_GROUP_ID)</string>
<key>cloudkit_id</key> <key>cloudkit_id</key>
<string>iCloud.$(CFG_GROUP_ID)</string> <string>iCloud.$(CFG_GROUP_ID)</string>
<key>cloudkit_shared_id</key>
<string>iCloud.$(CFG_GROUP_ID).Shared</string>
<key>group_id</key>
<string>group.$(CFG_GROUP_ID)</string>
</dict> </dict>
</dict> </dict>
</plist> </plist>

View File

@ -77,6 +77,12 @@ extension AppError: LocalizedError {
return nil return nil
} }
case .tunnel(let tunnelError):
switch tunnelError {
case .expired:
return V.tunnelExpired
}
case .generic(let error): case .generic(let error):
return error.localizedDescription return error.localizedDescription
} }

View File

@ -259,6 +259,8 @@ enum Unlocalized {
static let iCloud = "iCloud" static let iCloud = "iCloud"
static let appleTV = "Apple TV"
static let totp = "TOTP" static let totp = "TOTP"
} }
} }

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import Combine import Combine
import Foundation import Foundation
@preconcurrency import Intents @preconcurrency import Intents
@ -106,3 +107,4 @@ extension IntentsManager: INUIEditVoiceShortcutViewControllerDelegate {
shouldDismissIntentView.send() shouldDismissIntentView.send()
} }
} }
#endif

View File

@ -35,10 +35,14 @@ final class PersistenceManager: ObservableObject {
private let ckContainerId: String private let ckContainerId: String
private let ckSharedContainerId: String
private let ckCoreDataZone: String private let ckCoreDataZone: String
private var vpnPersistence: VPNPersistence? private var vpnPersistence: VPNPersistence?
private var sharedVPNPersistence: VPNPersistence?
private var providersPersistence: ProvidersPersistence? private var providersPersistence: ProvidersPersistence?
private(set) var isCloudSyncingEnabled: Bool { private(set) var isCloudSyncingEnabled: Bool {
@ -52,9 +56,13 @@ final class PersistenceManager: ObservableObject {
let didChangePersistence = PassthroughSubject<Void, Never>() let didChangePersistence = PassthroughSubject<Void, Never>()
init(store: KeyValueStore, ckContainerId: String, ckCoreDataZone: String) { init(store: KeyValueStore,
ckContainerId: String,
ckSharedContainerId: String,
ckCoreDataZone: String) {
self.store = store self.store = store
self.ckContainerId = ckContainerId self.ckContainerId = ckContainerId
self.ckSharedContainerId = ckSharedContainerId
self.ckCoreDataZone = ckCoreDataZone self.ckCoreDataZone = ckCoreDataZone
isCloudSyncingEnabled = store.canEnableCloudSyncing isCloudSyncingEnabled = store.canEnableCloudSyncing
@ -65,11 +73,23 @@ final class PersistenceManager: ObservableObject {
} }
func loadVPNPersistence(withName containerName: String) -> VPNPersistence { func loadVPNPersistence(withName containerName: String) -> VPNPersistence {
let persistence = VPNPersistence(withName: containerName, cloudKit: isCloudSyncingEnabled, author: persistenceAuthor) let persistence = VPNPersistence(withName: containerName,
cloudKit: isCloudSyncingEnabled,
cloudKitIdentifier: nil,
author: persistenceAuthor)
vpnPersistence = persistence vpnPersistence = persistence
return persistence return persistence
} }
func loadSharedVPNPersistence(withName containerName: String) -> VPNPersistence {
let persistence = VPNPersistence(withName: containerName,
cloudKit: true,
cloudKitIdentifier: ckSharedContainerId,
author: persistenceAuthor)
sharedVPNPersistence = persistence
return persistence
}
func loadProvidersPersistence(withName containerName: String) -> ProvidersPersistence { func loadProvidersPersistence(withName containerName: String) -> ProvidersPersistence {
let persistence = ProvidersPersistence(withName: containerName, cloudKit: false, author: persistenceAuthor) let persistence = ProvidersPersistence(withName: containerName, cloudKit: false, author: persistenceAuthor)
providersPersistence = persistence providersPersistence = persistence
@ -86,6 +106,10 @@ extension PersistenceManager {
fromContainerWithId: ckContainerId, fromContainerWithId: ckContainerId,
zoneId: .init(zoneName: ckCoreDataZone) zoneId: .init(zoneName: ckCoreDataZone)
) )
await Self.eraseCloudKitStore(
fromContainerWithId: ckSharedContainerId,
zoneId: .init(zoneName: ckCoreDataZone)
)
isErasingCloudKitStore = false isErasingCloudKitStore = false
} }
@ -109,7 +133,11 @@ private extension KeyValueStore {
} }
private var isCloudKitSupported: Bool { private var isCloudKitSupported: Bool {
#if !os(tvOS)
cloudKitToken != nil cloudKitToken != nil
#else
true
#endif
} }
var canEnableCloudSyncing: Bool { var canEnableCloudSyncing: Bool {

View File

@ -33,6 +33,7 @@ struct PassepartoutApp: App {
@SceneBuilder var body: some Scene { @SceneBuilder var body: some Scene {
WindowGroup { WindowGroup {
MainView() MainView()
#if !os(tvOS)
.withoutTitleBar() .withoutTitleBar()
.onIntentActivity(IntentDispatcher.connectVPN) .onIntentActivity(IntentDispatcher.connectVPN)
.onIntentActivity(IntentDispatcher.disableVPN) .onIntentActivity(IntentDispatcher.disableVPN)
@ -42,6 +43,7 @@ struct PassepartoutApp: App {
.onIntentActivity(IntentDispatcher.trustCurrentNetwork) .onIntentActivity(IntentDispatcher.trustCurrentNetwork)
.onIntentActivity(IntentDispatcher.untrustCellularNetwork) .onIntentActivity(IntentDispatcher.untrustCellularNetwork)
.onIntentActivity(IntentDispatcher.untrustCurrentNetwork) .onIntentActivity(IntentDispatcher.untrustCurrentNetwork)
#endif
} }
} }
} }

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import SwiftUI import SwiftUI
import UIKit import UIKit
@ -38,3 +39,4 @@ struct ActivityView: UIViewControllerRepresentable {
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) { func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) {
} }
} }
#endif

View File

@ -180,7 +180,9 @@ private extension GenericCreditsView {
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding() .padding()
}.navigationTitle(content.name) }.navigationTitle(content.name)
#if !os(tvOS)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
#endif
} }
} }

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import Intents import Intents
import IntentsUI import IntentsUI
import SwiftUI import SwiftUI
@ -41,3 +42,4 @@ struct IntentAddView: UIViewControllerRepresentable {
func updateUIViewController(_ uiViewController: INUIAddVoiceShortcutViewController, context: UIViewControllerRepresentableContext<IntentAddView>) { func updateUIViewController(_ uiViewController: INUIAddVoiceShortcutViewController, context: UIViewControllerRepresentableContext<IntentAddView>) {
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import Intents import Intents
import IntentsUI import IntentsUI
import SwiftUI import SwiftUI
@ -41,3 +42,4 @@ struct IntentEditView: UIViewControllerRepresentable {
func updateUIViewController(_ uiViewController: INUIEditVoiceShortcutViewController, context: UIViewControllerRepresentableContext<IntentEditView>) { func updateUIViewController(_ uiViewController: INUIEditVoiceShortcutViewController, context: UIViewControllerRepresentableContext<IntentEditView>) {
} }
} }
#endif

View File

@ -29,9 +29,15 @@ struct LongContentView: View {
@Binding var content: String @Binding var content: String
var body: some View { var body: some View {
#if !os(tvOS)
TextEditor(text: $content) TextEditor(text: $content)
.font(.system(.body, design: .monospaced)) .font(.system(.body, design: .monospaced))
// .padding() // .padding()
#else
Text(content)
.font(.system(.body, design: .monospaced))
// .padding()
#endif
// TODO: layout, add padding an inset, let content extend beyond safe areas // TODO: layout, add padding an inset, let content extend beyond safe areas
} }

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import MessageUI import MessageUI
import SwiftUI import SwiftUI
@ -82,3 +83,4 @@ extension MailComposerView {
} }
} }
} }
#endif

View File

@ -75,17 +75,21 @@ public final class Reviewer: ObservableObject {
defaults.removeObject(forKey: Keys.eventCount) defaults.removeObject(forKey: Keys.eventCount)
defaults.set(currentVersion, forKey: Keys.lastVersion) defaults.set(currentVersion, forKey: Keys.lastVersion)
#if !os(tvOS)
requestReview() requestReview()
#endif
return true return true
} }
// may or may not appear // may or may not appear
#if !os(tvOS)
private func requestReview() { private func requestReview() {
guard let scene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene else { guard let scene = UIApplication.shared.connectedScenes.first(where: { $0 is UIWindowScene }) as? UIWindowScene else {
return return
} }
SKStoreReviewController.requestReview(in: scene) SKStoreReviewController.requestReview(in: scene)
} }
#endif
public static func urlForReview(withAppId appId: String) -> URL { public static func urlForReview(withAppId appId: String) -> URL {
URL(string: "https://apps.apple.com/app/id\(appId)?action=write-review")! URL(string: "https://apps.apple.com/app/id\(appId)?action=write-review")!

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import Foundation import Foundation
import Intents import Intents
@ -53,3 +54,4 @@ struct Shortcut: Identifiable, Hashable, Comparable {
native.invocationPhrase.lowercased() native.invocationPhrase.lowercased()
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -89,3 +90,4 @@ private extension ShortcutType {
) )
} }
} }
#endif

View File

@ -33,10 +33,14 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
MacBundle.shared.utils.sendAppToBackground() MacBundle.shared.utils.sendAppToBackground()
#endif #endif
#if !os(tvOS)
rebuildShortcutItems() rebuildShortcutItems()
#endif
} }
#if !os(tvOS)
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
handleShortcutItem(shortcutItem) handleShortcutItem(shortcutItem)
} }
#endif
} }

View File

@ -67,6 +67,7 @@ struct DebugLogView: View {
refreshLog(scrollingToLatestWith: scrollProxy) refreshLog(scrollingToLatestWith: scrollProxy)
} }
} }
#if !os(tvOS)
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
.toolbar { .toolbar {
Button(action: copyDebugLog) { Button(action: copyDebugLog) {
@ -82,7 +83,9 @@ struct DebugLogView: View {
} else { } else {
ProgressView() ProgressView()
} }
}.sheet(isPresented: $isSharing, content: sharingActivityView) }
.sheet(isPresented: $isSharing, content: sharingActivityView)
#endif
#endif #endif
.edgesIgnoringSafeArea([.leading, .trailing]) .edgesIgnoringSafeArea([.leading, .trailing])
.onReceive(timer, perform: refreshLog) .onReceive(timer, perform: refreshLog)
@ -104,9 +107,11 @@ private extension DebugLogView {
// TODO: layout, a slight padding would be nice, but it glitches on first touch // TODO: layout, a slight padding would be nice, but it glitches on first touch
} }
#if !os(tvOS)
func sharingActivityView() -> some View { func sharingActivityView() -> some View {
ActivityView(activityItems: sharingItems) ActivityView(activityItems: sharingItems)
} }
#endif
var sharingItems: [Any] { var sharingItems: [Any] {
let raw = logLines.joined(separator: "\n") let raw = logLines.joined(separator: "\n")
@ -140,6 +145,7 @@ private extension DebugLogView {
} }
} }
#if !os(tvOS)
func shareDebugLog() { func shareDebugLog() {
guard !logLines.isEmpty else { guard !logLines.isEmpty else {
assertionFailure("Log is empty, why could it share?") assertionFailure("Log is empty, why could it share?")
@ -159,6 +165,7 @@ private extension DebugLogView {
Utils.copyToPasteboard(content) Utils.copyToPasteboard(content)
} }
#endif
func scrollToLatestUpdate(_ proxy: ScrollViewProxy) { func scrollToLatestUpdate(_ proxy: ScrollViewProxy) {
proxy.maybeScrollTo(logLines.count - 1, anchor: .bottomLeading) proxy.maybeScrollTo(logLines.count - 1, anchor: .bottomLeading)

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
import TunnelKitOpenVPN import TunnelKitOpenVPN
@ -200,3 +201,4 @@ private extension DiagnosticsView.OpenVPNView {
} }
} }
} }
#endif

View File

@ -32,7 +32,9 @@ struct DiagnosticsView: View {
var body: some View { var body: some View {
Group { Group {
if !profile.isPlaceholder { if !profile.isPlaceholder {
#if !os(tvOS)
vpnView vpnView
#endif
} else { } else {
genericView genericView
} }
@ -41,6 +43,8 @@ struct DiagnosticsView: View {
} }
private extension DiagnosticsView { private extension DiagnosticsView {
#if !os(tvOS)
var vpnView: some View { var vpnView: some View {
Group { Group {
switch profile.currentVPNProtocol { switch profile.currentVPNProtocol {
@ -56,6 +60,7 @@ private extension DiagnosticsView {
} }
} }
} }
#endif
var genericView: some View { var genericView: some View {
List { List {

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
import TunnelKitCore import TunnelKitCore
@ -111,3 +112,4 @@ private extension EndpointView.AddView {
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
import TunnelKitOpenVPN import TunnelKitOpenVPN
@ -394,3 +395,4 @@ private extension Profile {
} }
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
import TunnelKitWireGuard import TunnelKitWireGuard
@ -155,3 +156,4 @@ private extension ObservableProfile {
} }
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -48,3 +49,4 @@ struct EndpointView: View {
} }
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import SwiftUI import SwiftUI
struct MainView: View { struct MainView: View {
@ -33,3 +34,4 @@ struct MainView: View {
}.themeGlobal() }.themeGlobal()
} }
} }
#endif

View File

@ -137,8 +137,10 @@ private extension OnDemandView {
guard isEligibleForSiri else { guard isEligibleForSiri else {
return return
} }
#if !os(tvOS)
IntentDispatcher.donateTrustCellularNetwork() IntentDispatcher.donateTrustCellularNetwork()
IntentDispatcher.donateUntrustCellularNetwork() IntentDispatcher.donateUntrustCellularNetwork()
#endif
} }
// eligibility: donate intents if eligible for Siri // eligibility: donate intents if eligible for Siri
@ -146,7 +148,9 @@ private extension OnDemandView {
guard isEligibleForSiri else { guard isEligibleForSiri else {
return return
} }
#if !os(tvOS)
IntentDispatcher.donateTrustCurrentNetwork() IntentDispatcher.donateTrustCurrentNetwork()
IntentDispatcher.donateUntrustCurrentNetwork() IntentDispatcher.donateUntrustCurrentNetwork()
#endif
} }
} }

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -56,6 +57,7 @@ extension OrganizerView {
VPNToggle( VPNToggle(
profile: profile, profile: profile,
interactiveProfile: interactiveProfile, interactiveProfile: interactiveProfile,
title: L10n.Global.Strings.enabled,
rateLimit: Constants.RateLimit.vpnToggle rateLimit: Constants.RateLimit.vpnToggle
).labelsHidden() ).labelsHidden()
}.padding([.top, .bottom], 10) }.padding([.top, .bottom], 10)
@ -81,3 +83,4 @@ private extension OrganizerView.ProfileRow {
} }
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -187,3 +188,4 @@ private extension OrganizerView.ProfilesList {
} }
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -84,3 +85,4 @@ private extension OrganizerView.SceneView {
} }
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -89,12 +90,14 @@ struct OrganizerView: View {
presenting: alertType, presenting: alertType,
actions: alertActions, actions: alertActions,
message: alertMessage message: alertMessage
).fileImporter( )
.fileImporter(
isPresented: $isHostFileImporterPresented, isPresented: $isHostFileImporterPresented,
allowedContentTypes: hostFileTypes, allowedContentTypes: hostFileTypes,
allowsMultipleSelection: false, allowsMultipleSelection: false,
onCompletion: onHostFileImporterResult onCompletion: onHostFileImporterResult
).onOpenURL(perform: onOpenURL) )
.onOpenURL(perform: onOpenURL)
.themePrimaryView() .themePrimaryView()
} }
} }
@ -171,3 +174,4 @@ private extension OrganizerView {
addProfileModalType = .addHost(url, false) addProfileModalType = .addHost(url, false)
} }
} }
#endif

View File

@ -44,6 +44,8 @@ extension PaywallView {
@State private var purchaseState: PurchaseState? @State private var purchaseState: PurchaseState?
@State private var didPurchaseAppleTV = false
init(isPresented: Binding<Bool>, feature: LocalProduct? = nil) { init(isPresented: Binding<Bool>, feature: LocalProduct? = nil) {
productManager = .shared productManager = .shared
_isPresented = isPresented _isPresented = isPresented
@ -52,12 +54,22 @@ extension PaywallView {
var body: some View { var body: some View {
List { List {
featuresSection skFullVersion.map {
fullFeaturesSection(withHeader: $0.localizedTitle)
}
purchaseSection purchaseSection
.disabled(purchaseState != nil) .disabled(purchaseState != nil)
restoreSection restoreSection
.disabled(purchaseState != nil) .disabled(purchaseState != nil)
}.navigationTitle(Unlocalized.appName) }
.navigationTitle(Unlocalized.appName)
.alert(Unlocalized.Other.appleTV, isPresented: $didPurchaseAppleTV) {
Button(L10n.Global.Strings.ok) {
isPresented = false
}
} message: {
Text(L10n.Paywall.Alerts.Purchase.Appletv.Success.message)
}
// reloading // reloading
.task { .task {
@ -121,9 +133,9 @@ private struct PurchaseRow: View {
// MARK: - // MARK: -
private extension PaywallView.PurchaseView { private extension PaywallView.PurchaseView {
var featuresSection: some View { func fullFeaturesSection(withHeader header: String) -> some View {
Section { Section {
ForEach(features) { feature in ForEach(fullFeatures) { feature in
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(feature.title) Text(feature.title)
.themeCellTitleStyle() .themeCellTitleStyle()
@ -134,6 +146,8 @@ private extension PaywallView.PurchaseView {
} }
} }
} }
} header: {
Text(header)
} }
} }
@ -183,6 +197,9 @@ private extension PaywallView.PurchaseView {
// hide full version if already bought the other platform version // hide full version if already bought the other platform version
var skFullVersion: InAppProduct? { var skFullVersion: InAppProduct? {
guard !productManager.isFullVersion() else {
return nil
}
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
guard !productManager.hasPurchased(.fullVersion_iOS) else { guard !productManager.hasPurchased(.fullVersion_iOS) else {
return nil return nil
@ -195,17 +212,25 @@ private extension PaywallView.PurchaseView {
return productManager.product(withIdentifier: .fullVersion) return productManager.product(withIdentifier: .fullVersion)
} }
var features: [FeatureModel] { var skAppleTV: InAppProduct? {
guard feature == .appleTV else {
return nil
}
return productManager.product(withIdentifier: .appleTV)
}
var fullFeatures: [FeatureModel] {
productManager.featureProducts(excluding: { productManager.featureProducts(excluding: {
$0 == .fullVersion || $0.isPlatformVersion $0 == .fullVersion || $0 == .appleTV || $0.isLegacyPlatformVersion
}) })
.map { .map {
FeatureModel(product: $0) FeatureModel(product: $0)
} }
.sorted()
} }
var productRowModels: [InAppProduct] { var productRowModels: [InAppProduct] {
[skFullVersion] [skFullVersion, skAppleTV]
.compactMap { $0 } .compactMap { $0 }
} }
} }
@ -249,16 +274,25 @@ private extension PurchaseRow {
// MARK: - // MARK: -
// IMPORTANT: resync shared profiles after purchasing Apple TV feature (drop time limit)
private extension PaywallView.PurchaseView { private extension PaywallView.PurchaseView {
func purchaseProduct(_ product: InAppProduct) { func purchaseProduct(_ product: InAppProduct) {
purchaseState = .purchasing(product) purchaseState = .purchasing(product)
Task { Task {
do { do {
let wasEligibleForAppleTV = productManager.isEligible(forFeature: .appleTV)
let result = try await productManager.purchase(product) let result = try await productManager.purchase(product)
switch result { switch result {
case .done: case .done:
isPresented = false if !wasEligibleForAppleTV && productManager.isEligible(forFeature: .appleTV) {
ProfileManager.shared.refreshSharedProfiles()
didPurchaseAppleTV = true
} else {
isPresented = false
}
case .cancelled: case .cancelled:
break break
@ -281,8 +315,16 @@ private extension PaywallView.PurchaseView {
Task { Task {
do { do {
let wasEligibleForAppleTV = productManager.isEligible(forFeature: .appleTV)
try await productManager.restorePurchases() try await productManager.restorePurchases()
isPresented = false
if !wasEligibleForAppleTV && productManager.isEligible(forFeature: .appleTV) {
ProfileManager.shared.refreshSharedProfiles()
didPurchaseAppleTV = true
} else {
isPresented = false
}
purchaseState = nil purchaseState = nil
} catch { } catch {
pp_log.error("Unable to restore purchases: \(error)") pp_log.error("Unable to restore purchases: \(error)")

View File

@ -53,11 +53,13 @@ extension ProfileView {
Label(L10n.Global.Strings.protocol, systemImage: themeVPNProtocolImage) Label(L10n.Global.Strings.protocol, systemImage: themeVPNProtocolImage)
.withTrailingText(currentProfile.value.currentVPNProtocol.description) .withTrailingText(currentProfile.value.currentVPNProtocol.description)
} }
#if !os(tvOS)
NavigationLink { NavigationLink {
EndpointView(currentProfile: currentProfile) EndpointView(currentProfile: currentProfile)
} label: { } label: {
Label(L10n.Global.Strings.endpoint, systemImage: themeEndpointImage) Label(L10n.Global.Strings.endpoint, systemImage: themeEndpointImage)
} }
#endif
if currentProfile.value.requiresCredentials { if currentProfile.value.requiresCredentials {
NavigationLink { NavigationLink {
AccountView( AccountView(

View File

@ -96,6 +96,7 @@ extension ProfileView {
} }
} }
#if !os(tvOS)
struct ShortcutsButton: View { struct ShortcutsButton: View {
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
@ -114,6 +115,7 @@ extension ProfileView {
} }
} }
} }
#endif
struct RenameButton: View { struct RenameButton: View {
@Binding private var modalType: ModalType? @Binding private var modalType: ModalType?
@ -164,9 +166,11 @@ private extension ProfileView.MainMenu {
var mainView: some View { var mainView: some View {
Menu { Menu {
ProfileView.ReconnectButton() ProfileView.ReconnectButton()
#if !os(tvOS)
ProfileView.ShortcutsButton( ProfileView.ShortcutsButton(
modalType: $modalType modalType: $modalType
) )
#endif
Divider() Divider()
ProfileView.RenameButton( ProfileView.RenameButton(
modalType: $modalType modalType: $modalType
@ -238,11 +242,13 @@ private extension ProfileView.MainMenu {
} }
} }
#if !os(tvOS)
private extension ProfileView.ShortcutsButton { private extension ProfileView.ShortcutsButton {
var isEligibleForSiri: Bool { var isEligibleForSiri: Bool {
productManager.isEligible(forFeature: .siriShortcuts) productManager.isEligible(forFeature: .siriShortcuts)
} }
} }
#endif
// MARK: - // MARK: -
@ -260,6 +266,7 @@ private extension ProfileView.MainMenu {
} }
} }
#if !os(tvOS)
private extension ProfileView.ShortcutsButton { private extension ProfileView.ShortcutsButton {
func presentShortcutsOrPaywall() { func presentShortcutsOrPaywall() {
@ -271,6 +278,7 @@ private extension ProfileView.ShortcutsButton {
} }
} }
} }
#endif
private extension ProfileView.DuplicateButton { private extension ProfileView.DuplicateButton {
func duplicateProfile(withId id: UUID) { func duplicateProfile(withId id: UUID) {

View File

@ -0,0 +1,95 @@
//
// ProfileView+TV.swift
// Passepartout
//
// Created by Davide De Rosa on 12/22/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import PassepartoutLibrary
import SwiftUI
extension ProfileView {
struct TVSection: View {
@ObservedObject private var profileManager: ProfileManager
@ObservedObject private var productManager: ProductManager
@ObservedObject private var currentProfile: ObservableProfile
@Binding private var isProfileShared: Bool
@Binding private var modalType: ModalType?
init(currentProfile: ObservableProfile, modalType: Binding<ModalType?>) {
let profileManager: ProfileManager = .shared
self.profileManager = profileManager
productManager = .shared
self.currentProfile = currentProfile
_isProfileShared = Binding {
profileManager.isSharing(profile: currentProfile.value)
} set: {
profileManager.setSharing($0, profile: currentProfile.value)
}
_modalType = modalType
}
var body: some View {
Section {
Toggle(isOn: $isProfileShared) {
Label(shareText, systemImage: themeAppleTVImage)
}
// eligibility: present paywall for full support for Apple TV
if !isEligibleForAppleTV {
Button(L10n.Paywall.title) {
modalType = .paywallAppleTV
}
}
} footer: {
Text(footerText)
}
}
}
}
private extension ProfileView.TVSection {
var isEligibleForAppleTV: Bool {
productManager.isEligible(forFeature: .appleTV)
}
var shareText: String {
var sentences: [String] = [Unlocalized.Other.appleTV]
if !isEligibleForAppleTV {
sentences.append(L10n.Profile.Items.TvSharing.Caption.limited(Constants.InApp.tvLimitedMinutes))
}
return sentences.joined(separator: "")
}
var footerText: String {
var sentences: [String] = [L10n.Profile.Sections.Tv.Footer.encryption]
if !isEligibleForAppleTV {
sentences.append(L10n.Profile.Sections.Tv.Footer.Restricted.p1(Constants.InApp.tvLimitedMinutes))
sentences.append(L10n.Profile.Sections.Tv.Footer.Restricted.p2)
}
return sentences.joined(separator: " ")
}
}

View File

@ -73,6 +73,7 @@ private extension ProfileView.VPNSection {
VPNToggle( VPNToggle(
profile: profile, profile: profile,
interactiveProfile: interactiveProfile, interactiveProfile: interactiveProfile,
title: L10n.Global.Strings.enabled,
rateLimit: Constants.RateLimit.vpnToggle rateLimit: Constants.RateLimit.vpnToggle
) )
} }

View File

@ -30,7 +30,9 @@ struct ProfileView: View {
enum ModalType: Int, Identifiable { enum ModalType: Int, Identifiable {
case interactiveAccount case interactiveAccount
#if !os(tvOS)
case shortcuts case shortcuts
#endif
case rename case rename
@ -40,6 +42,8 @@ struct ProfileView: View {
case paywallTrustedNetworks case paywallTrustedNetworks
case paywallAppleTV
var id: Int { var id: Int {
rawValue rawValue
} }
@ -104,6 +108,10 @@ private extension ProfileView {
currentProfile: currentProfile, currentProfile: currentProfile,
modalType: $modalType modalType: $modalType
) )
TVSection(
currentProfile: currentProfile,
modalType: $modalType
)
ExtraSection(currentProfile: currentProfile) ExtraSection(currentProfile: currentProfile)
Section { Section {
DiagnosticsRow(currentProfile: currentProfile) DiagnosticsRow(currentProfile: currentProfile)
@ -122,10 +130,12 @@ private extension ProfileView {
InteractiveConnectionView(profile: currentProfile.value) InteractiveConnectionView(profile: currentProfile.value)
}.themeGlobal() }.themeGlobal()
#if !os(tvOS)
case .shortcuts: case .shortcuts:
NavigationView { NavigationView {
ShortcutsView(target: currentProfile.value) ShortcutsView(target: currentProfile.value)
}.themeGlobal() }.themeGlobal()
#endif
case .rename: case .rename:
NavigationView { NavigationView {
@ -155,6 +165,14 @@ private extension ProfileView {
feature: .trustedNetworks feature: .trustedNetworks
) )
}.themeGlobal() }.themeGlobal()
case .paywallAppleTV:
NavigationView {
PaywallView(
modalType: $modalType,
feature: .appleTV
)
}.themeGlobal()
} }
} }
} }

View File

@ -168,9 +168,11 @@ private extension ProviderLocationView {
ForEach(filteredLocations(for: category)) { location in ForEach(filteredLocations(for: category)) { location in
if isEditable { if isEditable {
locationRow(location) locationRow(location)
#if !os(tvOS)
.swipeActions(edge: .trailing, allowsFullSwipe: false) { .swipeActions(edge: .trailing, allowsFullSwipe: false) {
favoriteActions(location) favoriteActions(location)
} }
#endif
} else { } else {
locationRow(location) locationRow(location)
} }

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import MessageUI import MessageUI
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -88,3 +89,4 @@ struct ReportIssueView: View {
) )
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import Intents import Intents
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -162,3 +163,4 @@ private extension ShortcutsView.AddView {
pendingShortcut = shortcut pendingShortcut = shortcut
} }
} }
#endif

View File

@ -23,6 +23,7 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
#if !os(tvOS)
import Intents import Intents
import PassepartoutLibrary import PassepartoutLibrary
import SwiftUI import SwiftUI
@ -176,3 +177,4 @@ private extension ShortcutsView {
modalType = .add(shortcut: shortcut) modalType = .add(shortcut: shortcut)
} }
} }
#endif

View File

@ -0,0 +1,118 @@
//
// ActiveProfileView+TV.swift
// Passepartout
//
// Created by Davide De Rosa on 12/18/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
#if os(tvOS)
import PassepartoutLibrary
import SwiftUI
struct ActiveProfileView: View {
@ObservedObject private var profileManager: ProfileManager
@ObservedObject private var vpnState: ObservableVPNState
init(profileManager: ProfileManager) {
self.profileManager = profileManager
vpnState = .shared
}
var body: some View {
Section {
let activeProfile = profileManager.activeProfile
nameView(for: activeProfile)
if let activeProfile {
toggleView(for: activeProfile)
vpnProtocolView(for: activeProfile)
statusView(for: activeProfile)
if let expirationDate = activeProfile.connectionExpirationDate {
expirationView(at: expirationDate)
}
}
}
if let activeProfile = profileManager.activeProfile,
let server = activeProfile.providerServer(.shared) {
providerSection(with: server)
}
}
}
private extension ActiveProfileView {
func nameView(for profile: Profile?) -> some View {
NavigationLink {
ProfilesListView(profileManager: profileManager)
} label: {
Text(L10n.Global.Placeholders.profileName)
.withTrailingText(profile?.header.name)
}
}
func vpnProtocolView(for profile: Profile) -> some View {
Text(L10n.Global.Strings.protocol)
.withTrailingText(profile.currentVPNProtocol.description)
}
func toggleView(for profile: Profile) -> some View {
VPNToggle(
profile: profile,
interactiveProfile: .constant(nil),
title: L10n.Profile.Items.ConnectionStatus.caption,
rateLimit: Constants.RateLimit.vpnToggle
)
}
func statusView(for activeProfile: Profile) -> some View {
HStack {
Text(Unlocalized.VPN.vpn)
Spacer()
if vpnState.isEnabled && activeProfile.isExpired {
Text(L10n.Global.Errors.tunnelExpired)
.themeSecondaryTextStyle()
} else {
VPNStatusText(isActiveProfile: true)
.themeSecondaryTextStyle()
}
}
}
func expirationView(at expirationDate: Date) -> some View {
Text(L10n.Profile.Items.ExpiresAt.caption)
.withTrailingText(expirationDate.timestamp)
}
func providerSection(with server: ProviderServer) -> some View {
Section {
Text(L10n.Global.Strings.name)
.withTrailingText(server.providerMetadata.fullName)
HStack {
Text(L10n.Provider.Location.title)
Spacer()
Label(server.localizedDescription(style: .country), image: themeAssetsCountryImage(server.countryCode))
.themeSecondaryTextStyle()
}
} header: {
Text(L10n.Global.Strings.provider)
}
}
}
#endif

View File

@ -0,0 +1,43 @@
//
// MainView+TV.swift
// Passepartout
//
// Created by Davide De Rosa on 12/17/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
#if os(tvOS)
import SwiftUI
struct MainView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
OrganizerView()
}
.themeGlobal()
}
}
#Preview {
MainView()
}
#endif

View File

@ -0,0 +1,163 @@
//
// OrganizerView+TV.swift
// Passepartout
//
// Created by Davide De Rosa on 12/17/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
#if os(tvOS)
import PassepartoutLibrary
import SwiftUI
struct OrganizerView: View {
@ObservedObject private var profileManager: ProfileManager
#if targetEnvironment(simulator)
@State private var didLoadMockProfiles = false
#endif
init(profileManager: ProfileManager = .shared) {
self.profileManager = profileManager
}
var body: some View {
List {
ActiveProfileView(profileManager: profileManager)
aboutSection
}
.navigationTitle(Unlocalized.appName)
.themeTV()
.themeAnimation(on: profileManager.activeProfileId)
#if targetEnvironment(simulator)
.task {
await loadMockProfiles()
}
#endif
}
}
private extension OrganizerView {
var aboutSection: some View {
Section {
Text(L10n.Version.title)
.withTrailingText(Constants.Global.appVersionString)
} header: {
Text(L10n.About.title)
}
}
}
// MARK: Mock
#if targetEnvironment(simulator)
import TunnelKitOpenVPN
import TunnelKitWireGuard
// poor man's preview:
//
// https://developer.apple.com/forums/thread/719078
private let mockHosts: [(String, VPNProtocolType)] = [
("My Profile", .wireGuard),
("Friend's House", .openVPN),
("At School", .wireGuard)
]
private let mockProviders: [ProviderName] = [
.hideme,
.pia,
.protonvpn
]
@MainActor
private let mockRepository: ProfileRepository = {
let hostProfiles = mockHosts.map { name, vpnType in
let header = Profile.Header(name: name)
switch vpnType {
case .openVPN:
let ovpn = OpenVPN.ConfigurationBuilder()
return Profile(header, configuration: ovpn.build())
case .wireGuard:
let wg = WireGuard.ConfigurationBuilder()
return Profile(header, configuration: wg.build())
}
}
let providerProfiles = mockProviders.map { providerName in
let manager = ProviderManager.shared
let metadata = manager.provider(withName: providerName)!
let header = Profile.Header(name: metadata.fullName, providerName: providerName)
var provider = Profile.Provider(providerName)
let vpnType: VPNProtocolType = .openVPN // isOpenVPN ? .openVPN : .wireGuard
var settings = Profile.Provider.Settings()
let anyServer = manager.anyDefaultServer(providerName, vpnProtocol: vpnType)
settings.serverId = anyServer?.id
settings.presetId = anyServer?.presetIds.first
settings.account = .init("hello", "world")
provider.vpnSettings[vpnType] = settings
return Profile(header, provider: provider)
}
var profiles: [Profile] = []
profiles.append(contentsOf: hostProfiles)
profiles.append(contentsOf: providerProfiles)
let repo = MockProfileRepository()
try? repo.saveProfiles(profiles.map {
var copy = $0
copy.connectionExpirationDate = Date().addingTimeInterval(10.0)
return copy
})
return repo
}()
private extension OrganizerView {
func loadMockProfiles() async {
guard !didLoadMockProfiles else {
return
}
do {
let providerManager: ProviderManager = .shared
try await providerManager.fetchProvidersIndexPublisher(priority: .bundle).async()
for name in mockProviders {
try? await providerManager.fetchProviderPublisher(withName: name, vpnProtocol: .openVPN, priority: .bundle).async()
}
profileManager.swapProfileRepository(mockRepository)
profileManager.activateProfile(mockRepository.allProfiles().first!.value)
didLoadMockProfiles = true
} catch {
ErrorHandler.shared.handle(AppError(error))
}
}
}
#Preview {
NavigationStack {
OrganizerView()
}
}
#endif
#endif

View File

@ -0,0 +1,113 @@
//
// ProfilesList+TV.swift
// Passepartout
//
// Created by Davide De Rosa on 12/18/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
#if os(tvOS)
import PassepartoutLibrary
import SwiftUI
struct ProfilesListView: View {
@ObservedObject private var profileManager: ProfileManager
@Environment(\.presentationMode) private var presentationMode
@State private var profileIdPendingSelection: UUID?
@FocusState private var focusedProfileId: UUID?
init(profileManager: ProfileManager) {
self.profileManager = profileManager
}
var body: some View {
List {
Section {
Text(listHeader)
.font(.footnote)
.themeSecondaryTextStyle()
ForEach(profiles, content: profileRow)
.themeAnimation(on: profiles)
}
}
.onAppear {
focusedProfileId = profileManager.activeProfileId
}
.disabled(profileIdPendingSelection != nil)
.navigationTitle(Text(L10n.Global.Strings.profiles))
.themeTV()
}
}
private extension ProfilesListView {
var profiles: [Profile] {
profileManager.profiles.sorted()
}
var listHeader: String {
[
L10n.Organizer.Sections.Tv.ProfilesList.Header.p1,
L10n.Profile.Sections.Tv.Footer.encryption
]
.joined(separator: " ")
}
func profileRow(for profile: Profile) -> some View {
Button {
activateProfile(profile)
} label: {
HStack {
Text(profile.header.name)
Spacer()
if profile.header.id == profileIdPendingSelection {
ProgressView()
} else if profileManager.isActiveProfile(profile.header.id) {
themeCheckmarkImage.asSystemImage
}
}
}
.focused($focusedProfileId, equals: profile.id)
}
func activateProfile(_ profile: Profile) {
guard profile.id != profileManager.activeProfileId else {
presentationMode.wrappedValue.dismiss()
return
}
Task {
profileIdPendingSelection = profile.id
do {
try await profileManager.makeProfileReady(profile)
await VPNManager.shared.disable()
profileManager.activateProfile(profile)
presentationMode.wrappedValue.dismiss()
} catch {
ErrorHandler.shared.handle(
title: L10n.Global.Strings.profiles,
message: AppError(error).localizedDescription
)
}
}
}
}
#endif

View File

@ -39,22 +39,25 @@ struct VPNToggle: View {
@Binding private var interactiveProfile: Profile? @Binding private var interactiveProfile: Profile?
private let title: String
private let rateLimit: Int private let rateLimit: Int
@State private var canToggle = true @State private var canToggle = true
init(profile: Profile, interactiveProfile: Binding<Profile?>, rateLimit: Int) { init(profile: Profile, interactiveProfile: Binding<Profile?>, title: String, rateLimit: Int) {
profileManager = .shared profileManager = .shared
vpnManager = .shared vpnManager = .shared
currentVPNState = .shared currentVPNState = .shared
productManager = .shared productManager = .shared
self.profile = profile self.profile = profile
_interactiveProfile = interactiveProfile _interactiveProfile = interactiveProfile
self.title = title
self.rateLimit = rateLimit self.rateLimit = rateLimit
} }
var body: some View { var body: some View {
Toggle(L10n.Global.Strings.enabled, isOn: isEnabled) Toggle(title, isOn: isEnabled)
.disabled(!canToggle) .disabled(!canToggle)
.themeAnimation(on: currentVPNState.isEnabled) .themeAnimation(on: currentVPNState.isEnabled)
} }
@ -129,11 +132,13 @@ private extension VPNToggle {
pp_log.debug("Donating connection intents...") pp_log.debug("Donating connection intents...")
#if !os(tvOS)
IntentDispatcher.donateEnableVPN() IntentDispatcher.donateEnableVPN()
IntentDispatcher.donateDisableVPN() IntentDispatcher.donateDisableVPN()
IntentDispatcher.donateConnection( IntentDispatcher.donateConnection(
with: profile, with: profile,
providerManager: ProviderManager.shared providerManager: ProviderManager.shared
) )
#endif
} }
} }

View File

@ -71,9 +71,11 @@ extension View {
.truncationMode(truncationMode) .truncationMode(truncationMode)
if copyOnTap { if copyOnTap {
trailing trailing
#if !os(tvOS)
.onTapGesture { .onTapGesture {
text.map(Utils.copyToPasteboard) text.map(Utils.copyToPasteboard)
} }
#endif
} else { } else {
trailing trailing
} }

View File

@ -72,7 +72,7 @@
"global.strings.authentication" = "Authentifizierung"; "global.strings.authentication" = "Authentifizierung";
"global.messages.unlock_app" = "Passepartout ist gesperrt"; "global.messages.unlock_app" = "Passepartout ist gesperrt";
"global.messages.email_not_configured" = "Es wurde kein Email-Account konfiguriert."; "global.messages.email_not_configured" = "Es wurde kein Email-Account konfiguriert.";
"global.messages.share" = "Passepartout ist ein Benutzerfreundlicher, Open Source OpenVPN / WireGuard client für iOS und macOS"; "global.messages.share" = "Passepartout ist ein Benutzerfreundlicher, Open Source OpenVPN / WireGuard client für iOS und macOS"; // FIXME: l10n, Apple platforms
"global.alerts.buttons.remind" = "Später erinnern"; "global.alerts.buttons.remind" = "Später erinnern";
"global.alerts.buttons.never" = "Nicht erneut fragen"; "global.alerts.buttons.never" = "Nicht erneut fragen";
@ -83,6 +83,7 @@
"global.errors.missing_account" = "Fehlender Account"; "global.errors.missing_account" = "Fehlender Account";
"global.errors.missing_provider_server" = "Fehlender Standort"; "global.errors.missing_provider_server" = "Fehlender Standort";
"global.errors.missing_provider_preset" = "Fehlende Voreinstellung"; "global.errors.missing_provider_preset" = "Fehlende Voreinstellung";
"global.errors.tunnel_expired" = "Verbindung abgelaufen";
/* MARK: Menus */ /* MARK: Menus */
@ -132,6 +133,7 @@
/* MARK: OrganizerView */ /* MARK: OrganizerView */
"organizer.sections.active" = "In Benutzung"; "organizer.sections.active" = "In Benutzung";
"organizer.sections.tv.profiles_list.header.p1" = "Öffne Passepartout auf deinem iOS- oder macOS-Gerät und aktiviere den „Apple TV“-Schalter eines Profils, damit es hier angezeigt wird.";
/* MARK: OrganizerView */ /* MARK: OrganizerView */
"organizer.empty.no_profiles" = "Keine Profile"; "organizer.empty.no_profiles" = "Keine Profile";
@ -163,6 +165,9 @@
"profile.sections.vpn.footer" = "Die Verbindung wird immer aufgebaut wenn notwendig."; "profile.sections.vpn.footer" = "Die Verbindung wird immer aufgebaut wenn notwendig.";
"profile.sections.status.header" = "Verbindung"; "profile.sections.status.header" = "Verbindung";
"profile.sections.provider_infrastructure.footer" = "Zuletzt aktualisiert am %@."; "profile.sections.provider_infrastructure.footer" = "Zuletzt aktualisiert am %@.";
"profile.sections.tv.footer.encryption" = "Profile sind verschlüsselt und werden deinem Apple TV über iCloud zur Verfügung gestellt.";
"profile.sections.tv.footer.restricted.p1" = "Die Verbindung läuft jedoch nach %d Minuten ab.";
"profile.sections.tv.footer.restricted.p2" = "Bei einem Kauf entfällt diese Beschränkung.";
"profile.sections.vpn_survives_sleep.footer" = "Deaktivieren um die Batterielaufzeit zu verbessern, allerdings verzögert sich der Verbindungsaufbau beim Aufwachen."; "profile.sections.vpn_survives_sleep.footer" = "Deaktivieren um die Batterielaufzeit zu verbessern, allerdings verzögert sich der Verbindungsaufbau beim Aufwachen.";
"profile.sections.vpn_resolves_hostname.footer" = "Bevorzugt in den meisten Netzwerken und benötigt in manchen IPv6 Netzwerken. Deaktivieren wo DNS geblockt ist oder um die Aushandlung zu beschleunigen bei langsam antwortenden DNS."; "profile.sections.vpn_resolves_hostname.footer" = "Bevorzugt in den meisten Netzwerken und benötigt in manchen IPv6 Netzwerken. Deaktivieren wo DNS geblockt ist oder um die Aushandlung zu beschleunigen bei langsam antwortenden DNS.";
"profile.sections.feedback.header" = "Feedback"; "profile.sections.feedback.header" = "Feedback";
@ -178,6 +183,8 @@
"profile.items.only_shows_favorites.caption" = "Nur favorisierte Standorte anzeigen"; "profile.items.only_shows_favorites.caption" = "Nur favorisierte Standorte anzeigen";
"profile.items.vpn_survives_sleep.caption" = "Verbindung aktiv halten trotz Schlafmodus"; "profile.items.vpn_survives_sleep.caption" = "Verbindung aktiv halten trotz Schlafmodus";
"profile.items.vpn_resolves_hostname.caption" = "Server Hostname auflösen"; "profile.items.vpn_resolves_hostname.caption" = "Server Hostname auflösen";
"profile.items.tv_sharing.caption.limited" = "Begrenzt auf %d Minuten";
"profile.items.expires_at.caption" = "Endet";
"profile.alerts.rename.title" = "Profil umbenennen"; "profile.alerts.rename.title" = "Profil umbenennen";
"profile.alerts.reconnect_vpn.message" = "Möchtest du erneut zum VPN verbinden?"; "profile.alerts.reconnect_vpn.message" = "Möchtest du erneut zum VPN verbinden?";
@ -323,6 +330,7 @@
"paywall.items.full_version.extra_description" = "Alle Anbieter (inklusive Zukünftige)\n%@"; "paywall.items.full_version.extra_description" = "Alle Anbieter (inklusive Zukünftige)\n%@";
"paywall.items.restore.title" = "Einkäufe wiederherstellen"; "paywall.items.restore.title" = "Einkäufe wiederherstellen";
"paywall.items.restore.description" = "Wenn Sie diese App oder Funktion in der Vergangenheit gekauft haben, können Sie Ihre Einkäufe wiederherstellen und dieser Bildschirm wird nicht mehr angezeigt."; "paywall.items.restore.description" = "Wenn Sie diese App oder Funktion in der Vergangenheit gekauft haben, können Sie Ihre Einkäufe wiederherstellen und dieser Bildschirm wird nicht mehr angezeigt.";
"paywall.alerts.purchase.appletv.success.message" = "Vielen Dank! Das Zeitlimit entfällt, sobald iCloud auf dem neuesten Stand ist. Warte einen Moment und starte die Verbindung in der TV-App dann neu.";
/* MARK: DonateView */ /* MARK: DonateView */

View File

@ -72,7 +72,7 @@
"global.strings.authentication" = "Αυθεντικοποίηση"; "global.strings.authentication" = "Αυθεντικοποίηση";
"global.messages.unlock_app" = "Το πασπαρτού είναι κλειδωμένο"; "global.messages.unlock_app" = "Το πασπαρτού είναι κλειδωμένο";
"global.messages.email_not_configured" = "Δεν έχει ρυθμιστεί λογαριασμός ηλεκτρονικού ταχυδρομείου."; "global.messages.email_not_configured" = "Δεν έχει ρυθμιστεί λογαριασμός ηλεκτρονικού ταχυδρομείου.";
"global.messages.share" = "Το Passepartout είναι φιλικό προς το χρήστη, ανοιχτού κώδικα OpenVPN / WireGuard πρόγραμμα για iOS και macOS"; "global.messages.share" = "Το Passepartout είναι φιλικό προς το χρήστη, ανοιχτού κώδικα OpenVPN / WireGuard πρόγραμμα για iOS και macOS"; // FIXME: l10n, Apple platforms
"global.alerts.buttons.remind" = "Υπενθύμιση Αργότερα"; "global.alerts.buttons.remind" = "Υπενθύμιση Αργότερα";
"global.alerts.buttons.never" = "Μη με ρωτήσεις ξανά"; "global.alerts.buttons.never" = "Μη με ρωτήσεις ξανά";
@ -83,6 +83,7 @@
"global.errors.missing_account" = "Λείπει λογαριασμός"; "global.errors.missing_account" = "Λείπει λογαριασμός";
"global.errors.missing_provider_server" = "Λείπει η τοποθεσία"; "global.errors.missing_provider_server" = "Λείπει η τοποθεσία";
"global.errors.missing_provider_preset" = "Λείπει προεπιλογή"; "global.errors.missing_provider_preset" = "Λείπει προεπιλογή";
"global.errors.tunnel_expired" = "H σύνδεση έληξε";
/* MARK: Menus */ /* MARK: Menus */
@ -132,6 +133,7 @@
/* MARK: OrganizerView */ /* MARK: OrganizerView */
"organizer.sections.active" = "Σε χρήση"; "organizer.sections.active" = "Σε χρήση";
"organizer.sections.tv.profiles_list.header.p1" = "Ανοίξτε το Passepartout στη συσκευή σας iOS ή macOS και ενεργοποιήστε την εναλλαγή \"Apple TV\" ενός προφίλ για να εμφανιστεί εδώ.";
/* MARK: OrganizerView */ /* MARK: OrganizerView */
"organizer.empty.no_profiles" = "Δεν υπάρχουν προφίλ"; "organizer.empty.no_profiles" = "Δεν υπάρχουν προφίλ";
@ -163,6 +165,9 @@
"profile.sections.vpn.footer" = "Η σύνδεση θα πραγματοποιηθεί όποτε είναι απαραίτητο."; "profile.sections.vpn.footer" = "Η σύνδεση θα πραγματοποιηθεί όποτε είναι απαραίτητο.";
"profile.sections.status.header" = "Σύνδεση"; "profile.sections.status.header" = "Σύνδεση";
"profile.sections.provider_infrastructure.footer" = "Τελευταία ενημέρωση στις %@."; "profile.sections.provider_infrastructure.footer" = "Τελευταία ενημέρωση στις %@.";
"profile.sections.tv.footer.encryption" = "Τα προφίλ κρυπτογραφούνται και διατίθενται στο Apple TV σας μέσω iCloud.";
"profile.sections.tv.footer.restricted.p1" = "Ωστόσο, η σύνδεση θα λήξει μετά από %d λεπτά.";
"profile.sections.tv.footer.restricted.p2" = "Αγορά για την κατάργηση του περιορισμού.";
"profile.sections.vpn_survives_sleep.footer" = "Απενεργοποιήστε για να βελτιώσετε τη χρήση της μπαταρίας, εις βάρος των περιστασιακών επιβραδύνσεων που οφείλονται σε επανασύνδεση αφύπνισης."; "profile.sections.vpn_survives_sleep.footer" = "Απενεργοποιήστε για να βελτιώσετε τη χρήση της μπαταρίας, εις βάρος των περιστασιακών επιβραδύνσεων που οφείλονται σε επανασύνδεση αφύπνισης.";
"profile.sections.vpn_resolves_hostname.footer" = "Προτιμάται στα περισσότερα δίκτυα και απαιτείται σε ορισμένα δίκτυα IPv6. Απενεργοποιήστε το εκεί που μπλοκάρεται το DNS ή για να επιταχύνετε τη επικοινωνία όταν το DNS είναι αργό για να ανταποκριθεί."; "profile.sections.vpn_resolves_hostname.footer" = "Προτιμάται στα περισσότερα δίκτυα και απαιτείται σε ορισμένα δίκτυα IPv6. Απενεργοποιήστε το εκεί που μπλοκάρεται το DNS ή για να επιταχύνετε τη επικοινωνία όταν το DNS είναι αργό για να ανταποκριθεί.";
"profile.sections.feedback.header" = "Ανατροφοδότηση"; "profile.sections.feedback.header" = "Ανατροφοδότηση";
@ -178,6 +183,8 @@
"profile.items.only_shows_favorites.caption" = "Προβολή αγαπημένων τοποθεσιών μόνο"; "profile.items.only_shows_favorites.caption" = "Προβολή αγαπημένων τοποθεσιών μόνο";
"profile.items.vpn_survives_sleep.caption" = "Κρατήστε ζωντανό στον ύπνο"; "profile.items.vpn_survives_sleep.caption" = "Κρατήστε ζωντανό στον ύπνο";
"profile.items.vpn_resolves_hostname.caption" = "Επίλυση του ονόματος σέρβερ διακομιστή"; "profile.items.vpn_resolves_hostname.caption" = "Επίλυση του ονόματος σέρβερ διακομιστή";
"profile.items.tv_sharing.caption.limited" = "Περιορίστηκε σε %d λεπτά";
"profile.items.expires_at.caption" = "Λήξη";
"profile.alerts.rename.title" = "Μετονομασία προφίλ"; "profile.alerts.rename.title" = "Μετονομασία προφίλ";
"profile.alerts.reconnect_vpn.message" = "Θέλετε να συνδεθείτε ξανά με το VPN;"; "profile.alerts.reconnect_vpn.message" = "Θέλετε να συνδεθείτε ξανά με το VPN;";
@ -323,6 +330,7 @@
"paywall.items.full_version.extra_description" = "Όλοι οι πάροχοι (περιλαμβάνονται και οι μελλοντικοί)\n%@"; "paywall.items.full_version.extra_description" = "Όλοι οι πάροχοι (περιλαμβάνονται και οι μελλοντικοί)\n%@";
"paywall.items.restore.title" = "Επαναφορά Αγορών"; "paywall.items.restore.title" = "Επαναφορά Αγορών";
"paywall.items.restore.description" = "Εαν αγοράσατε την εφαρμογή στο παρελθόν, μπορείτε να κάνετε επαναφορά αγορών και αυτή η οθόνη δε θα εμφανιστεί ξανά."; "paywall.items.restore.description" = "Εαν αγοράσατε την εφαρμογή στο παρελθόν, μπορείτε να κάνετε επαναφορά αγορών και αυτή η οθόνη δε θα εμφανιστεί ξανά.";
"paywall.alerts.purchase.appletv.success.message" = "Σας ευχαριστούμε! Το χρονικό όριο θα αφαιρεθεί μόλις ενημερωθεί το iCloud. Περιμένετε μερικά λεπτά και μετά επανεκκινήστε τη σύνδεση στην εφαρμογή TV.";
/* MARK: DonateView */ /* MARK: DonateView */

View File

@ -72,7 +72,7 @@
"global.strings.authentication" = "Authentication"; "global.strings.authentication" = "Authentication";
"global.messages.unlock_app" = "Passepartout is locked"; "global.messages.unlock_app" = "Passepartout is locked";
"global.messages.email_not_configured" = "No e-mail account is configured."; "global.messages.email_not_configured" = "No e-mail account is configured.";
"global.messages.share" = "Passepartout is a user-friendly, open source OpenVPN / WireGuard client for iOS and macOS"; "global.messages.share" = "Passepartout is a user-friendly, open source OpenVPN / WireGuard client for iOS and macOS"; // FIXME: l10n, Apple platforms
"global.alerts.buttons.remind" = "Remind me later"; "global.alerts.buttons.remind" = "Remind me later";
"global.alerts.buttons.never" = "Don't ask again"; "global.alerts.buttons.never" = "Don't ask again";
@ -83,6 +83,7 @@
"global.errors.missing_account" = "Missing account"; "global.errors.missing_account" = "Missing account";
"global.errors.missing_provider_server" = "Missing location"; "global.errors.missing_provider_server" = "Missing location";
"global.errors.missing_provider_preset" = "Missing preset"; "global.errors.missing_provider_preset" = "Missing preset";
"global.errors.tunnel_expired" = "Connection expired";
/* MARK: Menus */ /* MARK: Menus */
@ -132,6 +133,7 @@
/* MARK: OrganizerView */ /* MARK: OrganizerView */
"organizer.sections.active" = "In use"; "organizer.sections.active" = "In use";
"organizer.sections.tv.profiles_list.header.p1" = "Open Passepartout on your iOS or macOS device and enable the \"Apple TV\" toggle of a profile to make it appear here.";
/* MARK: OrganizerView */ /* MARK: OrganizerView */
"organizer.empty.no_profiles" = "No profiles"; "organizer.empty.no_profiles" = "No profiles";
@ -163,6 +165,9 @@
"profile.sections.vpn.footer" = "The connection will be established whenever necessary."; "profile.sections.vpn.footer" = "The connection will be established whenever necessary.";
"profile.sections.status.header" = "Connection"; "profile.sections.status.header" = "Connection";
"profile.sections.provider_infrastructure.footer" = "Last updated on %@."; "profile.sections.provider_infrastructure.footer" = "Last updated on %@.";
"profile.sections.tv.footer.encryption" = "Profiles are encrypted and made available to your Apple TV via iCloud.";
"profile.sections.tv.footer.restricted.p1" = "However, the connection will expire after %d minutes.";
"profile.sections.tv.footer.restricted.p2" = "Purchase to drop the restriction.";
"profile.sections.vpn_survives_sleep.footer" = "Disable to improve battery usage, at the expense of occasional slowdowns due to wake-up reconnections."; "profile.sections.vpn_survives_sleep.footer" = "Disable to improve battery usage, at the expense of occasional slowdowns due to wake-up reconnections.";
"profile.sections.vpn_resolves_hostname.footer" = "Preferred in most networks and required in some IPv6 networks. Disable where DNS is blocked, or to speed up negotiation when DNS is slow to respond."; "profile.sections.vpn_resolves_hostname.footer" = "Preferred in most networks and required in some IPv6 networks. Disable where DNS is blocked, or to speed up negotiation when DNS is slow to respond.";
"profile.sections.feedback.header" = "Feedback"; "profile.sections.feedback.header" = "Feedback";
@ -178,6 +183,8 @@
"profile.items.only_shows_favorites.caption" = "Only show favorite locations"; "profile.items.only_shows_favorites.caption" = "Only show favorite locations";
"profile.items.vpn_survives_sleep.caption" = "Keep alive on sleep"; "profile.items.vpn_survives_sleep.caption" = "Keep alive on sleep";
"profile.items.vpn_resolves_hostname.caption" = "Resolve provider hostname"; "profile.items.vpn_resolves_hostname.caption" = "Resolve provider hostname";
"profile.items.tv_sharing.caption.limited" = "Limited to %d minutes";
"profile.items.expires_at.caption" = "Expiration";
"profile.alerts.rename.title" = "Rename profile"; "profile.alerts.rename.title" = "Rename profile";
"profile.alerts.reconnect_vpn.message" = "Do you want to reconnect to the VPN?"; "profile.alerts.reconnect_vpn.message" = "Do you want to reconnect to the VPN?";
@ -323,6 +330,7 @@
"paywall.items.full_version.extra_description" = "All providers (including future ones)\n%@"; "paywall.items.full_version.extra_description" = "All providers (including future ones)\n%@";
"paywall.items.restore.title" = "Restore purchases"; "paywall.items.restore.title" = "Restore purchases";
"paywall.items.restore.description" = "If you bought this app or feature in the past, you can restore your purchases and this screen won't show again."; "paywall.items.restore.description" = "If you bought this app or feature in the past, you can restore your purchases and this screen won't show again.";
"paywall.alerts.purchase.appletv.success.message" = "Thank you! The time limit will be dropped as soon as iCloud catches up. Wait a few moments, then restart the connection on the TV app.";
/* MARK: DonateView */ /* MARK: DonateView */

View File

@ -72,7 +72,7 @@
"global.strings.authentication" = "Autenticación"; "global.strings.authentication" = "Autenticación";
"global.messages.unlock_app" = "Passepartout está bloqueada"; "global.messages.unlock_app" = "Passepartout está bloqueada";
"global.messages.email_not_configured" = "Ningún e-mail configurado."; "global.messages.email_not_configured" = "Ningún e-mail configurado.";
"global.messages.share" = "Passepartout es un cliente OpenVPN / WireGuard intuitivo, de código abierto para iOS y macOS"; "global.messages.share" = "Passepartout es un cliente OpenVPN / WireGuard intuitivo, de código abierto para iOS y macOS"; // FIXME: l10n, Apple platforms
"global.alerts.buttons.remind" = "Recordar más tarde"; "global.alerts.buttons.remind" = "Recordar más tarde";
"global.alerts.buttons.never" = "No preguntar más"; "global.alerts.buttons.never" = "No preguntar más";
@ -83,6 +83,7 @@
"global.errors.missing_account" = "Sin cuenta"; "global.errors.missing_account" = "Sin cuenta";
"global.errors.missing_provider_server" = "Sin ubicación"; "global.errors.missing_provider_server" = "Sin ubicación";
"global.errors.missing_provider_preset" = "Sin ajuste"; "global.errors.missing_provider_preset" = "Sin ajuste";
"global.errors.tunnel_expired" = "Conexión caducada";
/* MARK: Menus */ /* MARK: Menus */
@ -132,6 +133,7 @@
/* MARK: OrganizerView */ /* MARK: OrganizerView */
"organizer.sections.active" = "En uso"; "organizer.sections.active" = "En uso";
"organizer.sections.tv.profiles_list.header.p1" = "Abre Passepartout desde tu dispositivo iOS o macOS y habilita el switch \"Apple TV\" de un perfil para que aparezca aquí abajo.";
/* MARK: OrganizerView */ /* MARK: OrganizerView */
"organizer.empty.no_profiles" = "Ningún perfil"; "organizer.empty.no_profiles" = "Ningún perfil";
@ -163,6 +165,9 @@
"profile.sections.vpn.footer" = "La conexión se establecerá siempre y cuando sea necesario."; "profile.sections.vpn.footer" = "La conexión se establecerá siempre y cuando sea necesario.";
"profile.sections.status.header" = "Conexión"; "profile.sections.status.header" = "Conexión";
"profile.sections.provider_infrastructure.footer" = "Última actualización: %@."; "profile.sections.provider_infrastructure.footer" = "Última actualización: %@.";
"profile.sections.tv.footer.encryption" = "Los perfiles están encriptados y puestos a disposición para tu Apple TV a través de iCloud.";
"profile.sections.tv.footer.restricted.p1" = "Sin embargo, la conexión caducará en %d minutos.";
"profile.sections.tv.footer.restricted.p2" = "Comprar para eliminar la restricción.";
"profile.sections.vpn_survives_sleep.footer" = "Deshabilitar para mejorar el uso de la batería, a costa de ralentizaciones ocasionales por las reconexiones al despertar el dispositivo."; "profile.sections.vpn_survives_sleep.footer" = "Deshabilitar para mejorar el uso de la batería, a costa de ralentizaciones ocasionales por las reconexiones al despertar el dispositivo.";
"profile.sections.vpn_resolves_hostname.footer" = "Preferido en la mayoría de las redes y necesario en algunas redes IPv6. Deshabilitar donde el DNS esté bloqueado, o para acelerar la negociación cuando el DNS sea lento en responder."; "profile.sections.vpn_resolves_hostname.footer" = "Preferido en la mayoría de las redes y necesario en algunas redes IPv6. Deshabilitar donde el DNS esté bloqueado, o para acelerar la negociación cuando el DNS sea lento en responder.";
"profile.sections.feedback.header" = "Feedback"; "profile.sections.feedback.header" = "Feedback";
@ -178,6 +183,8 @@
"profile.items.only_shows_favorites.caption" = "Mostrar solo ubicaciones favoritas"; "profile.items.only_shows_favorites.caption" = "Mostrar solo ubicaciones favoritas";
"profile.items.vpn_survives_sleep.caption" = "Mantener en modo inactivo"; "profile.items.vpn_survives_sleep.caption" = "Mantener en modo inactivo";
"profile.items.vpn_resolves_hostname.caption" = "Resolver hostname del servidor"; "profile.items.vpn_resolves_hostname.caption" = "Resolver hostname del servidor";
"profile.items.tv_sharing.caption.limited" = "Limitado a %d minutos";
"profile.items.expires_at.caption" = "Caducidad";
"profile.alerts.rename.title" = "Renombrar perfil"; "profile.alerts.rename.title" = "Renombrar perfil";
"profile.alerts.reconnect_vpn.message" = "Quieres reconectarte al VPN?"; "profile.alerts.reconnect_vpn.message" = "Quieres reconectarte al VPN?";
@ -323,6 +330,7 @@
"paywall.items.full_version.extra_description" = "Todos los proveedores (incluye los futuros)\n%@"; "paywall.items.full_version.extra_description" = "Todos los proveedores (incluye los futuros)\n%@";
"paywall.items.restore.title" = "Restaurar compras"; "paywall.items.restore.title" = "Restaurar compras";
"paywall.items.restore.description" = "Si compraste esta aplicación o funcionalidad anteriormente, puedes restaurar tus compras y esta pantalla no volverá a aparecer."; "paywall.items.restore.description" = "Si compraste esta aplicación o funcionalidad anteriormente, puedes restaurar tus compras y esta pantalla no volverá a aparecer.";
"paywall.alerts.purchase.appletv.success.message" = "Gracias! El limite de tiempo será eliminado en cuanto iCloud se sincronice. Espera unos momentos, y reinicia la conexión en la app de la TV.";
/* MARK: DonateView */ /* MARK: DonateView */

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1 +1,3 @@
Passepartout is a bare VPN client and, as such, does not store nor forward any personal data. Therefore, you should get in touch with your VPN service provider to learn about its data retention policy.
Passepartout may access GitHub repositories on request. Please refer to GitHub privacy statement for any information about data retention when accessing their website.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

View File

@ -1,4 +1,9 @@
### Fixed ### Added
- Persisted profile is overwritten with its former value. - WireGuard: Show data count.
### Changed
- Upgrade OpenSSL to 3.2.0.
- Encrypt profiles stored to iCloud.

Some files were not shown because too many files have changed in this diff Show More