Compare commits

...

9 commits

26 changed files with 454 additions and 947 deletions

23
.bartycrouch.toml Normal file
View file

@ -0,0 +1,23 @@
[update]
tasks = ["code", "normalize"]
[update.code]
codePaths = ["."]
localizablePaths = ["dns"]
defaultToKeys = true
additive = false
unstripped = false
plistArguments = false
ignoreKeys = ["#bartycrouch-ignore!", "#bc-ignore!", "#i!"]
[update.normalize]
paths = ["dns"]
subpathsToIgnore = ["InfoPlist.strings"]
sourceLocale = "en"
harmonizeWithSource = true
sortByKeys = true
[lint]
paths = ["dns"]
duplicateKeys = true
emptyValues = true

17
Config.xcconfig Normal file
View file

@ -0,0 +1,17 @@
// Avoid accidental checkins:
// git update-index --skip-worktree Config.xcconfig
// git update-index --no-skip-worktree Config.xcconfig
DEVELOPMENT_TEAM[config=Debug] =
APP_PRODUCT_BUNDLE_IDENTIFIER[config=Debug] =
APP_PROVISIONING_PROFILE_SPECIFIER[config=Debug] =
STICKERS_PRODUCT_BUNDLE_IDENTIFIER[config=Debug] =
STICKERS_PROVISIONING_PROFILE_SPECIFIER[config=Debug] =
DEVELOPMENT_TEAM[config=Release] =
APP_PRODUCT_BUNDLE_IDENTIFIER[config=Release] = uk.sr2.dns
APP_PROVISIONING_PROFILE_SPECIFIER[config=Release] =
STICKERS_PRODUCT_BUNDLE_IDENTIFIER[config=Release] = uk.sr2.dns.Stickers
STICKERS_PROVISIONING_PROFILE_SPECIFIER[config=Release] =

