mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 05:19:13 +00:00
bell ringing backend done now need to actually trigger it
added ring() to ring the bell terminal is now a fullscreencover can be dismissed and reopened or disconnected - need to make it keep the scrollback increased sleep between reads added support for title added setTitle function, defaults to username@server increased the buffer to 16384 bytes
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
VERSION = 1.0
|
VERSION = 1.0
|
||||||
BUILD = 3
|
BUILD = 27
|
||||||
|
|
||||||
// Configuration settings file format documentation can be found at:
|
// Configuration settings file format documentation can be found at:
|
||||||
// https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project
|
// https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A9083E3F2DF2225A0042906E /* libz.tbd */; };
|
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A9083E3F2DF2225A0042906E /* libz.tbd */; };
|
||||||
A92317282E07111E00ECE1E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A92317272E07111E00ECE1E6 /* SwiftTerm */; };
|
|
||||||
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92317292E07113100ECE1E6 /* TerminalController.swift */; };
|
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92317292E07113100ECE1E6 /* TerminalController.swift */; };
|
||||||
A923172D2E07138000ECE1E6 /* SSHTerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172C2E07138000ECE1E6 /* SSHTerminalView.swift */; };
|
A923172D2E07138000ECE1E6 /* SSHTerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172C2E07138000ECE1E6 /* SSHTerminalView.swift */; };
|
||||||
A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172E2E08851200ECE1E6 /* ShellView.swift */; };
|
A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A923172E2E08851200ECE1E6 /* ShellView.swift */; };
|
||||||
@@ -32,6 +31,7 @@
|
|||||||
A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545E2E056EDD009051BD /* KeychainLayer.swift */; };
|
A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545E2E056EDD009051BD /* KeychainLayer.swift */; };
|
||||||
A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; };
|
A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; };
|
||||||
A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; };
|
A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; };
|
||||||
|
A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A9A5871F2E0BF220006B31E6 /* SwiftTerm */; };
|
||||||
A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B15A992E0ABA0400F66E02 /* DialogView.swift */; };
|
A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B15A992E0ABA0400F66E02 /* DialogView.swift */; };
|
||||||
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
|
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
|
||||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
|
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
A95FAA542DF4B62900DE2F5A /* LibSSH.xcframework in Frameworks */,
|
A95FAA542DF4B62900DE2F5A /* LibSSH.xcframework in Frameworks */,
|
||||||
A92317282E07111E00ECE1E6 /* SwiftTerm in Frameworks */,
|
A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */,
|
||||||
A93143BE2DF4D0B300FCD5DB /* libpthread.tbd in Frameworks */,
|
A93143BE2DF4D0B300FCD5DB /* libpthread.tbd in Frameworks */,
|
||||||
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */,
|
A9083E402DF2226F0042906E /* libz.tbd in Frameworks */,
|
||||||
A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */,
|
A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */,
|
||||||
@@ -294,7 +294,7 @@
|
|||||||
);
|
);
|
||||||
name = ShhShell;
|
name = ShhShell;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
A92317272E07111E00ECE1E6 /* SwiftTerm */,
|
A9A5871F2E0BF220006B31E6 /* SwiftTerm */,
|
||||||
);
|
);
|
||||||
productName = ShhShell;
|
productName = ShhShell;
|
||||||
productReference = A925389A2DEE06DC007E0A18 /* ShhShell.app */;
|
productReference = A925389A2DEE06DC007E0A18 /* ShhShell.app */;
|
||||||
@@ -373,7 +373,7 @@
|
|||||||
mainGroup = A92538912DEE06DC007E0A18;
|
mainGroup = A92538912DEE06DC007E0A18;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */,
|
A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = A925389B2DEE06DC007E0A18 /* Products */;
|
productRefGroup = A925389B2DEE06DC007E0A18 /* Products */;
|
||||||
@@ -624,7 +624,7 @@
|
|||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_STRICT_CONCURRENCY = complete;
|
SWIFT_STRICT_CONCURRENCY = complete;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -660,7 +660,7 @@
|
|||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_STRICT_CONCURRENCY = complete;
|
SWIFT_STRICT_CONCURRENCY = complete;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -779,20 +779,20 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = {
|
A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/migueldeicaza/SwiftTerm";
|
repositoryURL = "https://github.com/migueldeicaza/SwiftTerm";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
branch = main;
|
||||||
minimumVersion = 1.2.5;
|
kind = branch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
A92317272E07111E00ECE1E6 /* SwiftTerm */ = {
|
A9A5871F2E0BF220006B31E6 /* SwiftTerm */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = A92317262E07111E00ECE1E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */;
|
package = A9A5871E2E0BF220006B31E6 /* XCRemoteSwiftPackageReference "SwiftTerm" */;
|
||||||
productName = SwiftTerm;
|
productName = SwiftTerm;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/migueldeicaza/SwiftTerm",
|
"location" : "https://github.com/migueldeicaza/SwiftTerm",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "e2b431dbf73f775fb4807a33e4572ffd3dc6933a",
|
"branch" : "main",
|
||||||
"version" : "1.2.5"
|
"revision" : "f8e6e08b5a8c8eb0b18b9c250f36121e55c68f49"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
|
|
||||||
// @Published var hostsManager = HostsManager()
|
// @Published var hostsManager = HostsManager()
|
||||||
|
|
||||||
|
@Published var title: String = ""
|
||||||
@Published var connected: Bool = false
|
@Published var connected: Bool = false
|
||||||
@Published var authorized: Bool = false
|
@Published var authorized: Bool = false
|
||||||
@Published var testSuceeded: Bool? = nil
|
@Published var testSuceeded: Bool? = nil
|
||||||
|
|
||||||
@Published var bell: UUID?
|
@Published var bell: UUID? = nil
|
||||||
|
|
||||||
@Published var host: Host
|
@Published var host: Host
|
||||||
|
|
||||||
@@ -76,6 +77,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
openShell()
|
openShell()
|
||||||
|
setTitle("\(host.username)@\(host.address)")
|
||||||
ssh_channel_request_env(channel, "TERM", "xterm-256color")
|
ssh_channel_request_env(channel, "TERM", "xterm-256color")
|
||||||
ssh_channel_request_env(channel, "LANG", "en_US.UTF-8")
|
ssh_channel_request_env(channel, "LANG", "en_US.UTF-8")
|
||||||
ssh_channel_request_env(channel, "LC_ALL", "en_US.UTF-8")
|
ssh_channel_request_env(channel, "LC_ALL", "en_US.UTF-8")
|
||||||
@@ -132,6 +134,17 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ring() {
|
||||||
|
withAnimation { bell = UUID() }
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
|
||||||
|
withAnimation { self.bell = nil }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTitle(_ newTitle: String) {
|
||||||
|
self.title = newTitle
|
||||||
|
}
|
||||||
|
|
||||||
func testExec() {
|
func testExec() {
|
||||||
if ssh_is_connected(session) == 0 {
|
if ssh_is_connected(session) == 0 {
|
||||||
withAnimation { testSuceeded = false }
|
withAnimation { testSuceeded = false }
|
||||||
@@ -208,6 +221,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: auth
|
||||||
func authWithPubkey(pub pubInp: Data, priv privInp: Data, pass: String) throws(KeyError) {
|
func authWithPubkey(pub pubInp: Data, priv privInp: Data, pass: String) throws(KeyError) {
|
||||||
guard session != nil else {
|
guard session != nil else {
|
||||||
withAnimation { authorized = false }
|
withAnimation { authorized = false }
|
||||||
@@ -309,6 +323,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
print(recievedMethod)
|
print(recievedMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: shell
|
||||||
func openShell() {
|
func openShell() {
|
||||||
var status: CInt
|
var status: CInt
|
||||||
|
|
||||||
@@ -346,7 +361,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer: [CChar] = Array(repeating: 0, count: 4096)
|
var buffer: [CChar] = Array(repeating: 0, count: 16_384)
|
||||||
let nbytes = ssh_channel_read_nonblocking(channel, &buffer, UInt32(buffer.count), 0)
|
let nbytes = ssh_channel_read_nonblocking(channel, &buffer, UInt32(buffer.count), 0)
|
||||||
|
|
||||||
guard nbytes > 0 else { return nil }
|
guard nbytes > 0 else { return nil }
|
||||||
@@ -354,7 +369,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
|||||||
let data = Data(bytes: buffer, count: Int(nbytes))
|
let data = Data(bytes: buffer, count: Int(nbytes))
|
||||||
if let string = String(data: data, encoding: .utf8) {
|
if let string = String(data: data, encoding: .utf8) {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!)
|
// print(String(data: Data(bytes: buffer, count: Int(nbytes)), encoding: .utf8)!)
|
||||||
#endif
|
#endif
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ struct ConnectionView: View {
|
|||||||
@State var pubkeyStr: String = ""
|
@State var pubkeyStr: String = ""
|
||||||
@State var privkeyStr: String = ""
|
@State var privkeyStr: String = ""
|
||||||
|
|
||||||
|
@State var showTerminal: Bool = false
|
||||||
@State var privPickerPresented: Bool = false
|
@State var privPickerPresented: Bool = false
|
||||||
@State var pubPickerPresented: Bool = false
|
@State var pubPickerPresented: Bool = false
|
||||||
|
|
||||||
@@ -128,12 +129,12 @@ struct ConnectionView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink() {
|
Button() {
|
||||||
ShellView(handler: handler)
|
showTerminal.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
Label("Open Terminal", systemImage: "apple.terminal")
|
Label("Show Terminal", systemImage: "apple.terminal")
|
||||||
}
|
}
|
||||||
.disabled(!(handler.connected && handler.authorized))
|
.disabled(!handler.connected || !handler.authorized)
|
||||||
|
|
||||||
Button() {
|
Button() {
|
||||||
if handler.authorized && handler.connected {
|
if handler.authorized && handler.connected {
|
||||||
@@ -169,6 +170,7 @@ struct ConnectionView: View {
|
|||||||
ToolbarItem() {
|
ToolbarItem() {
|
||||||
Button() {
|
Button() {
|
||||||
handler.go()
|
handler.go()
|
||||||
|
showTerminal = handler.connected && handler.authorized
|
||||||
} label: {
|
} label: {
|
||||||
Label(
|
Label(
|
||||||
handler.connected ? "Disconnect" : "Connect",
|
handler.connected ? "Disconnect" : "Connect",
|
||||||
@@ -178,6 +180,9 @@ struct ConnectionView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.fullScreenCover(isPresented: $showTerminal) {
|
||||||
|
ShellView(handler: handler)
|
||||||
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
guard hostsManager.getHostMatching(handler.host) == handler.host else {
|
guard hostsManager.getHostMatching(handler.host) == handler.host else {
|
||||||
hostsManager.updateHost(handler.host)
|
hostsManager.updateHost(handler.host)
|
||||||
|
|||||||
@@ -26,15 +26,15 @@ struct HostsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//proves that u can connect to multiple at the same time
|
//proves that u can connect to multiple at the same time
|
||||||
// NavigationLink() {
|
NavigationLink() {
|
||||||
// ForEach(hostsManager.savedHosts) { host in
|
ForEach(hostsManager.savedHosts) { host in
|
||||||
// let miniHandler = SSHHandler(host: host)
|
let miniHandler = SSHHandler(host: host)
|
||||||
// TerminalController(handler: miniHandler)
|
TerminalController(handler: miniHandler)
|
||||||
// .onAppear { miniHandler.go() }
|
.onAppear { miniHandler.go() }
|
||||||
// }
|
}
|
||||||
// } label: {
|
} label: {
|
||||||
// Label("multiview", systemImage: "square.split.2x2")
|
Label("multiview", systemImage: "square.split.2x2")
|
||||||
// }
|
}
|
||||||
|
|
||||||
ForEach(hostsManager.savedHosts) { host in
|
ForEach(hostsManager.savedHosts) { host in
|
||||||
NavigationLink() {
|
NavigationLink() {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
|
|||||||
await self.feed(text: read)
|
await self.feed(text: read)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try? await Task.sleep(nanoseconds: 1_000_000) //1ms
|
try? await Task.sleep(nanoseconds: 10_000_000) //10ms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
|
|||||||
await self.feed(text: read)
|
await self.feed(text: read)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Task{ try? await Task.sleep(nanoseconds: 1_000_000) }
|
Task{ try? await Task.sleep(nanoseconds: 10_000_000) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,9 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
nonisolated public func setTerminalTitle(source: TerminalView, title: String) {
|
nonisolated public func setTerminalTitle(source: TerminalView, title: String) {
|
||||||
print("set title to \(title)")
|
Task {
|
||||||
|
await MainActor.run { handler?.setTitle(title) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
|
public func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ struct ShellView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
if handler.bell != nil {
|
||||||
|
Text("🔔")
|
||||||
|
.font(.title)
|
||||||
|
.transition(.scale)
|
||||||
|
}
|
||||||
if !handler.connected {
|
if !handler.connected {
|
||||||
DialogView(handler: handler, showDialog: !handler.connected)
|
DialogView(handler: handler, showDialog: !handler.connected)
|
||||||
}
|
}
|
||||||
@@ -27,18 +32,27 @@ struct ShellView: View {
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem {
|
ToolbarItem {
|
||||||
Button() {
|
Button() {
|
||||||
handler.go()
|
Task {
|
||||||
if !handler.connected {
|
await handler.disconnect()
|
||||||
dismiss()
|
if !handler.connected { dismiss() }
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label(
|
Label("Disconnect", systemImage: "xmark.app.fill")
|
||||||
handler.connected ? "Disconnect" : "Connect",
|
}
|
||||||
systemImage: handler.connected ? "xmark.app.fill" : "power"
|
}
|
||||||
)
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button() {
|
||||||
|
dismiss()
|
||||||
|
} label: {
|
||||||
|
Label("Close", systemImage: "arrow.down.right.and.arrow.up.left")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: handler.connected) { _ in
|
||||||
|
if !handler.connected { dismiss() }
|
||||||
|
}
|
||||||
|
.navigationTitle(handler.title)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user