forked from zelo72/mastodon-ios
chore: add Combine style valid logic
This commit is contained in:
parent
a74ac3a41a
commit
243d3362e6
|
@ -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:
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue