feat: finish sign up page
This commit is contained in:
parent
c827d7630b
commit
8ef5a34a40
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -5,14 +5,13 @@
|
|||
// 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) } }
|
||||
|
@ -20,64 +19,151 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency {
|
|||
|
||||
var viewModel: MastodonRegisterViewModel!
|
||||
|
||||
let usernameLabel: UILabel = {
|
||||
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||
|
||||
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,
|
||||
])
|
||||
|
||||
stackView.addArrangedSubview(usernameLabel)
|
||||
stackView.addArrangedSubview(usernameTextField)
|
||||
stackView.addArrangedSubview(emailLabel)
|
||||
stackView.addArrangedSubview(emailTextField)
|
||||
stackView.addArrangedSubview(passwordLabel)
|
||||
stackView.addArrangedSubview(passwordTextField)
|
||||
// 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),
|
||||
])
|
||||
|
||||
// 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,9 +444,10 @@ extension MastodonRegisterViewController {
|
|||
|
||||
let query = Mastodon.API.Account.RegisterQuery(
|
||||
reason: nil,
|
||||
username: username,
|
||||
email: email,
|
||||
password: password,
|
||||
username: usernameTextField.text!,
|
||||
displayname: displayNameTextField.text!,
|
||||
email: emailTextField.text!,
|
||||
password: passwordTextField.text!,
|
||||
agreement: true, // TODO:
|
||||
locale: "en" // TODO:
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue