Skip to content

Commit

Permalink
remote(client): add connect view
Browse files Browse the repository at this point in the history
  • Loading branch information
osy committed Jan 25, 2024
1 parent b2dc870 commit 57d565a
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 0 deletions.
221 changes: 221 additions & 0 deletions Platform/iOS/UTMRemoteConnectView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
//
// Copyright © 2023 osy. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import SwiftUI

struct UTMRemoteConnectView: View {
@ObservedObject var remoteClientState: UTMRemoteClient.State
@Environment(\.openURL) private var openURL
@EnvironmentObject private var data: UTMData
@State private var selectedServer: UTMRemoteClient.State.Server?
@State private var isAutoConnect: Bool = false

private var idiom: UIUserInterfaceIdiom {
UIDevice.current.userInterfaceIdiom
}

private var remoteClient: UTMRemoteClient {
data.remoteClient
}

var body: some View {
VStack {
HStack {
ProgressView().progressViewStyle(.circular)
Spacer()
Button {
openURL(URL(string: "https://docs.getutm.app/remote/")!)
} label: {
Label("Help", systemImage: "questionmark.circle")
.labelStyle(.iconOnly)
.font(.title2)
}
Button {

} label: {
Label("New Connection", systemImage: "plus")
.labelStyle(.iconOnly)
.font(.title2)
}
}.padding()
List {
Section(header: Text("Saved")) {
ForEach(remoteClientState.savedServers) { server in
Button {
isAutoConnect = true
selectedServer = server
} label: {
Text(server.name)
}.contextMenu {
Button {
isAutoConnect = false
selectedServer = server
} label: {
Label("Edit…", systemImage: "slider.horizontal.3")
}
DestructiveButton("Delete") {

}
}
}.onDelete { indexSet in

}
}
Section(header: Text("Found")) {
ForEach(remoteClientState.foundServers) { server in
Button {
isAutoConnect = true
selectedServer = server
} label: {
Text(server.name)
}
}
}
}.listStyle(.plain)
}.frame(maxWidth: idiom == .pad ? 600 : nil)
.sheet(item: $selectedServer) { server in
ServerConnectView(remoteClientState: remoteClientState, server: server, isAutoConnect: $isAutoConnect)
}
.onAppear {
Task {
await remoteClient.startScanning()
}
}
.onDisappear {
Task {
await remoteClient.stopScanning()
}
}
}
}

private struct ServerConnectView: View {
@ObservedObject var remoteClientState: UTMRemoteClient.State
@State var server: UTMRemoteClient.State.Server
@Binding var isAutoConnect: Bool

@EnvironmentObject private var data: UTMData
@Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>

@State private var isConnecting: Bool = false
@State private var isPasswordRequired: Bool = false
@State private var willBeSaved: Bool = true

private var remoteClient: UTMRemoteClient {
data.remoteClient
}

var body: some View {
NavigationView {
Form {
Section {
TextField("Name", text: $server.name)
TextField("Server", text: .constant(server.hostname))
} header: {
Text("Connection")
}
if isPasswordRequired {
Section {
if #available(iOS 15, *) {
FocusedPasswordView(password: $server.password.bound)
} else {
SecureField("Password", text: $server.password.bound)
}
} header: {
Text("Authentication")
}
}
Section {
Toggle("Save Connection", isOn: $willBeSaved)
} header: {
Text("Options")
}
}.disabled(isConnecting)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Close")
}.disabled(isConnecting)
}
ToolbarItem(placement: .topBarTrailing) {
HStack {
if isConnecting {
ProgressView().progressViewStyle(.circular)
Button {
connect()
} label: {
Text("Cancel")
}
} else {
Button {
connect()
} label: {
Text("Connect")
}
}
}
}
}
}
.alert(item: $remoteClientState.alertMessage) { item in
Alert(title: Text(item.message))
}
.onAppear {
if isAutoConnect {
connect()
}
}
}

private func connect() {
Task {
isConnecting = true
do {
try await remoteClient.connect(server, shouldSaveDetails: willBeSaved)
} catch {
if case UTMRemoteClient.ConnectionError.passwordRequired = error {
withAnimation {
isPasswordRequired = true
}
} else {
remoteClientState.showErrorAlert(error.localizedDescription)
}
}
isConnecting = false
}
}
}

@available(iOS 15, *)
private struct FocusedPasswordView: View {
@Binding var password: String

@FocusState private var isFocused: Bool

var body: some View {
SecureField("Password", text: $password)
.focused($isFocused)
.onAppear {
isFocused = true
}
}
}

