Files
ShhShell/ShhShell/Views/Settings/SettingsView.swift
neon443 bcd52143cb improve ios 16 ux:
fix missing symbol in onboarding
fix settings empty headers
2025-09-01 12:01:29 +01:00

193 lines
5.3 KiB
Swift

//
// SettingsView.swift
// ShhShell
//
// Created by neon443 on 19/08/2025.
//
import SwiftUI
import SwiftTerm
struct SettingsView: View {
@ObservedObject var hostsManager: HostsManager
@ObservedObject var keyManager: KeyManager
@State private var blinkCursor: Int = 0
@State var blinkTimer: Timer?
func startBlinkingIfNeeded() {
if hostsManager.settings.cursorType.blink {
blinkTimer?.invalidate()
blinkTimer = nil
blinkTimer = Timer(timeInterval: 1, repeats: true) { timer in
Task { @MainActor in
blinkCursor += 1
}
}
RunLoop.main.add(blinkTimer!, forMode: .common)
} else {
blinkTimer?.invalidate()
if blinkCursor % 2 != 0 {
blinkCursor += 1
}
}
}
var body: some View {
ZStack {
hostsManager.selectedTheme.background.suiColor.opacity(0.7)
.ignoresSafeArea(.all)
List {
Section("Terminal") {
VStack(alignment: .leading) {
HStack {
Label("Scrollback", systemImage: "scroll")
Spacer()
Text("\(Int(hostsManager.settings.scrollback))")
.contentTransition(.numericText())
}
Slider(
value: $hostsManager.settings.scrollback,
in: 250...10_000,
step: 250
)
}
}
Section("Cursor") {
HStack(spacing: 20) {
Text("neon443")
.font(.largeTitle).monospaced()
.foregroundStyle(.terminalGreen)
Text("~")
.font(.largeTitle).monospaced()
.foregroundStyle(.blue)
Text(">")
.font(.largeTitle).monospaced()
.foregroundStyle(.blue)
ZStack {
if hostsManager.settings.cursorType.cursorShape == .block {
Rectangle()
.frame(width: 20, height: 40)
} else if hostsManager.settings.cursorType.cursorShape == .bar {
Rectangle()
.frame(width: 4, height: 40)
} else if hostsManager.settings.cursorType.cursorShape == .underline {
Rectangle()
.frame(width: 20, height: 4)
.padding(.top, 36)
}
}
// .padding(.leading, 248)
.id(hostsManager.settings.cursorType.cursorShape)
.animation(.default, value: hostsManager.settings.cursorType.cursorShape)
.transition(.opacity)
.onChange(of: hostsManager.settings.cursorType.blink) { _ in
startBlinkingIfNeeded()
}
.onAppear() {
startBlinkingIfNeeded()
}
.opacity(blinkCursor % 2 == 0 ? 1 : 0)
.animation(
Animation.spring(duration: 1),
value: blinkCursor
)
}
Picker("Blink", selection: $hostsManager.settings.cursorType.blink) {
Text("Blink").tag(true)
Text("Steady").tag(false)
}
.pickerStyle(.segmented)
Picker("Shape", selection: $hostsManager.settings.cursorType.cursorShape) {
ForEach(CursorShape.allCases, id: \.self) { type in
Text(type.description).tag(type)
}
}
.pickerStyle(.inline)
.labelsHidden()
}
Section("Keepalive") {
Toggle("location persistence", systemImage: "location.fill", isOn: $hostsManager.settings.locationPersist)
.onChange(of: hostsManager.settings.locationPersist) { _ in
if hostsManager.settings.locationPersist && !Backgrounder.shared.checkPermsStatus() {
Backgrounder.shared.requestPerms()
}
}
Toggle("keep screen awake", systemImage: "cup.and.saucer.fill", isOn: $hostsManager.settings.caffeinate)
}
Section("Bell") {
Toggle("bell sound", systemImage: "bell.and.waves.left.and.right", isOn: $hostsManager.settings.bellSound)
Toggle("bell haptic",systemImage: "iphone.radiowaves.left.and.right", isOn: $hostsManager.settings.bellHaptic)
}
Section("Terminal Filter") {
if #unavailable(iOS 17), hostsManager.settings.filter == .crt {
Label("iOS 17 Required", systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.yellow)
.transition(.opacity)
}
Picker("", selection: $hostsManager.settings.filter) {
ForEach(TerminalFilter.allCases, id: \.self) { filter in
Text(filter.description).tag(filter)
}
}
.pickerStyle(.inline)
.labelsHidden()
}
.animation(.spring, value: hostsManager.settings.filter)
Section("App Icon") {
ScrollView(.horizontal) {
HStack {
ForEach(AppIcon.allCases, id: \.self) { icon in
let isSelected = hostsManager.settings.appIcon == icon
ZStack(alignment: .top) {
RoundedRectangle(cornerRadius: 21.5)
.foregroundStyle(.gray.opacity(0.5))
.opacity(isSelected ? 1 : 0)
VStack(spacing: 0) {
icon.image
.resizable().scaledToFit()
.clipShape(RoundedRectangle(cornerRadius: 16.5))
.padding(5)
Text(icon.description).tag(icon)
.font(.caption)
.padding(.bottom, 5)
.padding(.horizontal, 5)
.multilineTextAlignment(.center)
}
}
.frame(maxWidth: 85, maxHeight: 110)
.onTapGesture {
withAnimation {
hostsManager.settings.appIcon = icon
hostsManager.setAppIcon()
}
}
}
}
}
}
}
.listStyle(.insetGrouped)
.scrollContentBackground(.hidden)
.onChange(of: hostsManager.settings) { _ in
hostsManager.saveSettings()
}
}
}
}
#Preview {
SettingsView(
hostsManager: HostsManager(previews: true),
keyManager: KeyManager()
)
}