chore: add Combine style valid logic

This commit is contained in:
sunxiaojian 2021-02-20 18:24:23 +08:00
parent a74ac3a41a
commit 243d3362e6
3 changed files with 117 additions and 74 deletions

View File

@ -34,7 +34,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency {
let largeTitleLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .largeTitle)
label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: UIFont.boldSystemFont(ofSize: 34))
label.textColor = Asset.Colors.Label.black.color
label.text = "Tell us about you."
return label
@ -60,6 +60,16 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency {
return button
}()
let plusIconBackground: UIImageView = {
let icon = UIImageView()
let boldFont = UIFont.systemFont(ofSize: 24)
let configuration = UIImage.SymbolConfiguration(font: boldFont)
let image = UIImage(systemName: "plus.circle", withConfiguration: configuration)
icon.image = image
icon.tintColor = .white
return icon
}()
let plusIcon: UIImageView = {
let icon = UIImageView()
let boldFont = UIFont.systemFont(ofSize: 24)
@ -246,6 +256,12 @@ extension MastodonRegisterViewController {
photoButton.centerXAnchor.constraint(equalTo: photoView.centerXAnchor),
photoButton.centerYAnchor.constraint(equalTo: photoView.centerYAnchor),
])
plusIconBackground.translatesAutoresizingMaskIntoConstraints = false
photoView.addSubview(plusIconBackground)
NSLayoutConstraint.activate([
plusIconBackground.trailingAnchor.constraint(equalTo: photoButton.trailingAnchor),
plusIconBackground.bottomAnchor.constraint(equalTo: photoButton.bottomAnchor),
])
plusIcon.translatesAutoresizingMaskIntoConstraints = false
photoView.addSubview(plusIcon)
NSLayoutConstraint.activate([
@ -309,7 +325,40 @@ extension MastodonRegisterViewController {
self.setTextFieldValidAppearance(self.usernameTextField, isValid: isValid)
}
.store(in: &disposeBag)
viewModel.isDisplaynameValid
.receive(on: DispatchQueue.main)
.sink { [weak self] isValid in
guard let self = self else { return }
self.setTextFieldValidAppearance(self.displayNameTextField, isValid: isValid)
}
.store(in: &disposeBag)
viewModel.isEmailValid
.receive(on: DispatchQueue.main)
.sink { [weak self] isValid in
guard let self = self else { return }
self.setTextFieldValidAppearance(self.emailTextField, isValid: isValid)
}
.store(in: &disposeBag)
viewModel.isPasswordValid
.receive(on: DispatchQueue.main)
.sink { [weak self] isValid in
guard let self = self else { return }
self.setTextFieldValidAppearance(self.passwordTextField, isValid: isValid)
}
.store(in: &disposeBag)
Publishers.CombineLatest4(
viewModel.isUsernameValid,
viewModel.isDisplaynameValid,
viewModel.isEmailValid,
viewModel.isPasswordValid
)
.receive(on: DispatchQueue.main)
.sink { [weak self] isUsernameValid, isDisplaynameValid, isEmailValid, isPasswordValid in
guard let self = self else { return }
self.signUpButton.isEnabled = isUsernameValid ?? false && isDisplaynameValid ?? false && isEmailValid ?? false && isPasswordValid ?? false
}
.store(in: &disposeBag)
viewModel.error
.compactMap { $0 }
@ -326,16 +375,13 @@ extension MastodonRegisterViewController {
)
}
.store(in: &disposeBag)
NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: passwordTextField)
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
guard let self = self else { return }
guard let text = self.passwordTextField.text else { return }
let validations = self.viewModel.validatePassword(text: text)
self.passwordCheckLabel.attributedText = self.viewModel.attributeStringForPassword(eightCharacters: validations.0, oneNumber: validations.1, oneSpecialCharacter: validations.2)
}
.store(in: &disposeBag)
@ -360,13 +406,13 @@ extension MastodonRegisterViewController: UITextFieldDelegate {
switch textField {
case usernameTextField:
viewModel.username.value = textField.text
default:
let valid = validateTextField(textField: textField)
if valid {
if validateAllTextField() {
signUpButton.isEnabled = true
}
}
case displayNameTextField:
viewModel.displayname.value = textField.text
case emailTextField:
viewModel.email.value = textField.text
case passwordTextField:
viewModel.password.value = textField.text
default: break
}
}
@ -374,60 +420,24 @@ extension MastodonRegisterViewController: UITextFieldDelegate {
// To apply Shadow
textField.layer.shadowOpacity = 1
textField.layer.shadowRadius = 2.0
textField.layer.shadowOffset = CGSize.zero // Use any CGSize
textField.layer.shadowOffset = CGSize.zero
textField.layer.shadowColor = color.cgColor
textField.layer.shadowPath = UIBezierPath(roundedRect: textField.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 2.0, height: 2.0)).cgPath
}
}
func validateUsername() -> Bool {
if usernameTextField.text?.count ?? 0 > 0 {
showShadowWithColor(color: Asset.Colors.TextField.successGreen.color, textField: usernameTextField)
return true
} else {
return false
}
}
func validateDisplayName() -> Bool {
if displayNameTextField.text?.count ?? 0 > 0 {
return true
} else {
return false
}
}
func validateEmail() -> Bool {
guard let email = emailTextField.text else {
return false
}
if !viewModel.isValidEmail(email) {
return false
}
return true
}
func validatePassword() -> Bool {
guard let password = passwordTextField.text else {
return false
}
let result = viewModel.validatePassword(text: password)
if !(result.0 && result.1 && result.2) {
return false
}
return true
}
func validateTextField(textField: UITextField) -> Bool {
signUpButton.isEnabled = false
var isvalid = false
// if textField == usernameTextField {
// isvalid = validateUsername()
// }
if textField == usernameTextField {
isvalid = viewModel.isUsernameValid.value ?? false
}
if textField == displayNameTextField {
isvalid = validateDisplayName()
isvalid = viewModel.isDisplaynameValid.value ?? false
}
if textField == emailTextField {
isvalid = validateEmail()
isvalid = viewModel.isEmailValid.value ?? false
}
if textField == passwordTextField {
isvalid = validatePassword()
isvalid = viewModel.isPasswordValid.value ?? false
}
if isvalid {
showShadowWithColor(color: Asset.Colors.TextField.successGreen.color, textField: textField)
@ -437,8 +447,9 @@ extension MastodonRegisterViewController: UITextFieldDelegate {
}
return isvalid
}
func validateAllTextField() -> Bool {
return validateUsername() && validateDisplayName() && validateEmail() && validatePassword()
return viewModel.isUsernameValid.value ?? false && viewModel.isDisplaynameValid.value ?? false && viewModel.isEmailValid.value ?? false && viewModel.isPasswordValid.value ?? false
}
private func setTextFieldValidAppearance(_ textField: UITextField, isValid: Bool?) {
@ -454,7 +465,6 @@ extension MastodonRegisterViewController: UITextFieldDelegate {
showShadowWithColor(color: Asset.Colors.lightDangerRed.color, textField: textField)
}
}
}
extension MastodonRegisterViewController {
@ -476,10 +486,9 @@ extension MastodonRegisterViewController {
let query = Mastodon.API.Account.RegisterQuery(
reason: nil,
username: usernameTextField.text!,
displayname: displayNameTextField.text!,
email: emailTextField.text!,
password: passwordTextField.text!,
username: viewModel.username.value!,
email: viewModel.email.value!,
password: viewModel.password.value!,
agreement: true, // TODO:
locale: "en" // TODO:
)

View File

@ -11,7 +11,6 @@ import MastodonSDK
import UIKit
final class MastodonRegisterViewModel {
var disposeBag = Set<AnyCancellable>()
// input
@ -19,10 +18,18 @@ final class MastodonRegisterViewModel {
let applicationToken: Mastodon.Entity.Token
let isRegistering = CurrentValueSubject<Bool, Never>(false)
let username = CurrentValueSubject<String?, Never>(nil)
let displayname = CurrentValueSubject<String?, Never>(nil)
let email = CurrentValueSubject<String?, Never>(nil)
let password = CurrentValueSubject<String?, Never>(nil)
// output
let applicationAuthorization: Mastodon.API.OAuth.Authorization
let isUsernameValid = CurrentValueSubject<Bool?, Never>(nil)
let isDisplaynameValid = CurrentValueSubject<Bool?, Never>(nil)
let isEmailValid = CurrentValueSubject<Bool?, Never>(nil)
let isPasswordValid = CurrentValueSubject<Bool?, Never>(nil)
let error = CurrentValueSubject<Error?, Never>(nil)
init(domain: String, applicationToken: Mastodon.Entity.Token) {
@ -39,15 +46,44 @@ final class MastodonRegisterViewModel {
}
.assign(to: \.value, on: isUsernameValid)
.store(in: &disposeBag)
displayname
.map { displayname in
guard let displayname = displayname else {
return nil
}
return !displayname.isEmpty
}
.assign(to: \.value, on: isDisplaynameValid)
.store(in: &disposeBag)
email
.map { [weak self] email in
guard let self = self else { return nil }
guard let email = email else {
return nil
}
return !email.isEmpty && self.isValidEmail(email)
}
.assign(to: \.value, on: isEmailValid)
.store(in: &disposeBag)
password
.map { [weak self] password in
guard let self = self else { return nil }
guard let password = password else {
return nil
}
let result = self.validatePassword(text: password)
return !password.isEmpty && result.0 && result.1 && result.2
}
.assign(to: \.value, on: isPasswordValid)
.store(in: &disposeBag)
}
}
extension MastodonRegisterViewModel {
func isValidEmail(_ email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
let emailPred = NSPredicate(format: "SELF MATCHES %@", emailRegEx)
return emailPred.evaluate(with: email)
}
@ -81,26 +117,26 @@ extension MastodonRegisterViewModel {
let start = NSAttributedString(string: "Your password needs at least:\n", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color])
attributeString.append(start)
attributeString.append(checkImage(color: eightCharacters ? color : falseColor))
attributeString.append(checkmarkImage(color: eightCharacters ? color : falseColor))
let eightCharactersDescription = NSAttributedString(string: "Eight characters\n", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color])
attributeString.append(eightCharactersDescription)
attributeString.append(checkImage(color: oneNumber ? color : falseColor))
attributeString.append(checkmarkImage(color: oneNumber ? color : falseColor))
let oneNumberDescription = NSAttributedString(string: "One number\n", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color])
attributeString.append(oneNumberDescription)
attributeString.append(checkImage(color: oneSpecialCharacter ? color : falseColor))
attributeString.append(checkmarkImage(color: oneSpecialCharacter ? color : falseColor))
let oneSpecialCharacterDescription = NSAttributedString(string: "One special character\n", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color])
attributeString.append(oneSpecialCharacterDescription)
return attributeString
}
func checkImage(color: UIColor) -> NSAttributedString {
let checkImage = NSTextAttachment()
func checkmarkImage(color: UIColor) -> NSAttributedString {
let checkmarkImage = NSTextAttachment()
let font = UIFont.preferredFont(forTextStyle: .caption1)
let configuration = UIImage.SymbolConfiguration(font: font)
checkImage.image = UIImage(systemName: "checkmark.circle.fill", withConfiguration: configuration)?.withTintColor(color)
return NSAttributedString(attachment: checkImage)
checkmarkImage.image = UIImage(systemName: "checkmark.circle.fill", withConfiguration: configuration)?.withTintColor(color)
return NSAttributedString(attachment: checkmarkImage)
}
}

View File

@ -161,16 +161,14 @@ extension Mastodon.API.Account {
public struct RegisterQuery: Codable, PostQuery {
public let reason: String?
public let username: String
public let displayname: String
public let email: String
public let password: String
public let agreement: Bool
public let locale: String
public init(reason: String? = nil, username: String, displayname: String, email: String, password: String, agreement: Bool, locale: String) {
public init(reason: String? = nil, username: String, email: String, password: String, agreement: Bool, locale: String) {
self.reason = reason
self.username = username
self.displayname = displayname
self.email = email
self.password = password
self.agreement = agreement