From 34f6738b69371d2186668ad759a9f47a1e16f644 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sun, 23 Jul 2023 13:28:47 +0200 Subject: [PATCH] Focus some text fields on appearance (#334) Feature from iOS 15, use it on: - New profile name - New profile passphrase - Renamed profile name - Account username --- Passepartout/App/Views/AccountView.swift | 17 ++++++++++++++++- Passepartout/App/Views/AddHostView+Name.swift | 11 +++++++++-- Passepartout/App/Views/AddProfileView.swift | 11 +++++++++++ .../App/Views/AddProviderView+Name.swift | 3 +++ Passepartout/App/Views/ProfileView+Rename.swift | 8 ++++++++ 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Passepartout/App/Views/AccountView.swift b/Passepartout/App/Views/AccountView.swift index 03f158278..b7d782e9c 100644 --- a/Passepartout/App/Views/AccountView.swift +++ b/Passepartout/App/Views/AccountView.swift @@ -27,6 +27,14 @@ import PassepartoutLibrary import SwiftUI struct AccountView: View { + enum Field { + case username + + case password + + case seed + } + @ObservedObject private var providerManager: ProviderManager private let providerName: ProviderName? @@ -41,6 +49,8 @@ struct AccountView: View { @State private var liveAccount = Profile.Account() + @FocusState private var focusedField: Field? + init( providerName: ProviderName?, vpnProtocol: VPNProtocolType, @@ -71,6 +81,7 @@ struct AccountView: View { TextField(usernamePlaceholder ?? L10n.Account.Items.Username.placeholder, text: $liveAccount.username) .textContentType(.username) .keyboardType(.emailAddress) + .focused($focusedField, equals: .username) .themeRawTextStyle() .withLeadingText(L10n.Account.Items.Username.caption) @@ -80,12 +91,14 @@ struct AccountView: View { EmptyView() } else { themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password) + .focused($focusedField, equals: .password) .withLeadingText(L10n.Account.Items.Password.caption) } - // TODO: interactive, scan QR code case .totp: + // TODO: interactive, scan QR code themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password, contentType: .oneTimeCode) + .focused($focusedField, equals: .seed) .withLeadingText(L10n.Account.Items.Seed.caption) } } footer: { @@ -111,6 +124,8 @@ struct AccountView: View { saveAnyway: saveAnyway, onSave: onSave ) + }.onAppear { + focusedField = .username }.navigationTitle(L10n.Account.title) } } diff --git a/Passepartout/App/Views/AddHostView+Name.swift b/Passepartout/App/Views/AddHostView+Name.swift index 61c514630..99b7298c0 100644 --- a/Passepartout/App/Views/AddHostView+Name.swift +++ b/Passepartout/App/Views/AddHostView+Name.swift @@ -42,6 +42,8 @@ extension AddHostView { @State private var isEnteringCredentials = false + @FocusState private var focusedField: AddProfileView.Field? + init( url: URL, deletingURLOnSuccess: Bool, @@ -75,7 +77,11 @@ extension AddHostView { isPresented: $viewModel.isAskingOverwrite, actions: alertOverwriteActions, message: alertOverwriteMessage - ).onAppear(perform: requestResourcePermissions) + ).onChange(of: viewModel.requiresPassphrase) { + if $0 { + focusedField = .passphrase + } + }.onAppear(perform: requestResourcePermissions) .onDisappear(perform: dropResourcePermissions) .navigationTitle(L10n.AddProfile.Shared.title) .themeSecondaryView() @@ -91,6 +97,7 @@ private extension AddHostView.NameView { var mainView: some View { AddProfileView.ProfileNameSection( profileName: $viewModel.profileName, + focusedField: $focusedField, errorMessage: viewModel.errorMessage ) { processProfile(replacingExisting: false) @@ -118,7 +125,7 @@ private extension AddHostView.NameView { Section { SecureField(L10n.AddProfile.Host.Sections.Encryption.footer, text: $viewModel.encryptionPassphrase) { processProfile(replacingExisting: false) - } + }.focused($focusedField, equals: .passphrase) } header: { Text(L10n.Global.Strings.encryption) } diff --git a/Passepartout/App/Views/AddProfileView.swift b/Passepartout/App/Views/AddProfileView.swift index c1f5abf24..98d15dae4 100644 --- a/Passepartout/App/Views/AddProfileView.swift +++ b/Passepartout/App/Views/AddProfileView.swift @@ -27,6 +27,12 @@ import PassepartoutLibrary import SwiftUI enum AddProfileView { + enum Field { + case name + + case passphrase + } + struct Bindings { @Binding var isPresented: Bool } @@ -34,6 +40,8 @@ enum AddProfileView { struct ProfileNameSection: View { @Binding var profileName: String + @FocusState.Binding var focusedField: Field? + let errorMessage: String? let onCommit: () -> Void @@ -41,11 +49,14 @@ enum AddProfileView { var body: some View { Section { TextField(L10n.Global.Placeholders.profileName, text: $profileName, onCommit: onCommit) + .focused($focusedField, equals: .name) .themeValidProfileName() } header: { Text(L10n.Global.Strings.name) } footer: { themeErrorMessage(errorMessage) + }.onAppear { + focusedField = .name } } } diff --git a/Passepartout/App/Views/AddProviderView+Name.swift b/Passepartout/App/Views/AddProviderView+Name.swift index af2a701fa..709475b98 100644 --- a/Passepartout/App/Views/AddProviderView+Name.swift +++ b/Passepartout/App/Views/AddProviderView+Name.swift @@ -40,6 +40,8 @@ extension AddProviderView { @State private var isEnteringCredentials = false + @FocusState private var focusedField: AddProfileView.Field? + init( profile: Binding, providerMetadata: ProviderMetadata, @@ -57,6 +59,7 @@ extension AddProviderView { List { AddProfileView.ProfileNameSection( profileName: $viewModel.profileName, + focusedField: $focusedField, errorMessage: viewModel.errorMessage ) { saveProfile(replacingExisting: false) diff --git a/Passepartout/App/Views/ProfileView+Rename.swift b/Passepartout/App/Views/ProfileView+Rename.swift index cec82799d..0f599ea73 100644 --- a/Passepartout/App/Views/ProfileView+Rename.swift +++ b/Passepartout/App/Views/ProfileView+Rename.swift @@ -28,6 +28,10 @@ import SwiftUI extension ProfileView { struct RenameView: View { + enum Field { + case name + } + @Environment(\.presentationMode) private var presentationMode @ObservedObject private var profileManager: ProfileManager @@ -38,6 +42,8 @@ extension ProfileView { @State private var isOverwritingExistingProfile = false + @FocusState private var focusedField: Field? + init(currentProfile: ObservableProfile) { profileManager = .shared self.currentProfile = currentProfile @@ -48,6 +54,7 @@ extension ProfileView { Section { TextField(L10n.Global.Placeholders.profileName, text: $newName, onCommit: commitRenaming) .themeValidProfileName() + .focused($focusedField, equals: .name) .onAppear(perform: loadCurrentName) } header: { Text(L10n.Profile.Alerts.Rename.title) @@ -96,6 +103,7 @@ private extension ProfileView.RenameView { private extension ProfileView.RenameView { func loadCurrentName() { newName = currentProfile.value.header.name + focusedField = .name } func commitRenaming() {