diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift index 44939d6a..300cff5b 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift @@ -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: ) diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift index e5151364..590f2c0d 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift @@ -11,7 +11,6 @@ import MastodonSDK import UIKit final class MastodonRegisterViewModel { - var disposeBag = Set() // input @@ -19,10 +18,18 @@ final class MastodonRegisterViewModel { let applicationToken: Mastodon.Entity.Token let isRegistering = CurrentValueSubject(false) let username = CurrentValueSubject(nil) + let displayname = CurrentValueSubject(nil) + let email = CurrentValueSubject(nil) + let password = CurrentValueSubject(nil) // output let applicationAuthorization: Mastodon.API.OAuth.Authorization + let isUsernameValid = CurrentValueSubject(nil) + let isDisplaynameValid = CurrentValueSubject(nil) + let isEmailValid = CurrentValueSubject(nil) + let isPasswordValid = CurrentValueSubject(nil) + let error = CurrentValueSubject(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) } } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account.swift index 97659533..d9b2a444 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Account.swift @@ -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