Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reveal password button and use a rounded checkbox #6268

Merged
merged 3 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "authentication_reveal_password.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Riot/Generated/Images.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ internal class Asset: NSObject {
internal static let authenticationEmailIcon = ImageAsset(name: "authentication_email_icon")
internal static let authenticationMsisdnIcon = ImageAsset(name: "authentication_msisdn_icon")
internal static let authenticationPasswordIcon = ImageAsset(name: "authentication_password_icon")
internal static let authenticationRevealPassword = ImageAsset(name: "authentication_reveal_password")
internal static let authenticationServerSelectionIcon = ImageAsset(name: "authentication_server_selection_icon")
internal static let authenticationSsoIconApple = ImageAsset(name: "authentication_sso_icon_apple")
internal static let authenticationSsoIconFacebook = ImageAsset(name: "authentication_sso_icon_facebook")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ struct AuthenticationTermsToggleStyle: ToggleStyle {

func makeBody(configuration: Configuration) -> some View {
Button { configuration.isOn.toggle() } label: {
Image(systemName: configuration.isOn ? "checkmark.square.fill" : "square")
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
.font(.title3.weight(.regular))
.imageScale(.large)
.foregroundColor(configuration.isOn ? theme.colors.accent : theme.colors.tertiaryContent)
.foregroundColor(theme.colors.accent)
}
.buttonStyle(.plain)
}
Expand Down
29 changes: 5 additions & 24 deletions RiotSwiftUI/Modules/Common/Util/ClearViewModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,14 @@

import SwiftUI

@available(iOS 14.0, *)
extension ThemableTextField {
/// Adds a clear button to the text field
/// - Parameters:
/// - show: A boolean that can be used to dynamically show/hide the button. Defaults to `true`.
/// - text: The text for the clear button to clear.
/// - alignment: The vertical alignment of the button in the text field. Default to `center`
@ViewBuilder
func showClearButton(_ show: Bool = true, text: Binding<String>, alignment: VerticalAlignment = .center) -> some View {
if show {
modifier(ClearViewModifier(alignment: alignment, text: text))
} else {
self
}
}
}

@available(iOS 14.0, *)
extension ThemableTextEditor {
func showClearButton(text: Binding<String>, alignment: VerticalAlignment = .top) -> some View {
return modifier(ClearViewModifier(alignment: alignment, text: text))
}
}

/// `ClearViewModifier` aims to add a clear button (e.g. `x` button) on the right side of any text editing view
@available(iOS 14.0, *)
struct ClearViewModifier: ViewModifier
{
struct ClearViewModifier: ViewModifier {
// MARK: - Properties

let alignment: VerticalAlignment
Expand All @@ -58,8 +38,7 @@ struct ClearViewModifier: ViewModifier

// MARK: - Public

public func body(content: Content) -> some View
{
public func body(content: Content) -> some View {
HStack(alignment: alignment) {
content
if !text.isEmpty {
Expand All @@ -70,7 +49,9 @@ struct ClearViewModifier: ViewModifier
.renderingMode(.template)
.foregroundColor(theme.colors.quarterlyContent)
}
.padding(EdgeInsets(top: alignment == .top ? 8 : 0, leading: 0, bottom: alignment == .bottom ? 8 : 0, trailing: 8))
.padding(.top, alignment == .top ? 8 : 0)
.padding(.bottom, alignment == .bottom ? 8 : 0)
.padding(.trailing, 12)
Copy link
Member Author

@pixlwave pixlwave Jun 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This trailing value aligns with Compound more closely

}
}
}
Expand Down
54 changes: 54 additions & 0 deletions RiotSwiftUI/Modules/Common/Util/PasswordButtonModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Copyright 2021 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import SwiftUI

/// Adds a reveal password button (e.g. an eye button) on the
/// right side of the view. For use with `ThemableTextField`.
struct PasswordButtonModifier: ViewModifier {

// MARK: - Properties

let text: String
@Binding var isSecureTextVisible: Bool
let alignment: VerticalAlignment

// MARK: - Private

@Environment(\.theme) private var theme: ThemeSwiftUI
@ScaledMetric private var iconSize = 16

// MARK: - Public

public func body(content: Content) -> some View {
HStack(alignment: .center) {
content

if !text.isEmpty {
Button { isSecureTextVisible.toggle() } label: {
Image(Asset.Images.authenticationRevealPassword.name)
.renderingMode(.template)
.resizable()
.frame(width: iconSize, height: iconSize)
.foregroundColor(theme.colors.secondaryContent)
}
.padding(.top, alignment == .top ? 8 : 0)
.padding(.bottom, alignment == .bottom ? 8 : 0)
.padding(.trailing, 12)
}
}
}
}
49 changes: 35 additions & 14 deletions RiotSwiftUI/Modules/Common/Util/RoundedBorderTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

import SwiftUI


@available(iOS 14.0, *)
struct RoundedBorderTextField: View {

// MARK: - Properties
Expand All @@ -30,14 +28,15 @@ struct RoundedBorderTextField: View {
var isFirstResponder = false

var configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration()
@State var isSecureTextVisible = false

var onTextChanged: ((String) -> Void)? = nil
var onEditingChanged: ((Bool) -> Void)? = nil
var onCommit: (() -> Void)? = nil

// MARK: Private

@State private var editing = false
@State private var isEditing = false

@Environment(\.theme) private var theme: ThemeSwiftUI
@Environment(\.isEnabled) private var isEnabled
Expand All @@ -51,7 +50,7 @@ struct RoundedBorderTextField: View {
.foregroundColor(theme.colors.primaryContent)
.font(theme.fonts.subheadline)
.multilineTextAlignment(.leading)
.padding(EdgeInsets(top: 0, leading: 0, bottom: 8, trailing: 0))
.padding(.bottom, 8)
}

ZStack(alignment: .leading) {
Expand All @@ -63,14 +62,17 @@ struct RoundedBorderTextField: View {
.accessibilityHidden(true)
}

ThemableTextField(placeholder: "", text: $text, configuration: configuration, onEditingChanged: { edit in
self.editing = edit
onEditingChanged?(edit)
}, onCommit: {
ThemableTextField(placeholder: "",
text: $text,
configuration: configuration,
isSecureTextVisible: $isSecureTextVisible) { isEditing in
self.isEditing = isEditing
onEditingChanged?(isEditing)
} onCommit: {
onCommit?()
})
}
.makeFirstResponder(isFirstResponder)
.showClearButton(isEnabled, text: $text)
.addButton(isEnabled)
.onChange(of: text) { newText in
onTextChanged?(newText)
}
Expand All @@ -81,25 +83,39 @@ struct RoundedBorderTextField: View {
}
.padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: text.isEmpty ? 8 : 0))
.background(RoundedRectangle(cornerRadius: 8).fill(theme.colors.background))
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(editing ? theme.colors.accent : (footerText != nil && isError ? theme.colors.alert : theme.colors.quinaryContent), lineWidth: editing || (footerText != nil && isError) ? 2 : 1))
.overlay(RoundedRectangle(cornerRadius: 8).stroke(borderColor, lineWidth: borderWidth))

if let footerText = self.footerText {
Text(footerText)
.foregroundColor(isError ? theme.colors.alert : theme.colors.tertiaryContent)
.font(theme.fonts.footnote)
.multilineTextAlignment(.leading)
.padding(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0))
.padding(.top, 8)
.transition(.opacity)
}
}
.animation(.easeOut(duration: 0.2))
}

