feat: add "Restore from Presets" button

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.
This commit is contained in:
Kenta Kubo 2024-07-17 02:34:38 +09:00
parent 9e4a505697
commit b18c5b60dc
No known key found for this signature in database
GPG key ID: 3D79E20285C94BE8
3 changed files with 88 additions and 0 deletions

View file

@ -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 = "<group>"; };
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 = "<group>"; };
894F33642C46D2A20060F385 /* RestorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationView.swift; sourceTree = "<group>"; };
8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+displayName.swift"; sourceTree = "<group>"; };
8986CDCE251D9B3400D947CD /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
8998041528DCDED800C8B421 /* DoTSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoTSections.swift; sourceTree = "<group>"; };
@ -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 */,

View file

@ -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<Resolver>) {
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()

View file

@ -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<Resolver>()
@State private var keyword = ""
let onAdd: (Set<Resolver>) -> ()
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 }
}