Do some refactoring in AppUI targets (#789)
- Refactor AppUI initialization in all platforms (sort of template method pattern) - Make AppMenu specific to macOS by wrapping it into a folder for consistency - Add SizeClassProviding for repeated checks on hsClass/vsClass Fixes #659
This commit is contained in:
parent
80dd6dc779
commit
237277d4db
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ import SwiftUI
|
|||
|
||||
extension AppDelegate: NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
configure(with: AppUIMain())
|
||||
hideIfLoginItem()
|
||||
configure()
|
||||
}
|
||||
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1540"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "AppUIMain"
|
||||
BuildableName = "AppUIMain"
|
||||
BlueprintName = "AppUIMain"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "AppUIMain"
|
||||
BuildableName = "AppUIMain"
|
||||
BlueprintName = "AppUIMain"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1540"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "AppUITV"
|
||||
BuildableName = "AppUITV"
|
||||
BlueprintName = "AppUITV"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "AppUITV"
|
||||
BuildableName = "AppUITV"
|
||||
BlueprintName = "AppUITV"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -23,24 +23,43 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
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 {
|
||||
|
|
|
@ -106,16 +106,16 @@ struct ThemeItemModalModifier<Modal, T>: ViewModifier where Modal: View, T: Iden
|
|||
}
|
||||
}
|
||||
|
||||
struct ThemeBooleanPopoverModifier<Popover>: ViewModifier where Popover: View {
|
||||
struct ThemeBooleanPopoverModifier<Popover>: 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<Popover>: 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
|
||||
|
|
|
@ -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<ModuleType> = [
|
||||
.openVPN
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -100,6 +100,8 @@ public struct Constants: Decodable, Sendable {
|
|||
}
|
||||
|
||||
public struct API: Decodable, Sendable {
|
||||
public let bundlePath: String
|
||||
|
||||
public let timeoutInterval: TimeInterval
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"refreshInterval": 3.0
|
||||
},
|
||||
"api": {
|
||||
"bundlePath": "API",
|
||||
"timeoutInterval": 5.0
|
||||
},
|
||||
"log": {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public protocol SizeClassProviding {
|
||||
var hsClass: UserInterfaceSizeClass? { get }
|
||||
|
||||
var vsClass: UserInterfaceSizeClass? { get }
|
||||
}
|
||||
|
||||
extension SizeClassProviding {
|
||||
public var isBigDevice: Bool {
|
||||
hsClass == .regular && vsClass == .regular
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue