IOS updates
This commit is contained in:
@@ -3,6 +3,8 @@ import SwiftUI
|
||||
struct IOSSettingsView: View {
|
||||
@EnvironmentObject private var appState: AppState
|
||||
@EnvironmentObject private var workspaceStore: WorkspaceStore
|
||||
@State private var editor: ServerEditorItem?
|
||||
@State private var serverToDelete: SonarServer?
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
@@ -13,20 +15,140 @@ struct IOSSettingsView: View {
|
||||
LabeledContent("Role", value: workspaceStore.selectedWorkspace.role.rawValue.capitalized)
|
||||
}
|
||||
|
||||
Section("SonarQube") {
|
||||
if let server = appState.selectedServer {
|
||||
Section {
|
||||
if appState.servers.isEmpty {
|
||||
ContentUnavailableView("No SonarQube Servers", systemImage: "server.rack", description: Text("Add a server to load your SonarQube projects."))
|
||||
} else {
|
||||
ForEach(appState.servers) { server in
|
||||
Button {
|
||||
appState.selectServer(server.id)
|
||||
} label: {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: appState.selectedServerID == server.id ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(appState.selectedServerID == server.id ? Color.accentColor : Color.secondary)
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text(server.name).foregroundStyle(.primary)
|
||||
Text(server.baseURL).font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
||||
}
|
||||
Spacer()
|
||||
Button { editor = ServerEditorItem(server: server) } label: { Image(systemName: "pencil") }
|
||||
.buttonStyle(.borderless)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) { serverToDelete = server } label: { Label("Delete", systemImage: "trash") }
|
||||
Button { editor = ServerEditorItem(server: server) } label: { Label("Edit", systemImage: "pencil") }.tint(.blue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button { editor = ServerEditorItem(server: nil) } label: {
|
||||
Label("Add SonarQube Server", systemImage: "plus.circle.fill")
|
||||
}
|
||||
} header: { Text("SonarQube Servers") } footer: {
|
||||
Text("Access tokens are stored only in this device's Keychain and are not written to iCloud.")
|
||||
}
|
||||
|
||||
if let server = appState.selectedServer {
|
||||
Section("Active Server") {
|
||||
LabeledContent("Server", value: server.name)
|
||||
LabeledContent("Address", value: server.baseURL)
|
||||
Text("The SonarQube access token is stored only in this device's Keychain.")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
Text("No server is configured on this device.")
|
||||
.foregroundStyle(.secondary)
|
||||
if let version = appState.serverVersion { LabeledContent("Version", value: version) }
|
||||
Button("Refresh Projects") { Task { await appState.refresh() } }
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.sheet(item: $editor) { item in
|
||||
IOSServerEditorView(server: item.server)
|
||||
.environmentObject(appState)
|
||||
}
|
||||
.confirmationDialog("Delete this server?", isPresented: Binding(get: { serverToDelete != nil }, set: { if !$0 { serverToDelete = nil } }), titleVisibility: .visible) {
|
||||
if let server = serverToDelete {
|
||||
Button("Delete \(server.name)", role: .destructive) { appState.removeServer(server); serverToDelete = nil }
|
||||
}
|
||||
Button("Cancel", role: .cancel) { serverToDelete = nil }
|
||||
} message: { Text("The server configuration and its token will be removed from this device.") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct ServerEditorItem: Identifiable {
|
||||
let id = UUID()
|
||||
let server: SonarServer?
|
||||
}
|
||||
|
||||
private struct IOSServerEditorView: View {
|
||||
@EnvironmentObject private var appState: AppState
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
let server: SonarServer?
|
||||
|
||||
@State private var name: String
|
||||
@State private var url: String
|
||||
@State private var token = ""
|
||||
@State private var isTesting = false
|
||||
@State private var isSaving = false
|
||||
@State private var testResult: String?
|
||||
@State private var errorMessage: String?
|
||||
|
||||
init(server: SonarServer?) {
|
||||
self.server = server
|
||||
_name = State(initialValue: server?.name ?? "")
|
||||
_url = State(initialValue: server?.baseURL ?? "https://")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section("Server") {
|
||||
TextField("Name", text: $name).textContentType(.organizationName)
|
||||
TextField("https://sonarqube.example.com", text: $url)
|
||||
.textInputAutocapitalization(.never).autocorrectionDisabled()
|
||||
.keyboardType(.URL).textContentType(.URL)
|
||||
}
|
||||
Section("Authentication") {
|
||||
SecureField(server == nil ? "SonarQube API token" : "New token (leave blank to keep current)", text: $token)
|
||||
.textInputAutocapitalization(.never).autocorrectionDisabled()
|
||||
Text(server == nil ? "Create a SonarQube user token and paste it here." : "For security the existing token cannot be displayed. Leave this blank unless you want to replace it.")
|
||||
.font(.footnote).foregroundStyle(.secondary)
|
||||
}
|
||||
Section {
|
||||
Button { Task { await testConnection() } } label: {
|
||||
HStack { if isTesting { ProgressView() }; Label("Test Connection", systemImage: "network") }
|
||||
}.disabled(isTesting || url.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || (server == nil && token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty))
|
||||
if let testResult { Label(testResult, systemImage: "checkmark.circle.fill").foregroundStyle(.green) }
|
||||
if let errorMessage { Text(errorMessage).foregroundStyle(.red).font(.footnote) }
|
||||
}
|
||||
}
|
||||
.navigationTitle(server == nil ? "Add Server" : "Edit Server")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } }
|
||||
ToolbarItem(placement: .confirmationAction) { Button("Save") { save() }.disabled(isSaving || url.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || (server == nil && token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor private func testConnection() async {
|
||||
isTesting = true; errorMessage = nil; testResult = nil
|
||||
defer { isTesting = false }
|
||||
do {
|
||||
let result: String
|
||||
if let server, token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
result = try await appState.testServer(id: server.id)
|
||||
} else {
|
||||
result = try await appState.testServerConfiguration(name: name, url: url, token: token)
|
||||
}
|
||||
testResult = "Connected — \(result)"
|
||||
} catch { errorMessage = error.localizedDescription }
|
||||
}
|
||||
|
||||
private func save() {
|
||||
isSaving = true; errorMessage = nil
|
||||
do {
|
||||
if let server { try appState.updateServer(id: server.id, name: name, url: url, token: token.isEmpty ? nil : token) }
|
||||
else { try appState.addServer(name: name, url: url, token: token) }
|
||||
dismiss()
|
||||
} catch { errorMessage = error.localizedDescription; isSaving = false }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user