From 71617ff998b5d9fa7a258f81db929239134ea095 Mon Sep 17 00:00:00 2001 From: Kenta Kubo <601636+kkebo@users.noreply.github.com> Date: Fri, 5 Sep 2025 03:17:43 +0900 Subject: [PATCH] fix: improve help UI/UX --- DNSecure/Views/ContentView.swift | 142 +++++++++++++++---------------- DNSecure/Views/DetailView.swift | 35 +++++++- 2 files changed, 103 insertions(+), 74 deletions(-) diff --git a/DNSecure/Views/ContentView.swift b/DNSecure/Views/ContentView.swift index ba93f1a..e55ee59 100644 --- a/DNSecure/Views/ContentView.swift +++ b/DNSecure/Views/ContentView.swift @@ -13,7 +13,7 @@ struct ContentView { @Environment(\.horizontalSizeClass) private var hSizeClass @Binding var servers: Resolvers @Binding var usedID: String? - @State private var isEnabled = false + @State private var isActivated = false @State private var selection: Int? @State private var isAlertPresented = false @State private var alertTitle = "" @@ -21,6 +21,14 @@ struct ContentView { @State private var isGuidePresented = false @State private var isRestoring = false + private var navigationBarTitleDisplayMode: NavigationBarItem.TitleDisplayMode { + if #available(iOS 26, *) { + .inline + } else { + .automatic + } + } + private func addNewDoTServer() { self.servers.append( .init( @@ -78,7 +86,7 @@ struct ContentView { logger.error("\(err.localizedDescription)") self.alert("Load Error", err.localizedDescription) } else { - self.isEnabled = manager.isEnabled + self.isActivated = manager.isEnabled } } #endif @@ -168,7 +176,6 @@ extension ContentView: View { private var modernBody: some View { NavigationSplitView { List(selection: self.$selection) { - NavigationLink("Instructions", value: -1) Section("Servers") { ForEach(0..= 0 { NavigationStack { self.detailView(at: i) + .navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode) } - } else if !self.isEnabled { + } else if !self.isActivated { HowToActivateView() + .navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode) } else { Text("Select a server on the sidebar") .navigationBarHidden(true) @@ -226,20 +242,6 @@ extension ContentView: View { private var legacyBody: some View { NavigationView { List { - if self.hSizeClass == .compact { - NavigationLink( - "Instructions", - tag: -1, - selection: self.$selection - ) { - HowToActivateView() - } - } else { - // Workaround for iOS 15 - Button("Instructions") { - self.selection = -1 - } - } Section("Servers") { ForEach(0..= 0 { self.detailView(at: i) - } else if !self.isEnabled { + } else if !self.isActivated { HowToActivateView() } else { Text("Select a server on the sidebar") @@ -308,54 +308,49 @@ extension ContentView: View { @ToolbarContentBuilder private var toolbarContent: some ToolbarContent { ToolbarItem(placement: .topBarLeading) { - Menu("Add", systemImage: "plus") { - Button("DNS-over-TLS", action: self.addNewDoTServer) - Button("DNS-over-HTTPS", action: self.addNewDoHServer) - Button("Restore from Presets") { - self.isRestoring = true - } - } - .sheet(isPresented: self.$isRestoring) { - RestorationView(onAdd: self.restoreFromPresets) + if #available(iOS 26, *) { + EditButton() + } else { + self.addMenu } } ToolbarItem(placement: .topBarTrailing) { - EditButton() - } - ToolbarItem(placement: .status) { - VStack(spacing: 0) { - HStack { - Circle() - .frame(width: 10, height: 10) - .foregroundStyle(self.isEnabled ? .green : .secondary) - Text(self.isEnabled ? "Active" : "Inactive") - } - if !self.isEnabled { - Button("How to Activate", systemImage: "questionmark.circle") { - self.isGuidePresented = true - } - .labelStyle(.titleAndIcon) - .font(.caption) - .sheet(isPresented: self.$isGuidePresented) { - NavigationView { - HowToActivateView() - .safeAreaInset(edge: .bottom) { - Button("Dismiss") { - self.isGuidePresented = false - } - .buttonStyle(.borderedProminent) - .controlSize(.large) - .hoverEffect() - .padding() - .frame(maxWidth: .infinity) - .background(Color(.systemBackground)) - } - } - .navigationViewStyle(.stack) - } - } + if #available(iOS 26, *) { + self.addMenu + } else { + EditButton() } } + ToolbarItem(placement: .status) { + if #available(iOS 26, *) { + } else { + self.statusIndicator + } + } + } + + private var addMenu: some View { + Menu("Add", systemImage: "plus") { + Button("DNS-over-TLS", action: self.addNewDoTServer) + Button("DNS-over-HTTPS", action: self.addNewDoHServer) + Button("Restore from Presets") { + self.isRestoring = true + } + } + .sheet(isPresented: self.$isRestoring) { + RestorationView(onAdd: self.restoreFromPresets) + } + } + + private var statusIndicator: some View { + Label { + Text(self.isActivated ? "Active" : "Inactive") + } icon: { + Circle() + .fill(self.isActivated ? .green : .secondary) + .frame(width: 10, height: 10) + } + .labelStyle(.titleAndIcon) } private func sidebarRow(at i: Int) -> some View { @@ -367,6 +362,10 @@ extension ContentView: View { } if self.usedID == self.servers[i].id.uuidString { Spacer() + if !self.isActivated { + Image(systemName: "exclamationmark.triangle") + .foregroundStyle(.red) + } Image(systemName: "checkmark") } } @@ -375,7 +374,7 @@ extension ContentView: View { private func detailView(at i: Int) -> some View { DetailView( server: self.$servers[i], - isOn: .init( + isSelected: .init( get: { self.usedID == self.servers[i].id.uuidString }, @@ -386,7 +385,8 @@ extension ContentView: View { self.removeSettings() } } - ) + ), + isActivated: self.$isActivated ) } } diff --git a/DNSecure/Views/DetailView.swift b/DNSecure/Views/DetailView.swift index fb6f6d8..6e7bb69 100644 --- a/DNSecure/Views/DetailView.swift +++ b/DNSecure/Views/DetailView.swift @@ -9,7 +9,9 @@ import SwiftUI struct DetailView { @Binding var server: Resolver - @Binding var isOn: Bool + @Binding var isSelected: Bool + @Binding var isActivated: Bool + @State private var isGuidePresented = false private func binding(for id: UUID) -> Binding { guard let index = self.server.onDemandRules.map(\.id).firstIndex(of: id) else { @@ -24,7 +26,33 @@ extension DetailView: View { var body: some View { Form { Section { - Toggle("Use This Server", isOn: self.$isOn) + Toggle("Use This Server", isOn: self.$isSelected) + if self.isSelected && !self.isActivated { + Button("One more step is required.", systemImage: "exclamationmark.triangle") { + self.isGuidePresented = true + } + .labelStyle(.titleAndIcon) + .tint(.red) + .sheet(isPresented: self.$isGuidePresented) { + NavigationView { + HowToActivateView() + .toolbar { + ToolbarItem(placement: .cancellationAction) { + if #available(iOS 26, *) { + Button("Close", systemImage: "xmark", role: .close) { + self.isGuidePresented = false + } + } else { + Button("Close", systemImage: "xmark", role: .cancel) { + self.isGuidePresented = false + } + } + } + } + } + .navigationViewStyle(.stack) + } + } } Section("Name") { LazyTextField("Name", text: self.$server.name) @@ -81,6 +109,7 @@ extension DetailView: View { configuration: .dnsOverTLS(DoTConfiguration()) ) ), - isOn: .constant(true) + isSelected: .constant(true), + isActivated: .constant(true) ) }