Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions StikJIT/StikJITApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,19 @@ class VPNLogger: ObservableObject {
class TunnelManager: ObservableObject {
@Published var tunnelStatus: TunnelStatus = .disconnected
static var shared = TunnelManager()

private let sharedDefaults = UserDefaults(suiteName: "group.com.stik.sj")
private let vpnStatusKey = "vpnStatus"

private var vpnManager: NETunnelProviderManager?
private var tunnelDeviceIp: String {
UserDefaults.standard.string(forKey: "TunnelDeviceIP") ?? "10.7.0.0"
sharedDefaults?.string(forKey: "TunnelDeviceIP") ?? "10.7.0.0"
}
private var tunnelFakeIp: String {
UserDefaults.standard.string(forKey: "TunnelFakeIP") ?? "10.7.0.1"
sharedDefaults?.string(forKey: "TunnelFakeIP") ?? "10.7.0.1"
}
private var tunnelSubnetMask: String {
UserDefaults.standard.string(forKey: "TunnelSubnetMask") ?? "255.255.255.0"
sharedDefaults?.string(forKey: "TunnelSubnetMask") ?? "255.255.255.0"
}
private var tunnelBundleId: String {
Bundle.main.bundleIdentifier!.appending(".TunnelProv")
Expand All @@ -107,8 +110,12 @@ class TunnelManager: ObservableObject {
case disconnecting = "Disconnecting"
case error = "Error"
}

private init() {
if let saved = sharedDefaults?.string(forKey: vpnStatusKey),
let status = TunnelStatus(rawValue: saved) {
tunnelStatus = status
}
loadTunnelPreferences()
NotificationCenter.default.addObserver(self, selector: #selector(statusDidChange(_:)), name: .NEVPNStatusDidChange, object: nil)
}
Expand Down Expand Up @@ -166,6 +173,7 @@ class TunnelManager: ObservableObject {
@unknown default:
self.tunnelStatus = .error
}
self.sharedDefaults?.set(self.tunnelStatus.rawValue, forKey: vpnStatusKey)
VPNLogger.shared.log("VPN status updated: \(self.tunnelStatus.rawValue)")
}
}
Expand Down Expand Up @@ -240,6 +248,7 @@ class TunnelManager: ObservableObject {
return
}
tunnelStatus = .connecting
sharedDefaults?.set(tunnelStatus.rawValue, forKey: vpnStatusKey)
let options: [String: NSObject] = [
"TunnelDeviceIP": tunnelDeviceIp as NSObject,
"TunnelFakeIP": tunnelFakeIp as NSObject,
Expand All @@ -250,13 +259,15 @@ class TunnelManager: ObservableObject {
VPNLogger.shared.log("Network tunnel start initiated")
} catch {
tunnelStatus = .error
sharedDefaults?.set(tunnelStatus.rawValue, forKey: vpnStatusKey)
VPNLogger.shared.log("Failed to start tunnel: \(error.localizedDescription)")
}
}

func stopVPN() {
guard let manager = vpnManager else { return }
tunnelStatus = .disconnecting
sharedDefaults?.set(tunnelStatus.rawValue, forKey: vpnStatusKey)
manager.connection.stopVPNTunnel()
VPNLogger.shared.log("Network tunnel stop initiated")
}
Expand Down
47 changes: 43 additions & 4 deletions StikJIT/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ struct SettingsView: View {

@State private var showingConsoleLogsView = false
@State private var showingDisplayView = false

@AppStorage("TunnelDeviceIP", store: UserDefaults(suiteName: "group.com.stik.sj")) private var deviceIP: String = "10.7.0.0"
@AppStorage("TunnelFakeIP", store: UserDefaults(suiteName: "group.com.stik.sj")) private var fakeIP: String = "10.7.0.1"
@AppStorage("TunnelSubnetMask", store: UserDefaults(suiteName: "group.com.stik.sj")) private var subnetMask: String = "255.255.255.0"

private var appVersion: String {
let marketingVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0"
Expand Down Expand Up @@ -201,10 +205,10 @@ struct SettingsView: View {

// Developer Disk Image section
SettingsCard {
VStack(alignment: .leading, spacing: 20) {
Text("Developer Disk Image")
.font(.headline)
.foregroundColor(.primary)
VStack(alignment: .leading, spacing: 20) {
Text("Developer Disk Image")
.font(.headline)
.foregroundColor(.primary)
.padding(.bottom, 4)

// Status indicator with icon
Expand Down Expand Up @@ -264,6 +268,34 @@ struct SettingsView: View {
self.mounted = isMounted()
}
}
// VPN configuration section
SettingsCard {
VStack(alignment: .leading, spacing: 16) {
Text("VPN Configuration")
.font(.headline)
.foregroundColor(.primary)

TextField("Device IP", text: $deviceIP)
.textFieldStyle(RoundedBorderTextFieldStyle())

TextField("Fake IP", text: $fakeIP)
.textFieldStyle(RoundedBorderTextFieldStyle())

TextField("Subnet Mask", text: $subnetMask)
.textFieldStyle(RoundedBorderTextFieldStyle())

Button(action: saveIPSettings) {
Text("Save")
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
.background(accentColor)
.foregroundColor(accentColor.contrastText())
.cornerRadius(12)
}
}
.padding(.vertical, 20)
.padding(.horizontal, 16)
}
SettingsCard {
VStack(alignment: .leading, spacing: 20) {
Text("Behavior")
Expand Down Expand Up @@ -611,6 +643,13 @@ struct SettingsView: View {
}
}
}

private func saveIPSettings() {
let defaults = UserDefaults(suiteName: "group.com.stik.sj")
defaults?.set(deviceIP, forKey: "TunnelDeviceIP")
defaults?.set(fakeIP, forKey: "TunnelFakeIP")
defaults?.set(subnetMask, forKey: "TunnelSubnetMask")
}
}

// MARK: - Helper Components
Expand Down
10 changes: 10 additions & 0 deletions StikVPN/StikVPNApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import SwiftUI

@main
struct StikVPNApp: App {
var body: some Scene {
WindowGroup {
VPNMainView()
}
}
}
89 changes: 89 additions & 0 deletions StikVPN/Utilities/Color.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// Color.swift
// StikJIT
//
// Created by Stephen on 3/27/25.
//


import SwiftUI

extension Color {
func toHex() -> String? {
let components = UIColor(self).cgColor.components
let r = Float(components?[0] ?? 0)
let g = Float(components?[1] ?? 0)
let b = Float(components?[2] ?? 0)
let hex = String(format: "%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255))
return "#" + hex
}

init?(hex: String) {
if hex.isEmpty || hex.count < 2 {
return nil
}

let r, g, b: CGFloat

if hex.hasPrefix("#") && hex.count >= 7 {
let start = hex.index(hex.startIndex, offsetBy: 1)
let hexColor = String(hex[start...])

if hexColor.count == 6, let hexNumber = Int(hexColor, radix: 16) {
r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
b = CGFloat(hexNumber & 0x0000ff) / 255
self.init(red: r, green: g, blue: b)
return
}
}

return nil
}
}

extension Color {
static let primaryBackground = Color.black
static let cardBackground = Color.white.opacity(0.2)

// Instead of a static color, provide a function that gets the current accent color
static func dynamicAccentColor(opacity: Double = 0.8) -> Color {
let colorHex = UserDefaults.standard.string(forKey: "customAccentColor") ?? ""
if colorHex.isEmpty {
return Color.blue.opacity(opacity)
} else {
return (Color(hex: colorHex) ?? .blue).opacity(opacity)
}
}

// For backward compatibility
static var cardBackground2: Color {
return dynamicAccentColor(opacity: 0.8)
}

static let primaryText = Color.white
static let secondaryText = Color.white.opacity(0.7)

// Determine if a color is dark or light to decide text color
func isDark() -> Bool {
let uiColor = UIColor(self)
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0

uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

// Calculate perceived brightness using the formula for luminance
// 0.299*R + 0.587*G + 0.114*B
let brightness = (0.299 * red) + (0.587 * green) + (0.114 * blue)

// Return true if the color is dark (brightness < 0.5)
return brightness < 0.5
}

// Returns black or white depending on the background brightness
func contrastText() -> Color {
return self.isDark() ? .white : .black
}
}
Loading