/// The text field's border color.
private var borderColor: Color {
if isEditing {
return theme.colors.accent
} else if footerText != nil && isError {
return theme.colors.alert
} else {
return theme.colors.quinaryContent
}
}

/// The text field's border width.
private var borderWidth: CGFloat {
isEditing || (footerText != nil && isError) ? 2 : 1
}
}

// MARK: - Previews

@available(iOS 14.0, *)
struct TextFieldWithError_Previews: PreviewProvider {
static var previews: some View {

Expand All @@ -118,6 +134,11 @@ struct TextFieldWithError_Previews: PreviewProvider {
RoundedBorderTextField(title: "A title", placeHolder: "A placeholder", text: .constant("Some very long text used to check overlapping with the delete button"), footerText: "Some normal text", isError: false)
RoundedBorderTextField(title: "A title", placeHolder: "A placeholder", text: .constant("Some very long text used to check overlapping with the delete button"), footerText: "Some normal text", isError: false)
.disabled(true)

Spacer().frame(height: 0)

RoundedBorderTextField(title: "Password", placeHolder: "Enter your password", text: .constant(""), configuration: UIKitTextInputConfiguration(isSecureTextEntry: true))
RoundedBorderTextField(title: "Password", placeHolder: "Enter your password", text: .constant("password"), configuration: UIKitTextInputConfiguration(isSecureTextEntry: true))
}
}
}
25 changes: 22 additions & 3 deletions RiotSwiftUI/Modules/Common/Util/ThemableTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ struct UIKitTextInputConfiguration {
var autocorrectionType: UITextAutocorrectionType = .default
}

