feat: finish sign up page

This commit is contained in:
sunxiaojian 2021-02-20 13:55:06 +08:00
parent c827d7630b
commit 8ef5a34a40
9 changed files with 451 additions and 69 deletions

View File

@ -23,6 +23,7 @@
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1F625CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift */; };
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1FD25CD481700561493 /* StatusProvider.swift */; };
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */; };
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */; };
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; };
2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonContent.swift */; };
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; };
@ -196,6 +197,7 @@
2D38F1F625CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeTimelineViewModel+LoadOldestState.swift"; sourceTree = "<group>"; };
2D38F1FD25CD481700561493 /* StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusProvider.swift; sourceTree = "<group>"; };
2D38F20725CD491300561493 /* DisposeBagCollectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisposeBagCollectable.swift; sourceTree = "<group>"; };
2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITapGestureRecognizer.swift; sourceTree = "<group>"; };
2D42FF6A25C817D2004A627A /* MastodonContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonContent.swift; sourceTree = "<group>"; };
2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionToolBarContainer.swift; sourceTree = "<group>"; };
2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestExpandedButton.swift; sourceTree = "<group>"; };
@ -815,6 +817,7 @@
children = (
DB084B5125CBC56300F898ED /* CoreDataStack */,
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */,
2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */,
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */,
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */,
2D46976325C2A71500CF4AA9 /* UIIamge.swift */,
@ -1243,6 +1246,7 @@
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */,
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */,
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,

View File

@ -0,0 +1,26 @@
//
// UITapGestureRecognizer.swift
// Mastodon
//
// Created by sxiaojian on 2021/2/19.
//
import UIKit
extension UITapGestureRecognizer {
static var singleTapGestureRecognizer: UITapGestureRecognizer {
let tapGestureRecognizer = UITapGestureRecognizer()
tapGestureRecognizer.numberOfTapsRequired = 1
tapGestureRecognizer.numberOfTouchesRequired = 1
return tapGestureRecognizer
}
static var doubleTapGestureRecognizer: UITapGestureRecognizer {
let tapGestureRecognizer = UITapGestureRecognizer()
tapGestureRecognizer.numberOfTapsRequired = 2
tapGestureRecognizer.numberOfTouchesRequired = 1
return tapGestureRecognizer
}
}

View File

@ -44,6 +44,9 @@ internal enum Asset {
internal static let primary = ColorAsset(name: "Colors/Label/primary")
internal static let secondary = ColorAsset(name: "Colors/Label/secondary")
}
internal enum TextField {
internal static let successGreen = ColorAsset(name: "Colors/TextField/successGreen")
}
internal static let lightAlertYellow = ColorAsset(name: "Colors/lightAlertYellow")
internal static let lightBackground = ColorAsset(name: "Colors/lightBackground")
internal static let lightBrandBlue = ColorAsset(name: "Colors/lightBrandBlue")

View File

@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x84",
"green" : "0x69",
"red" : "0x60"
"blue" : "132",
"green" : "105",
"red" : "96"
}
},
"idiom" : "universal"