#Preview {
UTMRemoteConnectView(remoteClientState: .init())
}
20 changes: 20 additions & 0 deletions Platform/iOS/UTMSingleWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ struct UTMSingleWindowView: View {
if let session = session {
VMWindowView(id: identifier!, isInteractive: isInteractive).environmentObject(session)
} else if isInteractive {
#if WITH_REMOTE
RemoteContentView(remoteClientState: data.remoteClient.state).environmentObject(data)
#else
ContentView().environmentObject(data)
#endif
} else {
VStack {
Text("Waiting for VM to connect to display...")
Expand Down Expand Up @@ -69,6 +73,22 @@ struct UTMSingleWindowView: View {
}
}

#if WITH_REMOTE
struct RemoteContentView: View {
@ObservedObject var remoteClientState: UTMRemoteClient.State

var body: some View {
if remoteClientState.isConnected {
ContentView()
.transition(.move(edge: .trailing))
} else {
UTMRemoteConnectView(remoteClientState: remoteClientState)
.transition(.move(edge: .leading))
}
}
}
#endif

struct UTMSingleWindowView_Previews: PreviewProvider {
static var previews: some View {
UTMSingleWindowView()
Expand Down
2 changes: 2 additions & 0 deletions Remote/UTMRemoteClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ extension UTMRemoteClient {

@Published var isScanning: Bool = false

@Published private(set) var isConnected: Bool = false

@Published var alertMessage: AlertMessage?

init() {
Expand Down
4 changes: 4 additions & 0 deletions UTM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@
CEDF83F9258AE24E0030E4AC /* UTMPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDF83F8258AE24E0030E4AC /* UTMPasteboard.swift */; };
CEDF83FA258AE24E0030E4AC /* UTMPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDF83F8258AE24E0030E4AC /* UTMPasteboard.swift */; };
CEE06B272B2FC89400A811AE /* UTMServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE06B262B2FC89400A811AE /* UTMServerView.swift */; };
CEE06B292B30013500A811AE /* UTMRemoteConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE06B282B30013500A811AE /* UTMRemoteConnectView.swift */; };
CEE7E936287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = CEE7E934287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m */; };
CEE7E937287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = CEE7E934287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m */; };
CEE7E938287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = CEE7E934287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m */; };
Expand Down Expand Up @@ -1991,6 +1992,7 @@
CEE0421024418F2E0001680F /* UTMLegacyQemuConfiguration+Miscellaneous.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UTMLegacyQemuConfiguration+Miscellaneous.h"; sourceTree = "<group>"; };
CEE0421124418F2E0001680F /* UTMLegacyQemuConfiguration+Miscellaneous.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UTMLegacyQemuConfiguration+Miscellaneous.m"; sourceTree = "<group>"; };
CEE06B262B2FC89400A811AE /* UTMServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMServerView.swift; sourceTree = "<group>"; };
CEE06B282B30013500A811AE /* UTMRemoteConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMRemoteConnectView.swift; sourceTree = "<group>"; };
CEE7E934287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UTMLegacyQemuConfiguration+Constants.m"; sourceTree = "<group>"; };
CEE7E935287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UTMLegacyQemuConfiguration+Constants.h"; sourceTree = "<group>"; };
CEE7ED472A90256100E6B4AB /* VMDisplayMetalViewController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VMDisplayMetalViewController+Private.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2639,6 +2641,7 @@
841E58CA28937EE200137A20 /* UTMExternalSceneDelegate.swift */,
841E58CD28937FED00137A20 /* UTMSingleWindowView.swift */,
842B9F8C28CC58B700031EE7 /* UTMPatches.swift */,
CEE06B282B30013500A811AE /* UTMRemoteConnectView.swift */,
84CE3DB02904C7A100FF068B /* UTMSettingsView.swift */,
CE2D954D24AD4F980059923A /* VMConfigNetworkPortForwardView.swift */,
84CF5DD2288DCE6400D01721 /* VMDisplayHostedView.swift */,
Expand Down Expand Up @@ -3935,6 +3938,7 @@
CEF7F5982AEEDCC400E34952 /* VMSettingsAddDeviceMenuView.swift in Sources */,
CEF7F5992AEEDCC400E34952 /* VMRemovableDrivesView.swift in Sources */,
CEF7F59A2AEEDCC400E34952 /* UTMQemuConfigurationDrive.swift in Sources */,
CEE06B292B30013500A811AE /* UTMRemoteConnectView.swift in Sources */,
CEF7F59B2AEEDCC400E34952 /* UTMQemuConfigurationSharing.swift in Sources */,
CEF7F59C2AEEDCC400E34952 /* ContentView.swift in Sources */,
CEF7F59D2AEEDCC400E34952 /* VMData.swift in Sources */,
Expand Down

0 comments on commit 57d565a

Please sign in to comment.