feat: initial ui implementation
This commit is contained in:
parent
1612ed099c
commit
13254d63c2
9 changed files with 709 additions and 31 deletions
22
LICENCE
Normal file
22
LICENCE
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Copyright 2021-2026 SR2 Communications Limited.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
22
README.md
Normal file
22
README.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
cloud-dns-ios
|
||||
=============
|
||||
|
||||
[](https://hosted.weblate.org/engage/sr2/)
|
||||
[](https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
iOS app to manage SR2® Cloud DNS configuration
|
||||
|
||||
Translations
|
||||
------------
|
||||
|
||||
Snapshot templates support localisation.
|
||||
Translations of strings in the template are managed on Weblate.
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/sr2/">
|
||||
<img src="https://hosted.weblate.org/widget/sr2/cloud-dns-ios/multi-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
Licence & Copyright
|
||||
-------------------
|
||||
|
||||
© SR2 Communications Limited. See [LICENCE](./LICENCE) for details of the BSD 2 clause licence.
|
||||
|
|
@ -184,6 +184,7 @@
|
|||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
|
|
@ -240,6 +241,7 @@
|
|||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
|
|
@ -249,8 +251,10 @@
|
|||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
|
|
@ -265,6 +269,7 @@
|
|||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
|
|
@ -280,8 +285,10 @@
|
|||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
|
|
@ -296,6 +303,7 @@
|
|||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||
|
|
|
|||
71
dns/BlockedCount.swift
Normal file
71
dns/BlockedCount.swift
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
struct BlockedCount: View {
|
||||
@State private var txtRecord: String = "..."
|
||||
|
||||
var body: some View {
|
||||
Text(txtRecord)
|
||||
.onAppear {
|
||||
fetchTXTRecord()
|
||||
}
|
||||
}
|
||||
|
||||
func parseResponse(data: Data) -> String? {
|
||||
// This is a DNS wire format response and we make a lot of assumptions
|
||||
// It is not critical functionality so just let it fail if it fails
|
||||
|
||||
guard data.count > 44 else { return nil }
|
||||
|
||||
let startIndex = 44
|
||||
let subData = data.suffix(from: startIndex)
|
||||
|
||||
// Find the first space character (ASCII 32)
|
||||
var endIndex: Int?
|
||||
for (offset, byte) in subData.enumerated() {
|
||||
if byte == 32 { // Space character
|
||||
endIndex = offset
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
guard let foundEndIndex = endIndex else { return nil }
|
||||
|
||||
// Extract bytes between start and space
|
||||
let numberData = subData.prefix(foundEndIndex)
|
||||
|
||||
return String(decoding: numberData, as: UTF8.self)
|
||||
}
|
||||
|
||||
func fetchTXTRecord() {
|
||||
let dohURL = URL(string: "https://dns.sr2.uk/dns-query?dns=DoQBAAABAAAAAAAABXN0YXRzB2ludmFsaWQAABAAAQ")!
|
||||
|
||||
let request = URLRequest(url: dohURL)
|
||||
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
DispatchQueue.main.async {
|
||||
if error != nil {
|
||||
txtRecord = "Error"
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
txtRecord = "Error"
|
||||
return
|
||||
}
|
||||
|
||||
guard let count = parseResponse(data: data) else {
|
||||
txtRecord = "Error"
|
||||
return
|
||||
}
|
||||
|
||||
txtRecord = count
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
BlockedCount()
|
||||
}
|
||||
41
dns/BlocklistRow.swift
Normal file
41
dns/BlocklistRow.swift
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import SwiftUI
|
||||
|
||||
struct BlocklistRow: View {
|
||||
let option: BlocklistOption
|
||||
let isSelected: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 16) {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(isSelected ? Color.green.opacity(0.15) : Color.gray.opacity(0.1))
|
||||
.frame(width: 44, height: 44)
|
||||
|
||||
Image(systemName: option.icon)
|
||||
.font(.title3)
|
||||
.foregroundStyle(isSelected ? .green : .secondary)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(option.id + " " + (option.enabled ? "" : "(Coming Soon)"))
|
||||
.font(.body)
|
||||
.fontWeight(isSelected ? .semibold : .regular)
|
||||
|
||||
Text(option.description)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
.font(.title3)
|
||||
.transition(.scale.combined(with: .opacity))
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// ContentView.swift
|
||||
// dns
|
||||
//
|
||||
// Created by irl on 12/04/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image(systemName: "globe")
|
||||
.imageScale(.large)
|
||||
.foregroundStyle(.tint)
|
||||
Text("Hello, world!")
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContentView()
|
||||
}
|
||||
281
dns/HomeView.swift
Normal file
281
dns/HomeView.swift
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
enum BlocklistOption: String, CaseIterable, Identifiable {
|
||||
case secure = "Secure"
|
||||
case securePlusAdblock = "Secure + Adblock"
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var enabled: Bool {
|
||||
switch self {
|
||||
case .secure:
|
||||
return true
|
||||
case .securePlusAdblock:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .secure:
|
||||
return "Malware and phishing protection"
|
||||
case .securePlusAdblock:
|
||||
return "Security plus ad and tracker blocking"
|
||||
}
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .secure:
|
||||
return "shield"
|
||||
case .securePlusAdblock:
|
||||
return "shield.righthalf.filled"
|
||||
}
|
||||
}
|
||||
|
||||
var server: String {
|
||||
switch self {
|
||||
case .secure:
|
||||
return "dns.sr2.uk"
|
||||
case .securePlusAdblock:
|
||||
return "dnsplus.sr2.uk"
|
||||
}
|
||||
}
|
||||
|
||||
var ipv4: String {
|
||||
switch self {
|
||||
case .secure:
|
||||
return "144.76.160.194"
|
||||
case .securePlusAdblock:
|
||||
return "192.0.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
var ipv6: String {
|
||||
switch self {
|
||||
case .secure:
|
||||
return "2a01:4f8:2210:23ea::4"
|
||||
case .securePlusAdblock:
|
||||
return "2001:db8::1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ServiceStatus {
|
||||
case pending
|
||||
case operational
|
||||
case degraded
|
||||
case outage
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .pending:
|
||||
return "Fetching service status"
|
||||
case .operational:
|
||||
return "No issues detected"
|
||||
case .degraded:
|
||||
return "Performance degraded"
|
||||
case .outage:
|
||||
return "Service disruption"
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .pending:
|
||||
return .gray
|
||||
case .operational:
|
||||
return .green
|
||||
case .degraded:
|
||||
return .orange
|
||||
case .outage:
|
||||
return .red
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HomeView: View {
|
||||
@State private var isEnabled = false
|
||||
@State private var selectedBlocklist: BlocklistOption = .secure
|
||||
@State private var serviceStatus: ServiceStatus = .operational
|
||||
|
||||
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 {
|
||||
NavigationStack {
|
||||
List {
|
||||
|
||||
// Main toggle section
|
||||
Section {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("DNS Protection")
|
||||
.font(.headline)
|
||||
Text(isEnabled ? "Active" : "Inactive")
|
||||
.font(.caption)
|
||||
.foregroundStyle(isEnabled ? .green : .secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Toggle("", isOn: $isEnabled)
|
||||
.labelsHidden()
|
||||
.tint(.green)
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
|
||||
// Blocklist selection
|
||||
Section {
|
||||
ForEach(BlocklistOption.allCases) { option in
|
||||
BlocklistRow(
|
||||
option: option,
|
||||
isSelected: selectedBlocklist == option
|
||||
)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
guard option.enabled else { return }
|
||||
withAnimation(.spring(duration: 0.3)) {
|
||||
selectedBlocklist = option
|
||||
}
|
||||
}
|
||||
.opacity(option.enabled ? 1 : 0.6)
|
||||
}
|
||||
} header: {
|
||||
Text("Blocklist")
|
||||
} footer: {
|
||||
Text("Select the level of protection for your DNS queries")
|
||||
}
|
||||
|
||||
// Status section
|
||||
if isEnabled {
|
||||
Section {
|
||||
HStack {
|
||||
Label("Status", systemImage: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
Spacer()
|
||||
Text("Connected")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Label("Server", systemImage: "server.rack")
|
||||
Spacer()
|
||||
Text(selectedBlocklist.server)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
}
|
||||
|
||||
HStack {
|
||||
Label("IPv4", systemImage: "globe")
|
||||
Spacer()
|
||||
Text(selectedBlocklist.ipv4)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
}
|
||||
|
||||
HStack {
|
||||
Label("IPv6", systemImage: "globe")
|
||||
Spacer()
|
||||
Text(selectedBlocklist.ipv6)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
}
|
||||
|
||||
HStack {
|
||||
Label("Domains in blocklist", systemImage: "xmark.shield.fill")
|
||||
Spacer()
|
||||
BlockedCount()
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} header: {
|
||||
Text("Connection Details")
|
||||
}
|
||||
}
|
||||
|
||||
// Support section
|
||||
Section {
|
||||
Link(destination: falsePositiveURL) {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Report False Positive")
|
||||
} icon: {
|
||||
Image(systemName: "exclamationmark.bubble")
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "arrow.up.right.square")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}.foregroundStyle(.primary)
|
||||
} footer: {
|
||||
Text("Submit incorrectly blocked domains for review")
|
||||
}
|
||||
|
||||
// Service status section
|
||||
Section {
|
||||
Link(destination: statusURL) {
|
||||
HStack(spacing: 12) {
|
||||
Circle()
|
||||
.fill(serviceStatus.color)
|
||||
.frame(width: 12, height: 12)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Service Status")
|
||||
.font(.headline)
|
||||
Text(serviceStatus.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("Terms of Service")
|
||||
Spacer()
|
||||
Image(systemName: "arrow.up.right.square")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
}
|
||||
}.foregroundStyle(.primary)
|
||||
|
||||
Link(destination: privacyPolicyURL) {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: "doc.text")
|
||||
Text("Privacy Policy")
|
||||
Spacer()
|
||||
Image(systemName: "arrow.up.right.square")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
}
|
||||
}.foregroundStyle(.primary)
|
||||
}
|
||||
}
|
||||
.navigationTitle("SR2® Cloud DNS")
|
||||
.animation(.default, value: isEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
HomeView()
|
||||
}
|
||||
263
dns/Localizable.xcstrings
Normal file
263
dns/Localizable.xcstrings
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"" : {
|
||||
|
||||
},
|
||||
"(Coming Soon)" : {
|
||||
"comment" : "Indicates that this feature is not yet implemented but will be soon",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "(Coming Soon)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Active" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Active"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Blocklist" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Blocklist"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Connected" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Connected"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Connection Details" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Connection Details"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DNS Protection" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "DNS Protection"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Domains in blocklist" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Domains in blocklist"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Inactive" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Inactive"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"IPv4" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "IPv4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"IPv6" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "IPv6"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Malware and phishing protection" : {
|
||||
"comment" : "Description of the blocklist contents",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Malware and phishing protection"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"No issues detected" : {
|
||||
"comment" : "No current issues detected with the service",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "No issues detected"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Privacy Policy" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Privacy Policy"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Report False Positive" : {
|
||||
"comment" : "Link to report that a domain name has been incorrectly blocked",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Report False Positive"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Secure" : {
|
||||
"comment" : "Name of the blocklist that only includes malware and security threats",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Secure"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Secure + Adblock" : {
|
||||
"comment" : "Name of the blocklist that contains “Secure” plus ad blocking",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Secure + Adblock"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Security plus ad and tracker blocking" : {
|
||||
"comment" : "Description of the blocklist contents",
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Security plus ad and tracker blocking"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Select the level of protection for your DNS queries" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Select the level of protection for your DNS queries"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Server" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Server"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Service Status" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Service Status"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SR2® Cloud DNS" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "SR2® Cloud DNS"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Status" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Status"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Submit incorrectly blocked domains for review" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Submit incorrectly blocked domains for review"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Terms of Service" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Terms of Service"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.2"
|
||||
}
|
||||
|
|
@ -1,9 +1,3 @@
|
|||
//
|
||||
// dnsApp.swift
|
||||
// dns
|
||||
//
|
||||
// Created by irl on 12/04/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
|
@ -11,7 +5,7 @@ import SwiftUI
|
|||
struct dnsApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
HomeView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue