mirror of
https://github.com/jkcoxson/LocalDevVPN.git
synced 2026-03-02 14:36:16 +01:00
clean up and fix the broken ui
This commit is contained in:
@@ -685,12 +685,13 @@ struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
NBNavigationStack {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 20) {
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
TitleWithSettingsRow(showSettings: $showSettings)
|
||||
|
||||
StatusOverviewCard()
|
||||
|
||||
ConnectivityControlsCard(
|
||||
autoConnect: $autoConnect,
|
||||
action: {
|
||||
tunnelManager.tunnelStatus == .connected ? tunnelManager.stopVPN() : tunnelManager.startVPN()
|
||||
}
|
||||
@@ -701,24 +702,16 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 24)
|
||||
.padding(.vertical, 12)
|
||||
.frame(maxWidth: .infinity, alignment: .top)
|
||||
}
|
||||
.applyAdaptiveBounce()
|
||||
.background(backgroundColor.ignoresSafeArea())
|
||||
.navigationTitle("LocalDevVPN")
|
||||
.navigationTitle("")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
.tvOSNavigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
showSettings = true
|
||||
} label: {
|
||||
Image(systemName: "gear")
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: tunnelManager.waitingOnSettings) { finished in
|
||||
if tunnelManager.tunnelStatus != .connected && autoConnect && finished {
|
||||
tunnelManager.startVPN()
|
||||
@@ -742,6 +735,31 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct TitleWithSettingsRow: View {
|
||||
@Binding var showSettings: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 10) {
|
||||
Text("LocalDevVPN")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.bold)
|
||||
.accessibilityAddTraits(.isHeader)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
showSettings = true
|
||||
} label: {
|
||||
Image(systemName: "gear")
|
||||
.font(.title2.weight(.semibold))
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.accessibilityLabel(Text("settings"))
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
@ViewBuilder
|
||||
func tvOSNavigationBarTitleDisplayMode(_ displayMode: NavigationBarItem.TitleDisplayMode) -> some View {
|
||||
@@ -751,10 +769,20 @@ extension View {
|
||||
self
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func applyAdaptiveBounce() -> some View {
|
||||
if #available(iOS 16.4, tvOS 16.4, *) {
|
||||
scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StatusOverviewCard: View {
|
||||
@StateObject private var tunnelManager = TunnelManager.shared
|
||||
@AppStorage("TunnelDeviceIP") private var deviceIP = "10.7.0.0"
|
||||
|
||||
var body: some View {
|
||||
DashboardCard {
|
||||
@@ -785,7 +813,10 @@ struct StatusOverviewCard: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Text("connected_at")
|
||||
Text(Date(), style: .time)
|
||||
}
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
@@ -796,9 +827,9 @@ struct StatusOverviewCard: View {
|
||||
private var statusTip: String {
|
||||
switch tunnelManager.tunnelStatus {
|
||||
case .connected:
|
||||
return NSLocalizedString("connected_to_10.7.0.1", comment: "")
|
||||
return String(format: NSLocalizedString("connected_to_ip", comment: ""), deviceIP)
|
||||
case .connecting:
|
||||
return NSLocalizedString("macos_might_ask_you_to_approve_the_vpn", comment: "")
|
||||
return NSLocalizedString("ios_might_ask_you_to_allow_the_vpn", comment: "")
|
||||
case .disconnecting:
|
||||
return NSLocalizedString("disconnecting_safely", comment: "")
|
||||
case .error:
|
||||
@@ -812,13 +843,13 @@ struct StatusOverviewCard: View {
|
||||
struct StatusGlyphView: View {
|
||||
@StateObject private var tunnelManager = TunnelManager.shared
|
||||
@State private var ringScale: CGFloat = 1.0
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(tunnelManager.tunnelStatus.color.opacity(0.25), lineWidth: 6)
|
||||
.scaleEffect(reduceMotion ? 1 : ringScale, anchor: .center)
|
||||
.scaleEffect(ringScale, anchor: .center)
|
||||
.animation(.easeInOut(duration: 1.2).repeatForever(autoreverses: true), value: ringScale)
|
||||
|
||||
Circle()
|
||||
.fill(tunnelManager.tunnelStatus.color.opacity(0.15))
|
||||
@@ -828,30 +859,17 @@ struct StatusGlyphView: View {
|
||||
.foregroundColor(tunnelManager.tunnelStatus.color)
|
||||
}
|
||||
.frame(width: 92, height: 92)
|
||||
.onAppear(perform: restartPulse)
|
||||
.onChange(of: tunnelManager.tunnelStatus) { _ in
|
||||
restartPulse()
|
||||
}
|
||||
.onChange(of: reduceMotion) { _ in
|
||||
restartPulse()
|
||||
}
|
||||
.onAppear(perform: startPulse)
|
||||
}
|
||||
|
||||
private func restartPulse() {
|
||||
guard !reduceMotion else {
|
||||
ringScale = 1
|
||||
return
|
||||
}
|
||||
|
||||
ringScale = 1.0
|
||||
withAnimation(.easeInOut(duration: 1.2).repeatForever(autoreverses: true)) {
|
||||
private func startPulse() {
|
||||
DispatchQueue.main.async {
|
||||
ringScale = 1.08
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectivityControlsCard: View {
|
||||
@Binding var autoConnect: Bool
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
@@ -866,24 +884,13 @@ struct ConnectivityControlsCard: View {
|
||||
}
|
||||
|
||||
ConnectionButton(action: action)
|
||||
|
||||
Toggle(isOn: $autoConnect) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("auto-connect_on_launch")
|
||||
.fontWeight(.semibold)
|
||||
Text("resume_your_last_state_automatically")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectionInfoRow: View {
|
||||
let title: String
|
||||
let title: LocalizedStringKey
|
||||
let value: String
|
||||
let icon: String
|
||||
|
||||
@@ -972,16 +979,14 @@ struct ConnectionButton: View {
|
||||
|
||||
struct ConnectionStatsView: View {
|
||||
@StateObject private var tunnelManager = TunnelManager.shared
|
||||
@State private var time = 0
|
||||
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
@AppStorage("TunnelDeviceIP") private var deviceIP = "10.7.0.0"
|
||||
@AppStorage("TunnelFakeIP") private var fakeIP = "10.7.0.1"
|
||||
@AppStorage("TunnelSubnetMask") private var subnetMask = "255.255.255.0"
|
||||
|
||||
var body: some View {
|
||||
DashboardCard {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text("session_details")
|
||||
.font(.headline)
|
||||
Text("live_stats_while_the_tunnel_is_connected")
|
||||
@@ -989,19 +994,6 @@ struct ConnectionStatsView: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
HStack(spacing: 16) {
|
||||
StatItemView(
|
||||
title: "time_connected",
|
||||
value: formattedTime,
|
||||
icon: "clock.fill"
|
||||
)
|
||||
StatItemView(
|
||||
title: "status",
|
||||
value: statusValue,
|
||||
icon: tunnelManager.tunnelStatus.systemImage
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Text("network_configuration")
|
||||
@@ -1027,36 +1019,8 @@ struct ConnectionStatsView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
.onReceive(timer) { _ in
|
||||
time += 1
|
||||
}
|
||||
}
|
||||
|
||||
var formattedTime: String {
|
||||
let minutes = (time / 60) % 60
|
||||
let hours = time / 3600
|
||||
let seconds = time % 60
|
||||
|
||||
if hours > 0 {
|
||||
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
|
||||
} else {
|
||||
return String(format: "%02d:%02d", minutes, seconds)
|
||||
}
|
||||
}
|
||||
private var statusValue: String {
|
||||
switch tunnelManager.tunnelStatus {
|
||||
case .connected:
|
||||
return NSLocalizedString("Active", comment: "")
|
||||
case .connecting:
|
||||
return NSLocalizedString("Connecting", comment: "")
|
||||
case .disconnecting:
|
||||
return NSLocalizedString("Disconnecting", comment: "")
|
||||
case .error:
|
||||
return NSLocalizedString("Error", comment: "")
|
||||
default:
|
||||
return NSLocalizedString("Idle", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StatItemView: View {
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
"local_tunnel_active" = "Local Tunnel Active";
|
||||
"local_tunnel_inactive" = "Local Tunnel Inactive";
|
||||
|
||||
"connected_to_10.7.0.1" = "Connected to 10.7.0.1";
|
||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS might ask you to approve the VPN";
|
||||
"connected_to_ip" = "Connected to %@";
|
||||
"connected_at" = "Connected at";
|
||||
"ios_might_ask_you_to_allow_the_vpn" = "iOS might ask you to allow the VPN";
|
||||
"disconnecting_safely" = "Disconnecting safely…";
|
||||
"open_settings_to_review_details" = "Open Settings to review details";
|
||||
"tap_connect_to_create_the_tunnel" = "Tap connect to create the tunnel";
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
"local_tunnel_active" = "Túnel local activo";
|
||||
"local_tunnel_inactive" = "Túnel local inactivo";
|
||||
|
||||
"connected_to_10.7.0.1" = "Conectado a 10.7.0.1";
|
||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS podría pedirte que apruebes la VPN";
|
||||
"connected_to_ip" = "Conectado a %@";
|
||||
"connected_at" = "Connected at";
|
||||
"ios_might_ask_you_to_allow_the_vpn" = "iOS podría pedirte que permitas la VPN";
|
||||
"disconnecting_safely" = "Desconectando de forma segura…";
|
||||
"open_settings_to_review_details" = "Abre Configuración para revisar los detalles";
|
||||
"tap_connect_to_create_the_tunnel" = "Toca conectar para crear el túnel";
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
"local_tunnel_active" = "Tunnel locale attivo";
|
||||
"local_tunnel_inactive" = "Tunnel locale inattivo";
|
||||
|
||||
"connected_to_10.7.0.1" = "Connesso a 10.7.0.1";
|
||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS potrebbe chiederti di approvare la VPN";
|
||||
"connected_to_ip" = "Connesso a %@";
|
||||
"connected_at" = "Connected at";
|
||||
"ios_might_ask_you_to_allow_the_vpn" = "iOS potrebbe chiederti di consentire la VPN";
|
||||
"disconnecting_safely" = "Disconnessione in corso in modo sicuro…";
|
||||
"open_settings_to_review_details" = "Apri Impostazioni per visualizzare i dettagli";
|
||||
"tap_connect_to_create_the_tunnel" = "Tocca Connetti per creare il tunnel";
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
"local_tunnel_active" = "로컬 터널 활성화됨";
|
||||
"local_tunnel_inactive" = "로컬 터널 비활성화됨";
|
||||
|
||||
"connected_to_10.7.0.1" = "10.7.0.1에 연결됨";
|
||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS에서 VPN 승인 요청이 나타날 수 있습니다";
|
||||
"connected_to_ip" = "%@에 연결됨";
|
||||
"connected_at" = "Connected at";
|
||||
"ios_might_ask_you_to_allow_the_vpn" = "iOS에서 VPN 허용 요청이 나타날 수 있습니다";
|
||||
"disconnecting_safely" = "안전하게 연결 해제 중…";
|
||||
"open_settings_to_review_details" = "자세한 내용을 보려면 설정을 여세요";
|
||||
"tap_connect_to_create_the_tunnel" = "터널을 생성하려면 연결을 누르세요";
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
"local_tunnel_active" = "Lokalny tunel aktywny";
|
||||
"local_tunnel_inactive" = "Lokalny tunel nieaktywny";
|
||||
|
||||
"connected_to_10.7.0.1" = "Połączono z 10.7.0.1";
|
||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS może poprosić Cię o zatwierdzenie VPN";
|
||||
"connected_to_ip" = "Połączono z %@";
|
||||
"connected_at" = "Connected at";
|
||||
"ios_might_ask_you_to_allow_the_vpn" = "iOS może poprosić Cię o pozwolenie na VPN";
|
||||
"disconnecting_safely" = "Bezpieczne rozłączanie…";
|
||||
"open_settings_to_review_details" = "Otwórz Ustawienia, aby zobaczyć szczegóły";
|
||||
"tap_connect_to_create_the_tunnel" = "Stuknij Połącz, aby utworzyć tunel";
|
||||
|
||||
Reference in New Issue
Block a user