From 22cfcdece8664f19eeac82b7632699e516f79ac8 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Fri, 28 Apr 2023 00:02:14 -0500 Subject: [PATCH 01/10] Finding ssh keys in ~/.ssh and adding them to the ssh key dropdown in account settings --- CodeEdit.xcodeproj/project.pbxproj | 8 +-- .../AccountsSettingsDetailsView.swift | 52 +++++++++++++++++-- ...wift => AccountsSettingsProviderRow.swift} | 0 3 files changed, 51 insertions(+), 9 deletions(-) rename CodeEdit/Features/Settings/Pages/AccountsSettings/{AccoundsSettingsProviderRow.swift => AccountsSettingsProviderRow.swift} (100%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index fd2141d790..e0502a46b8 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -331,7 +331,7 @@ B6E41C7029DD157F0088F9F4 /* AccountsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */; }; B6E41C7429DD40010088F9F4 /* View+HideSidebarToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */; }; B6E41C7929DE02800088F9F4 /* AccountSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */; }; - B6E41C7C29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7B29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift */; }; + B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */; }; B6E41C8B29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */; }; B6E41C8F29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */; }; B6E41C9429DEAE260088F9F4 /* SourceControlAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E41C9329DEAE260088F9F4 /* SourceControlAccount.swift */; }; @@ -735,7 +735,7 @@ B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsView.swift; sourceTree = ""; }; B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+HideSidebarToggle.swift"; sourceTree = ""; }; B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSelectionView.swift; sourceTree = ""; }; - B6E41C7B29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccoundsSettingsProviderRow.swift; sourceTree = ""; }; + B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsProviderRow.swift; sourceTree = ""; }; B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsSigninView.swift; sourceTree = ""; }; B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsDetailsView.swift; sourceTree = ""; }; B6E41C9329DEAE260088F9F4 /* SourceControlAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlAccount.swift; sourceTree = ""; }; @@ -2178,7 +2178,7 @@ children = ( B6E41C9229DEAE0A0088F9F4 /* Models */, B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */, - B6E41C7B29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift */, + B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */, B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */, B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */, B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */, @@ -2747,7 +2747,7 @@ 587B9E9529301D8F00AC7927 /* BitBucketUser.swift in Sources */, 587B9E7C29301D8F00AC7927 /* GitHubRepositoryRouter.swift in Sources */, 286471AB27ED51FD0039369D /* ProjectNavigatorView.swift in Sources */, - B6E41C7C29DE2B110088F9F4 /* AccoundsSettingsProviderRow.swift in Sources */, + B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */, 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */, 6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */, B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */, diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index 78d6e35c86..0beb6a77d7 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -19,6 +19,27 @@ struct AccountsSettingsDetailsView: View { _account = account } + /// Default instance of the `FileManager` + private let filemanager = FileManager.default + + /// The base URL of settings. + /// + /// Points to `~/Library/Application Support/CodeEdit/` + internal var sshURL: URL { + filemanager + .homeDirectoryForCurrentUser + .appendingPathComponent(".ssh", isDirectory: true) + } + + func isSSHKey(_ contents: String) -> Bool { + if contents.starts(with: "-----BEGIN OPENSSH PRIVATE KEY-----\n") && + contents.hasSuffix("\n-----END OPENSSH PRIVATE KEY-----\n") { + return true + } else { + return false + } + } + var body: some View { SettingsDetailsView(title: account.description) { SettingsForm { @@ -44,11 +65,32 @@ struct AccountsSettingsDetailsView: View { + " using \(cloneUsing ? "SSH" : "HTTPS").") } .pickerStyle(.radioGroup) - - Picker("SSH Key", selection: $settings.accounts.sourceControlAccounts.sshKey) { - Text("None") - Text("Create New...") - Text("Choose...") + if cloneUsing { + Picker("SSH Key", selection: $settings.accounts.sourceControlAccounts.sshKey) { + Text("None") + .tag("") + Divider() + if let sshPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent( + ".ssh", + isDirectory: true + ) as URL? { + if let files = try? FileManager.default.contentsOfDirectory( + atPath: sshPath.path + ) { + ForEach(files, id: \.self) { filename in + let fileURL = sshPath.appendingPathComponent(filename) + if let contents = try? String(contentsOf: fileURL) { + if isSSHKey(contents) { + Text(filename).tag(contents) + } + } + } + Divider() + } + } + Text("Create New...") + Text("Choose...") + } } } footer: { HStack { diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccoundsSettingsProviderRow.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift similarity index 100% rename from CodeEdit/Features/Settings/Pages/AccountsSettings/AccoundsSettingsProviderRow.swift rename to CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsProviderRow.swift From 0eb6761666567556ca2c8ea904ce194a0dd41466 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Fri, 28 Apr 2023 18:04:38 -0500 Subject: [PATCH 02/10] Added jump actions to the SSH Keys Picker menu (Chose... and Create a new ssh key...). Added the non-working view for create a new ssh key. --- CodeEdit.xcodeproj/project.pbxproj | 4 ++ .../AccountsSettingsDetailsView.swift | 23 ++++++- .../AccountsSettings/CreateSSHKeyView.swift | 67 +++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index e0502a46b8..f2b4596d7d 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -323,6 +323,7 @@ B66A4E5329C91831004573B4 /* CodeEditCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5229C91831004573B4 /* CodeEditCommands.swift */; }; B66A4E5629C918A0004573B4 /* SceneID.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5529C918A0004573B4 /* SceneID.swift */; }; B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */; }; + B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */; }; B6C6A42A297716A500A3D28F /* TabBarItemCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A429297716A500A3D28F /* TabBarItemCloseButton.swift */; }; B6C6A42E29771A8D00A3D28F /* TabBarItemButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A42D29771A8D00A3D28F /* TabBarItemButtonStyle.swift */; }; B6C6A43029771F7100A3D28F /* TabBarItemBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A42F29771F7100A3D28F /* TabBarItemBackground.swift */; }; @@ -727,6 +728,7 @@ B66A4E5229C91831004573B4 /* CodeEditCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditCommands.swift; sourceTree = ""; }; B66A4E5529C918A0004573B4 /* SceneID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneID.swift; sourceTree = ""; }; B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = ""; }; + B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSSHKeyView.swift; sourceTree = ""; }; B6C6A429297716A500A3D28F /* TabBarItemCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItemCloseButton.swift; sourceTree = ""; }; B6C6A42D29771A8D00A3D28F /* TabBarItemButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarItemButtonStyle.swift; sourceTree = ""; }; B6C6A42F29771F7100A3D28F /* TabBarItemBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItemBackground.swift; sourceTree = ""; }; @@ -2182,6 +2184,7 @@ B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */, B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */, B6E41C8E29DE9CD80088F9F4 /* AccountsSettingsDetailsView.swift */, + B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */, ); path = AccountsSettings; sourceTree = ""; @@ -2772,6 +2775,7 @@ 5879821B292D92370085B254 /* SearchResultMatchModel.swift in Sources */, 587B9E5929301D8F00AC7927 /* GitCheckoutBranchView+CheckoutBranch.swift in Sources */, 58F2EB09292FB2B0004A9BDE /* TerminalSettings.swift in Sources */, + B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */, 587D9B742933BF5700BF7490 /* FileItem+Array.swift in Sources */, 587D9B772933BF5700BF7490 /* Live.swift in Sources */, B6EA200229DB7F81001BF195 /* View+ConstrainHeightToWindow.swift in Sources */, diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index 0beb6a77d7..6a649c8283 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -14,6 +14,8 @@ struct AccountsSettingsDetailsView: View { @State var cloneUsing: Bool = false @State var deleteConfirmationIsPresented: Bool = false + @State var prevSshKey: String = Settings[\.accounts].sourceControlAccounts.sshKey + @State var createSshKeyIsPresented: Bool = false init(_ account: Binding) { _account = account @@ -22,9 +24,7 @@ struct AccountsSettingsDetailsView: View { /// Default instance of the `FileManager` private let filemanager = FileManager.default - /// The base URL of settings. - /// - /// Points to `~/Library/Application Support/CodeEdit/` + /// The URL of the users ssh folder. Points to `~/.ssh` internal var sshURL: URL { filemanager .homeDirectoryForCurrentUser @@ -89,8 +89,25 @@ struct AccountsSettingsDetailsView: View { } } Text("Create New...") + .tag("CREATE_NEW") Text("Choose...") + .tag("CHOOSE") } + .onReceive([settings.accounts.sourceControlAccounts.sshKey].publisher.first()) { value in + if value == "CREATE_NEW" { + print("Create a new ssh key...") + createSshKeyIsPresented = true + settings.accounts.sourceControlAccounts.sshKey = prevSshKey + } else if value == "CHOOSE" { + print("Choose a ssh key...") + settings.accounts.sourceControlAccounts.sshKey = prevSshKey + } else { + prevSshKey = settings.accounts.sourceControlAccounts.sshKey + // TODO: Validate SSH key and check if it is uploaded to git provider. + // If not provide button to do so + } + } + .sheet(isPresented: $createSshKeyIsPresented, content: { CreateSSHKeyView() }) } } footer: { HStack { diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift new file mode 100644 index 0000000000..81ff28d4f2 --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/CreateSSHKeyView.swift @@ -0,0 +1,67 @@ +// +// CreateSSHKeyView.swift +// CodeEdit +// +// Created by Austin Condiff on 4/28/23. +// + +import SwiftUI + +struct CreateSSHKeyView: View { + @Environment(\.dismiss) private var dismiss + + enum KeyType: String, CaseIterable { + case ed25519 = "ED25519" + case ecdsa = "ECDSA" + case rsa = "RSA" + case dsa = "DSA" + } + + @State var selectedKeyType: KeyType = .ed25519 + @State var passphrase: String = "" + @State var confirmPassphrase: String = "" + + var body: some View { + VStack { + Form { + Section("Create SSH key") { + Picker("Key Type", selection: $selectedKeyType) { + Text(KeyType.ed25519.rawValue) + .tag(KeyType.ed25519) + Text(KeyType.ecdsa.rawValue) + .tag(KeyType.ecdsa) + Divider() + Group { + Text(KeyType.rsa.rawValue) + Text(" (less secure)").foregroundColor(.secondary) + } + .tag(KeyType.rsa) + Group { + Text(KeyType.dsa.rawValue) + Text(" (less secure)").foregroundColor(.secondary) + } + .tag(KeyType.dsa) + } + SecureField("Passphrase", text: $passphrase) + if !passphrase.isEmpty { + SecureField("Confirm Passphrase", text: $confirmPassphrase) + } + } + } + .formStyle(.grouped) + .fixedSize() + .scrollDisabled(true) + HStack { + Spacer() + Button("Cancel") { + dismiss() + } + Button("Create") { + // create the ssh key + dismiss() + } + .buttonStyle(.borderedProminent) + } + .padding(.horizontal, 20) + .padding(.bottom, 20) + } + } +} From 9bb145a93f7fe8f61f7701389532fba1e23f5b32 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sat, 29 Apr 2023 01:16:16 -0500 Subject: [PATCH 03/10] urlProtocol/cloneUsing is now a new enum called URLProtocol. SSH Key now using account.sshKey instead of settings.accounts.sshKey --- .../AccountsSettingsDetailsView.swift | 42 ++++++++++++------- .../AccountsSettingsSigninView.swift | 2 +- .../Models/SourceControlAccount.swift | 9 +++- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index 6a649c8283..0013954861 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -12,13 +12,13 @@ struct AccountsSettingsDetailsView: View { @Binding var account: SourceControlAccount - @State var cloneUsing: Bool = false @State var deleteConfirmationIsPresented: Bool = false - @State var prevSshKey: String = Settings[\.accounts].sourceControlAccounts.sshKey + @State var prevSshKey: String @State var createSshKeyIsPresented: Bool = false init(_ account: Binding) { _account = account + prevSshKey = account.sshKey.wrappedValue } /// Default instance of the `FileManager` @@ -31,7 +31,7 @@ struct AccountsSettingsDetailsView: View { .appendingPathComponent(".ssh", isDirectory: true) } - func isSSHKey(_ contents: String) -> Bool { + func isPrivateSSHKey(_ contents: String) -> Bool { if contents.starts(with: "-----BEGIN OPENSSH PRIVATE KEY-----\n") && contents.hasSuffix("\n-----END OPENSSH PRIVATE KEY-----\n") { return true @@ -40,6 +40,18 @@ struct AccountsSettingsDetailsView: View { } } + func isPublicSSHKey(_ contents: String) -> Bool { + let sshKeyPattern = "^ssh-(rsa|dss|ed25519)\\s+[A-Za-z0-9+/]+[=]{0,2}(\\s+.+)?$" + do { + let regex = try NSRegularExpression(pattern: sshKeyPattern) + let range = NSRange(location: 0, length: contents.utf16.count) + return regex.firstMatch(in: contents, options: [], range: range) != nil + } catch { + print("Error creating regular expression: \(error.localizedDescription)") + return false + } + } + var body: some View { SettingsDetailsView(title: account.description) { SettingsForm { @@ -54,19 +66,19 @@ struct AccountsSettingsDetailsView: View { } Section { - Picker(selection: $cloneUsing) { + Picker(selection: $account.urlProtocol) { Text("HTTPS") - .tag(false) // temporary + .tag(SourceControlAccount.URLProtocol.https) Text("SSH") - .tag(true) // temporary + .tag(SourceControlAccount.URLProtocol.ssh) } label: { Text("Clone Using") Text("New repositories will be cloned from \(account.provider.name)" - + " using \(cloneUsing ? "SSH" : "HTTPS").") + + " using \(account.urlProtocol.rawValue).") } .pickerStyle(.radioGroup) - if cloneUsing { - Picker("SSH Key", selection: $settings.accounts.sourceControlAccounts.sshKey) { + if account.urlProtocol == .ssh { + Picker("SSH Key", selection: $account.sshKey) { Text("None") .tag("") Divider() @@ -80,8 +92,8 @@ struct AccountsSettingsDetailsView: View { ForEach(files, id: \.self) { filename in let fileURL = sshPath.appendingPathComponent(filename) if let contents = try? String(contentsOf: fileURL) { - if isSSHKey(contents) { - Text(filename).tag(contents) + if isPublicSSHKey(contents) { + Text(filename.replacingOccurrences(of: ".pub", with: "")).tag(contents) } } } @@ -93,19 +105,19 @@ struct AccountsSettingsDetailsView: View { Text("Choose...") .tag("CHOOSE") } - .onReceive([settings.accounts.sourceControlAccounts.sshKey].publisher.first()) { value in + .onReceive([account.sshKey].publisher.first()) { value in if value == "CREATE_NEW" { print("Create a new ssh key...") createSshKeyIsPresented = true - settings.accounts.sourceControlAccounts.sshKey = prevSshKey + account.sshKey = prevSshKey } else if value == "CHOOSE" { print("Choose a ssh key...") - settings.accounts.sourceControlAccounts.sshKey = prevSshKey + account.sshKey = prevSshKey } else { - prevSshKey = settings.accounts.sourceControlAccounts.sshKey // TODO: Validate SSH key and check if it is uploaded to git provider. // If not provide button to do so } + prevSshKey = account.sshKey } .sheet(isPresented: $createSshKeyIsPresented, content: { CreateSSHKeyView() }) } diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift index fe64a765ef..a3ba5cfb9e 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift @@ -202,7 +202,7 @@ struct AccountsSettingsSigninView: View { description: provider.name, provider: provider, serverURL: providerLink, - urlProtocol: true, + urlProtocol: .https, sshKey: "", isTokenValid: true ) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift index 646b63222f..27e74dc38b 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/Models/SourceControlAccount.swift @@ -14,7 +14,7 @@ struct SourceControlAccount: Codable, Identifiable, Hashable { description: String, provider: Provider, serverURL: String, - urlProtocol: Bool, + urlProtocol: URLProtocol, sshKey: String, isTokenValid: Bool ) { @@ -35,10 +35,15 @@ struct SourceControlAccount: Codable, Identifiable, Hashable { var serverURL: String // TODO: Should we use an enum instead of a boolean here: // If true we use the HTTP protocol else if false we use SSH - var urlProtocol: Bool + var urlProtocol: URLProtocol var sshKey: String var isTokenValid: Bool + enum URLProtocol: String, Codable, CaseIterable { + case https = "HTTPS" + case ssh = "SSH" + } + enum Provider: Codable, CaseIterable, Identifiable { case bitbucketCloud case bitbucketServer From 842fe8c8d845b470143a9994b7ff47e10c2003b0 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 30 Apr 2023 18:45:31 -0500 Subject: [PATCH 04/10] Update AccountsSettingsDetailsView.swift --- .../Pages/AccountsSettings/AccountsSettingsDetailsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index 02ec10ba25..d6a9a79e39 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -8,9 +8,9 @@ import SwiftUI struct AccountsSettingsDetailsView: View { + @Environment(\.dismiss) private var dismiss @AppSettings(\.accounts.sourceControlAccounts.sshKey) var sshKey @AppSettings(\.accounts.sourceControlAccounts.gitAccounts) var gitAccounts - @Binding var account: SourceControlAccount @State var deleteConfirmationIsPresented: Bool = false From e813f0e5c7ada994381fdfd34af26e87fcce260e Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 30 Apr 2023 19:49:25 -0500 Subject: [PATCH 05/10] Fixed state initial value --- .../Pages/AccountsSettings/AccountsSettingsDetailsView.swift | 2 +- .../Settings/Pages/AccountsSettings/AccountsSettingsView.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index d6a9a79e39..b520788435 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -19,7 +19,7 @@ struct AccountsSettingsDetailsView: View { init(_ account: Binding) { _account = account - prevSshKey = account.sshKey.wrappedValue + _prevSshKey = State(initialValue: account.sshKey.wrappedValue) } /// Default instance of the `FileManager` diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift index 9baefee8b3..3e282cafc7 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift @@ -21,7 +21,7 @@ struct AccountsSettingsView: View { .foregroundColor(.secondary) .frame(maxWidth: .infinity, alignment: .center) } else { - ForEach($gitAccounts) { $account in + ForEach($gitAccounts, id: \.self) { $account in AccountsSettingsAccountLink($account) } } @@ -68,6 +68,7 @@ struct AccountsSettingsAccountLink: View { init(_ account: Binding) { _account = account + print(account.id) } var body: some View { From e3cdc4a8ef514ae44dbe18ab78c1d4d896702b8d Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 30 Apr 2023 21:04:17 -0500 Subject: [PATCH 06/10] Fixed binding issue where value changes wouldn't update view --- .../AccountsSettingsDetailsView.swift | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index b520788435..67299692fb 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -13,12 +13,14 @@ struct AccountsSettingsDetailsView: View { @AppSettings(\.accounts.sourceControlAccounts.gitAccounts) var gitAccounts @Binding var account: SourceControlAccount + @State var currentAccount: SourceControlAccount @State var deleteConfirmationIsPresented: Bool = false @State var prevSshKey: String @State var createSshKeyIsPresented: Bool = false init(_ account: Binding) { _account = account + _currentAccount = State(initialValue: account.wrappedValue) _prevSshKey = State(initialValue: account.sshKey.wrappedValue) } @@ -54,32 +56,32 @@ struct AccountsSettingsDetailsView: View { } var body: some View { - SettingsDetailsView(title: account.description) { + SettingsDetailsView(title: currentAccount.description) { SettingsForm { Section { LabeledContent("Account") { - Text(account.name) + Text(currentAccount.name) } - TextField("Description", text: $account.description) - if account.provider.baseURL == nil { - TextField("Server", text: $account.serverURL) + TextField("Description", text: $currentAccount.description) + if currentAccount.provider.baseURL == nil { + TextField("Server", text: $currentAccount.serverURL) } } Section { - Picker(selection: $account.urlProtocol) { + Picker(selection: $currentAccount.urlProtocol) { Text("HTTPS") .tag(SourceControlAccount.URLProtocol.https) Text("SSH") .tag(SourceControlAccount.URLProtocol.ssh) } label: { Text("Clone Using") - Text("New repositories will be cloned from \(account.provider.name)" - + " using \(account.urlProtocol.rawValue).") + Text("New repositories will be cloned from \(currentAccount.provider.name)" + + " using \(currentAccount.urlProtocol.rawValue).") } .pickerStyle(.radioGroup) - if account.urlProtocol == .ssh { - Picker("SSH Key", selection: $account.sshKey) { + if currentAccount.urlProtocol == .ssh { + Picker("SSH Key", selection: $currentAccount.sshKey) { Text("None") .tag("") Divider() @@ -106,19 +108,19 @@ struct AccountsSettingsDetailsView: View { Text("Choose...") .tag("CHOOSE") } - .onReceive([account.sshKey].publisher.first()) { value in + .onReceive([currentAccount.sshKey].publisher.first()) { value in if value == "CREATE_NEW" { print("Create a new ssh key...") createSshKeyIsPresented = true - account.sshKey = prevSshKey + currentAccount.sshKey = prevSshKey } else if value == "CHOOSE" { print("Choose a ssh key...") - account.sshKey = prevSshKey + currentAccount.sshKey = prevSshKey } else { // TODO: Validate SSH key and check if it is uploaded to git provider. // If not provide button to do so } - prevSshKey = account.sshKey + prevSshKey = currentAccount.sshKey } .sheet(isPresented: $createSshKeyIsPresented, content: { CreateSSHKeyView() }) } @@ -149,6 +151,9 @@ struct AccountsSettingsDetailsView: View { .padding(.top, 10) } } + .onChange(of: currentAccount) { newValue in + account = newValue + } } } From d6d3cf397c1d6df3ccff8fb110512c099e3447eb Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 30 Apr 2023 21:09:36 -0500 Subject: [PATCH 07/10] Cleaned up code --- CodeEdit.xcodeproj/project.pbxproj | 4 +++ .../AccountsSettingsAccountLink.swift | 36 +++++++++++++++++++ .../AccountsSettingsView.swift | 29 --------------- 3 files changed, 40 insertions(+), 29 deletions(-) create mode 100644 CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index d34679f13a..01653b5be8 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -323,6 +323,7 @@ B66A4E5329C91831004573B4 /* CodeEditCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5229C91831004573B4 /* CodeEditCommands.swift */; }; B66A4E5629C918A0004573B4 /* SceneID.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E5529C918A0004573B4 /* SceneID.swift */; }; B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */; }; + B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */; }; B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */; }; B6C6A42A297716A500A3D28F /* TabBarItemCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A429297716A500A3D28F /* TabBarItemCloseButton.swift */; }; B6C6A42E29771A8D00A3D28F /* TabBarItemButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C6A42D29771A8D00A3D28F /* TabBarItemButtonStyle.swift */; }; @@ -729,6 +730,7 @@ B66A4E5229C91831004573B4 /* CodeEditCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditCommands.swift; sourceTree = ""; }; B66A4E5529C918A0004573B4 /* SceneID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneID.swift; sourceTree = ""; }; B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = ""; }; + B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsAccountLink.swift; sourceTree = ""; }; B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSSHKeyView.swift; sourceTree = ""; }; B6C6A429297716A500A3D28F /* TabBarItemCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarItemCloseButton.swift; sourceTree = ""; }; B6C6A42D29771A8D00A3D28F /* TabBarItemButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarItemButtonStyle.swift; sourceTree = ""; }; @@ -2181,6 +2183,7 @@ children = ( B6E41C9229DEAE0A0088F9F4 /* Models */, B6E41C6F29DD157F0088F9F4 /* AccountsSettingsView.swift */, + B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */, B6E41C7B29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift */, B6E41C7829DE02800088F9F4 /* AccountSelectionView.swift */, B6E41C8A29DE7AE80088F9F4 /* AccountsSettingsSigninView.swift */, @@ -2752,6 +2755,7 @@ B6E41C7C29DE2B110088F9F4 /* AccountsSettingsProviderRow.swift in Sources */, 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */, 6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */, + B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */, B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */, 587B9DA629300ABD00AC7927 /* ToolbarBranchPicker.swift in Sources */, 58F2EB05292FB2B0004A9BDE /* Settings.swift in Sources */, diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift new file mode 100644 index 0000000000..4fbed5bd48 --- /dev/null +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift @@ -0,0 +1,36 @@ +// +// AccountsSettingsAccountLink.swift +// CodeEdit +// +// Created by Austin Condiff on 4/30/23. +// + +import SwiftUI + +struct AccountsSettingsAccountLink: View { + @Binding var account: SourceControlAccount + + init(_ account: Binding) { + _account = account + } + + var body: some View { + NavigationLink(destination: AccountsSettingsDetailsView($account)) { + Label { + Text(account.provider.name) + Text(account.name) + .font(.footnote) + .foregroundColor(.secondary) + } icon: { + Image(account.provider.iconName) + .resizable() + .aspectRatio(contentMode: .fill) + .cornerRadius(6) + .frame(width: 26, height: 26) + .padding(.top, 2) + .padding(.bottom, 2) + .padding(.leading, 2) + } + } + } +} diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift index 3e282cafc7..5f1e5cfbfa 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift @@ -62,32 +62,3 @@ struct AccountsSettingsView: View { .padding(20) } } - -struct AccountsSettingsAccountLink: View { - @Binding var account: SourceControlAccount - - init(_ account: Binding) { - _account = account - print(account.id) - } - - var body: some View { - NavigationLink(destination: AccountsSettingsDetailsView($account)) { - Label { - Text(account.provider.name) - Text(account.name) - .font(.footnote) - .foregroundColor(.secondary) - } icon: { - Image(account.provider.iconName) - .resizable() - .aspectRatio(contentMode: .fill) - .cornerRadius(6) - .frame(width: 26, height: 26) - .padding(.top, 2) - .padding(.bottom, 2) - .padding(.leading, 2) - } - } - } -} From 41c96a9aeecd37db520b2905e57bd4ff0418812e Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 30 Apr 2023 22:21:58 -0500 Subject: [PATCH 08/10] Fixed delete account error --- .../AccountsSettingsDetailsView.swift | 5 +++-- .../AccountsSettingsSigninView.swift | 3 +++ .../AccountsSettings/AccountsSettingsView.swift | 3 +-- CodeEdit/Features/Settings/SettingsView.swift | 17 ----------------- 4 files changed, 7 insertions(+), 21 deletions(-) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index 67299692fb..72f3526e60 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -159,7 +159,8 @@ struct AccountsSettingsDetailsView: View { private func handleAccountDelete() { // Delete account by finding the position of the account and remove by position - // We can abort if it is `nil` because account should exist - gitAccounts.remove(at: gitAccounts.firstIndex(of: account)!) + if let gitAccount = gitAccounts.firstIndex(of: account) { + gitAccounts.remove(at: gitAccount) + } } } diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift index e2312fe1de..9e86cfed84 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsSigninView.swift @@ -127,6 +127,9 @@ struct AccountsSettingsSigninView: View { } .formStyle(.grouped) .scrollDisabled(true) + .onSubmit { + signin() + } HStack { Button { addAccountSheetPresented.toggle() diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift index 5f1e5cfbfa..6d90c62cc2 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsView.swift @@ -53,9 +53,8 @@ struct AccountsSettingsView: View { Button("Close") { addAccountSheetPresented.toggle() selectedProvider = nil - } - .buttonStyle(.borderedProminent) + .buttonStyle(.borderedProminent) } .frame(maxWidth: .infinity, alignment: .trailing) } diff --git a/CodeEdit/Features/Settings/SettingsView.swift b/CodeEdit/Features/Settings/SettingsView.swift index 2b4235a15c..7535d7d740 100644 --- a/CodeEdit/Features/Settings/SettingsView.swift +++ b/CodeEdit/Features/Settings/SettingsView.swift @@ -59,23 +59,6 @@ struct SettingsView: View { .searchable(text: $searchText, placement: .sidebar, prompt: "Search") .scrollDisabled(true) } -// .safeAreaInset(edge: .top) { -// TextField("Search", text: $searchText, prompt: Text("Search")) -// .textFieldStyle(.roundedBorder) -// .padding(.horizontal, 10) -// .controlSize(.large) -// .introspectTextField { textField in -// let iconImage = NSImage(systemSymbolName: "magnifyingglass", accessibilityDescription: nil) -// -// let attachment = NSTextAttachment() -// attachment.contents = iconImage -// -// let cell = NSTextAttachmentCell(imageCell: iconImage) -// attachment.attachmentCell = cell -// -// textField.setCell_(NSSearchFieldCell.alloc().init()) -// } -// } } detail: { Group { switch selectedPage.name { From 44ecaf5917e4cc9f53517a58b32147d0db932437 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 30 Apr 2023 22:38:01 -0500 Subject: [PATCH 09/10] Using the ssh key file path instead of the file content as the tag --- .../Pages/AccountsSettings/AccountsSettingsDetailsView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index 72f3526e60..9122c70fe0 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -96,7 +96,8 @@ struct AccountsSettingsDetailsView: View { let fileURL = sshPath.appendingPathComponent(filename) if let contents = try? String(contentsOf: fileURL) { if isPublicSSHKey(contents) { - Text(filename.replacingOccurrences(of: ".pub", with: "")).tag(contents) + Text(filename.replacingOccurrences(of: ".pub", with: "")) + .tag(fileURL.path) } } } From 10306c0eee491c3db0cd204eb8e0cca093cfa27a Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 30 Apr 2023 23:15:06 -0500 Subject: [PATCH 10/10] Made Account window title update live --- CodeEdit.xcodeproj/project.pbxproj | 8 +- .../AccountsSettingsAccountLink.swift | 2 +- .../AccountsSettingsDetailsView.swift | 170 +++++++++--------- CodeEdit/Features/Settings/SettingsView.swift | 6 +- ...View+NavigationBarBackButtonVisible.swift} | 25 ++- 5 files changed, 103 insertions(+), 108 deletions(-) rename CodeEdit/Features/Settings/Views/{SettingsDetailsView.swift => View+NavigationBarBackButtonVisible.swift} (59%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 01653b5be8..b02eb0b214 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -313,7 +313,7 @@ B61A606929F4481A009B43F9 /* MonospacedFontPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */; }; B61DA9DF29D929E100BF4A43 /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */; }; B640A99E29E2184700715F20 /* SettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = B640A99D29E2184700715F20 /* SettingsForm.swift */; }; - B640A9A129E2188F00715F20 /* SettingsDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B640A9A029E2188F00715F20 /* SettingsDetailsView.swift */; }; + B640A9A129E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */; }; B658FB3427DA9E1000EA4DBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3327DA9E1000EA4DBD /* Assets.xcassets */; }; B658FB3727DA9E1000EA4DBD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */; }; B66A4E4529C8E86D004573B4 /* CommandsFixes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B66A4E4429C8E86D004573B4 /* CommandsFixes.swift */; }; @@ -715,7 +715,7 @@ B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonospacedFontPicker.swift; sourceTree = ""; }; B61DA9DE29D929E100BF4A43 /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = ""; }; B640A99D29E2184700715F20 /* SettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsForm.swift; sourceTree = ""; }; - B640A9A029E2188F00715F20 /* SettingsDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDetailsView.swift; sourceTree = ""; }; + B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+NavigationBarBackButtonVisible.swift"; sourceTree = ""; }; B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeEdit.app; sourceTree = BUILT_PRODUCTS_DIR; }; B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WorkspaceView.swift; sourceTree = ""; tabWidth = 4; }; B658FB3327DA9E1000EA4DBD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -2168,10 +2168,10 @@ children = ( B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */, B640A99D29E2184700715F20 /* SettingsForm.swift */, - B640A9A029E2188F00715F20 /* SettingsDetailsView.swift */, B6EA1FFF29DB7966001BF195 /* SettingsColorPicker.swift */, B61A606829F4481A009B43F9 /* MonospacedFontPicker.swift */, B61A606029F188AB009B43F9 /* ExternalLink.swift */, + B640A9A029E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift */, B6EA200129DB7F81001BF195 /* View+ConstrainHeightToWindow.swift */, B6E41C7329DD40010088F9F4 /* View+HideSidebarToggle.swift */, ); @@ -2729,7 +2729,7 @@ 587B9E8929301D8F00AC7927 /* GitHubGist.swift in Sources */, 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */, 58D01C94293167DC00C5B6B4 /* Color+HEX.swift in Sources */, - B640A9A129E2188F00715F20 /* SettingsDetailsView.swift in Sources */, + B640A9A129E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift in Sources */, 58798251292E78D80085B254 /* OtherFileView.swift in Sources */, 587D9B752933BF5700BF7490 /* FileItem.swift in Sources */, 587B9E7929301D8F00AC7927 /* GitHubIssueRouter.swift in Sources */, diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift index 4fbed5bd48..ed3621cd2a 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsAccountLink.swift @@ -17,7 +17,7 @@ struct AccountsSettingsAccountLink: View { var body: some View { NavigationLink(destination: AccountsSettingsDetailsView($account)) { Label { - Text(account.provider.name) + Text(account.description) Text(account.name) .font(.footnote) .foregroundColor(.secondary) diff --git a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift index 9122c70fe0..64e915932b 100644 --- a/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Pages/AccountsSettings/AccountsSettingsDetailsView.swift @@ -56,106 +56,106 @@ struct AccountsSettingsDetailsView: View { } var body: some View { - SettingsDetailsView(title: currentAccount.description) { - SettingsForm { - Section { - LabeledContent("Account") { - Text(currentAccount.name) - } - TextField("Description", text: $currentAccount.description) - if currentAccount.provider.baseURL == nil { - TextField("Server", text: $currentAccount.serverURL) - } + SettingsForm { + Section { + LabeledContent("Account") { + Text(currentAccount.name) + } + TextField("Description", text: $currentAccount.description) + if currentAccount.provider.baseURL == nil { + TextField("Server", text: $currentAccount.serverURL) } + } - Section { - Picker(selection: $currentAccount.urlProtocol) { - Text("HTTPS") - .tag(SourceControlAccount.URLProtocol.https) - Text("SSH") - .tag(SourceControlAccount.URLProtocol.ssh) - } label: { - Text("Clone Using") - Text("New repositories will be cloned from \(currentAccount.provider.name)" - + " using \(currentAccount.urlProtocol.rawValue).") - } - .pickerStyle(.radioGroup) - if currentAccount.urlProtocol == .ssh { - Picker("SSH Key", selection: $currentAccount.sshKey) { - Text("None") - .tag("") - Divider() - if let sshPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent( - ".ssh", - isDirectory: true - ) as URL? { - if let files = try? FileManager.default.contentsOfDirectory( - atPath: sshPath.path - ) { - ForEach(files, id: \.self) { filename in - let fileURL = sshPath.appendingPathComponent(filename) - if let contents = try? String(contentsOf: fileURL) { - if isPublicSSHKey(contents) { - Text(filename.replacingOccurrences(of: ".pub", with: "")) - .tag(fileURL.path) - } + Section { + Picker(selection: $currentAccount.urlProtocol) { + Text("HTTPS") + .tag(SourceControlAccount.URLProtocol.https) + Text("SSH") + .tag(SourceControlAccount.URLProtocol.ssh) + } label: { + Text("Clone Using") + Text("New repositories will be cloned from \(currentAccount.provider.name)" + + " using \(currentAccount.urlProtocol.rawValue).") + } + .pickerStyle(.radioGroup) + if currentAccount.urlProtocol == .ssh { + Picker("SSH Key", selection: $currentAccount.sshKey) { + Text("None") + .tag("") + Divider() + if let sshPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent( + ".ssh", + isDirectory: true + ) as URL? { + if let files = try? FileManager.default.contentsOfDirectory( + atPath: sshPath.path + ) { + ForEach(files, id: \.self) { filename in + let fileURL = sshPath.appendingPathComponent(filename) + if let contents = try? String(contentsOf: fileURL) { + if isPublicSSHKey(contents) { + Text(filename.replacingOccurrences(of: ".pub", with: "")) + .tag(fileURL.path) } } - Divider() } + Divider() } - Text("Create New...") - .tag("CREATE_NEW") - Text("Choose...") - .tag("CHOOSE") } - .onReceive([currentAccount.sshKey].publisher.first()) { value in - if value == "CREATE_NEW" { - print("Create a new ssh key...") - createSshKeyIsPresented = true - currentAccount.sshKey = prevSshKey - } else if value == "CHOOSE" { - print("Choose a ssh key...") - currentAccount.sshKey = prevSshKey - } else { - // TODO: Validate SSH key and check if it is uploaded to git provider. - // If not provide button to do so - } - prevSshKey = currentAccount.sshKey + Text("Create New...") + .tag("CREATE_NEW") + Text("Choose...") + .tag("CHOOSE") + } + .onReceive([currentAccount.sshKey].publisher.first()) { value in + if value == "CREATE_NEW" { + print("Create a new ssh key...") + createSshKeyIsPresented = true + currentAccount.sshKey = prevSshKey + } else if value == "CHOOSE" { + print("Choose a ssh key...") + currentAccount.sshKey = prevSshKey + } else { + // TODO: Validate SSH key and check if it is uploaded to git provider. + // If not provide button to do so } - .sheet(isPresented: $createSshKeyIsPresented, content: { CreateSSHKeyView() }) + prevSshKey = currentAccount.sshKey } - } footer: { - HStack { - Button("Delete Account...") { - deleteConfirmationIsPresented.toggle() + .sheet(isPresented: $createSshKeyIsPresented, content: { CreateSSHKeyView() }) + } + } footer: { + HStack { + Button("Delete Account...") { + deleteConfirmationIsPresented.toggle() + } + .alert( + Text("Are you sure you want to delete the account “\(account.description)”?"), + isPresented: $deleteConfirmationIsPresented + ) { + Button("OK") { + // Handle the account delete + handleAccountDelete() + dismiss() } - .alert( - Text("Are you sure you want to delete the account “\(account.description)”?"), - isPresented: $deleteConfirmationIsPresented - ) { - Button("OK") { - // Handle the account delete - handleAccountDelete() - dismiss() - } - Button("Cancel") { - // Handle the cancel, dismiss the alert - deleteConfirmationIsPresented.toggle() - } - } message: { - Text("Deleting this account will remove it from CodeEdit.") + Button("Cancel") { + // Handle the cancel, dismiss the alert + deleteConfirmationIsPresented.toggle() } - - Spacer() + } message: { + Text("Deleting this account will remove it from CodeEdit.") } - .padding(.top, 10) + + Spacer() } + .padding(.top, 10) } - .onChange(of: currentAccount) { newValue in - account = newValue - } } + .onChange(of: currentAccount) { newValue in + account = newValue + } + .navigationTitle(currentAccount.description) + .navigationBarBackButtonVisible() } private func handleAccountDelete() { diff --git a/CodeEdit/Features/Settings/SettingsView.swift b/CodeEdit/Features/Settings/SettingsView.swift index 7535d7d740..30e092ff4b 100644 --- a/CodeEdit/Features/Settings/SettingsView.swift +++ b/CodeEdit/Features/Settings/SettingsView.swift @@ -83,13 +83,13 @@ struct SettingsView: View { .navigationSplitViewColumnWidth(500) .hideSidebarToggle() .onAppear { - model.showingDetails = false + model.backButtonVisible = false } } .navigationTitle(selectedPage.name.rawValue) .toolbar { ToolbarItem(placement: .navigation) { - if !model.showingDetails { + if !model.backButtonVisible { Rectangle() .frame(width: 10) .opacity(0) @@ -104,6 +104,6 @@ struct SettingsView: View { } class SettingsViewModel: ObservableObject { - @Published var showingDetails: Bool = false + @Published var backButtonVisible: Bool = false @Published var scrolledToTop: Bool = false } diff --git a/CodeEdit/Features/Settings/Views/SettingsDetailsView.swift b/CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift similarity index 59% rename from CodeEdit/Features/Settings/Views/SettingsDetailsView.swift rename to CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift index 91f0c921a5..9c4dbb4e41 100644 --- a/CodeEdit/Features/Settings/Views/SettingsDetailsView.swift +++ b/CodeEdit/Features/Settings/Views/View+NavigationBarBackButtonVisible.swift @@ -1,5 +1,5 @@ // -// SettingsDetailsView.swift +// View+NavigationBarBackButtonVisible.swift // CodeEdit // // Created by Austin Condiff on 4/8/23. @@ -7,18 +7,12 @@ import SwiftUI -struct SettingsDetailsView: View { +struct NavigationBarBackButtonVisible: ViewModifier { @Environment(\.presentationMode) var presentationMode @EnvironmentObject var model: SettingsViewModel - let title: String - - @ViewBuilder - var content: Content - - var body: some View { + func body(content: Content) -> some View { content - .navigationTitle("") .toolbar { ToolbarItem(placement: .navigation) { Button { @@ -27,16 +21,17 @@ struct SettingsDetailsView: View { } label: { Image(systemName: "chevron.left") } - Text(title) } } .hideSidebarToggle() - .task { - let window = NSApp.windows.first { $0.identifier?.rawValue == "settings" }! - window.title = title - } .onAppear { - model.showingDetails = true + model.backButtonVisible = true } } } + +extension View { + func navigationBarBackButtonVisible() -> some View { + modifier(NavigationBarBackButtonVisible()) + } +}