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:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
#platform: ["iOS", "macOS", "tvOS"]
|
platform: ["iOS", "macOS", "tvOS"]
|
||||||
platform: ["iOS", "macOS"]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: passepartoutvpn/action-prepare-xcode-build@master
|
- uses: passepartoutvpn/action-prepare-xcode-build@master
|
||||||
with:
|
with:
|
||||||
|
@ -64,8 +63,7 @@ jobs:
|
||||||
PILOT_GROUPS: ${{ vars.PILOT_GROUPS }}
|
PILOT_GROUPS: ${{ vars.PILOT_GROUPS }}
|
||||||
PILOT_NOTIFY_EXTERNAL_TESTERS: ${{ vars.PILOT_NOTIFY_EXTERNAL_TESTERS }}
|
PILOT_NOTIFY_EXTERNAL_TESTERS: ${{ vars.PILOT_NOTIFY_EXTERNAL_TESTERS }}
|
||||||
run: |
|
run: |
|
||||||
#PLATFORMS=("iOS" "macOS" "tvOS")
|
PLATFORMS=("iOS" "macOS" "tvOS")
|
||||||
PLATFORMS=("iOS" "macOS")
|
|
||||||
for PLATFORM in ${PLATFORMS[@]}; do
|
for PLATFORM in ${PLATFORMS[@]}; do
|
||||||
bundle exec fastlane --env $PLATFORM public_beta
|
bundle exec fastlane --env $PLATFORM public_beta
|
||||||
done
|
done
|
||||||
|
|
|
@ -33,19 +33,8 @@ final class AppDelegate: NSObject {
|
||||||
let context: AppContext = .shared
|
let context: AppContext = .shared
|
||||||
// let context: AppContext = .mock(withRegistry: .shared)
|
// let context: AppContext = .mock(withRegistry: .shared)
|
||||||
|
|
||||||
func configure() {
|
func configure(with appUIConfiguring: AppUIConfiguring) {
|
||||||
PassepartoutConfiguration.shared.configureLogging(
|
AppUI(appUIConfiguring)
|
||||||
to: BundleConfiguration.urlForAppLog,
|
.configure(with: context)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import SwiftUI
|
||||||
|
|
||||||
extension AppDelegate: UIApplicationDelegate {
|
extension AppDelegate: UIApplicationDelegate {
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||||
configure()
|
configure(with: AppUIMain())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,8 +32,8 @@ import SwiftUI
|
||||||
|
|
||||||
extension AppDelegate: NSApplicationDelegate {
|
extension AppDelegate: NSApplicationDelegate {
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
configure(with: AppUIMain())
|
||||||
hideIfLoginItem()
|
hideIfLoginItem()
|
||||||
configure()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
|
|
|
@ -30,7 +30,7 @@ import SwiftUI
|
||||||
|
|
||||||
extension AppDelegate: UIApplicationDelegate {
|
extension AppDelegate: UIApplicationDelegate {
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||||
configure()
|
configure(with: AppUITV())
|
||||||
return true
|
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/>.
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import CommonLibrary
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
public protocol AppUIConfiguring {
|
public protocol AppUIConfiguring {
|
||||||
static func configure(with context: AppContext)
|
func configure(with context: AppContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AppUI {
|
public final class AppUI: AppUIConfiguring {
|
||||||
public static func configure(with context: AppContext) {
|
private let appUIConfiguring: AppUIConfiguring?
|
||||||
assertMissingModuleImplementations()
|
|
||||||
|
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 {
|
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 {
|
private extension AppUI {
|
||||||
public static func assertMissingModuleImplementations() {
|
func assertMissingImplementations() {
|
||||||
ModuleType.allCases.forEach { moduleType in
|
ModuleType.allCases.forEach { moduleType in
|
||||||
let builder = moduleType.newModule()
|
let builder = moduleType.newModule()
|
||||||
guard builder is ModuleTypeProviding else {
|
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
|
@EnvironmentObject
|
||||||
private var theme: Theme
|
private var theme: Theme
|
||||||
|
|
||||||
@Environment(\.horizontalSizeClass)
|
@Environment(\.horizontalSizeClass)
|
||||||
private var hsClass
|
var hsClass
|
||||||
|
|
||||||
@Environment(\.verticalSizeClass)
|
@Environment(\.verticalSizeClass)
|
||||||
private var vsClass
|
var vsClass
|
||||||
|
|
||||||
@Binding
|
@Binding
|
||||||
var isPresented: Bool
|
var isPresented: Bool
|
||||||
|
@ -124,7 +124,7 @@ struct ThemeBooleanPopoverModifier<Popover>: ViewModifier where Popover: View {
|
||||||
let popover: Popover
|
let popover: Popover
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
if hsClass == .regular && vsClass == .regular {
|
if isBigDevice {
|
||||||
content
|
content
|
||||||
.popover(isPresented: $isPresented) {
|
.popover(isPresented: $isPresented) {
|
||||||
popover
|
popover
|
||||||
|
|
|
@ -26,15 +26,17 @@
|
||||||
@_exported import AppUI
|
@_exported import AppUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum AppUIMain: AppUIConfiguring {
|
public final class AppUIMain: AppUIConfiguring {
|
||||||
public static func configure(with context: AppContext) {
|
public init() {
|
||||||
assertMissingModuleImplementations()
|
}
|
||||||
AppUI.configure(with: context)
|
|
||||||
|
public func configure(with context: AppContext) {
|
||||||
|
assertMissingImplementations()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AppUIMain {
|
private extension AppUIMain {
|
||||||
static func assertMissingModuleImplementations() {
|
func assertMissingImplementations() {
|
||||||
let providerModuleTypes: Set<ModuleType> = [
|
let providerModuleTypes: Set<ModuleType> = [
|
||||||
.openVPN
|
.openVPN
|
||||||
]
|
]
|
||||||
|
|
|
@ -27,14 +27,15 @@ import AppLibrary
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UtilsLibrary
|
||||||
|
|
||||||
public struct AppCoordinator: View {
|
public struct AppCoordinator: View, SizeClassProviding {
|
||||||
|
|
||||||
@Environment(\.horizontalSizeClass)
|
@Environment(\.horizontalSizeClass)
|
||||||
private var hsClass
|
public var hsClass
|
||||||
|
|
||||||
@Environment(\.verticalSizeClass)
|
@Environment(\.verticalSizeClass)
|
||||||
private var vsClass
|
public var vsClass
|
||||||
|
|
||||||
@AppStorage(AppPreference.profilesLayout.key)
|
@AppStorage(AppPreference.profilesLayout.key)
|
||||||
private var layout: ProfilesLayout = .list
|
private var layout: ProfilesLayout = .list
|
||||||
|
@ -59,7 +60,7 @@ public struct AppCoordinator: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
if hsClass == .regular && vsClass == .regular {
|
if isBigDevice {
|
||||||
AppModalCoordinator(
|
AppModalCoordinator(
|
||||||
layout: $layout,
|
layout: $layout,
|
||||||
profileManager: profileManager,
|
profileManager: profileManager,
|
||||||
|
|
|
@ -26,14 +26,15 @@
|
||||||
import AppLibrary
|
import AppLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UtilsLibrary
|
||||||
|
|
||||||
struct AppToolbar: ToolbarContent {
|
struct AppToolbar: ToolbarContent, SizeClassProviding {
|
||||||
|
|
||||||
@Environment(\.horizontalSizeClass)
|
@Environment(\.horizontalSizeClass)
|
||||||
private var hsClass
|
var hsClass
|
||||||
|
|
||||||
@Environment(\.verticalSizeClass)
|
@Environment(\.verticalSizeClass)
|
||||||
private var vsClass
|
var vsClass
|
||||||
|
|
||||||
let profileManager: ProfileManager
|
let profileManager: ProfileManager
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ struct AppToolbar: ToolbarContent {
|
||||||
let onNewProfile: (Profile) -> Void
|
let onNewProfile: (Profile) -> Void
|
||||||
|
|
||||||
var body: some ToolbarContent {
|
var body: some ToolbarContent {
|
||||||
if hsClass == .regular && vsClass == .regular {
|
if isBigDevice {
|
||||||
ToolbarItemGroup {
|
ToolbarItemGroup {
|
||||||
addProfileMenu
|
addProfileMenu
|
||||||
aboutButton
|
aboutButton
|
||||||
|
|
|
@ -26,8 +26,10 @@
|
||||||
@_exported import AppUI
|
@_exported import AppUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum AppUITV: AppUIConfiguring {
|
public final class AppUITV: AppUIConfiguring {
|
||||||
public static func configure(with context: AppContext) {
|
public init() {
|
||||||
AppUI.configure(with: context)
|
}
|
||||||
|
|
||||||
|
public func configure(with context: AppContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import AppLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// FIXME: ###, UI for Apple TV
|
// FIXME: #788, UI for Apple TV
|
||||||
|
|
||||||
public struct AppCoordinator: View {
|
public struct AppCoordinator: View {
|
||||||
private let profileManager: ProfileManager
|
private let profileManager: ProfileManager
|
||||||
|
|
|
@ -100,6 +100,8 @@ public struct Constants: Decodable, Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct API: Decodable, Sendable {
|
public struct API: Decodable, Sendable {
|
||||||
|
public let bundlePath: String
|
||||||
|
|
||||||
public let timeoutInterval: TimeInterval
|
public let timeoutInterval: TimeInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"refreshInterval": 3.0
|
"refreshInterval": 3.0
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
|
"bundlePath": "API",
|
||||||
"timeoutInterval": 5.0
|
"timeoutInterval": 5.0
|
||||||
},
|
},
|
||||||
"log": {
|
"log": {
|
||||||
|
|
|
@ -72,7 +72,7 @@ extension API {
|
||||||
]
|
]
|
||||||
|
|
||||||
public static let bundled: APIMapper = {
|
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")
|
fatalError("Unable to find bundled API")
|
||||||
}
|
}
|
||||||
let ws = API.V5.DefaultWebServices(
|
let ws = API.V5.DefaultWebServices(
|
||||||
|
|
|
@ -35,7 +35,7 @@ public struct LongContentView: View {
|
||||||
public var copySystemImage: String?
|
public var copySystemImage: String?
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
TextEditor(text: $content)
|
contentView
|
||||||
.toolbar {
|
.toolbar {
|
||||||
Button {
|
Button {
|
||||||
copyToPasteboard(content)
|
copyToPasteboard(content)
|
||||||
|
@ -43,7 +43,18 @@ public struct LongContentView: View {
|
||||||
Image(systemName: copySystemImage ?? "doc.on.doc")
|
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