View file

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
A051B5122FC9E0D700EACDC0 /* AsyncDNSResolver in Frameworks */ = {isa = PBXBuildFile; productRef = A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */; };
A08264132FC718790077B227 /* Stickers.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A082640C2FC718790077B227 /* Stickers.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; A08264132FC718790077B227 /* Stickers.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = A082640C2FC718790077B227 /* Stickers.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -36,6 +37,8 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
069DCCFA2F8C0DCE00F1EB16 /* dns.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dns.app; sourceTree = BUILT_PRODUCTS_DIR; }; 069DCCFA2F8C0DCE00F1EB16 /* dns.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dns.app; sourceTree = BUILT_PRODUCTS_DIR; };
A051B50B2FC9DA9100EACDC0 /* .bartycrouch.toml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .bartycrouch.toml; sourceTree = "<group>"; };
A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
A06A74772F8E95410093A9E4 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; }; A06A74772F8E95410093A9E4 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
A06A74782F8E95410093A9E4 /* LICENCE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENCE; sourceTree = "<group>"; }; A06A74782F8E95410093A9E4 /* LICENCE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENCE; sourceTree = "<group>"; };
A06A74792F8E95410093A9E4 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; A06A74792F8E95410093A9E4 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -73,6 +76,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A051B5122FC9E0D700EACDC0 /* AsyncDNSResolver in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -85,6 +89,8 @@
A06A74772F8E95410093A9E4 /* .gitignore */, A06A74772F8E95410093A9E4 /* .gitignore */,
A06A74782F8E95410093A9E4 /* LICENCE */, A06A74782F8E95410093A9E4 /* LICENCE */,
A06A74792F8E95410093A9E4 /* README.md */, A06A74792F8E95410093A9E4 /* README.md */,
A051B50B2FC9DA9100EACDC0 /* .bartycrouch.toml */,
A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */,
069DCCFC2F8C0DCE00F1EB16 /* dns */, 069DCCFC2F8C0DCE00F1EB16 /* dns */,
A082640D2FC718790077B227 /* Stickers */, A082640D2FC718790077B227 /* Stickers */,
069DCCFB2F8C0DCE00F1EB16 /* Products */, 069DCCFB2F8C0DCE00F1EB16 /* Products */,
@ -107,6 +113,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 069DCD052F8C0DCE00F1EB16 /* Build configuration list for PBXNativeTarget "dns" */; buildConfigurationList = 069DCD052F8C0DCE00F1EB16 /* Build configuration list for PBXNativeTarget "dns" */;
buildPhases = ( buildPhases = (
A051B50A2FC9DA4300EACDC0 /* BartyCrouch */,
069DCCF62F8C0DCE00F1EB16 /* Sources */, 069DCCF62F8C0DCE00F1EB16 /* Sources */,
069DCCF72F8C0DCE00F1EB16 /* Frameworks */, 069DCCF72F8C0DCE00F1EB16 /* Frameworks */,
069DCCF82F8C0DCE00F1EB16 /* Resources */, 069DCCF82F8C0DCE00F1EB16 /* Resources */,
@ -122,6 +129,7 @@
); );
name = dns; name = dns;
packageProductDependencies = ( packageProductDependencies = (
A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */,
); );
productName = dns; productName = dns;
productReference = 069DCCFA2F8C0DCE00F1EB16 /* dns.app */; productReference = 069DCCFA2F8C0DCE00F1EB16 /* dns.app */;
@ -155,7 +163,7 @@
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 2640; LastSwiftUpdateCheck = 2640;
LastUpgradeCheck = 2640; LastUpgradeCheck = 2650;
TargetAttributes = { TargetAttributes = {
069DCCF92F8C0DCE00F1EB16 = { 069DCCF92F8C0DCE00F1EB16 = {
CreatedOnToolsVersion = 26.4; CreatedOnToolsVersion = 26.4;
@ -171,9 +179,13 @@
knownRegions = ( knownRegions = (
en, en,
Base, Base,
de,
); );
mainGroup = 069DCCF12F8C0DCD00F1EB16; mainGroup = 069DCCF12F8C0DCD00F1EB16;
minimizedProjectReferenceProxies = 1; minimizedProjectReferenceProxies = 1;
packageReferences = (
A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */,
);
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = 069DCCFB2F8C0DCE00F1EB16 /* Products */; productRefGroup = 069DCCFB2F8C0DCE00F1EB16 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -202,6 +214,28 @@
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
A051B50A2FC9DA4300EACDC0 /* BartyCrouch */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = BartyCrouch;
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [ -f /opt/homebrew/bin/bartycrouch ]; then\n /opt/homebrew/bin/bartycrouch update -x\n /opt/homebrew/bin/bartycrouch lint -x\nelse\n echo \"warning: BartyCrouch not installed. Download it form https://github.com/Flinesoft/BartyCrouch\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
069DCCF62F8C0DCE00F1EB16 /* Sources */ = { 069DCCF62F8C0DCE00F1EB16 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
@ -223,9 +257,11 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
069DCD032F8C0DCE00F1EB16 /* Debug */ = { 069DCD032F8C0DCE00F1EB16 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@ -287,9 +323,11 @@
}; };
069DCD042F8C0DCE00F1EB16 /* Release */ = { 069DCD042F8C0DCE00F1EB16 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A051B5132FC9E7BC00EACDC0 /* Config.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
@ -348,11 +386,10 @@
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_ENTITLEMENTS = dns/dns.entitlements; CODE_SIGN_ENTITLEMENTS = dns/dns.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "SR2 Cloud DNS"; INFOPLIST_KEY_CFBundleDisplayName = "SR2 Cloud DNS";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
@ -368,10 +405,10 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "$(APP_PROVISIONING_PROFILE_SPECIFIER)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@ -387,11 +424,10 @@
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_ENTITLEMENTS = dns/dns.entitlements; CODE_SIGN_ENTITLEMENTS = dns/dns.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "SR2 Cloud DNS"; INFOPLIST_KEY_CFBundleDisplayName = "SR2 Cloud DNS";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
@ -407,10 +443,10 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "$(APP_PROVISIONING_PROFILE_SPECIFIER)";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@ -424,19 +460,17 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon"; ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Stickers/Info.plist; INFOPLIST_FILE = Stickers/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Stickers; INFOPLIST_KEY_CFBundleDisplayName = Stickers;
INFOPLIST_KEY_NSStickerSharingLevel = OS; INFOPLIST_KEY_NSStickerSharingLevel = OS;
IPHONEOS_DEPLOYMENT_TARGET = 26.5; IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns.Stickers; PRODUCT_BUNDLE_IDENTIFIER = "$(STICKERS_PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "$(STICKERS_PROVISIONING_PROFILE_SPECIFIER)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@ -448,19 +482,17 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon"; ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Stickers/Info.plist; INFOPLIST_FILE = Stickers/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Stickers; INFOPLIST_KEY_CFBundleDisplayName = Stickers;
INFOPLIST_KEY_NSStickerSharingLevel = OS; INFOPLIST_KEY_NSStickerSharingLevel = OS;
IPHONEOS_DEPLOYMENT_TARGET = 26.5; IPHONEOS_DEPLOYMENT_TARGET = 26.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = uk.sr2.dns.Stickers; PRODUCT_BUNDLE_IDENTIFIER = "$(STICKERS_PRODUCT_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "$(STICKERS_PROVISIONING_PROFILE_SPECIFIER)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@ -499,6 +531,25 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/apple/swift-async-dns-resolver/";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.7.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
A051B5112FC9E0D700EACDC0 /* AsyncDNSResolver */ = {
isa = XCSwiftPackageProductDependency;
package = A051B5102FC9E0D700EACDC0 /* XCRemoteSwiftPackageReference "swift-async-dns-resolver" */;
productName = AsyncDNSResolver;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 069DCCF22F8C0DCD00F1EB16 /* Project object */; rootObject = 069DCCF22F8C0DCD00F1EB16 /* Project object */;
} }

View file

@ -0,0 +1,15 @@
{
"originHash" : "8d197ac71cfa33d9e3396085f37fbee66795a894a6d74e24ff28bd887e01b9f2",
"pins" : [
{
"identity" : "swift-async-dns-resolver",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-async-dns-resolver/",
"state" : {
"revision" : "9900d9b4113427a170d5f26075c2128be94984df",
"version" : "0.7.0"
}
}
],
"version" : 3
}

View file

@ -1,9 +1,13 @@
import SwiftUI import SwiftUI
import Foundation import Foundation
import AsyncDNSResolver
struct BlockedCount: View { struct BlockedCount: View {
@Environment(\.scenePhase)
private var scenePhase
@EnvironmentObject @EnvironmentObject
private var viewModel: ViewModel private var viewModel: ViewModel
@ -21,53 +25,39 @@ struct BlockedCount: View {
var body: some View { var body: some View {
Text(txtRecord) Text(txtRecord)
.onAppear { .onAppear {
fetchTXTRecord() fetchTxtRecord()
} }
.onChange(of: viewModel.blocklist) { _ in .onChange(of: viewModel.blocklist) { _ in
fetchTXTRecord() fetchTxtRecord()
}
.onChange(of: scenePhase) { newPhase in
if newPhase == .active {
fetchTxtRecord()
}
} }
} }
func parseResponse(data: Data) -> String? { private func fetchTxtRecord() {
// 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 > Self.startIndex else {
return nil
}
// Find the first space character (ASCII 32).
guard let endIndex = data.suffix(from: Self.startIndex).firstIndex(of: 32) else {
return nil
}
guard let numberString = String(data: data[Self.startIndex ..< endIndex], encoding: .utf8)
else {
return nil
}
return Self.formatter.string(for: Int(numberString))
}
func fetchTXTRecord() {
let dohURL = URL(string: "https://\(viewModel.blocklist.server)/dns-query?dns=DoQBAAABAAAAAAAABXN0YXRzB2ludmFsaWQAABAAAQ")!
let request = URLRequest(url: dohURL)
txtRecord = ""
Task { Task {
do { do {
let (data, _) = try await URLSession.shared.data(for: request) let resolver = try AsyncDNSResolver()
if let count = parseResponse(data: data) { let records = try await resolver.queryTXT(name: "stats.invalid")
txtRecord = count
let text = records.map { $0.txt }.joined()
if let number = text.split(separator: " ").first,
let intValue = Int(number),
let formatted = Self.formatter.string(for: intValue)
{
txtRecord = formatted
} }
else { else {
txtRecord = "Error" txtRecord = NSLocalizedString("Error", comment: "")
} }
} }
catch { catch {
txtRecord = "Error" txtRecord = NSLocalizedString("Error", comment: "")
} }
} }
} }

View file

@ -12,7 +12,15 @@ enum BlocklistOption: String, CaseIterable, Identifiable {
case secure = "Secure" case secure = "Secure"
case securePlusAdblock = "Secure + Adblock" case securePlusAdblock = "Secure + Adblock"
var id: String { rawValue } var id: String {
switch self {
case .secure:
return NSLocalizedString("Secure", comment: "")
case .securePlusAdblock:
return NSLocalizedString("Secure + Adblock", comment: "")
}
}
var enabled: Bool { var enabled: Bool {
switch self { switch self {
@ -26,12 +34,16 @@ enum BlocklistOption: String, CaseIterable, Identifiable {
var description: String { var description: String {
switch self { switch self {
case .secure: case .secure:
return "Malware and phishing protection" return NSLocalizedString("Malware and phishing protection", comment: "")
case .securePlusAdblock: case .securePlusAdblock:
return "Security plus ad and tracker blocking" return NSLocalizedString("Security plus ad and tracker blocking", comment: "")
} }
} }
var title: String {
return String(format: "%1$@: %2$@", ViewModel.title, id)
}
var icon: String { var icon: String {
switch self { switch self {
case .secure: case .secure:

View file

@ -17,7 +17,7 @@ struct BlocklistRow: View {
} }
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(option.id + " " + (option.enabled ? "" : "(Coming Soon)")) Text(option.enabled ? option.id : String(format: NSLocalizedString("%@ (Coming Soon)", comment: ""), option.id))
.font(.body) .font(.body)
.fontWeight(isSelected ? .semibold : .regular) .fontWeight(isSelected ? .semibold : .regular)

View file

@ -3,6 +3,9 @@ import SwiftUI
struct HomeView: View { struct HomeView: View {
@Environment(\.scenePhase)
private var scenePhase
@EnvironmentObject @EnvironmentObject
private var viewModel: ViewModel private var viewModel: ViewModel
@ -20,9 +23,9 @@ struct HomeView: View {
Section { Section {
HStack { HStack {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("DNS Protection") Text(NSLocalizedString("DNS Protection", comment: ""))
.font(.headline) .font(.headline)
Text(viewModel.isDnsEnabled ? "Active" : "Inactive") Text(viewModel.isDnsEnabled ? NSLocalizedString("Active", comment: "") : NSLocalizedString("Inactive", comment: ""))
.font(.caption) .font(.caption)
.foregroundStyle(viewModel.isDnsEnabled ? .green : .secondary) .foregroundStyle(viewModel.isDnsEnabled ? .green : .secondary)
} }
@ -31,11 +34,37 @@ struct HomeView: View {
Toggle("", isOn: $viewModel.isDnsEnabled) Toggle("", isOn: $viewModel.isDnsEnabled)
.labelsHidden() .labelsHidden()
.disabled(true)
.tint(.green) .tint(.green)
} }
.padding(.vertical, 4) .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 // Blocklist selection
Section { Section {
ForEach(BlocklistOption.allCases) { option in ForEach(BlocklistOption.allCases) { option in
@ -53,24 +82,24 @@ struct HomeView: View {
.opacity(option.enabled ? 1 : 0.6) .opacity(option.enabled ? 1 : 0.6)
} }
} header: { } header: {
Text("Blocklist") Text(NSLocalizedString("Blocklist", comment: ""))
} footer: { } footer: {
Text("Select the level of protection for your DNS queries") Text(NSLocalizedString("Select the level of protection for your DNS queries", comment: ""))
} }
// Status section // Status section
if viewModel.isDnsEnabled { if viewModel.isDnsEnabled {
Section { Section {
HStack { HStack {
Label("Status", systemImage: "checkmark.circle.fill") Label(NSLocalizedString("Status", comment: ""), systemImage: "checkmark.circle.fill")
.foregroundStyle(.green) .foregroundStyle(.green)
Spacer() Spacer()
Text("Connected") Text(NSLocalizedString("Connected", comment: ""))
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
HStack { HStack {
Label("Server", systemImage: "server.rack") Label(NSLocalizedString("Server", comment: ""), systemImage: "server.rack")
Spacer() Spacer()
Text(viewModel.blocklist.server) Text(viewModel.blocklist.server)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
@ -94,13 +123,13 @@ struct HomeView: View {
} }
HStack { HStack {
Label("Domains in blocklist", systemImage: "xmark.shield.fill") Label(NSLocalizedString("Domains in blocklist", comment: ""), systemImage: "xmark.shield.fill")
Spacer() Spacer()
BlockedCount() BlockedCount()
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
} header: { } header: {
Text("Connection Details") Text(NSLocalizedString("Connection Details", comment: ""))
} }
} }
@ -109,7 +138,7 @@ struct HomeView: View {
Link(destination: falsePositiveURL) { Link(destination: falsePositiveURL) {
HStack { HStack {
Label { Label {
Text("Report False Positive") Text(NSLocalizedString("Report False Positive", comment: ""))
} icon: { } icon: {
Image(systemName: "exclamationmark.bubble") Image(systemName: "exclamationmark.bubble")
.foregroundStyle(.orange) .foregroundStyle(.orange)
@ -123,7 +152,7 @@ struct HomeView: View {
} }
}.foregroundStyle(.primary) }.foregroundStyle(.primary)
} footer: { } footer: {
Text("Submit incorrectly blocked domains for review") Text(NSLocalizedString("Submit incorrectly blocked domains for review", comment: ""))
} }
// Service status section // Service status section
@ -135,7 +164,7 @@ struct HomeView: View {
.frame(width: 12, height: 12) .frame(width: 12, height: 12)
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("Service Status") Text(NSLocalizedString("Service Status", comment: ""))
.font(.headline) .font(.headline)
Text(viewModel.summaryStatus.description) Text(viewModel.summaryStatus.description)
.font(.caption) .font(.caption)
@ -154,7 +183,7 @@ struct HomeView: View {
Link(destination: tosURL) { Link(destination: tosURL) {
HStack(spacing: 12) { HStack(spacing: 12) {
Image(systemName: "doc.text") Image(systemName: "doc.text")
Text("Terms of Service") Text(NSLocalizedString("Terms of Service", comment: ""))
Spacer() Spacer()
Image(systemName: "arrow.up.right.square") Image(systemName: "arrow.up.right.square")
.font(.caption) .font(.caption)
@ -166,7 +195,7 @@ struct HomeView: View {
Link(destination: privacyPolicyURL) { Link(destination: privacyPolicyURL) {
HStack(spacing: 12) { HStack(spacing: 12) {
Image(systemName: "doc.text") Image(systemName: "doc.text")
Text("Privacy Policy") Text(NSLocalizedString("Privacy Policy", comment: ""))
Spacer() Spacer()
Image(systemName: "arrow.up.right.square") Image(systemName: "arrow.up.right.square")
.font(.caption) .font(.caption)
@ -176,8 +205,15 @@ struct HomeView: View {
}.foregroundStyle(.primary) }.foregroundStyle(.primary)
} }
} }
.navigationTitle("SR2® Cloud DNS") .navigationTitle(ViewModel.title)
.animation(.default, value: viewModel.isDnsEnabled) .animation(.default, value: viewModel.isDnsEnabled)
.onChange(of: scenePhase) { newPhase in
if newPhase == .active {
Task {
await viewModel.refreshConfig()
}
}
}
} }
} }
} }

View file

@ -1,263 +0,0 @@
{
"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"
}

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -1,147 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="dns/dns-InfoPlist.xcstrings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="26.4" build-num="17E192"/>
</header>
<body>
<trans-unit id="CFBundleName" xml:space="preserve">
<source>dns</source>
<target state="new">dns</target>
<note>Bundle name</note>
</trans-unit>
</body>
</file>
<file original="dns/Localizable.xcstrings" source-language="en" target-language="en" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="26.4" build-num="17E192"/>
</header>
<body>
<trans-unit id="" xml:space="preserve">
<source/>
<target state="new"/>
<note/>
</trans-unit>
<trans-unit id="(Coming Soon)" xml:space="preserve">
<source>(Coming Soon)</source>
<target state="translated">(Coming Soon)</target>
<note>Indicates that this feature is not yet implemented but will be soon</note>
</trans-unit>
<trans-unit id="Active" xml:space="preserve">
<source>Active</source>
<target state="translated">Active</target>
<note/>
</trans-unit>
<trans-unit id="Blocklist" xml:space="preserve">
<source>Blocklist</source>
<target state="translated">Blocklist</target>
<note/>
</trans-unit>
<trans-unit id="Connected" xml:space="preserve">
<source>Connected</source>
<target state="translated">Connected</target>
<note/>
</trans-unit>
<trans-unit id="Connection Details" xml:space="preserve">
<source>Connection Details</source>
<target state="translated">Connection Details</target>
<note/>
</trans-unit>
<trans-unit id="DNS Protection" xml:space="preserve">
<source>DNS Protection</source>
<target state="translated">DNS Protection</target>
<note/>
</trans-unit>
<trans-unit id="Domains in blocklist" xml:space="preserve">
<source>Domains in blocklist</source>
<target state="translated">Domains in blocklist</target>
<note/>
</trans-unit>
<trans-unit id="IPv4" xml:space="preserve">
<source>IPv4</source>
<target state="translated">IPv4</target>
<note/>
</trans-unit>
<trans-unit id="IPv6" xml:space="preserve">
<source>IPv6</source>
<target state="translated">IPv6</target>
<note/>
</trans-unit>
<trans-unit id="Inactive" xml:space="preserve">
<source>Inactive</source>
<target state="translated">Inactive</target>
<note/>
</trans-unit>
<trans-unit id="Malware and phishing protection" xml:space="preserve">
<source>Malware and phishing protection</source>
<target state="translated">Malware and phishing protection</target>
<note>Description of the blocklist contents</note>
</trans-unit>
<trans-unit id="No issues detected" xml:space="preserve">
<source>No issues detected</source>
<target state="translated">No issues detected</target>
<note>No current issues detected with the service</note>
</trans-unit>
<trans-unit id="Privacy Policy" xml:space="preserve">
<source>Privacy Policy</source>
<target state="translated">Privacy Policy</target>
<note/>
</trans-unit>
<trans-unit id="Report False Positive" xml:space="preserve">
<source>Report False Positive</source>
<target state="translated">Report False Positive</target>
<note>Link to report that a domain name has been incorrectly blocked</note>
</trans-unit>
<trans-unit id="SR2® Cloud DNS" xml:space="preserve">
<source>SR2® Cloud DNS</source>
<target state="translated">SR2® Cloud DNS</target>
<note/>
</trans-unit>
<trans-unit id="Secure" xml:space="preserve">
<source>Secure</source>
<target state="translated">Secure</target>
<note>Name of the blocklist that only includes malware and security threats</note>
</trans-unit>
<trans-unit id="Secure + Adblock" xml:space="preserve">
<source>Secure + Adblock</source>
<target state="translated">Secure + Adblock</target>
<note>Name of the blocklist that contains “Secure” plus ad blocking</note>
</trans-unit>
<trans-unit id="Security plus ad and tracker blocking" xml:space="preserve">
<source>Security plus ad and tracker blocking</source>
<target state="translated">Security plus ad and tracker blocking</target>
<note>Description of the blocklist contents</note>
</trans-unit>
<trans-unit id="Select the level of protection for your DNS queries" xml:space="preserve">
<source>Select the level of protection for your DNS queries</source>
<target state="translated">Select the level of protection for your DNS queries</target>
<note/>
</trans-unit>
<trans-unit id="Server" xml:space="preserve">
<source>Server</source>
<target state="translated">Server</target>
<note/>
</trans-unit>
<trans-unit id="Service Status" xml:space="preserve">
<source>Service Status</source>
<target state="translated">Service Status</target>
<note/>
</trans-unit>
<trans-unit id="Status" xml:space="preserve">
<source>Status</source>
<target state="translated">Status</target>
<note/>
</trans-unit>
<trans-unit id="Submit incorrectly blocked domains for review" xml:space="preserve">
<source>Submit incorrectly blocked domains for review</source>
<target state="translated">Submit incorrectly blocked domains for review</target>
<note/>
</trans-unit>
<trans-unit id="Terms of Service" xml:space="preserve">
<source>Terms of Service</source>
<target state="translated">Terms of Service</target>
<note/>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -1,263 +0,0 @@
{
"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"
}

View file

@ -1,18 +0,0 @@
{
"sourceLanguage" : "en",
"strings" : {
"CFBundleName" : {
"comment" : "Bundle name",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "dns"
}
}
}
}
},
"version" : "1.2"
}

View file

@ -1,12 +0,0 @@
{
"developmentRegion" : "en",
"project" : "dns.xcodeproj",
"targetLocale" : "en",
"toolInfo" : {
"toolBuildNumber" : "17E192",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "26.4"
},
"version" : "1.0"
}

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="Weblate" source-language="en" datatype="plaintext">
<body>
</body>
</file>
</xliff>

View file

@ -1,24 +0,0 @@
//
// Settings.swift
// dns
//
// Created by Benjamin Erhart on 15.04.26.
//
import Foundation
class Settings {
private static let blocklistKey = "blocklist"
private static let defaults = UserDefaults.standard
class var blocklist: BlocklistOption {
get {
BlocklistOption(rawValue: defaults.string(forKey: blocklistKey) ?? BlocklistOption.secure.rawValue) ?? .secure
}
set {
defaults.set(newValue.rawValue, forKey: blocklistKey)
}
}
}

View file

@ -23,19 +23,19 @@ enum SummaryStatus: String, Codable, CustomStringConvertible {
var description: String { var description: String {
switch self { switch self {
case .pending: case .pending:
return "Fetching service status" return NSLocalizedString("Fetching service status", comment: "")
case .ok: case .ok:
return "No issues detected" return NSLocalizedString("No issues detected", comment: "")
case .notice: case .notice:
return "In maintenance" return NSLocalizedString("In maintenance", comment: "")
case .disrupted: case .disrupted:
return "Service disruption" return NSLocalizedString("Service disruption", comment: "")
case .down: case .down:
return "Service down" return NSLocalizedString("Service down", comment: "")
} }
} }

View file

@ -12,28 +12,31 @@ import OSLog
class ViewModel: NSObject, ObservableObject { class ViewModel: NSObject, ObservableObject {
static let title = "SR2® Cloud DNS"
// MARK: Public Properties // MARK: Public Properties
@Published @Published
var blocklist: BlocklistOption = .secure { var blocklist: BlocklistOption = .secure {
didSet { didSet {
Settings.blocklist = blocklist Task {
manager.dnsSettings = blocklist.settings
manager.localizedDescription = blocklist.title
if isDnsEnabled { do {
toggleDns() try await manager.saveToPreferences()
} }
else { catch {
isProgrammaticChange = false log.error("Error storing preferences: \(error)")
}
isDnsEnabled = manager.isEnabled
} }
} }
} }
@Published @Published
var isDnsEnabled = false { var isDnsEnabled = false
didSet {
toggleDns()
}
}
@Published @Published
var summaryStatus: SummaryStatus = .pending var summaryStatus: SummaryStatus = .pending
@ -41,8 +44,6 @@ class ViewModel: NSObject, ObservableObject {
// MARK: Private Properties // MARK: Private Properties
private var isProgrammaticChange = false
private let manager = NEDNSSettingsManager.shared() private let manager = NEDNSSettingsManager.shared()
private let log = Logger(subsystem: String(describing: ViewModel.self), category: String(describing: ViewModel.self)) private let log = Logger(subsystem: String(describing: ViewModel.self), category: String(describing: ViewModel.self))
@ -51,33 +52,8 @@ class ViewModel: NSObject, ObservableObject {
override init() { override init() {
super.init() super.init()
isProgrammaticChange = true
blocklist = Settings.blocklist
Task { Task {
do { await refreshConfig()
try await manager.loadFromPreferences()
}
catch {
log.error("Error loading preferences: \(error)")
return
}
if manager.isEnabled, let settings = manager.dnsSettings {
for dnsServer in BlocklistOption.allCases {
if settings.servers.contains(dnsServer.ipv4) {
await MainActor.run {
isProgrammaticChange = true
blocklist = dnsServer
isProgrammaticChange = true
isDnsEnabled = true
}
break
}
}
}
} }
Task { Task {
@ -88,43 +64,31 @@ class ViewModel: NSObject, ObservableObject {
// MARK: Public Methods // MARK: Public Methods
func toggleDns() { func refreshConfig() async {
guard !isProgrammaticChange else { do {
// Reset, so next one is recognized as coming from the user again. try await manager.loadFromPreferences()
isProgrammaticChange = false }
catch {
return log.error("Error loading preferences: \(error)")
} }
Task { isDnsEnabled = manager.isEnabled
if isDnsEnabled {
manager.dnsSettings = blocklist.settings
manager.localizedDescription = blocklist.description
do { if let settings = manager.dnsSettings {
try await manager.saveToPreferences() for dnsServer in BlocklistOption.allCases {
} if settings.servers.contains(dnsServer.ipv4) {
catch { await MainActor.run {
log.error("Error storing preferences: \(error)") blocklist = dnsServer
}
delayedToggle(false) break
}
if !manager.isEnabled {
delayedToggle(false)
}
}
else {
do {
try await manager.removeFromPreferences()
}
catch {
log.error("Error removing preferences: \(error)")
delayedToggle(true)
} }
} }
} }
else {
// Trigger `blocklist.didSet` to store right away, so user has something to select.
blocklist = blocklist
}
} }
func fetchServerStatus() async { func fetchServerStatus() async {
@ -139,18 +103,4 @@ class ViewModel: NSObject, ObservableObject {
log.error("Error while checking status: \(error)") log.error("Error while checking status: \(error)")
} }
} }
// MARK: Private Methods
private func delayedToggle(_ enabled: Bool) {
Task {
try? await Task.sleep(nanoseconds: 500_000_000)
await MainActor.run {
isProgrammaticChange = true
isDnsEnabled = enabled
}
}
}
} }

View file

@ -0,0 +1,98 @@
/* No comment provided by engineer. */
"%1$@ DNS" = "%1$@ DNS";
/* No comment provided by engineer. */
"%1$@ Go to General" = "%1$@ Gehe zu Allgemein";
/* No comment provided by engineer. */
"%1$@ Select \"%2$@\"" = "%1$@ Wähle \"%2$@\"";
/* No comment provided by engineer. */
"%1$@ Tap \"%2$@\"" = "%1$@ Tippe auf \"%2$@\"";
/* No comment provided by engineer. */
"%1$@ VPN & Network" = "%1$@ VPN und Geräteverwaltung";
/* No comment provided by engineer. */
"%@ (Coming Soon)" = "%@ (bald)";
/* No comment provided by engineer. */
"Active" = "Aktiv";
/* No comment provided by engineer. */
"Blocklist" = "Sperrliste";
/* No comment provided by engineer. */
"Connected" = "Verbunden";
/* No comment provided by engineer. */
"Connection Details" = "Verbindungsdetails";
/* No comment provided by engineer. */
"DNS Protection" = "DNSSchutz";
/* No comment provided by engineer. */
"Domains in blocklist" = "Domains in Sperrliste";
/* No comment provided by engineer. */
"Error" = "Fehler";
/* No comment provided by engineer. */
"Fetching service status" = "Servicestatus ermitteln";
/* No comment provided by engineer. */
"In maintenance" = "Wartung";
/* No comment provided by engineer. */
"Inactive" = "Inaktiv";
/* No comment provided by engineer. */
"Malware and phishing protection" = "Malware- und Phishingschutz";
/* No comment provided by engineer. */
"No issues detected" = "Keine Probleme erkannt";
/* No comment provided by engineer. */
"Open Settings" = "Öffne Einstellungen";
/* No comment provided by engineer. */
"Privacy Policy" = "Datenschutzerklärung";
/* No comment provided by engineer. */
"Report False Positive" = "Berichte über falsch-positive Sperre";
/* No comment provided by engineer. */
"Secure" = "Sicherheit";
/* No comment provided by engineer. */
"Secure + Adblock" = "Sicherheit + Werbeblocker";
/* No comment provided by engineer. */
"Security plus ad and tracker blocking" = "Sicherheit und Werbe- und Trackerschutz";
/* No comment provided by engineer. */
"Select the level of protection for your DNS queries" = "Wähle das Schutzniveau für Deine DNS-Anfragen";
/* No comment provided by engineer. */
"Server" = "Server";
/* No comment provided by engineer. */
"Service disruption" = "Serviceunterbrechung";
/* No comment provided by engineer. */
"Service down" = "Service unerreichbar";
/* No comment provided by engineer. */
"Service Status" = "Servicestatus";
/* No comment provided by engineer. */
"Status" = "Status";
/* No comment provided by engineer. */
"Submit incorrectly blocked domains for review" = "Falsch blockierte Domains zur Überprüfung einreichen";
/* No comment provided by engineer. */
"Terms of Service" = "Geschäftsbedingungen";
/* No comment provided by engineer. */
"To enable %@:" = "Um %@ zu aktivieren:";

View file

@ -0,0 +1,98 @@
/* No comment provided by engineer. */
"%1$@ DNS" = "%1$@ DNS";
/* No comment provided by engineer. */
"%1$@ Go to General" = "%1$@ Go to General";
/* No comment provided by engineer. */
"%1$@ Select \"%2$@\"" = "%1$@ Select \"%2$@\"";
/* No comment provided by engineer. */
"%1$@ Tap \"%2$@\"" = "%1$@ Tap \"%2$@\"";
/* No comment provided by engineer. */
"%1$@ VPN & Network" = "%1$@ VPN & Network";
/* No comment provided by engineer. */
"%@ (Coming Soon)" = "%@ (Coming Soon)";
/* No comment provided by engineer. */
"Active" = "Active";
/* No comment provided by engineer. */
"Blocklist" = "Blocklist";
/* No comment provided by engineer. */
"Connected" = "Connected";
/* No comment provided by engineer. */
"Connection Details" = "Connection Details";
/* No comment provided by engineer. */
"DNS Protection" = "DNS Protection";
/* No comment provided by engineer. */
"Domains in blocklist" = "Domains in blocklist";
/* No comment provided by engineer. */
"Error" = "Error";
/* No comment provided by engineer. */
"Fetching service status" = "Fetching service status";
/* No comment provided by engineer. */
"In maintenance" = "In maintenance";
/* No comment provided by engineer. */
"Inactive" = "Inactive";
/* No comment provided by engineer. */
"Malware and phishing protection" = "Malware and phishing protection";
/* No comment provided by engineer. */
"No issues detected" = "No issues detected";
/* No comment provided by engineer. */
"Open Settings" = "Open Settings";
/* No comment provided by engineer. */
"Privacy Policy" = "Privacy Policy";
/* No comment provided by engineer. */
"Report False Positive" = "Report False Positive";
/* No comment provided by engineer. */
"Secure" = "Secure";
/* No comment provided by engineer. */
"Secure + Adblock" = "Secure + Adblock";
/* No comment provided by engineer. */
"Security plus ad and tracker blocking" = "Security plus ad and tracker blocking";
/* No comment provided by engineer. */
"Select the level of protection for your DNS queries" = "Select the level of protection for your DNS queries";
/* No comment provided by engineer. */
"Server" = "Server";
/* No comment provided by engineer. */
"Service disruption" = "Service disruption";
/* No comment provided by engineer. */
"Service down" = "Service down";
/* No comment provided by engineer. */
"Service Status" = "Service Status";
/* No comment provided by engineer. */
"Status" = "Status";
/* No comment provided by engineer. */
"Submit incorrectly blocked domains for review" = "Submit incorrectly blocked domains for review";
/* No comment provided by engineer. */
"Terms of Service" = "Terms of Service";
/* No comment provided by engineer. */
"To enable %@:" = "To enable %@:";