From 1a4b8f3e819dc10831a0316771880dac7147c011 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Nov 2022 09:35:19 +0800 Subject: [PATCH 1/4] Add a button to show/hide the text field input for a secure field. --- .../AccountCreationFormFieldView.swift | 46 +++++++++++++++++-- .../SwiftUI Components/TextFieldStyles.swift | 16 ++++++- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift index dad68c9bd49..259198dc0a2 100644 --- a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift +++ b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift @@ -22,8 +22,16 @@ struct AccountCreationFormFieldViewModel { struct AccountCreationFormFieldView: View { private let viewModel: AccountCreationFormFieldViewModel + /// Whether the text field is *shown* as secure. + /// When the field is secure, there is a button to show/hide the text field input. + @State private var isTextFieldSecure: Bool = true + + // Tracks the scale of the view due to accessibility changes. + @ScaledMetric private var scale: CGFloat = 1.0 + init(viewModel: AccountCreationFormFieldViewModel) { self.viewModel = viewModel + self.isTextFieldSecure = viewModel.isSecure } var body: some View { @@ -31,9 +39,38 @@ struct AccountCreationFormFieldView: View { Text(viewModel.header) .subheadlineStyle() if viewModel.isSecure { - SecureField(viewModel.placeholder, text: viewModel.text) - .textFieldStyle(RoundedBorderTextFieldStyle(focused: viewModel.isFocused)) + ZStack(alignment: .trailing) { + // Text field based on the `isTextFieldSecure` state. + Group { + if isTextFieldSecure { + SecureField(viewModel.placeholder, text: viewModel.text) + } else { + TextField(viewModel.placeholder, text: viewModel.text) + } + } + .font(.body) + .textFieldStyle(RoundedBorderTextFieldStyle( + focused: viewModel.isFocused, + // Custom insets to leave trailing space for the reveal button. + insets: .init(top: RoundedBorderTextFieldStyle.Defaults.insets.top, + leading: RoundedBorderTextFieldStyle.Defaults.insets.leading, + bottom: RoundedBorderTextFieldStyle.Defaults.insets.bottom, + trailing: Layout.secureFieldRevealButtonHorizontalPadding * 2 + Layout.secureFieldRevealButtonDimension * scale)) + ) .keyboardType(viewModel.keyboardType) + + // Button to show/hide the text field content. + Button(action: { + isTextFieldSecure.toggle() + }) { + Image(systemName: isTextFieldSecure ? "eye.slash" : "eye") + .accentColor(.gray) + .frame(width: Layout.secureFieldRevealButtonDimension * scale, + height: Layout.secureFieldRevealButtonDimension * scale) + .padding(.leading, Layout.secureFieldRevealButtonHorizontalPadding) + .padding(.trailing, Layout.secureFieldRevealButtonHorizontalPadding) + } + } } else { TextField(viewModel.placeholder, text: viewModel.text) .textFieldStyle(RoundedBorderTextFieldStyle(focused: viewModel.isFocused)) @@ -50,6 +87,8 @@ struct AccountCreationFormFieldView: View { private extension AccountCreationFormFieldView { enum Layout { static let verticalSpacing: CGFloat = 8 + static let secureFieldRevealButtonHorizontalPadding: CGFloat = 16 + static let secureFieldRevealButtonDimension: CGFloat = 18 } } @@ -65,9 +104,10 @@ struct AccountCreationFormField_Previews: PreviewProvider { AccountCreationFormFieldView(viewModel: .init(header: "Choose a password", placeholder: "Password", keyboardType: .default, - text: .constant("w"), + text: .constant("wwwwwwwwwwwwwwwwwwwwwwww"), isSecure: true, errorMessage: "Too simple", isFocused: false)) + .environment(\.sizeCategory, .extraExtraExtraLarge) } } diff --git a/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift b/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift index 8a53afbb502..e55b004838d 100644 --- a/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift +++ b/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift @@ -2,11 +2,17 @@ import SwiftUI /// Text field has a rounded border that has a thicker border and brighter border color when the field is focused. struct RoundedBorderTextFieldStyle: TextFieldStyle { - let focused: Bool + private let focused: Bool + private let insets: EdgeInsets + + init(focused: Bool, insets: EdgeInsets = Defaults.insets) { + self.focused = focused + self.insets = insets + } func _body(configuration: TextField) -> some View { configuration - .padding(10) + .padding(insets) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) .stroke(focused ? Color(.brand): Color.gray, @@ -14,3 +20,9 @@ struct RoundedBorderTextFieldStyle: TextFieldStyle { ) } } + +extension RoundedBorderTextFieldStyle { + enum Defaults { + static let insets = EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10) + } +} From 4ef59bc0527bd045b52600b27013c6ae176997df Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Nov 2022 09:42:01 +0800 Subject: [PATCH 2/4] Rename `isTextFieldSecure` to `showsSecureInput` for better readability. --- .../Authentication/AccountCreationFormFieldView.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift index 259198dc0a2..99c4b9da65d 100644 --- a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift +++ b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift @@ -24,14 +24,14 @@ struct AccountCreationFormFieldView: View { /// Whether the text field is *shown* as secure. /// When the field is secure, there is a button to show/hide the text field input. - @State private var isTextFieldSecure: Bool = true + @State private var showsSecureInput: Bool = true // Tracks the scale of the view due to accessibility changes. @ScaledMetric private var scale: CGFloat = 1.0 init(viewModel: AccountCreationFormFieldViewModel) { self.viewModel = viewModel - self.isTextFieldSecure = viewModel.isSecure + self.showsSecureInput = viewModel.isSecure } var body: some View { @@ -42,7 +42,7 @@ struct AccountCreationFormFieldView: View { ZStack(alignment: .trailing) { // Text field based on the `isTextFieldSecure` state. Group { - if isTextFieldSecure { + if showsSecureInput { SecureField(viewModel.placeholder, text: viewModel.text) } else { TextField(viewModel.placeholder, text: viewModel.text) @@ -61,9 +61,9 @@ struct AccountCreationFormFieldView: View { // Button to show/hide the text field content. Button(action: { - isTextFieldSecure.toggle() + showsSecureInput.toggle() }) { - Image(systemName: isTextFieldSecure ? "eye.slash" : "eye") + Image(systemName: showsSecureInput ? "eye.slash" : "eye") .accentColor(.gray) .frame(width: Layout.secureFieldRevealButtonDimension * scale, height: Layout.secureFieldRevealButtonDimension * scale) From 82b75ba6976b4091ad8474768a314fbc0667aef5 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Nov 2022 09:48:45 +0800 Subject: [PATCH 3/4] Update image color to textSubtle. --- .../Authentication/AccountCreationFormFieldView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift index 99c4b9da65d..b9d0fc0e656 100644 --- a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift +++ b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift @@ -64,7 +64,7 @@ struct AccountCreationFormFieldView: View { showsSecureInput.toggle() }) { Image(systemName: showsSecureInput ? "eye.slash" : "eye") - .accentColor(.gray) + .accentColor(Color(.textSubtle)) .frame(width: Layout.secureFieldRevealButtonDimension * scale, height: Layout.secureFieldRevealButtonDimension * scale) .padding(.leading, Layout.secureFieldRevealButtonHorizontalPadding) From a09e7e3911290142d76b25ab59b9ae9b8c499c09 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Nov 2022 13:27:06 +0800 Subject: [PATCH 4/4] Add an optional height parameter to `RoundedBorderTextFieldStyle` so that the password field has the same height when the secure content is shown and hidden. --- .../AccountCreationFormFieldView.swift | 32 +++++++++++++------ .../SwiftUI Components/TextFieldStyles.swift | 27 +++++++++++++++- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift index b9d0fc0e656..b35a6be738d 100644 --- a/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift +++ b/WooCommerce/Classes/ViewRelated/Authentication/AccountCreationFormFieldView.swift @@ -55,8 +55,9 @@ struct AccountCreationFormFieldView: View { insets: .init(top: RoundedBorderTextFieldStyle.Defaults.insets.top, leading: RoundedBorderTextFieldStyle.Defaults.insets.leading, bottom: RoundedBorderTextFieldStyle.Defaults.insets.bottom, - trailing: Layout.secureFieldRevealButtonHorizontalPadding * 2 + Layout.secureFieldRevealButtonDimension * scale)) - ) + trailing: Layout.secureFieldRevealButtonHorizontalPadding * 2 + Layout.secureFieldRevealButtonDimension * scale), + height: 44 * scale + )) .keyboardType(viewModel.keyboardType) // Button to show/hide the text field content. @@ -101,13 +102,24 @@ struct AccountCreationFormField_Previews: PreviewProvider { isSecure: false, errorMessage: nil, isFocused: true)) - AccountCreationFormFieldView(viewModel: .init(header: "Choose a password", - placeholder: "Password", - keyboardType: .default, - text: .constant("wwwwwwwwwwwwwwwwwwwwwwww"), - isSecure: true, - errorMessage: "Too simple", - isFocused: false)) - .environment(\.sizeCategory, .extraExtraExtraLarge) + VStack { + AccountCreationFormFieldView(viewModel: .init(header: "Choose a password", + placeholder: "Password", + keyboardType: .default, + text: .constant("wwwwwwwwwwwwwwwwwwwwwwww"), + isSecure: true, + errorMessage: "Too simple", + isFocused: false)) + .environment(\.sizeCategory, .medium) + + AccountCreationFormFieldView(viewModel: .init(header: "Choose a password", + placeholder: "Password", + keyboardType: .default, + text: .constant("wwwwwwwwwwwwwwwwwwwwwwww"), + isSecure: true, + errorMessage: "Too simple", + isFocused: false)) + .environment(\.sizeCategory, .extraExtraExtraLarge) + } } } diff --git a/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift b/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift index b8d7afae153..7ff2f8a6f4e 100644 --- a/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift +++ b/WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TextFieldStyles.swift @@ -6,15 +6,24 @@ struct RoundedBorderTextFieldStyle: TextFieldStyle { private let focusedBorderColor: Color private let unfocusedBorderColor: Color private let insets: EdgeInsets + private let height: CGFloat? + /// - Parameters: + /// - focused: Whether the field is focused or not. + /// - focusedBorderColor: The border color when the field is focused. + /// - unfocusedBorderColor: The border color when the field is not focused. + /// - insets: The insets between the background border and the text input. + /// - height: An optional fixed height for the field. init(focused: Bool, focusedBorderColor: Color = Defaults.focusedBorderColor, unfocusedBorderColor: Color = Defaults.unfocusedBorderColor, - insets: EdgeInsets = Defaults.insets) { + insets: EdgeInsets = Defaults.insets, + height: CGFloat? = nil) { self.focused = focused self.focusedBorderColor = focusedBorderColor self.unfocusedBorderColor = unfocusedBorderColor self.insets = insets + self.height = height } func _body(configuration: TextField) -> some View { @@ -24,7 +33,9 @@ struct RoundedBorderTextFieldStyle: TextFieldStyle { RoundedRectangle(cornerRadius: 8, style: .continuous) .stroke(focused ? focusedBorderColor: unfocusedBorderColor, lineWidth: focused ? 2: 1) + .frame(height: height) ) + .frame(height: height) } } @@ -51,6 +62,20 @@ struct TextFieldStyles_Previews: PreviewProvider { TextField("placeholder", text: .constant("custom insets")) .textFieldStyle(RoundedBorderTextFieldStyle(focused: false, insets: .init(top: 20, leading: 0, bottom: 10, trailing: 50))) .frame(width: 150) + HStack { + TextField("placeholder", text: .constant("text field")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: true)) + SecureField("placeholder", text: .constant("secure")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: true)) + } + .environment(\.sizeCategory, .extraExtraExtraLarge) + HStack { + TextField("placeholder", text: .constant("text field")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: true, height: 100)) + SecureField("placeholder", text: .constant("secure")) + .textFieldStyle(RoundedBorderTextFieldStyle(focused: true)) + } + .environment(\.sizeCategory, .extraExtraExtraLarge) } .preferredColorScheme(.dark) }