forked from zelo72/mastodon-ios
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 */; };
|
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1F625CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift */; };
|
||||||
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1FD25CD481700561493 /* StatusProvider.swift */; };
|
2D38F1FE25CD481700561493 /* StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1FD25CD481700561493 /* StatusProvider.swift */; };
|
||||||
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F20725CD491300561493 /* DisposeBagCollectable.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 */; };
|
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; };
|
||||||
2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonContent.swift */; };
|
2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonContent.swift */; };
|
||||||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestExpandedButton.swift; sourceTree = "<group>"; };
|
||||||
|
@ -815,6 +817,7 @@
|
||||||
children = (
|
children = (
|
||||||
DB084B5125CBC56300F898ED /* CoreDataStack */,
|
DB084B5125CBC56300F898ED /* CoreDataStack */,
|
||||||
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */,
|
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */,
|
||||||
|
2D3F9E0325DFA133004262D9 /* UITapGestureRecognizer.swift */,
|
||||||
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */,
|
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */,
|
||||||
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */,
|
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */,
|
||||||
2D46976325C2A71500CF4AA9 /* UIIamge.swift */,
|
2D46976325C2A71500CF4AA9 /* UIIamge.swift */,
|
||||||
|
@ -1243,6 +1246,7 @@
|
||||||
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */,
|
2D45E5BF25C9549700A6D639 /* PublicTimelineViewModel+State.swift in Sources */,
|
||||||
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */,
|
2D46976425C2A71500CF4AA9 /* UIIamge.swift in Sources */,
|
||||||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
|
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */,
|
||||||
|
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
||||||
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
|
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */,
|
||||||
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
|
2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */,
|
||||||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.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 primary = ColorAsset(name: "Colors/Label/primary")
|
||||||
internal static let secondary = ColorAsset(name: "Colors/Label/secondary")
|
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 lightAlertYellow = ColorAsset(name: "Colors/lightAlertYellow")
|
||||||
internal static let lightBackground = ColorAsset(name: "Colors/lightBackground")
|
internal static let lightBackground = ColorAsset(name: "Colors/lightBackground")
|
||||||
internal static let lightBrandBlue = ColorAsset(name: "Colors/lightBrandBlue")
|
internal static let lightBrandBlue = ColorAsset(name: "Colors/lightBrandBlue")
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0x84",
|
"blue" : "132",
|
||||||
"green" : "0x69",
|
"green" : "105",
|
||||||
"red" : "0x60"
|
"red" : "96"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"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.
|
// Created by MainasuK Cirno on 2021-2-5.
|
||||||
//
|
//
|
||||||
|
|
||||||
import os.log
|
|
||||||
import UIKit
|
|
||||||
import Combine
|
import Combine
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
import UITextField_Shake
|
import UITextField_Shake
|
||||||
|
|
||||||
final class MastodonRegisterViewController: UIViewController, NeedsDependency {
|
final class MastodonRegisterViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
@ -20,64 +19,151 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
var viewModel: MastodonRegisterViewModel!
|
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()
|
let label = UILabel()
|
||||||
label.font = .preferredFont(forTextStyle: .headline)
|
label.font = .preferredFont(forTextStyle: .headline)
|
||||||
label.textColor = Asset.Colors.Label.primary.color
|
label.textColor = Asset.Colors.Label.black.color
|
||||||
label.text = "Username:"
|
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let usernameTextField: UITextField = {
|
let usernameTextField: UITextField = {
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
textField.placeholder = "Username"
|
|
||||||
textField.autocapitalizationType = .none
|
textField.autocapitalizationType = .none
|
||||||
textField.autocorrectionType = .no
|
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
|
return textField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let emailLabel: UILabel = {
|
let usernameIsTakenLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = .preferredFont(forTextStyle: .headline)
|
|
||||||
label.textColor = Asset.Colors.Label.primary.color
|
|
||||||
label.text = "Email:"
|
|
||||||
return label
|
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 emailTextField: UITextField = {
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
textField.placeholder = "example@gmail.com"
|
|
||||||
textField.autocapitalizationType = .none
|
textField.autocapitalizationType = .none
|
||||||
textField.autocorrectionType = .no
|
textField.autocorrectionType = .no
|
||||||
textField.keyboardType = .emailAddress
|
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
|
return textField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let passwordLabel: UILabel = {
|
let passwordCheckLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = .preferredFont(forTextStyle: .headline)
|
label.numberOfLines = 4
|
||||||
label.textColor = Asset.Colors.Label.primary.color
|
|
||||||
label.text = "Password:"
|
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let passwordTextField: UITextField = {
|
let passwordTextField: UITextField = {
|
||||||
let textField = UITextField()
|
let textField = UITextField()
|
||||||
textField.placeholder = "Password"
|
|
||||||
textField.autocapitalizationType = .none
|
textField.autocapitalizationType = .none
|
||||||
textField.autocorrectionType = .no
|
textField.autocorrectionType = .no
|
||||||
textField.keyboardType = .asciiCapable
|
textField.keyboardType = .asciiCapable
|
||||||
textField.isSecureTextEntry = true
|
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
|
return textField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let signUpButton: UIButton = {
|
let signUpButton: UIButton = {
|
||||||
let button = UIButton(type: .system)
|
let button = UIButton(type: .system)
|
||||||
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
|
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.lightBrandBlue.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.lightDisabled.color), for: .disabled)
|
||||||
|
button.isEnabled = false
|
||||||
button.setTitleColor(Asset.Colors.Label.primary.color, for: .normal)
|
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.masksToBounds = true
|
||||||
button.layer.cornerRadius = 8
|
button.layer.cornerRadius = 8
|
||||||
button.layer.cornerCurve = .continuous
|
button.layer.cornerCurve = .continuous
|
||||||
|
@ -89,36 +175,97 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency {
|
||||||
activityIndicatorView.hidesWhenStopped = true
|
activityIndicatorView.hidesWhenStopped = true
|
||||||
return activityIndicatorView
|
return activityIndicatorView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MastodonRegisterViewController {
|
extension MastodonRegisterViewController {
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
title = "Sign Up"
|
navigationController?.navigationBar.isHidden = true
|
||||||
view.backgroundColor = Asset.Colors.Background.systemBackground.color
|
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()
|
let stackView = UIStackView()
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
stackView.axis = .vertical
|
||||||
view.addSubview(stackView)
|
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([
|
NSLayoutConstraint.activate([
|
||||||
stackView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 16),
|
scrollview.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
||||||
stackView.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
|
scrollview.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
|
||||||
stackView.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
|
view.trailingAnchor.constraint(equalTo: scrollview.frameLayoutGuide.trailingAnchor, constant: 20),
|
||||||
|
scrollview.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
stackView.axis = .vertical
|
// stackview
|
||||||
stackView.spacing = 8
|
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)
|
// photoview
|
||||||
stackView.addArrangedSubview(usernameTextField)
|
photoView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackView.addArrangedSubview(emailLabel)
|
photoView.addSubview(photoButton)
|
||||||
stackView.addArrangedSubview(emailTextField)
|
NSLayoutConstraint.activate([
|
||||||
stackView.addArrangedSubview(passwordLabel)
|
photoView.heightAnchor.constraint(equalToConstant: 90).priority(.defaultHigh),
|
||||||
stackView.addArrangedSubview(passwordTextField)
|
])
|
||||||
|
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
|
signUpButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackView.addArrangedSubview(signUpButton)
|
stackView.addArrangedSubview(signUpButton)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
@ -126,18 +273,31 @@ extension MastodonRegisterViewController {
|
||||||
])
|
])
|
||||||
|
|
||||||
signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(signUpActivityIndicatorView)
|
scrollview.addSubview(signUpActivityIndicatorView)
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor),
|
signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor),
|
||||||
signUpActivityIndicatorView.centerYAnchor.constraint(equalTo: signUpButton.centerYAnchor),
|
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
|
viewModel.isRegistering
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] isRegistering in
|
.sink { [weak self] isRegistering in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
isRegistering ? self.signUpActivityIndicatorView.startAnimating() : self.signUpActivityIndicatorView.stopAnimating()
|
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
|
self.signUpButton.isEnabled = !isRegistering
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
@ -157,28 +317,125 @@ extension MastodonRegisterViewController {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.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)
|
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 {
|
extension MastodonRegisterViewController {
|
||||||
|
@objc private func _resignFirstResponder() {
|
||||||
|
usernameTextField.resignFirstResponder()
|
||||||
|
displayNameTextField.resignFirstResponder()
|
||||||
|
emailTextField.resignFirstResponder()
|
||||||
|
passwordTextField.resignFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func signUpButtonPressed(_ sender: UIButton) {
|
@objc private func signUpButtonPressed(_ sender: UIButton) {
|
||||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
|
||||||
guard let username = usernameTextField.text else {
|
if !validateAllTextField() {
|
||||||
usernameTextField.shake()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let email = emailTextField.text else {
|
|
||||||
emailTextField.shake()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let password = passwordTextField.text else {
|
|
||||||
passwordTextField.shake()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,11 +444,12 @@ extension MastodonRegisterViewController {
|
||||||
|
|
||||||
let query = Mastodon.API.Account.RegisterQuery(
|
let query = Mastodon.API.Account.RegisterQuery(
|
||||||
reason: nil,
|
reason: nil,
|
||||||
username: username,
|
username: usernameTextField.text!,
|
||||||
email: email,
|
displayname: displayNameTextField.text!,
|
||||||
password: password,
|
email: emailTextField.text!,
|
||||||
agreement: true, // TODO:
|
password: passwordTextField.text!,
|
||||||
locale: "en" // TODO:
|
agreement: true, // TODO:
|
||||||
|
locale: "en" // TODO:
|
||||||
)
|
)
|
||||||
|
|
||||||
context.apiService.accountRegister(
|
context.apiService.accountRegister(
|
||||||
|
@ -211,7 +469,7 @@ extension MastodonRegisterViewController {
|
||||||
}
|
}
|
||||||
} receiveValue: { [weak self] response in
|
} receiveValue: { [weak self] response in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let _ = response.value
|
_ = response.value
|
||||||
// TODO:
|
// TODO:
|
||||||
let alertController = UIAlertController(title: "Success", message: "Regsiter request sent. Please check your email.\n(Auto sign in not implement yet.)", preferredStyle: .alert)
|
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
|
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))
|
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
|
||||||
}
|
}
|
||||||
.store(in: &disposeBag)
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
// Created by MainasuK Cirno on 2021-2-5.
|
// Created by MainasuK Cirno on 2021-2-5.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import Foundation
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
|
import UIKit
|
||||||
|
|
||||||
final class MastodonRegisterViewModel {
|
final class MastodonRegisterViewModel {
|
||||||
|
|
||||||
// input
|
// input
|
||||||
let domain: String
|
let domain: String
|
||||||
let applicationToken: Mastodon.Entity.Token
|
let applicationToken: Mastodon.Entity.Token
|
||||||
|
@ -25,5 +25,67 @@ final class MastodonRegisterViewModel {
|
||||||
self.applicationToken = applicationToken
|
self.applicationToken = applicationToken
|
||||||
self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken)
|
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 struct RegisterQuery: Codable, PostQuery {
|
||||||
public let reason: String?
|
public let reason: String?
|
||||||
public let username: String
|
public let username: String
|
||||||
|
public let displayname: String
|
||||||
public let email: String
|
public let email: String
|
||||||
public let password: String
|
public let password: String
|
||||||
public let agreement: Bool
|
public let agreement: Bool
|
||||||
public let locale: String
|
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.reason = reason
|
||||||
self.username = username
|
self.username = username
|
||||||
|
self.displayname = displayname
|
||||||
self.email = email
|
self.email = email
|
||||||
self.password = password
|
self.password = password
|
||||||
self.agreement = agreement
|
self.agreement = agreement
|
||||||
|
|
Loading…
Reference in New Issue