From 522702386cbbb9f1986288bb6b986e1d3ec4f78e Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Fri, 26 Feb 2021 12:52:37 +0800 Subject: [PATCH 1/2] feat: add support for inviteEnabled instance --- .../StringsConvertor/input/en_US/app.json | 95 ------------------- .../output/en.lproj/Localizable.strings | 46 --------- Localization/app.json | 3 + Mastodon/Generated/Strings.swift | 4 + .../Resources/en.lproj/Localizable.strings | 3 +- .../MastodonRegisterViewController.swift | 68 +++++++++---- .../Register/MastodonRegisterViewModel.swift | 50 ++++++++-- .../Entity/Mastodon+Entity+Instance.swift | 2 +- update_localization.sh | 2 +- 9 files changed, 98 insertions(+), 175 deletions(-) delete mode 100644 Localization/StringsConvertor/input/en_US/app.json delete mode 100644 Localization/StringsConvertor/output/en.lproj/Localizable.strings diff --git a/Localization/StringsConvertor/input/en_US/app.json b/Localization/StringsConvertor/input/en_US/app.json deleted file mode 100644 index c970f34f3..000000000 --- a/Localization/StringsConvertor/input/en_US/app.json +++ /dev/null @@ -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" - } - } -} diff --git a/Localization/StringsConvertor/output/en.lproj/Localizable.strings b/Localization/StringsConvertor/output/en.lproj/Localizable.strings deleted file mode 100644 index 9aee2d458..000000000 --- a/Localization/StringsConvertor/output/en.lproj/Localizable.strings +++ /dev/null @@ -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."; \ No newline at end of file diff --git a/Localization/app.json b/Localization/app.json index 538b7a763..ce5ba81e0 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -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", diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 88a7c0160..7223cfc73 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -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") diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 19180b69a..3902036cd 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -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"; @@ -61,4 +62,4 @@ any server."; "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."; +back in your hands."; \ No newline at end of file diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift index 074d5c762..7d92651a1 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift @@ -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 = .black + 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.inviteEnabled { + stackView.addArrangedSubview(inviteTextField) + } // scrollView view.addSubview(scrollView) NSLayoutConstraint.activate([ @@ -445,6 +463,31 @@ extension MastodonRegisterViewController { } .store(in: &disposeBag) + if self.viewModel.inviteEnabled { + + 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) } @@ -456,25 +499,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, diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift index a4d791954..298ecc565 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift @@ -11,7 +11,6 @@ import MastodonSDK import UIKit final class MastodonRegisterViewModel { - var disposeBag = Set() // input @@ -24,20 +23,30 @@ final class MastodonRegisterViewModel { let displayName = CurrentValueSubject("") let email = CurrentValueSubject("") let password = CurrentValueSubject("") + let invite = CurrentValueSubject("") + let isUsernameValidateDalay = CurrentValueSubject(true) let isDisplayNameValidateDalay = CurrentValueSubject(true) let isEmailValidateDalay = CurrentValueSubject(true) let isPasswordValidateDalay = CurrentValueSubject(true) + let isInviteValidateDelay = CurrentValueSubject(true) let isRegistering = CurrentValueSubject(false) - // output + lazy var inviteEnabled: Bool = { + if let inviteEnabled = instance.invitesEnabled { + return inviteEnabled + } + return false + }() + let applicationAuthorization: Mastodon.API.OAuth.Authorization let usernameValidateState = CurrentValueSubject(.empty) let displayNameValidateState = CurrentValueSubject(.empty) let emailValidateState = CurrentValueSubject(.empty) let passwordValidateState = CurrentValueSubject(.empty) + let inviteValidateState = CurrentValueSubject(.empty) let isAllValid = CurrentValueSubject(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,39 @@ final class MastodonRegisterViewModel { } .assign(to: \.value, on: passwordValidateState) .store(in: &disposeBag) - - Publishers.CombineLatest4( + if inviteEnabled { + invite + .map { invite in + guard !invite.isEmpty else { return .empty } + return .valid + } + .assign(to: \.value, on: inviteValidateState) + .store(in: &disposeBag) + } + let publisherOne = Publishers.CombineLatest( usernameValidateState.eraseToAnyPublisher(), - displayNameValidateState.eraseToAnyPublisher(), - emailValidateState.eraseToAnyPublisher(), - passwordValidateState.eraseToAnyPublisher() + displayNameValidateState.eraseToAnyPublisher() ) - .map { $0.0 == .valid && $0.1 == .valid && $0.2 == .valid && $0.3 == .valid } + let publisherTwo = Publishers.CombineLatest3( + emailValidateState.eraseToAnyPublisher(), + passwordValidateState.eraseToAnyPublisher(), + inviteValidateState.eraseToAnyPublisher() + ) + Publishers.CombineLatest( + publisherOne, + publisherTwo + ) + .map { [weak self] in + guard let self = self else { return false } + if self.inviteEnabled { + return $0.0.0 == .valid && $0.0.1 == .valid && $0.1.0 == .valid && $0.1.1 == .valid && $0.1.2 == .valid + } else { + return $0.0.0 == .valid && $0.0.1 == .valid && $0.1.0 == .valid && $0.1.1 == .valid + } + } .assign(to: \.value, on: isAllValid) .store(in: &disposeBag) } - } extension MastodonRegisterViewModel { diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift index 56984f842..18e41b3e0 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Instance.swift @@ -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 diff --git a/update_localization.sh b/update_localization.sh index b1c16c025..b531128b9 100755 --- a/update_localization.sh +++ b/update_localization.sh @@ -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 From 68d53652951d26d264802e9524dc9898f2475132 Mon Sep 17 00:00:00 2001 From: sunxiaojian Date: Fri, 26 Feb 2021 15:39:05 +0800 Subject: [PATCH 2/2] chore: make code clear --- .../MastodonRegisterViewController.swift | 14 ++++---- .../Register/MastodonRegisterViewModel.swift | 33 ++++++++----------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift index 7d92651a1..58415b5fa 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift @@ -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)]) @@ -175,7 +175,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.Invite.registrationUserInviteRequest, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) @@ -242,7 +242,7 @@ extension MastodonRegisterViewController { stackView.addArrangedSubview(emailTextField) stackView.addArrangedSubview(passwordTextField) stackView.addArrangedSubview(passwordCheckLabel) - if self.viewModel.inviteEnabled { + if self.viewModel.approvalRequired { stackView.addArrangedSubview(inviteTextField) } // scrollView @@ -463,7 +463,7 @@ extension MastodonRegisterViewController { } .store(in: &disposeBag) - if self.viewModel.inviteEnabled { + if self.viewModel.approvalRequired { inviteTextField.delegate = self NSLayoutConstraint.activate([ diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift index 298ecc565..9afa531f8 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift @@ -33,9 +33,9 @@ final class MastodonRegisterViewModel { let isRegistering = CurrentValueSubject(false) // output - lazy var inviteEnabled: Bool = { - if let inviteEnabled = instance.invitesEnabled { - return inviteEnabled + lazy var approvalRequired: Bool = { + if let approvalRequired = instance.approvalRequired { + return approvalRequired } return false }() @@ -106,7 +106,7 @@ final class MastodonRegisterViewModel { } .assign(to: \.value, on: passwordValidateState) .store(in: &disposeBag) - if inviteEnabled { + if approvalRequired { invite .map { invite in guard !invite.isEmpty else { return .empty } @@ -115,26 +115,21 @@ final class MastodonRegisterViewModel { .assign(to: \.value, on: inviteValidateState) .store(in: &disposeBag) } - let publisherOne = Publishers.CombineLatest( + let publisherOne = Publishers.CombineLatest4( usernameValidateState.eraseToAnyPublisher(), - displayNameValidateState.eraseToAnyPublisher() - ) - let publisherTwo = Publishers.CombineLatest3( + displayNameValidateState.eraseToAnyPublisher(), emailValidateState.eraseToAnyPublisher(), - passwordValidateState.eraseToAnyPublisher(), - inviteValidateState.eraseToAnyPublisher() - ) + passwordValidateState.eraseToAnyPublisher() + ).map { + $0.0 == .valid && $0.1 == .valid && $0.2 == .valid && $0.3 == .valid + } + Publishers.CombineLatest( publisherOne, - publisherTwo + approvalRequired ? inviteValidateState.map {$0 == .valid}.eraseToAnyPublisher() : Just(true).eraseToAnyPublisher() ) - .map { [weak self] in - guard let self = self else { return false } - if self.inviteEnabled { - return $0.0.0 == .valid && $0.0.1 == .valid && $0.1.0 == .valid && $0.1.1 == .valid && $0.1.2 == .valid - } else { - return $0.0.0 == .valid && $0.0.1 == .valid && $0.1.0 == .valid && $0.1.1 == .valid - } + .map { + return $0 && $1 } .assign(to: \.value, on: isAllValid) .store(in: &disposeBag)