mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2025-01-31 04:52:05 +00:00
Move more views to UILibrary (#919)
- Move about subviews to UILibrary - Refactor about to single coordinator + platform views - Refactor debug log to single view + content views - Take out debug log routes from about routes - Rename Settings* to Preferences* - Reuse empty modifier in debug log - Fix a visual bug in .themeTrailingValue() (extra Spacer) Preparation for #914
This commit is contained in:
parent
a301806ac7
commit
2a46173169
@ -65,7 +65,7 @@ extension PassepartoutApp {
|
||||
.defaultSize(width: 600, height: 400)
|
||||
|
||||
Settings {
|
||||
SettingsView(profileManager: context.profileManager)
|
||||
PreferencesView(profileManager: context.profileManager)
|
||||
.frame(minWidth: 300, minHeight: 300)
|
||||
.withEnvironment(from: context, theme: theme)
|
||||
.environmentObject(settings)
|
||||
|
@ -0,0 +1,139 @@
|
||||
//
|
||||
// AboutCoordinator.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/22/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 CommonLibrary
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
import UILibrary
|
||||
|
||||
struct AboutCoordinator: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var iapManager: IAPManager
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
let profileManager: ProfileManager
|
||||
|
||||
let tunnel: ExtendedTunnel
|
||||
|
||||
@State
|
||||
private var path = NavigationPath()
|
||||
|
||||
@State
|
||||
private var navigationRoute: AboutCoordinatorRoute?
|
||||
|
||||
var body: some View {
|
||||
AboutContentView(
|
||||
profileManager: profileManager,
|
||||
isRestricted: iapManager.isRestricted,
|
||||
path: $path,
|
||||
navigationRoute: $navigationRoute,
|
||||
linkContent: linkView(to:),
|
||||
aboutDestination: pushDestination(for:),
|
||||
logDestination: pushDestination(for:)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension AboutCoordinator {
|
||||
func linkView(to route: AboutCoordinatorRoute) -> some View {
|
||||
NavigationLink(title(for: route), value: route)
|
||||
}
|
||||
|
||||
func title(for route: AboutCoordinatorRoute) -> String {
|
||||
switch route {
|
||||
case .credits:
|
||||
return Strings.Views.About.Credits.title
|
||||
|
||||
case .diagnostics:
|
||||
return Strings.Views.Diagnostics.title
|
||||
|
||||
case .donate:
|
||||
return Strings.Views.Donate.title
|
||||
|
||||
case .links:
|
||||
return Strings.Views.About.Links.title
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func pushDestination(for item: AboutCoordinatorRoute?) -> some View {
|
||||
switch item {
|
||||
case .credits:
|
||||
CreditsView()
|
||||
|
||||
case .diagnostics:
|
||||
DiagnosticsView(profileManager: profileManager, tunnel: tunnel)
|
||||
|
||||
case .donate:
|
||||
DonateView()
|
||||
|
||||
case .links:
|
||||
LinksView()
|
||||
|
||||
default:
|
||||
Text(Strings.Global.noSelection)
|
||||
.themeEmptyMessage()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func pushDestination(for item: DebugLogRoute?) -> some View {
|
||||
switch item {
|
||||
case .app(let title):
|
||||
DebugLogView(withAppParameters: Constants.shared.log) {
|
||||
DebugLogContentView(lines: $0)
|
||||
}
|
||||
.navigationTitle(title)
|
||||
|
||||
case .tunnel(let title, let url):
|
||||
if let url {
|
||||
DebugLogView(withURL: url) {
|
||||
DebugLogContentView(lines: $0)
|
||||
}
|
||||
.navigationTitle(title)
|
||||
} else {
|
||||
DebugLogView(withTunnel: tunnel, parameters: Constants.shared.log) {
|
||||
DebugLogContentView(lines: $0)
|
||||
}
|
||||
.navigationTitle(title)
|
||||
}
|
||||
|
||||
default:
|
||||
Text(Strings.Global.noSelection)
|
||||
.themeEmptyMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AboutCoordinator(
|
||||
profileManager: .mock,
|
||||
tunnel: .mock
|
||||
)
|
||||
.withMockEnvironment()
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
//
|
||||
// AboutRouterView+iOS.swift
|
||||
// AboutCoordinatorRoute.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/26/24.
|
||||
// Created by Davide De Rosa on 11/23/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
@ -23,21 +23,14 @@
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
import Foundation
|
||||
|
||||
import CommonLibrary
|
||||
import SwiftUI
|
||||
enum AboutCoordinatorRoute: Hashable {
|
||||
case credits
|
||||
|
||||
extension AboutRouterView {
|
||||
var body: some View {
|
||||
AboutView(
|
||||
profileManager: profileManager,
|
||||
navigationRoute: $navigationRoute
|
||||
)
|
||||
.navigationDestination(for: NavigationRoute.self, destination: pushDestination)
|
||||
.themeNavigationDetail()
|
||||
.themeNavigationStack(closable: true, path: $path)
|
||||
}
|
||||
case diagnostics
|
||||
|
||||
case donate
|
||||
|
||||
case links
|
||||
}
|
||||
|
||||
#endif
|
@ -1,105 +0,0 @@
|
||||
//
|
||||
// AboutRouterView.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/22/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 CommonLibrary
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
struct AboutRouterView: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
var dismiss
|
||||
|
||||
let profileManager: ProfileManager
|
||||
|
||||
let tunnel: ExtendedTunnel
|
||||
|
||||
@State
|
||||
var path = NavigationPath()
|
||||
|
||||
@State
|
||||
var navigationRoute: NavigationRoute?
|
||||
}
|
||||
|
||||
extension AboutRouterView {
|
||||
enum NavigationRoute: Hashable {
|
||||
case appDebugLog(title: String)
|
||||
|
||||
case credits
|
||||
|
||||
case diagnostics
|
||||
|
||||
case donate
|
||||
|
||||
case links
|
||||
|
||||
case tunnelDebugLog(title: String, url: URL?)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func pushDestination(for item: NavigationRoute?) -> some View {
|
||||
switch item {
|
||||
case .appDebugLog(let title):
|
||||
DebugLogView.withApp(parameters: Constants.shared.log)
|
||||
.navigationTitle(title)
|
||||
|
||||
case .credits:
|
||||
CreditsView()
|
||||
|
||||
case .diagnostics:
|
||||
DiagnosticsView(
|
||||
profileManager: profileManager,
|
||||
tunnel: tunnel
|
||||
)
|
||||
|
||||
case .donate:
|
||||
DonateView()
|
||||
|
||||
case .links:
|
||||
LinksView()
|
||||
|
||||
case .tunnelDebugLog(let title, let url):
|
||||
if let url {
|
||||
DebugLogView.withURL(url)
|
||||
.navigationTitle(title)
|
||||
} else {
|
||||
DebugLogView.withTunnel(tunnel, parameters: Constants.shared.log)
|
||||
.navigationTitle(title)
|
||||
}
|
||||
|
||||
default:
|
||||
Text(Strings.Global.noSelection)
|
||||
.themeEmptyMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AboutRouterView(
|
||||
profileManager: .mock,
|
||||
tunnel: .mock
|
||||
)
|
||||
.withMockEnvironment()
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
//
|
||||
// AboutView.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/23/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 CommonLibrary
|
||||
import CommonUtils
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
struct AboutView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
var iapManager: IAPManager
|
||||
|
||||
let profileManager: ProfileManager
|
||||
|
||||
@Binding
|
||||
var navigationRoute: AboutRouterView.NavigationRoute?
|
||||
|
||||
var body: some View {
|
||||
listView
|
||||
}
|
||||
}
|
||||
|
||||
extension AboutView {
|
||||
var creditsLink: some View {
|
||||
navLink(Strings.Views.About.Credits.title, to: .credits)
|
||||
}
|
||||
|
||||
var diagnosticsLink: some View {
|
||||
navLink(Strings.Views.Diagnostics.title, to: .diagnostics)
|
||||
}
|
||||
|
||||
var donateLink: some View {
|
||||
navLink(Strings.Views.Donate.title, to: .donate)
|
||||
}
|
||||
|
||||
var linksLink: some View {
|
||||
navLink(Strings.Views.About.Links.title, to: .links)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AboutView {
|
||||
func navLink(_ title: String, to route: AboutRouterView.NavigationRoute) -> some View {
|
||||
NavigationLink(title, value: route)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AboutView(
|
||||
profileManager: .mock,
|
||||
navigationRoute: .constant(nil)
|
||||
)
|
||||
.withMockEnvironment()
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// AboutView+iOS.swift
|
||||
// AboutContentView+iOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/27/24.
|
||||
@ -25,23 +25,55 @@
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
import CommonLibrary
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
import UILibrary
|
||||
|
||||
extension AboutView {
|
||||
struct AboutContentView<LinkContent, AboutDestination, LogDestination>: View where LinkContent: View, AboutDestination: View, LogDestination: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
let profileManager: ProfileManager
|
||||
|
||||
let isRestricted: Bool
|
||||
|
||||
@Binding
|
||||
var path: NavigationPath
|
||||
|
||||
@Binding
|
||||
var navigationRoute: AboutCoordinatorRoute?
|
||||
|
||||
let linkContent: (AboutCoordinatorRoute) -> LinkContent
|
||||
|
||||
let aboutDestination: (AboutCoordinatorRoute?) -> AboutDestination
|
||||
|
||||
let logDestination: (DebugLogRoute?) -> LogDestination
|
||||
|
||||
var body: some View {
|
||||
listView
|
||||
.navigationDestination(for: AboutCoordinatorRoute.self, destination: aboutDestination)
|
||||
.navigationDestination(for: DebugLogRoute.self, destination: logDestination)
|
||||
.themeNavigationDetail()
|
||||
.themeNavigationStack(closable: true, path: $path)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AboutContentView {
|
||||
var listView: some View {
|
||||
List {
|
||||
SettingsSectionGroup(profileManager: profileManager)
|
||||
PreferencesGroup(profileManager: profileManager)
|
||||
Group {
|
||||
linksLink
|
||||
creditsLink
|
||||
if !iapManager.isRestricted {
|
||||
donateLink
|
||||
linkContent(.links)
|
||||
linkContent(.credits)
|
||||
if !isRestricted {
|
||||
linkContent(.donate)
|
||||
}
|
||||
}
|
||||
.themeSection(header: Strings.Views.About.Sections.resources)
|
||||
Section {
|
||||
diagnosticsLink
|
||||
linkContent(.diagnostics)
|
||||
Text(Strings.Global.version)
|
||||
.themeTrailingValue(BundleConfiguration.mainVersionString)
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
//
|
||||
// AboutContentView+macOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/27/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/>.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
import CommonLibrary
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
struct AboutContentView<LinkContent, AboutDestination, LogDestination>: View where LinkContent: View, AboutDestination: View, LogDestination: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
let profileManager: ProfileManager
|
||||
|
||||
let isRestricted: Bool
|
||||
|
||||
@Binding
|
||||
var path: NavigationPath
|
||||
|
||||
@Binding
|
||||
var navigationRoute: AboutCoordinatorRoute?
|
||||
|
||||
let linkContent: (AboutCoordinatorRoute) -> LinkContent
|
||||
|
||||
let aboutDestination: (AboutCoordinatorRoute?) -> AboutDestination
|
||||
|
||||
let logDestination: (DebugLogRoute?) -> LogDestination
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
listView
|
||||
} detail: {
|
||||
aboutDestination(navigationRoute)
|
||||
.navigationDestination(for: AboutCoordinatorRoute.self, destination: aboutDestination)
|
||||
.navigationDestination(for: DebugLogRoute.self, destination: logDestination)
|
||||
.themeNavigationStack(closable: false, path: $path)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(Strings.Global.ok) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onLoad {
|
||||
navigationRoute = .links
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AboutContentView {
|
||||
var listView: some View {
|
||||
List(selection: $navigationRoute) {
|
||||
Section {
|
||||
linkContent(.links)
|
||||
linkContent(.credits)
|
||||
if !isRestricted {
|
||||
linkContent(.donate)
|
||||
}
|
||||
linkContent(.diagnostics)
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
Text(BundleConfiguration.mainVersionString)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.navigationTitle(Strings.Views.About.title)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,56 +0,0 @@
|
||||
//
|
||||
// AboutRouterView+macOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/26/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/>.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
import CommonLibrary
|
||||
import SwiftUI
|
||||
|
||||
extension AboutRouterView {
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
AboutView(
|
||||
profileManager: profileManager,
|
||||
navigationRoute: $navigationRoute
|
||||
)
|
||||
} detail: {
|
||||
pushDestination(for: navigationRoute)
|
||||
.navigationDestination(for: NavigationRoute.self, destination: pushDestination)
|
||||
.themeNavigationStack(closable: false, path: $path)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(Strings.Global.ok) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onLoad {
|
||||
navigationRoute = .links
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,51 +0,0 @@
|
||||
//
|
||||
// AboutView+macOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/27/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/>.
|
||||
//
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
extension AboutView {
|
||||
var listView: some View {
|
||||
List(selection: $navigationRoute) {
|
||||
Section {
|
||||
linksLink
|
||||
creditsLink
|
||||
if !iapManager.isRestricted {
|
||||
donateLink
|
||||
}
|
||||
diagnosticsLink
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
Text(BundleConfiguration.mainVersionString)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.navigationTitle(Strings.Views.About.title)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -27,6 +27,7 @@ import CommonLibrary
|
||||
import CommonUtils
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
import UILibrary
|
||||
|
||||
public struct AppCoordinator: View, AppCoordinatorConforming {
|
||||
|
||||
@ -99,7 +100,7 @@ extension AppCoordinator {
|
||||
|
||||
case migrateProfiles
|
||||
|
||||
case settings
|
||||
case preferences
|
||||
|
||||
var id: Int {
|
||||
switch self {
|
||||
@ -107,7 +108,7 @@ extension AppCoordinator {
|
||||
case .editProfile: return 2
|
||||
case .editProviderEntity: return 3
|
||||
case .migrateProfiles: return 4
|
||||
case .settings: return 5
|
||||
case .preferences: return 5
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,8 +191,8 @@ extension AppCoordinator {
|
||||
profileManager: profileManager,
|
||||
layout: $layout,
|
||||
isImporting: $isImporting,
|
||||
onSettings: {
|
||||
present(.settings)
|
||||
onPreferences: {
|
||||
present(.preferences)
|
||||
},
|
||||
onAbout: {
|
||||
present(.about)
|
||||
@ -207,7 +208,7 @@ extension AppCoordinator {
|
||||
func modalDestination(for item: ModalRoute?) -> some View {
|
||||
switch item {
|
||||
case .about:
|
||||
AboutRouterView(
|
||||
AboutCoordinator(
|
||||
profileManager: profileManager,
|
||||
tunnel: tunnel
|
||||
)
|
||||
@ -240,8 +241,8 @@ extension AppCoordinator {
|
||||
)
|
||||
.themeNavigationStack(closable: true, path: $migrationPath)
|
||||
|
||||
case .settings:
|
||||
SettingsView(profileManager: profileManager)
|
||||
case .preferences:
|
||||
PreferencesView(profileManager: profileManager)
|
||||
|
||||
default:
|
||||
EmptyView()
|
||||
|
@ -44,7 +44,7 @@ struct AppToolbar: ToolbarContent, SizeClassProviding {
|
||||
@Binding
|
||||
var isImporting: Bool
|
||||
|
||||
let onSettings: () -> Void
|
||||
let onPreferences: () -> Void
|
||||
|
||||
let onAbout: () -> Void
|
||||
|
||||
@ -81,8 +81,8 @@ private extension AppToolbar {
|
||||
)
|
||||
}
|
||||
|
||||
var settingsButton: some View {
|
||||
Button(action: onSettings) {
|
||||
var preferencesButton: some View {
|
||||
Button(action: onPreferences) {
|
||||
ThemeImage(.settings)
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ private extension AppToolbar {
|
||||
profileManager: .mock,
|
||||
layout: .constant(.list),
|
||||
isImporting: .constant(false),
|
||||
onSettings: {},
|
||||
onPreferences: {},
|
||||
onAbout: {},
|
||||
onMigrateProfiles: {},
|
||||
onNewProfile: { _ in }
|
||||
|
@ -265,7 +265,7 @@ private struct CardModifier: ViewModifier {
|
||||
#if os(iOS)
|
||||
content
|
||||
.padding(.vertical)
|
||||
#elseif os(macOS)
|
||||
#else
|
||||
content
|
||||
#endif
|
||||
|
||||
|
@ -0,0 +1,32 @@
|
||||
//
|
||||
// DebugLogRoute.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 11/23/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 Foundation
|
||||
|
||||
enum DebugLogRoute: Hashable {
|
||||
case app(title: String)
|
||||
|
||||
case tunnel(title: String, url: URL?)
|
||||
}
|
@ -102,8 +102,8 @@ struct DiagnosticsView: View {
|
||||
private extension DiagnosticsView {
|
||||
var liveLogSection: some View {
|
||||
Group {
|
||||
navLink(Strings.Views.Diagnostics.Rows.app, to: .appDebugLog(title: Strings.Views.Diagnostics.Rows.app))
|
||||
navLink(Strings.Views.Diagnostics.Rows.tunnel, to: .tunnelDebugLog(title: Strings.Views.Diagnostics.Rows.tunnel, url: nil))
|
||||
navLink(Strings.Views.Diagnostics.Rows.app, to: .app(title: Strings.Views.Diagnostics.Rows.app))
|
||||
navLink(Strings.Views.Diagnostics.Rows.tunnel, to: .tunnel(title: Strings.Views.Diagnostics.Rows.tunnel, url: nil))
|
||||
|
||||
Toggle(Strings.Views.Diagnostics.Rows.includePrivateData, isOn: $logsPrivateData)
|
||||
.onChange(of: logsPrivateData) {
|
||||
@ -156,13 +156,13 @@ private extension DiagnosticsView {
|
||||
func logView(for item: LogEntry) -> some View {
|
||||
ThemeRemovableItemRow(isEditing: true) {
|
||||
let dateString = dateFormatter.string(from: item.date)
|
||||
navLink(dateString, to: .tunnelDebugLog(title: dateString, url: item.url))
|
||||
navLink(dateString, to: .tunnel(title: dateString, url: item.url))
|
||||
} removeAction: {
|
||||
removeTunnelLog(at: item.url)
|
||||
}
|
||||
}
|
||||
|
||||
func navLink(_ title: String, to value: AboutRouterView.NavigationRoute) -> some View {
|
||||
func navLink(_ title: String, to value: DebugLogRoute) -> some View {
|
||||
NavigationLink(title, value: value)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// DebugLogView+iOS.swift
|
||||
// DebugLogContentView+iOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/31/24.
|
||||
@ -27,9 +27,11 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension DebugLogView {
|
||||
var contentView: some View {
|
||||
TextEditor(text: .constant(content))
|
||||
struct DebugLogContentView: View {
|
||||
let lines: [String]
|
||||
|
||||
var body: some View {
|
||||
TextEditor(text: .constant(lines.joined(separator: "\n")))
|
||||
.font(.caption)
|
||||
.monospaced()
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// DebugLogView+macOS.swift
|
||||
// DebugLogContentView+macOS.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/31/24.
|
||||
@ -27,10 +27,12 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension DebugLogView {
|
||||
var contentView: some View {
|
||||
struct DebugLogContentView: View {
|
||||
let lines: [String]
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(Array(currentLines.enumerated()), id: \.offset) {
|
||||
ForEach(Array(lines.enumerated()), id: \.offset) {
|
||||
Text($0.element)
|
||||
.monospaced()
|
||||
}
|
@ -104,7 +104,7 @@ private extension ProfileCoordinator {
|
||||
)
|
||||
.themeNavigationDetail()
|
||||
.themeNavigationStack(if: modally, path: $path)
|
||||
#elseif os(macOS)
|
||||
#else
|
||||
ProfileSplitView(
|
||||
profileEditor: profileEditor,
|
||||
moduleViewFactory: moduleViewFactory,
|
||||
|
@ -214,6 +214,10 @@ extension View {
|
||||
modifier(ThemeProgressViewModifier(isProgressing: isProgressing, isEmpty: isEmpty, emptyContent: emptyContent))
|
||||
}
|
||||
|
||||
public func themeTrailingValue(_ value: CustomStringConvertible?, truncationMode: Text.TruncationMode = .tail) -> some View {
|
||||
modifier(ThemeTrailingValueModifier(value: value, truncationMode: truncationMode))
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
public func themeWindow(width: CGFloat, height: CGFloat) -> some View {
|
||||
modifier(ThemeWindowModifier(size: .init(width: width, height: height)))
|
||||
@ -223,10 +227,6 @@ extension View {
|
||||
modifier(ThemePlainButtonModifier(action: action))
|
||||
}
|
||||
|
||||
public func themeTrailingValue(_ value: CustomStringConvertible?, truncationMode: Text.TruncationMode = .tail) -> some View {
|
||||
modifier(ThemeTrailingValueModifier(value: value, truncationMode: truncationMode))
|
||||
}
|
||||
|
||||
public func themeGridHeader(title: String?) -> some View {
|
||||
modifier(ThemeGridSectionModifier(title: title))
|
||||
}
|
||||
@ -479,16 +479,6 @@ struct ThemeProgressViewModifier<EmptyContent>: ViewModifier where EmptyContent:
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
|
||||
struct ThemeWindowModifier: ViewModifier {
|
||||
let size: CGSize
|
||||
}
|
||||
|
||||
struct ThemePlainButtonModifier: ViewModifier {
|
||||
let action: () -> Void
|
||||
}
|
||||
|
||||
struct ThemeTrailingValueModifier: ViewModifier {
|
||||
let value: CustomStringConvertible?
|
||||
|
||||
@ -497,7 +487,6 @@ struct ThemeTrailingValueModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
LabeledContent {
|
||||
if let value {
|
||||
Spacer()
|
||||
Text(value.description)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
@ -509,6 +498,16 @@ struct ThemeTrailingValueModifier: ViewModifier {
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
|
||||
struct ThemeWindowModifier: ViewModifier {
|
||||
let size: CGSize
|
||||
}
|
||||
|
||||
struct ThemePlainButtonModifier: ViewModifier {
|
||||
let action: () -> Void
|
||||
}
|
||||
|
||||
struct ThemeGridSectionModifier: ViewModifier {
|
||||
|
||||
@EnvironmentObject
|
||||
|
@ -27,8 +27,11 @@ import CommonLibrary
|
||||
import CommonUtils
|
||||
import SwiftUI
|
||||
|
||||
struct CreditsView: View {
|
||||
var body: some View {
|
||||
public struct CreditsView: View {
|
||||
public init() {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
GenericCreditsView(
|
||||
credits: Self.credits,
|
||||
licensesHeader: Strings.Views.About.Credits.licenses,
|
@ -27,7 +27,7 @@ import CommonLibrary
|
||||
import CommonUtils
|
||||
import SwiftUI
|
||||
|
||||
struct DonateView: View {
|
||||
public struct DonateView: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var iapManager: IAPManager
|
||||
@ -50,7 +50,10 @@ struct DonateView: View {
|
||||
@StateObject
|
||||
private var errorHandler: ErrorHandler = .default()
|
||||
|
||||
var body: some View {
|
||||
public init() {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
donationsView
|
||||
.themeProgress(if: isFetchingProducts)
|
||||
.navigationTitle(title)
|
@ -27,8 +27,11 @@ import CommonLibrary
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
struct LinksView: View {
|
||||
var body: some View {
|
||||
public struct LinksView: View {
|
||||
public init() {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Form {
|
||||
supportSection
|
||||
webSection
|
@ -28,55 +28,21 @@ import CommonUtils
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
extension DebugLogView {
|
||||
static func withApp(parameters: Constants.Log) -> DebugLogView {
|
||||
DebugLogView {
|
||||
PassepartoutConfiguration.shared.currentLog(parameters: parameters)
|
||||
}
|
||||
}
|
||||
public struct DebugLogView<Content>: View where Content: View {
|
||||
private let fetchLines: () async -> [String]
|
||||
|
||||
static func withTunnel(_ tunnel: ExtendedTunnel, parameters: Constants.Log) -> DebugLogView {
|
||||
DebugLogView {
|
||||
await tunnel.currentLog(parameters: parameters)
|
||||
}
|
||||
}
|
||||
|
||||
static func withURL(_ url: URL) -> DebugLogView {
|
||||
DebugLogView {
|
||||
do {
|
||||
return try String(contentsOf: url)
|
||||
.split(separator: "\n")
|
||||
.map(String.init)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DebugLogView: View {
|
||||
let fetchLines: () async -> [String]
|
||||
private let content: ([String]) -> Content
|
||||
|
||||
@State
|
||||
private(set) var currentLines: [String] = []
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if !currentLines.isEmpty {
|
||||
contentView
|
||||
} else {
|
||||
Text(Strings.Global.noContent)
|
||||
.themeEmptyMessage()
|
||||
public var body: some View {
|
||||
content(currentLines)
|
||||
.themeEmpty(if: currentLines.isEmpty, message: Strings.Global.noContent)
|
||||
.toolbar(content: toolbarContent)
|
||||
.task {
|
||||
currentLines = await fetchLines()
|
||||
}
|
||||
}
|
||||
.toolbar(content: toolbarContent)
|
||||
.task {
|
||||
currentLines = await fetchLines()
|
||||
}
|
||||
}
|
||||
|
||||
var content: String {
|
||||
currentLines.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,7 +58,7 @@ private extension DebugLogView {
|
||||
|
||||
var copyButton: some View {
|
||||
Button {
|
||||
Utils.copyToPasteboard(content)
|
||||
Utils.copyToPasteboard(currentLines.joined(separator: "\n"))
|
||||
} label: {
|
||||
ThemeImage(.copy)
|
||||
}
|
||||
@ -104,3 +70,47 @@ private extension DebugLogView {
|
||||
// ShareLink(item: content)
|
||||
// }
|
||||
}
|
||||
|
||||
// MARK: - Shortcuts
|
||||
|
||||
extension DebugLogView {
|
||||
public init(
|
||||
withAppParameters parameters: Constants.Log,
|
||||
content: @escaping ([String]) -> Content
|
||||
) {
|
||||
self.init {
|
||||
PassepartoutConfiguration.shared.currentLog(parameters: parameters)
|
||||
} content: {
|
||||
content($0)
|
||||
}
|
||||
}
|
||||
|
||||
public init(
|
||||
withTunnel tunnel: ExtendedTunnel,
|
||||
parameters: Constants.Log,
|
||||
content: @escaping ([String]) -> Content
|
||||
) {
|
||||
self.init {
|
||||
await tunnel.currentLog(parameters: parameters)
|
||||
} content: {
|
||||
content($0)
|
||||
}
|
||||
}
|
||||
|
||||
public init(
|
||||
withURL url: URL,
|
||||
content: @escaping ([String]) -> Content
|
||||
) {
|
||||
self.init {
|
||||
do {
|
||||
return try String(contentsOf: url)
|
||||
.split(separator: "\n")
|
||||
.map(String.init)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
} content: {
|
||||
content($0)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// SettingsSectionGroup.swift
|
||||
// PreferencesGroup.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 10/3/24.
|
||||
@ -28,27 +28,32 @@ import CommonUtils
|
||||
import PassepartoutKit
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsSectionGroup: View {
|
||||
let profileManager: ProfileManager
|
||||
public struct PreferencesGroup: View {
|
||||
|
||||
#if os(iOS)
|
||||
@AppStorage(AppPreference.locksInBackground.key)
|
||||
private var locksInBackground = false
|
||||
#else
|
||||
#elseif os(macOS)
|
||||
@EnvironmentObject
|
||||
private var settings: MacSettingsModel
|
||||
#endif
|
||||
|
||||
private let profileManager: ProfileManager
|
||||
|
||||
@State
|
||||
private var isConfirmingEraseiCloud = false
|
||||
|
||||
@State
|
||||
private var isErasingiCloud = false
|
||||
|
||||
var body: some View {
|
||||
public init(profileManager: ProfileManager) {
|
||||
self.profileManager = profileManager
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
#if os(iOS)
|
||||
lockInBackgroundToggle
|
||||
#else
|
||||
#elseif os(macOS)
|
||||
launchesOnLoginToggle
|
||||
keepsInMenuToggle
|
||||
#endif
|
||||
@ -56,13 +61,13 @@ struct SettingsSectionGroup: View {
|
||||
}
|
||||
}
|
||||
|
||||
private extension SettingsSectionGroup {
|
||||
private extension PreferencesGroup {
|
||||
#if os(iOS)
|
||||
var lockInBackgroundToggle: some View {
|
||||
Toggle(Strings.Views.Settings.locksInBackground, isOn: $locksInBackground)
|
||||
.themeSectionWithSingleRow(footer: Strings.Views.Settings.LocksInBackground.footer)
|
||||
}
|
||||
#else
|
||||
#elseif os(macOS)
|
||||
var launchesOnLoginToggle: some View {
|
||||
Toggle(Strings.Views.Settings.launchesOnLogin, isOn: $settings.launchesOnLogin)
|
||||
.themeSectionWithSingleRow(footer: Strings.Views.Settings.LaunchesOnLogin.footer)
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// SettingsView.swift
|
||||
// PreferencesView.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 9/28/24.
|
||||
@ -26,8 +26,8 @@
|
||||
import CommonLibrary
|
||||
import SwiftUI
|
||||
|
||||
public struct SettingsView: View {
|
||||
let profileManager: ProfileManager
|
||||
public struct PreferencesView: View {
|
||||
private let profileManager: ProfileManager
|
||||
|
||||
@State
|
||||
private var path = NavigationPath()
|
||||
@ -38,7 +38,7 @@ public struct SettingsView: View {
|
||||
|
||||
public var body: some View {
|
||||
Form {
|
||||
SettingsSectionGroup(profileManager: profileManager)
|
||||
PreferencesGroup(profileManager: profileManager)
|
||||
}
|
||||
.themeForm()
|
||||
.navigationTitle(Strings.Global.settings)
|
@ -81,7 +81,7 @@ extension ProfileManagerTests {
|
||||
XCTAssertTrue(sut.hasProfiles)
|
||||
XCTAssertEqual(sut.previews.count, 2)
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Search") {
|
||||
$0.search(byName: "ar")
|
||||
} until: {
|
||||
$0.previews.count == 1
|
||||
@ -165,7 +165,7 @@ extension ProfileManagerTests {
|
||||
XCTAssertFalse(sut.hasProfiles)
|
||||
|
||||
let profile = newProfile()
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Save") {
|
||||
try await $0.save(profile)
|
||||
} until: {
|
||||
$0.hasProfiles
|
||||
@ -187,7 +187,7 @@ extension ProfileManagerTests {
|
||||
builder.name = "newName"
|
||||
let renamedProfile = try builder.tryBuild()
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Save") {
|
||||
try await $0.save(renamedProfile)
|
||||
} until: {
|
||||
$0.previews.first?.name == renamedProfile.name
|
||||
@ -259,7 +259,7 @@ extension ProfileManagerTests {
|
||||
XCTAssertTrue(sut.isReady)
|
||||
XCTAssertTrue(sut.hasProfiles)
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Remove") {
|
||||
await $0.remove(withId: profile.id)
|
||||
} until: {
|
||||
!$0.hasProfiles
|
||||
@ -349,19 +349,19 @@ extension ProfileManagerTests {
|
||||
|
||||
try await waitForReady(sut)
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Duplicate 1") {
|
||||
try await $0.duplicate(profileWithId: profile.id)
|
||||
} until: {
|
||||
$0.previews.count == 2
|
||||
}
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Duplicate 2") {
|
||||
try await $0.duplicate(profileWithId: profile.id)
|
||||
} until: {
|
||||
$0.previews.count == 3
|
||||
}
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Duplicate 3") {
|
||||
try await $0.duplicate(profileWithId: profile.id)
|
||||
} until: {
|
||||
$0.previews.count == 4
|
||||
@ -396,7 +396,7 @@ extension ProfileManagerTests {
|
||||
remoteRepository
|
||||
})
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Remote import") {
|
||||
try await $0.observeLocal()
|
||||
try await $0.observeRemote(true)
|
||||
} until: {
|
||||
@ -433,7 +433,7 @@ extension ProfileManagerTests {
|
||||
remoteRepository
|
||||
})
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Remote import") {
|
||||
try await $0.observeLocal()
|
||||
try await $0.observeRemote(true)
|
||||
} until: {
|
||||
@ -485,9 +485,9 @@ extension ProfileManagerTests {
|
||||
observeRemoteImport(sut) {
|
||||
didImport = true
|
||||
}
|
||||
try await waitForReady(sut)
|
||||
try await wait(sut) { _ in
|
||||
//
|
||||
try await wait(sut, "Remote import") {
|
||||
try await $0.observeLocal()
|
||||
try await $0.observeRemote(true)
|
||||
} until: { _ in
|
||||
didImport
|
||||
}
|
||||
@ -526,9 +526,9 @@ extension ProfileManagerTests {
|
||||
observeRemoteImport(sut) {
|
||||
didImport = true
|
||||
}
|
||||
try await waitForReady(sut)
|
||||
try await wait(sut) { _ in
|
||||
//
|
||||
try await wait(sut, "Remote import") {
|
||||
try await $0.observeLocal()
|
||||
try await $0.observeRemote(true)
|
||||
} until: { _ in
|
||||
didImport
|
||||
}
|
||||
@ -560,7 +560,7 @@ extension ProfileManagerTests {
|
||||
remoteRepository
|
||||
})
|
||||
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Remote import") {
|
||||
try await $0.observeLocal()
|
||||
try await $0.observeRemote(true)
|
||||
} until: {
|
||||
@ -574,13 +574,13 @@ extension ProfileManagerTests {
|
||||
let fp2 = UUID()
|
||||
let fp3 = UUID()
|
||||
|
||||
try await wait(sut) { _ in
|
||||
try await wait(sut, "Multiple imports") { _ in
|
||||
remoteRepository.profiles = [
|
||||
newProfile("remote1", id: r1)
|
||||
]
|
||||
remoteRepository.profiles = [
|
||||
newProfile("remote1", id: r1),
|
||||
newProfile("remote2", id: r2),
|
||||
newProfile("remote2", id: r2)
|
||||
]
|
||||
remoteRepository.profiles = [
|
||||
newProfile("remote1", id: r1, fingerprint: fp1),
|
||||
@ -623,13 +623,13 @@ extension ProfileManagerTests {
|
||||
observeRemoteImport(sut) {
|
||||
didImport = true
|
||||
}
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Remote import") {
|
||||
try await $0.observeLocal()
|
||||
try await $0.observeRemote(true)
|
||||
} until: {
|
||||
$0.previews.count == 1
|
||||
}
|
||||
try await wait(sut) { _ in
|
||||
try await wait(sut, "Remote reset") { _ in
|
||||
remoteRepository.profiles = []
|
||||
} until: { _ in
|
||||
didImport
|
||||
@ -652,13 +652,13 @@ extension ProfileManagerTests {
|
||||
observeRemoteImport(sut) {
|
||||
didImport = true
|
||||
}
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Remote import") {
|
||||
try await $0.observeLocal()
|
||||
try await $0.observeRemote(true)
|
||||
} until: {
|
||||
$0.previews.count == 1
|
||||
}
|
||||
try await wait(sut) { _ in
|
||||
try await wait(sut, "Remote reset") { _ in
|
||||
remoteRepository.profiles = []
|
||||
} until: { _ in
|
||||
didImport
|
||||
@ -684,7 +684,7 @@ private extension ProfileManagerTests {
|
||||
}
|
||||
|
||||
func waitForReady(_ sut: ProfileManager, importingRemote: Bool = true) async throws {
|
||||
try await wait(sut) {
|
||||
try await wait(sut, "Ready") {
|
||||
try await $0.observeLocal()
|
||||
try await $0.observeRemote(importingRemote)
|
||||
} until: {
|
||||
@ -705,10 +705,11 @@ private extension ProfileManagerTests {
|
||||
|
||||
func wait(
|
||||
_ sut: ProfileManager,
|
||||
_ description: String,
|
||||
after action: (ProfileManager) async throws -> Void,
|
||||
until condition: @escaping (ProfileManager) -> Bool
|
||||
) async throws {
|
||||
let exp = expectation(description: "Wait")
|
||||
let exp = expectation(description: description)
|
||||
var wasMet = false
|
||||
sut.objectWillChange
|
||||
.sink {
|
||||
|
Loading…
Reference in New Issue
Block a user