Skip to content

Commit e963a83

Browse files
authored
Merge pull request #136 from kkebo/116-improve-help-ui-ux
fix: improve help UI/UX
2 parents 47c4f34 + 71617ff commit e963a83

2 files changed

Lines changed: 101 additions & 72 deletions

File tree

DNSecure/Views/ContentView.swift

Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,22 @@ struct ContentView {
1313
@Environment(\.horizontalSizeClass) private var hSizeClass
1414
@Binding var servers: Resolvers
1515
@Binding var usedID: String?
16-
@State private var isEnabled = false
16+
@State private var isActivated = false
1717
@State private var selection: Int?
1818
@State private var isAlertPresented = false
1919
@State private var alertTitle = ""
2020
@State private var alertMessage = ""
2121
@State private var isGuidePresented = false
2222
@State private var isRestoring = false
2323

24+
private var navigationBarTitleDisplayMode: NavigationBarItem.TitleDisplayMode {
25+
if #available(iOS 26, *) {
26+
.inline
27+
} else {
28+
.automatic
29+
}
30+
}
31+
2432
private func addNewDoTServer() {
2533
self.servers.append(
2634
.init(
@@ -78,7 +86,7 @@ struct ContentView {
7886
logger.error("\(err.localizedDescription)")
7987
self.alert("Load Error", err.localizedDescription)
8088
} else {
81-
self.isEnabled = manager.isEnabled
89+
self.isActivated = manager.isEnabled
8290
}
8391
}
8492
#endif
@@ -168,7 +176,6 @@ extension ContentView: View {
168176
private var modernBody: some View {
169177
NavigationSplitView {
170178
List(selection: self.$selection) {
171-
NavigationLink("Instructions", value: -1)
172179
Section("Servers") {
173180
ForEach(0..<self.servers.count, id: \.self) { i in
174181
NavigationLink(value: i) {
@@ -180,20 +187,29 @@ extension ContentView: View {
180187
}
181188
}
182189
.navigationTitle(Bundle.main.displayName!)
183-
.toolbar { self.toolbarContent }
190+
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode)
191+
.toolbar {
192+
if #available(iOS 26, *) {
193+
ToolbarItem(placement: .subtitle) {
194+
self.statusIndicator
195+
.foregroundStyle(.secondary)
196+
}
197+
}
198+
self.toolbarContent
199+
}
184200
.alert(self.alertTitle, isPresented: self.$isAlertPresented) {
185201
} message: {
186202
Text(self.alertMessage)
187203
}
188204
} detail: {
189-
if self.selection == -1 {
190-
HowToActivateView()
191-
} else if let i = self.selection {
205+
if let i = self.selection, i >= 0 {
192206
NavigationStack {
193207
self.detailView(at: i)
208+
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode)
194209
}
195-
} else if !self.isEnabled {
210+
} else if !self.isActivated {
196211
HowToActivateView()
212+
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode)
197213
} else {
198214
Text("Select a server on the sidebar")
199215
.navigationBarHidden(true)
@@ -226,20 +242,6 @@ extension ContentView: View {
226242
private var legacyBody: some View {
227243
NavigationView {
228244
List {
229-
if self.hSizeClass == .compact {
230-
NavigationLink(
231-
"Instructions",
232-
tag: -1,
233-
selection: self.$selection
234-
) {
235-
HowToActivateView()
236-
}
237-
} else {
238-
// Workaround for iOS 15
239-
Button("Instructions") {
240-
self.selection = -1
241-
}
242-
}
243245
Section("Servers") {
244246
ForEach(0..<self.servers.count, id: \.self) { i in
245247
if self.hSizeClass == .compact {
@@ -271,11 +273,9 @@ extension ContentView: View {
271273
Text(self.alertMessage)
272274
}
273275

274-
if self.selection == -1 {
275-
HowToActivateView()
276-
} else if let i = self.selection {
276+
if let i = self.selection, i >= 0 {
277277
self.detailView(at: i)
278-
} else if !self.isEnabled {
278+
} else if !self.isActivated {
279279
HowToActivateView()
280280
} else {
281281
Text("Select a server on the sidebar")
@@ -308,56 +308,51 @@ extension ContentView: View {
308308

309309
@ToolbarContentBuilder private var toolbarContent: some ToolbarContent {
310310
ToolbarItem(placement: .topBarLeading) {
311-
Menu("Add", systemImage: "plus") {
312-
Button("DNS-over-TLS", action: self.addNewDoTServer)
313-
Button("DNS-over-HTTPS", action: self.addNewDoHServer)
314-
Button("Restore from Presets") {
315-
self.isRestoring = true
316-
}
317-
}
318-
.sheet(isPresented: self.$isRestoring) {
319-
RestorationView(onAdd: self.restoreFromPresets)
311+
if #available(iOS 26, *) {
312+
EditButton()
313+
} else {
314+
self.addMenu
320315
}
321316
}
322317
ToolbarItem(placement: .topBarTrailing) {
323-
EditButton()
318+
if #available(iOS 26, *) {
319+
self.addMenu
320+
} else {
321+
EditButton()
322+
}
324323
}
325324
ToolbarItem(placement: .status) {
326-
VStack(spacing: 0) {
327-
HStack {
328-
Circle()
329-
.frame(width: 10, height: 10)
330-
.foregroundStyle(self.isEnabled ? .green : .secondary)
331-
Text(self.isEnabled ? "Active" : "Inactive")
332-
}
333-
if !self.isEnabled {
334-
Button("How to Activate", systemImage: "questionmark.circle") {
335-
self.isGuidePresented = true
336-
}
337-
.labelStyle(.titleAndIcon)
338-
.font(.caption)
339-
.sheet(isPresented: self.$isGuidePresented) {
340-
NavigationView {
341-
HowToActivateView()
342-
.safeAreaInset(edge: .bottom) {
343-
Button("Dismiss") {
344-
self.isGuidePresented = false
345-
}
346-
.buttonStyle(.borderedProminent)
347-
.controlSize(.large)
348-
.hoverEffect()
349-
.padding()
350-
.frame(maxWidth: .infinity)
351-
.background(Color(.systemBackground))
352-
}
353-
}
354-
.navigationViewStyle(.stack)
355-
}
356-
}
325+
if #available(iOS 26, *) {
326+
} else {
327+
self.statusIndicator
357328
}
358329
}
359330
}
360331

332+
private var addMenu: some View {
333+
Menu("Add", systemImage: "plus") {
334+
Button("DNS-over-TLS", action: self.addNewDoTServer)
335+
Button("DNS-over-HTTPS", action: self.addNewDoHServer)
336+
Button("Restore from Presets") {
337+
self.isRestoring = true
338+
}
339+
}
340+
.sheet(isPresented: self.$isRestoring) {
341+
RestorationView(onAdd: self.restoreFromPresets)
342+
}
343+
}
344+
345+
private var statusIndicator: some View {
346+
Label {
347+
Text(self.isActivated ? "Active" : "Inactive")
348+
} icon: {
349+
Circle()
350+
.fill(self.isActivated ? .green : .secondary)
351+
.frame(width: 10, height: 10)
352+
}
353+
.labelStyle(.titleAndIcon)
354+
}
355+
361356
private func sidebarRow(at i: Int) -> some View {
362357
HStack {
363358
VStack(alignment: .leading) {
@@ -367,6 +362,10 @@ extension ContentView: View {
367362
}
368363
if self.usedID == self.servers[i].id.uuidString {
369364
Spacer()
365+
if !self.isActivated {
366+
Image(systemName: "exclamationmark.triangle")
367+
.foregroundStyle(.red)
368+
}
370369
Image(systemName: "checkmark")
371370
}
372371
}
@@ -375,7 +374,7 @@ extension ContentView: View {
375374
private func detailView(at i: Int) -> some View {
376375
DetailView(
377376
server: self.$servers[i],
378-
isOn: .init(
377+
isSelected: .init(
379378
get: {
380379
self.usedID == self.servers[i].id.uuidString
381380
},
@@ -386,7 +385,8 @@ extension ContentView: View {
386385
self.removeSettings()
387386
}
388387
}
389-
)
388+
),
389+
isActivated: self.$isActivated
390390
)
391391
}
392392
}

DNSecure/Views/DetailView.swift

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import SwiftUI
99

1010
struct DetailView {
1111
@Binding var server: Resolver
12-
@Binding var isOn: Bool
12+
@Binding var isSelected: Bool
13+
@Binding var isActivated: Bool
14+
@State private var isGuidePresented = false
1315

1416
private func binding(for id: UUID) -> Binding<OnDemandRule> {
1517
guard let index = self.server.onDemandRules.map(\.id).firstIndex(of: id) else {
@@ -24,7 +26,33 @@ extension DetailView: View {
2426
var body: some View {
2527
Form {
2628
Section {
27-
Toggle("Use This Server", isOn: self.$isOn)
29+
Toggle("Use This Server", isOn: self.$isSelected)
30+
if self.isSelected && !self.isActivated {
31+
Button("One more step is required.", systemImage: "exclamationmark.triangle") {
32+
self.isGuidePresented = true
33+
}
34+
.labelStyle(.titleAndIcon)
35+
.tint(.red)
36+
.sheet(isPresented: self.$isGuidePresented) {
37+
NavigationView {
38+
HowToActivateView()
39+
.toolbar {
40+
ToolbarItem(placement: .cancellationAction) {
41+
if #available(iOS 26, *) {
42+
Button("Close", systemImage: "xmark", role: .close) {
43+
self.isGuidePresented = false
44+
}
45+
} else {
46+
Button("Close", systemImage: "xmark", role: .cancel) {
47+
self.isGuidePresented = false
48+
}
49+
}
50+
}
51+
}
52+
}
53+
.navigationViewStyle(.stack)
54+
}
55+
}
2856
}
2957
Section("Name") {
3058
LazyTextField("Name", text: self.$server.name)
@@ -81,6 +109,7 @@ extension DetailView: View {
81109
configuration: .dnsOverTLS(DoTConfiguration())
82110
)
83111
),
84-
isOn: .constant(true)
112+
isSelected: .constant(true),
113+
isActivated: .constant(true)
85114
)
86115
}

0 commit comments

Comments
 (0)