ui background matches theme

ui bg is 70% opacity
This commit is contained in:
neon443
2025-06-29 18:51:52 +01:00
parent a4a71968fe
commit 1e730582c0
6 changed files with 240 additions and 213 deletions

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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")
}
}
}

View File

@@ -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")
}
}
}
}
}