From b18c5b60dc2254eef0db07210689cb3bd5ab406c Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Wed, 17 Jul 2024 02:34:38 +0900 Subject: [PATCH] feat: add "Restore from Presets" button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This feature allows you to select and add any server from the presets. Without it, when a new server like Freifunk München DNS was added to the preset, existing users would have to reinstall the app in order to add it to their server list. This change resolves such a problem. --- DNSecure.xcodeproj/project.pbxproj | 4 ++ DNSecure/Views/ContentView.swift | 11 +++++ DNSecure/Views/RestorationView.swift | 73 ++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 DNSecure/Views/RestorationView.swift diff --git a/DNSecure.xcodeproj/project.pbxproj b/DNSecure.xcodeproj/project.pbxproj index 60b4a62..b092033 100644 --- a/DNSecure.xcodeproj/project.pbxproj +++ b/DNSecure.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 8940025924ACBD2800EBE74B /* DNSecureUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940025824ACBD2800EBE74B /* DNSecureUITests.swift */; }; 8940026924ACBE4900EBE74B /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8940026824ACBE4900EBE74B /* NetworkExtension.framework */; }; 894958AD2548405E009691D5 /* RuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894958AC2548405E009691D5 /* RuleView.swift */; }; + 894F33652C46D2F00060F385 /* RestorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894F33642C46D2A20060F385 /* RestorationView.swift */; }; 8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */; }; 8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8986CDCE251D9B3400D947CD /* Resolver.swift */; }; 8998041628DCDED800C8B421 /* DoTSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8998041528DCDED800C8B421 /* DoTSections.swift */; }; @@ -75,6 +76,7 @@ 8940026624ACBE4900EBE74B /* DNSecure.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DNSecure.entitlements; sourceTree = ""; }; 8940026824ACBE4900EBE74B /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 894958AC2548405E009691D5 /* RuleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleView.swift; sourceTree = ""; }; + 894F33642C46D2A20060F385 /* RestorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationView.swift; sourceTree = ""; }; 8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+displayName.swift"; sourceTree = ""; }; 8986CDCE251D9B3400D947CD /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = ""; }; 8998041528DCDED800C8B421 /* DoTSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoTSections.swift; sourceTree = ""; }; @@ -112,6 +114,7 @@ 893AA816258F790D0060B022 /* Views */ = { isa = PBXGroup; children = ( + 894F33642C46D2A20060F385 /* RestorationView.swift */, 8940023D24ACBD2700EBE74B /* ContentView.swift */, 89CB922025209DD100B6983C /* HowToActivateView.swift */, 890B80D4251DC3A20046BAA0 /* DetailView.swift */, @@ -354,6 +357,7 @@ 893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */, 893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */, 893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */, + 894F33652C46D2F00060F385 /* RestorationView.swift in Sources */, 8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */, 894958AD2548405E009691D5 /* RuleView.swift in Sources */, 8998041828DCDEEF00C8B421 /* DoHSections.swift in Sources */, diff --git a/DNSecure/Views/ContentView.swift b/DNSecure/Views/ContentView.swift index 0db3607..a9e4b01 100644 --- a/DNSecure/Views/ContentView.swift +++ b/DNSecure/Views/ContentView.swift @@ -19,6 +19,7 @@ struct ContentView { @State private var alertTitle = "" @State private var alertMessage = "" @State private var guideIsPresented = false + @State private var isRestoring = false private func addNewDoTServer() { self.servers.append( @@ -40,6 +41,10 @@ struct ContentView { self.selection = self.servers.count - 1 } + private func restoreFromPresets(resolvers: Set) { + self.servers.append(contentsOf: resolvers) + } + private func removeServers(at indexSet: IndexSet) { if let current = self.selection, indexSet.contains(where: { $0 <= current }) { // FIXME: This is a workaround not to crash on deletion. @@ -271,9 +276,15 @@ extension ContentView: View { Menu { Button("DNS-over-TLS", action: self.addNewDoTServer) Button("DNS-over-HTTPS", action: self.addNewDoHServer) + Button("Restore from Presets") { + self.isRestoring = true + } } label: { Image(systemName: "plus") } + .sheet(isPresented: self.$isRestoring) { + RestorationView(onAdd: self.restoreFromPresets) + } } ToolbarItem(placement: .topBarTrailing) { EditButton() diff --git a/DNSecure/Views/RestorationView.swift b/DNSecure/Views/RestorationView.swift new file mode 100644 index 0000000..1eb6b3b --- /dev/null +++ b/DNSecure/Views/RestorationView.swift @@ -0,0 +1,73 @@ +// +// RestorationView.swift +// DNSecure +// +// Created by Kenta Kubo on 7/17/24. +// + +import SwiftUI + +struct RestorationView { + @Environment(\.dismiss) private var dismiss + @State private var selection = Set() + @State private var keyword = "" + let onAdd: (Set) -> () + + private var servers: Resolvers { + guard !self.keyword.isEmpty else { return Presets.servers } + return Presets.servers.filter { $0.name.localizedCaseInsensitiveContains(self.keyword) } + } +} + +extension RestorationView: View { + var body: some View { + NavigationView { + List(self.servers, id: \.self) { resolver in + Button { + if self.selection.contains(resolver) { + self.selection.remove(resolver) + } else { + self.selection.insert(resolver) + } + } label: { + HStack { + VStack(alignment: .leading) { + Text(resolver.name) + Text(resolver.configuration.description) + .foregroundStyle(.secondary) + } + Spacer() + if self.selection.contains(resolver) { + Image(systemName: "checkmark") + } + } + .tint(.primary) + } + } + .navigationTitle("Presets") + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button("Add") { + self.onAdd(self.selection) + self.dismiss() + } + .disabled(self.selection.isEmpty) + } + ToolbarItem(placement: .cancellationAction) { + Button("Cancel", role: .cancel) { + self.dismiss() + } + } + ToolbarItem(placement: .bottomBar) { + Text("\(self.selection.count) Selected") + } + } + } + .navigationViewStyle(.stack) + .searchable(text: self.$keyword, placement: .navigationBarDrawer(displayMode: .always)) + } +} + +#Preview { + RestorationView { _ in } +}