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
This commit is contained in:
Davide De Rosa 2023-07-23 13:28:47 +02:00 committed by GitHub
parent 6ede6f052a
commit 34f6738b69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 47 additions and 3 deletions

View File

@ -27,6 +27,14 @@ import PassepartoutLibrary
import SwiftUI import SwiftUI
struct AccountView: View { struct AccountView: View {
enum Field {
case username
case password
case seed
}
@ObservedObject private var providerManager: ProviderManager @ObservedObject private var providerManager: ProviderManager
private let providerName: ProviderName? private let providerName: ProviderName?
@ -41,6 +49,8 @@ struct AccountView: View {
@State private var liveAccount = Profile.Account() @State private var liveAccount = Profile.Account()
@FocusState private var focusedField: Field?
init( init(
providerName: ProviderName?, providerName: ProviderName?,
vpnProtocol: VPNProtocolType, vpnProtocol: VPNProtocolType,
@ -71,6 +81,7 @@ struct AccountView: View {
TextField(usernamePlaceholder ?? L10n.Account.Items.Username.placeholder, text: $liveAccount.username) TextField(usernamePlaceholder ?? L10n.Account.Items.Username.placeholder, text: $liveAccount.username)
.textContentType(.username) .textContentType(.username)
.keyboardType(.emailAddress) .keyboardType(.emailAddress)
.focused($focusedField, equals: .username)
.themeRawTextStyle() .themeRawTextStyle()
.withLeadingText(L10n.Account.Items.Username.caption) .withLeadingText(L10n.Account.Items.Username.caption)
@ -80,12 +91,14 @@ struct AccountView: View {
EmptyView() EmptyView()
} else { } else {
themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password) themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password)
.focused($focusedField, equals: .password)
.withLeadingText(L10n.Account.Items.Password.caption) .withLeadingText(L10n.Account.Items.Password.caption)
} }
// TODO: interactive, scan QR code
case .totp: case .totp:
// TODO: interactive, scan QR code
themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password, contentType: .oneTimeCode) themeSecureField(L10n.Account.Items.Password.placeholder, text: $liveAccount.password, contentType: .oneTimeCode)
.focused($focusedField, equals: .seed)
.withLeadingText(L10n.Account.Items.Seed.caption) .withLeadingText(L10n.Account.Items.Seed.caption)
} }
} footer: { } footer: {
@ -111,6 +124,8 @@ struct AccountView: View {
saveAnyway: saveAnyway, saveAnyway: saveAnyway,
onSave: onSave onSave: onSave
) )
}.onAppear {
focusedField = .username
}.navigationTitle(L10n.Account.title) }.navigationTitle(L10n.Account.title)
} }
} }

View File

