Refactor ViewModel
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
open class BaseViewModel: ObservableObject {
|
||||||
|
@Published public var alert: Alert?
|
||||||
|
@Published public var isLoading = false
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public func showError(_ error: Error) {
|
||||||
|
alert = Alert(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func execute(_ operation: () async throws -> Void) async {
|
||||||
|
do {
|
||||||
|
try await operation()
|
||||||
|
} catch {
|
||||||
|
alert = Alert(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func executeOnBackground(_ operation: @escaping @Sendable () async throws -> Void) async {
|
||||||
|
do {
|
||||||
|
try await operation()
|
||||||
|
} catch {
|
||||||
|
await MainActor.run {
|
||||||
|
alert = Alert(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ public struct NavigationButtonsView: View {
|
|||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
private var tvOSBody: some View {
|
private var tvOSBody: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if showConnectionsButton {
|
if showConnectionsButton {
|
||||||
Button {
|
Button {
|
||||||
onConnectionsTap()
|
onConnectionsTap()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public func NavigationStackCompat(@ViewBuilder content: () -> some View) -> some View {
|
public func NavigationStackCompat(@ViewBuilder content: () -> some View) -> some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if #available(iOS 17.0, macOS 13.0, tvOS 17.0, *) {
|
if #available(iOS 17.0, macOS 13.0, tvOS 17.0, *) {
|
||||||
// view not updating in iOS 16, but why?
|
// view not updating in iOS 16, but why?
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public func RequestReviewButton(label: @escaping () -> some View) -> some View {
|
public func RequestReviewButton(label: @escaping () -> some View) -> some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if #available(iOS 16.0, macOS 13.0, visionOS 1.0, *) {
|
if #available(iOS 16.0, macOS 13.0, visionOS 1.0, *) {
|
||||||
RequestReviewButton0(label: label)
|
RequestReviewButton0(label: label)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
public func viewBuilder(@ViewBuilder _ builder: () -> some View) -> some View {
|
|
||||||
builder()
|
|
||||||
}
|
|
||||||
@@ -2,22 +2,12 @@ import SwiftUI
|
|||||||
|
|
||||||
public extension View {
|
public extension View {
|
||||||
func onChangeCompat(of value: some Equatable, _ action: @escaping () -> Void) -> some View {
|
func onChangeCompat(of value: some Equatable, _ action: @escaping () -> Void) -> some View {
|
||||||
// if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {
|
|
||||||
// return onChange(of: value, action)
|
|
||||||
// } else {
|
|
||||||
onChange(of: value) { _ in
|
onChange(of: value) { _ in
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func onChangeCompat<V>(of value: V, _ action: @escaping (_ newValue: V) -> Void) -> some View where V: Equatable {
|
func onChangeCompat<V>(of value: V, _ action: @escaping (_ newValue: V) -> Void) -> some View where V: Equatable {
|
||||||
// if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {
|
|
||||||
// return onChange(of: value) { _, newValue in
|
|
||||||
// action(newValue)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
onChange(of: value, perform: action)
|
onChange(of: value, perform: action)
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public class ConnectionListViewModel: ObservableObject {
|
public class ConnectionListViewModel: BaseViewModel {
|
||||||
@Published public var isLoading = true
|
|
||||||
@Published public var connections: [Connection] = []
|
@Published public var connections: [Connection] = []
|
||||||
@Published public var searchText = ""
|
@Published public var searchText = ""
|
||||||
@Published public var alert: Alert?
|
|
||||||
@Published public var connectionStateFilter: ConnectionStateFilter {
|
@Published public var connectionStateFilter: ConnectionStateFilter {
|
||||||
didSet {
|
didSet {
|
||||||
saveStateFilterTask?.cancel()
|
saveStateFilterTask?.cancel()
|
||||||
@@ -30,9 +28,11 @@ public class ConnectionListViewModel: ObservableObject {
|
|||||||
private var saveStateFilterTask: Task<Void, Never>?
|
private var saveStateFilterTask: Task<Void, Never>?
|
||||||
private var saveSortTask: Task<Void, Never>?
|
private var saveSortTask: Task<Void, Never>?
|
||||||
|
|
||||||
public init() {
|
public override init() {
|
||||||
connectionStateFilter = .active
|
connectionStateFilter = .active
|
||||||
connectionSort = .byDate
|
connectionSort = .byDate
|
||||||
|
super.init()
|
||||||
|
isLoading = true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func connect() {
|
public func connect() {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import SwiftUI
|
|||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@EnvironmentObject private var profile: ExtensionProfile
|
@EnvironmentObject private var profile: ExtensionProfile
|
||||||
@StateObject private var coordinator = DashboardCoordinator()
|
@StateObject private var coordinator = DashboardViewModel()
|
||||||
@State private var cardConfigurationVersion = 0
|
@State private var cardConfigurationVersion = 0
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
@State private var showCardManagement = false
|
@State private var showCardManagement = false
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public struct ExtensionStatusView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var body0: some View {
|
public var body0: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
VStack {
|
VStack {
|
||||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) {
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) {
|
||||||
if ApplicationLibrary.inPreview {
|
if ApplicationLibrary.inPreview {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public struct StartStopButton: View {
|
|||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if ApplicationLibrary.inPreview {
|
if ApplicationLibrary.inPreview {
|
||||||
Button {} label: {
|
Button {} label: {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public extension DashboardPage {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func contentView(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding<Int64>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>, _ cardConfigurationVersion: Int) -> some View {
|
func contentView(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding<Int64>, _ systemProxyAvailable: Binding<Bool>, _ systemProxyEnabled: Binding<Bool>, _ cardConfigurationVersion: Int) -> some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
switch self {
|
switch self {
|
||||||
case .overview:
|
case .overview:
|
||||||
OverviewView(profileList, selectedProfileID, systemProxyAvailable, systemProxyEnabled, cardConfigurationVersion: cardConfigurationVersion)
|
OverviewView(profileList, selectedProfileID, systemProxyAvailable, systemProxyEnabled, cardConfigurationVersion: cardConfigurationVersion)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public struct DashboardView: View {
|
|||||||
@Environment(\.importProfile) private var importProfile
|
@Environment(\.importProfile) private var importProfile
|
||||||
@Environment(\.importRemoteProfile) private var importRemoteProfile
|
@Environment(\.importRemoteProfile) private var importRemoteProfile
|
||||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@StateObject private var coordinator = DashboardCoordinator()
|
@StateObject private var coordinator = DashboardViewModel()
|
||||||
@State private var importRemoteProfileRequest: NewProfileView.ImportRequest?
|
@State private var importRemoteProfileRequest: NewProfileView.ImportRequest?
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
|
|||||||
+5
-4
@@ -5,11 +5,9 @@ import NetworkExtension
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class DashboardCoordinator: ObservableObject {
|
public final class DashboardViewModel: BaseViewModel {
|
||||||
@Published public var isLoading = true
|
|
||||||
@Published public var profileList: [ProfilePreview] = []
|
@Published public var profileList: [ProfilePreview] = []
|
||||||
@Published public var selectedProfileID: Int64 = 0
|
@Published public var selectedProfileID: Int64 = 0
|
||||||
@Published public var alert: Alert?
|
|
||||||
@Published public var selection = DashboardPage.overview
|
@Published public var selection = DashboardPage.overview
|
||||||
@Published public var systemProxyAvailable = false
|
@Published public var systemProxyAvailable = false
|
||||||
@Published public var systemProxyEnabled = false
|
@Published public var systemProxyEnabled = false
|
||||||
@@ -22,7 +20,10 @@ public final class DashboardCoordinator: ObservableObject {
|
|||||||
public var onEmptyProfilesChange: ((Bool) -> Void)?
|
public var onEmptyProfilesChange: ((Bool) -> Void)?
|
||||||
private var openURL: ((URL) -> Void)?
|
private var openURL: ((URL) -> Void)?
|
||||||
|
|
||||||
public init() {}
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
isLoading = true
|
||||||
|
}
|
||||||
|
|
||||||
public func setOpenURL(_ openURL: @escaping (URL) -> Void) {
|
public func setOpenURL(_ openURL: @escaping (URL) -> Void) {
|
||||||
self.openURL = openURL
|
self.openURL = openURL
|
||||||
@@ -7,7 +7,7 @@ import SwiftUI
|
|||||||
public struct OverviewView: View {
|
public struct OverviewView: View {
|
||||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@EnvironmentObject private var profile: ExtensionProfile
|
@EnvironmentObject private var profile: ExtensionProfile
|
||||||
@StateObject private var coordinator = OverviewCoordinator()
|
@StateObject private var coordinator = OverviewViewModel()
|
||||||
@StateObject private var configuration = DashboardCardConfiguration()
|
@StateObject private var configuration = DashboardCardConfiguration()
|
||||||
|
|
||||||
@Binding private var profileList: [ProfilePreview]
|
@Binding private var profileList: [ProfilePreview]
|
||||||
|
|||||||
+1
-4
@@ -4,12 +4,9 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class OverviewCoordinator: ObservableObject {
|
public final class OverviewViewModel: BaseViewModel {
|
||||||
@Published public var alert: Alert?
|
|
||||||
@Published public var reasserting = false
|
@Published public var reasserting = false
|
||||||
|
|
||||||
public init() {}
|
|
||||||
|
|
||||||
public func switchProfile(_ profileID: Int64, profile: ExtensionProfile, environments: ExtensionEnvironments) async {
|
public func switchProfile(_ profileID: Int64, profile: ExtensionProfile, environments: ExtensionEnvironments) async {
|
||||||
await SharedPreferences.selectedProfileID.set(profileID)
|
await SharedPreferences.selectedProfileID.set(profileID)
|
||||||
environments.selectedProfileUpdate.send()
|
environments.selectedProfileUpdate.send()
|
||||||
@@ -3,14 +3,15 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public class GroupListViewModel: ObservableObject {
|
public class GroupListViewModel: BaseViewModel {
|
||||||
@Published public var isLoading = true
|
|
||||||
@Published public var groups: [OutboundGroup] = []
|
@Published public var groups: [OutboundGroup] = []
|
||||||
@Published public var alert: Alert?
|
|
||||||
|
|
||||||
private var pendingSelections: [String: String] = [:]
|
private var pendingSelections: [String: String] = [:]
|
||||||
|
|
||||||
public init() {}
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
isLoading = true
|
||||||
|
}
|
||||||
|
|
||||||
public func connect() {
|
public func connect() {
|
||||||
if ApplicationLibrary.inPreview {
|
if ApplicationLibrary.inPreview {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public extension NavigationPage {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
var contentView: some View {
|
var contentView: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
switch self {
|
switch self {
|
||||||
case .dashboard:
|
case .dashboard:
|
||||||
DashboardView()
|
DashboardView()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
@Environment(\.profileEditor) private var profileEditor
|
@Environment(\.profileEditor) private var profileEditor
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView().onAppear {
|
||||||
Task {
|
Task {
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var defaultEditorView: some View {
|
private var defaultEditorView: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if readOnly {
|
if readOnly {
|
||||||
TextEditor(text: .constant(viewModel.profileContent))
|
TextEditor(text: .constant(viewModel.profileContent))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,17 +4,17 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class EditProfileContentViewModel: ObservableObject {
|
public final class EditProfileContentViewModel: BaseViewModel {
|
||||||
@Published public var isLoading = true
|
|
||||||
@Published public var profile: Profile?
|
@Published public var profile: Profile?
|
||||||
@Published public var profileContent = ""
|
@Published public var profileContent = ""
|
||||||
@Published public var isChanged = false
|
@Published public var isChanged = false
|
||||||
@Published public var alert: Alert?
|
|
||||||
|
|
||||||
private let profileID: Int64?
|
private let profileID: Int64?
|
||||||
|
|
||||||
public init(profileID: Int64?) {
|
public init(profileID: Int64?) {
|
||||||
self.profileID = profileID
|
self.profileID = profileID
|
||||||
|
super.init()
|
||||||
|
isLoading = true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func markAsChanged() {
|
public func markAsChanged() {
|
||||||
|
|||||||
@@ -3,15 +3,11 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class EditProfileViewModel: ObservableObject {
|
public final class EditProfileViewModel: BaseViewModel {
|
||||||
@Published public var isLoading = false
|
|
||||||
@Published public var isChanged = false
|
@Published public var isChanged = false
|
||||||
@Published public var alert: Alert?
|
|
||||||
@Published public var shareLinkPresented = false
|
@Published public var shareLinkPresented = false
|
||||||
@Published public var shareLinkText: String?
|
@Published public var shareLinkText: String?
|
||||||
|
|
||||||
public init() {}
|
|
||||||
|
|
||||||
public func markAsChanged() {
|
public func markAsChanged() {
|
||||||
isChanged = true
|
isChanged = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,13 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class ImportProfileViewModel: ObservableObject {
|
public final class ImportProfileViewModel: BaseViewModel {
|
||||||
@Published public var isLoading = false
|
|
||||||
@Published public var selected = false
|
@Published public var selected = false
|
||||||
@Published public var alert: Alert?
|
|
||||||
@Published public var connection: NWConnection?
|
@Published public var connection: NWConnection?
|
||||||
@Published public var socket: NWSocket?
|
@Published public var socket: NWSocket?
|
||||||
@Published public var profiles: [LibboxProfilePreview]?
|
@Published public var profiles: [LibboxProfilePreview]?
|
||||||
@Published public var isImporting = false
|
@Published public var isImporting = false
|
||||||
|
|
||||||
public init() {}
|
|
||||||
|
|
||||||
public func reset() {
|
public func reset() {
|
||||||
if let connection {
|
if let connection {
|
||||||
connection.stateUpdateHandler = nil
|
connection.stateUpdateHandler = nil
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public struct NewProfileView: View {
|
|||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
#endif
|
#endif
|
||||||
viewBuilder {
|
Group {
|
||||||
if viewModel.fileImport {
|
if viewModel.fileImport {
|
||||||
HStack {
|
HStack {
|
||||||
Text("File Path")
|
Text("File Path")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class NewProfileViewModel: ObservableObject {
|
public final class NewProfileViewModel: BaseViewModel {
|
||||||
@Published public var isSaving = false
|
@Published public var isSaving = false
|
||||||
@Published public var profileName = ""
|
@Published public var profileName = ""
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@@ -18,9 +18,9 @@ public final class NewProfileViewModel: ObservableObject {
|
|||||||
@Published public var autoUpdate = true
|
@Published public var autoUpdate = true
|
||||||
@Published public var autoUpdateInterval: Int32 = 60
|
@Published public var autoUpdateInterval: Int32 = 60
|
||||||
@Published public var pickerPresented = false
|
@Published public var pickerPresented = false
|
||||||
@Published public var alert: Alert?
|
|
||||||
|
|
||||||
public init(importRequest: NewProfileView.ImportRequest? = nil) {
|
public init(importRequest: NewProfileView.ImportRequest? = nil) {
|
||||||
|
super.init()
|
||||||
if let importRequest {
|
if let importRequest {
|
||||||
profileName = importRequest.name
|
profileName = importRequest.name
|
||||||
profileType = .remote
|
profileType = .remote
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public struct ProfileView: View {
|
|||||||
} else {
|
} else {
|
||||||
List {
|
List {
|
||||||
ForEach(viewModel.profileList, id: \.id) { profile in
|
ForEach(viewModel.profileList, id: \.id) { profile in
|
||||||
viewBuilder {
|
Group {
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
if viewModel.editMode.isEditing == true {
|
if viewModel.editMode.isEditing == true {
|
||||||
Text(profile.name)
|
Text(profile.name)
|
||||||
@@ -182,7 +182,7 @@ public struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var draggableBody: some View {
|
private var draggableBody: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
FormNavigationLink {
|
FormNavigationLink {
|
||||||
EditProfileView().environmentObject(profile.origin)
|
EditProfileView().environmentObject(profile.origin)
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import Library
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public class ProfileViewModel: ObservableObject {
|
public class ProfileViewModel: BaseViewModel {
|
||||||
@Published public var importRemoteProfileRequest: NewProfileView.ImportRequest?
|
@Published public var importRemoteProfileRequest: NewProfileView.ImportRequest?
|
||||||
@Published public var importRemoteProfilePresented = false
|
@Published public var importRemoteProfilePresented = false
|
||||||
@Published public var isLoading = true
|
|
||||||
@Published public var isUpdating = false
|
@Published public var isUpdating = false
|
||||||
@Published public var alert: Alert?
|
|
||||||
@Published public var profileList: [ProfilePreview] = []
|
@Published public var profileList: [ProfilePreview] = []
|
||||||
|
|
||||||
#if os(iOS) || os(tvOS)
|
#if os(iOS) || os(tvOS)
|
||||||
@@ -18,7 +16,10 @@ public class ProfileViewModel: ObservableObject {
|
|||||||
|
|
||||||
private weak var environments: ExtensionEnvironments?
|
private weak var environments: ExtensionEnvironments?
|
||||||
|
|
||||||
public init() {}
|
public override init() {
|
||||||
|
super.init()
|
||||||
|
isLoading = true
|
||||||
|
}
|
||||||
|
|
||||||
public func setEnvironments(_ environments: ExtensionEnvironments) {
|
public func setEnvironments(_ environments: ExtensionEnvironments) {
|
||||||
self.environments = environments
|
self.environments = environments
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public struct CoreView: View {
|
|||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView().onAppear {
|
||||||
Task {
|
Task {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public struct AppView: View {
|
|||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView().onAppear {
|
||||||
Task {
|
Task {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public struct OnDemandRulesView: View {
|
|||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView().onAppear {
|
||||||
Task.detached {
|
Task.detached {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct PacketTunnelView: View {
|
|||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
var body: some View {
|
var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView().onAppear {
|
||||||
Task.detached {
|
Task.detached {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public struct ProfileOverrideView: View {
|
|||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView().onAppear {
|
||||||
Task.detached {
|
Task.detached {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public struct ServiceLogView: View {
|
|||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView().onAppear {
|
||||||
Task {
|
Task {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public struct SettingView: View {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
var contentView: some View {
|
var contentView: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
switch self {
|
switch self {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
case .app:
|
case .app:
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ public struct MenuView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView().onAppear {
|
ProgressView().onAppear {
|
||||||
Task {
|
Task {
|
||||||
|
|||||||
+55
-86
@@ -37,46 +37,28 @@ struct MainView: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var tabViewContent: some View {
|
private var tabViewContent: some View {
|
||||||
if shouldShowBottomAccessory {
|
if shouldShowBottomAccessory {
|
||||||
TabView(selection: $selection) {
|
baseTabView
|
||||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
.tabViewBottomAccessory {
|
||||||
NavigationStackCompat {
|
HStack(spacing: 12) {
|
||||||
page.contentView
|
if let profile = environments.extensionProfile {
|
||||||
.navigationTitle(page.title)
|
StatusText(profile: profile)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
NavigationButtonsView(
|
||||||
|
showGroupsButton: buttonState.showGroupsButton,
|
||||||
|
showConnectionsButton: buttonState.showConnectionsButton,
|
||||||
|
groupsCount: buttonState.groupsCount,
|
||||||
|
connectionsCount: buttonState.connectionsCount,
|
||||||
|
onGroupsTap: { showGroups = true },
|
||||||
|
onConnectionsTap: { showConnections = true }
|
||||||
|
)
|
||||||
|
Divider()
|
||||||
|
StartStopButton()
|
||||||
}
|
}
|
||||||
.tag(page)
|
.padding(.horizontal)
|
||||||
.tabItem { page.label }
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.tabViewBottomAccessory {
|
|
||||||
HStack(spacing: 12) {
|
|
||||||
if let profile = environments.extensionProfile {
|
|
||||||
StatusText(profile: profile)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
}
|
|
||||||
NavigationButtonsView(
|
|
||||||
showGroupsButton: buttonState.showGroupsButton,
|
|
||||||
showConnectionsButton: buttonState.showConnectionsButton,
|
|
||||||
groupsCount: buttonState.groupsCount,
|
|
||||||
connectionsCount: buttonState.connectionsCount,
|
|
||||||
onGroupsTap: { showGroups = true },
|
|
||||||
onConnectionsTap: { showConnections = true }
|
|
||||||
)
|
|
||||||
Divider()
|
|
||||||
StartStopButton()
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
TabView(selection: $selection) {
|
baseTabView
|
||||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
|
||||||
NavigationStackCompat {
|
|
||||||
page.contentView
|
|
||||||
.navigationTitle(page.title)
|
|
||||||
}
|
|
||||||
.tag(page)
|
|
||||||
.tabItem { page.label }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,25 +70,27 @@ struct MainView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var baseTabView: some View {
|
||||||
|
TabView(selection: $selection) {
|
||||||
|
ForEach(NavigationPage.allCases, id: \.self) { page in
|
||||||
|
NavigationStackCompat {
|
||||||
|
page.contentView
|
||||||
|
.navigationTitle(page.title)
|
||||||
|
}
|
||||||
|
.tag(page)
|
||||||
|
.tabItem { page.label }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var mainBody: some View {
|
private var mainBody: some View {
|
||||||
viewBuilder {
|
Group {
|
||||||
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
if #available(iOS 26.0, *), !Variant.debugNoIOS26 {
|
||||||
tabViewContent
|
tabViewContent
|
||||||
.onAppear {
|
.onAppear {
|
||||||
environments.postReload()
|
|
||||||
updateButtonVisibility()
|
updateButtonVisibility()
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
|
||||||
.onChangeCompat(of: scenePhase) { newValue in
|
|
||||||
if newValue == .active {
|
|
||||||
environments.postReload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChangeCompat(of: selection) { newValue in
|
|
||||||
if newValue == .logs {
|
|
||||||
environments.connect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onReceive(environments.commandClient.$groups) { _ in
|
.onReceive(environments.commandClient.$groups) { _ in
|
||||||
updateButtonVisibility()
|
updateButtonVisibility()
|
||||||
}
|
}
|
||||||
@@ -125,12 +109,6 @@ struct MainView: View {
|
|||||||
.onReceive(environments.$emptyProfiles) { _ in
|
.onReceive(environments.$emptyProfiles) { _ in
|
||||||
updateButtonVisibility()
|
updateButtonVisibility()
|
||||||
}
|
}
|
||||||
.environment(\.selection, $selection)
|
|
||||||
.environment(\.importProfile, $importProfile)
|
|
||||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
|
||||||
.environment(\.profileEditor, profileEditor)
|
|
||||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
|
||||||
.onOpenURL(perform: openURL)
|
|
||||||
.sheet(isPresented: $showGroups) {
|
.sheet(isPresented: $showGroups) {
|
||||||
GroupsSheetContent()
|
GroupsSheetContent()
|
||||||
}
|
}
|
||||||
@@ -138,38 +116,29 @@ struct MainView: View {
|
|||||||
ConnectionsSheetContent()
|
ConnectionsSheetContent()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TabView(selection: $selection) {
|
baseTabView
|
||||||
ForEach(NavigationPage.allCases, id: \.self) { page in
|
|
||||||
NavigationStackCompat {
|
|
||||||
page.contentView
|
|
||||||
.navigationTitle(page.title)
|
|
||||||
}
|
|
||||||
.tag(page)
|
|
||||||
.tabItem { page.label }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
environments.postReload()
|
|
||||||
}
|
|
||||||
.alertBinding($alert)
|
|
||||||
.onChangeCompat(of: scenePhase) { newValue in
|
|
||||||
if newValue == .active {
|
|
||||||
environments.postReload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChangeCompat(of: selection) { newValue in
|
|
||||||
if newValue == .logs {
|
|
||||||
environments.connect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.environment(\.selection, $selection)
|
|
||||||
.environment(\.importProfile, $importProfile)
|
|
||||||
.environment(\.importRemoteProfile, $importRemoteProfile)
|
|
||||||
.environment(\.profileEditor, profileEditor)
|
|
||||||
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
|
||||||
.onOpenURL(perform: openURL)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
environments.postReload()
|
||||||
|
}
|
||||||
|
.alertBinding($alert)
|
||||||
|
.onChangeCompat(of: scenePhase) { newValue in
|
||||||
|
if newValue == .active {
|
||||||
|
environments.postReload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChangeCompat(of: selection) { newValue in
|
||||||
|
if newValue == .logs {
|
||||||
|
environments.connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.environment(\.selection, $selection)
|
||||||
|
.environment(\.importProfile, $importProfile)
|
||||||
|
.environment(\.importRemoteProfile, $importRemoteProfile)
|
||||||
|
.environment(\.profileEditor, profileEditor)
|
||||||
|
.handlesExternalEvents(preferring: [], allowing: ["*"])
|
||||||
|
.onOpenURL(perform: openURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateButtonVisibility() {
|
private func updateButtonVisibility() {
|
||||||
|
|||||||
Reference in New Issue
Block a user