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;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
|
@ -240,6 +241,7 @@
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
@ -249,8 +251,10 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
|
|
@ -265,6 +269,7 @@
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns;
|
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
||||||
|
|
@ -280,8 +285,10 @@
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = "";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
|
|
@ -296,6 +303,7 @@
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns;
|
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
|
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
|
import SwiftUI
|
||||||
|
|
||||||
|
|
@ -11,7 +5,7 @@ import SwiftUI
|
||||||
struct dnsApp: App {
|
struct dnsApp: App {
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
HomeView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue