From 1e730582c06664f670f84ee1d42cac97b4409544 Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Sun, 29 Jun 2025 18:51:52 +0100 Subject: [PATCH] ui background matches theme ui bg is 70% opacity --- ShhShell/Views/ContentView.swift | 51 ++++---- ShhShell/Views/Hosts/ConnectionView.swift | 71 +++++------ ShhShell/Views/Keys/HostkeysView.swift | 63 +++++----- ShhShell/Views/Keys/KeyDetailView.swift | 105 +++++++++-------- ShhShell/Views/Keys/KeyManagerView.swift | 45 +++---- ShhShell/Views/Themes/ThemeManagerView.swift | 118 ++++++++++--------- 6 files changed, 240 insertions(+), 213 deletions(-) diff --git a/ShhShell/Views/ContentView.swift b/ShhShell/Views/ContentView.swift index 8e122a5..1fa5d9d 100644 --- a/ShhShell/Views/ContentView.swift +++ b/ShhShell/Views/ContentView.swift @@ -14,30 +14,35 @@ struct ContentView: View { var body: some View { NavigationStack { - List { - SessionsListView( - handler: handler, - hostsManager: hostsManager, - keyManager: keyManager - ) - - HostsView( - handler: handler, - hostsManager: hostsManager, - keyManager: keyManager - ) - - NavigationLink { - KeyManagerView(hostsManager: hostsManager, keyManager: keyManager) - } label: { - Label("Keys", systemImage: "key.fill") - } - - NavigationLink { - HostkeysView(hostsManager: hostsManager) - } label: { - Label("Hostkey Fingerprints", systemImage: "lock.display") + ZStack { + hostsManager.selectedTheme.background.suiColor.opacity(0.7) + .ignoresSafeArea(.all) + List { + SessionsListView( + handler: handler, + hostsManager: hostsManager, + keyManager: keyManager + ) + + HostsView( + handler: handler, + hostsManager: hostsManager, + keyManager: keyManager + ) + + NavigationLink { + KeyManagerView(hostsManager: hostsManager, keyManager: keyManager) + } label: { + Label("Keys", systemImage: "key.fill") + } + + NavigationLink { + HostkeysView(hostsManager: hostsManager) + } label: { + Label("Hostkey Fingerprints", systemImage: "lock.display") + } } + .scrollContentBackground(.hidden) } } } diff --git a/ShhShell/Views/Hosts/ConnectionView.swift b/ShhShell/Views/Hosts/ConnectionView.swift index 138ab06..8850f25 100644 --- a/ShhShell/Views/Hosts/ConnectionView.swift +++ b/ShhShell/Views/Hosts/ConnectionView.swift @@ -24,7 +24,9 @@ struct ConnectionView: View { @State var hostKeyChangedAlert: Bool = false var body: some View { - NavigationStack { + ZStack { + hostsManager.selectedTheme.background.suiColor.opacity(0.7) + .ignoresSafeArea(.all) List { Section { ScrollView(.horizontal) { @@ -128,6 +130,34 @@ struct ConnectionView: View { } } } + .scrollContentBackground(.hidden) + .transition(.opacity) + .onChange(of: handler.host.key) { _ in + guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return } + guard handler.host.key == previousKnownHost.key else { + hostKeyChangedAlert = true + return + } + } + .onDisappear { + hostsManager.updateHost(handler.host) + } + .task { + if let publicKeyData = handler.host.publicKey { + pubkeyStr = String(data: publicKeyData, encoding: .utf8) ?? "" + } + if let privateKeyData = handler.host.privateKey { + privkeyStr = String(data: privateKeyData, encoding: .utf8) ?? "" + } + } + .onAppear { + if shellView == nil { + shellView = ShellView(handler: handler, hostsManager: hostsManager) + } + } + .onAppear { + hostsManager.addHostIfNeeded(handler.host) + } .alert("Hostkey changed", isPresented: $hostKeyChangedAlert) { Button("Accept New Hostkey", role: .destructive) { hostsManager.updateHost(handler.host) @@ -141,7 +171,6 @@ struct ConnectionView: View { } message: { Text("Expected \(handler.host.key ?? "nil")\nbut recieved \(handler.getHostkey() ?? "nil") from the server") } - .transition(.opacity) .toolbar { ToolbarItem() { Button() { @@ -156,40 +185,14 @@ struct ConnectionView: View { .disabled(handler.hostInvalid()) } } - } - .fullScreenCover(isPresented: $showTerminal) { - if let shellView { - shellView - } else { - Text("no shellview") + .fullScreenCover(isPresented: $showTerminal) { + if let shellView { + shellView + } else { + Text("no shellview") + } } } - .onChange(of: handler.host.key) { _ in - guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return } - guard handler.host.key == previousKnownHost.key else { - hostKeyChangedAlert = true - return - } - } - .onDisappear { - hostsManager.updateHost(handler.host) - } - .task { - if let publicKeyData = handler.host.publicKey { - pubkeyStr = String(data: publicKeyData, encoding: .utf8) ?? "" - } - if let privateKeyData = handler.host.privateKey { - privkeyStr = String(data: privateKeyData, encoding: .utf8) ?? "" - } - } - .onAppear { - if shellView == nil { - shellView = ShellView(handler: handler, hostsManager: hostsManager) - } - } - .onAppear { - hostsManager.addHostIfNeeded(handler.host) - } } } diff --git a/ShhShell/Views/Keys/HostkeysView.swift b/ShhShell/Views/Keys/HostkeysView.swift index 006d037..b559ff0 100644 --- a/ShhShell/Views/Keys/HostkeysView.swift +++ b/ShhShell/Views/Keys/HostkeysView.swift @@ -10,47 +10,52 @@ import SwiftUI struct HostkeysView: View { @ObservedObject var hostsManager: HostsManager - var body: some View { - NavigationStack { - List { - if hostsManager.hosts.isEmpty { - VStack(alignment: .leading) { - Text("Looking empty 'round here...") - .font(.title3) - .bold() - .padding(.bottom) + var body: some View { + ZStack { + hostsManager.selectedTheme.background.suiColor.opacity(0.7) + .ignoresSafeArea(.all) + NavigationStack { + List { + if hostsManager.hosts.isEmpty { VStack(alignment: .leading) { - Text("Connect to some hosts to collect more hostkeys!") + Text("Looking empty 'round here...") + .font(.title3) + .bold() .padding(.bottom) - Text("ShhShell remembers hostkey fingerprints for you, and can alert you if they change.") - .font(.subheadline) - Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.") - .font(.subheadline) + VStack(alignment: .leading) { + Text("Connect to some hosts to collect more hostkeys!") + .padding(.bottom) + Text("ShhShell remembers hostkey fingerprints for you, and can alert you if they change.") + .font(.subheadline) + Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.") + .font(.subheadline) + } } } - } - - ForEach(hostsManager.hosts) { host in - VStack(alignment: .leading) { - if !host.name.isEmpty { - Text("name") + + ForEach(hostsManager.hosts) { host in + VStack(alignment: .leading) { + if !host.name.isEmpty { + Text("name") + .foregroundStyle(.gray) + .font(.caption) + Text(host.name) + .bold() + } + Text("address") .foregroundStyle(.gray) .font(.caption) - Text(host.name) + Text(host.address) .bold() + Text(host.key ?? "nil") } - Text("address") - .foregroundStyle(.gray) - .font(.caption) - Text(host.address) - .bold() - Text(host.key ?? "nil") } } + .scrollContentBackground(.hidden) + .navigationTitle("Hostkeys") } - .navigationTitle("Hostkeys") } - } + } } #Preview { diff --git a/ShhShell/Views/Keys/KeyDetailView.swift b/ShhShell/Views/Keys/KeyDetailView.swift index cfeeedb..fbd7e62 100644 --- a/ShhShell/Views/Keys/KeyDetailView.swift +++ b/ShhShell/Views/Keys/KeyDetailView.swift @@ -12,63 +12,68 @@ struct KeyDetailView: View { @State var keypair: Keypair @State private var reveal: Bool = false - var body: some View { - List { - VStack(alignment: .leading) { - Text("Used on") - .bold() - ForEach(hostsManager.getHostsKeysUsedOn([keypair])) { host in - HStack { - SymbolPreview(symbol: host.symbol, label: host.label) - .frame(width: 40, height: 40) - Text(hostsManager.makeLabel(forHost: host)) - } - } - } - VStack(alignment: .leading) { - Text("Public key") - .bold() - Text(String(data: keypair.publicKey!, encoding: .utf8) ?? "nil") - } - VStack(alignment: .leading) { - Text("Private key") - .bold() - .frame(maxWidth: .infinity) - ZStack(alignment: .center) { - Text(String(data: keypair.privateKey!, encoding: .utf8) ?? "nil") - .blur(radius: reveal ? 0 : 5) - VStack { - Image(systemName: "eye.slash.fill") - .resizable().scaledToFit() - .frame(width: 50) - Text("Tap to reveal") - } - .opacity(reveal ? 0 : 1) - } - .frame(maxWidth: .infinity) - .onTapGesture { - Task { - if !reveal { - guard await hostsManager.authWithBiometrics() else { return } + var body: some View { + ZStack { + hostsManager.selectedTheme.background.suiColor.opacity(0.7) + .ignoresSafeArea(.all) + List { + VStack(alignment: .leading) { + Text("Used on") + .bold() + ForEach(hostsManager.getHostsKeysUsedOn([keypair])) { host in + HStack { + SymbolPreview(symbol: host.symbol, label: host.label) + .frame(width: 40, height: 40) + Text(hostsManager.makeLabel(forHost: host)) } - withAnimation(.spring) { reveal.toggle() } } } - } - - Button { - Task { - guard await hostsManager.authWithBiometrics() else { return } - if let privateKey = keypair.privateKey { - UIPasteboard.general.string = String(data: privateKey, encoding: .utf8) + VStack(alignment: .leading) { + Text("Public key") + .bold() + Text(String(data: keypair.publicKey!, encoding: .utf8) ?? "nil") + } + VStack(alignment: .leading) { + Text("Private key") + .bold() + .frame(maxWidth: .infinity) + ZStack(alignment: .center) { + Text(String(data: keypair.privateKey!, encoding: .utf8) ?? "nil") + .blur(radius: reveal ? 0 : 5) + VStack { + Image(systemName: "eye.slash.fill") + .resizable().scaledToFit() + .frame(width: 50) + Text("Tap to reveal") + } + .opacity(reveal ? 0 : 1) + } + .frame(maxWidth: .infinity) + .onTapGesture { + Task { + if !reveal { + guard await hostsManager.authWithBiometrics() else { return } + } + withAnimation(.spring) { reveal.toggle() } + } } } - } label: { - CenteredLabel(title: "Copy private key", systemName: "document.on.document") + + Button { + Task { + guard await hostsManager.authWithBiometrics() else { return } + if let privateKey = keypair.privateKey { + UIPasteboard.general.string = String(data: privateKey, encoding: .utf8) + } + } + } label: { + CenteredLabel(title: "Copy private key", systemName: "document.on.document") + } + .listRowSeparator(.hidden) } - .listRowSeparator(.hidden) + .scrollContentBackground(.hidden) } - } + } } #Preview { diff --git a/ShhShell/Views/Keys/KeyManagerView.swift b/ShhShell/Views/Keys/KeyManagerView.swift index 5c664ea..e8cf15d 100644 --- a/ShhShell/Views/Keys/KeyManagerView.swift +++ b/ShhShell/Views/Keys/KeyManagerView.swift @@ -12,32 +12,37 @@ struct KeyManagerView: View { @ObservedObject var keyManager: KeyManager var body: some View { - NavigationStack { - List { - Section { - ForEach(hostsManager.getKeys()) { keypair in - NavigationLink { - KeyDetailView(hostsManager: hostsManager, keypair: keypair) - } label: { - if let publicKey = keypair.publicKey { - Text(String(data: publicKey, encoding: .utf8) ?? "nil") + ZStack { + hostsManager.selectedTheme.background.suiColor.opacity(0.7) + .ignoresSafeArea(.all) + NavigationStack { + List { + Section { + ForEach(hostsManager.getKeys()) { keypair in + NavigationLink { + KeyDetailView(hostsManager: hostsManager, keypair: keypair) + } label: { + if let publicKey = keypair.publicKey { + Text(String(data: publicKey, encoding: .utf8) ?? "nil") + } } } } - } - - Button("ed25519") { - keyManager.generateEd25519() - } - Button("rsa") { - do { - try keyManager.generateRSA() - } catch { - print(error.localizedDescription) + + Button("ed25519") { + keyManager.generateEd25519() + } + Button("rsa") { + do { + try keyManager.generateRSA() + } catch { + print(error.localizedDescription) + } } } + .scrollContentBackground(.hidden) + .navigationTitle("Keys") } - .navigationTitle("Keys") } } } diff --git a/ShhShell/Views/Themes/ThemeManagerView.swift b/ShhShell/Views/Themes/ThemeManagerView.swift index e19996c..2166ae0 100644 --- a/ShhShell/Views/Themes/ThemeManagerView.swift +++ b/ShhShell/Views/Themes/ThemeManagerView.swift @@ -25,75 +25,79 @@ struct ThemeManagerView: View { ) var body: some View { - GeometryReader { geo in - let columns: Int = Int(geo.size.width)/200 - let layout = Array(repeating: grid, count: columns) - ScrollView { - if hostsManager.themes.isEmpty { - VStack(alignment: .leading) { - Image(systemName: "paintpalette") - .resizable().scaledToFit() - .symbolRenderingMode(.multicolor) - .frame(width: 50) - Text("No themes (yet)") - .font(.title) - .padding(.vertical, 10) - .bold() - Text("Tap the Safari icon at the top right to find themes!") - Text("Once you find one that you like, copy it's link and enter it here using the link button.") + ZStack { + hostsManager.selectedTheme.background.suiColor.opacity(0.7) + .ignoresSafeArea(.all) + GeometryReader { geo in + let columns: Int = Int(geo.size.width)/200 + let layout = Array(repeating: grid, count: columns) + ScrollView { + if hostsManager.themes.isEmpty { + VStack(alignment: .leading) { + Image(systemName: "paintpalette") + .resizable().scaledToFit() + .symbolRenderingMode(.multicolor) + .frame(width: 50) + Text("No themes (yet)") + .font(.title) + .padding(.vertical, 10) + .bold() + Text("Tap the Safari icon at the top right to find themes!") + Text("Once you find one that you like, copy it's link and enter it here using the link button.") + } + } else { + LazyVGrid(columns: layout, alignment: .center, spacing: 8) { + ForEach(hostsManager.themes) { theme in + ThemePreview(hostsManager: hostsManager, theme: theme, canModify: true) + } + } + .padding(.horizontal) + .animation(.default, value: hostsManager.themes) + } + + HStack { + Text("Built-in Themes") + .padding(.top) + .padding(.horizontal) + .font(.headline) + Spacer() } - } else { LazyVGrid(columns: layout, alignment: .center, spacing: 8) { - ForEach(hostsManager.themes) { theme in - ThemePreview(hostsManager: hostsManager, theme: theme, canModify: true) + ForEach(Theme.builtinThemes) { theme in + ThemePreview(hostsManager: hostsManager, theme: theme, canModify: false) } } .padding(.horizontal) .animation(.default, value: hostsManager.themes) } - - HStack { - Text("Built-in Themes") - .padding(.top) - .padding(.horizontal) - .font(.headline) - Spacer() - } - LazyVGrid(columns: layout, alignment: .center, spacing: 8) { - ForEach(Theme.builtinThemes) { theme in - ThemePreview(hostsManager: hostsManager, theme: theme, canModify: false) - } - } - .padding(.horizontal) - .animation(.default, value: hostsManager.themes) - } - .navigationTitle("Themes") - .alert("Enter URL", isPresented: $showAlert) { - TextField("", text: $importURL, prompt: Text("URL")) - Button("Cancel") {} - Button() { - hostsManager.downloadTheme(fromUrl: URL(string: importURL)) - importURL = "" - } label: { - Label("Import", systemImage: "square.and.arrow.down") - .bold() - } - } - .toolbar { - ToolbarItem() { + .navigationTitle("Themes") + .alert("Enter URL", isPresented: $showAlert) { + TextField("", text: $importURL, prompt: Text("URL")) + Button("Cancel") {} Button() { - UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!) + hostsManager.downloadTheme(fromUrl: URL(string: importURL)) + importURL = "" } label: { - Label("Open themes site", systemImage: "safari") + Label("Import", systemImage: "square.and.arrow.down") + .bold() } } - ToolbarItem() { - Button() { - showAlert.toggle() - } label: { - Label("From URL", systemImage: "link") + .toolbar { + ToolbarItem() { + Button() { + UIApplication.shared.open(URL(string: "https://iterm2colorschemes.com")!) + } label: { + Label("Open themes site", systemImage: "safari") + } + } + ToolbarItem() { + Button() { + showAlert.toggle() + } label: { + Label("From URL", systemImage: "link") + } + } - } } }