View File

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "89",
"green" : "199",
"red" : "52"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -5,79 +5,165 @@
// Created by MainasuK Cirno on 2021-2-5.
//
import os.log
import UIKit
import Combine
import MastodonSDK
import os.log
import UIKit
import UITextField_Shake
final class MastodonRegisterViewController: UIViewController, NeedsDependency {
var disposeBag = Set<AnyCancellable>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: MastodonRegisterViewModel!
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
let usernameLabel: UILabel = {
let stackViewTopDistance: CGFloat = 16
var keyboardFrame: CGRect!
var scrollview: UIScrollView = {
let scrollview = UIScrollView()
scrollview.showsVerticalScrollIndicator = false
scrollview.translatesAutoresizingMaskIntoConstraints = false
return scrollview
}()
let largeTitleLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .largeTitle)
label.textColor = Asset.Colors.Label.black.color
label.text = "Tell us about you."
return label
}()
let photoView: UIView = {
let view = UIView()
view.backgroundColor = .clear
return view
}()
let photoButton: UIButton = {
let button = UIButton(type: .custom)
let boldFont = UIFont.systemFont(ofSize: 42)
let configuration = UIImage.SymbolConfiguration(font: boldFont)
let image = UIImage(systemName: "person.fill.viewfinder", withConfiguration: configuration)
button.setImage(image?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate), for: UIControl.State.normal)
button.imageView?.tintColor = Asset.Colors.Icon.photo.color
button.backgroundColor = .white
button.layer.cornerRadius = 45
button.clipsToBounds = true
return button
}()
let plusIcon: UIImageView = {
let icon = UIImageView()
let boldFont = UIFont.systemFont(ofSize: 24)
let configuration = UIImage.SymbolConfiguration(font: boldFont)
let image = UIImage(systemName: "plus.circle.fill", withConfiguration: configuration)
icon.image = image
icon.tintColor = Asset.Colors.Icon.plus.color
return icon
}()
let domainLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .headline)
label.textColor = Asset.Colors.Label.primary.color
label.text = "Username:"
label.textColor = Asset.Colors.Label.black.color
return label
}()
let usernameTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Username"
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.backgroundColor = .white
textField.textColor = .black
textField.attributedPlaceholder = NSAttributedString(string: "username",
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
textField.leftView = paddingView
textField.leftViewMode = .always
return textField
}()
let emailLabel: UILabel = {
let usernameIsTakenLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .headline)
label.textColor = Asset.Colors.Label.primary.color
label.text = "Email:"
return label
}()
let displayNameTextField: UITextField = {
let textField = UITextField()
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.backgroundColor = .white
textField.textColor = .black
textField.attributedPlaceholder = NSAttributedString(string: "display name",
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
textField.leftView = paddingView
textField.leftViewMode = .always
return textField
}()
let emailTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "example@gmail.com"
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.keyboardType = .emailAddress
textField.backgroundColor = .white
textField.textColor = .black
textField.attributedPlaceholder = NSAttributedString(string: "email",
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
textField.leftView = paddingView
textField.leftViewMode = .always
return textField
}()
let passwordLabel: UILabel = {
let passwordCheckLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .headline)
label.textColor = Asset.Colors.Label.primary.color
label.text = "Password:"
label.numberOfLines = 4
return label
}()
let passwordTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Password"
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.keyboardType = .asciiCapable
textField.isSecureTextEntry = true
textField.backgroundColor = .white
textField.textColor = .black
textField.attributedPlaceholder = NSAttributedString(string: "password",
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
textField.leftView = paddingView
textField.leftViewMode = .always
return textField
}()
let signUpButton: UIButton = {
let button = UIButton(type: .system)
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Background.secondarySystemBackground.color), for: .normal)
button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Background.secondarySystemBackground.color.withAlphaComponent(0.8)), for: .disabled)
button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.lightBrandBlue.color), for: .normal)
button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.lightDisabled.color), for: .disabled)
button.isEnabled = false
button.setTitleColor(Asset.Colors.Label.primary.color, for: .normal)
button.setTitle("Sign up", for: .normal)
button.setTitle("Continue", for: .normal)
button.layer.masksToBounds = true
button.layer.cornerRadius = 8
button.layer.cornerCurve = .continuous
@ -89,36 +175,97 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency {
activityIndicatorView.hidesWhenStopped = true
return activityIndicatorView
}()
}
extension MastodonRegisterViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Sign Up"
view.backgroundColor = Asset.Colors.Background.systemBackground.color
navigationController?.navigationBar.isHidden = true
view.backgroundColor = Asset.Colors.Background.signUpSystemBackground.color
domainLabel.text = "@" + viewModel.domain + " "
domainLabel.sizeToFit()
passwordCheckLabel.attributedText = viewModel.attributeStringForPassword()
usernameTextField.rightView = domainLabel
usernameTextField.rightViewMode = .always
usernameTextField.delegate = self
displayNameTextField.delegate = self
emailTextField.delegate = self
passwordTextField.delegate = self
// gesture
view.addGestureRecognizer(tapGestureRecognizer)
tapGestureRecognizer.addTarget(self, action: #selector(_resignFirstResponder))
// stackview
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.axis = .vertical
stackView.distribution = .fill
stackView.spacing = 40
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 4)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.addArrangedSubview(largeTitleLabel)
stackView.addArrangedSubview(photoView)
stackView.addArrangedSubview(usernameTextField)
stackView.addArrangedSubview(displayNameTextField)
stackView.addArrangedSubview(emailTextField)
stackView.addArrangedSubview(passwordTextField)
stackView.addArrangedSubview(passwordCheckLabel)
// scrollview
view.addSubview(scrollview)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 16),
stackView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
scrollview.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
scrollview.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
view.trailingAnchor.constraint(equalTo: scrollview.frameLayoutGuide.trailingAnchor, constant: 20),
scrollview.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
])
stackView.axis = .vertical
stackView.spacing = 8
// stackview
scrollview.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
let bottomEdgeLayoutConstraint: NSLayoutConstraint = scrollview.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: scrollview.contentLayoutGuide.topAnchor, constant: stackViewTopDistance),
stackView.leadingAnchor.constraint(equalTo: scrollview.contentLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: scrollview.contentLayoutGuide.trailingAnchor),
stackView.widthAnchor.constraint(equalTo: scrollview.frameLayoutGuide.widthAnchor),
bottomEdgeLayoutConstraint,
])
// photoview
photoView.translatesAutoresizingMaskIntoConstraints = false
photoView.addSubview(photoButton)
NSLayoutConstraint.activate([
photoView.heightAnchor.constraint(equalToConstant: 90).priority(.defaultHigh),
])
photoButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
photoButton.heightAnchor.constraint(equalToConstant: 90).priority(.defaultHigh),
photoButton.widthAnchor.constraint(equalToConstant: 90).priority(.defaultHigh),
photoButton.centerXAnchor.constraint(equalTo: photoView.centerXAnchor),
photoButton.centerYAnchor.constraint(equalTo: photoView.centerYAnchor),
])
plusIcon.translatesAutoresizingMaskIntoConstraints = false
photoView.addSubview(plusIcon)
NSLayoutConstraint.activate([
plusIcon.trailingAnchor.constraint(equalTo: photoButton.trailingAnchor),
plusIcon.bottomAnchor.constraint(equalTo: photoButton.bottomAnchor),
])
stackView.addArrangedSubview(usernameLabel)
stackView.addArrangedSubview(usernameTextField)
stackView.addArrangedSubview(emailLabel)
stackView.addArrangedSubview(emailTextField)
stackView.addArrangedSubview(passwordLabel)
stackView.addArrangedSubview(passwordTextField)
// textfield
NSLayoutConstraint.activate([
usernameTextField.heightAnchor.constraint(equalToConstant: 50).priority(.defaultHigh),
displayNameTextField.heightAnchor.constraint(equalToConstant: 50).priority(.defaultHigh),
emailTextField.heightAnchor.constraint(equalToConstant: 50).priority(.defaultHigh),
passwordTextField.heightAnchor.constraint(equalToConstant: 50).priority(.defaultHigh),
])
// password
stackView.setCustomSpacing(6, after: passwordTextField)
stackView.setCustomSpacing(32, after: passwordCheckLabel)
// button
signUpButton.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(signUpButton)
NSLayoutConstraint.activate([
@ -126,18 +273,31 @@ extension MastodonRegisterViewController {
])
signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(signUpActivityIndicatorView)
scrollview.addSubview(signUpActivityIndicatorView)
NSLayoutConstraint.activate([
signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor),
signUpActivityIndicatorView.centerYAnchor.constraint(equalTo: signUpButton.centerYAnchor),
])
NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification, object: nil)
.sink { [weak self] notification in
guard let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
return
}
self?.keyboardFrame = endFrame
UIView.animate(withDuration: 0.3) {
bottomEdgeLayoutConstraint.constant = UIScreen.main.bounds.height - endFrame.origin.y + 26
self?.view.layoutIfNeeded()
}
}
.store(in: &disposeBag)
viewModel.isRegistering
.receive(on: DispatchQueue.main)
.sink { [weak self] isRegistering in
guard let self = self else { return }
isRegistering ? self.signUpActivityIndicatorView.startAnimating() : self.signUpActivityIndicatorView.stopAnimating()
self.signUpButton.setTitle(isRegistering ? "" : "Sign up", for: .normal)
self.signUpButton.setTitle(isRegistering ? "" : "Continue", for: .normal)
self.signUpButton.isEnabled = !isRegistering
}
.store(in: &disposeBag)
@ -157,28 +317,125 @@ 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)
signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside)
}
}
extension MastodonRegisterViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
var bottomOffsetY: CGFloat = textField.frame.origin.y + textField.frame.height - scrollview.frame.height + keyboardFrame.size.height + stackViewTopDistance
if textField == passwordTextField {
bottomOffsetY += passwordCheckLabel.frame.height
}
if bottomOffsetY > 0 {
scrollview.setContentOffset(CGPoint(x: 0, y: bottomOffsetY), animated: true)
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
let valid = validateTextField(textField: textField)
if valid {
if validateAllTextField() {
signUpButton.isEnabled = true
}
}
}
func showShadowWithColor(color: UIColor, textField: UITextField) {
// To apply Shadow
textField.layer.shadowOpacity = 1
textField.layer.shadowRadius = 2.0
textField.layer.shadowOffset = CGSize.zero // Use any CGSize
textField.layer.shadowColor = color.cgColor
}
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 == displayNameTextField {
isvalid = validateDisplayName()
}
if textField == emailTextField {
isvalid = validateEmail()
}
if textField == passwordTextField {
isvalid = validatePassword()
}
if isvalid {
showShadowWithColor(color: Asset.Colors.TextField.successGreen.color, textField: textField)
} else {
textField.shake()
showShadowWithColor(color: Asset.Colors.lightDangerRed.color, textField: textField)
}
return isvalid
}
func validateAllTextField() -> Bool {
return validateUsername() && validateDisplayName() && validateEmail() && validatePassword()
}
}
extension MastodonRegisterViewController {
@objc private func _resignFirstResponder() {
usernameTextField.resignFirstResponder()
displayNameTextField.resignFirstResponder()
emailTextField.resignFirstResponder()
passwordTextField.resignFirstResponder()
}
@objc private func signUpButtonPressed(_ sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard let username = usernameTextField.text else {
usernameTextField.shake()
return
}
guard let email = emailTextField.text else {
emailTextField.shake()
return
}
guard let password = passwordTextField.text else {
passwordTextField.shake()
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
if !validateAllTextField() {
return
}
@ -187,11 +444,12 @@ extension MastodonRegisterViewController {
let query = Mastodon.API.Account.RegisterQuery(
reason: nil,
username: username,
email: email,
password: password,
agreement: true, // TODO:
locale: "en" // TODO:
username: usernameTextField.text!,
displayname: displayNameTextField.text!,
email: emailTextField.text!,
password: passwordTextField.text!,
agreement: true, // TODO:
locale: "en" // TODO:
)
context.apiService.accountRegister(
@ -211,7 +469,7 @@ extension MastodonRegisterViewController {
}
} receiveValue: { [weak self] response in
guard let self = self else { return }
let _ = response.value
_ = response.value
// TODO:
let alertController = UIAlertController(title: "Success", message: "Regsiter request sent. Please check your email.\n(Auto sign in not implement yet.)", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] _ in
@ -222,7 +480,5 @@ extension MastodonRegisterViewController {
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
}
.store(in: &disposeBag)
}
}

View File

@ -5,12 +5,12 @@
// Created by MainasuK Cirno on 2021-2-5.
//
import Foundation
import Combine
import Foundation
import MastodonSDK
import UIKit
final class MastodonRegisterViewModel {
// input
let domain: String
let applicationToken: Mastodon.Entity.Token
@ -25,5 +25,67 @@ final class MastodonRegisterViewModel {
self.applicationToken = applicationToken
self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken)
}
}
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)
return emailPred.evaluate(with: email)
}
func validatePassword(text: String) -> (Bool, Bool, Bool) {
let trimmedText = text.trimmingCharacters(in: .whitespaces)
let isEightCharacters = trimmedText.count >= 8
let isOneNumber = trimmedText.range(of: ".*[0-9]", options: .regularExpression) != nil
let isOneSpecialCharacter = trimmedText.trimmingCharacters(in: .decimalDigits).trimmingCharacters(in: .letters).count > 0
return (isEightCharacters, isOneNumber, isOneSpecialCharacter)
}
func attributeStringForUsername() -> NSAttributedString {
let resultAttributeString = NSMutableAttributedString()
let redImage = NSTextAttachment()
let font = UIFont.preferredFont(forTextStyle: .caption1)
let configuration = UIImage.SymbolConfiguration(font: font)
redImage.image = UIImage(systemName: "xmark.octagon.fill", withConfiguration: configuration)?.withTintColor(Asset.Colors.lightDangerRed.color)
let imageAttribute = NSAttributedString(attachment: redImage)
let stringAttribute = NSAttributedString(string: "This username is taken.", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: Asset.Colors.lightDangerRed.color])
resultAttributeString.append(imageAttribute)
resultAttributeString.append(stringAttribute)
return resultAttributeString
}
func attributeStringForPassword(eightCharacters: Bool = false, oneNumber: Bool = false, oneSpecialCharacter: Bool = false) -> NSAttributedString {
let font = UIFont.preferredFont(forTextStyle: .caption1)
let color = UIColor.black
let falseColor = UIColor.clear
let attributeString = NSMutableAttributedString()
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))
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))
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))
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()
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)
}
}

View File

@ -161,14 +161,16 @@ 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, email: String, password: String, agreement: Bool, locale: String) {
public init(reason: String? = nil, username: String, displayname: 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