diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6563a4d9..ee1e1a06 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -24,8 +24,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- #platform: ["iOS", "macOS", "tvOS"]
- platform: ["iOS", "macOS"]
+ platform: ["iOS", "macOS", "tvOS"]
steps:
- uses: passepartoutvpn/action-prepare-xcode-build@master
with:
@@ -64,8 +63,7 @@ jobs:
PILOT_GROUPS: ${{ vars.PILOT_GROUPS }}
PILOT_NOTIFY_EXTERNAL_TESTERS: ${{ vars.PILOT_NOTIFY_EXTERNAL_TESTERS }}
run: |
- #PLATFORMS=("iOS" "macOS" "tvOS")
- PLATFORMS=("iOS" "macOS")
+ PLATFORMS=("iOS" "macOS" "tvOS")
for PLATFORM in ${PLATFORMS[@]}; do
bundle exec fastlane --env $PLATFORM public_beta
done
diff --git a/Passepartout/App/AppDelegate.swift b/Passepartout/App/AppDelegate.swift
index f51762c7..ed8e1ec0 100644
--- a/Passepartout/App/AppDelegate.swift
+++ b/Passepartout/App/AppDelegate.swift
@@ -33,19 +33,8 @@ final class AppDelegate: NSObject {
let context: AppContext = .shared
// let context: AppContext = .mock(withRegistry: .shared)
- func configure() {
- PassepartoutConfiguration.shared.configureLogging(
- to: BundleConfiguration.urlForAppLog,
- parameters: Constants.shared.log,
- logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
- )
- AppUI.configure(with: context)
-
-#if os(macOS)
- // keep this for login item because scenePhase is not triggered
- Task {
- try await context.tunnel.prepare(purge: true)
- }
-#endif
+ func configure(with appUIConfiguring: AppUIConfiguring) {
+ AppUI(appUIConfiguring)
+ .configure(with: context)
}
}
diff --git a/Passepartout/App/Platforms/App+iOS.swift b/Passepartout/App/Platforms/App+iOS.swift
index 182f81a3..12ff2149 100644
--- a/Passepartout/App/Platforms/App+iOS.swift
+++ b/Passepartout/App/Platforms/App+iOS.swift
@@ -30,7 +30,7 @@ import SwiftUI
extension AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
- configure()
+ configure(with: AppUIMain())
return true
}
}
diff --git a/Passepartout/App/Platforms/App+macOS.swift b/Passepartout/App/Platforms/App+macOS.swift
index e482493d..fcd267f0 100644
--- a/Passepartout/App/Platforms/App+macOS.swift
+++ b/Passepartout/App/Platforms/App+macOS.swift
@@ -32,8 +32,8 @@ import SwiftUI
extension AppDelegate: NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
+ configure(with: AppUIMain())
hideIfLoginItem()
- configure()
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
diff --git a/Passepartout/App/Platforms/App+tvOS.swift b/Passepartout/App/Platforms/App+tvOS.swift
index 158b396d..f3eccb92 100644
--- a/Passepartout/App/Platforms/App+tvOS.swift
+++ b/Passepartout/App/Platforms/App+tvOS.swift
@@ -30,7 +30,7 @@ import SwiftUI
extension AppDelegate: UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
- configure()
+ configure(with: AppUITV())
return true
}
}
diff --git a/Passepartout/Library/.swiftpm/xcode/xcshareddata/xcschemes/AppUIMain.xcscheme b/Passepartout/Library/.swiftpm/xcode/xcshareddata/xcschemes/AppUIMain.xcscheme
new file mode 100644
index 00000000..8e8bf209
--- /dev/null
+++ b/Passepartout/Library/.swiftpm/xcode/xcshareddata/xcschemes/AppUIMain.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Passepartout/Library/.swiftpm/xcode/xcshareddata/xcschemes/AppUITV.xcscheme b/Passepartout/Library/.swiftpm/xcode/xcshareddata/xcschemes/AppUITV.xcscheme
new file mode 100644
index 00000000..02c14f13
--- /dev/null
+++ b/Passepartout/Library/.swiftpm/xcode/xcshareddata/xcschemes/AppUITV.xcscheme
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Passepartout/Library/Sources/AppUI/AppUI.swift b/Passepartout/Library/Sources/AppUI/AppUI.swift
index 1f9ff8ac..cd87279b 100644
--- a/Passepartout/Library/Sources/AppUI/AppUI.swift
+++ b/Passepartout/Library/Sources/AppUI/AppUI.swift
@@ -23,24 +23,43 @@
// along with Passepartout. If not, see .
//
+import CommonLibrary
import Foundation
import PassepartoutKit
public protocol AppUIConfiguring {
- static func configure(with context: AppContext)
+ func configure(with context: AppContext)
}
-public enum AppUI {
- public static func configure(with context: AppContext) {
- assertMissingModuleImplementations()
+public final class AppUI: AppUIConfiguring {
+ private let appUIConfiguring: AppUIConfiguring?
+
+ public init(_ appUIConfiguring: AppUIConfiguring?) {
+ self.appUIConfiguring = appUIConfiguring
+ }
+
+ public func configure(with context: AppContext) {
+ PassepartoutConfiguration.shared.configureLogging(
+ to: BundleConfiguration.urlForAppLog,
+ parameters: Constants.shared.log,
+ logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
+ )
+
+ assertMissingImplementations()
+ appUIConfiguring?.configure(with: context)
+
Task {
- try? await context.providerManager.fetchIndex(from: API.shared)
+ try await context.providerManager.fetchIndex(from: API.shared)
+#if os(macOS)
+ // keep this for login item because scenePhase is not triggered
+ try await context.tunnel.prepare(purge: true)
+#endif
}
}
}
-extension AppUI {
- public static func assertMissingModuleImplementations() {
+private extension AppUI {
+ func assertMissingImplementations() {
ModuleType.allCases.forEach { moduleType in
let builder = moduleType.newModule()
guard builder is ModuleTypeProviding else {
diff --git a/Passepartout/Library/Sources/AppUI/Theme/Theme+UI.swift b/Passepartout/Library/Sources/AppUI/Theme/Theme+UI.swift
index 2b64bc22..6d8f4421 100644
--- a/Passepartout/Library/Sources/AppUI/Theme/Theme+UI.swift
+++ b/Passepartout/Library/Sources/AppUI/Theme/Theme+UI.swift
@@ -106,16 +106,16 @@ struct ThemeItemModalModifier: ViewModifier where Modal: View, T: Iden
}
}
-struct ThemeBooleanPopoverModifier: ViewModifier where Popover: View {
+struct ThemeBooleanPopoverModifier: ViewModifier, SizeClassProviding where Popover: View {
@EnvironmentObject
private var theme: Theme
@Environment(\.horizontalSizeClass)
- private var hsClass
+ var hsClass
@Environment(\.verticalSizeClass)
- private var vsClass
+ var vsClass
@Binding
var isPresented: Bool
@@ -124,7 +124,7 @@ struct ThemeBooleanPopoverModifier: ViewModifier where Popover: View {
let popover: Popover
func body(content: Content) -> some View {
- if hsClass == .regular && vsClass == .regular {
+ if isBigDevice {
content
.popover(isPresented: $isPresented) {
popover
diff --git a/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift b/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift
index 5b511919..aa88c711 100644
--- a/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift
+++ b/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift
@@ -26,15 +26,17 @@
@_exported import AppUI
import Foundation
-public enum AppUIMain: AppUIConfiguring {
- public static func configure(with context: AppContext) {
- assertMissingModuleImplementations()
- AppUI.configure(with: context)
+public final class AppUIMain: AppUIConfiguring {
+ public init() {
+ }
+
+ public func configure(with context: AppContext) {
+ assertMissingImplementations()
}
}
private extension AppUIMain {
- static func assertMissingModuleImplementations() {
+ func assertMissingImplementations() {
let providerModuleTypes: Set = [
.openVPN
]
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift
index f0e86f7b..e71cd303 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift
@@ -27,14 +27,15 @@ import AppLibrary
import CommonLibrary
import PassepartoutKit
import SwiftUI
+import UtilsLibrary
-public struct AppCoordinator: View {
+public struct AppCoordinator: View, SizeClassProviding {
@Environment(\.horizontalSizeClass)
- private var hsClass
+ public var hsClass
@Environment(\.verticalSizeClass)
- private var vsClass
+ public var vsClass
@AppStorage(AppPreference.profilesLayout.key)
private var layout: ProfilesLayout = .list
@@ -59,7 +60,7 @@ public struct AppCoordinator: View {
}
public var body: some View {
- if hsClass == .regular && vsClass == .regular {
+ if isBigDevice {
AppModalCoordinator(
layout: $layout,
profileManager: profileManager,
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/AppToolbar.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/AppToolbar.swift
index c0563164..f460e82e 100644
--- a/Passepartout/Library/Sources/AppUIMain/Views/App/AppToolbar.swift
+++ b/Passepartout/Library/Sources/AppUIMain/Views/App/AppToolbar.swift
@@ -26,14 +26,15 @@
import AppLibrary
import PassepartoutKit
import SwiftUI
+import UtilsLibrary
-struct AppToolbar: ToolbarContent {
+struct AppToolbar: ToolbarContent, SizeClassProviding {
@Environment(\.horizontalSizeClass)
- private var hsClass
+ var hsClass
@Environment(\.verticalSizeClass)
- private var vsClass
+ var vsClass
let profileManager: ProfileManager
@@ -50,7 +51,7 @@ struct AppToolbar: ToolbarContent {
let onNewProfile: (Profile) -> Void
var body: some ToolbarContent {
- if hsClass == .regular && vsClass == .regular {
+ if isBigDevice {
ToolbarItemGroup {
addProfileMenu
aboutButton
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/AppMenu+Model.swift b/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu+Model.swift
similarity index 100%
rename from Passepartout/Library/Sources/AppUIMain/Views/AppMenu/AppMenu+Model.swift
rename to Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu+Model.swift
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/AppMenu.swift b/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu.swift
similarity index 100%
rename from Passepartout/Library/Sources/AppUIMain/Views/AppMenu/AppMenu.swift
rename to Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenu.swift
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/AppMenuImage.swift b/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenuImage.swift
similarity index 100%
rename from Passepartout/Library/Sources/AppUIMain/Views/AppMenu/AppMenuImage.swift
rename to Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppMenuImage.swift
diff --git a/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/AppWindow.swift b/Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppWindow.swift
similarity index 100%
rename from Passepartout/Library/Sources/AppUIMain/Views/AppMenu/AppWindow.swift
rename to Passepartout/Library/Sources/AppUIMain/Views/AppMenu/macOS/AppWindow.swift
diff --git a/Passepartout/Library/Sources/AppUITV/AppUITV.swift b/Passepartout/Library/Sources/AppUITV/AppUITV.swift
index dde25466..752ad072 100644
--- a/Passepartout/Library/Sources/AppUITV/AppUITV.swift
+++ b/Passepartout/Library/Sources/AppUITV/AppUITV.swift
@@ -26,8 +26,10 @@
@_exported import AppUI
import Foundation
-public enum AppUITV: AppUIConfiguring {
- public static func configure(with context: AppContext) {
- AppUI.configure(with: context)
+public final class AppUITV: AppUIConfiguring {
+ public init() {
+ }
+
+ public func configure(with context: AppContext) {
}
}
diff --git a/Passepartout/Library/Sources/AppUITV/Views/AppCoordinator.swift b/Passepartout/Library/Sources/AppUITV/Views/AppCoordinator.swift
index 1388f628..87a463b6 100644
--- a/Passepartout/Library/Sources/AppUITV/Views/AppCoordinator.swift
+++ b/Passepartout/Library/Sources/AppUITV/Views/AppCoordinator.swift
@@ -27,7 +27,7 @@ import AppLibrary
import PassepartoutKit
import SwiftUI
-// FIXME: ###, UI for Apple TV
+// FIXME: #788, UI for Apple TV
public struct AppCoordinator: View {
private let profileManager: ProfileManager
diff --git a/Passepartout/Library/Sources/CommonLibrary/Domain/Constants.swift b/Passepartout/Library/Sources/CommonLibrary/Domain/Constants.swift
index 090e8325..95767760 100644
--- a/Passepartout/Library/Sources/CommonLibrary/Domain/Constants.swift
+++ b/Passepartout/Library/Sources/CommonLibrary/Domain/Constants.swift
@@ -100,6 +100,8 @@ public struct Constants: Decodable, Sendable {
}
public struct API: Decodable, Sendable {
+ public let bundlePath: String
+
public let timeoutInterval: TimeInterval
}
diff --git a/Passepartout/Library/Sources/CommonLibrary/Resources/Constants.json b/Passepartout/Library/Sources/CommonLibrary/Resources/Constants.json
index 3def3bdc..ea9b7e65 100644
--- a/Passepartout/Library/Sources/CommonLibrary/Resources/Constants.json
+++ b/Passepartout/Library/Sources/CommonLibrary/Resources/Constants.json
@@ -26,6 +26,7 @@
"refreshInterval": 3.0
},
"api": {
+ "bundlePath": "API",
"timeoutInterval": 5.0
},
"log": {
diff --git a/Passepartout/Library/Sources/CommonLibrary/Shared.swift b/Passepartout/Library/Sources/CommonLibrary/Shared.swift
index 2fd9667d..d0201006 100644
--- a/Passepartout/Library/Sources/CommonLibrary/Shared.swift
+++ b/Passepartout/Library/Sources/CommonLibrary/Shared.swift
@@ -72,7 +72,7 @@ extension API {
]
public static let bundled: APIMapper = {
- guard let url = Bundle.module.url(forResource: "API", withExtension: nil) else {
+ guard let url = Bundle.module.url(forResource: Constants.shared.api.bundlePath, withExtension: nil) else {
fatalError("Unable to find bundled API")
}
let ws = API.V5.DefaultWebServices(
diff --git a/Passepartout/Library/Sources/UtilsLibrary/Views/LongContentView.swift b/Passepartout/Library/Sources/UtilsLibrary/Views/LongContentView.swift
index a4290479..11215c27 100644
--- a/Passepartout/Library/Sources/UtilsLibrary/Views/LongContentView.swift
+++ b/Passepartout/Library/Sources/UtilsLibrary/Views/LongContentView.swift
@@ -35,7 +35,7 @@ public struct LongContentView: View {
public var copySystemImage: String?
public var body: some View {
- TextEditor(text: $content)
+ contentView
.toolbar {
Button {
copyToPasteboard(content)
@@ -43,7 +43,18 @@ public struct LongContentView: View {
Image(systemName: copySystemImage ?? "doc.on.doc")
}
}
- // TODO: #659, add padding as inset, let content extend beyond safe areas
+ }
+
+ @ViewBuilder
+ private var contentView: some View {
+ if #available(iOS 17, macOS 14, *) {
+ TextEditor(text: $content)
+// .contentMargins(8)
+// .scrollContentBackground(.hidden)
+ .scrollClipDisabled()
+ } else {
+ TextEditor(text: $content)
+ }
}
}
diff --git a/Passepartout/Library/Sources/UtilsLibrary/Views/SizeClassProviding.swift b/Passepartout/Library/Sources/UtilsLibrary/Views/SizeClassProviding.swift
new file mode 100644
index 00000000..48467f3c
--- /dev/null
+++ b/Passepartout/Library/Sources/UtilsLibrary/Views/SizeClassProviding.swift
@@ -0,0 +1,38 @@
+//
+// SizeClassProviding.swift
+// Passepartout
+//
+// Created by Davide De Rosa on 10/31/24.
+// Copyright (c) 2024 Davide De Rosa. All rights reserved.
+//
+// https://github.com/passepartoutvpn
+//
+// This file is part of Passepartout.
+//
+// Passepartout is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Passepartout is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Passepartout. If not, see .
+//
+
+import SwiftUI
+
+public protocol SizeClassProviding {
+ var hsClass: UserInterfaceSizeClass? { get }
+
+ var vsClass: UserInterfaceSizeClass? { get }
+}
+
+extension SizeClassProviding {
+ public var isBigDevice: Bool {
+ hsClass == .regular && vsClass == .regular
+ }
+}