fixed snippet manager ui when no snippets

added haptics and stuff
improved picker ui
This commit is contained in:
neon443
2025-07-26 22:44:00 +01:00
parent e47b598c74
commit ddf8d68e3b
6 changed files with 116 additions and 4 deletions

View File

@@ -76,6 +76,7 @@
A9BA1D192E1D9AE1005BDCEF /* SwiftTerm.Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */; };
A9BA1D1E2E1EAD51005BDCEF /* SF-Mono-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */; };
A9BA1D1F2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */; };
A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EA2E357FD300CA9374 /* Haptics.swift */; };
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; };
@@ -202,6 +203,7 @@
A9BA1D182E1D9AE1005BDCEF /* SwiftTerm.Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTerm.Color.swift; sourceTree = "<group>"; };
A9BA1D1C2E1EAD51005BDCEF /* SF-Mono-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-Bold.otf"; sourceTree = "<group>"; };
A9BA1D1D2E1EAD51005BDCEF /* SF-Mono-BoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SF-Mono-BoldItalic.otf"; sourceTree = "<group>"; };
A9C060EA2E357FD300CA9374 /* Haptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Haptics.swift; sourceTree = "<group>"; };
A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.swift; sourceTree = "<group>"; };
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
@@ -357,6 +359,7 @@
A9D8192A2E0E904900442D38 /* Themes */,
A90936862E1AC4C600856059 /* Fonts */,
A93F283F2E2A5EC80092B8D5 /* Snippets */,
A9C060E92E357FC400CA9374 /* Misc */,
A92538D32DEE0749007E0A18 /* Views */,
A90936852E1AC33C00856059 /* Info.plist */,
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
@@ -535,6 +538,14 @@
path = Keys;
sourceTree = "<group>";
};
A9C060E92E357FC400CA9374 /* Misc */ = {
isa = PBXGroup;
children = (
A9C060EA2E357FD300CA9374 /* Haptics.swift */,
);
path = Misc;
sourceTree = "<group>";
};
A9C8976F2DF1980900EF9A5F /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -783,6 +794,7 @@
A9485C762E1AF59F00209824 /* FontManagerView.swift in Sources */,
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
A93F283D2E2A5DCB0092B8D5 /* SnippetManagerView.swift in Sources */,
A9C060EB2E357FD300CA9374 /* Haptics.swift in Sources */,
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */,
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,

View File

@@ -188,12 +188,14 @@ class HostsManager: ObservableObject, @unchecked Sendable {
var newTheme = themes[index]
newTheme.name = newName
newTheme.id = UUID().uuidString
Haptic.medium.trigger()
withAnimation { themes[index] = newTheme }
saveThemes()
}
func deleteTheme(_ themeToDel: Theme) {
guard let index = themes.firstIndex(where: {$0 == themeToDel}) else { return }
Haptic.medium.trigger()
themes.remove(at: index)
saveThemes()
}
@@ -204,6 +206,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
guard var theme = Theme.decodeTheme(data: data) else { return }
theme.name = fromUrl?.lastPathComponent.replacingOccurrences(of: ".itermcolors", with: "") ?? ""
self.themes.append(theme)
Haptic.success.trigger()
saveThemes()
}
@@ -234,6 +237,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
} else {
withAnimation { hosts.append(updatedHost) }
}
Haptic.medium.trigger()
}
func duplicateHost(_ hostToDup: Host) {
@@ -241,6 +245,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
hostNewID.id = UUID()
if let index = hosts.firstIndex(where: { $0 == hostToDup }) {
hosts.insert(hostNewID, at: index+1)
Haptic.medium.trigger()
}
}
@@ -270,6 +275,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
func removeHost(_ host: Host) {
if let index = hosts.firstIndex(where: { $0.id == host.id }) {
let _ = withAnimation { hosts.remove(at: index) }
Haptic.medium.trigger()
saveHosts()
}
}

View File

@@ -0,0 +1,68 @@
//
// Haptics.swift
// ShhShell
//
// Created by neon443 on 26/07/2025.
//
import Foundation
#if canImport(UIKit)
import UIKit
enum Haptic {
case success
case error
case light
case medium
case heavy
case soft
case rigid
var isUIImpact: Bool {
switch self {
case .light, .medium, .heavy, .soft, .rigid:
return true
case .success, .error:
return false
}
}
@MainActor
var uiImpact: UIImpactFeedbackGenerator? {
guard self.isUIImpact else { return nil }
switch self {
case .light, .medium, .heavy, .soft, .rigid:
switch self {
case .light:
return UIImpactFeedbackGenerator(style: .light)
case .medium:
return UIImpactFeedbackGenerator(style: .medium)
case .heavy:
return UIImpactFeedbackGenerator(style: .heavy)
case .soft:
return UIImpactFeedbackGenerator(style: .soft)
case .rigid:
return UIImpactFeedbackGenerator(style: .rigid)
default: return nil
}
default: return nil
}
}
func trigger() {
Task { @MainActor in
if self.isUIImpact {
self.uiImpact?.impactOccurred()
} else {
switch self {
case .success:
UINotificationFeedbackGenerator().notificationOccurred(.success)
case .error:
UINotificationFeedbackGenerator().notificationOccurred(.error)
default: print("idk atp")
}
}
}
}
}
#endif

View File

@@ -80,13 +80,13 @@ struct ConnectionView: View {
TextBox(label: "Password", text: $handler.host.password, prompt: "not required if using publickeys", secure: true)
Picker("Private key", selection: $handler.host.privateKeyID) {
Text("None")
.tag(nil as UUID?)
Divider()
ForEach(keyManager.keypairs) { keypair in
Text(keypair.label)
.tag(keypair.id as UUID?)
}
Divider()
Text("None")
.tag(nil as UUID?)
}
}

View File

@@ -30,6 +30,7 @@ struct AddSnippetView: View {
.foregroundStyle(.gray)
.listRowSeparator(.hidden)
.multilineTextAlignment(.leading)
.frame(alignment: .leading)
TextEditor(text: $content)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)

View File

@@ -17,12 +17,31 @@ struct SnippetManagerView: View {
.ignoresSafeArea(.all)
NavigationStack {
List {
if hostsManager.snippets.isEmpty {
VStack(alignment: .leading) {
Image(systemName: "questionmark.square.dashed")
.resizable().scaledToFit()
.frame(width: 50)
.foregroundStyle(hostsManager.tint)
.shadow(color: hostsManager.tint, radius: 2)
Text("No Snippets")
.font(.title)
.monospaced()
.padding(.bottom)
Text("Snippets are strings of commands that can be run at once in a terminal.")
.padding(.bottom)
.foregroundStyle(.gray)
.foregroundStyle(.foreground.opacity(0.7))
}
}
ForEach(hostsManager.snippets) { snip in
Group {
VStack(alignment: .leading) {
Text(snip.name)
.bold()
.foregroundStyle(.gray)
.font(.subheadline)
Text(snip.content)
.lineLimit(3)
}
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
@@ -37,6 +56,12 @@ struct SnippetManagerView: View {
Label("Duplicate", systemImage: "square.filled.on.square")
}
.tint(.blue)
Button {
UIPasteboard().string = snip.content
} label: {
Label("Copy", systemImage: "doc.on.clipboard")
}
.tint(.blue)
}
}
}