@ -42,6 +42,8 @@ extension AddHostView {
@State private var isEnteringCredentials = false @State private var isEnteringCredentials = false
@FocusState private var focusedField: AddProfileView.Field?
init( init(
url: URL, url: URL,
deletingURLOnSuccess: Bool, deletingURLOnSuccess: Bool,
@ -75,7 +77,11 @@ extension AddHostView {
isPresented: $viewModel.isAskingOverwrite, isPresented: $viewModel.isAskingOverwrite,
actions: alertOverwriteActions, actions: alertOverwriteActions,
message: alertOverwriteMessage message: alertOverwriteMessage
).onAppear(perform: requestResourcePermissions) ).onChange(of: viewModel.requiresPassphrase) {
if $0 {
focusedField = .passphrase
}
}.onAppear(perform: requestResourcePermissions)
.onDisappear(perform: dropResourcePermissions) .onDisappear(perform: dropResourcePermissions)
.navigationTitle(L10n.AddProfile.Shared.title) .navigationTitle(L10n.AddProfile.Shared.title)
.themeSecondaryView() .themeSecondaryView()
@ -91,6 +97,7 @@ private extension AddHostView.NameView {
var mainView: some View { var mainView: some View {
AddProfileView.ProfileNameSection( AddProfileView.ProfileNameSection(
profileName: $viewModel.profileName, profileName: $viewModel.profileName,
focusedField: $focusedField,
errorMessage: viewModel.errorMessage errorMessage: viewModel.errorMessage
) { ) {
processProfile(replacingExisting: false) processProfile(replacingExisting: false)
@ -118,7 +125,7 @@ private extension AddHostView.NameView {
Section { Section {
SecureField(L10n.AddProfile.Host.Sections.Encryption.footer, text: $viewModel.encryptionPassphrase) { SecureField(L10n.AddProfile.Host.Sections.Encryption.footer, text: $viewModel.encryptionPassphrase) {
processProfile(replacingExisting: false) processProfile(replacingExisting: false)
} }.focused($focusedField, equals: .passphrase)
} header: { } header: {
Text(L10n.Global.Strings.encryption) Text(L10n.Global.Strings.encryption)
} }

View File

@ -27,6 +27,12 @@ import PassepartoutLibrary
import SwiftUI import SwiftUI
enum AddProfileView { enum AddProfileView {
enum Field {
case name
case passphrase
}
struct Bindings { struct Bindings {
@Binding var isPresented: Bool @Binding var isPresented: Bool
} }
@ -34,6 +40,8 @@ enum AddProfileView {
struct ProfileNameSection: View { struct ProfileNameSection: View {
@Binding var profileName: String @Binding var profileName: String
@FocusState.Binding var focusedField: Field?
let errorMessage: String? let errorMessage: String?
let onCommit: () -> Void let onCommit: () -> Void
@ -41,11 +49,14 @@ enum AddProfileView {
var body: some View { var body: some View {
Section { Section {
TextField(L10n.Global.Placeholders.profileName, text: $profileName, onCommit: onCommit) TextField(L10n.Global.Placeholders.profileName, text: $profileName, onCommit: onCommit)
.focused($focusedField, equals: .name)
.themeValidProfileName() .themeValidProfileName()
} header: { } header: {
Text(L10n.Global.Strings.name) Text(L10n.Global.Strings.name)
} footer: { } footer: {
themeErrorMessage(errorMessage) themeErrorMessage(errorMessage)
}.onAppear {
focusedField = .name
} }
} }
} }

View File

@ -40,6 +40,8 @@ extension AddProviderView {
@State private var isEnteringCredentials = false @State private var isEnteringCredentials = false
@FocusState private var focusedField: AddProfileView.Field?
init( init(
profile: Binding<Profile>, profile: Binding<Profile>,
providerMetadata: ProviderMetadata, providerMetadata: ProviderMetadata,
@ -57,6 +59,7 @@ extension AddProviderView {
List { List {
AddProfileView.ProfileNameSection( AddProfileView.ProfileNameSection(
profileName: $viewModel.profileName, profileName: $viewModel.profileName,
focusedField: $focusedField,
errorMessage: viewModel.errorMessage errorMessage: viewModel.errorMessage
) { ) {
saveProfile(replacingExisting: false) saveProfile(replacingExisting: false)

View File

@ -28,6 +28,10 @@ import SwiftUI
extension ProfileView { extension ProfileView {
struct RenameView: View { struct RenameView: View {
enum Field {
case name
}
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
@ObservedObject private var profileManager: ProfileManager @ObservedObject private var profileManager: ProfileManager
@ -38,6 +42,8 @@ extension ProfileView {
@State private var isOverwritingExistingProfile = false @State private var isOverwritingExistingProfile = false
@FocusState private var focusedField: Field?
init(currentProfile: ObservableProfile) { init(currentProfile: ObservableProfile) {
profileManager = .shared profileManager = .shared
self.currentProfile = currentProfile self.currentProfile = currentProfile
@ -48,6 +54,7 @@ extension ProfileView {
Section { Section {
TextField(L10n.Global.Placeholders.profileName, text: $newName, onCommit: commitRenaming) TextField(L10n.Global.Placeholders.profileName, text: $newName, onCommit: commitRenaming)
.themeValidProfileName() .themeValidProfileName()
.focused($focusedField, equals: .name)
.onAppear(perform: loadCurrentName) .onAppear(perform: loadCurrentName)
} header: { } header: {
Text(L10n.Profile.Alerts.Rename.title) Text(L10n.Profile.Alerts.Rename.title)
@ -96,6 +103,7 @@ private extension ProfileView.RenameView {
private extension ProfileView.RenameView { private extension ProfileView.RenameView {
func loadCurrentName() { func loadCurrentName() {
newName = currentProfile.value.header.name newName = currentProfile.value.header.name
focusedField = .name
} }
func commitRenaming() { func commitRenaming() {