From 8e764c56eb69c7e421a183a9f6456a6c3d52710d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 1 Dec 2025 17:10:51 +0800 Subject: [PATCH] Refactor ViewModel --- .../Views/Abstract/BaseViewModel.swift | 31 ++++ .../Abstract/NavigationButtonsView.swift | 2 +- .../Abstract/NavigationStackCompat.swift | 2 +- .../Views/Abstract/RequestReviewButton.swift | 2 +- .../Views/Abstract/ViewBuilder.swift | 6 - .../Views/Abstract/ViewCompat.swift | 10 -- .../Connections/ConnectionListViewModel.swift | 8 +- .../Views/Dashboard/ActiveDashboardView.swift | 2 +- .../Components/ExtensionStatusView.swift | 2 +- .../Components/StartStopButton.swift | 2 +- .../Views/Dashboard/DashboardPage.swift | 2 +- .../Views/Dashboard/DashboardView.swift | 2 +- ...dinator.swift => DashboardViewModel.swift} | 9 +- .../Dashboard/Overview/OverviewView.swift | 2 +- ...rdinator.swift => OverviewViewModel.swift} | 5 +- .../Views/Groups/GroupListViewModel.swift | 9 +- ApplicationLibrary/Views/NavigationPage.swift | 2 +- .../Profile/EditProfileContentView.swift | 4 +- .../Profile/EditProfileContentViewModel.swift | 6 +- .../Views/Profile/EditProfileViewModel.swift | 6 +- .../Profile/ImportProfileViewModel.swift | 6 +- .../Views/Profile/NewProfileView.swift | 2 +- .../Views/Profile/NewProfileViewModel.swift | 4 +- .../Views/Profile/ProfileView.swift | 4 +- .../Views/Profile/ProfileViewModel.swift | 9 +- .../Views/Setting/CoreView.swift | 2 +- .../Views/Setting/MacAppView.swift | 2 +- .../Views/Setting/OnDemandRulesView.swift | 2 +- .../Views/Setting/PacketTunnelView.swift | 2 +- .../Views/Setting/ProfileOverrideView.swift | 2 +- .../Views/Setting/ServiceLogView.swift | 2 +- .../Views/Setting/SettingView.swift | 2 +- MacLibrary/MenuView.swift | 2 +- SFI/MainView.swift | 141 +++++++----------- 34 files changed, 136 insertions(+), 160 deletions(-) create mode 100644 ApplicationLibrary/Views/Abstract/BaseViewModel.swift delete mode 100644 ApplicationLibrary/Views/Abstract/ViewBuilder.swift rename ApplicationLibrary/Views/Dashboard/{DashboardCoordinator.swift => DashboardViewModel.swift} (97%) rename ApplicationLibrary/Views/Dashboard/Overview/{OverviewCoordinator.swift => OverviewViewModel.swift} (93%) diff --git a/ApplicationLibrary/Views/Abstract/BaseViewModel.swift b/ApplicationLibrary/Views/Abstract/BaseViewModel.swift new file mode 100644 index 0000000..2d74020 --- /dev/null +++ b/ApplicationLibrary/Views/Abstract/BaseViewModel.swift @@ -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) + } + } + } +} diff --git a/ApplicationLibrary/Views/Abstract/NavigationButtonsView.swift b/ApplicationLibrary/Views/Abstract/NavigationButtonsView.swift index f8b348a..b27f17b 100644 --- a/ApplicationLibrary/Views/Abstract/NavigationButtonsView.swift +++ b/ApplicationLibrary/Views/Abstract/NavigationButtonsView.swift @@ -35,7 +35,7 @@ public struct NavigationButtonsView: View { #if os(tvOS) private var tvOSBody: some View { - viewBuilder { + Group { if showConnectionsButton { Button { onConnectionsTap() diff --git a/ApplicationLibrary/Views/Abstract/NavigationStackCompat.swift b/ApplicationLibrary/Views/Abstract/NavigationStackCompat.swift index ead485e..84901fe 100644 --- a/ApplicationLibrary/Views/Abstract/NavigationStackCompat.swift +++ b/ApplicationLibrary/Views/Abstract/NavigationStackCompat.swift @@ -1,7 +1,7 @@ import SwiftUI public func NavigationStackCompat(@ViewBuilder content: () -> some View) -> some View { - viewBuilder { + Group { if #available(iOS 17.0, macOS 13.0, tvOS 17.0, *) { // view not updating in iOS 16, but why? NavigationStack { diff --git a/ApplicationLibrary/Views/Abstract/RequestReviewButton.swift b/ApplicationLibrary/Views/Abstract/RequestReviewButton.swift index a7459cd..505975b 100644 --- a/ApplicationLibrary/Views/Abstract/RequestReviewButton.swift +++ b/ApplicationLibrary/Views/Abstract/RequestReviewButton.swift @@ -4,7 +4,7 @@ import SwiftUI public func RequestReviewButton(label: @escaping () -> some View) -> some View { - viewBuilder { + Group { if #available(iOS 16.0, macOS 13.0, visionOS 1.0, *) { RequestReviewButton0(label: label) } else { diff --git a/ApplicationLibrary/Views/Abstract/ViewBuilder.swift b/ApplicationLibrary/Views/Abstract/ViewBuilder.swift deleted file mode 100644 index e13c1e4..0000000 --- a/ApplicationLibrary/Views/Abstract/ViewBuilder.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import SwiftUI - -public func viewBuilder(@ViewBuilder _ builder: () -> some View) -> some View { - builder() -} diff --git a/ApplicationLibrary/Views/Abstract/ViewCompat.swift b/ApplicationLibrary/Views/Abstract/ViewCompat.swift index 3454686..2274783 100644 --- a/ApplicationLibrary/Views/Abstract/ViewCompat.swift +++ b/ApplicationLibrary/Views/Abstract/ViewCompat.swift @@ -2,22 +2,12 @@ import SwiftUI public extension 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 action() } -// } } func onChangeCompat(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) -// } } } diff --git a/ApplicationLibrary/Views/Connections/ConnectionListViewModel.swift b/ApplicationLibrary/Views/Connections/ConnectionListViewModel.swift index aadc29c..1c3cb55 100644 --- a/ApplicationLibrary/Views/Connections/ConnectionListViewModel.swift +++ b/ApplicationLibrary/Views/Connections/ConnectionListViewModel.swift @@ -3,11 +3,9 @@ import Library import SwiftUI @MainActor -public class ConnectionListViewModel: ObservableObject { - @Published public var isLoading = true +public class ConnectionListViewModel: BaseViewModel { @Published public var connections: [Connection] = [] @Published public var searchText = "" - @Published public var alert: Alert? @Published public var connectionStateFilter: ConnectionStateFilter { didSet { saveStateFilterTask?.cancel() @@ -30,9 +28,11 @@ public class ConnectionListViewModel: ObservableObject { private var saveStateFilterTask: Task? private var saveSortTask: Task? - public init() { + public override init() { connectionStateFilter = .active connectionSort = .byDate + super.init() + isLoading = true } public func connect() { diff --git a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift index 87e5b92..9c59a10 100644 --- a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift @@ -7,7 +7,7 @@ import SwiftUI @Environment(\.scenePhase) private var scenePhase @EnvironmentObject private var environments: ExtensionEnvironments @EnvironmentObject private var profile: ExtensionProfile - @StateObject private var coordinator = DashboardCoordinator() + @StateObject private var coordinator = DashboardViewModel() @State private var cardConfigurationVersion = 0 #if os(iOS) || os(tvOS) @State private var showCardManagement = false diff --git a/ApplicationLibrary/Views/Dashboard/Components/ExtensionStatusView.swift b/ApplicationLibrary/Views/Dashboard/Components/ExtensionStatusView.swift index 368c2cf..8f105f5 100644 --- a/ApplicationLibrary/Views/Dashboard/Components/ExtensionStatusView.swift +++ b/ApplicationLibrary/Views/Dashboard/Components/ExtensionStatusView.swift @@ -25,7 +25,7 @@ public struct ExtensionStatusView: View { } public var body0: some View { - viewBuilder { + Group { VStack { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount), alignment: .leading) { if ApplicationLibrary.inPreview { diff --git a/ApplicationLibrary/Views/Dashboard/Components/StartStopButton.swift b/ApplicationLibrary/Views/Dashboard/Components/StartStopButton.swift index 2030626..7362494 100644 --- a/ApplicationLibrary/Views/Dashboard/Components/StartStopButton.swift +++ b/ApplicationLibrary/Views/Dashboard/Components/StartStopButton.swift @@ -9,7 +9,7 @@ public struct StartStopButton: View { public init() {} public var body: some View { - viewBuilder { + Group { if ApplicationLibrary.inPreview { Button {} label: { #if os(tvOS) diff --git a/ApplicationLibrary/Views/Dashboard/DashboardPage.swift b/ApplicationLibrary/Views/Dashboard/DashboardPage.swift index e97cd7f..c3d52f4 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardPage.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardPage.swift @@ -52,7 +52,7 @@ public extension DashboardPage { @MainActor func contentView(_ profileList: Binding<[ProfilePreview]>, _ selectedProfileID: Binding, _ systemProxyAvailable: Binding, _ systemProxyEnabled: Binding, _ cardConfigurationVersion: Int) -> some View { - viewBuilder { + Group { switch self { case .overview: OverviewView(profileList, selectedProfileID, systemProxyAvailable, systemProxyEnabled, cardConfigurationVersion: cardConfigurationVersion) diff --git a/ApplicationLibrary/Views/Dashboard/DashboardView.swift b/ApplicationLibrary/Views/Dashboard/DashboardView.swift index 2bd9523..3720ba7 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardView.swift @@ -9,7 +9,7 @@ public struct DashboardView: View { @Environment(\.importProfile) private var importProfile @Environment(\.importRemoteProfile) private var importRemoteProfile @EnvironmentObject private var environments: ExtensionEnvironments - @StateObject private var coordinator = DashboardCoordinator() + @StateObject private var coordinator = DashboardViewModel() @State private var importRemoteProfileRequest: NewProfileView.ImportRequest? #if os(macOS) diff --git a/ApplicationLibrary/Views/Dashboard/DashboardCoordinator.swift b/ApplicationLibrary/Views/Dashboard/DashboardViewModel.swift similarity index 97% rename from ApplicationLibrary/Views/Dashboard/DashboardCoordinator.swift rename to ApplicationLibrary/Views/Dashboard/DashboardViewModel.swift index c5113c2..636de70 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardCoordinator.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardViewModel.swift @@ -5,11 +5,9 @@ import NetworkExtension import SwiftUI @MainActor -public final class DashboardCoordinator: ObservableObject { - @Published public var isLoading = true +public final class DashboardViewModel: BaseViewModel { @Published public var profileList: [ProfilePreview] = [] @Published public var selectedProfileID: Int64 = 0 - @Published public var alert: Alert? @Published public var selection = DashboardPage.overview @Published public var systemProxyAvailable = false @Published public var systemProxyEnabled = false @@ -22,7 +20,10 @@ public final class DashboardCoordinator: ObservableObject { public var onEmptyProfilesChange: ((Bool) -> Void)? private var openURL: ((URL) -> Void)? - public init() {} + public override init() { + super.init() + isLoading = true + } public func setOpenURL(_ openURL: @escaping (URL) -> Void) { self.openURL = openURL diff --git a/ApplicationLibrary/Views/Dashboard/Overview/OverviewView.swift b/ApplicationLibrary/Views/Dashboard/Overview/OverviewView.swift index bf76f32..f362425 100644 --- a/ApplicationLibrary/Views/Dashboard/Overview/OverviewView.swift +++ b/ApplicationLibrary/Views/Dashboard/Overview/OverviewView.swift @@ -7,7 +7,7 @@ import SwiftUI public struct OverviewView: View { @EnvironmentObject private var environments: ExtensionEnvironments @EnvironmentObject private var profile: ExtensionProfile - @StateObject private var coordinator = OverviewCoordinator() + @StateObject private var coordinator = OverviewViewModel() @StateObject private var configuration = DashboardCardConfiguration() @Binding private var profileList: [ProfilePreview] diff --git a/ApplicationLibrary/Views/Dashboard/Overview/OverviewCoordinator.swift b/ApplicationLibrary/Views/Dashboard/Overview/OverviewViewModel.swift similarity index 93% rename from ApplicationLibrary/Views/Dashboard/Overview/OverviewCoordinator.swift rename to ApplicationLibrary/Views/Dashboard/Overview/OverviewViewModel.swift index 5b23195..64df657 100644 --- a/ApplicationLibrary/Views/Dashboard/Overview/OverviewCoordinator.swift +++ b/ApplicationLibrary/Views/Dashboard/Overview/OverviewViewModel.swift @@ -4,12 +4,9 @@ import Library import SwiftUI @MainActor -public final class OverviewCoordinator: ObservableObject { - @Published public var alert: Alert? +public final class OverviewViewModel: BaseViewModel { @Published public var reasserting = false - public init() {} - public func switchProfile(_ profileID: Int64, profile: ExtensionProfile, environments: ExtensionEnvironments) async { await SharedPreferences.selectedProfileID.set(profileID) environments.selectedProfileUpdate.send() diff --git a/ApplicationLibrary/Views/Groups/GroupListViewModel.swift b/ApplicationLibrary/Views/Groups/GroupListViewModel.swift index 70b01b5..0d5a6ed 100644 --- a/ApplicationLibrary/Views/Groups/GroupListViewModel.swift +++ b/ApplicationLibrary/Views/Groups/GroupListViewModel.swift @@ -3,14 +3,15 @@ import Library import SwiftUI @MainActor -public class GroupListViewModel: ObservableObject { - @Published public var isLoading = true +public class GroupListViewModel: BaseViewModel { @Published public var groups: [OutboundGroup] = [] - @Published public var alert: Alert? private var pendingSelections: [String: String] = [:] - public init() {} + public override init() { + super.init() + isLoading = true + } public func connect() { if ApplicationLibrary.inPreview { diff --git a/ApplicationLibrary/Views/NavigationPage.swift b/ApplicationLibrary/Views/NavigationPage.swift index 24e36a4..1de0ed3 100644 --- a/ApplicationLibrary/Views/NavigationPage.swift +++ b/ApplicationLibrary/Views/NavigationPage.swift @@ -64,7 +64,7 @@ public extension NavigationPage { @MainActor var contentView: some View { - viewBuilder { + Group { switch self { case .dashboard: DashboardView() diff --git a/ApplicationLibrary/Views/Profile/EditProfileContentView.swift b/ApplicationLibrary/Views/Profile/EditProfileContentView.swift index abada04..c6da72f 100644 --- a/ApplicationLibrary/Views/Profile/EditProfileContentView.swift +++ b/ApplicationLibrary/Views/Profile/EditProfileContentView.swift @@ -22,7 +22,7 @@ @Environment(\.profileEditor) private var profileEditor public var body: some View { - viewBuilder { + Group { if viewModel.isLoading { ProgressView().onAppear { Task { @@ -104,7 +104,7 @@ @ViewBuilder private var defaultEditorView: some View { - viewBuilder { + Group { if readOnly { TextEditor(text: .constant(viewModel.profileContent)) } else { diff --git a/ApplicationLibrary/Views/Profile/EditProfileContentViewModel.swift b/ApplicationLibrary/Views/Profile/EditProfileContentViewModel.swift index ffbeaa0..cf8dbe3 100644 --- a/ApplicationLibrary/Views/Profile/EditProfileContentViewModel.swift +++ b/ApplicationLibrary/Views/Profile/EditProfileContentViewModel.swift @@ -4,17 +4,17 @@ import SwiftUI @MainActor - public final class EditProfileContentViewModel: ObservableObject { - @Published public var isLoading = true + public final class EditProfileContentViewModel: BaseViewModel { @Published public var profile: Profile? @Published public var profileContent = "" @Published public var isChanged = false - @Published public var alert: Alert? private let profileID: Int64? public init(profileID: Int64?) { self.profileID = profileID + super.init() + isLoading = true } public func markAsChanged() { diff --git a/ApplicationLibrary/Views/Profile/EditProfileViewModel.swift b/ApplicationLibrary/Views/Profile/EditProfileViewModel.swift index 9ba97a9..c47bd27 100644 --- a/ApplicationLibrary/Views/Profile/EditProfileViewModel.swift +++ b/ApplicationLibrary/Views/Profile/EditProfileViewModel.swift @@ -3,15 +3,11 @@ import Library import SwiftUI @MainActor -public final class EditProfileViewModel: ObservableObject { - @Published public var isLoading = false +public final class EditProfileViewModel: BaseViewModel { @Published public var isChanged = false - @Published public var alert: Alert? @Published public var shareLinkPresented = false @Published public var shareLinkText: String? - public init() {} - public func markAsChanged() { isChanged = true } diff --git a/ApplicationLibrary/Views/Profile/ImportProfileViewModel.swift b/ApplicationLibrary/Views/Profile/ImportProfileViewModel.swift index 619d61a..a170ee8 100644 --- a/ApplicationLibrary/Views/Profile/ImportProfileViewModel.swift +++ b/ApplicationLibrary/Views/Profile/ImportProfileViewModel.swift @@ -7,17 +7,13 @@ import SwiftUI @MainActor - public final class ImportProfileViewModel: ObservableObject { - @Published public var isLoading = false + public final class ImportProfileViewModel: BaseViewModel { @Published public var selected = false - @Published public var alert: Alert? @Published public var connection: NWConnection? @Published public var socket: NWSocket? @Published public var profiles: [LibboxProfilePreview]? @Published public var isImporting = false - public init() {} - public func reset() { if let connection { connection.stateUpdateHandler = nil diff --git a/ApplicationLibrary/Views/Profile/NewProfileView.swift b/ApplicationLibrary/Views/Profile/NewProfileView.swift index 8c8dbc3..caf7151 100644 --- a/ApplicationLibrary/Views/Profile/NewProfileView.swift +++ b/ApplicationLibrary/Views/Profile/NewProfileView.swift @@ -57,7 +57,7 @@ public struct NewProfileView: View { #if os(tvOS) .disabled(true) #endif - viewBuilder { + Group { if viewModel.fileImport { HStack { Text("File Path") diff --git a/ApplicationLibrary/Views/Profile/NewProfileViewModel.swift b/ApplicationLibrary/Views/Profile/NewProfileViewModel.swift index e6c7f74..83320eb 100644 --- a/ApplicationLibrary/Views/Profile/NewProfileViewModel.swift +++ b/ApplicationLibrary/Views/Profile/NewProfileViewModel.swift @@ -4,7 +4,7 @@ import Library import SwiftUI @MainActor -public final class NewProfileViewModel: ObservableObject { +public final class NewProfileViewModel: BaseViewModel { @Published public var isSaving = false @Published public var profileName = "" #if !os(tvOS) @@ -18,9 +18,9 @@ public final class NewProfileViewModel: ObservableObject { @Published public var autoUpdate = true @Published public var autoUpdateInterval: Int32 = 60 @Published public var pickerPresented = false - @Published public var alert: Alert? public init(importRequest: NewProfileView.ImportRequest? = nil) { + super.init() if let importRequest { profileName = importRequest.name profileType = .remote diff --git a/ApplicationLibrary/Views/Profile/ProfileView.swift b/ApplicationLibrary/Views/Profile/ProfileView.swift index 2f18373..36f2628 100644 --- a/ApplicationLibrary/Views/Profile/ProfileView.swift +++ b/ApplicationLibrary/Views/Profile/ProfileView.swift @@ -67,7 +67,7 @@ public struct ProfileView: View { } else { List { ForEach(viewModel.profileList, id: \.id) { profile in - viewBuilder { + Group { #if os(iOS) || os(tvOS) if viewModel.editMode.isEditing == true { Text(profile.name) @@ -182,7 +182,7 @@ public struct ProfileView: View { } private var draggableBody: some View { - viewBuilder { + Group { #if !os(macOS) FormNavigationLink { EditProfileView().environmentObject(profile.origin) diff --git a/ApplicationLibrary/Views/Profile/ProfileViewModel.swift b/ApplicationLibrary/Views/Profile/ProfileViewModel.swift index bebc868..1374fd8 100644 --- a/ApplicationLibrary/Views/Profile/ProfileViewModel.swift +++ b/ApplicationLibrary/Views/Profile/ProfileViewModel.swift @@ -4,12 +4,10 @@ import Library import SwiftUI @MainActor -public class ProfileViewModel: ObservableObject { +public class ProfileViewModel: BaseViewModel { @Published public var importRemoteProfileRequest: NewProfileView.ImportRequest? @Published public var importRemoteProfilePresented = false - @Published public var isLoading = true @Published public var isUpdating = false - @Published public var alert: Alert? @Published public var profileList: [ProfilePreview] = [] #if os(iOS) || os(tvOS) @@ -18,7 +16,10 @@ public class ProfileViewModel: ObservableObject { private weak var environments: ExtensionEnvironments? - public init() {} + public override init() { + super.init() + isLoading = true + } public func setEnvironments(_ environments: ExtensionEnvironments) { self.environments = environments diff --git a/ApplicationLibrary/Views/Setting/CoreView.swift b/ApplicationLibrary/Views/Setting/CoreView.swift index ee5a6ae..efebf5f 100644 --- a/ApplicationLibrary/Views/Setting/CoreView.swift +++ b/ApplicationLibrary/Views/Setting/CoreView.swift @@ -13,7 +13,7 @@ public struct CoreView: View { public init() {} public var body: some View { - viewBuilder { + Group { if isLoading { ProgressView().onAppear { Task { diff --git a/ApplicationLibrary/Views/Setting/MacAppView.swift b/ApplicationLibrary/Views/Setting/MacAppView.swift index 8bcc899..3200ed0 100644 --- a/ApplicationLibrary/Views/Setting/MacAppView.swift +++ b/ApplicationLibrary/Views/Setting/MacAppView.swift @@ -17,7 +17,7 @@ public struct AppView: View { public init() {} public var body: some View { - viewBuilder { + Group { if isLoading { ProgressView().onAppear { Task { diff --git a/ApplicationLibrary/Views/Setting/OnDemandRulesView.swift b/ApplicationLibrary/Views/Setting/OnDemandRulesView.swift index cc48c73..09752e1 100644 --- a/ApplicationLibrary/Views/Setting/OnDemandRulesView.swift +++ b/ApplicationLibrary/Views/Setting/OnDemandRulesView.swift @@ -8,7 +8,7 @@ public struct OnDemandRulesView: View { public init() {} public var body: some View { - viewBuilder { + Group { if isLoading { ProgressView().onAppear { Task.detached { diff --git a/ApplicationLibrary/Views/Setting/PacketTunnelView.swift b/ApplicationLibrary/Views/Setting/PacketTunnelView.swift index 8de6396..581c23e 100644 --- a/ApplicationLibrary/Views/Setting/PacketTunnelView.swift +++ b/ApplicationLibrary/Views/Setting/PacketTunnelView.swift @@ -14,7 +14,7 @@ struct PacketTunnelView: View { init() {} var body: some View { - viewBuilder { + Group { if isLoading { ProgressView().onAppear { Task.detached { diff --git a/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift b/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift index 1984f2a..45a1af5 100644 --- a/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift +++ b/ApplicationLibrary/Views/Setting/ProfileOverrideView.swift @@ -9,7 +9,7 @@ public struct ProfileOverrideView: View { public init() {} public var body: some View { - viewBuilder { + Group { if isLoading { ProgressView().onAppear { Task.detached { diff --git a/ApplicationLibrary/Views/Setting/ServiceLogView.swift b/ApplicationLibrary/Views/Setting/ServiceLogView.swift index cee10e4..a38663a 100644 --- a/ApplicationLibrary/Views/Setting/ServiceLogView.swift +++ b/ApplicationLibrary/Views/Setting/ServiceLogView.swift @@ -12,7 +12,7 @@ public struct ServiceLogView: View { public init() {} public var body: some View { - viewBuilder { + Group { if viewModel.isLoading { ProgressView().onAppear { Task { diff --git a/ApplicationLibrary/Views/Setting/SettingView.swift b/ApplicationLibrary/Views/Setting/SettingView.swift index bfc2088..17ff2ed 100644 --- a/ApplicationLibrary/Views/Setting/SettingView.swift +++ b/ApplicationLibrary/Views/Setting/SettingView.swift @@ -57,7 +57,7 @@ public struct SettingView: View { @MainActor var contentView: some View { - viewBuilder { + Group { switch self { #if os(macOS) case .app: diff --git a/MacLibrary/MenuView.swift b/MacLibrary/MenuView.swift index f578dac..fb847d4 100644 --- a/MacLibrary/MenuView.swift +++ b/MacLibrary/MenuView.swift @@ -129,7 +129,7 @@ public struct MenuView: View { } var body: some View { - viewBuilder { + Group { if isLoading { ProgressView().onAppear { Task { diff --git a/SFI/MainView.swift b/SFI/MainView.swift index 45036f4..4cebd55 100644 --- a/SFI/MainView.swift +++ b/SFI/MainView.swift @@ -37,46 +37,28 @@ struct MainView: View { @ViewBuilder private var tabViewContent: some View { if shouldShowBottomAccessory { - TabView(selection: $selection) { - ForEach(NavigationPage.allCases, id: \.self) { page in - NavigationStackCompat { - page.contentView - .navigationTitle(page.title) + baseTabView + .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() } - .tag(page) - .tabItem { page.label } + .padding(.horizontal) } - } - .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 { - TabView(selection: $selection) { - ForEach(NavigationPage.allCases, id: \.self) { page in - NavigationStackCompat { - page.contentView - .navigationTitle(page.title) - } - .tag(page) - .tabItem { page.label } - } - } + baseTabView } } @@ -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 { - viewBuilder { + Group { if #available(iOS 26.0, *), !Variant.debugNoIOS26 { tabViewContent .onAppear { - environments.postReload() 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 updateButtonVisibility() } @@ -125,12 +109,6 @@ struct MainView: View { .onReceive(environments.$emptyProfiles) { _ in updateButtonVisibility() } - .environment(\.selection, $selection) - .environment(\.importProfile, $importProfile) - .environment(\.importRemoteProfile, $importRemoteProfile) - .environment(\.profileEditor, profileEditor) - .handlesExternalEvents(preferring: [], allowing: ["*"]) - .onOpenURL(perform: openURL) .sheet(isPresented: $showGroups) { GroupsSheetContent() } @@ -138,38 +116,29 @@ struct MainView: View { ConnectionsSheetContent() } } else { - TabView(selection: $selection) { - 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) + baseTabView } } + .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() {