mirror of
https://github.com/jkcoxson/LocalDevVPN.git
synced 2026-03-02 06:26: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 {
|
var body: some View {
|
||||||
NBNavigationStack {
|
NBNavigationStack {
|
||||||
ScrollView(showsIndicators: false) {
|
ScrollView {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 16) {
|
||||||
|
TitleWithSettingsRow(showSettings: $showSettings)
|
||||||
|
|
||||||
StatusOverviewCard()
|
StatusOverviewCard()
|
||||||
|
|
||||||
ConnectivityControlsCard(
|
ConnectivityControlsCard(
|
||||||
autoConnect: $autoConnect,
|
|
||||||
action: {
|
action: {
|
||||||
tunnelManager.tunnelStatus == .connected ? tunnelManager.stopVPN() : tunnelManager.startVPN()
|
tunnelManager.tunnelStatus == .connected ? tunnelManager.stopVPN() : tunnelManager.startVPN()
|
||||||
}
|
}
|
||||||
@@ -701,24 +702,16 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.vertical, 24)
|
.padding(.vertical, 12)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .top)
|
||||||
}
|
}
|
||||||
|
.applyAdaptiveBounce()
|
||||||
.background(backgroundColor.ignoresSafeArea())
|
.background(backgroundColor.ignoresSafeArea())
|
||||||
.navigationTitle("LocalDevVPN")
|
.navigationTitle("")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.large)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
.tvOSNavigationBarTitleDisplayMode(.inline)
|
.tvOSNavigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
|
||||||
Button {
|
|
||||||
showSettings = true
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "gear")
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: tunnelManager.waitingOnSettings) { finished in
|
.onChange(of: tunnelManager.waitingOnSettings) { finished in
|
||||||
if tunnelManager.tunnelStatus != .connected && autoConnect && finished {
|
if tunnelManager.tunnelStatus != .connected && autoConnect && finished {
|
||||||
tunnelManager.startVPN()
|
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 {
|
extension View {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func tvOSNavigationBarTitleDisplayMode(_ displayMode: NavigationBarItem.TitleDisplayMode) -> some View {
|
func tvOSNavigationBarTitleDisplayMode(_ displayMode: NavigationBarItem.TitleDisplayMode) -> some View {
|
||||||
@@ -751,10 +769,20 @@ extension View {
|
|||||||
self
|
self
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func applyAdaptiveBounce() -> some View {
|
||||||
|
if #available(iOS 16.4, tvOS 16.4, *) {
|
||||||
|
scrollBounceBehavior(.basedOnSize)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StatusOverviewCard: View {
|
struct StatusOverviewCard: View {
|
||||||
@StateObject private var tunnelManager = TunnelManager.shared
|
@StateObject private var tunnelManager = TunnelManager.shared
|
||||||
|
@AppStorage("TunnelDeviceIP") private var deviceIP = "10.7.0.0"
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
DashboardCard {
|
DashboardCard {
|
||||||
@@ -785,7 +813,10 @@ struct StatusOverviewCard: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Text("connected_at")
|
||||||
Text(Date(), style: .time)
|
Text(Date(), style: .time)
|
||||||
|
}
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
@@ -796,9 +827,9 @@ struct StatusOverviewCard: View {
|
|||||||
private var statusTip: String {
|
private var statusTip: String {
|
||||||
switch tunnelManager.tunnelStatus {
|
switch tunnelManager.tunnelStatus {
|
||||||
case .connected:
|
case .connected:
|
||||||
return NSLocalizedString("connected_to_10.7.0.1", comment: "")
|
return String(format: NSLocalizedString("connected_to_ip", comment: ""), deviceIP)
|
||||||
case .connecting:
|
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:
|
case .disconnecting:
|
||||||
return NSLocalizedString("disconnecting_safely", comment: "")
|
return NSLocalizedString("disconnecting_safely", comment: "")
|
||||||
case .error:
|
case .error:
|
||||||
@@ -812,13 +843,13 @@ struct StatusOverviewCard: View {
|
|||||||
struct StatusGlyphView: View {
|
struct StatusGlyphView: View {
|
||||||
@StateObject private var tunnelManager = TunnelManager.shared
|
@StateObject private var tunnelManager = TunnelManager.shared
|
||||||
@State private var ringScale: CGFloat = 1.0
|
@State private var ringScale: CGFloat = 1.0
|
||||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Circle()
|
Circle()
|
||||||
.stroke(tunnelManager.tunnelStatus.color.opacity(0.25), lineWidth: 6)
|
.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()
|
Circle()
|
||||||
.fill(tunnelManager.tunnelStatus.color.opacity(0.15))
|
.fill(tunnelManager.tunnelStatus.color.opacity(0.15))
|
||||||
@@ -828,30 +859,17 @@ struct StatusGlyphView: View {
|
|||||||
.foregroundColor(tunnelManager.tunnelStatus.color)
|
.foregroundColor(tunnelManager.tunnelStatus.color)
|
||||||
}
|
}
|
||||||
.frame(width: 92, height: 92)
|
.frame(width: 92, height: 92)
|
||||||
.onAppear(perform: restartPulse)
|
.onAppear(perform: startPulse)
|
||||||
.onChange(of: tunnelManager.tunnelStatus) { _ in
|
|
||||||
restartPulse()
|
|
||||||
}
|
|
||||||
.onChange(of: reduceMotion) { _ in
|
|
||||||
restartPulse()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func restartPulse() {
|
private func startPulse() {
|
||||||
guard !reduceMotion else {
|
DispatchQueue.main.async {
|
||||||
ringScale = 1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ringScale = 1.0
|
|
||||||
withAnimation(.easeInOut(duration: 1.2).repeatForever(autoreverses: true)) {
|
|
||||||
ringScale = 1.08
|
ringScale = 1.08
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConnectivityControlsCard: View {
|
struct ConnectivityControlsCard: View {
|
||||||
@Binding var autoConnect: Bool
|
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -866,24 +884,13 @@ struct ConnectivityControlsCard: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConnectionButton(action: action)
|
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 {
|
struct ConnectionInfoRow: View {
|
||||||
let title: String
|
let title: LocalizedStringKey
|
||||||
let value: String
|
let value: String
|
||||||
let icon: String
|
let icon: String
|
||||||
|
|
||||||
@@ -972,16 +979,14 @@ struct ConnectionButton: View {
|
|||||||
|
|
||||||
struct ConnectionStatsView: View {
|
struct ConnectionStatsView: View {
|
||||||
@StateObject private var tunnelManager = TunnelManager.shared
|
@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("TunnelDeviceIP") private var deviceIP = "10.7.0.0"
|
||||||
@AppStorage("TunnelFakeIP") private var fakeIP = "10.7.0.1"
|
@AppStorage("TunnelFakeIP") private var fakeIP = "10.7.0.1"
|
||||||
@AppStorage("TunnelSubnetMask") private var subnetMask = "255.255.255.0"
|
@AppStorage("TunnelSubnetMask") private var subnetMask = "255.255.255.0"
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
DashboardCard {
|
DashboardCard {
|
||||||
VStack(alignment: .leading, spacing: 18) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 3) {
|
||||||
Text("session_details")
|
Text("session_details")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text("live_stats_while_the_tunnel_is_connected")
|
Text("live_stats_while_the_tunnel_is_connected")
|
||||||
@@ -989,19 +994,6 @@ struct ConnectionStatsView: View {
|
|||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 16) {
|
|
||||||
StatItemView(
|
|
||||||
title: "time_connected",
|
|
||||||
value: formattedTime,
|
|
||||||
icon: "clock.fill"
|
|
||||||
)
|
|
||||||
StatItemView(
|
|
||||||
title: "status",
|
|
||||||
value: statusValue,
|
|
||||||
icon: tunnelManager.tunnelStatus.systemImage
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
Text("network_configuration")
|
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 {
|
struct StatItemView: View {
|
||||||
|
|||||||
@@ -11,8 +11,9 @@
|
|||||||
"local_tunnel_active" = "Local Tunnel Active";
|
"local_tunnel_active" = "Local Tunnel Active";
|
||||||
"local_tunnel_inactive" = "Local Tunnel Inactive";
|
"local_tunnel_inactive" = "Local Tunnel Inactive";
|
||||||
|
|
||||||
"connected_to_10.7.0.1" = "Connected to 10.7.0.1";
|
"connected_to_ip" = "Connected to %@";
|
||||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS might ask you to approve the VPN";
|
"connected_at" = "Connected at";
|
||||||
|
"ios_might_ask_you_to_allow_the_vpn" = "iOS might ask you to allow the VPN";
|
||||||
"disconnecting_safely" = "Disconnecting safely…";
|
"disconnecting_safely" = "Disconnecting safely…";
|
||||||
"open_settings_to_review_details" = "Open Settings to review details";
|
"open_settings_to_review_details" = "Open Settings to review details";
|
||||||
"tap_connect_to_create_the_tunnel" = "Tap connect to create the tunnel";
|
"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_active" = "Túnel local activo";
|
||||||
"local_tunnel_inactive" = "Túnel local inactivo";
|
"local_tunnel_inactive" = "Túnel local inactivo";
|
||||||
|
|
||||||
"connected_to_10.7.0.1" = "Conectado a 10.7.0.1";
|
"connected_to_ip" = "Conectado a %@";
|
||||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS podría pedirte que apruebes la VPN";
|
"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…";
|
"disconnecting_safely" = "Desconectando de forma segura…";
|
||||||
"open_settings_to_review_details" = "Abre Configuración para revisar los detalles";
|
"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";
|
"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_active" = "Tunnel locale attivo";
|
||||||
"local_tunnel_inactive" = "Tunnel locale inattivo";
|
"local_tunnel_inactive" = "Tunnel locale inattivo";
|
||||||
|
|
||||||
"connected_to_10.7.0.1" = "Connesso a 10.7.0.1";
|
"connected_to_ip" = "Connesso a %@";
|
||||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS potrebbe chiederti di approvare la VPN";
|
"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…";
|
"disconnecting_safely" = "Disconnessione in corso in modo sicuro…";
|
||||||
"open_settings_to_review_details" = "Apri Impostazioni per visualizzare i dettagli";
|
"open_settings_to_review_details" = "Apri Impostazioni per visualizzare i dettagli";
|
||||||
"tap_connect_to_create_the_tunnel" = "Tocca Connetti per creare il tunnel";
|
"tap_connect_to_create_the_tunnel" = "Tocca Connetti per creare il tunnel";
|
||||||
|
|||||||
@@ -11,8 +11,9 @@
|
|||||||
"local_tunnel_active" = "로컬 터널 활성화됨";
|
"local_tunnel_active" = "로컬 터널 활성화됨";
|
||||||
"local_tunnel_inactive" = "로컬 터널 비활성화됨";
|
"local_tunnel_inactive" = "로컬 터널 비활성화됨";
|
||||||
|
|
||||||
"connected_to_10.7.0.1" = "10.7.0.1에 연결됨";
|
"connected_to_ip" = "%@에 연결됨";
|
||||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS에서 VPN 승인 요청이 나타날 수 있습니다";
|
"connected_at" = "Connected at";
|
||||||
|
"ios_might_ask_you_to_allow_the_vpn" = "iOS에서 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" = "Lokalny tunel aktywny";
|
"local_tunnel_active" = "Lokalny tunel aktywny";
|
||||||
"local_tunnel_inactive" = "Lokalny tunel nieaktywny";
|
"local_tunnel_inactive" = "Lokalny tunel nieaktywny";
|
||||||
|
|
||||||
"connected_to_10.7.0.1" = "Połączono z 10.7.0.1";
|
"connected_to_ip" = "Połączono z %@";
|
||||||
"macos_might_ask_you_to_approve_the_vpn" = "macOS może poprosić Cię o zatwierdzenie VPN";
|
"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…";
|
"disconnecting_safely" = "Bezpieczne rozłączanie…";
|
||||||
"open_settings_to_review_details" = "Otwórz Ustawienia, aby zobaczyć szczegóły";
|
"open_settings_to_review_details" = "Otwórz Ustawienia, aby zobaczyć szczegóły";
|
||||||
"tap_connect_to_create_the_tunnel" = "Stuknij Połącz, aby utworzyć tunel";
|
"tap_connect_to_create_the_tunnel" = "Stuknij Połącz, aby utworzyć tunel";
|
||||||
|
|||||||
Reference in New Issue
Block a user