added history view

added history loading and saving functions
added a thingy that will combine multiple entries like the phone app
added history data struct
fix hosts with no name just bneing called " copy" when duplicated
remove the showterminal button
fix crash when closing the terminal
This commit is contained in:
neon443
2025-08-15 13:05:57 +01:00
parent d32356eaf6
commit 3e713b8561
7 changed files with 112 additions and 9 deletions

View File

@@ -83,6 +83,8 @@
A9C060ED2E3FBCD000CA9374 /* SnippetPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */; }; A9C060ED2E3FBCD000CA9374 /* SnippetPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */; };
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; }; A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C4140B2E096DB7005E3047 /* SSHError.swift */; };
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; }; A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
A9CC786B2E4E681400FAEE58 /* RecentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CC786A2E4E681400FAEE58 /* RecentsView.swift */; };
A9CC786D2E4F534600FAEE58 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9CC786C2E4F534600FAEE58 /* History.swift */; };
A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; }; A9D819292E0E904200442D38 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819282E0E904200442D38 /* Theme.swift */; };
A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */; }; A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */; };
A9D8192F2E0F1BEE00442D38 /* ThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */; }; A9D8192F2E0F1BEE00442D38 /* ThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */; };
@@ -214,6 +216,8 @@
A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnippetPicker.swift; sourceTree = "<group>"; }; A9C060EC2E3FBCD000CA9374 /* SnippetPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnippetPicker.swift; sourceTree = "<group>"; };
A9C4140B2E096DB7005E3047 /* SSHError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHError.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>"; }; A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
A9CC786A2E4E681400FAEE58 /* RecentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentsView.swift; sourceTree = "<group>"; };
A9CC786C2E4F534600FAEE58 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = "<group>"; };
A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; }; A9D819282E0E904200442D38 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManagerView.swift; sourceTree = "<group>"; }; A9D8192C2E0E9EB500442D38 /* ThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManagerView.swift; sourceTree = "<group>"; };
A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = "<group>"; }; A9D8192E2E0F1BEE00442D38 /* ThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeButton.swift; sourceTree = "<group>"; };
@@ -521,6 +525,7 @@
A98554622E0587DF009051BD /* HostsView.swift */, A98554622E0587DF009051BD /* HostsView.swift */,
A985545C2E055D4D009051BD /* ConnectionView.swift */, A985545C2E055D4D009051BD /* ConnectionView.swift */,
A98CAB432E4229F7005E4C42 /* HostSymbolPicker.swift */, A98CAB432E4229F7005E4C42 /* HostSymbolPicker.swift */,
A9CC786A2E4E681400FAEE58 /* RecentsView.swift */,
); );
path = Hosts; path = Hosts;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -542,6 +547,7 @@
A93143BF2DF61B3200FCD5DB /* Host.swift */, A93143BF2DF61B3200FCD5DB /* Host.swift */,
A98554602E058433009051BD /* HostsManager.swift */, A98554602E058433009051BD /* HostsManager.swift */,
A9DA97702E0D30ED00142DDC /* HostSymbol.swift */, A9DA97702E0D30ED00142DDC /* HostSymbol.swift */,
A9CC786C2E4F534600FAEE58 /* History.swift */,
); );
path = Host; path = Host;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -792,6 +798,8 @@
A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */, A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */,
A9FD37692E16A6BF005319A8 /* ShellTabView.swift in Sources */, A9FD37692E16A6BF005319A8 /* ShellTabView.swift in Sources */,
A9DA97732E0D40C100142DDC /* HostSymbolPreview.swift in Sources */, A9DA97732E0D40C100142DDC /* HostSymbolPreview.swift in Sources */,
A9CC786B2E4E681400FAEE58 /* RecentsView.swift in Sources */,
A9CC786D2E4F534600FAEE58 /* History.swift in Sources */,
A90B38342E3EA046002B56FC /* Bundle.swift in Sources */, A90B38342E3EA046002B56FC /* Bundle.swift in Sources */,
A9FD376B2E16DABF005319A8 /* AnsiPickerView.swift in Sources */, A9FD376B2E16DABF005319A8 /* AnsiPickerView.swift in Sources */,
A9C060ED2E3FBCD000CA9374 /* SnippetPicker.swift in Sources */, A9C060ED2E3FBCD000CA9374 /* SnippetPicker.swift in Sources */,

View File

@@ -0,0 +1,15 @@
//
// History.swift
// ShhShell
//
// Created by neon443 on 15/08/2025.
//
import Foundation
struct History: Identifiable {
var id: UUID = UUID()
var host: Host
var count: Int
}

View File

