added a thingy to select an ansi color (accent color) in thememanger

improved ui for tab selector, uses theme colours and close button is in the tab
made sshhandler optional for shelltabview
implemented the tabbed terminal to sessionlistview in home
added selectedAnsi to select an accent color from the 16 ansi colors
remove toolbar and navbar in shellview, fix the wierd input issue
This commit is contained in:
neon443
2025-07-03 15:31:49 +01:00
parent a58a86acec
commit 288b37a0d6
6 changed files with 141 additions and 97 deletions

View File

@@ -15,6 +15,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
@Published var hosts: [Host] = []
@Published var themes: [Theme] = []
@Published var selectedTheme: Theme = Theme.defaultTheme
@Published var selectedAnsi: Int = 1
init() {
loadHosts()
@@ -33,11 +34,25 @@ class HostsManager: ObservableObject, @unchecked Sendable {
self.themes.append(synthedTheme)
}
guard let dataSelTheme = userDefaults.data(forKey: "selectedTheme") else { return }
guard let decodedSelTheme = Theme.decodeTheme(data: dataSelTheme) else { return }
//name doesnt matter
self.selectedTheme = decodedSelTheme
selectedAnsi = Int(userDefaults.longLong(forKey: "selectedAnsi"))
}
func saveThemes() {
let encoder = JSONEncoder()
// map the theme to themecodable
guard let encodedThemes = try? encoder.encode(themes.map({$0.themeCodable})) else { return }
userDefaults.set(encodedThemes, forKey: "themes")
guard let encodedSelectedTheme = try? encoder.encode(selectedTheme.themeCodable) else { return }
userDefaults.set(encodedSelectedTheme, forKey: "selectedTheme")
userDefaults.set(Int64(selectedAnsi), forKey: "selectedAnsi")
userDefaults.synchronize()
}
func downloadTheme(fromUrl: URL?) {
@@ -87,17 +102,6 @@ class HostsManager: ObservableObject, @unchecked Sendable {
saveThemes()
}
func saveThemes() {
let encoder = JSONEncoder()
// map the theme to themecodable
guard let encodedThemes = try? encoder.encode(themes.map({$0.themeCodable})) else { return }
userDefaults.set(encodedThemes, forKey: "themes")
guard let encodedSelectedTheme = try? encoder.encode(selectedTheme.themeCodable) else { return }
userDefaults.set(encodedSelectedTheme, forKey: "selectedTheme")
userDefaults.synchronize()
}
func getHostIndexMatching(_ hostSearchingFor: Host) -> Int? {
if let index = hosts.firstIndex(where: { $0.id == hostSearchingFor.id }) {
return index

View File

@@ -28,6 +28,7 @@ struct ShhShellApp: App {
keyManager: keyManager
)
.colorScheme(.dark)
.tint(hostsManager.selectedTheme.ansi[hostsManager.selectedAnsi].suiColor)
}
}
}

View File

@@ -34,10 +34,7 @@ struct SessionView: View {
}
}
.fullScreenCover(isPresented: $shellPresented) {
ShellView(
handler: container.sessions[key]?.handler ?? SSHHandler(host: Host.blank, keyManager: keyManager),
hostsManager: hostsManager
)
ShellTabView(handler: nil, hostsManager: hostsManager, selectedID: key)
}
}
}

View File

@@ -8,7 +8,7 @@
import SwiftUI
struct ShellTabView: View {
@ObservedObject var handler: SSHHandler
@State var handler: SSHHandler?
@ObservedObject var hostsManager: HostsManager
@ObservedObject var container = TerminalViewContainer.shared
@@ -23,13 +23,38 @@ struct ShellTabView: View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(container.sessionIDs, id: \.self) { id in
let selected = selectedID == id
let foreground = hostsManager.selectedTheme.foreground.suiColor
let ansi7 = hostsManager.selectedTheme.ansi[6].suiColor.opacity(0.7)
let background = hostsManager.selectedTheme.background.suiColor
ZStack {
Rectangle()
.fill(selectedID == id ? .orange : .gray)
.opacity(0.5)
Text(container.sessions[id]!.handler.host.description)
.frame(width: oneTabWidth)
.fill(selected ? ansi7 : background)
HStack {
if selected {
Button() {
container.sessions[id]?.handler.disconnect()
} label: {
Image(systemName: "xmark.app.fill")
.resizable().scaledToFit()
}
.padding()
}
Spacer()
VStack {
Text(container.sessions[id]!.handler.title)
.monospaced()
.foregroundStyle(foreground)
.bold(selected)
Text(container.sessions[id]!.handler.host.description)
.foregroundStyle(foreground.opacity(0.7))
.monospaced()
.font(.caption)
}
Spacer()
}
}
.frame(width: oneTabWidth)
.ignoresSafeArea(.all)
.onTapGesture {
withAnimation { selectedID = id }
@@ -40,7 +65,11 @@ struct ShellTabView: View {
.frame(height: 30)
.onAppear {
if selectedID == nil {
selectedID = handler.sessionID
if let handler {
selectedID = handler.sessionID
} else {
dismiss()
}
}
}
@@ -62,7 +91,11 @@ struct ShellTabView: View {
.id(selectedID)
.transition(.opacity)
} else {
ShellView(handler: handler, hostsManager: hostsManager)
if let handler {
ShellView(handler: handler, hostsManager: hostsManager)
} else {
Text("No SSH Handler")
}
}
}
}

View File

@@ -37,24 +37,6 @@ struct ShellView: View {
.onAppear {
handler.applySelectedTheme()
}
.navigationTitle(handler.title)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button() {
handler.disconnect()
} label: {
Label("Disconnect", systemImage: "xmark.app.fill")
}
}
ToolbarItem(placement: .cancellationAction) {
Button() {
dismiss()
} label: {
Label("Close", systemImage: "arrow.down.right.and.arrow.up.left")
}
}
}
}
}
}

View File

@@ -33,74 +33,101 @@ struct ThemeManagerView: View {
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
.ignoresSafeArea(.all)
GeometryReader { geo in
let columns: Int = max(1, Int((geo.size.width - 2*spacing) / (minColWidth + spacing)))
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.")
VStack {
VStack(spacing: 0) {
HStack(spacing: 0) {
ForEach(0..<8, id: \.self) { index in
Rectangle()
.fill(hostsManager.selectedTheme.ansi[index].suiColor)
.onTapGesture {
hostsManager.selectedAnsi = index
hostsManager.saveThemes()
}
}
}
HStack(spacing: 0) {
ForEach(8..<16, id: \.self) { index in
Rectangle()
.fill(hostsManager.selectedTheme.ansi[index].suiColor)
.onTapGesture {
hostsManager.selectedAnsi = index
hostsManager.saveThemes()
}
}
}
}
.clipShape(RoundedRectangle(cornerRadius: 15))
.frame(maxWidth: geo.size.width, maxHeight: geo.size.height/2)
let columns: Int = max(1, Int((geo.size.width - 2*spacing) / (minColWidth + spacing)))
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")
}
}
}
}
}