import SwiftUI struct HomeView: View { @Environment(\.scenePhase) private var scenePhase @EnvironmentObject private var viewModel: ViewModel private let falsePositiveURL = URL(string: "https://www.sr2.uk/contact")! private let statusURL = URL(string: "https://status.sr2.uk/")! private let tosURL = URL(string: "https://www.sr2.uk/terms")! private let privacyPolicyURL = URL(string: "https://www.sr2.uk/privacy")! var body: some View { NavigationCompat { List { // Main toggle section Section { HStack { VStack(alignment: .leading, spacing: 4) { Text(NSLocalizedString("DNS Protection", comment: "")) .font(.headline) Text(viewModel.isDnsEnabled ? NSLocalizedString("Active", comment: "") : NSLocalizedString("Inactive", comment: "")) .font(.caption) .foregroundStyle(viewModel.isDnsEnabled ? .green : .secondary) } Spacer() Toggle("", isOn: $viewModel.isDnsEnabled) .labelsHidden() .disabled(true) .tint(.green) } .padding(.vertical, 4) } if !viewModel.isDnsEnabled { Section { VStack(alignment: .leading, spacing: 4) { Text(String(format: NSLocalizedString("To enable %@:", comment: ""), ViewModel.title)) Text(String(format: NSLocalizedString("%1$@ Tap \"%2$@\"", comment: ""), "–", NSLocalizedString("Open Settings", comment: ""))) Text(String(format: NSLocalizedString("%1$@ Go to General", comment: ""), "–")) Text(String(format: NSLocalizedString("%1$@ VPN & Network", comment: ""), "–")) Text(String(format: NSLocalizedString("%1$@ DNS", comment: ""), "–")) Text(String(format: NSLocalizedString("%1$@ Select \"%2$@\"", comment: ""), "–", viewModel.blocklist.title)) Spacer() Button { if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(url) } } label: { Text(NSLocalizedString("Open Settings", comment: "")) .frame(maxWidth: .infinity, minHeight: 32) } .buttonStyle(.borderedProminent) } } } // Blocklist selection Section { ForEach(BlocklistOption.allCases) { option in BlocklistRow( option: option, isSelected: viewModel.blocklist == option ) .contentShape(Rectangle()) .onTapGesture { guard option.enabled else { return } withAnimation(.spring(duration: 0.3)) { viewModel.blocklist = option } } .opacity(option.enabled ? 1 : 0.6) } } header: { Text(NSLocalizedString("Blocklist", comment: "")) } footer: { Text(NSLocalizedString("Select the level of protection for your DNS queries", comment: "")) } // Status section if viewModel.isDnsEnabled { Section { HStack { Label(NSLocalizedString("Status", comment: ""), systemImage: "checkmark.circle.fill") .foregroundStyle(.green) Spacer() Text(NSLocalizedString("Connected", comment: "")) .foregroundStyle(.secondary) } HStack { Label(NSLocalizedString("Server", comment: ""), systemImage: "server.rack") Spacer() Text(viewModel.blocklist.server) .foregroundStyle(.secondary) .font(.system(.body, design: .monospaced)) } HStack { Label("IPv4", systemImage: "globe") Spacer() Text(viewModel.blocklist.ipv4) .foregroundStyle(.secondary) .font(.system(.body, design: .monospaced)) } HStack { Label("IPv6", systemImage: "globe") Spacer() Text(viewModel.blocklist.ipv6) .foregroundStyle(.secondary) .font(.system(.body, design: .monospaced)) } HStack { Label(NSLocalizedString("Domains in blocklist", comment: ""), systemImage: "xmark.shield.fill") Spacer() BlockedCount() .foregroundStyle(.secondary) } } header: { Text(NSLocalizedString("Connection Details", comment: "")) } } // Support section Section { Link(destination: falsePositiveURL) { HStack { Label { Text(NSLocalizedString("Report False Positive", comment: "")) } icon: { Image(systemName: "exclamationmark.bubble") .foregroundStyle(.orange) } Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) .foregroundStyle(.secondary) } }.foregroundStyle(.primary) } footer: { Text(NSLocalizedString("Submit incorrectly blocked domains for review", comment: "")) } // Service status section Section { Link(destination: statusURL) { HStack(spacing: 12) { Circle() .fill(viewModel.summaryStatus.color) .frame(width: 12, height: 12) VStack(alignment: .leading, spacing: 4) { Text(NSLocalizedString("Service Status", comment: "")) .font(.headline) Text(viewModel.summaryStatus.description) .font(.caption) .foregroundStyle(.secondary) } Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) .foregroundStyle(.secondary) } .padding(.vertical, 4) }.foregroundStyle(.primary) Link(destination: tosURL) { HStack(spacing: 12) { Image(systemName: "doc.text") Text(NSLocalizedString("Terms of Service", comment: "")) Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) .foregroundStyle(.secondary) } }.foregroundStyle(.primary) Link(destination: privacyPolicyURL) { HStack(spacing: 12) { Image(systemName: "doc.text") Text(NSLocalizedString("Privacy Policy", comment: "")) Spacer() Image(systemName: "arrow.up.right.square") .font(.caption) .foregroundStyle(.secondary) } }.foregroundStyle(.primary) } } .navigationTitle(ViewModel.title) .animation(.default, value: viewModel.isDnsEnabled) .onChange(of: scenePhase) { newPhase in if newPhase == .active { Task { await viewModel.refreshConfig() } } } } } } #Preview { HomeView() .environmentObject(ViewModel()) }