@available(iOS 14.0, *)
struct ThemableTextField: UIViewRepresentable {

// MARK: Properties

@State var placeholder: String?
@Binding var text: String
@State var configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration()
@Binding var isSecureTextVisible: Bool
var onEditingChanged: ((_ edit: Bool) -> Void)?
var onCommit: (() -> Void)?

Expand All @@ -47,11 +47,13 @@ struct ThemableTextField: UIViewRepresentable {
init(placeholder: String? = nil,
text: Binding<String>,
configuration: UIKitTextInputConfiguration = UIKitTextInputConfiguration(),
isSecureTextVisible: Binding<Bool> = .constant(false),
onEditingChanged: ((_ edit: Bool) -> Void)? = nil,
onCommit: (() -> Void)? = nil) {
self._text = text
self._placeholder = State(initialValue: placeholder)
self._configuration = State(initialValue: configuration)
self._isSecureTextVisible = isSecureTextVisible
self.onEditingChanged = onEditingChanged
self.onCommit = onCommit

Expand Down Expand Up @@ -89,7 +91,7 @@ struct ThemableTextField: UIViewRepresentable {

uiView.keyboardType = configuration.keyboardType
uiView.returnKeyType = configuration.returnKeyType
uiView.isSecureTextEntry = configuration.isSecureTextEntry
uiView.isSecureTextEntry = configuration.isSecureTextEntry ? !isSecureTextVisible : false
uiView.autocapitalizationType = configuration.autocapitalizationType
uiView.autocorrectionType = configuration.autocorrectionType
}
Expand Down Expand Up @@ -149,7 +151,6 @@ struct ThemableTextField: UIViewRepresentable {

// MARK: - modifiers

@available(iOS 14.0, *)
extension ThemableTextField {
func makeFirstResponder() -> ThemableTextField {
return makeFirstResponder(true)
Expand All @@ -159,4 +160,22 @@ extension ThemableTextField {
internalParams.isFirstResponder = isFirstResponder
return self
}

/// Adds a button button to the text field
/// - Parameters:
/// - show: A boolean that can be used to dynamically show/hide the button. Defaults to `true`.
/// - alignment: The vertical alignment of the button in the text field. Default to `center`
@ViewBuilder
func addButton(_ show: Bool, alignment: VerticalAlignment = .center) -> some View {
if show && configuration.isSecureTextEntry {
modifier(PasswordButtonModifier(text: text,
isSecureTextVisible: $isSecureTextVisible,
alignment: alignment))
} else if show {
modifier(ClearViewModifier(alignment: alignment,
text: $text))
} else {
self
}
}
}
1 change: 1 addition & 0 deletions changelog.d/pr-6268.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Authentication: Add reveal password button and use a rounded checkbox