Unify DashboardViewModel instances

This commit is contained in:
世界
2025-12-01 21:02:28 +08:00
parent 4ddfcdb324
commit 714f77dbbf
7 changed files with 47 additions and 42 deletions
@@ -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 = DashboardViewModel() @ObservedObject 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
@@ -18,20 +18,25 @@ import SwiftUI
private let externalCardConfigurationVersion: Int? private let externalCardConfigurationVersion: Int?
public init(externalCardConfigurationVersion: Int? = nil) { public init(coordinator: DashboardViewModel, externalCardConfigurationVersion: Int? = nil) {
_coordinator = ObservedObject(wrappedValue: coordinator)
self.externalCardConfigurationVersion = externalCardConfigurationVersion self.externalCardConfigurationVersion = externalCardConfigurationVersion
} }
public var body: some View { public var body: some View {
viewContent
}
@ViewBuilder private var viewContent: some View {
if coordinator.isLoading { if coordinator.isLoading {
ProgressView().onAppear { ProgressView()
coordinator.onEmptyProfilesChange = { #if os(iOS) || os(tvOS)
environments.emptyProfiles = $0 .onAppear {
Task {
await coordinator.reload()
}
} }
Task { #endif
await coordinator.reload()
}
}
} else { } else {
content.onAppear { content.onAppear {
guard !ApplicationLibrary.inPreview, profile.status.isConnected else { guard !ApplicationLibrary.inPreview, profile.status.isConnected else {
@@ -40,8 +45,7 @@ import SwiftUI
Task { Task {
await coordinator.reloadSystemProxy() await coordinator.reloadSystemProxy()
} }
}.onChangeCompat(of: profile.status) { }.onChangeCompat(of: profile.status) { status in
status in
guard !ApplicationLibrary.inPreview, status == .connected else { guard !ApplicationLibrary.inPreview, status == .connected else {
return return
} }
@@ -77,25 +81,21 @@ import SwiftUI
} else { } else {
environments.connect() environments.connect()
} }
}.onChangeCompat(of: scenePhase) { }.onChangeCompat(of: scenePhase) { phase in
phase in
guard phase == .active else { guard phase == .active else {
return return
} }
environments.connect() environments.connect()
}.onChangeCompat(of: profile.status) { }.onChangeCompat(of: profile.status) { status in
status in
guard status.isConnected else { guard status.isConnected else {
return return
} }
environments.connect() environments.connect()
}.onReceive(environments.profileUpdate) { }.onReceive(environments.profileUpdate) { _ in
_ in
Task { Task {
await coordinator.reload() await coordinator.reload()
} }
}.onReceive(environments.selectedProfileUpdate) { }.onReceive(environments.selectedProfileUpdate) { _ in
_ in
Task { Task {
await coordinator.updateSelectedProfile() await coordinator.updateSelectedProfile()
if profile.status.isConnected { if profile.status.isConnected {
@@ -104,20 +104,16 @@ import SwiftUI
} }
} }
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
.onReceive(environments.commandClient.$groups) { .onReceive(environments.commandClient.$groups) { _ in
_ in
updateButtonVisibility() updateButtonVisibility()
}.onReceive(environments.commandClient.$connections) { }.onReceive(environments.commandClient.$connections) { _ in
_ in
updateButtonVisibility() updateButtonVisibility()
}.onReceive(profile.$status) { }.onReceive(profile.$status) { _ in
_ in
updateButtonVisibility() updateButtonVisibility()
}.onAppear { }.onAppear {
updateButtonVisibility() updateButtonVisibility()
} }
#endif #endif
.alert($coordinator.alert)
} }
@ViewBuilder private var overviewPage: some View { @ViewBuilder private var overviewPage: some View {
@@ -22,6 +22,7 @@ public struct DashboardView: View {
content content
.onAppear { .onAppear {
coordinator.setOpenURL { openURL($0) } coordinator.setOpenURL { openURL($0) }
coordinator.setEnvironments(environments)
#if os(macOS) #if os(macOS)
Task { await coordinator.reload() } Task { await coordinator.reload() }
#endif #endif
@@ -133,9 +134,9 @@ public struct DashboardView: View {
@ViewBuilder @ViewBuilder
private var activeDashboardView: some View { private var activeDashboardView: some View {
#if os(macOS) #if os(macOS)
ActiveDashboardView(externalCardConfigurationVersion: cardConfigurationVersion) ActiveDashboardView(coordinator: coordinator, externalCardConfigurationVersion: cardConfigurationVersion)
#else #else
ActiveDashboardView() ActiveDashboardView(coordinator: coordinator)
#endif #endif
} }
} }
@@ -17,9 +17,13 @@ public final class DashboardViewModel: BaseViewModel {
@Published public var systemExtensionInstalled = true @Published public var systemExtensionInstalled = true
#endif #endif
public var onEmptyProfilesChange: ((Bool) -> Void)? private weak var environments: ExtensionEnvironments?
private var openURL: ((URL) -> Void)? private var openURL: ((URL) -> Void)?
public func setEnvironments(_ environments: ExtensionEnvironments) {
self.environments = environments
}
override public init() { override public init() {
super.init() super.init()
isLoading = true isLoading = true
@@ -55,7 +59,7 @@ public final class DashboardViewModel: BaseViewModel {
do { do {
profileList = try await ProfileManager.list().map { ProfilePreview($0) } profileList = try await ProfileManager.list().map { ProfilePreview($0) }
guard !profileList.isEmpty else { guard !profileList.isEmpty else {
onEmptyProfilesChange?(true) environments?.emptyProfiles = true
return return
} }
@@ -69,7 +73,7 @@ public final class DashboardViewModel: BaseViewModel {
return return
} }
} }
onEmptyProfilesChange?(profileList.isEmpty) environments?.emptyProfiles = profileList.isEmpty
} }
public func reloadSystemProxy() async { public func reloadSystemProxy() async {
@@ -10,13 +10,12 @@ import SwiftUI
#endif #endif
@MainActor @MainActor
public class LogViewModel: ObservableObject { public class LogViewModel: BaseViewModel {
@Published public var selectedLogLevel: Int? @Published public var selectedLogLevel: Int?
@Published public var isPaused = false @Published public var isPaused = false
@Published public var searchText = "" @Published public var searchText = ""
@Published public var isSearching = false @Published public var isSearching = false
@Published public var filteredLogs: [LogEntry] = [] @Published public var filteredLogs: [LogEntry] = []
@Published public var alert: AlertState?
@Published public var showFileExporter = false @Published public var showFileExporter = false
@Published public var logFileURL: URL? @Published public var logFileURL: URL?
@@ -42,6 +41,7 @@ public class LogViewModel: ObservableObject {
public init(commandClient: CommandClient) { public init(commandClient: CommandClient) {
self.commandClient = commandClient self.commandClient = commandClient
super.init()
let debouncedSearchText = $searchText let debouncedSearchText = $searchText
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main) .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
@@ -3,10 +3,13 @@ import Library
import SwiftUI import SwiftUI
@MainActor @MainActor
final class ServiceLogViewModel: ObservableObject { final class ServiceLogViewModel: BaseViewModel {
@Published var isLoading = true
@Published var content = "" @Published var content = ""
@Published var alert: AlertState?
override init() {
super.init()
isLoading = true
}
var isEmpty: Bool { var isEmpty: Bool {
content.isEmpty content.isEmpty
@@ -2,10 +2,14 @@ import Library
import SwiftUI import SwiftUI
@MainActor @MainActor
final class SettingViewModel: ObservableObject { final class SettingViewModel: BaseViewModel {
@Published var isLoading = true
@Published var taiwanFlagAvailable = false @Published var taiwanFlagAvailable = false
override init() {
super.init()
isLoading = true
}
nonisolated func checkTaiwanFlagAvailability() async { nonisolated func checkTaiwanFlagAvailability() async {
let available: Bool let available: Bool
if ApplicationLibrary.inPreview { if ApplicationLibrary.inPreview {
+1 -4
View File
@@ -5,13 +5,10 @@ import Library
import SwiftUI import SwiftUI
@MainActor @MainActor
public class MainViewModel: ObservableObject { public class MainViewModel: BaseViewModel {
@Published public var selection = NavigationPage.dashboard @Published public var selection = NavigationPage.dashboard
@Published public var importProfile: LibboxProfileContent? @Published public var importProfile: LibboxProfileContent?
@Published public var importRemoteProfile: LibboxImportRemoteProfile? @Published public var importRemoteProfile: LibboxImportRemoteProfile?
@Published public var alert: AlertState?
public init() {}
public func onAppear(environments: ExtensionEnvironments) { public func onAppear(environments: ExtensionEnvironments) {
environments.postReload() environments.postReload()