@@ -24,6 +24,8 @@ class HostsManager: ObservableObject, @unchecked Sendable {
@Published var snippets: [Snippet] = [] @Published var snippets: [Snippet] = []
@Published var history: [Host] = []
var tint: SwiftUI.Color { var tint: SwiftUI.Color {
selectedTheme.ansi[selectedAnsi].suiColor selectedTheme.ansi[selectedAnsi].suiColor
} }
@@ -33,6 +35,37 @@ class HostsManager: ObservableObject, @unchecked Sendable {
loadThemes() loadThemes()
loadFonts() loadFonts()
loadSnippets() loadSnippets()
loadHistory()
}
func loadHistory() {
guard let data = userDefaults.data(forKey: "history") else { return }
guard let decoded = try? JSONDecoder().decode([Host].self, from: data) else { return }
withAnimation { self.history = decoded }
}
func formatHistory() -> [History] {
var result: [History] = []
for host in history {
if result.last?.host == host {
guard var lastOne = result.popLast() else { return result }
lastOne.count += 1
result.append(lastOne)
} else {
result.append(History(host: host, count: 1))
}
}
return result
}
func saveHistory() {
let data = try? JSONEncoder().encode(history)
userDefaults.set(data, forKey: "history")
}
func removeFromHistory(_ toRemove: Host) {
history.removeAll(where: { $0.id == toRemove.id })
saveHistory()
} }
func addSnippet(_ toAdd: Snippet) { func addSnippet(_ toAdd: Snippet) {
@@ -239,7 +272,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
func duplicateHost(_ hostToDup: Host) { func duplicateHost(_ hostToDup: Host) {
var hostNewID = hostToDup var hostNewID = hostToDup
hostNewID.id = UUID() hostNewID.id = UUID()
hostNewID.name.append(" copy") hostNewID.name = hostToDup.description.appending(" copy")
if let index = hosts.firstIndex(where: { $0 == hostToDup }) { if let index = hosts.firstIndex(where: { $0 == hostToDup }) {
hosts.insert(hostNewID, at: index+1) hosts.insert(hostNewID, at: index+1)
Haptic.medium.trigger() Haptic.medium.trigger()

View File

@@ -24,6 +24,11 @@ struct ContentView: View {
keyManager: keyManager keyManager: keyManager
) )
RecentsView(
hostsManager: hostsManager,
keyManager: keyManager
)
HostsView( HostsView(
handler: handler, handler: handler,
hostsManager: hostsManager, hostsManager: hostsManager,

View File

@@ -81,13 +81,6 @@ struct ConnectionView: View {
} }
} }
Button() {
showTerminal.toggle()
} label: {
Label("Show Terminal", systemImage: "apple.terminal")
}
.disabled(!checkShell(handler.state))
Button() { Button() {
handler.testExec() handler.testExec()
} label: { } label: {
@@ -153,6 +146,7 @@ Hostkey fingerprint is \(handler.getHostkey() ?? "nil")
handler.go() handler.go()
showTerminal = checkShell(handler.state) showTerminal = checkShell(handler.state)
if showTerminal { if showTerminal {
hostsManager.history.append(handler.host)
handler.writeToChannel(hostsManager.snippets.first(where: { $0.id == handler.host.startupSnippetID })?.content) handler.writeToChannel(hostsManager.snippets.first(where: { $0.id == handler.host.startupSnippetID })?.content)
} }
} label: { } label: {

View File

@@ -0,0 +1,48 @@
//
// RecentsView.swift
// ShhShell
//
// Created by neon443 on 14/08/2025.
//
import SwiftUI
struct RecentsView: View {
@ObservedObject var hostsManager: HostsManager
@ObservedObject var keyManager: KeyManager
var body: some View {
if !hostsManager.history.isEmpty {
Section("Recents") {
ForEach(hostsManager.formatHistory()) { history in
NavigationLink() {
ConnectionView(
handler: SSHHandler(
host: history.host,
keyManager: keyManager
),
hostsManager: hostsManager,
keyManager: keyManager
)
} label: {
Text(history.host.description)
Text("\(history.count)")
}
.swipeActions {
Button("Remove", systemImage: "trash", role: .destructive) {
hostsManager.removeFromHistory(history.host)
}
.tint(.red)
}
}
}
}
}
}
#Preview {
RecentsView(
hostsManager: HostsManager(),
keyManager: KeyManager()
)
}

View File

@@ -14,7 +14,7 @@ struct ShellTabView: View {
@ObservedObject var container = TerminalViewContainer.shared @ObservedObject var container = TerminalViewContainer.shared
@State var selectedID: UUID? @State var selectedID: UUID?
var selectedHandler: SSHHandler { var selectedHandler: SSHHandler {
container.sessions[selectedID ?? UUID()]?.handler ?? handler! container.sessions[selectedID ?? UUID()]?.handler ?? handler ?? SSHHandler(host: Host.blank, keyManager: nil)
} }
@State var showSnippetPicker: Bool = false @State var showSnippetPicker: Bool = false