Merge pull request #25 from tootsuite/feature/invite
feat: add support for inviteEnabled instance
This commit is contained in:
commit
56b010a47d
|
@ -1,95 +0,0 @@
|
|||
{
|
||||
"common": {
|
||||
"alerts": {},
|
||||
"controls": {
|
||||
"actions": {
|
||||
"add": "Add",
|
||||
"remove": "Remove",
|
||||
"edit": "Edit",
|
||||
"save": "Save",
|
||||
"ok": "OK",
|
||||
"confirm": "Confirm",
|
||||
"continue": "Continue",
|
||||
"cancel": "Cancel",
|
||||
"take_photo": "Take photo",
|
||||
"save_photo": "Save photo",
|
||||
"sign_in": "Sign in",
|
||||
"sign_up": "Sign up",
|
||||
"see_more": "See More",
|
||||
"preview": "Preview",
|
||||
"open_in_safari": "Open in Safari"
|
||||
},
|
||||
"status": {
|
||||
"user_boosted": "%s boosted",
|
||||
"content_warning": "content warning",
|
||||
"show_post": "Show Post"
|
||||
},
|
||||
"timeline": {
|
||||
"load_more": "Load More"
|
||||
}
|
||||
},
|
||||
"countable": {
|
||||
"photo": {
|
||||
"single": "photo",
|
||||
"multiple": "photos"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scene": {
|
||||
"welcome": {
|
||||
"slogan": "Social networking\nback in your hands."
|
||||
},
|
||||
"server_picker": {
|
||||
"title": "Pick a Server,\nany server.",
|
||||
"Button": {
|
||||
"Category": {
|
||||
"All": "All"
|
||||
},
|
||||
"SeeLess": "See Less",
|
||||
"SeeMore": "See More"
|
||||
},
|
||||
"Label": {
|
||||
"Language": "LANGUAGE",
|
||||
"Users": "USERS",
|
||||
"Category": "CATEGORY"
|
||||
},
|
||||
"input": {
|
||||
"placeholder": "Find a server or join your own..."
|
||||
}
|
||||
},
|
||||
"register": {
|
||||
"title": "Tell us about you.",
|
||||
"input": {
|
||||
"username": {
|
||||
"placeholder": "username",
|
||||
"duplicate_prompt": "This username is taken."
|
||||
},
|
||||
"display_name": {
|
||||
"placeholder": "display name"
|
||||
},
|
||||
"email": {
|
||||
"placeholder": "email"
|
||||
},
|
||||
"password": {
|
||||
"placeholder": "password",
|
||||
"prompt": "Your password needs at least:",
|
||||
"prompt_eight_characters": "Eight characters"
|
||||
}
|
||||
}
|
||||
},
|
||||
"server_rules": {
|
||||
"title": "Some ground rules.",
|
||||
"subtitle": "These rules are set by the admins of %s.",
|
||||
"prompt": "By continuing, you're subject to the terms of service and privacy policy for %s.",
|
||||
"button": {
|
||||
"confirm": "I Agree"
|
||||
}
|
||||
},
|
||||
"home_timeline": {
|
||||
"title": "Home"
|
||||
},
|
||||
"public_timeline": {
|
||||
"title": "Public"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
"Common.Controls.Actions.Add" = "Add";
|
||||
"Common.Controls.Actions.Cancel" = "Cancel";
|
||||
"Common.Controls.Actions.Confirm" = "Confirm";
|
||||
"Common.Controls.Actions.Continue" = "Continue";
|
||||
"Common.Controls.Actions.Edit" = "Edit";
|
||||
"Common.Controls.Actions.Ok" = "OK";
|
||||
"Common.Controls.Actions.OpenInSafari" = "Open in Safari";
|
||||
"Common.Controls.Actions.Preview" = "Preview";
|
||||
"Common.Controls.Actions.Remove" = "Remove";
|
||||
"Common.Controls.Actions.Save" = "Save";
|
||||
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
||||
"Common.Controls.Actions.SeeMore" = "See More";
|
||||
"Common.Controls.Actions.SignIn" = "Sign in";
|
||||
"Common.Controls.Actions.SignUp" = "Sign up";
|
||||
"Common.Controls.Actions.TakePhoto" = "Take photo";
|
||||
"Common.Controls.Status.ContentWarning" = "content warning";
|
||||
"Common.Controls.Status.ShowPost" = "Show Post";
|
||||
"Common.Controls.Status.UserBoosted" = "%@ boosted";
|
||||
"Common.Controls.Timeline.LoadMore" = "Load More";
|
||||
"Common.Countable.Photo.Multiple" = "photos";
|
||||
"Common.Countable.Photo.Single" = "photo";
|
||||
"Scene.HomeTimeline.Title" = "Home";
|
||||
"Scene.PublicTimeline.Title" = "Public";
|
||||
"Scene.Register.Input.DisplayName.Placeholder" = "display name";
|
||||
"Scene.Register.Input.Email.Placeholder" = "email";
|
||||
"Scene.Register.Input.Password.Placeholder" = "password";
|
||||
"Scene.Register.Input.Password.Prompt" = "Your password needs at least:";
|
||||
"Scene.Register.Input.Password.PromptEightCharacters" = "Eight characters";
|
||||
"Scene.Register.Input.Username.DuplicatePrompt" = "This username is taken.";
|
||||
"Scene.Register.Input.Username.Placeholder" = "username";
|
||||
"Scene.Register.Title" = "Tell us about you.";
|
||||
"Scene.ServerPicker.Button.Category.All" = "All";
|
||||
"Scene.ServerPicker.Button.Seeless" = "See Less";
|
||||
"Scene.ServerPicker.Button.Seemore" = "See More";
|
||||
"Scene.ServerPicker.Input.Placeholder" = "Find a server or join your own...";
|
||||
"Scene.ServerPicker.Label.Category" = "CATEGORY";
|
||||
"Scene.ServerPicker.Label.Language" = "LANGUAGE";
|
||||
"Scene.ServerPicker.Label.Users" = "USERS";
|
||||
"Scene.ServerPicker.Title" = "Pick a Server,
|
||||
any server.";
|
||||
"Scene.ServerRules.Button.Confirm" = "I Agree";
|
||||
"Scene.ServerRules.Prompt" = "By continuing, you're subject to the terms of service and privacy policy for %@.";
|
||||
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||
"Scene.ServerRules.Title" = "Some ground rules.";
|
||||
"Scene.Welcome.Slogan" = "Social networking
|
||||
back in your hands.";
|
|
@ -84,6 +84,9 @@
|
|||
"placeholder": "password",
|
||||
"prompt": "Your password needs at least:",
|
||||
"prompt_eight_characters": "Eight characters"
|
||||
},
|
||||
"invite": {
|
||||
"registration_user_invite_request": "Why do you want to join?"
|
||||
}
|
||||
},
|
||||
"success": "Success",
|
||||
|
|
|
@ -141,6 +141,10 @@ internal enum L10n {
|
|||
/// email
|
||||
internal static let placeholder = L10n.tr("Localizable", "Scene.Register.Input.Email.Placeholder")
|
||||
}
|
||||
internal enum Invite {
|
||||
/// Why do you want to join?
|
||||
internal static let registrationUserInviteRequest = L10n.tr("Localizable", "Scene.Register.Input.Invite.RegistrationUserInviteRequest")
|
||||
}
|
||||
internal enum Password {
|
||||
/// password
|
||||
internal static let placeholder = L10n.tr("Localizable", "Scene.Register.Input.Password.Placeholder")
|
||||
|
|
|
@ -40,6 +40,7 @@ tap the link to confirm your account.";
|
|||
"Scene.Register.CheckEmail" = "Regsiter request sent. Please check your email.";
|
||||
"Scene.Register.Input.DisplayName.Placeholder" = "display name";
|
||||
"Scene.Register.Input.Email.Placeholder" = "email";
|
||||
"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Why do you want to join?";
|
||||
"Scene.Register.Input.Password.Placeholder" = "password";
|
||||
"Scene.Register.Input.Password.Prompt" = "Your password needs at least:";
|
||||
"Scene.Register.Input.Password.PromptEightCharacters" = "Eight characters";
|
||||
|
|
|
@ -97,7 +97,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||
textField.autocapitalizationType = .none
|
||||
textField.autocorrectionType = .no
|
||||
textField.backgroundColor = .white
|
||||
textField.textColor = .black
|
||||
textField.textColor = Asset.Colors.Label.secondary.color
|
||||
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Username.placeholder,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
|
@ -118,7 +118,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||
textField.autocapitalizationType = .none
|
||||
textField.autocorrectionType = .no
|
||||
textField.backgroundColor = .white
|
||||
textField.textColor = .black
|
||||
textField.textColor = Asset.Colors.Label.secondary.color
|
||||
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.DisplayName.placeholder,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
|
@ -135,7 +135,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||
textField.autocorrectionType = .no
|
||||
textField.keyboardType = .emailAddress
|
||||
textField.backgroundColor = .white
|
||||
textField.textColor = .black
|
||||
textField.textColor = Asset.Colors.Label.secondary.color
|
||||
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Email.placeholder,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
|
@ -159,7 +159,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||
textField.keyboardType = .asciiCapable
|
||||
textField.isSecureTextEntry = true
|
||||
textField.backgroundColor = .white
|
||||
textField.textColor = .black
|
||||
textField.textColor = Asset.Colors.Label.secondary.color
|
||||
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Password.placeholder,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
|
@ -170,6 +170,22 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||
return textField
|
||||
}()
|
||||
|
||||
lazy var inviteTextField: UITextField = {
|
||||
let textField = UITextField()
|
||||
textField.autocapitalizationType = .none
|
||||
textField.autocorrectionType = .no
|
||||
textField.backgroundColor = .white
|
||||
textField.textColor = Asset.Colors.Label.secondary.color
|
||||
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest,
|
||||
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)
|
||||
|
@ -226,7 +242,9 @@ extension MastodonRegisterViewController {
|
|||
stackView.addArrangedSubview(emailTextField)
|
||||
stackView.addArrangedSubview(passwordTextField)
|
||||
stackView.addArrangedSubview(passwordCheckLabel)
|
||||
|
||||
if self.viewModel.approvalRequired {
|
||||
stackView.addArrangedSubview(inviteTextField)
|
||||
}
|
||||
// scrollView
|
||||
view.addSubview(scrollView)
|
||||
NSLayoutConstraint.activate([
|
||||
|
@ -445,6 +463,31 @@ extension MastodonRegisterViewController {
|
|||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
if self.viewModel.approvalRequired {
|
||||
|
||||
inviteTextField.delegate = self
|
||||
NSLayoutConstraint.activate([
|
||||
inviteTextField.heightAnchor.constraint(equalToConstant: 50).priority(.defaultHigh)
|
||||
])
|
||||
|
||||
viewModel.inviteValidateState
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] validateState in
|
||||
guard let self = self else { return }
|
||||
self.setTextFieldValidAppearance(self.inviteTextField, validateState: validateState)
|
||||
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
NotificationCenter.default
|
||||
.publisher(for: UITextField.textDidChangeNotification, object: inviteTextField)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.viewModel.invite.value = self.inviteTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
|
@ -457,25 +500,6 @@ extension MastodonRegisterViewController {
|
|||
|
||||
extension MastodonRegisterViewController: UITextFieldDelegate {
|
||||
|
||||
// FIXME: keyboard listener trigger when switch between text fields. Maybe could remove it
|
||||
// func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
// // align to password label when overlap
|
||||
// if textField === passwordTextField,
|
||||
// KeyboardResponderService.shared.isShow.value,
|
||||
// KeyboardResponderService.shared.state.value == .dock
|
||||
// {
|
||||
// let endFrame = KeyboardResponderService.shared.willEndFrame.value
|
||||
// let contentFrame = scrollView.convert(signUpButton.frame, to: nil)
|
||||
// let padding = contentFrame.maxY - endFrame.minY
|
||||
// if padding > 0 {
|
||||
// let contentOffsetY = scrollView.contentOffset.y
|
||||
// DispatchQueue.main.async {
|
||||
// self.scrollView.setContentOffset(CGPoint(x: 0, y: contentOffsetY + padding + 16.0), animated: true)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
|
||||
|
@ -488,6 +512,8 @@ extension MastodonRegisterViewController: UITextFieldDelegate {
|
|||
viewModel.email.value = text
|
||||
case passwordTextField:
|
||||
viewModel.password.value = text
|
||||
case inviteTextField:
|
||||
viewModel.invite.value = text
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -542,7 +568,7 @@ extension MastodonRegisterViewController {
|
|||
}
|
||||
|
||||
let query = Mastodon.API.Account.RegisterQuery(
|
||||
reason: nil,
|
||||
reason: viewModel.invite.value,
|
||||
username: username,
|
||||
email: email,
|
||||
password: password,
|
||||
|
|
|
@ -11,7 +11,6 @@ import MastodonSDK
|
|||
import UIKit
|
||||
|
||||
final class MastodonRegisterViewModel {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
// input
|
||||
|
@ -24,20 +23,30 @@ final class MastodonRegisterViewModel {
|
|||
let displayName = CurrentValueSubject<String, Never>("")
|
||||
let email = CurrentValueSubject<String, Never>("")
|
||||
let password = CurrentValueSubject<String, Never>("")
|
||||
let invite = CurrentValueSubject<String, Never>("")
|
||||
|
||||
let isUsernameValidateDalay = CurrentValueSubject<Bool, Never>(true)
|
||||
let isDisplayNameValidateDalay = CurrentValueSubject<Bool, Never>(true)
|
||||
let isEmailValidateDalay = CurrentValueSubject<Bool, Never>(true)
|
||||
let isPasswordValidateDalay = CurrentValueSubject<Bool, Never>(true)
|
||||
let isInviteValidateDelay = CurrentValueSubject<Bool, Never>(true)
|
||||
let isRegistering = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
|
||||
// output
|
||||
lazy var approvalRequired: Bool = {
|
||||
if let approvalRequired = instance.approvalRequired {
|
||||
return approvalRequired
|
||||
}
|
||||
return false
|
||||
}()
|
||||
|
||||
let applicationAuthorization: Mastodon.API.OAuth.Authorization
|
||||
|
||||
let usernameValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
let displayNameValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
let emailValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
let passwordValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
let inviteValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
|
||||
let isAllValid = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
|
@ -67,7 +76,7 @@ final class MastodonRegisterViewModel {
|
|||
// 0-9 (isASCII && isNumber)
|
||||
// _ ("_")
|
||||
for char in username {
|
||||
guard char.isASCII, (char.isLetter || char.isNumber || char == "_") else {
|
||||
guard char.isASCII, char.isLetter || char.isNumber || char == "_" else {
|
||||
isValid = false
|
||||
break
|
||||
}
|
||||
|
@ -97,18 +106,34 @@ final class MastodonRegisterViewModel {
|
|||
}
|
||||
.assign(to: \.value, on: passwordValidateState)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
Publishers.CombineLatest4(
|
||||
if approvalRequired {
|
||||
invite
|
||||
.map { invite in
|
||||
guard !invite.isEmpty else { return .empty }
|
||||
return .valid
|
||||
}
|
||||
.assign(to: \.value, on: inviteValidateState)
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
let publisherOne = Publishers.CombineLatest4(
|
||||
usernameValidateState.eraseToAnyPublisher(),
|
||||
displayNameValidateState.eraseToAnyPublisher(),
|
||||
emailValidateState.eraseToAnyPublisher(),
|
||||
passwordValidateState.eraseToAnyPublisher()
|
||||
).map {
|
||||
$0.0 == .valid && $0.1 == .valid && $0.2 == .valid && $0.3 == .valid
|
||||
}
|
||||
|
||||
Publishers.CombineLatest(
|
||||
publisherOne,
|
||||
approvalRequired ? inviteValidateState.map {$0 == .valid}.eraseToAnyPublisher() : Just(true).eraseToAnyPublisher()
|
||||
)
|
||||
.map { $0.0 == .valid && $0.1 == .valid && $0.2 == .valid && $0.3 == .valid }
|
||||
.map {
|
||||
return $0 && $1
|
||||
}
|
||||
.assign(to: \.value, on: isAllValid)
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonRegisterViewModel {
|
||||
|
|
|
@ -45,7 +45,7 @@ extension Mastodon.Entity {
|
|||
case languages
|
||||
case registrations
|
||||
case approvalRequired = "approval_required"
|
||||
case invitesEnabled
|
||||
case invitesEnabled = "invites_enabled"
|
||||
case urls
|
||||
case statistics
|
||||
|
||||
|
|
|
@ -23,4 +23,4 @@ fi
|
|||
|
||||
#task4 clean temp file
|
||||
rm -rf ${SRCROOT}/Localization/StringsConvertor/output
|
||||
rm -rf ${SRCROOT}/Localization/StringsConvertor/intput
|
||||
rm -rf ${SRCROOT}/Localization/StringsConvertor/input
|
||||
|
|
Loading…
Reference in New Issue