Skip to content

Commit c09db42

Browse files
committed
Add remote control
1 parent baa72c9 commit c09db42

40 files changed

Lines changed: 2407 additions & 404 deletions

ApplicationLibrary/Views/Abstract/GlobalChecksModifier.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ public struct GlobalChecksModifier: ViewModifier {
7272
.onChangeCompat(of: importRemoteProfile.wrappedValue) { _ in
7373
Task { @MainActor in handleImportRemoteProfile() }
7474
}
75+
.onReceive(environments.$remoteControlAlert) { alertState in
76+
guard let alertState else { return }
77+
Task { @MainActor in
78+
environments.remoteControlAlert = nil
79+
alert = alertState
80+
}
81+
}
7582
.background {
7683
if let profile = environments.extensionProfile {
7784
ProfileStatusObserver(profile: profile, onChange: handleStatusChange)

ApplicationLibrary/Views/Connections/ConnectionListViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public class ConnectionListViewModel: BaseViewModel {
8888

8989
public func closeAllConnections() {
9090
do {
91-
try LibboxNewStandaloneCommandClient()!.closeConnections()
91+
try CommandTarget.standaloneClient().closeConnections()
9292
} catch {
9393
alert = AlertState(action: "close all connections", error: error)
9494
}

ApplicationLibrary/Views/Connections/ConnectionView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Libbox
2+
import Library
23
import SwiftUI
34

45
@MainActor
@@ -119,7 +120,7 @@ public struct ConnectionView: View {
119120

120121
private nonisolated func closeConnection() async {
121122
do {
122-
try await LibboxNewStandaloneCommandClient()!.closeConnection(connection.id)
123+
try await CommandTarget.standaloneClient().closeConnection(connection.id)
123124
} catch {
124125
await MainActor.run {
125126
alert = AlertState(action: "close connection", error: error)

ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift

Lines changed: 58 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,17 @@ import SwiftUI
88
@EnvironmentObject private var environments: ExtensionEnvironments
99
@EnvironmentObject private var profile: ExtensionProfile
1010
@ObservedObject private var coordinator: DashboardViewModel
11-
@StateObject private var cardConfiguration = DashboardCardConfiguration()
12-
#if os(iOS) || os(tvOS)
13-
@State private var showCardManagement = false
14-
#endif
11+
@ObservedObject private var cardConfiguration: DashboardCardConfiguration
1512
#if os(tvOS)
13+
@State private var showCardManagement = false
1614
@State private var showGroups = false
1715
@State private var showConnections = false
1816
@State private var buttonState = ButtonVisibilityState()
1917
#endif
20-
#if os(macOS)
21-
@Environment(\.cardConfigurationVersion) private var cardConfigurationVersion
22-
#endif
2318

24-
public init(coordinator: DashboardViewModel) {
19+
public init(coordinator: DashboardViewModel, cardConfiguration: DashboardCardConfiguration) {
2520
_coordinator = ObservedObject(wrappedValue: coordinator)
21+
_cardConfiguration = ObservedObject(wrappedValue: cardConfiguration)
2622
}
2723

2824
public var body: some View {
@@ -62,11 +58,20 @@ import SwiftUI
6258
Group {
6359
overviewPage
6460
}
65-
#if os(iOS) || os(tvOS)
61+
#if os(tvOS)
6662
.toolbar {
67-
toolbar
63+
ToolbarItemGroup(placement: .topBarLeading) {
64+
navigationButtons
65+
}
66+
ToolbarItemGroup(placement: .topBarTrailing) {
67+
Button {
68+
showCardManagement = true
69+
} label: {
70+
Image(systemName: "square.grid.2x2")
71+
}
72+
StartStopButton()
73+
}
6874
}
69-
#if os(tvOS)
7075
.navigationDestination(isPresented: $showGroups) {
7176
GroupListView()
7277
.navigationTitle("Groups")
@@ -96,58 +101,42 @@ import SwiftUI
96101
}
97102
}
98103
}
99-
#else
100-
.sheet(isPresented: $showCardManagement, onDismiss: {
101-
Task { await cardConfiguration.reload() }
102-
}, content: {
103-
if #available(iOS 16.0, *) {
104-
CardManagementSheet().presentationDetents([.large]).presentationDragIndicator(.visible)
105-
} else {
106-
CardManagementSheet()
107-
}
108-
})
109-
#endif
110104
#endif
111105
.onAppear {
112-
environments.connect()
113-
}.onChangeCompat(of: scenePhase) { phase in
114-
guard phase == .active else {
115-
return
116-
}
117-
environments.connect()
118-
}.onChangeCompat(of: profile.status) { status in
119-
guard status.isConnected else {
120-
return
121-
}
122-
environments.connect()
123-
}.onReceive(environments.profileUpdate) { _ in
124-
Task {
125-
await coordinator.reload()
126-
}
127-
}.onReceive(environments.selectedProfileUpdate) { _ in
128-
Task {
129-
await coordinator.updateSelectedProfile()
130-
if profile.status.isConnected {
131-
await coordinator.reloadSystemProxy()
132-
}
106+
environments.connect()
107+
}.onChangeCompat(of: scenePhase) { phase in
108+
guard phase == .active else {
109+
return
110+
}
111+
environments.connect()
112+
}.onChangeCompat(of: profile.status) { status in
113+
guard status.isConnected else {
114+
return
115+
}
116+
environments.connect()
117+
}.onReceive(environments.profileUpdate) { _ in
118+
Task {
119+
await coordinator.reload()
120+
}
121+
}.onReceive(environments.selectedProfileUpdate) { _ in
122+
Task {
123+
await coordinator.updateSelectedProfile()
124+
if profile.status.isConnected {
125+
await coordinator.reloadSystemProxy()
133126
}
134127
}
128+
}
135129
#if os(tvOS)
136-
.onReceive(environments.commandClient.$groups) { _ in
137-
Task { @MainActor in
138-
updateButtonVisibility()
139-
}
140-
}.onReceive(profile.$status) { _ in
141-
Task { @MainActor in
142-
updateButtonVisibility()
143-
}
144-
}.onAppear {
130+
.onReceive(environments.commandClient.$groups) { _ in
131+
Task { @MainActor in
145132
updateButtonVisibility()
146133
}
147-
#endif
148-
#if os(macOS)
149-
.onChangeCompat(of: cardConfigurationVersion) { _ in
150-
Task { await cardConfiguration.reload() }
134+
}.onReceive(profile.$status) { _ in
135+
Task { @MainActor in
136+
updateButtonVisibility()
137+
}
138+
}.onAppear {
139+
updateButtonVisibility()
151140
}
152141
#endif
153142
}
@@ -166,62 +155,20 @@ import SwiftUI
166155
private func updateButtonVisibility() {
167156
buttonState.update(profile: profile, commandClient: environments.commandClient)
168157
}
169-
#endif
170158

171-
#if os(iOS) || os(tvOS)
172-
@ToolbarContentBuilder private var toolbar: some ToolbarContent {
173-
#if os(tvOS)
174-
ToolbarItemGroup(placement: .topBarLeading) {
175-
navigationButtons
176-
}
177-
#endif
178-
ToolbarItemGroup(placement: .topBarTrailing) {
179-
if #available(iOS 16.0, tvOS 17.0, *) {
180-
cardManagementButton
181-
}
182-
#if os(tvOS)
183-
StartStopButton()
184-
#endif
185-
}
186-
}
187-
188-
#if os(tvOS)
189-
private var navigationButtons: some View {
190-
NavigationButtonsView(
191-
showGroupsButton: buttonState.showGroupsButton,
192-
showConnectionsButton: buttonState.showConnectionsButton,
193-
groupsCount: buttonState.groupsCount,
194-
connectionsCount: buttonState.connectionsCount,
195-
onGroupsTap: {
196-
showGroups = true
197-
},
198-
onConnectionsTap: {
199-
showConnections = true
200-
}
201-
)
202-
}
203-
#endif
204-
#endif
205-
206-
#if os(iOS) || os(tvOS)
207-
@available(iOS 16.0, *) @ViewBuilder private var cardManagementButton: some View {
208-
#if os(iOS)
209-
Menu {
210-
Button {
211-
showCardManagement = true
212-
} label: {
213-
Label("Dashboard Items", systemImage: "square.grid.2x2")
214-
}
215-
} label: {
216-
Label("Others", systemImage: "line.3.horizontal.circle")
217-
}
218-
#elseif os(tvOS)
219-
Button {
220-
showCardManagement = true
221-
} label: {
222-
Image(systemName: "square.grid.2x2")
223-
}
224-
#endif
159+
private var navigationButtons: some View {
160+
NavigationButtonsView(
161+
showGroupsButton: buttonState.showGroupsButton,
162+
showConnectionsButton: buttonState.showConnectionsButton,
163+
groupsCount: buttonState.groupsCount,
164+
connectionsCount: buttonState.connectionsCount,
165+
onGroupsTap: {
166+
showGroups = true
167+
},
168+
onConnectionsTap: {
169+
showConnections = true
170+
}
171+
)
225172
}
226173
#endif
227174
}

ApplicationLibrary/Views/Dashboard/ButtonVisibilityState.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22
import Library
33

44
@MainActor
5-
public struct ButtonVisibilityState {
5+
public struct ButtonVisibilityState: Equatable {
66
public var showGroupsButton = false
77
public var showConnectionsButton = false
88
public var groupsCount = 0
@@ -34,6 +34,13 @@ public struct ButtonVisibilityState {
3434
showGroupsButton = isConnected && hasGroups
3535
}
3636

37+
public mutating func update(remoteClient commandClient: CommandClient) {
38+
groupsCount = commandClient.groups?.count ?? 0
39+
connectionsCount = Int(commandClient.status?.connectionsIn ?? 0)
40+
showConnectionsButton = commandClient.isConnected
41+
showGroupsButton = commandClient.isConnected && groupsCount > 0
42+
}
43+
3744
private mutating func reset() {
3845
showGroupsButton = false
3946
showConnectionsButton = false

ApplicationLibrary/Views/Dashboard/Cards/ClashModeCard.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public struct ClashModeCard: View {
192192

193193
private nonisolated func setClashMode(_ newMode: String) async {
194194
do {
195-
try LibboxNewStandaloneCommandClient()!.setClashMode(newMode)
195+
try CommandTarget.standaloneClient().setClashMode(newMode)
196196
} catch {
197197
await MainActor.run {
198198
alert = AlertState(action: "set clash mode", error: error)

ApplicationLibrary/Views/Dashboard/DashboardView.swift

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@ import SwiftUI
66
public struct DashboardView: View {
77
@EnvironmentObject private var environments: ExtensionEnvironments
88
@StateObject private var coordinator = DashboardViewModel()
9+
@StateObject private var cardConfiguration = DashboardCardConfiguration()
10+
11+
#if os(iOS)
12+
@State private var showCardManagement = false
13+
@State private var remoteServers: [RemoteServer] = []
14+
#endif
915

1016
#if os(macOS)
1117
@Environment(\.controlActiveState) private var controlActiveState
18+
@Environment(\.cardConfigurationVersion) private var cardConfigurationVersion
1219
#endif
1320

1421
public init() {}
@@ -22,6 +29,28 @@ public struct DashboardView: View {
2229
Task { await coordinator.reload() }
2330
#endif
2431
}
32+
#if os(iOS)
33+
.toolbar {
34+
ToolbarItem(placement: .topBarTrailing) {
35+
othersMenu
36+
}
37+
}
38+
.onAppear {
39+
Task { await reloadRemoteServers() }
40+
}
41+
.onReceive(NotificationCenter.default.publisher(for: .remoteServersUpdated)) { _ in
42+
Task { await reloadRemoteServers() }
43+
}
44+
.sheet(isPresented: $showCardManagement, onDismiss: {
45+
Task { await cardConfiguration.reload() }
46+
}, content: {
47+
if #available(iOS 16.0, *) {
48+
CardManagementSheet().presentationDetents([.large]).presentationDragIndicator(.visible)
49+
} else {
50+
CardManagementSheet()
51+
}
52+
})
53+
#endif
2554
#if os(tvOS)
2655
.navigationDestination(item: $environments.pendingImportRemoteProfile) { request in
2756
NewProfileView(.init(name: request.name, url: request.url))
@@ -34,9 +63,9 @@ public struct DashboardView: View {
3463
BackButton()
3564
}
3665
}
37-
}
66+
}
3867
#else
39-
.sheet(item: $environments.pendingImportRemoteProfile) { request in
68+
.sheet(item: $environments.pendingImportRemoteProfile) { request in
4069
importRemoteProfileSheet(for: request)
4170
}
4271
#endif
@@ -45,9 +74,31 @@ public struct DashboardView: View {
4574
guard state != .inactive, Variant.useSystemExtension, !coordinator.isLoading else { return }
4675
Task { await coordinator.reload() }
4776
}
77+
.onChangeCompat(of: cardConfigurationVersion) { _ in
78+
Task { await cardConfiguration.reload() }
79+
}
4880
#endif
4981
}
5082

83+
#if os(iOS)
84+
private var othersMenu: some View {
85+
Menu {
86+
Button {
87+
showCardManagement = true
88+
} label: {
89+
Label("Dashboard Items", systemImage: "square.grid.2x2")
90+
}
91+
RemoteControlMenuItems(servers: remoteServers)
92+
} label: {
93+
Label("Others", systemImage: "line.3.horizontal.circle")
94+
}
95+
}
96+
97+
private func reloadRemoteServers() async {
98+
remoteServers = await (try? RemoteServerManager.list()) ?? []
99+
}
100+
#endif
101+
51102
private func importRemoteProfileSheet(for request: ImportRemoteProfileRequest) -> some View {
52103
NavigationSheet(title: "Import Profile", onDismiss: {
53104
environments.profileUpdate.send()
@@ -76,7 +127,9 @@ public struct DashboardView: View {
76127

77128
@ViewBuilder
78129
private var mainContent: some View {
79-
if environments.extensionProfileLoading {
130+
if environments.remoteServer != nil {
131+
RemoteDashboardView(commandClient: environments.commandClient, cardConfiguration: cardConfiguration)
132+
} else if environments.extensionProfileLoading {
80133
ProgressView()
81134
} else if let profile = environments.extensionProfile {
82135
activeDashboardView
@@ -99,6 +152,6 @@ public struct DashboardView: View {
99152
}
100153

101154
private var activeDashboardView: some View {
102-
ActiveDashboardView(coordinator: coordinator)
155+
ActiveDashboardView(coordinator: coordinator, cardConfiguration: cardConfiguration)
103156
}
104157
}

0 commit comments

Comments
 (0)