clean up and fix the broken ui

This commit is contained in:
se2crid
2025-12-12 15:21:11 +01:00
parent 0774986e79
commit 249d82eda1
6 changed files with 75 additions and 106 deletions

View File

@@ -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,9 +813,12 @@ struct StatusOverviewCard: View {
Spacer() Spacer()
Text(Date(), style: .time) HStack(spacing: 4) {
.font(.caption) Text("connected_at")
.foregroundColor(.secondary) Text(Date(), style: .time)
}
.font(.caption)
.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 {

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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" = "터널을 생성하려면 연결을 누르세요";

View File

@@ -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";